ShadowsocksR-Live / shadowsocksr-native

翻墙 从容穿越党国敏感日 ShadowsocksR (SSRoT) native implementation for all platforms, GFW terminator
https://github.com/ShadowsocksR-Live/shadowsocksr-native/wiki
GNU General Public License v3.0
2.74k stars 763 forks source link

shadowsocksr-native混淆验证auth.c存在基于堆的越界写漏洞 #128

Closed xinali closed 4 years ago

xinali commented 4 years ago

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.cauth_aes128_sha1_server_post_decrypt,其出问题部分代码

image-20200428231326261

1553free内存时出错。

具体分析一下代码

// 给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成功造成了堆越界写。

这个代码是啥时引入到库中的呢?通过gitlog可以发现其在507e009这个一天前的commit引入

image-20200429001505667

再来看看以前的代码为啥没有这个漏洞呢

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,可能会影响很多梯子

目前修复办法:回滚不受影响版本

ssrlive commented 4 years ago

Fixed. Thanks.

Test passed.