How this project decides what is true¶
Reverse engineering does not usually fail by finding nothing. It fails by producing confident wrong answers that nothing catches, so most of what follows is about making a wrong answer visible.
Rule zero¶
The original firmware is the oracle. A difference between it and Chikuma means Chikuma strayed, not that the device is odd.
That is affordable because both firmwares boot the same disk on the same emulated machine, so "is this right" is something you measure - drive both, screenshot both, subtract - rather than something you argue about.
Say which it is¶
Every claim is one of three things, and it says which:
- MEASURED: read from the image, or from a live trace.
- INFERRED: reasoned on top of a measurement.
- OURS: a stand-in, which declares itself in the file, at the place it stands in.
A stand-in must cite a function somebody opened¶
The failure this guards against looks like a conclusion. A gap gets filled with an invention, and the firmware function that would have answered it is cited in the same comment: "…until the reader is found", two lines under the reader's own address.
So make gaps reads every stand-in marker in the tree, pulls the addresses cited in its comment, and
asks the symbol dump what those functions are called now. A citation that is still FUN_xxxxxxxx is
a function nobody has ever opened, and that list is the worklist.
Markers also carry history, which the tool has to tell apart from live debt: about one block in ten describes behaviour that has since been fixed. An uppercase marker means live debt; a closed gap keeps its history in lowercase ("was ours", "no longer ours").
The loop¶
- Refresh: re-dump the disassembly before reading anything, or you will re-derive a function that was named last week.
- Hypothesise: in one line, and say what would disprove it.
- Measure: read the body, or trace the original running on the same bytes.
- Name it: apply the finding to the Ghidra project and re-dump, at the point it is established rather than at the end of the thread.
- Write it: reproduce it, with a test that fails if the behaviour changes, run against the real volume or the real archive rather than a fixture we wrote.
- Record it: update the report, flip the row in the worklist, and commit it with the code.
Then back to 2.
Prefer the live oracle to reading code¶
The filesystem window holds 1,355 functions. Tracing one boot named the three that matter.
Things worth checking before you believe them¶
- A number that fits is not a mechanism. Check a formula against a case where the inputs actually differ, not just against the set it was derived from.
- Constants are ambiguous.
0xff8is an end-of-chain marker and a bit mask. Confirm by behaviour: does it loop, does it scale a cluster number, does it call a block read. - An absence in one place is not an absence everywhere. "The firmware has no
co64support" came from one reader's literal pool; the string is in the image, reached PC-relative, so searching for a pointer to it finds nothing. Search the image for the string. - A displayed value is not a measurement of the thing it displays. A probe printing a chapter
start as "5682" with
%.0fdoes not contradict a reader that answers 5681 by integer division. Assert what the file carries, in the unit it carries it. - The disassembly beats the decompiler, and indirect calls are where it shows: a
blxthrough a descriptor slot renders as a plain call, so the C reads like a settled answer while hiding the question. - If the firmware crashes, it is almost certainly our fault. A stall is often a crash - the firmware's halt path disables the watchdog, pokes the clock controller and spins, so a dead machine looks idle. Profile the PC first; every sample landing on one address names it immediately.
Probe hygiene¶
- Hook the instruction that uses a value, not the load before it, or you read the previous register contents and get a plausible, wrong table.
- Hook the ATA command write, not the data fetch. DMA completes long after the driver returned, so by then the caller's frames are gone.
- Write a long run's output incrementally. A capture that only writes its file at the end leaves nothing behind when it is killed.
- Save a savestate just before anything expensive to reach.
- A probe that prints nothing has usually measured nothing. Empty log, zero hits, "nobody read it": check the hook fired before reporting the result.
Two about other people's work¶
Ask what a thing is called here before building it - this project's tunes-server already syncs an
iPod, and three Python tools written in parallel to it were deleted the same day.
And read the notes before reversing. The readers for the library, the artwork and Genius were all recovered and written up long before anyone went looking for them a second time.