When to use / when not to use
- Use: MIPS architecture ELF samples/malware (executables extracted from router and embedded device firmware)
- Use: logic analysis of programs inside router firmware (web backends, services, utility binaries)
- Use: MIPS big-endian (mips)/little-endian (mipsel) binaries of any endianness
- Do not use: other architectures such as ARM / x86 (general foundation [[re-binary-core]], this skill only supplements MIPS-specific semantics)
- Do not use: when you only need to unpack firmware and look at the contents (go to [[re-fw-extract]])
- Do not use: when you only need to analyze the filesystem/configuration (go to [[re-fw-rootfs]])
- Do not use: when you need to run the firmware as a whole (go to [[re-fw-emulate]])
Tool preparation
Verify all tools before use. Static analysis can be done without a sandbox; qemu-user dynamic execution defaults to sandbox + network isolation ([[re-analyze/platform-tips]] highest principle).
Decompiler (choose either Ghidra / IDA)
- Ghidra (built-in MIPS processor modules, with complete mips / mipsel / mips64 / mips64el variants):
- Linux: official release package (requires JDK); some distribution repositories have
apt install ghidra/pacman -S ghidra - macOS:
brew install --cask ghidra; Windows: official zip - Verify:
analyzeHeadless -help(headless mode) or import a MIPS ELF via the GUI
- Linux: official release package (requires JDK); some distribution repositories have
- IDA: the commercial version includes a MIPS module; the architecture support scope of the Freeware version is subject to the official page
- When importing, confirm the processor variant and endianness are correct (mips big-endian / mipsel little-endian); choosing wrong makes everything misaligned
binwalk / unblob —— firmware and embedded file scanning
- For installation channels and version differentiation (v3 mainline / v2 legacy), see the binwalk/unblob installation matrix in [[re-fw-extract]]
- Verify:
binwalk --version、unblob --version
readelf / file —— architecture and byte order confirmation (binutils)
- Linux: bundled with binutils (
apt install binutils, etc.); macOS:brew install binutilsor LLVM-based readelf; Windows: inside WSL - Verify:
readelf --version
qemu-user —— MIPS user-mode emulation (dynamic verification)
- Linux:
apt install qemu-user/dnf install qemu-user/pacman -S qemu-user - macOS:
brew install qemu(including user mode); Windows/WSL: Linux version inside WSL - Big-endian and little-endian are separate binaries:
qemu-mips(big-endian) /qemu-mipsel(little-endian), 64-bit isqemu-mips64/qemu-mips64el - Verify:
qemu-mips --version、qemu-mipsel --version
python3
- Linux:
apt install python3/dnf install python3/pacman -S python - macOS: built in; Windows: official installer
- Verify:
python3 --version
Operation steps
Execute in order, archive the result of each step; step 5 (dynamic execution) defaults to sandbox.
-
Prerequisite: firmware extraction and architecture confirmation:
- Whole firmware file → unpack with [[re-fw-extract]]; extracted filesystem → [[re-fw-rootfs]]; extracted ELF continues with this step
- Architecture confirmation:
file target.elf # 是否 MIPS + 端序提示 readelf -h target.elf # Machine=MIPS;Data=big-endian/little-endian readelf -A target.elf | head # 子版本/ABI 附加信息 fileEnter this skill's workflow only if the output contains "MIPS"; if not MIPS, go to the [[re-binary-core]] general foundation
-
Endianness determination (big-endian / little-endian):
fileOutput MSB → big-endian (processor variant mips); LSB → little-endian (mipsel)readelf -hThe Data field directly gives the endianness; e_flags contains ABI information (o32 / n32 / n64)- strings readability verification: big-endian data appears byte-reversed from a little-endian perspective (e.g., magic number
hsqsappears in reverse order) - After confirming, import into the decompiler and select the correct variant, then handle $gp, jump targets, and string offsets throughout according to that endianness—if the endianness is wrong, the data, instructions, and addresses will all be misaligned
-
Decompilation notes (MIPS-specific semantics):
- Delay slot (hardware feature): the ordinary branch/jump in pre-R6 (
beq/bne/j/jal) is followed by one instruction that executes on both the taken / not-taken paths (control transfer occurs after execution);Branch Likely(beql/bnel/blezl/bgtzl/bltzl/bgezland FP likely types) are different—the delay slot executes only when taken, and when not-taken the delay slot is annul(skipped);beq/bne/j/jalis often followed by an assembler-filled nop or a useful instruction; when patching, the delay slot after a branch must be handled (fill with nop or an equivalent instruction); MIPS32r6 removes branch-likely and adds a compact branch form with no delay slot (beqc/bnec/bc/balc/jic/jialc), butbeq/bne/jalr/jrstill has a delay slot—classify by ISA revision + opcode, and do not say categorically that R6 eliminates delay slots - Calling convention: $a0-$a3 pass the first 4 arguments (extra arguments passed on the stack), $v0 is the return value, $ra is the return address (
jalis written automatically), $s0-$s7 are callee-saved, $t0-$t9 are caller-saved; PIC indirect call patternjalr $t9($t9 loads the address of the called function, and the function prologue often reloads $gp) - $gp and .got: $gp (r28) generally points to the middle of .got (commonly .got+0x7FF0, with a ±32KB offset covering the entire GOT); data access takes the form
lw $t0, off($gp)/addiu $t0, $gp, off; the function prologue commonly haslui gp, %hi(...)+addiu gp, gp, %lo(...)resetting $gp - -mlong-calls trampoline:
j/jalimmediate jump only covers the same 256MB segment (26-bit address shifted left by 2 bits); cross-segment calls use compile-time generated stub trampolines (first load the address thenjalr); seeing blocks oflui + addiu + jalrthree-instruction sequences are mostly trampolines, do not treat them as business logic - Decompilers (such as Ghidra) handle delay slots and GOT references by default, but when symbols/static chains are stripped the $gp value needs manual confirmation (see pitfall 3)
- Delay slot (hardware feature): the ordinary branch/jump in pre-R6 (
-
Router firmware specifics:
- Locating the web backend httpd: in the rootfs find httpd-family programs (httpd / mini_httpd / boa / lighttpd and vendor-renamed versions) and web directories (www / htdocs / cgi-bin); search strings for "GET " / "HTTP/1" / response header strings to locate the main request loop
- Authentication bypass / command injection patterns: search
system(/popen(/execcall sites → trace back whether the argument source is a URL parameter or CGI field; a common authentication flaw is the backend trusting client-controllable fields (cookie, Referer, fixed password) or hardcoded password comparison - Identifying busybox integrated commands: busybox is an all-in-one—multiple applets share the same main and dispatch by argv[0]; how to locate: at runtime
busybox --list, statically find the applet table in the symbol table (applet_names); multiple applet logics in a single binary, avoid mistakenly judging "one function handles everything" - Config and password hash extraction: /etc/passwd, /etc/shadow, /etc/config configuration directory (common in open-source router systems) and web backup configs; password hashes commonly
$1$(MD5-crypt)、$5$/$6$(SHA-crypt) and custom formats; backup configurations are often base64 / gzip / custom-packed (connecting to [[re-fw-rootfs]] and [[re-crypto-id]])
-
Dynamic validation (sandbox):
- Default sandbox principle: before running MIPS programs with qemu-user, first enter [[re-sandbox]] (network isolation + snapshot)
-
qemu-mips -L <rootfs路径> ./target # 大端;小端用 qemu-mipsel qemu-mips -L <rootfs路径> -strace ./target # 系统调用跟踪 qemu-mips -L <rootfs路径> -E <ENV=val> ./target -LSpecify rootfs as the source of dynamic loaders/libraries (MIPS programs often depend on firmware-internal libc and configuration)- Dynamic is only for behavior validation (file/network/configuration reads); the main logic is primarily static
Cross-domain federation
- [[re-firmware]]: gateway path for firmware-type samples (re-firmware → re-fw-extract → re-fw-rootfs → this skill)
- [[re-fw-extract]] / [[re-fw-rootfs]]: prerequisites for firmware unpacking and filesystem/configuration analysis
- [[re-binary-core]]: general MIPS ELF reconnaissance/decompilation/debugging foundation (sub-skills such as [[re-ghidra]] and [[re-gdb]] are used as usual)
- [[re-vuln]]: vulnerability confirmation and exploitation validation for web authentication bypass/command injection
- [[re-sandbox]]: mandatory prerequisite for all dynamic execution ([[re-analyze/platform-tips]] default sandbox principle)
- Supporting: [[re-emulation]] (use Unicorn to emulate MIPS instructions in scenarios without QEMU), [[re-fw-emulate]] (when the entire firmware needs to be booted), [[re-crypto-keys]] (hardcoded keys in configurations/firmware), [[re-patching]] (byte patching aware of delay slots)
Common pitfalls and traps
- Delay slot execution flow misalignment (including annul semantics of branch-likely): Symptom—the logic at a manually read branch does not line up; the instruction immediately following the branch "seemingly should not execute" but changes state (or conversely, when a likely branch is not-taken, the instruction in the slot does not execute); Cause—MIPS hardware characteristic: after pre-R6 ordinary branches/jumps (beq/bne/j/jal), the immediately following instruction executes before control transfers on either path of the jump, whereas Branch Likely (beql/bnel/blezl/bgtzl/bltzl/bgezl and FP likely types) executes the delay slot only when taken, and annuls when not-taken; Countermeasure—distinguish non-likely from likely: for the former, count the delay slot instruction into both jump paths; for the latter, count it only in the taken path; patch by adding a nop/equivalent instruction after the branch; when relying on decompiler output, verify the restored position of the delay slot instruction
- Wrong endianness determination → everything becomes garbled: Symptom—strings, immediates, and addresses are all garbled, and decompilation is unrecognizable; Cause—MIPS has both big-endian and little-endian variants (router firmware is mostly big-endian, but little-endian also exists); after reading the wrong endianness, all 4-byte whole words are misaligned; Countermeasure—
file(MSB/LSB)+readelf -hTriple confirmation with the Data field + strings readability, and choose the correct mips / mipsel variant in the import tool - Without relocation information, the $gp base address can only be guessed: Symptom—
lw $t0, off($gp)points to something unknown, and GOT references cannot be resolved; Cause—after symbols are stripped/static linking, relocations are lost, and the $gp value (commonly .got+0x7FF0) must be confirmed manually; Countermeasure—in_start/ the crt0 prologue'slui gp+addiu gpcalculate the actual gp value and annotate it in the decompiler; when there are no symbols, infer it from the .got segment base address - Non-standard headers/multi-layer compression in router firmware: Symptom—binwalk cannot extract it, or the extracted content is not a filesystem, and the web backend cannot be found; Cause—vendor custom headers + multi-layer compression/nested packing (same common pitfall as [[re-fw-extract]]); Countermeasure—manually inspect magic numbers with hexdump, skip the header offset and split with dd, confirm layer by layer with file, and then analyze
- Differences in decompiler tool handling of pseudo-instructions: Symptom - the same function has large output differences across different decompilers, and the expansion of li / la / move is inconsistent; Cause - the MIPS assembler expands pseudo-instructions in various ways (li split into lui+ori, etc.), and tools restore them according to their own rules; Countermeasure - take machine-code semantics as authoritative, cross-compare tools, and verify key logic (delay slots / trampolines / GP references) back at the instruction level
- Misjudgment of busybox single-binary multiple applets: Symptom - in one binary, the logic "does everything," and no entry point can be found; Cause - busybox dispatches applets according to argv[0], and all logic shares one main; Countermeasure -
busybox --listor the symbol table applet table (applet_names) to locate the specific applet, and use the corresponding subcommand entry as the starting point for analysis