Route 0xff80 - tiredboffin/fffw GitHub Wiki
Note: This isn’t a step-by-step guide or a polished technical write-up. Think of it as a series of notes and discoveries made while exploring Fujifilm firmware and the mysterious 0xff80 interface. Some parts are more story than documentation, mixing experiments, failed attempts, and the occasional accidental success. If you’re here for a clean protocol spec, look elsewhere—but if you want to see how things were actually poked, prodded, and sometimes broken along the way, you’re in the right place.
Knocking on the Back Door
It started with the X-T4 — a camera I liked so much I wanted to void its warranty. I got curious about how it actually worked at the lowest level, and stumbled across the Fujihack project on GitHub. That’s where I learned about @petabyt’s work on the X-A2, which included static analysis, USB poking, and even running Doom on the thing. I was hooked.
I didn’t want to risk bricking my beloved X-T4, so I looked for something cheaper and closer to the X-A2 under the hood to experiment on. My idea was to first replicate Fujihack on a similar camera and pick up some skills with the tools along the way. I grabbed a used X-E2, fully expecting it to be a throwaway test mule — but honestly, it turned out to be a really enjoyable camera. With firmware 4.00 and up, the X-E2 basically turned into a whole new camera: updated menus, extra film simulations, and all sorts of other goodies. It’s not nearly as ancient as its release date might make you think -- now almost-forgotten Kaizen in action.
The original Fujihack approach was all about exploiting a vulnerability in the USB PTP handler—basically allowing the injection of arbitrary code and dumping the camera’s RAM. That made it possible to disassemble the memory dump and get a much deeper look at the firmware and what was actually going on under the hood. The source code had a bunch of stubs and hints, but it definitely wasn’t a step-by-step guide for porting the exploit to other cameras. Let’s just say it was… left as an exercise for the reader.
And I, as the reader, lacked the required background in exploit development to complete that exercise. The hardware was different enough, the details sparse enough, and my exploit skills weak enough that I hit a dead end.
Front Door
Since I couldn’t get the exploit working, I shifted focus to something more tractable: the firmware file itself. It looked compressed but not encrypted, and thanks to Fujihack, I already knew it wasn’t digitally signed. That made it a much more approachable target.
Most of the file was compressed -- which blocked static analysis -- but if I could decompress it, I’d have access to more code and structure to work with.
I initially expected the decompression algorithm to be recoverable by analyzing the uncompressed sections of the firmware code. After all, the firmware loader must include logic to decompress the data, and that implementation should, in principle, be accessible through reverse engineering. However, the reality proved more complex. Disassembly revealed that decompression is handled entirely by a dedicated hardware IP block integrated into the SoC. The firmware’s role is limited to initializing the source and destination memory pointers and invoking the hardware decompressor, which processes each 16 KB block via DMA. The only software involvement is a straightforward interrupt handler that advances the source pointer and triggers decompression for subsequent 16 KB pages. There is no explicit decompression logic in the firmware itself—only a minimal control loop delegating the process to hardware.
Having previously worked with various compression formats, I was reasonably confident that I could identify the patterns and attempt to infer the underlying algorithm. The presence of ASCII fragments within the compressed regions suggested a relatively simple scheme—likely LZ-based, without any entropy coding such as Huffman or arithmetic coding. This assumption proved correct: the format appeared to belong to the broad family of algorithms originating from the Ziv-Lempel 1977 paper. However, I was unable to match it exactly to any known published variant.
The result was ffun — a tool that parses Fujifilm DAT firmware files, decompresses the relevant sections, and verifies checksums when possible. It works in two stages: first, ffun
extracts the individual binary segments and generates a “manifest” file (in JSON) that lists all the extracted files, their RAM addresses, and any other identified metadata to keep track of everything.
ffun
can then use this "manifest" to:
-
Generate an ELF file that includes memory layout and limited debug information for select functions and structures. The ELF format can be loaded into Ghidra, IDA, or other disassemblers.
-
Generate a Ghidra script to load and annotate a raw binary with segment names, labels, and structure definitions -- often more flexible and richer than working with ELF alone.
-
Rebuild a valid DAT file from modified binary segments, restoring compression and checksums.
This enables meaningful static analysis without hardware or code execution access -- though of course, some limitations remain.
Having a RAM dump from the X-A2 helped a lot. It let me compare known-good memory contents against decompressed output and confirm I was on the right track. At one point, I thought I’d broken something -- some of the decompressed text looked completely mangled.
Turned out it was just Japanese. Not a bug. Just me not reading it.
For Staff Only
While looking through the decompressed firmware, I noticed something interesting: leftover debug strings. They referenced test commands, memory access, and what looked like a structured request-response protocol. It was clear some kind of diagnostic interface was still in the code -- possibly over USB.
I already knew that the camera could enter a “service mode,” where it identifies over USB with Fujifilm Vendor Id 04cb
and Product ID ff80
. It was not known though how this interface actually worked or what kind of communication it expected.
Guessing USB commands blindly didn’t get me far. I tried poking at the interface by sending reads, writes, and control messages to its endpoints. Nothing useful came back -- just silence. Whatever this mode did, it wasn’t responding to random USB traffic.
Eventually, Google turned out to be more helpful than USB fuzzing. A search for the Vendor and Product Id turned up something unexpected: an old FinePix CX400 printer Windows XP driver. Buried in the .INF
file was the same vendor ID, and along with it came a DLL named `FTLUSB.dll'
That DLL turned out to be the jackpot. By reverse engineering it, I was able to extract the protocol used to communicate with Fujifilm service devices. Once I understood the structure -- basic framing, command IDs -- I tried it on the camera.
And it worked. The camera responded to my test packet -- a basic “ping” -- with actual data. The interface was alive.
Once I had the low-level protocol mapped out, the next step was hunting down its implementation in the firmware to see how the camera actually uses it (since, let’s be honest, it’s not really a printer). Thanks to some distinctive constants—and a bit of luck—finding the handler wasn’t too difficult. The disassembled code matched up perfectly with the structure I’d already reverse engineered from the DLL: opcode dispatch, length fields, session IDs, checksums, and so on. Each command had its own handler, and some of them just straight-up exposed internal memory or hardware routines.
With that figured out, I started building my own host-side tool to talk to the camera over the protocol. I called it ff80, after the USB ID used in service mode. It started out pretty basic—just enough to ping the camera and get some responses—but it quickly grew as I added support for more commands and tweaked the framing logic. It felt like I was building client software for something never meant to be public -- internal tools left behind, undocumented but functional.
With ff80
in hand and the protocol mapped out, it was time to explore.
Peeking at Memory
ff80
allows easy RAM dumping , for e.g. command
ff80 ram read -o output.bin 0x0
reads as much as possible starting from address 0x0 — effectively replicating RAM dump that was achieved on the X-A2 via exploit, but now applicable to more cameras without needing code injection. However, it has some limitations: some pre EXR-II cameras implement access restrictions in software, but those can be bypassed with minimal effort. On newer cameras (from X Processor Pro) MMU protects some memory regions — for e.g. safeguard to catch stack corruption. All normal memory remains readable, though executable memory is not writable. (for more on CPU in Fujifilm read for example wiki)
Using ff80 ram read/write
commands, I could inspect, dump and modify different regions of memory in a live system — a huge help for verifying my assumptions during static analysis. Being able to match code and data found in the DAT file against their live in-memory representations helped me pin down function boundaries, confirm segment base addresses, and better understand system layout.
On EXR* cameras, since executable memory is writable, it’s trivial to inject your own code. That makes it easy to poke around, try out different paths, and quickly experiment or reverse-engineer the camera’s logic.
Booting Without Permission
One of the more interesting discoveries came from a block mapped at 0x80000000
on the X-E2. It didn’t match anything from the main firmware — and looked suspiciously like ROM.
Finding it wasn’t exactly buried treasure; it was just sitting there, exposed, ready to be dumped and disassembled. No protection, no scrambling. Just another room on the other side of the front door.
Reversing the ROM opened up a lot: bare-metal initialization code, early boot logic, and — most interestingly — SD card and EEPROM access routines. EXR II cameras like the X-E2 and X-A2 appear to share the same boot ROM, while older EXR models use an earlier variant that supports fewer types of EEPROMs and lacks some later features. The ROM includes boot snippets for multiple storage types — NAND, eMMC, and FAT-formatted SD cards — making it flexible in how it can bring up the system. that worked before the main firmware was even loaded. That meant I could, in theory, write code that ran without loading any part of the normal system.
So I did.
I wrote a minimal SD bootloader that could blink an LED on the X-E2 — bare metal, no firmware required. That evolved into a small proof-of-concept platform that could read and write files on the SD card using FAT, with the eventual goal of both analyzing the EEPROM content and loading patched firmware images directly from disk.
The next step I had in mind was to craft custom recovery software to bring back a bricked camera -- to build a tool that could convert an existing firmware into something bootable from the SD card in “Firmware Update” mode. That way, recovery wouldn’t depend on knowing the exact NAND or EEPROM layout for every model — and it’d reduce the risk of corrupting the EEPROM entirely. Cool in theory, but to actually pull it off, I’d need a much deeper understanding of the firmware’s structure and internal logic.**
So I put this bare-metal side quest on hold. I learned a lot of new and interesting things — though, as it turned out, not everything new was interesting, and not everything interesting was new.
There had to be something more productive to do with this than turn X-E2 camera into a five dollar Arduino, right?
Settings Cabinet
In the camera’s RAM address space, there’s a special region I refer to as cfgdata
in ff80
. At boot time, the firmware loads configuration data into this section — including the serial number, calibration values, and a ton of other parameters. While it’s just ordinary RAM, it’s accessed via dedicated ff80
functions rather than generic memory read/write commands. This means that even though the RAM address of cfgdata
may change between camera models and firmware versions, access to it remains consistent across all models. These cfgdata
functions behave uniformly, but the internal structure of the data itself varies. Only a handful of basic parameters have fixed, stable offsets within cfgdata
.
When accessed through ff80
, the API can read these parameters either from RAM or directly (and slowly) from EEPROM. Writes, however, go only to RAM — so to persist changes, one must issue a ff80 cfgdata save
command to commit them back to EEPROM.
Some of the parameter offsets are expected — serial number(s), model ID, firmware internal version number, calibration data etc — but others are surprising. For example, offset 0xf7
enables a high-level debug mode: the camera begins logging certain internal operations, and those logs can be read in real time over ff80
. It’s also possible to configure the camera to boot straight into ff80
mode, bypassing the normal USB PTP stack — though this disables standard USB functionality.
Another well-known offset is 0xA2
, which enables AUTO.SCR script processing — a key element in the original Fujihack exploit. There are plenty of other offsets tied to subsystem diagnostics and internal flags.
Figuring out what each offset does isn’t trivial. Right now, the only way is to disassemble the firmware, identify what code references each value, form a hypothesis about its purpose, and then test it on real hardware.
This is how I identified additional "debug" parameters that enable logging and specialized diagnostic behaviors within specific camera subsystems. On the X-E2, for example, it’s possible to configure the camera to save intermediate files from the image processing pipeline—such as the "true" raw sensor data or the demosaiced (but not yet compressed) data, similar to TIFF files. It’s also possible to enable very verbose logging. Interestingly, some of these logs appear to be intended not for human consumption, but as input for off-camera automated analysis—likely as part of an internal test system. The in-camera diagnostic features appear to change from model to model, but the overall approach has remained largely consistent across generations, even in the latest cameras.
Service Room
While exploring the capabilities of the ff80 cfgdata
interface, I discovered what I call app80
— a set of internal API functions likely used by Fujifilm’s service software at repair centers. These functions can perform all sorts of risky and powerful operations: testing buttons and dials, accessing embedded flash, calibrating PDA(phase-detect autofocus), adjusting power regulators, compensating for dead pixels and even sensor scratches. There are dozens of them, all accessible via ff80
. The trick is to write a request into a specific cfgdata offset in the configuration area. Writing a byte to address 0x80
triggers execution of the corresponding function code.
However, I also found that the API is highly camera-specific. I suspect Fujifilm provides different versions of its service tool for each model — or at least scripts tailored to each camera (and possibly even firmware version).
Since app80
can permanently modify camera settings — and very easily cause the camera to misbehave — I doubt a generic or public version of the interface will ever be released. It’s clearly meant for controlled use in service environments, not for casual experimentation.
But it’s a valuable research tool if used with caution. For example, mapping GPIOs to physical buttons and dials became trivial — I simply pressed buttons on the camera while watching GPIO states update in real time via app80
. The insights gained through app80
then helped make sense of some parts of the disassembled code.
to be contiuned