从笔记中随机抽取一篇文章发一下,现在网上应该是没有qiling的fuzz资料的,我来一个成功的例子示范。

环境是tenda ac15

搭建环境

启动http服务后会在这里卡住,check一下二进制程序。

1
2
3
4
5
6
7
8
9
10
11
12
/bin # ./httpd 
init_core_dump 1816: rlim_cur = 0, rlim_max = -1
init_core_dump 1825: open core dump success
sh: can't create /proc/sys/kernel/core_pattern: nonexistent directory
init_core_dump 1834: rlim_cur = 5242880, rlim_max = 5242880


Yes:

****** WeLoveLinux******

Welcome to ...

跟踪字符串,猜测是这里的check_network函数的问题

1
2
3
4
5
v3 = puts("\n\nYes:\n\n      ****** WeLoveLinux****** \n\n Welcome to ...");
sub_30A5C(v3);
while ( check_network(v21) <= 0 )
sleep(1u);
v4 = sleep(1u);

patch源文件

把r0寄存器改成#1即可

image-20230504142452518

patch完会变成这样

1
2
3
4
 Welcome to ...
connect: No such file or directory
Connect to server failed.
connect cfm failed!

我们定位connect cfm failed!字符串,然后继续patch。

image-20230505100633694

patch完发现ip不对

我们可以找一下check_network这个函数,可以发现在lib/libcommon.so

1
2
3
4
5
6
7
8
bool __fastcall check_network(int a1)
{
int LanIfName; // r0

LanIfName = j_getLanIfName();
// 调用了外部函数get_eth_name(0),在libChipApi.so里
return j_getIfIp(LanIfName, a1) >= 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const char *__fastcall get_eth_name(int a1)
{
const char *v1; // r3

switch ( a1 )
{
case 0:
v1 = "br0";
break;
case 1:
v1 = "br1";
break;
...
case 55:
v1 = "br20";
break;
default:
v1 = (const char *)&unk_66C8;
break;
}
return v1;
}

所以我们需要一张网卡是br0的,不然就会出现服务启动,但是无法连接的状况。

1
2
3
4
ip link add name br0 type veth peer name br0-peer
ip addr add 192.168.3.3/24 dev br0
ip link set dev br0 up
# ip这边可以在qemu-system创建时直接加一个,然后本地也要有,这个可选方案很多

此时ip正确了,但是看不到网站。去读启动项文件rcS可以看见cp -rf /webroot_ro/* /webroot/,照做即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#! /bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin/
export PATH
mount -t ramfs none /var/
mkdir -p /var/etc
mkdir -p /var/media
mkdir -p /var/webroot
mkdir -p /var/etc/iproute
mkdir -p /var/run
cp -rf /etc_ro/* /etc/
cp -rf /webroot_ro/* /webroot/
mkdir -p /var/etc/upan
mount -a
...

成功复现

qiling patch

诉苦:鬼知道我为了调试这个东西用了多久,最后鉴定为未来战士,现在底层写的太不完善了。为了调试这个东西真的把网上所有跟qiling相关的文章都看了一遍了,各种报错,各种出问题。

最后在不断的patch中,翻遍了qiling github上所有的issue,顺便找到了一个调完的师傅,终于把这个摸明白了。(找到的时候我已经把所有的坑都踩完了QAQ)

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#


# 1. Download AC15 Firmware from https://down.tenda.com.cn/uploadfile/AC15/US_AC15V1.0BR_V15.03.05.19_multi_TD01.zip
# 2. unzip
# 3. binwalk -e US_AC15V1.0BR_V15.03.05.19_multi_TD01.bin
# 4. locate squashfs-root
# 5. rm -rf webroot && mv webroot_ro webroot
#
# notes: we are using rootfs in this example, so rootfs = squashfs-root
#

import os, socket, threading

import sys
sys.path.append("..")

from qiling import Qiling
from qiling.const import QL_VERBOSE

def patcher(ql: Qiling):
br0_addr = ql.mem.search("br0".encode() + b'\x00')

for addr in br0_addr:
ql.mem.write(addr, b'lo\x00')

def nvram_listener():
server_address = 'rootfs/var/cfm_socket'
data = ""

try:
os.unlink(server_address)
except OSError:
if os.path.exists(server_address):
raise

# Create UDS socket
sock = socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
sock.bind(server_address)
sock.listen(1)

while True:
connection, _ = sock.accept()

try:
while True:
data += str(connection.recv(1024))

if "lan.webiplansslen" in data:
connection.send('10.211.55.7'.encode())
else:
break

data = ""
finally:
connection.close()

def myvfork(ql: Qiling):
regreturn = 0
ql.log.info("vfork() = %d" % regreturn)

return regreturn

def my_sandbox(path, rootfs):
#ql = Qiling(path, rootfs, verbose=QL_VERBOSE.DEBUG)
ql = Qiling(path, rootfs,verbose=QL_VERBOSE.OFF)
ql.add_fs_mapper("/dev/urandom","/dev/urandom")
ql.hook_address(patcher, ql.loader.elf_entry)

# $ gdb-multiarch -q rootfs/bin/httpd
# gdb> set remotetimeout 100
# gdb> target remote localhost:9999
ql.debugger = False

if ql.debugger == True:
ql.os.set_syscall("vfork", myvfork)

ql.run()

if __name__ == "__main__":
nvram_listener_therad = threading.Thread(target=nvram_listener, daemon=True)
nvram_listener_therad.start()

my_sandbox(["rootfs/bin/httpd"], "rootfs")

运行成功,其实我觉得这个东西还挺巧妙的,你从它patch的时候就能看出来(和我们手动patch不一样),但是我觉得这个多少有点给自己增加工作量,我可以用qeme+ida把固件基本上patch明白,用这个多少有点增加工作量。

打开web页面就看见程序崩溃了,原因是打开之后请求资源太多,虚拟的环境崩溃了

加一下内存也许就能跑了。

其余fuzz其实网上有很多资料了,麻烦的只有如何使用qiling将固件模拟起来,这里不过多赘述了。