D-Link-DIR-815固件学习
最近看着朋友门的文章和网上的参考资料 复现了一个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时会出现如下报错
用如下命令可以解决
1 | sudo wget https://github.com/devttys0/sasquatch/pull/51.patch && sudo patch -p1 <51.patch |
3.固件下载DIR-815A1_FW101SSB03.bin
4.mipsrop 寻找mipsrop的插件mipsROP
前期工作
1
解包 binwalk -e DIR-815A1_FW101SSB03.bin 如果时新版binwalk,为了安全会把部分软连接指向null,只需要加入 –preserve-symlinks 命令即可
(实测还是有点问题,无论是加上命令还是用旧版 软链接总是显示broken,这里删除后手动添加链接)
2
该如何模拟运行这个固件,首先我们知道对于一个普通的mips程序,我们只需要用qemu+gdb/qemu+ida 即可调试,同时还分为静态和动态链接库这两种。
以MIPS程序举例(编译要提前安装好交叉编译环境)
静态运行
1 | mips-linux-gnu-gcc 1.c -o 1 --static |
动态运行
1 | mips-linux-gnu-gcc 1.c -o 1 |
ida调试
1 | 1开启监听 qemu-mips -L /usr/mips-linux-gnu -g 10000 ./1 |
gdb调试
1 | 1开启监听 qemu-mips -L /usr/mips-linux-gnu -g 10000 ./1 |
对于一个解包好的固件,调试通常分为用户模式和系统模式
这里介绍一下用户模式命令:
1 | cp $(which qemu-mipsel-static) ./ |
而对于我们要调试的固件,不能这么向上面这么简单的模拟,因为要调试的程序仅仅只是一部分,它具体接受哪些参数和文件都需要在前期测试分析的时候了解。对于本固件,参考看雪的文章写一个bash脚本帮助我们调试。
1 | #!/bin/bash |
此脚本设置一些列的参数传给程序
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完成调试的。
成功断下
逆向分析
整个溢出点在于sprintf,函数里存在两个sprintf,这两个函数的参数都是通过v4传递的,而v4就是cooki,即我们构造的2000个垃圾数据。
进入第一个sprintf必须满足是post传参
而进入第二个sprintf必须满足
1.存在/var/tmp/temp.xml 这个在我们解包的固件是没有的,而真实环境是存在的,需要手动创建
2.环境变量REQUEST_URI中也必须有内容才行
具体分析可以参考一下看雪的文章
这里我们调试看一下,首先sp为0x407ff338 而在函数开始扩展了0x4e8
计算可得上一个sp为0x407ff338+0x4e8=0x407ff820
在堆栈入口往上看果然找到了mian函数的地址,在0x407ff81c
现在运行到第一个sprintf 查看是否覆盖了栈,可以观察到已经成功覆盖了我们的垃圾数据。
第二个sprintf
因为第一个垃圾数据会被第二关覆盖 这里计算一下第二个垃圾数据的偏移。
最后在运行到末尾的返回处,发现就是返回到0x646b6161
找到返回地址,接下来准备构造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 | shellcode = asm(''' |
解释一下就是
1 | slti $a2, $zero, -1 #参数a2设置为-1 |
对于如何找到syscall的gadget的地址,这里是因为没有开aslr通过调试可以知道
最终payload如下:
1 | from pwn import * |
(实际测试感觉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 | addiu $s0,$sp,xxx #xxx就是后面填充数据的偏移 |
misprop这个插件可以帮助我们去寻找很多的gadget
1 | mipsrop.find("addiu $s0,$sp,.*") |
1 | mipsrop.tail() #栈上的数据给寄存器赋值 |
1 | mipsrop.system() #发现system |
先利用两个memset确定libc基质,这里计算为:0x3Ff38000
然后这里kk看雪上面的payload看如何绕过00截断:
返回地址是最后一条payload,写入bin/sh到a0寄存器,然后让s0寄存器内容+1,s0寄存器保存着system的地址-1,这样就把system的地址保存在s0里,并且利用-1+1绕过了00截断,最后跳到system地址进行getshell
1 | from pwn import * |
可以发现最后进入system函数了
但是却利用不成功,是因为
system
函数中有调用fork()
函数,而用户模式是不支持多线程的,这里fork()
的失败,会导致后面$fp
是个空指针,就会出错,在系统模式下不会出错
所以最佳组合为:第一个的shllcode+第二个的gadget