Skip to content
New issue

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

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

Closed
xinali opened this issue Apr 28, 2020 · 1 comment
Closed

Comments

@xinali
Copy link

xinali commented Apr 28, 2020

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:00080x7ffc850a6c60 ◂— 0x20 /* ' ' */
02:00100x7ffc850a6c68 ◂— 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:00080x7ffc2b636f68 —▸ 0x7ffc2b637113 ◂— 0x688c800000000000
02:00100x7ffc2b636f70 —▸ 0x1688c80 ◂— 0x8be
03:00180x7ffc2b636f78 —▸ 0x168aba0 ◂— 0x0
04:00200x7ffc2b636f80 ◂— 0x0
05:00280x7ffc2b636f88 ◂— 0xaf141dd900000000
06:00300x7ffc2b636f90 —▸ 0x7ffc2b637030 —▸ 0x7ffc2b63a8c0 ◂— 0x3
07:00380x7ffc2b636f98 ◂— 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:00080x7ffc2b636f68 —▸ 0x7ffc2b637113 ◂— 0x688c800000000000
02:00100x7ffc2b636f70 —▸ 0x1688c80 ◂— 0x8be
03:00180x7ffc2b636f78 —▸ 0x168aba0 ◂— 0x0
04:00200x7ffc2b636f80 ◂— 0x0
05:00280x7ffc2b636f88 ◂— 0xaf141dd900000000
06:00300x7ffc2b636f90 —▸ 0x7ffc2b637030 —▸ 0x7ffc2b63a8c0 ◂— 0x3
07:00380x7ffc2b636f98 ◂— 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 added a commit that referenced this issue Apr 28, 2020
@ssrlive
Copy link
Member

ssrlive commented Apr 28, 2020

Fixed. Thanks.

Test passed.

@ssrlive ssrlive closed this as completed Apr 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants