How this project decides what is true¶
Reverse engineering fails in a particular way: not by being unable to find things, but by producing confident wrong answers that nothing catches. Everything on this page exists because that happened here and cost real time.
Rule zero¶
The original firmware is the oracle. A difference means we strayed, not that the device is odd.
That is affordable because both firmwares boot the same disk on the same emulated machine. "Is this right" is a measurement -- drive both, screenshot both, subtract -- rather than a judgement.
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¶
This is the rule that gets broken most, always in the same shape: a gap filled with an invention, while the firmware function that would answer it is cited in the same comment. Three times in one session. "…until the reader is found" sat two lines under the reader's address. "Element semantics not pursued" sat in the same paragraph as the function that defines them.
Neither was a shortage of evidence. Both were a stop, written up in prose that reads like a conclusion -- and prose is what no gate can catch.
So there is a tool. 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 -- which was the category all three failures
were in. That list is the worklist.
The loop¶
A finding that has not been through all six steps is not finished, and starting a new dig with an unfinished one behind you is how the tree fills with half-facts.
- Refresh -- re-dump the decompilation before reading anything. Stale symbols cost an hour re-deriving a function that was already named.
- Hypothesise -- in one line, and state what would disprove it. "This is the mount decision" is
a hypothesis; "it writes
BS_FilSysType, so it is the formatter" is what killed it. - Measure -- read the body, or trace the original running on the same bytes.
- Name it -- apply the finding to the Ghidra project, then re-dump so the next read sees the name. At the point it is established, not 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. Never against a fixture we invented.
- Record it -- update the report, flip the row in the worklist, and commit, in the same commit as the code.
Then back to 2. Report each pass as it lands -- hypothesis, evidence, verdict -- rather than batching a summary at the end. Saying a hypothesis died is a result, not a failure.
Prefer the live oracle to reading code¶
The filesystem window holds 1,355 functions. Tracing one boot named the three that matter.
Traps, each one paid for¶
A number that fits is not a mechanism. Check any formula against a case where the inputs actually differ before believing it. Three coincidences in a single day were reported as causes and then refuted.
Scoring candidates on constants finds coincidences. 0xff8 is an end-of-chain marker and a bit
mask. An address that matched every FAT string turned out to be the formatter. 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 co64 support" came
from one reader's literal pool. The string is right there in the image, reached PC-relative, so a
search for a pointer to it finds nothing. Search the image for the string, not the reader for the
constant -- otherwise our limitation gets published as the device's.
A displayed value is not a measurement of the thing it displays. A probe printed a chapter start
as "5682" seconds with %.0f; the reader answered 5681 by integer division. Both correct -- and
asserting the probe's number against the reader would have failed a right answer and sent a session
hunting a bug in a correct walk. Assert what the file carries, in the unit it carries it.
The disassembly beats the decompiler, and indirect calls are where it matters: a blx through a
descriptor slot renders as a plain call, and the C then 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 before theorising: every sample landing on one address names the problem instantly.
Probe hygiene¶
- Hook the instruction that uses a value, not the load before it. Hooking the load reads the previous register contents and produces a plausible, wrong table.
- Hook the ATA command write, not the data fetch: DMA completes after the driver returned, so the caller's frames are gone.
- A long run must write its output incrementally. A 25-minute capture was killed and left nothing because it only wrote its file at the end.
- 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.
And the two rules about other people's work¶
Ask what a thing is called here before building it. Three Python tools were written in parallel to an existing project of the owner's that already did the job, and deleted the same day.
Read the notes before reversing. The readers for the library, the artwork and Genius were all already reversed and written up when they were "discovered" a second time. So were several functions that were already named in the project.