From 70561276feb98d314ccabadc8eb9f97531df6be8 Mon Sep 17 00:00:00 2001 From: "Dr. Christian Giessen" Date: Mon, 27 Apr 2026 12:09:49 +0000 Subject: [PATCH] Rewrite ROM analysis walkthrough for real firmware dumps - Step-by-step format detection using od - Packed vs padded vs boot stream identification - File size divisibility check - Python one-liner to strip 4-byte padding - PM vs DM file explanation - DM file inspection with od - r2 setup including parser warning suppression - Full analysis workflow (aaa, afl, afb, VV) - Pattern recognition for FIR, IIR, init, I/O - Complete r2 command reference --- docs/ROM_ANALYSIS_WALKTHROUGH.md | 189 ++++++++++++++++++++++++------- 1 file changed, 148 insertions(+), 41 deletions(-) diff --git a/docs/ROM_ANALYSIS_WALKTHROUGH.md b/docs/ROM_ANALYSIS_WALKTHROUGH.md index c009292..98c1cd6 100644 --- a/docs/ROM_ANALYSIS_WALKTHROUGH.md +++ b/docs/ROM_ANALYSIS_WALKTHROUGH.md @@ -1,48 +1,134 @@ # ROM Analysis Walkthrough -## 1. Determine the ROM Format +## 1. Identify Your Files -ADSP-2191 instructions are 24 bits (3 bytes). A raw dump can be: +A firmware dump from an ADSP-2191 typically comes as separate +files for each memory space: -- **Packed (3 bytes/word)**: Most common for SPI flash dumps. - Load directly: `r2 -a adsp219x -b 24 dump.bin` -- **Padded (4 bytes/word)**: 32-bit aligned with a leading 0x00. - Strip padding or use `r2 -s 1` to skip the first pad byte. -- **Boot stream**: Contains block headers (target address, byte - count, flags) followed by data. Requires parsing the header - format first. +- **PM files** (Program Memory): 24-bit words — code and PM data +- **DM files** (Data Memory): 16-bit words — variables, buffers -### Quick Format Check +Only PM files contain executable code. DM files are pure data +and cannot be disassembled as instructions. -Look at the first few bytes. If you see `00 00 00` at offset 0 -(a NOP), you likely have packed 3-byte format. If you see -`00 00 00 00` followed by meaningful data at offset 4, it is -probably 4-byte padded. +If you have multiple sets (e.g. three directories), these may be: +- Different firmware versions +- Different memory banks (Block 0, 1, 2) +- Boot loader vs application code -## 2. Find the Entry Point +## 2. Determine the PM Format -The reset vector is at PM address 0x0000. Typical patterns: +ADSP-2191 instructions are 24 bits (3 bytes). A raw dump can be +packed (3 bytes/word) or padded (4 bytes/word, 32-bit aligned). - 0x0000: JUMP main (Type 10a, opcode starts with 0x1C) +### Check with od + +Dump the first 24 bytes and look at the pattern: + + od -A x -t x1 -N 24 firmware_pm.bin + +**Packed (3 bytes/word)** — most common: + + 000000 1c 00 30 00 00 00 50 10 00 41 23 40 ... + +Every group of 3 bytes is one instruction. The first byte of +real code is typically 0x1C (JUMP), 0x00 (NOP), or 0x50/0x30 +(register load). + +**Padded (4 bytes/word)** — some EPROM/JTAG tools: + + 000000 00 1c 00 30 00 00 00 00 00 50 10 00 ... + +There is a leading 0x00 before each 3-byte instruction. +To strip padding: `dd if=input.bin of=output.bin bs=3 count=N` +after removing every 4th byte. + +**Boot stream** — ADSP boot loader format: + + 000000 xx xx xx xx yy yy ... + +Has block headers (target address, byte count, flags) before +the actual code data. Look for a repeating structure of +header + data blocks. The data payload inside is packed 24-bit. + +### Check file size + + ls -la firmware_pm.bin + +- Packed: file size is divisible by 3 +- Padded: file size is divisible by 4 +- Boot stream: neither cleanly divisible + + python3 -c "import os; s=os.path.getsize('firmware_pm.bin'); print(f'Size: {s} bytes, /3={s/3:.1f}, /4={s/4:.1f}')" + +### Verify with a test disassembly + + r2 -a adsp219x -b 24 -e asm.parser=null -q -c "pd 20" firmware_pm.bin + +If you see coherent instructions (register loads, JUMPs, NOPs), +the format is correct. If the output is mostly `unk 0x...` or +nonsensical, try the other format or adjust the start offset. + +## 3. Load in radare2 + +### Packed 3-byte format (direct) + + r2 -a adsp219x -b 24 -e asm.parser=null firmware_pm.bin + +### Padded 4-byte format + +Strip padding first, then load: + + python3 -c " + d = open('firmware_pm.bin','rb').read() + o = b''.join(d[i+1:i+4] for i in range(0, len(d), 4)) + open('firmware_pm_packed.bin','wb').write(o) + " + r2 -a adsp219x -b 24 -e asm.parser=null firmware_pm_packed.bin + +### Suppress the parser warning + +Add to ~/.radare2rc (one-time): + + echo "e asm.parser=null" >> ~/.radare2rc + +## 4. Initial Analysis + + [0x00000000]> aaa # Full auto-analysis + [0x00000000]> afl # List detected functions + [0x00000000]> afb @ main # Show basic blocks + [0x00000000]> VV # Visual control flow graph + +## 5. Find the Entry Point + +The reset vector is at PM address 0x0000. Typical patterns: + + 0x0000: JUMP main (Type 10a, opcode byte 0x1C) 0x0000: NOP (entry at next instruction) -The interrupt vector table occupies the first ~128 PM words, -with 4-word spacing per vector. Most vectors contain RTI (return -from interrupt) or JUMP to a handler. +The interrupt vector table occupies the first ~128 PM words +(0x000-0x17F), with 4-word spacing per vector. Most vectors +contain RTI or JUMP to a handler. -## 3. Identify Code vs Data Regions +## 6. Identify Code vs Data Regions **Code regions** produce coherent disassembly: register loads, compute instructions, jumps, and loops in logical sequence. **Data regions** (coefficient tables, lookup tables) produce -nonsensical disassembly: random-looking mnemonics, impossible -register combinations, jumps to invalid addresses. Mark these -as data in r2: +nonsensical output: random-looking mnemonics, jumps to invalid +addresses, many `unk` opcodes. Mark as data in r2: - Cd 300 @ 0x1000 # Mark 300 bytes as data at offset 0x1000 + Cd 300 @ 0x1000 # 300 bytes as data at offset 0x1000 -## 4. Recognize DSP Patterns +**Null regions** (0x000000 repeated) are uninitialized memory: + + # Find next non-null byte + /x 01 + # Skip to it + s hit0_0 + +## 7. Recognize DSP Patterns ### FIR Filter @@ -51,26 +137,47 @@ as data in r2: MR = MR + MX0*MY0 (SS), MX0 = DM(I0,M0), MY0 = PM(I4,M4); loop_end: ... -Look for: Type 11 (DO UNTIL CE) followed by Type 1 multifunction -instructions with MAC operations. +Look for: DO UNTIL CE + multifunction MAC instructions. ### IIR Filter (Biquad) -Nested loops: outer loop over samples, inner loop over biquad -sections. Contains ASHIFT for scaling between stages. +Nested loops: outer over samples, inner over biquad sections. +Contains ASHIFT for inter-stage scaling. ### Initialization Sequence -Sequences of Type 6/7 instructions loading I/M/L registers. -This sets up circular buffers for the signal processing kernel. +Sequences of Type 6/7 loads (I/M/L register setup). +Circular buffer initialization before entering a processing loop. -## 5. Useful r2 Commands +### I/O Configuration - e asm.arch = adsp219x - e asm.bits = 24 - pd 200 # Disassemble 200 instructions - pD 600 # Disassemble 600 bytes (= 200 instructions) - /x 1c00 # Find unconditional JUMPs - /x 16 # Find DO UNTIL loops - /x 0a # Find RTS/RTI instructions - V # Enter visual mode +IO(addr) = Dreg / Dreg = IO(addr) instructions configure +peripherals: Serial Ports, Timers, DMA, etc. + +## 8. DM File Analysis + +DM files contain 16-bit data words. These are not code. +You can inspect them for patterns: + + od -A x -t x2 -N 200 firmware_dm.bin # 16-bit hex words + od -A x -t d2 -N 200 firmware_dm.bin # Signed 16-bit decimal + +Common contents: +- Filter coefficients (Q15 fixed-point: values near 0x0000-0x7FFF) +- Lookup tables (sine, cosine, window functions) +- Configuration data (peripheral registers) + +## 9. Useful r2 Commands Reference + + pd 200 # Disassemble 200 instructions + pD 600 # Disassemble 600 bytes (= 200 words) + /x 1c # Find unconditional JUMPs + /x 16 # Find DO UNTIL loops + /x 0a # Find RTS/RTI instructions + /x 0b # Find indirect JUMP/CALL + axt @ addr # Who references this address? + axf @ addr # What does this address reference? + VV # Visual graph mode + V # Visual hex/disasm mode + pdf # Print current function disassembly + agf # ASCII control flow graph