When to Use / When Not to Use
- Use: need to run programs inside the firmware to observe behavior/verify hypotheses (firmware web interface, server-side logic)
- Use: rootfs has been extracted and needs to be booted as a whole (full-system emulation)
- Don't use: only static analysis is needed (already covered by [[re-fw-rootfs]])
- Don't use: a physical board is available and real hardware interaction is required (use [[re-hardware-io]], hardware extraction/interface debugging)
- Don't use: the goal is only to unpack and view contents (use [[re-fw-extract]])
Tool Preparation
Verify every tool before use. Emulation = dynamic execution, sandbox + network isolation by default ([[re-analyze/platform-tips]] highest principle); user-mode emulation preferred (lightest feasible approach).
qemu-user — user-mode emulation (lightest)
- Linux:
apt install qemu-user/dnf install qemu-user/pacman -S qemu-user - macOS:
brew install qemu(includes user-mode and system-mode) - Windows/WSL: Linux version inside WSL
- Verify:
qemu-arm --version、qemu-mips --version(big-endian),qemu-mipsel --version(little-endian)
qemu-system — full-system emulation
- Linux:
apt install qemu-system-arm qemu-system-mips/dnf install qemu-system-arm qemu-system-mips/pacman -S qemu-system-arm qemu-system-mips - macOS:
brew install qemu - Windows/WSL: Linux version inside WSL
- Verify:
qemu-system-arm --version
binfmt_misc — execute cross-architecture programs directly
- Linux:
apt install binfmt-support qemu-user-static(on Debian/Ubuntu, installing qemu-user-static automatically registers binfmt entries for each architecture; on Fedora use systemd binfmt configuration); manual registration:update-binfmts --enable qemu-arm(or a configuration file under /etc/binfmt.d/) - macOS/Windows: not supported, use
qemu-<arch>to invoke explicitly - Verify:
ls /proc/sys/fs/binfmt_misc/qemu-arm entries are visible; afterwards you can directly execute./rootfs_out/usr/sbin/httpd
gdb-multiarch + gdbserver — cross-debugging
- Linux:
apt install gdb-multiarch/dnf install gdb-multiarch/pacman -S gdb-multiarch;gdbserver:apt install gdbserver(or provided with the gdb package) - macOS:
brew install gdb(requires Developer Tools authorization, see the macOS branch of [[re-analyze/platform-tips]]) or the Linux version inside WSL - Windows/WSL: Linux version inside WSL
- Verify:
gdb-multiarch --version
firmadyne — full-system automated emulation framework (for reference on the approach, optional install)
- Install:
git clone https://github.com/firmadyne/firmadyne, depends on qemu-system-* and a precompiled kernel (downloaded under scripts/), initial setup is heavy - Verify:
ls sources/has scripts such as getArch.py;which qemu-system-mips - most single-program analysis does not need it—user-mode first ([[re-analyze/platform-tips]] gives the lightest feasible approach first)
Procedure
Execute in order, record the result of each step.
-
User-mode emulation (lightest, preferred):
file rootfs_out/usr/sbin/httpd # 确认架构:ARM 32/64、MIPS(BE/EL)、RISC-V qemu-arm -L rootfs_out rootfs_out/usr/sbin/httpd # -L 把 rootfs 当 sysroot(动态库/链接器从 rootfs 加载) # 大端 MIPS: qemu-mips -L rootfs_out ... # 小端 MIPS: qemu-mipsel -L rootfs_out ...Missing library errors → cross
ldd/readelf -dto see dependencies, supplement libraries from rootfs; for web service programs you can add-Eto pass environment variables.-stracerecord the guest program's system calls (equivalent to strace inside emulation—use it directly to troubleshoot mmap/ioctl crash points)-0 <argv0>forge argv[0] (used when a program branches on the invocation name);-cpu <型号>specify CPU (e.g.qemu-arm -cpu cortex-a9)- can't run but is statically analyzable → go back to [[re-fw-rootfs]] / [[re-binary-core]], don't keep fighting with emulation.
-
Full-system emulation:
# ARM(vexpress 平台)+ 内核与 initramfs: qemu-system-arm -M vexpress-a9 -kernel vmlinuz -initrd initramfs.img -nographic \ -append "console=ttyAMA0 root=/dev/ram rdinit=/sbin/init" # MIPS(malta 平台,大端示例): qemu-system-mips -M malta -kernel vmlinuz -initrd initramfs.img -nographic \ -append "console=ttyS0 rdinit=/sbin/init"Make the rootfs into a disk image (ext2 mounted as root) or initramfs (package rootfs_out with cpio); firmadyne's scripts automate this whole process. When the rootfs is already a disk image, mount the disk directly:
qemu-system-arm -M vexpress-a9 -kernel vmlinuz -drive file=rootfs.ext2,format=raw \ -nographic -append "console=ttyAMA0 root=/dev/mmcblk0 rdinit=/sbin/init"root device names are determined by the platform disk controller (vexpress=mmcblk0, malta's IDE=/dev/sda, etc.; refer to the
-Mplatform documentation). -
Missing peripherals: use stub/loopback:
- User mode: program crashes when mmap'ing a fixed address (GPIO/UART registers) →
stracelocate the access point,LD_PRELOADprovide a stub library that returns fake register values - Full system:
-deviceattach virtual peripherals (e1000 / virtio, etc.); real chip peripherals (wifi/baseband) cannot be emulated by QEMU → patch to skip initialization or stub that ioctl - First determine up to which step of program initialization it crashes (serial output/logs), then decide which part to stub
- User mode: program crashes when mmap'ing a fixed address (GPIO/UART registers) →
-
Cross debugging (qemu -g + gdb-multiarch):
qemu-arm -g 1234 -L rootfs_out rootfs_out/usr/sbin/httpd & # -g 起 gdbstub gdb-multiarch rootfs_out/usr/sbin/httpd (gdb) target remote :1234Inside the full system: put gdbserver into the rootfs,
gdbserver :1234 /usr/sbin/httpd, hosttarget remote <qemu_ip>:1234; debugging techniques follow [[re-gdb]]. -
Emulation under network isolation:
- User mode: it is not a network sandbox (socket/connect are forwarded to the host kernel via syscall); when a controlled network is needed, switch to the full-system approach
- Full system: if QEMU is not given network parameters, it creates a NIC (e1000) + user backend by default—first explicitly
-nic noneconfirm the behavior, then add a controlled network with-nic user,restrict=on(user-mode NAT, emulates outbound only) - Isolate before analyzing callbacks/protocols ([[re-analyze/platform-tips]] highest principle); traffic capture and protocol reconstruction go to [[re-protocol]]; firmadyne also comes with a NIC by default and must be isolated first per this principle
Cross-domain integration
- [[re-firmware]]: step 4 of the workflow always invokes this skill
- Architecture identification and instruction-level deep dive: ARM (vector table/Thumb/MMIO peripheral cross-referencing) → [[re-arm]]; RISC-V (RV32/RV64/ecall) → [[re-riscv]] (cross-check before choosing the right qemu- prefix)
- Observe dynamic behavior inside emulation → [[re-tracing]] + [[re-gdb]] (by default inside the sandbox, [[re-analyze/platform-tips]] highest principle)
- Firmware execution produces communication → [[re-protocol]]; deep dive into ELF inside emulation → [[re-binary-core]]
- Programs that cannot be emulated fall back to static → [[re-fw-rootfs]]
Common pitfalls and traps
- Crash on peripheral register access: symptom—segmentation fault when reading GPIO/UART registers after the program mmap's a fixed address; cause—QEMU user mode does not emulate peripherals, so the address is unmapped; countermeasure—use strace to locate the access point, LD_PRELOAD a stub that returns simulated values (step 3)
- Wrong architecture choice causes an immediate segfault: symptom—qemu-arm running a MIPS program crashes instantly; cause—did not first
file/readelfconfirm the architecture and endianness (big-endian mips ≠ mipsel); countermeasure—confirm first in step 1, choose the right qemu- - NIC model mismatch → initialization hangs: symptom—the program hangs at NIC initialization and does not exit; cause—the firmware initializes for a specific chip, the default-attached e1000 does not match, and the ioctl never returns; countermeasure—add
-device <型号>and other virtual NICs as the firmware expects, or first-nic noneobserve whether it skips (step 5) - Timestamp/clock function traps: symptom—the program reads strange times (1970/going backwards) and behaves differently from the real device; cause—QEMU's virtual clock is not synchronized with wall-clock time; countermeasure—
-rtc base=utcfix it, or stub out clock_gettime-related calls - Emulating without network isolation: Phenomenon — the firmware actually connects back to the external network (C2/update server); Cause — QEMU creates a NIC + user backend by default (restrict=off), so skipping explicit isolation means it goes online; Countermeasure — for full-system emulation, explicitly
-nic none, and user mode is not a network sandbox either (step 5); before callback analysis, isolate according to [[re-analyze/platform-tips]] - For a quick reference of command families and operation sequences, see [[commands]]; for tool-specific pitfalls and version differences, see [[gotchas]]