This research started in 2013, so if you find some of my methods silly and dangerous - you are right, they were. However, I still learnt a lot from the process.


It all started several months before my first child was born. My wife and I always wanted a Leica camera and suddenly we realized that if we didn’t buy it now, we will not be able to for a while. So we put in an order for M240 and … bam, backlog for almost half a year. Pretty soon I got bored waiting and started to explore the Leica website. The downloads section caught my attention immediately. Well, you can guess why - firmware images!

Leica firmware files

Leica M8 firmware

It was unencrypted, uncompressed file (m8-2_005.upd) starting with PWAD magic[1]. Does anyone recognise it? Yes you got it right - Doom Patch WAD format. These guys seem to love the classics. The format is very well documented and writing the splitting tool was a pretty simple task to do[2].

It is actually very funny, because later when I was looking into the compressed Leica T firmware, the first thing I did was to check the compression methods used by id Software in the past. Wikipedia says they used LHA[3] which is essentially LZW. But when I tried some generic LZW decompressors it didn’t work, so I started to look for an id Software specific implementation and voila, the one from Catacomb Armageddon source[4] was spot on. I have to admit it was a lucky coincidence.

Anyway, back to M8, this is the firmware structure

RULES: 0x0000008C ( 3036:0x00000BDC) - XML description
LUTS: 0x00000C68 ( 183274:0x0002CBEA) GAMMA: 0x0000007C ( 31760:0x00007C10) GAIN: 0x00007C8C ( 50344:0x0000C4A8) LEICA: 0x00014134 ( 7000:0x00001B58) BLEMISH: 0x00015C8C ( 250:0x000000FA) WREF: 0x00015D88 ( 82480:0x00014230) OBJ: 0x00029FB8 ( 11268:0x00002C04) VERSION: 0x0002CBBC ( 46:0x0000002E)
PXA: 0x0002D854 ( 858384:0x000D1910)
BF: 0x000FF164 ( 134522:0x00020D7A) - Analog Devices Blackfin Processor family
GUI: 0x0011FEE0 ( 3574180:0x003689A4) TRANS: 0x0000005C ( 59988:0x0000EA54) - localization IMAGES: 0x0000EAB0 ( 267433:0x000414A9) 21_1PRT: 0x000000CC ( 18411:0x000047EB) - JFIF image 21_2GRP: 0x000048B8 ( 23172:0x00005A84) - JFIF image 21_3PAN: 0x0000A33C ( 23034:0x000059FA) - JFIF image 24_1PRT: 0x0000FD38 ( 18489:0x00004839) - JFIF image 24_2GRP: 0x00014574 ( 23230:0x00005ABE) - JFIF image 24_3PAN: 0x0001A034 ( 22998:0x000059D6) - JFIF image 28_1PRT: 0x0001FA0C ( 22605:0x0000584D) - JFIF image 28_2GRP: 0x0002525C ( 23081:0x00005A29) - JFIF image 28_3PAN: 0x0002AC88 ( 23282:0x00005AF2) - JFIF image 35_1PRT: 0x0003077C ( 22496:0x000057E0) - JFIF image 35_2GRP: 0x00035F5C ( 23532:0x00005BEC) - JFIF image 35_3PAN: 0x0003BB48 ( 22881:0x00005961) - JFIF image FONT1: 0x0004FF5C ( 1522988:0x00173D2C) FONT2: 0x001C3C88 ( 1723676:0x001A4D1C) VERSION: 0x003689A4 ( 0:0x00000000)
M16C: 0x00488884 ( 130406:0x0001FD66) - Renesas M16C Family (Motorola S-record)
FPGA: 0x004A85EC ( 131604:0x00020214) - Xilinx Spartan 3
FSL: 0x004C8800 ( 814:0x0000032E) - First Stage Loader

IDA doesn’t support Blackfin processors out of the box, but one third-party plugin does[5].

Leica M9 firmware

This one (m9-1_196.upd) looked encrypted (histogram shows distribution around 0.45%).
End of story? Maybe not, because Leica used to put pretty weak CPUs in their cameras and XOR encryption was very popular at that time in consumer electronics, so I decided to write a simple XOR manipulation tool to compare the firmware with itself and calculate some statistics along the way.

Key length was determined by looking for the longest repeating pattern. This makes sense since any firmware usually includes big blocks of repeating data like 0x00/0xFF paddings or graphics with LUT pixels. The key itself is calculated based on per byte statistics within key length where most frequently occurring byte goes to key buffer. The output clearly pointed to XOR encryption. Then it was a matter of modifying my tool a bit to get a potential key and decrypt[6]. Yet again, it was PWAD file after decryption.

The PWAD contents revealed the following structure:

RULES: 0x0000007C ( 2788:0x00000AE4) - XML description
LUTS: 0x00000B60 ( 4060616:0x003DF5C8) PROCESS: 0x0000004C ( 3900572:0x003B849C) CREATE: 0x0000004C ( 20:0x00000014) - timestamp LUTS: 0x00000060 ( 427744:0x000686E0) GAINMAP: 0x00068740 ( 20008:0x00004E28) LENS: 0x0006D568 ( 3452724:0x0034AF34) CCD: 0x003B84E8 ( 148662:0x000244B6) CREATE: 0x0000004C ( 20:0x00000014) - timestamp BLEMISH: 0x00000060 ( 1092:0x00000444) WREF: 0x000004A4 ( 147452:0x00023FFC) LIN: 0x000244A0 ( 22:0x00000016) ICCPROF: 0x003DC9A0 ( 4304:0x000010D0) ECI-RGB: 0x0000003C ( 540:0x0000021C) sRGB: 0x00000258 ( 3144:0x00000C48) A-RGB: 0x00000EA0 ( 560:0x00000230) WBPARAM: 0x003DDA70 ( 7000:0x00001B58)
BF561: 0x003E0128 ( 289128:0x00046968) - Analog Devices Blackfin Processor family bf0: 0x0000004C ( 117846:0x0001CC56) - main processor firmware bf1: 0x0001CCA4 ( 117826:0x0001CC42) - sub-processor firmware 0x000398E8 ( 27072:0x000069C0) - main processor firmware map with symbols :D 0x000402A8 ( 26304:0x000066C0) - sub-processor firmware map with symbols :D
BODY: 0x00426A90 ( 143280:0x00022FB0) - Renesas M16C Family (Motorola S-record)
GUI: 0x00449A40 ( 3647624:0x0037A888) TRANS: 0x0000005C ( 131656:0x00020248) - localization IMAGES: 0x000202A4 ( 267433:0x000414A9) 21_1PRT: 0x000000CC ( 18411:0x000047EB) - JFIF image 21_2GRP: 0x000048B8 ( 23172:0x00005A84) - JFIF image 21_3PAN: 0x0000A33C ( 23034:0x000059FA) - JFIF image 24_1PRT: 0x0000FD38 ( 18489:0x00004839) - JFIF image 24_2GRP: 0x00014574 ( 23230:0x00005ABE) - JFIF image 24_3PAN: 0x0001A034 ( 22998:0x000059D6) - JFIF image 28_1PRT: 0x0001FA0C ( 22605:0x0000584D) - JFIF image 28_2GRP: 0x0002525C ( 23081:0x00005A29) - JFIF image 28_3PAN: 0x0002AC88 ( 23282:0x00005AF2) - JFIF image 35_1PRT: 0x0003077C ( 22496:0x000057E0) - JFIF image 35_2GRP: 0x00035F5C ( 23532:0x00005BEC) - JFIF image 35_3PAN: 0x0003BB48 ( 22881:0x00005961) - JFIF image FONT1: 0x00061750 ( 1522988:0x00173D2C) USBLOGO: 0x001D547C ( 1775:0x000006EF) - JFIF image FONT2: 0x001D5B6C ( 1723676:0x001A4D1C)
FPGA: 0x007C42C8 ( 150176:0x00024AA0) - Xilinx Spartan 3A
BF547: 0x007E8D68 ( 937576:0x000E4E68) - Analog Devices Blackfin Processor family (FSL?)

Leica M240 firmware

It became a habit to check Leica firmware download page every morning. Eventually a new file became available - FW_M240_1_1_0_2.FW.

It didn’t look encrypted, but it did look compressed…


The histogram had a huge spike on 0x9D.
Probably this is some kind of compression magic. Googling for “9D” and “compression” didn’t help apart from the fact that 0x1F9D is used as LZW compression signature[7]. Just in case, I got myself familiar with LZ compression types and decided to look around for all bytes following 0x9D. I found four different patterns:

  1. 9D 70 C4
  2. 9D 00
  3. 9D XX YY
  4. 9D XX 8Y YY

My observations regarding these pattern types were:

  • (1) appears once at address 0x30, probably used as compressed data indicator
  • XX is never bigger than 0x7F
  • last byte YY in (3) and (4) is never bigger than 0x7F

From what I learnt about LZ, it looks a lot like LZ77 or LZSS if YY is the step back distance and XX is the count of bytes to copy. And (2) is a special case to output 0x9D. Writing a simple C function implementing this logic confirmed that it was heading in the right direction, but still not quite there yet because of (4).

I spent some time trying different ways to interpret it, but nothing worked. So I just asked some other folks what they thought and one guy noticed that according to my own observations fourth byte YY appears only when highest bit of 0x8Y is set adding extra length to step back distance. Shame on me, it was so obvious. Finally the decompressor started to output a valid stream… until it stuck somewhere in the middle of the firmware file. This was due to an unknown length of a sliding window. Extra debugging and experiments fixed it.

Then it was time for a new tool to work with M240 firmware files[8].

Firmware structure

To deal an unknown file format, I couldn’t think of anything better than to measure some offsets and sizes in the file and try to find the closest values in the file header. Like this block for example:

0x00: 1E 1C AF 2E 01 01 00 02 07 E1 EA 5E 00 5C 1A B1
0x10: 01 29 1A 7E AE 38 73 65 9C 3D 75 B4 34 2F 44 6E
0x20: 13 17 8E 6B 00 00 00 01 00 00 00 30 E1 E3 50 D1

eventually turned into:

1E1CAF2E - looks like "LEICA FILE"
01010002 -
005C1AB1 - compressed file size (big endian)
01291A7E - uncompressed file size (big endian)
AE3873659C3D75B4342F446E13178E6B - MD5 hash
00000001 - number of payloads
00000030 - first payload offset

The tool was growing along with better understanding of firmware structures and eventually the output looked like this:

Running with options: + firmware folder: M240_FIRMWARE + verbose enabled Open firmware file: FW_M240_1_1_0_2.FW File size: 6036193 | 0x005C1AE1 Parse container header: version: packed size: 6036145 | 0x005C1AB1 unpacked size: 19470974 | 0x01291A7E body blocks: 1 | 0x00000001 body offset: 48 | 0x00000030 MD5: AE387365 9C3D75B4 342F446E 13178E6B MD5 check: PASSED Uncompress container body: ◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼ 6036145 -> 19470974 Uncompression: DONE Split container: Number of sections: 9 | 0x00000009 Section table size: 612 | 0x00000264 Section table offset: 36 | 0x00000024 Section 1 Section Name: "[A]IMG_LOKI-212" Section offset: 0 | 0x00000000 Section size: 7340032 | 0x00700000 Section base: 1048576 | 0x00100000 MD5: A8D55AA2 B0ACDB14 0673AD79 707674F3 MD5 check: PASSED Create file: M240_FIRMWARE/IMG_LOKI-212.bin ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Section 9 Section Name: "[A]IMG-LENSDATA-213" Section offset: 19214844 | 0x012531FC Section size: 255478 | 0x0003E5F6 Section base: 16252928 | 0x00F80000 MD5: 39C2BEC0 27ED23F6 2C1C8513 EEE697B9 MD5 check: PASSED Create file: M240_FIRMWARE/IMG-LENSDATA-213.bin Splitting container: DONE
Extraction COMPLETE!

M240 firmware includes one container with 9 items:

IMG_LOKI-212.bin - Application Processor Firmware
IMG_LOKI-213.bin - Application Processor Firmware
CTRL_SYS-11.bin - IO Processor Firmware
IMG-FPGA-212.bin - Image Processing (Sensor) Firmware IMG-FPGA-213.bin - Image Processing (Sensor) Firmware IMG-DSP-212.bin - DSP Firmware
IMG-DSP-213.bin - DSP Firmware
IMG-LENSDATA-212.bin - Lens Data
IMG-LENSDATA-213.bin - Lens Data

As you have noticed there are two sets of files in one firmware. Later I found out that 212 is an Image Board version meant that there were two different Leica M240 types in the wild. The current research is based on 212 one.

System Control - CTRL_SYS-11.bin

The only common part was the firmware for some system control chip. This binary actually has lots of strings and looking through them it is not hard to get an idea what this part is for.

$ strings CTRL_SYS-11.bin | rg SH
-> Test SH7216 data flash driver
-> Test SH7216 SCI driver
-> Test SH7216 I2C driver
-> Test SH7216 MTU2 driver
-> Test SH7216 ADC functions
-> Test SH7216 CMT driver

So this is a Renesas SH7216 (SH-2A) and it is responsible for early boot stage, IO tests and firmware update. IDA supports this processor type out of the box, so it was a matter of finding the correct image base which was known from the firmware section description - 0x0:

Section Name: "[A]CTRL_SYS-11"
Section offset: 14680064 | 0x00E00000
Section size: 917277 | 0x000DFF1D
Section base: 0 | 0x00000000

I have obviously put it into IDA and recognised all functions, but didn’t really dig into it much since I was lot more curious about the main processor firmware.

Another thing to note here is that UART from this chip is exposed on service port where it prints boot log. We will get back to that later.

Main Chip - IMG_LOKI-212.bin

In order to start reverse engineering this firmware it was necessary to answer several questions first:

  1. what is the processor type
  2. what is the image base
  3. what OS it is based on if any

We already know image base from the M240FwTool, it is 0x100000

Section Name: "[A]IMG_LOKI-212"
Section offset: 0 | 0x00000000
Section size: 7340032 | 0x00700000
Section base: 1048576 | 0x00100000

Answers to the remaining questions were stored inside the firmware in human readable form. This string for example:

$ strings ./IMG_LOKI-212.bin | rg Softune
6Softune REALOS/FR is Realtime OS for FR Family, based on micro-ITRON COPYRIGHT(C) FUJITSU LIMITED 1994-1999

So we are dealing with custom Fujitsu FR (Leica calls it Maestro) and Softune REALOS. Actually, it was a lot more promising than Blackfin because IDA provides FR support out of the box.

IDA FR processor module

Reality was not that bright though, because when I put the firmware file into IDA and chose FR processor I discovered that this module is barely usable due to missing instructions, absence of xrefs etc.

I decided to fix it but ended up rewriting some parts completely[9]. This is the result:



Apart from fixes in ana, ins and out stages, brand new emu code was able to

  • recognize various types of code and data xrefs
  • recognize switch statements
  • perform stack trace
  • split stack arguments and local variables (thanks to clean FR ABI)
  • recognise functions properly

But the biggest change as you have noticed was capital letters for instructions :)

Would you like to see the full instruction set?

Here it is:


That’s it, nice and simple.

By the way, you may have noticed that some instructions are not aligned:

 BRA:D loc_xxx LDI:8 #0x64, R5

It is not a bug in a processor module, but actually a feature of Fujitsu FR family. It is called “Delay Slot”[10] and quite typical for RISC processors.

From the FR80 hardware manual[11]:

The instruction that is located immediately following a branch instruction (the location is called a "delay slot") is executed before branching, and an instruction at the branch destination is executed after that. Because the instruction in the delay slot is executed before the branch operation, the apparent execution speed is 1 cycle. 

So this is essentially a pipeline optimisation and it is better to keep that in mind since it is used everywhere in Leica firmware.

Softune REALOS

From wiki[12]:

Softune is an Integrated development environment from Fujitsu for the Fujitsu FR, FR-V and F²MC processor families. It provides an REALOS µITRON realtime kernel.
It is for example used for Nikon DSLRs (see Nikon EXPEED) and some Pentax K mount cameras.

So it is quite a popular decent RTOS with tasks, semaphores and other goodies and I was wondering if it is possible to recognize some standard library functions in the Leica firmware.


I should have called this part “an ode to time wasting” and here is why.

It was pretty hard to find Softune IDE in the wild but eventually I managed to get something to play with. As expected the IDE included libraries. There were four binaries:

  • lib911.lib
  • lib911e.lib
  • lib911if.lib
  • lib911p.lib

I don’t know why, but maybe by inertia, since I was so into hacking everything related to Leica but I have actually started to reverse engineer object format. Yes very well documented Object Module Format[13]. And yes of course I wrote a tool to deal with it[14]:

Fujitsu RISC Library Tool v1.0
Usage: FRLibTool [-s start] [-i imagebase] [-o output] [-f index] [-dv] FIRMWARE.BIN LIBRARY.LIB This tool will help you to find Softune REALOS library functions in FR (Fujitsu RISC) firmware.
Use following arguments: -f Specify firmware image file -s Specify firmware image scan offset -b Specify firmware imagebase -o Specify output type (exclusively) list - list of functions idc - IDC script py - IDA python script pat - FLAIR pattern file -i xxx Specify index of particular function -d Dump library -v Be verbose

Using this tool it was possible to create *.pat files and use them as input for the IDA FLAIR tool to generate signature files[15].

$ FRLibTool -o pat lib911.lib
$ FRLibTool -o pat lib911e.lib
$ FRLibTool -o pat lib911if.lib
$ FRLibTool -o pat lib911p.lib
$ sigmake -n "SOFTUNE C/C++ Library" lib911.pat lib911e.pat lib911if.pat lib911p.pat softune.sig

Finally, after applying this signature, I was very pleased to see matches in IMG_LOKI-212.idb .



The first thing I noticed was the amount of strings in the firmware. Many functions had their names in the body or at least some indication of their behaviour. This was extremely helpful during reverse engineering in order to understand the layout.

It is also important to note here that some parts of the firmware file are copied to different address in reset handler. For example, there is a bootloader embedded in code which is relocated to the higher RAM in runtime.

I had to create additional sections manually and eventually got to the following layout.



An interrupt vector table can be found by TBR access (Table Base Register):

 LDI:32 #int_table, R0 MOV R0, TBR

This usually happens in the reset vector handler right in the beginning of the firmware.

Handler addresses in the table are stored in reverse order according to formula TBR + (0x3FC - 4 × inum), so that reset vector is located at the end of the table at offset 0x3FC.
I found most of these interrupts defined in FR Hardware Manual[11] and just assumed Leica’s Maestro processor had a similar layout.
Then looking into every handler and I tried to find a string or any other hint revealing interrupt purpose.

This is what I ended up with:


Many of these like AUDIO/SDIO/VIDEO/JPEG/RAW were expected, but can you spot the most intriguing one?
I am talking about int_uart_in, which means the camera probably has some sort of UART CLI.


Like pretty much any other OS, SOFTUNE REALOS is designed to use system calls for IPC and other operations.

In assembly system call looks like this:


The actual address of system call hander is calculated in the following manner.
Let’s start with finding INT #0x40 interrupt handler. According to the previous section this is

(0x3FC - 4 × inum) = (0x3FC - 4 × 0x40) = 0x2FC = int_realos_syscall

Looking through the handler it is easy to find reference to the bottom of the syscall table. It contains 16bit words.
Particular entry in this table is calculated using following formula syscall_table_bottom + (num * 2):

[syscall_table_bottom + (-23 * 2)] = [syscall_table_bottom - 0x2E] = [0x1012EA] = 0xE68

As you can see this doesn’t looks like address, because the actual system call handler address is calculated as syscall_table_bottom + offset. Following diagram shows the whole process.


All system calls and their magics are listed in SOFTUNE REALOS/FR KERNEL MANUAL[16], therefore it was possible to recover all implemented handlers in the table and improve IDB a bit further.