考研期间想复现的漏洞,现在挖出来盘一下。
Tenda-AC15栈溢出漏洞(CVE-2018-5767) by 星期五实验室
复现环境
1 | Ubuntu 20.04 |
Tenda AC15 15.03.1.16_multi下载地址
复现准备
分离固件系统
binwalk -Me US_AC15V1.0BR_V15.03.1.16_multi_TD01.bin
,得到扫描信息。切换到
./~.extracted/squashfs-root
,可以看到提取出的文件系统目录:
1 | fuzz@fuzz-virtual-machine:~/repeat/_US_AC15V1.0BR_V15.03.1.16_multi_TD01.rar.extracted/squashfs-root$ ls |
- 在
/bin
下看到了busybox
文件,依稀记得哪年国赛有见到过,busybox
是一个集成了一百多个常用Linux命令和工具的软件,在嵌入式Linux
应用中,busybox
应用非常广泛,同时大多数Linux发行版
的安装程序中都含有busybox
。我们查看一下其文件头信息:
1 | fuzz@fuzz-virtual-machine:~/repeat/_US_AC15V1.0BR_V15.03.1.16_multi_TD01.rar.extracted/squashfs-root/bin$ readelf -h busybox |
- 我们采用
qemu
配合chroot
来启动/bin/httpd
:
1 | sudo apt install qemu-user-static libc6-arm* libc6-dev-arm* |
- 启动后发现程序一直卡在
Welcome to ...
:
- 查看源程序,发现有两个检查,在
qemu
模拟环境下连接不到相关服务,因此patch
掉:
1 | check_network:检查网络 |
- 启动修改后的
httpd
,出现了新问题,ip
地址不是本地的:
- 由此需要自行配置网桥
br0
后重新执行:
1 | sudo apt install uml-utilities bridge-utils |
漏洞分析
- 漏洞位于
R7WebsSecurityHandler
中:
- 先来看看
sscanf
的用法:
1 | sscanf("zhou456 hedf", "%[^ ]", str); //取到指定字符为止的字符串,取遇到空格为止字符串 |
即将源字符串以一定形式(正则)读入到目的字符串。
password
是用户可控的,它是http
请求中cookie
上的一个字段,也是伪代码中的v40
,而v33
数组只有128
大小,sscanf(v40, "%*[^=]=%[^;];*", v33);
存在栈溢出漏洞。- 我们检查下保护机制,除了栈不可执行,其他保护均未开启,容易想到
ROP
:
1 | Arch: arm-32-little |
漏洞复现
- 首先我们要进入漏洞函数,只需访问如
/goform/anza
即可绕过判断。
- 访问
http://< ip >/goform/anza
,用火狐配合burpsuite
抓个包:
1 | GET /goform/anza |
- 增加
Cookie
字段送过去,尝试使进程崩溃:
1 | GET /goform/anza |
- 查看服务端,发现程序出现了段错误:
- 使用
gdb-multiarch
调试程序,在此之前现在固定端口打开程序:
1 | sudo chroot ./ ./qemu-arm-static -g 1234 ./bin/httpd |
- 另开一个窗口进行调试:
1 | gdb-multiarch |
1 | LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA |
- 断点下在
End of function R7WebsSecurityHandler
即执行如下命令:
1 | b *0x002ED18 |
- 而后再打开一个窗口,我们用
python
的request库
将数据传过去:
1 | # exp.py |
- 但我们没有按预期返回,而是被某子函数干扰了,
gdb
中利用bt
命令溯源一下:
- 在
ida
中找到干扰函数所在位置,我们要想办法防止程序执行流走到该分支:
- 修改
payload
,使password
加上.gif
即可直接返回,如下:
1 | import requests |
- 重复上述流程,程序执行流来到我们发生的垃圾数据,我们注意到地址是
0x61616160
而非0x61616161
,这是因为arm
模式与thumb
模式的切换问题。在函数退出时执行了pop pc
的操作,而其最低有效位(LSB)将会被写入到CPSR寄存器
(当前程序状态寄存器)的T位中:
- 在构造
ROP
链之前,首先我们需要明白32位arm
架构是如何进行系统调用的:
- 本质和
x86/x64
大差不差,用ROPgagdet
找到需要的gadget
:
1 | fuzz@fuzz-virtual-machine:~/repeat/_US_AC15V1.0BR_V15.03.1.16_multi_TD01.rar.extracted/squashfs-root/bin$ ROPgadget --binary httpd --only "pop" |
第一次还看不懂,想着ret
在哪里,忽然发现pop pc
就是ret
。
- 计算偏移为
448
,构造执行puts("ANZA_HACK")
的rop
链发现失败,不难猜测原因是sscanf
被\x00
截断了(因为用的源程序的gadget
,高位为\x00
),故采用libc
的gagdet
,首先要计算本地libc
基址,根据之前在strstr
处出错的图可得:
注:与 ctf 中不同的一点是这种 web 服务在崩溃后一般会立刻重启,并且重启之后的 libc 地址和之前是相同的,所以爆破 libc 是比较常用的攻击手段,不过 qemu 没办法提供这种爆破 libc 的条件,我们先假定我们知道 libc 基址。
1 | libc_base=0xff613954-24-libc.sym["strstr"] |
- 最终
exp
如下:
1 | import requests |
- 其他博客的结果都不能
getshell
。。。。我反而可以getshell
,效果如下:
总结
很有意思的一次实验,接触了很多以前只是听过但是未投入实践的知识,算是入门漏洞挖掘的第一步吧,当然还有很多细节没有盘清楚,未来还需努力。再次感谢博客师傅们。