We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
作为该库的忠实用户,首先感谢作者无私的奉献,继续开发ssr给我们广大用户使用,因为偶然的原因, 我更新了一直使用的ssr,更新完之后发现, 突然不能使用了,为了找到其中的原因就调试了一下该库,现在把我的具体调试过程,以及如何发现漏洞的简单介绍一下。 希望大家能跟作者共同努力,把ssr做的更加安全,稳定。
ssr
版本:使用master分支最新commit: 2bea1e1fadadd85497632d20e36e6d1bc55f121e
master
commit
2bea1e1fadadd85497632d20e36e6d1bc55f121e
系统
服务器:ubuntu 16.04 x86_64 客户端:macos/windows ssr
服务器配置文件
{ "password": "fuckshit", "method": "chacha20-ietf", "protocol": "auth_aes128_sha1", "protocol_param": "", "obfs": "tls1.2_ticket_auth", "obfs_param": "", "udp": false, "timeout": 300, "server_settings": { "listen_address": "0.0.0.0", "listen_port": 9090 } }
通过cmake编译为debug版本,以方便调试,之后利用gdb服务器端启动ssr-server
cmake
debug版本
gdb
ssr-server
启动完成,当客户端发送数据,漏洞形成
root@c664e51799f5:~/ssr-n/build# ./src/ssr-server -c vps/configfiles/ssr/config_tls.json ssr-server 2020/04/28 15:05 info ShadowsocksR native server ssr-server 2020/04/28 15:05 info listen port 9090 ssr-server 2020/04/28 15:05 info method chacha20-ietf ssr-server 2020/04/28 15:05 info password fuckshit ssr-server 2020/04/28 15:05 info protocol auth_aes128_sha1 ssr-server 2020/04/28 15:05 info obfs tls1.2_ticket_auth ssr-server 2020/04/28 15:05 info udp relay no ssr-server 2020/04/28 15:06 info ==== tunnel created count 1 ==== *** Error in `./src/ssr-server': free(): invalid next size (fast): 0x00000000024890f0 *** ======= Backtrace: ========= /lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7f9221be97e5] /lib/x86_64-linux-gnu/libc.so.6(+0x8037a)[0x7f9221bf237a] /lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7f9221bf653c] ./src/ssr-server(auth_aes128_sha1_server_post_decrypt+0x74a)[0x433793] ./src/ssr-server(tunnel_cipher_server_decrypt+0x283)[0x427ffa] ./src/ssr-server[0x42c446] ./src/ssr-server[0x42b84f] ./src/ssr-server[0x42ba3a] ./src/ssr-server[0x42a2f1] ./src/ssr-server[0x45ebb8] ./src/ssr-server[0x45ee79] ./src/ssr-server[0x46469a] ./src/ssr-server(uv_run+0xb1)[0x453cab] ./src/ssr-server[0x42b122] ./src/ssr-server(main+0x1da)[0x42ae2d] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f9221b92830] ./src/ssr-server(_start+0x29)[0x4139d9] ======= Memory map: ======== 00400000-004d9000 r-xp 00000000 08:01 3413376 /root/ssr-n/build/src/ssr-server 006d8000-006d9000 r--p 000d8000 08:01 3413376 /root/ssr-n/build/src/ssr-server 006d9000-006db000 rw-p 000d9000 08:01 3413376 /root/ssr-n/build/src/ssr-server 006db000-006de000 rw-p 00000000 00:00 0 0246e000-0248f000 rw-p 00000000 00:00 0 [heap] 7f921c000000-7f921c021000 rw-p 00000000 00:00 0 7f921c021000-7f9220000000 ---p 00000000 00:00 0 7f922195c000-7f9221972000 r-xp 00000000 08:01 2360260 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f9221972000-7f9221b71000 ---p 00016000 08:01 2360260 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f9221b71000-7f9221b72000 rw-p 00015000 08:01 2360260 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f9221b72000-7f9221d32000 r-xp 00000000 08:01 2360239 /lib/x86_64-linux-gnu/libc-2.23.so 7f9221d32000-7f9221f32000 ---p 001c0000 08:01 2360239 /lib/x86_64-linux-gnu/libc-2.23.so 7f9221f32000-7f9221f36000 r--p 001c0000 08:01 2360239 /lib/x86_64-linux-gnu/libc-2.23.so 7f9221f36000-7f9221f38000 rw-p 001c4000 08:01 2360239 /lib/x86_64-linux-gnu/libc-2.23.so 7f9221f38000-7f9221f3c000 rw-p 00000000 00:00 0 7f9221f3c000-7f9221f43000 r-xp 00000000 08:01 2360313 /lib/x86_64-linux-gnu/librt-2.23.so 7f9221f43000-7f9222142000 ---p 00007000 08:01 2360313 /lib/x86_64-linux-gnu/librt-2.23.so 7f9222142000-7f9222143000 r--p 00006000 08:01 2360313 /lib/x86_64-linux-gnu/librt-2.23.so 7f9222143000-7f9222144000 rw-p 00007000 08:01 2360313 /lib/x86_64-linux-gnu/librt-2.23.so 7f9222144000-7f922215c000 r-xp 00000000 08:01 2360307 /lib/x86_64-linux-gnu/libpthread-2.23.so 7f922215c000-7f922235b000 ---p 00018000 08:01 2360307 /lib/x86_64-linux-gnu/libpthread-2.23.so 7f922235b000-7f922235c000 r--p 00017000 08:01 2360307 /lib/x86_64-linux-gnu/libpthread-2.23.so 7f922235c000-7f922235d000 rw-p 00018000 08:01 2360307 /lib/x86_64-linux-gnu/libpthread-2.23.so 7f922235d000-7f9222361000 rw-p 00000000 00:00 0 7f9222361000-7f9222469000 r-xp 00000000 08:01 2360271 /lib/x86_64-linux-gnu/libm-2.23.so 7f9222469000-7f9222668000 ---p 00108000 08:01 2360271 /lib/x86_64-linux-gnu/libm-2.23.so 7f9222668000-7f9222669000 r--p 00107000 08:01 2360271 /lib/x86_64-linux-gnu/libm-2.23.so 7f9222669000-7f922266a000 rw-p 00108000 08:01 2360271 /lib/x86_64-linux-gnu/libm-2.23.so 7f922266a000-7f9222690000 r-xp 00000000 08:01 2360219 /lib/x86_64-linux-gnu/ld-2.23.so 7f9222883000-7f9222888000 rw-p 00000000 00:00 0 7f922288e000-7f922288f000 rw-p 00000000 00:00 0 7f922288f000-7f9222890000 r--p 00025000 08:01 2360219 /lib/x86_64-linux-gnu/ld-2.23.so 7f9222890000-7f9222891000 rw-p 00026000 08:01 2360219 /lib/x86_64-linux-gnu/ld-2.23.so 7f9222891000-7f9222892000 rw-p 00000000 00:00 0 7ffc3cda1000-7ffc3cdc2000 rw-p 00000000 00:00 0 [stack] 7ffc3cdcd000-7ffc3cdcf000 r--p 00000000 00:00 0 [vvar] 7ffc3cdcf000-7ffc3cdd1000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] Aborted
上面ssr-server崩溃了,启动gdb调试分析
Program received signal SIGABRT, Aborted. 0x00007f73e2b11428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54 54 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory. LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ────────────────────────────────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────────────────────────────── RAX 0x0 RBX 0x6a RCX 0x7f73e2b11428 (raise+56) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x6 RDI 0x29df RSI 0x29df R8 0xe R9 0x0 R10 0x8 R11 0x202 R12 0x6a R13 0x7ffc850a6e08 ◂— 0x8000000000 R14 0x7ffc850a6e08 ◂— 0x8000000000 R15 0x2 RBP 0x7ffc850a6ff0 —▸ 0x7ffc850a7040 ◂— '0000000002016d10' RSP 0x7ffc850a6c58 —▸ 0x7f73e2b1302a (abort+362) ◂— mov rdx, qword ptr fs:[0x10] RIP 0x7f73e2b11428 (raise+56) ◂— cmp rax, -0x1000 /* 'H=' */ ──────────────────────────────────────────────────────────────────────────[ DISASM ]────────────────────────────────────────────────────────────────────────── ► 0x7f73e2b11428 <raise+56> cmp rax, -0x1000 0x7f73e2b1142e <raise+62> ja raise+96 <0x7f73e2b11450> 0x7f73e2b11430 <raise+64> ret 0x7f73e2b11432 <raise+66> nop word ptr [rax + rax] 0x7f73e2b11438 <raise+72> test ecx, ecx 0x7f73e2b1143a <raise+74> jg raise+43 <0x7f73e2b1141b> ↓ 0x7f73e2b1141b <raise+43> movsxd rdx, edi 0x7f73e2b1141e <raise+46> mov eax, 0xea 0x7f73e2b11423 <raise+51> movsxd rdi, ecx 0x7f73e2b11426 <raise+54> syscall ► 0x7f73e2b11428 <raise+56> cmp rax, -0x1000 ──────────────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────────────── 00:0000│ rsp 0x7ffc850a6c58 —▸ 0x7f73e2b1302a (abort+362) ◂— mov rdx, qword ptr fs:[0x10] 01:0008│ 0x7ffc850a6c60 ◂— 0x20 /* ' ' */ 02:0010│ 0x7ffc850a6c68 ◂— 0x0 ... ↓ ────────────────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────────────── ► f 0 7f73e2b11428 raise+56 f 1 7f73e2b1302a abort+362 f 2 7f73e2b537ea f 3 7f73e2b5c37a _int_free+1578 f 4 7f73e2b5c37a _int_free+1578 f 5 7f73e2b6053c free+76 f 6 433793 auth_aes128_sha1_server_post_decrypt+1866 f 7 427ffa tunnel_cipher_server_decrypt+643 f 8 42c446 do_handle_client_feedback+245 f 9 42b84f do_next+550 f 10 42ba3a tunnel_read_done+35 pwndbg> bt #0 0x00007f73e2b11428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54 #1 0x00007f73e2b1302a in __GI_abort () at abort.c:89 #2 0x00007f73e2b537ea in __libc_message (do_abort=do_abort@entry=2, fmt=fmt@entry=0x7f73e2c6ced8 "*** Error in `%s': %s: 0x%s ***\n") at ../sysdeps/posix/libc_fatal.c:175 #3 0x00007f73e2b5c37a in malloc_printerr (ar_ptr=<optimized out>, ptr=<optimized out>, str=0x7f73e2c6cf50 "free(): invalid next size (fast)", action=3) at malloc.c:5006 #4 _int_free (av=<optimized out>, p=<optimized out>, have_lock=0) at malloc.c:3867 #5 0x00007f73e2b6053c in __GI___libc_free (mem=<optimized out>) at malloc.c:2968 #6 0x0000000000433793 in auth_aes128_sha1_server_post_decrypt (obfs=0x2018420, buf=0x2017370, need_feedback=0x7ffc850a72a3) at /root/ssr-n/src/obfs/auth.c:1553 #7 0x0000000000427ffa in tunnel_cipher_server_decrypt (tc=0x200f430, buf=0x200f400, obfs_receipt=0x7ffc850a7310, proto_confirm=0x7ffc850a7318) at /root/ssr-n/src/ssr_executive.c:624 #8 0x000000000042c446 in do_handle_client_feedback (tunnel=0x1ff3dc0, incoming=0x1ffdc70) at /root/ssr-n/src/server/server.c:708 #9 0x000000000042b84f in do_next (tunnel=0x1ff3dc0, socket=0x1ffdc70) at /root/ssr-n/src/server/server.c:432 #10 0x000000000042ba3a in tunnel_read_done (tunnel=0x1ff3dc0, socket=0x1ffdc70) at /root/ssr-n/src/server/server.c:480 #11 0x000000000042a2f1 in socket_read_done_cb (handle=0x1ffdc90, nread=1757, buf=0x7ffc850a7430) at /root/ssr-n/src/tunnel.c:413 #12 0x000000000045ebb8 in uv__read (stream=0x1ffdc90) at /root/ssr-n/depends/libuv/src/unix/stream.c:1238 #13 0x000000000045ee79 in uv__stream_io (loop=0x1ff3160, w=0x1ffdd18, events=1) at /root/ssr-n/depends/libuv/src/unix/stream.c:1305 #14 0x000000000046469a in uv__io_poll (loop=0x1ff3160, timeout=300000) at /root/ssr-n/depends/libuv/src/unix/linux-core.c:421 #15 0x0000000000453cab in uv_run (loop=0x1ff3160, mode=UV_RUN_DEFAULT) at /root/ssr-n/depends/libuv/src/unix/core.c:375 #16 0x000000000042b122 in ssr_server_run_loop (config=0x1ff3060) at /root/ssr-n/src/server/server.c:251 #17 0x000000000042ae2d in main (argc=3, argv=0x7ffc850aaa58) at /root/ssr-n/src/server/server.c:170 #18 0x00007f73e2afc830 in __libc_start_main (main=0x42ac53 <main>, argc=3, argv=0x7ffc850aaa58, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7ffc850aaa48) at ../csu/libc-start.c:291 #19 0x00000000004139d9 in _start ()
最近的触发点在/root/ssr-n/src/obfs/auth.c的auth_aes128_sha1_server_post_decrypt,其出问题部分代码
/root/ssr-n/src/obfs/auth.c
auth_aes128_sha1_server_post_decrypt
在1553行free内存时出错。
1553
free
具体分析一下代码
// 给key分配内存,并将所有数据置零 uint8_t *key = (uint8_t*) calloc(b64len + 1, sizeof(*key)); size_t key_len; (void)in_data; // 获取local_key长度 key_len = (size_t) std_base64_encode(local_key, (int)local_key_len, key); // 将local->salt复制到key[key_len]之后的位置 memmove(key+key_len, (uint8_t *)local->salt, strlen(local->salt)); key_len += strlen(local->salt); bytes_to_key_with_size(key, key_len, enc_key, sizeof(enc_key)); ss_aes_128_cbc_decrypt(16, buffer_get_data(local->recv_buffer, NULL)+11, head, enc_key); // 释放key free(key);
上面把跟key有关的所有操作,都做了标注,其实我们细想就可以发现memmove很有可能会出现越界写的问题
key
memmove
memmove(key+key_len, (uint8_t *)local->salt, strlen(local->salt));
如果越界足够长,就会覆盖接下来的堆块,造成释放时出现崩溃,我们来调试一下,验证我们的猜想
首先在auth.c:1541设下断点
auth.c:1541
pwndbg> b auth.c:1541 Breakpoint 1 at 0x433685: file /root/ssr-n/src/obfs/auth.c, line 1541. pwndbg> r -c vps/configfiles/ssr/config_tls.json // 省略部分输出 In file: /root/ssr-n/src/obfs/auth.c 1536 uint8_t in_data[32 + 1] = { 0 }; 1537 size_t local_key_len = 0; 1538 const uint8_t *local_key = buffer_get_data(local->user_key, &local_key_len); 1539 1540 size_t b64len = (size_t) std_base64_encode_len((int)local_key_len); ► 1541 uint8_t *key = (uint8_t*) calloc(b64len + 1, sizeof(*key)); 1542 size_t key_len; 1543 1544 (void)in_data; 1545 key_len = (size_t) std_base64_encode(local_key, (int)local_key_len, key); 1546 memmove(key+key_len, (uint8_t *)local->salt, strlen(local->salt)); ──────────────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────────────── 00:0000│ rsp 0x7ffc2b636f60 ◂— 0x0 01:0008│ 0x7ffc2b636f68 —▸ 0x7ffc2b637113 ◂— 0x688c800000000000 02:0010│ 0x7ffc2b636f70 —▸ 0x1688c80 ◂— 0x8be 03:0018│ 0x7ffc2b636f78 —▸ 0x168aba0 ◂— 0x0 04:0020│ 0x7ffc2b636f80 ◂— 0x0 05:0028│ 0x7ffc2b636f88 ◂— 0xaf141dd900000000 06:0030│ 0x7ffc2b636f90 —▸ 0x7ffc2b637030 —▸ 0x7ffc2b63a8c0 ◂— 0x3 07:0038│ 0x7ffc2b636f98 ◂— 0xd1bfed17a43e5a00 ────────────────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────────────── ► f 0 433685 auth_aes128_sha1_server_post_decrypt+1596 f 1 427ffa tunnel_cipher_server_decrypt+643 f 2 42c446 do_handle_client_feedback+245 f 3 42b84f do_next+550 f 4 42ba3a tunnel_read_done+35 f 5 42a2f1 socket_read_done_cb+399 f 6 45ebb8 uv.read+1206 f 7 45ee79 uv.stream_io+239 f 8 46469a uv.io_poll+1926 f 9 453cab uv_run+177 f 10 42b122 ssr_server_run_loop+667 pwndbg> p b64len // *key的类型为uint8_t,所以分配的长度为46 $1 = 45 pwndbg> n // 省略输出 pwndbg> x/46bx key // key 分配成功 0x1689c70: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x1689c78: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x1689c80: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x1689c88: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x1689c90: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x1689c98: 0x00 0x00 0x00 0x00 0x00 0x00
继续执行,来到关键代码处
pwndbg> n // 省略 In file: /root/ssr-n/src/obfs/auth.c 1541 uint8_t *key = (uint8_t*) calloc(b64len + 1, sizeof(*key)); 1542 size_t key_len; 1543 1544 (void)in_data; 1545 key_len = (size_t) std_base64_encode(local_key, (int)local_key_len, key); ► 1546 memmove(key+key_len, (uint8_t *)local->salt, strlen(local->salt)); 1547 key_len += strlen(local->salt); 1548 1549 bytes_to_key_with_size(key, key_len, enc_key, sizeof(enc_key)); 1550 1551 ss_aes_128_cbc_decrypt(16, buffer_get_data(local->recv_buffer, NULL)+11, head, enc_key); // 省略 pwndbg> p key_len // 查看memmove起始位置 $2 = 44 pwndbg> p local->salt $3 = 0x4ac19e "auth_aes128_sha1" pwndbg> x/20bx local->salt // 如果长度过长,会发生越界写 0x4ac19e: 0x61 0x75 0x74 0x68 0x5f 0x61 0x65 0x73 0x4ac1a6: 0x31 0x32 0x38 0x5f 0x73 0x68 0x61 0x31 0x4ac1ae: 0x00 0x61 0x75 0x74
memmove执行之前来看一下内存及堆情况
pwndbg> heap // 省略多个chunk输出 Allocated chunk Addr: 0x168ef10 Size: 0x811 Allocated chunk Addr: 0x168f720 Size: 0x1211 Allocated chunk Addr: 0x1690930 Size: 0x811 Allocated chunk Addr: 0x1691140 Size: 0x911 Allocated chunk Addr: 0x1691a50 Size: 0x8e1 Top chunk Addr: 0x1692330 Size: 0x4cd1 // top chunk的长度 pwndbg> x/46bx key // 在最后一个块中 0x1689c70: 0x4c 0x6b 0x73 0x2f 0x53 0x56 0x4f 0x64 0x1689c78: 0x54 0x41 0x38 0x74 0x46 0x32 0x30 0x48 0x1689c80: 0x49 0x55 0x4f 0x4e 0x39 0x47 0x49 0x36 0x1689c88: 0x37 0x2f 0x51 0x67 0x71 0x67 0x54 0x74 0x1689c90: 0x56 0x79 0x42 0x53 0x64 0x59 0x44 0x41 0x1689c98: 0x2b 0x36 0x73 0x3d 0x00 0x00
继续执行
► 0x43370e <auth_aes128_sha1_server_post_decrypt+1733> mov rax, qword ptr [rbp - 0x110] 0x433715 <auth_aes128_sha1_server_post_decrypt+1740> mov rax, qword ptr [rax + 0x18] 0x433719 <auth_aes128_sha1_server_post_decrypt+1744> mov rdi, rax 0x43371c <auth_aes128_sha1_server_post_decrypt+1747> call strlen@plt <0x412d50> 0x433721 <auth_aes128_sha1_server_post_decrypt+1752> add qword ptr [rbp - 0xd0], rax 0x433728 <auth_aes128_sha1_server_post_decrypt+1759> lea rdx, [rbp - 0x90] 0x43372f <auth_aes128_sha1_server_post_decrypt+1766> mov rsi, qword ptr [rbp - 0xd0] 0x433736 <auth_aes128_sha1_server_post_decrypt+1773> mov rax, qword ptr [rbp - 0xd8] 0x43373d <auth_aes128_sha1_server_post_decrypt+1780> mov ecx, 0x10 0x433742 <auth_aes128_sha1_server_post_decrypt+1785> mov rdi, rax 0x433745 <auth_aes128_sha1_server_post_decrypt+1788> call bytes_to_key_with_size <0x41e806> ──────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────────────────────────────────────── In file: /root/ssr-n/src/obfs/auth.c 1542 size_t key_len; 1543 1544 (void)in_data; 1545 key_len = (size_t) std_base64_encode(local_key, (int)local_key_len, key); 1546 memmove(key+key_len, (uint8_t *)local->salt, strlen(local->salt)); ► 1547 key_len += strlen(local->salt); 1548 1549 bytes_to_key_with_size(key, key_len, enc_key, sizeof(enc_key)); 1550 1551 ss_aes_128_cbc_decrypt(16, buffer_get_data(local->recv_buffer, NULL)+11, head, enc_key); 1552 ──────────────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────────────── 00:0000│ rsp 0x7ffc2b636f60 ◂— 0x0 01:0008│ 0x7ffc2b636f68 —▸ 0x7ffc2b637113 ◂— 0x688c800000000000 02:0010│ 0x7ffc2b636f70 —▸ 0x1688c80 ◂— 0x8be 03:0018│ 0x7ffc2b636f78 —▸ 0x168aba0 ◂— 0x0 04:0020│ 0x7ffc2b636f80 ◂— 0x0 05:0028│ 0x7ffc2b636f88 ◂— 0xaf141dd900000000 06:0030│ 0x7ffc2b636f90 —▸ 0x7ffc2b637030 —▸ 0x7ffc2b63a8c0 ◂— 0x3 07:0038│ 0x7ffc2b636f98 ◂— 0xd1bfed17a43e5a00 ────────────────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────────────── ► f 0 43370e auth_aes128_sha1_server_post_decrypt+1733 f 1 427ffa tunnel_cipher_server_decrypt+643 f 2 42c446 do_handle_client_feedback+245 f 3 42b84f do_next+550 f 4 42ba3a tunnel_read_done+35 f 5 42a2f1 socket_read_done_cb+399 f 6 45ebb8 uv.read+1206 f 7 45ee79 uv.stream_io+239 f 8 46469a uv.io_poll+1926 f 9 453cab uv_run+177 f 10 42b122 ssr_server_run_loop+667
再来看一下堆的情况
pwndbg> heap // 省略多个chunk输出 Free chunk (fastbins) Addr: 0x1689bc0 Size: 0x71 fd: 0x00 Allocated chunk Addr: 0x1689c30 Size: 0x31 Allocated chunk Addr: 0x1689c60 Size: 0x41 Free chunk (unsortedbin) Addr: 0x1689ca0 Size: 0x31616873 // size被改写 fd: 0x7f73962bcb78 bk: 0x00 pwndbg> x/60bx key // key被改写后的长度 0x1689c70: 0x4c 0x6b 0x73 0x2f 0x53 0x56 0x4f 0x64 0x1689c78: 0x54 0x41 0x38 0x74 0x46 0x32 0x30 0x48 0x1689c80: 0x49 0x55 0x4f 0x4e 0x39 0x47 0x49 0x36 0x1689c88: 0x37 0x2f 0x51 0x67 0x71 0x67 0x54 0x74 0x1689c90: 0x56 0x79 0x42 0x53 0x64 0x59 0x44 0x41 0x1689c98: 0x2b 0x36 0x73 0x3d 0x61 0x75 0x74 0x68 0x1689ca0: 0x5f 0x61 0x65 0x73 0x31 0x32 0x38 0x5f 0x1689ca8: 0x73 0x68 0x61 0x31 pwndbg> x/20wx key 0x1689c70: 0x2f736b4c 0x644f5653 0x74384154 0x48303246 0x1689c80: 0x4e4f5549 0x36494739 0x67512f37 0x74546771 0x1689c90: 0x53427956 0x41445964 0x3d73362b 0x68747561 0x1689ca0: 0x7365615f 0x5f383231 0x31616873(size) 0x00000000 0x1689cb0: 0x962bcb78 0x00007f73 0x962bcb78 0x00007f73
到这里可以发现,memmove成功造成了堆越界写。
这个代码是啥时引入到库中的呢?通过git的log可以发现其在507e009这个一天前的commit引入
git
log
507e009
再来看看以前的代码为啥没有这个漏洞呢
struct buffer_t *key = buffer_create(b64len + 1); (void)in_data; key->len = (size_t) std_base64_encode(local_key, (int)local_key_len, key->buffer); buffer_concatenate(key, (uint8_t *)local->salt, strlen(local->salt)); bytes_to_key_with_size(key->buffer, key->len, enc_key, sizeof(enc_key)); head = buffer_create(16); head->len = 16; ss_aes_128_cbc_decrypt(16, local->recv_buffer->buffer+11, head->buffer, enc_key); buffer_release(key);
key的赋值是通过buffer_concatenate来完成的,来看看这个函数的具体实现
buffer_concatenate
size_t buffer_concatenate(struct buffer_t *ptr, const uint8_t *data, size_t size) { size_t result = buffer_realloc(ptr, ptr->len + size); // 分配了足够的空间 memmove(ptr->buffer + ptr->len, data, size); ptr->len += size; check_memory_content(ptr); return min(ptr->len, result); }
它会调用buffer_realloc函数,对其超过自身长度的内存进行realloc,这样也就不存在越界写的问题了。
buffer_realloc
realloc
调试完后,我们再来看看auth.c中的auth_aes128_sha1_server_post_decrypt函数,其中需要注意的,我已经做了标示
auth.c
struct buffer_t * auth_aes128_sha1_server_post_decrypt(struct obfs_t *obfs, struct buffer_t *buf, bool *need_feedback) { struct server_info_t *server_info = &obfs->server_info; // 传入server_info struct buffer_t *out_buf = NULL; struct buffer_t *mac_key = NULL; uint8_t sha1data[SHA1_BYTES + 1] = { 0 }; size_t length; bool sendback = false; auth_simple_local_data *local = (auth_simple_local_data*)obfs->l_data; //传入混淆数据 buffer_concatenate2(local->recv_buffer, buf); out_buf = buffer_create(SSR_BUFF_SIZE); mac_key = buffer_create_from(server_info->recv_iv, server_info->recv_iv_len); buffer_concatenate(mac_key, server_info->key, server_info->key_len); if (local->has_recv_header == false) { uint32_t utc_time; uint32_t client_id; uint32_t connection_id; uint16_t rnd_len; int time_diff; uint32_t uid; char uid_str[32] = { 0 }; const char *auth_key = NULL; bool is_multi_user = false; bool user_exist = false; uint8_t head[16] = { 0 }; size_t len = buffer_get_length(local->recv_buffer); if ((len >= 7) || (len==2 || len==3)) { size_t recv_len = min(len, 7); struct buffer_t *_msg = buffer_create_from(buffer_get_data(local->recv_buffer, NULL), 1); local->hmac(sha1data, _msg, mac_key); buffer_release(_msg); if (memcmp(sha1data, buffer_get_data(local->recv_buffer, NULL)+1, recv_len - 1) != 0) { return auth_aes128_not_match_return(obfs, local->recv_buffer, need_feedback); } } if (buffer_get_length(local->recv_buffer) < 31) { if (need_feedback) { *need_feedback = false; } return buffer_create(1); } { struct buffer_t *_msg = buffer_create_from(buffer_get_data(local->recv_buffer, NULL)+7, 20); local->hmac(sha1data, _msg, mac_key); buffer_release(_msg); } if (memcmp(sha1data, buffer_get_data(local->recv_buffer, NULL)+27, 4) != 0) { // '%s data incorrect auth HMAC-SHA1 from %s:%d, data %s' if (buffer_get_length(local->recv_buffer) < (31 + local->extra_wait_size)) { if (need_feedback) { *need_feedback = false; } return buffer_create(1); } return auth_aes128_not_match_return(obfs, local->recv_buffer, need_feedback); } memcpy(local->uid, buffer_get_data(local->recv_buffer, NULL) + 7, 4); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wstrict-aliasing" uid = (uint32_t) (*((uint32_t *)(local->uid))); // TODO: ntohl #pragma GCC diagnostic pop sprintf(uid_str, "%d", (int)uid); if (obfs->audit_incoming_user) { user_exist = obfs->audit_incoming_user(obfs, uid_str, &auth_key, &is_multi_user); } if (user_exist) { uint8_t hash[SHA1_BYTES + 1] = { 0 }; assert(is_multi_user); assert(auth_key); local->hash(hash, (const uint8_t*)auth_key, strlen(auth_key)); buffer_store(local->user_key, hash, local->hash_len); } else { if (is_multi_user == false) { // user_key 最终来自于obfs->server_info buffer_store(local->user_key, server_info->key, server_info->key_len); } else { buffer_store(local->user_key, server_info->recv_iv, server_info->recv_iv_len); } } { uint8_t enc_key[16] = { 0 }; uint8_t in_data[32 + 1] = { 0 }; size_t local_key_len = 0; const uint8_t *local_key = buffer_get_data(local->user_key, &local_key_len); size_t b64len = (size_t) std_base64_encode_len((int)local_key_len); uint8_t *key = (uint8_t*) calloc(b64len + 1, sizeof(*key)); size_t key_len; (void)in_data; key_len = (size_t) std_base64_encode(local_key, (int)local_key_len, key); // 同样来自于 struct obfs_t *obfs memmove(key+key_len, (uint8_t *)local->salt, strlen(local->salt)); key_len += strlen(local->salt); bytes_to_key_with_size(key, key_len, enc_key, sizeof(enc_key)); ss_aes_128_cbc_decrypt(16, buffer_get_data(local->recv_buffer, NULL)+11, head, enc_key); free(key); }
可以发现所有的数据都来源于传入的参数struct obfs_t *obfs,只要能够控制它就控制了其他所有操作了。
struct obfs_t *obfs
所以我们只要能通过客户端的流量数据控制混淆的输入数据,那么就可以成功造成RCE
RCE
今天把这个调试完,有点晚了,回头我细看一下,看看能不能控制混淆的数据输入,能控制的话,那就是RCE,可能会影响很多梯子
目前修复办法:回滚不受影响版本
The text was updated successfully, but these errors were encountered:
Fixing #128
18768fa
Fixed. Thanks.
Test passed.
Sorry, something went wrong.
No branches or pull requests
shadowsocksr-native混淆验证auth.c存在基于堆的越界写漏洞
作为该库的忠实用户,首先感谢作者无私的奉献,继续开发
ssr
给我们广大用户使用,因为偶然的原因, 我更新了一直使用的ssr
,更新完之后发现, 突然不能使用了,为了找到其中的原因就调试了一下该库,现在把我的具体调试过程,以及如何发现漏洞的简单介绍一下。 希望大家能跟作者共同努力,把ssr
做的更加安全,稳定。基本环境
版本:使用
master
分支最新commit
:2bea1e1fadadd85497632d20e36e6d1bc55f121e
系统
服务器配置文件
漏洞形成
通过
cmake
编译为debug版本
,以方便调试,之后利用gdb
服务器端启动ssr-server
启动完成,当客户端发送数据,漏洞形成
漏洞分析
上面
ssr-server
崩溃了,启动gdb
调试分析最近的触发点在
/root/ssr-n/src/obfs/auth.c
的auth_aes128_sha1_server_post_decrypt
,其出问题部分代码在
1553
行free
内存时出错。具体分析一下代码
上面把跟
key
有关的所有操作,都做了标注,其实我们细想就可以发现memmove
很有可能会出现越界写的问题如果越界足够长,就会覆盖接下来的堆块,造成释放时出现崩溃,我们来调试一下,验证我们的猜想
首先在
auth.c:1541
设下断点继续执行,来到关键代码处
memmove
执行之前来看一下内存及堆情况继续执行
再来看一下堆的情况
到这里可以发现,
memmove
成功造成了堆越界写。这个代码是啥时引入到库中的呢?通过
git
的log
可以发现其在507e009
这个一天前的commit引入再来看看以前的代码为啥没有这个漏洞呢
key的赋值是通过
buffer_concatenate
来完成的,来看看这个函数的具体实现它会调用
buffer_realloc
函数,对其超过自身长度的内存进行realloc
,这样也就不存在越界写的问题了。总结
调试完后,我们再来看看
auth.c
中的auth_aes128_sha1_server_post_decrypt
函数,其中需要注意的,我已经做了标示可以发现所有的数据都来源于传入的参数
struct obfs_t *obfs
,只要能够控制它就控制了其他所有操作了。所以我们只要能通过客户端的流量数据控制混淆的输入数据,那么就可以成功造成
RCE
今天把这个调试完,有点晚了,回头我细看一下,看看能不能控制混淆的数据输入,能控制的话,那就是
RCE
,可能会影响很多梯子目前修复办法:回滚不受影响版本
The text was updated successfully, but these errors were encountered: