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
This commit is contained in:
@@ -1,48 +1,134 @@
|
|||||||
# ROM Analysis Walkthrough
|
# 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.
|
- **PM files** (Program Memory): 24-bit words — code and PM data
|
||||||
Load directly: `r2 -a adsp219x -b 24 dump.bin`
|
- **DM files** (Data Memory): 16-bit words — variables, buffers
|
||||||
- **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.
|
|
||||||
|
|
||||||
### 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
|
If you have multiple sets (e.g. three directories), these may be:
|
||||||
(a NOP), you likely have packed 3-byte format. If you see
|
- Different firmware versions
|
||||||
`00 00 00 00` followed by meaningful data at offset 4, it is
|
- Different memory banks (Block 0, 1, 2)
|
||||||
probably 4-byte padded.
|
- Boot loader vs application code
|
||||||
|
|
||||||
## 2. Find the Entry Point
|
## 2. Determine the PM Format
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
### 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:
|
The reset vector is at PM address 0x0000. Typical patterns:
|
||||||
|
|
||||||
0x0000: JUMP main (Type 10a, opcode starts with 0x1C)
|
0x0000: JUMP main (Type 10a, opcode byte 0x1C)
|
||||||
0x0000: NOP (entry at next instruction)
|
0x0000: NOP (entry at next instruction)
|
||||||
|
|
||||||
The interrupt vector table occupies the first ~128 PM words,
|
The interrupt vector table occupies the first ~128 PM words
|
||||||
with 4-word spacing per vector. Most vectors contain RTI (return
|
(0x000-0x17F), with 4-word spacing per vector. Most vectors
|
||||||
from interrupt) or JUMP to a handler.
|
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,
|
**Code regions** produce coherent disassembly: register loads,
|
||||||
compute instructions, jumps, and loops in logical sequence.
|
compute instructions, jumps, and loops in logical sequence.
|
||||||
|
|
||||||
**Data regions** (coefficient tables, lookup tables) produce
|
**Data regions** (coefficient tables, lookup tables) produce
|
||||||
nonsensical disassembly: random-looking mnemonics, impossible
|
nonsensical output: random-looking mnemonics, jumps to invalid
|
||||||
register combinations, jumps to invalid addresses. Mark these
|
addresses, many `unk` opcodes. Mark as data in r2:
|
||||||
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
|
### FIR Filter
|
||||||
|
|
||||||
@@ -51,26 +137,47 @@ as data in r2:
|
|||||||
MR = MR + MX0*MY0 (SS), MX0 = DM(I0,M0), MY0 = PM(I4,M4);
|
MR = MR + MX0*MY0 (SS), MX0 = DM(I0,M0), MY0 = PM(I4,M4);
|
||||||
loop_end: ...
|
loop_end: ...
|
||||||
|
|
||||||
Look for: Type 11 (DO UNTIL CE) followed by Type 1 multifunction
|
Look for: DO UNTIL CE + multifunction MAC instructions.
|
||||||
instructions with MAC operations.
|
|
||||||
|
|
||||||
### IIR Filter (Biquad)
|
### IIR Filter (Biquad)
|
||||||
|
|
||||||
Nested loops: outer loop over samples, inner loop over biquad
|
Nested loops: outer over samples, inner over biquad sections.
|
||||||
sections. Contains ASHIFT for scaling between stages.
|
Contains ASHIFT for inter-stage scaling.
|
||||||
|
|
||||||
### Initialization Sequence
|
### Initialization Sequence
|
||||||
|
|
||||||
Sequences of Type 6/7 instructions loading I/M/L registers.
|
Sequences of Type 6/7 loads (I/M/L register setup).
|
||||||
This sets up circular buffers for the signal processing kernel.
|
Circular buffer initialization before entering a processing loop.
|
||||||
|
|
||||||
## 5. Useful r2 Commands
|
### I/O Configuration
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
e asm.arch = adsp219x
|
|
||||||
e asm.bits = 24
|
|
||||||
pd 200 # Disassemble 200 instructions
|
pd 200 # Disassemble 200 instructions
|
||||||
pD 600 # Disassemble 600 bytes (= 200 instructions)
|
pD 600 # Disassemble 600 bytes (= 200 words)
|
||||||
/x 1c00 # Find unconditional JUMPs
|
/x 1c # Find unconditional JUMPs
|
||||||
/x 16 # Find DO UNTIL loops
|
/x 16 # Find DO UNTIL loops
|
||||||
/x 0a # Find RTS/RTI instructions
|
/x 0a # Find RTS/RTI instructions
|
||||||
V # Enter visual mode
|
/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
|
||||||
|
|||||||
Reference in New Issue
Block a user