articles
articles copied to clipboard
shadowsocksr-native混淆验证auth.c存在基于堆的越界写漏洞
shadowsocksr-native混淆验证auth.c存在基于堆的越界写漏洞
作为该库的忠实用户,首先感谢作者无私的奉献,继续开发ssr
给我们广大用户使用,因为偶然的原因, 我更新了一直使用的ssr
,更新完之后发现, 突然不能使用了,为了找到其中的原因就调试了一下该库,现在把我的具体调试过程,以及如何发现漏洞的简单介绍一下。 希望大家能跟作者共同努力,把ssr
做的更加安全,稳定。
基本环境
版本:使用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
启动完成,当客户端发送数据,漏洞形成
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
,其出问题部分代码
在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
很有可能会出现越界写的问题
memmove(key+key_len, (uint8_t *)local->salt, strlen(local->salt));
如果越界足够长,就会覆盖接下来的堆块,造成释放时出现崩溃,我们来调试一下,验证我们的猜想
首先在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引入
再来看看以前的代码为啥没有这个漏洞呢
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
来完成的,来看看这个函数的具体实现
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
,这样也就不存在越界写的问题了。
总结
调试完后,我们再来看看auth.c
中的auth_aes128_sha1_server_post_decrypt
函数,其中需要注意的,我已经做了标示
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
,只要能够控制它就控制了其他所有操作了。
所以我们只要能通过客户端的流量数据控制混淆的输入数据,那么就可以成功造成RCE
今天把这个调试完,有点晚了,回头我细看一下,看看能不能控制混淆的数据输入,能控制的话,那就是RCE
,可能会影响很多梯子
目前修复办法:回滚不受影响版本