Closed xinali closed 4 years ago
作为该库的忠实用户,首先感谢作者无私的奉献,继续开发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,可能会影响很多梯子
目前修复办法:回滚不受影响版本
Fixed. Thanks.
Test passed.
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
,可能会影响很多梯子目前修复办法:回滚不受影响版本