此为后篇,主要是ROP和环境配置。
ROP Chain
该程序没有任何保护,且拥有RWX段,我们通过构造ROP即可劫持控制流
在mips中包含一种cache incoherency
的问题,指令cache和数据cache需要一个时间来同步。具体的东西在后续文章中再研究。
最简单可靠的让缓存数据写入内存的方式是调用一个堵塞函数。比如sleep(1)或者其他类似的函数。sleep的过程中,处理器会切换上下文让给其他正在执行的程序,缓存会自动执行flush。
手工制作
在这里,我们可以使用 s3 中的值(在这里等于 5)设置调用参数
1 | move $t9, $s1 ; |
这相当于 a0=5 和 jmp s1(我们能控制 s1)。
注意:MIPS 的另一个特殊性是分支延迟槽,当执行涉及跳转的指令时,在到达目的地之前也执行后面的指令。这解释了为什么在我们的小工具中跳转后还包含一条指令。
由于函数调用将使用 ra 中的值返回,我们需要找到一个gadget来设置该寄存器并通过另一个寄存器跳转。这个不难找,很多函数结尾都是类似下面这样的:
1 | move $t9, $s2 ; |
相当于 ra=0x24(sp), sp+=0x28, jmp $s2
.
由于堆栈是可执行的,我可以跳到堆栈中执行一些东西;这个gadget在v0中加载带有偏移量的堆栈地址,并跳转到s0值所指向的位置
1 | addiu $v0, $sp, 0x40 ; |
最后,我们可以使用所有gadget中最简单的一个跳到v0
1 | jr $v0 ; |
接下来只要编写好shellcode就可以
mips 调用 shellcode 构造模板:
通过框架可以构造出shellcode,但是不能直接使用
因为程序会对 ssid 输入内容在 stringModify 进行过滤,导致 shellcode 中的 lui 指令的字节码 0x3c(<) 被替换,所以需要对 shellcode 进行改造。 可以使用指令逃逸的方法,使用一些无关指令,如 ori $t1,$t1,0xff3c
,其对应的机器码为\x35\x29\xFF\x3C
,会被替换为\x35\x29\xFF\x5c\x3c
,这样\x3c
就会逃逸到下一个内存空间去,在对shellcode修改时,加入我们要修改的代码为"\x3c\x0e\xc0\xa8" // lui $t6, 0xc0a8
,只需在无关指令后填入\x0e\xc0\xa8
即可。
使用反弹本地9999端口的shellcode构造好exp后,在宿主机中通过nc连接即可。
1 | import requests |
【借鉴】socket方法
如果我们分析指令,我们会发现当writePageParamSet()
函数被调用时,req是通过寄存器s7传递的;之后,在内部调用stringModify()
(导致溢出),并在同一帧调用httpPrintf()
。幸运的是,其中一个内部调用会将s7存储在堆栈中。
由于这些gadet都是确定性的,我们可以计算出req和sq的偏移量。当我们控制pc寄存器时,结果是req在sp-0x480+0x204。然后我们就获得了req的socket成员变量的地址(&req + 0x34)。通过它可以得到一个shel.
环境搭建篇
虽然IOT-vulhub提供了很多实用的docker,但是单论调试httpd还是不好用
尝试了很多种方法,光是环境就折腾了好几天
1.真机
一开始想用真机,但是一来真机要等发货,而来往真机上传gdbserver也不容易,最好的情况是用ftp,最坏的情况就得用串口了
1 | (laptop) $ pip3 install --user ptftpd |
该漏洞挖掘者认为下载固件来模拟是更简单方法
2.漏洞发现人的办法
自主构建内核
1 | $ export ARCH=mips |
qemu
1 | $ qemu-system-mips \ |
这启动了我们的仿真机,并允许从端口2222连接“ssh”,并在端口8080暴露web接口
1 | $ ssh -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@127.0.0.1 |
现在可以复制文件系统,在其中mount
proc
文件系统并chroot
1 | root@debian-mips:~# mount --bind /proc /tl-rootfs/proc/ |
很明显,开箱即用的httpd是不起作用的,因为一些设备文件不存在,但可以使用LD_PRELOAD机制来欺骗守护进程,特别是我截获了system()和fork()调用,以避免发生网络配置。
3.本文办法
本文最后采取的办法是qemu-sytem+ssh
前置工作
使用 binwalk 解压固件:
环境不能直接使用,必须hook掉httpd文件里面的阻塞函数。
先构建 buildroot 环境,交叉编译得到 hook.so
1 | $ mips-buildroot-linux-uclibc-gcc -shared -fPIC hook.c -o hook.so |
需要hook掉fork和system函数
1 |
|
正式搭建
使用命令打开mis qemu系统模式:
1 | $ sudo apt-get install uml-utilities # 安装tunctl |
开启成功后使用root:root登录
配置网卡IP:
1 | ifconfig eth0 192.168.0.1/24 up |
将文件系统复制到qemu虚拟机root目录下,同时把gdbserver和hook.so传进去。
1 | scp -r ./squashfs-root root@192.168.0.1:~/ |
使用 chroot 切换根目录固件文件系统
1 | # 挂载,确保能够访问到 |
PS:使用 chroot 后,系统读取的是新根下的目录和文件,也就是固件的目录和文件 chroot 默认不会切换 /dev 和 /proc, 因此切换根目录前需要现挂载这两个目录,这样我们的程序在访问一些内核信息时候能够读取到。
1 | # 连上虚拟机,开启调试 |
1 | (TL-WR841N)$ ./gdbserver 0.0.0.0:1234 --attach 2373 |