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
 bf0.map:   0x000398E8 (   27072:0x000069C0) - main processor firmware map with symbols :D
 bf1.map:   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:
  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:

    ADD      OR       BTSTH    LSR     MOV     BN       LDRES     EXTSH   
    ADD2     ORH      MUL      LSR2    JMP     BP       STRES     EXTUH   
    ADDC     ORB      MULU     ASR     CALL    BV       COPOP     SRCH0   
    ADDN     EOR      MULH     ASR2    RET     BNV      COPLD     SRCH1   
    ADDN2    EORH     MULUH    LDI     INT     BLT      COPST     SRCHC   
    SUB      EORB     DIV0S    LDI     INTE    BGE      COPSV     LDM0    
    SUBC     BANDL    DIV0U    LDI     RETI    BLE      NOP       LDM1    
    SUBN     BANDH    DIV1     LD      BRA     BGT      ANDCCR    STM0    
    CMP      BORL     DIV2     LDUH    BNO     BLS      ORCCR     STM1    
    CMP2     BORH     DIV3     LDUB    BEQ     BHI      STILM     ENTER   
    AND      BEORL    DIV4S    ST      BNE     DMOV     ADDSP     LEAVE   
    ANDH     BEORH    LSL      STH     BC      DMOVH    EXTSB     XCHB    
    ANDB     BTSTL    LSL2     STB     BNC     DMOVB    EXTUB      

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.


And of course, it was possible to make code even prettier defining syscall types in IDA.


I actually wrote an IDA python script to find that and some other stuff automatically[17]


Looking at sta_tsk syscall I noticed that there is no main task function passed as parameter, instead code is passing pid. This means it was a time to look for big array of task descriptors. And it makes sense to start from sta_tsk itself.

ROM:102180 sys_sta_tsk:
ROM:102180                 ST      RP, @-R15
ROM:102182                 LDUB    @(R14, 0x4F), R3
ROM:102184                 LDI:32  #word_100B80, R14

Right at the beginning we see some reference. I had to play a bit with data types, but eventually pieces came together into this:

ROM:100B80 word_100B80:    .word 0xF           ; number of tasks
ROM:100B82                 .word 0x1C          ; task descriptor size

ROM:100B84                 .long 0x82A09F5C    ; task 1 descriptor
ROM:100B88                 .long 0x1000D
ROM:100B8C                 .long 0
ROM:100B90                 .long 0x40000000
ROM:100B94                 .long sub_1A7DB2    ; task main
ROM:100B98                 .long 0x8286EEC0
ROM:100B9C                 .long 0

ROM:100BA0                 .long 0x82A09F88    ; task 2 descriptor
ROM:100BA4                 .long 0x20010
ROM:100BA8                 .long 0
ROM:100BAC                 .long 0x40000000
ROM:100BB0                 .long sub_1A6BD2    ; task main
ROM:100BB4                 .long 0x8287EEC0
ROM:100BB8                 .long 0

and so on. 15 tasks in total. It was just a matter of time to look into every single main function and find the name and purpose of the task (apart from the last one). Here is the full list:

  1. SubCPU
    This task seems to be responsible for capture operations like exposure, live view control etc.
  2. KeyManager
    Most likely this task is handling hardware buttons.
  3. GuiManager
    Pretty big task implementing UI state machine and interface drawing.
  4. DebugManager
    Yeah, there is something for debug. Yum Yum.
  5. FileManager
    This task is all about file operations.
  6. FamManager
    I would say this one is responsible for file memory because it depends of File Manager and Memory Manager tasks
  7. MemoryManager
    No surprises here, memory operations, pool control etc.
  8. ImageManager
    This task is controlling encode/decode and other image workflows
  9. UsbManager
    Current task is handling communication over USB which includes MassStorage, PTP and some Leica Custom protocol.
  10. IOManager
    Looks like this task is managing storage devices like SD and CF cards (what? CF? maybe that is what 213 board is all about).
  11. SystemManager
    Various things like general system operations, power control etc.
  12. SettingsManager
    This task is handling changes in camera state and settings.
  13. MonitorManager
    The purpose of this task is to track changes in camera state and inform other tasks.
  14. PeripheralManager
    GPS, luminance and some other sensors are controlled by this task.
  15. Unknown
    Unfortunately, I didn’t find anything relevant about this one

Interesting to note here that there is one more outstanding task descriptor after main array.

ROM:100D28 dword_100D28:   .long 0x82A0A1F0
ROM:100D2C                 .long 0x21
ROM:100D30                 .long 0
ROM:100D34                 .long 0x80000000
ROM:100D38                 .long tid16_task
ROM:100D3C                 .long 0x8285EEC0
ROM:100D40                 .long 0

And the task function is just a branch to itself.

ROM:101494 sub_101494:
ROM:101494                 BRA     sub_101494      ; CODE XREF: sub_101494

This descriptor is referenced at the end of start function which is responsible for spawning other tasks and firmware setup. So it is most likely an idle task.

Modules and Messages

Apart from tasks it was also possible to define some logical objects like IO and Peripheral modules. Modules are represented as group of message handlers within one of the tasks.

IO group seems to include:

  • IO Manager
  • Sub CPU
  • USB Manager
  • USB Leica Custom
  • USB Mass Storage
  • Key Manager
  • Debug Manager
  • Lens Manager

while Peripheral group has:

  • Peripheral Manager
  • Luminance Sensor
  • LEDs
  • Beeper
  • Tilt sensor
  • Cover Detection
  • GPS module
  • 3DAxis module

Messaging system itself appears to utilise standard SOFTUNE structures:

struct RealOS_MsgPayload
  uint32_t msgID;   // +0x0
  uint32_t data[];  // +0x4

struct RealOS_Message
  uint32_t            os_reserved1; // +0x0
  uint32_t            os_reserved2; // +0x4
  uint32_t            to;           // +0x8
  uint32_t            from;         // +0xC
  RealOS_MsgPayload*  payload;      // +0x10

As expected IPC is also designed to have several message groups. Taking into consideration that there are plenty of messages handled in tasks and modules, I was able to recover just some of these groups browsing through the firmware:

0x1101xxxx - global system messages like  
             0x11010002 = SYS_UPDATE_BOOTLOADER or  
             0x11010005 = SYS_ERASE_SETTINGS
0x1102xxxx - messages related to image capture for example  
             0x11020001 = CMD_CAP_CAPTURE or  
             0x11020008 = IMAGE_STATUS_CHANGED  
0x1104xxxx - playback related messages cover events like  
             0x11040002 = PLY_DISABLE_PLAY_MODE or  
             0x11040004 = PLY_IMAGE_READY  
0x1108xxxx - various messages for PTP debugging e.g.  
             0x11080002 = DBG_CHANGE_LEVEL or  
             0x11080012 = DBG_WRITE_ROM_DUMP_SD  
0x2201xxxx - USB PTP messages like  
             0x22010108 = Camera Settings Change or  
             0x22010118 = Request DebugObject  
0x2202xxxx - pretty big group of SUBCPU messages including for example  
             0x22020002 = E_SUBCPU_REQUEST_M_EXPOSURE_REQUEST  
             0x22020015 = E_IO_SUBCPU_COMMAND_CLEANING_SENSOR  
0x2203xxxx - some other debugging message  
             0x22030001 = Debug String Command  
0x2204xxxx - various IO messages like  
             0x2204000C = Enable/Disable Mass Storage or  
             0x22040012 = Reset device  
0x330000xx - another big group related to UI, for instance  
             0x33000001 = Key pressed  
             0x33000007 = Lens connected  
0x440000xx - not many info about this one, but looks like image processing group  
             0x44000013 = E_IMG_CMD_CHANGE_PINFO  
0x55xxxxxx - group of FAM message groups:  
             0x558800xx = FAM file manager group or  
             0x558888xx = FAM menu settings 1 group  
0x6602xxxx - seems to be some LED control messages e.g.  
             0x66020001 - Toggle LED with X Hz  
             0x66020002 = Enable Continuous LED  
0x6604xxxx - beeper control messages including  
             0x66040001 = Beeper set or  
             0x66040007 = Card full noise  
0x6611xxxx - memory related debug messages  
0x6622xxxx - memory related image processing messages  
0x6660xxxx - some other memory related messages like   
             0x66600006 = memory HISTOGRAM  
             0x66600011 = memory RAWCOMP  
0x771100xx and 0x77AA00xx - camera mode switch related messages  

Unfortunately, many others are still unknown.


Let’s take a look again at the parts of the firmware file: CTRL_SYS-11, IMG-LOKI-212, IMG-DSP-212, IMG-FPGA-212 and IMG-LENSDATA-212.

What surprised me a bit was the absence of any GUI assets. But it must be somewhere and most likely it is embedded into IMG-LOKI-212.

One of my usual approaches to firmware reverse engineering is to recover all possible cross references. Not only from code, but in data section as well. Then I browse through them trying to find some patterns or links to known parts of code.

Leica firmware was not an exception. There were plenty of similar looking data sequences with addresses to data sequences with addresses to data sequences etc. Climbing up through this reference hierarchy I eventually appeared at a function I recognised.

For example, I found data structure without any references

g_data = { ... }

It was referenced from other structure

g_data_struct1 = { ... , &g_data }

Which was in turn referenced from one more structure

g_data_struct2 = { &g_data, ... }

This data structure was referenced from code and passed as parameter to another function

╰ func2(..., &g_data_struct2, ...)

However, func1() was not called directly from another function, instead if was stored in some array

g_func_list1[] = { ..., func1(), ... }

Looking above I found a reference to g_func_list1 from code

func3() {

And again, this function was stored in array

g_func_list2[] = { ..., func3(), ... }

Array itself was referenced from some other code

func4() {

Luckily this time function was called from another function and so on up until gui_MADE_ApplicationRun

╰ gui_MADE_ApplicationRun()
  ╰ func5()
    ╰ func4()

According to some strings, GUI subsystem is called “MADE” and page transitions are handled using MADE_GetSysTri whatever it means. GUI state machine is mostly implemented in gui_Statemachine_DoStateChange function. Later getting more and more information about GUI overall picture started to look like that


As you can see the core function dealing with GUI assets is gui_CopyImageDesc (it is not a real name though). It has following arguments:

    uint32_t			dstAddress;		// R4 - destination address
    UIDescType			type;			// R5 - description type
    UITarget			target;			// R6 - rendering target
    uint32_t			descAddress;	    	// R7 - description address
    uint8_t			always0;        	// (SP + 0x0) - always 0
    uint8_t			index1;			// (SP + 0x4) - index 1
    uint8_t			index2;			// (SP + 0x8) - index 2
    uint16_t			x_offset;		// (SP + 0xC) - x offset
    uint16_t			y_offset;		// (SP + 0x10) - y offset
    uint16_t			unknown2;		// (SP + 0x14) -
    uint32_t			language1;		// (SP + 0x18) - language id 1
    uint32_t			language2;		// (SP + 0x1C) - language id 2
    uint32_t			funcAddress;		// (SP + 0x20) - function address

There are four types of asset descriptions:

struct UIDescType0Header         struct UIDescType1Header         struct UIDescType2                struct UIDescType3       
{                                {                                {                                 {                        
    uint32_t    address;             uint32_t    address;             uint32_t    reg;                  uint16_t    x_offset;
    uint16_t    entries;             uint16_t    entries;             uint32_t    address;              uint16_t    y_offset;
    uint16_t    unknown;             uint16_t    unknown;             uint16_t    unknown1;             uint32_t    address; 
}                                }                                    uint16_t    unknown2;         }                        
                                                                      uint16_t    unknown3;                                        
struct UIDescType0Entry          struct UIDescType1Entry              uint16_t    tableoff;                                        
{                                {                                }                                                                
    uint16_t    x_offset;            uint16_t    x_offset;                   
    uint16_t    y_offset;            uint16_t    y_offset;                   
    uint32_t    address;             uint32_t    address;                    
}                                    uint16_t    objects;                    
                                     uint16_t    total_w; 
                                     uint16_t    total_h;                    
                                     uint16_t    unknown;                    

First type has header with the reference to array of entries. Every entry has coordinates and pixel data address. Current type seems to be describing state-dependent elements, like icons which can be greyed out or disappear from the UI.

Second type also starts with header and is used for localization, describing strings or blocks of text.

Third type describes character maps for different languages.

The last type is responsible for all other static assets, like images, backgrounds etc.

Now let’s take a look at the image data itself.

+0x00: 00 08 00 14 00 01 A2 FF 0A 04 05 FF 0C 04 03 FF
+0x10: 0D 04 03 FF 0E 04 02 FF 0E 04 02 FF 04 04 06 FF
+0x20: 04 04 02 FF 04 04 06 FF 04 04 02 FF 04 04 06 FF
+0x30: 04 04 02 FF 04 04 06 FF 04 04 02 FF 04 04 06 FF
+0x40: 04 04 02 FF 04 04 06 FF 04 04 02 FF 04 04 06 FF
+0x50: 04 04 02 FF 04 04 06 FF 04 04 02 FF 0E 04 02 FF
+0x60: 0E 04 02 FF 0D 04 03 FF 0D 04 03 FF 0C 04 04 FF
+0x70: 04 04 0C FF 04 04 0C FF 04 04 0C FF 04 04 0C FF
+0x80: 04 04 0C FF 04 04 0C FF 04 04 0C FF 04 04 0C FF
+0x90: 04 04 0D FF 02 04 2D FF 00 06 00 14 00 01 79 FF

First 6 bytes look like a little header followed by some repeating pattern where every second byte is either 0xFF or 0x04. Obvious guess for the 0x0008 and 0x0014 would be width and height in big endian. At the end of this dump we see a beginning of another sequence 00 06 00 14 00 01 which is most likely next image asset (this was also confirmed by reference to it). So the size of actual image data is 146 bytes. But the size of an image should be 0x8 * 0x14 = 0xA0 = 160. Clearly image data is not pure pixels and not even 8-bit LUT because it is 14 bytes smaller. Then what? There must be some kind of compression involved.

Looking at this hex dump it is hard to believe that they used something sophisticated. Also, Leica GUI is not very colourful or full of gradients and from my experience LUT is the best approach here. In this case UI assets will be full of repeating LUT indices like 03 03 03 or A1 A1 A1. Usually compressor it trying to get rid of repeating information replacing it with reference. These arrays of indices are perfect data to compress even with simple method like RLE [data][number]. In other words, write data to the output number of times.

Keeping all that in mind I assumed that this is most likely a simple image with two LUT colours (0xFF and 0x04) and the byte before colour is number of pixels to draw.

And then you wrote another tool” you may think. Nope, I grabbed pen and paper and started to fill squares. Funny enough I still have this original drawing.


Somewhere along the way I realized that 160 pixels is not enough to fit this image, instead 0x8 and 0x14 should be multiplied by two. The third word 0x0001 is indicating if image is ASCII character so that final ImageAsset structure looks like this:

struct ImageAsset
    uint16_t    width;        // width/2 (big endian)
    uint16_t    height;       // height/2 (big endian)
    uint16_t    ascii;        // 1 if ASCII character
    struct      image_data {
        uint8_t number;       // number of pixels to render
        uint8_t color;        // index of pixel color in LUT
    } data[];

However, one part is still missing - the LUT.

It was not that hard to find one because a lot of references and structures were already recovered manually, so I was slowly scrolling through data sections looking for 256 item array of 16bit or 32bit values until I ran into this:

 .long  0x7008080, 0x72D8080, 0x73C8080, 0x75A8080, 0x79B8080, 0x71DFF6B, 0x7BE8080, 0x7FF8080
 .long  0x77BBD27, 0x75B60E7, 0x7835F4A, 0x7D3089F, 0x7018080, 0x7028080, 0x7038080, 0x7048080
 .long  0x7058080, 0x7068080, 0x7078080, 0x7088080, 0x7098080, 0x70A8080, 0x70B8080, 0x70C8080
 .long  0x70D8080, 0x70E8080, 0x70F8080, 0x7108080, 0x7118080, 0x7128080, 0x7952B15, 0x7138080
 .long  0x7148080, 0x7158080, 0x7168080, 0x7178080, 0x7188080, 0x7198080, 0x71A8080, 0x71C8080
 .long  0x71D8080, 0x71E8080, 0x71F8080, 0x7338080, 0x7208080, 0x7218080, 0x7228080, 0x7238080
 .long  0x7248080, 0x7248080, 0x7268080, 0x7278080, 0x7288080, 0x7298080, 0x72A8080, 0x72B8080
 .long  0x72C8080, 0x75E8080, 0x7608080, 0x7628080, 0x7648080, 0x7678080, 0x7688080, 0x7698080
 .long  0x76B8080, 0x76E8080, 0x7708080, 0x7728080, 0x7758080, 0x7778080, 0x7798080, 0x77C8080
 .long  0x77E8080, 0x7818080, 0x7838080, 0x7868080, 0x7888080, 0x78B8080, 0x78D8080, 0x7908080
 .long  0x7928080, 0x7958080, 0x7978080, 0x7998080, 0x79C8080, 0x79D8080, 0x7668080, 0x79E8080
 .long  0x7A18080, 0x7A28080, 0x7A38080, 0x7A68080, 0x7A78080, 0x7A88080, 0x7AB8080, 0x7AC8080
 .long  0x7AD8080, 0x7B08080, 0x7B28080, 0x7B58080, 0x7B88080, 0x7B98080, 0x7BC8080, 0x7CC8080
 .long  0x7AB3BBB, 0x7E10094, 0x7E4556E, 0x4008080, 0x2922D17, 0x7B2AB00, 0x7C2A262, 0x71DFF6B
 .long  0x768D4A2, 0x769D4EA, 0x7BD88AE, 0x705997B, 0x70BB377, 0x711CC73, 0x717E66F, 0x7238866
 .long  0x729A262, 0x72FBB5E, 0x735D55A, 0x7417751, 0x747914D, 0x74DAA48, 0x753C444, 0x75F663B
 .long  0x76B9933, 0x7998080, 0x771B32F, 0x77D5526, 0x7836F22, 0x789881E, 0x78FA21A, 0x7159095
 .long  0x71AAA91, 0x720C38D, 0x726DD88, 0x7506F6A, 0x7568866, 0x75CA262, 0x762BB5E, 0x76E5E55
 .long  0x7747751, 0x77A914D, 0x780AA48, 0x78C4D3F, 0x792663B, 0x7988037, 0x79E9933, 0x7AA3C2A
 .long  0x7B05526, 0x7B66F22, 0x7BC881E, 0x72488AE, 0x72AA1AA, 0x72FBBA6, 0x735D4A2, 0x7427799
 .long  0x7489095, 0x74DAA91, 0x753C38D, 0x77E556E, 0x7836F6A, 0x7898866, 0x78FA262, 0x79C4459
 .long  0x7A15E55, 0x7A77751, 0x7AD914D, 0x7BF4D3F, 0x7CC8080, 0x7C5663B, 0x7CB8037, 0x7337FC8
 .long  0x73999C4, 0x73FB2C0, 0x745CCBB, 0x7757799, 0x74C54FF, 0x77B9095, 0x780AA91, 0x7AB3C72
 .long  0x7B1556E, 0x7B66F6A, 0x7BC8866, 0x74277E1, 0x74890DD, 0x74EAAD9, 0x754C3D5, 0x76066CC
 .long  0x7667FC8, 0x76C99C4, 0x772B2C0, 0x77E55B7, 0x7846EB3, 0x78A88AE, 0x790A1AA, 0x7526EFB
 .long  0x75787F7, 0x75DA1F3, 0x763BAEE, 0x76F5DE6, 0x77577E1, 0x77B90DD, 0x781AAD9, 0x78D4CD0
 .long  0x79366CC, 0x79F99C4, 0x7E10094, 0x7CF44A1, 0x7DB7799, 0x7E71A90, 0x7ED338C, 0x7FF8080
 .long  0x7328080, 0x7DC8080, 0x7C88080, 0x7508080, 0x775CD2C, 0x76944EA, 0x7808080, 0x71A61FF
 .long  0x7244D40, 0x7242C15, 0xFFF8080, 0xF338080, 0xF668080, 0xF998080, 0xFCC8080, 0xF008080
 .long  0xF4C54FF, 0xFAB3BBB, 0xFE10094, 0xFE4556E, 0xF952B15, 0xFDA7751, 0xFB2AB00, 0xFC2A262
 .long  0xF1DFF6B, 0xF68D4A2, 0xF69D4EA, 0xFBD88AE, 0xA922D17, 0xC6E4130, 0xE286963, 0x74C55FF
 .long  0x768D536, 0x7FF8080, 0x7FF8080, 0x7FF8080, 0x2922D17, 0x46E4130, 0x6286963,    0x8080

Again, thanks to my work for Blackmagic Design I was able to spot YUV pixels straight away (like all these 8080 values for example).

Obviously it was insane to dump the entire UI by hand with the pen again, so yeah, I created another tool - M240UITool[18]

Leica M (typ 240) UI Tool v1.0
Usage: ./M240UITool [-a address] [-i imagebase] [-s script] [-d dump] [-f folder] [-l LUT] [-rbv] FIRMWARE.BIN

This tool will help you to find UI resources in firmware.
Use following arguments:
    -a      Specify address of the gui_CopyImageDesc function (ex. 0x2F95E0)
    -i      Specify firmware imagebase
    -s      Specify IDC file name
    -c      Specify container file name
    -d      Specify dump image format
       png  - PNG format
       bmp  - BMP (ARGB) format
    -f      Specify folder for dumped images
    -l      Specify LUT for images (filename of address)
    -b      Specify number of bytes to display in verbose mode
    -r      Try to recover string characters
    -v      Be verbose

Apart from dumping all image assets from firmware file to BMP/PNG, this tool can also produce IDC script for IDA to define all UI resources.

So far we already know that gui_CopyImageDesc is called multiple times from the function creating one UI page. I thought it would be awesome to have a UI resource browser and define all page-rendering functions. This is what the -c option is for - it produces a special container to be used in the viewer.

And who said that a UI resource browser cannot look fancy?


Being interactive (semi-transparent buttons on a screenshot above) this tool allows you to not only scroll through EVF/LCD menu pages, but also to step through rendering stages within one page.

Unfortunately, the source for this masterpiece was lost somewhere but the header files are still there as a part of M240UITool, so it is technically possible to recreate it from scratch.

Debug Menu

What is the first string the reverse engineer usually searches for in the target? I bet on “debug” and derivatives.

There were plenty of interesting strings in firmware, but these ones are special:

$ strings ./IMG_LOKI-212_1.1.0.2.bin | grep "Debug Mode"
GUI: State: %d! Scanning for Debug Mode successful
GUI: Scanning for Debug Mode: State: %d, Ignore long DEL
GUI: Scanning for Debug Mode: State: %d
GUI: Scanning for Debug Mode: State: %d, Ignore long DEL
GUI: Scanning for Debug Mode: State: %d
GUI: Scanning for Debug Mode: State: %d, Ignore long DEL
GUI: Scanning for Debug Mode: State: %d
GUI: Scanning for Debug Mode: State: %d, Ignore long DEL
GUI: Scanning for Debug Mode: State: %d
GUI: Scanning for Debug Mode: State: %d, Ignore long DEL
GUI: Scanning for Debug Mode: State: %d
GUI: ScanningForDebugWithKeyAndJoyStick(): g_GUI_CheckForDebugWithKeyAndJoyStick = %d

Looks like it is possible to enter camera debug mode using some key combo. All these strings are referenced from one giant function ScanningForDebugWithKeyAndJoyStick which implements key scanning state machine. This is what it looks like in IDA


I am not going to lie, it took some time to understand how hardware buttons are handled in firmware and then to recover enums for keys and joystick. But even after I got the combo it was pretty disappointing to find out that it does nothing. Probably it works only from some particular GUI page. A couple more evenings of manual GUI state machine tracing and this problem was solved as well pointing to the Reset menu page.

Finally - Welcome to Debug Mode


I have been thinking a lot if I should make this combo public but decided not to do that. I respect the hard work Leica is doing bringing their unique cameras to market and don’t want to be responsible if their Service Centres are flooded with broken bodies as a result of some thoughtless curiosity.

Having said that, I would like to provide some enums to make reverse engineering a lot easier for someone who is willing to walk the same path.

enum ControlActionType {
    kControlAction_Idle,        // 0
    kControlAction_Push,        // 1
    kControlAction_Release,     // 2
    kControlAction_LongPush     // 3

enum ControlBtnType {
    kControlBtn_LV,             // 0
    kControlBtn_PLAY,           // 1
    kControlBtn_DEL,            // 2
    kControlBtn_ISO,            // 3
    kControlBtn_MENU,           // 4
    kControlBtn_SET             // 5

enum ControlJoystickType {
    kControlJoy_INFO,           // 0
    kControlJoy_Up,             // 1
    kControlJoy_Down,           // 2
    kControlJoy_Left,           // 3
    kControlJoy_Right           // 4


Looking around USB task code I was able to identify three different USB modes (it was also confirmed by debug menu):

  • PTP
  • MSC (Mass Storage Class)
  • Leica Custom

PTP is the most interesting one because it is well documented and allows you to control the camera.

It is pretty easy to locate PTP handlers in the firmware because there are a lot of strings referenced from that code.
All PTP requests are divided into three groups: Legacy, Leica Extended (LE) and Production.

Debug messages helped to name pretty much every code.

Legacy:                            Leica Extented:                          Production:                           
0x1001 - GetDeviceInfo             0x9001 - Set Camera Settings             0x9100 - Open Production Session      
0x1002 - OpenSession               0x9002 - Get Camera Settings             0x9101 - Close Production Session     
0x1003 - CloseSession              0x9003 - Get Lens Parameter              0x9102 - UpdateFirmware               
0x1004 - Get Storage ID            0x9004 - Release Stage                   0x9103 - Open OSD Session             
0x1005 - Get Storage Info          0x9005 - Open LE Session                 0x9104 - Close OSD Session            
0x1006 - GetNumObjects             0x9006 - Close LE Session                0x9105 - Get OSD Data                 
0x1007 - GetObjectHandles          0x9007 - RequestObjectTransferReady      0x9106 - GetFirmwareStruct            
0x1008 - GetObjectInfo             0x9008 - GetGeoTackingData               0x910B - GetDebugMenu                 
0x1009 - GetObject                 0x900A - Open Debug Session              0x910C - SetDebugMenu                 
0x100A - Get Thumb                 0x900B - Close Debug Session             0x910D - ODIN Message                 
0x100B - Delete Object             0x900C - Get Debug Buffer                0x910E - GetDebugObjectHandles        
0x100E - Initiate Capture          0x900D - Debug Command String            0x910F - GetDebugObject               
0x1014 - GetDevicePropDesc         0x900E - Get Debug Route                 0x9110 - DeleteDebugObject            
0x1015 - GetDevicePropV            0x900F - SetIPTCData                     0x9111 - GetDebugObjectInfo           
0x101C - Initiate Open Capture     0x9010 - GetIPTCData                     0x9112 - WriteDebugObject             
                                   0x9020 - Get3DAxisData                   0x9113 - CreateDebugObject            
                                   0x9030 - OpenLiveViewSession             0x9114 - Calibrate 3Daxis             
                                   0x9031 - CloseLiveViewSession            0x9115 - Magnetic calibration         
                                   0x9033 - Unknown                         0x9116 - Get Viewfinder Data          

The PTP interface implementation itself seems standard, however some commands have constrains that I intentionally omit here.

Anyway, all the above is pretty exciting, so you may think “Lets just connect camera over USB and start probing with libptp”.

Oh, shi…

Leica M240 does NOT have a USB port by design.

Handgrip Port

Leica doesn’t offer a lot of accessories for this camera, however there is one particularly interesting. I am talking about Leica Multifunctional Handgrip M (14495). It replaces the bottom metallic place and provides built-in GPS and several outputs like USB, SCA flash terminal, DIN/ISO-X and power sockets.


And you may think again “Awesome, let’s just buy that, attach it to the camera, connect the camera over USB and start probing with libptp”.

Oh, shi…

It costs almost $900 USD.

It is like nine hundred reasons to craft my own adapter instead. However, just in case I set up eBay notifications for used grip.

The Socket

Socket on a camera looks like this:


I tried searching for it on the internet, but seriously, how would you describe it to google?

Being a bit desperate I have started to think about some crazy things like gluing foil or needles to the eraser until one day at work in Blackmagic Design looking at camera PCB I have noticed that one socket has a very familiar shape. The next day I brought my Leica M240 to work and yes, it looked similar, just a lot longer with more contact pads.

So it was a matter of asking our component manager for a part number and then browsing Samtec for the one I need - ERM8-013-05.0-L-DV-TR [19]


We also asked Samtec if it is possible to get a sample and yes, they kindly agreed to send us some.


A bit of soldering, cardboard and tape to get my own breakout v2013.


Later in 2018 I decided to ask Samtec personally for another sample. However, this time I wanted something better.

ERCD-013-05.00-TTR-TTR-1-D [20]


Then it was lots of soldering, swearing, wire cutting, swearing and soldering again just to get breakout v2018:



The socket has 26 contacts - 13 on each side. Even before I managed to build my breakout I have done some research probing camera socket using multimeter and logic analyser. By the way, it is necessary to put a magnet on a bottom lid sensor in order for camera to think a cover is attached.

Ground (camera is off, no battery)

I always start with the ground because it is safe and very easy to find.


So there are 8 ground lines altogether (dark grey).

Potential (camera is on)

When the camera is ON it is possible to measure the potential on each pad in order to get an idea about logic and power levels.


The levels on 8-9 and 11-13 are too high to be logic, therefore I defined these pads as power (red).

Resistance (camera is off, no battery)

Another useful thing to measure is resistance. In some cases it helps to identify inputs and group some lines.


Linked outputs (camera is off, no battery)

Then I have decided to probe all external contact pads on the camera body to check if they are linked to the service port.


Flash Sync pad on hotshoe was directly connected to line 10.

Logic Analyser (camera is on)

Data for every line was captured using the following sequence:
Turn ON, camera should be in LV mode, take a picture, start video recording


There were two lines showing some kind of data transfers: 01 and 21.

01 - 115200, 8 Bits per Transfer, 1 Stop Bit, Even Parity Bit, LSB first
signal01_orig.png Every 500ms it sends some counter C3 3C 02 81 00 01 00 82, C3 3C 02 81 01 01 00 83, C3 3C 02 81 02 01 00 80

21 - 115200, 8 Bits per Transfer, 1 Stop Bit, No Parity Bit, LSB first
signal21_orig.png It sends SH7216 bootloader log (“Leica Camera AG” on screenshot above)

Let’s mark them with dark blue. It is pretty sad not having Maestro log exposed somewhere even with maximum debug level enabled in Debug menu.


The ones with signals have resistance around 310kOhm.
Don’t know why, but I assumed that other data lines might have similar resistance or close. Therefore, I have defined ~300kOhm, ~200kOhm and ~100kOhm lines as data as well (shades of blue on a picture).

Combined, I have the following picture.


12 candidates for data lines. But how to test that? After a brief chat with hardware people about electrical safety working with ICs, I ended up poking via 4kOhm resistor which supposed to reduce the current to a level where I am unlikely to burn inputs.


Another assumption I made is that RX line should be next to TX line. Lines 02, 03 and 20 look like good candidates because they are both 3.3V like TX.
Initially, I used Bus Pirate to talk to these lines, but unfortunately it couldn’t keep up giving a pretty messy result. Then I switched to SiLabs based cables since they are a lot more reliable and do not conflict with anything on macOS.

At first, I attached cable TX to pin 20 and started to type help after bootloader banner. As expected the camera echoed characters back after a short delay.


The next UART looking pins are 02 and 03. Unfortunately, there was no indication that someone is listening on those.

On a diagram known UARTs are defined with darker shade of green.



It all started with a cut in half USB cable with header in a middle and 4kOhm resistors to probe. Signal integrity for differential pair? Nah, it didn’t bother me much back then :)


Then I sniffed some consumer devices with USB at home to get an idea what USB comm looks like.

Canon Camera

Blackmagic Pocket Cinema Camera

Canon Camcoder

JVC Camcoder


KidiZoom Camera

They are all a bit different, but initial D- D+ state is low. Well, good to know, let’s check what is left on grip port similar to that:
22 - unlikely because D- D+ are differential pair and should be pretty close
04/05 - unlikely because they have different resistance
14/15 - unlikely because they have different resistance
15/16 - possible because they are close and have similar resistance

Therefore, I attached USB D- D+ to the 15/16 and plugged it to iMac…


There was USB PTP on a screen, but camera still didn’t appear on the host. I tried setting up various USB termination schemes on breadboard but nothing worked. Beagle showed many corrupted packets and other errors. Eventually I gave up and got back to reverse engineering firmware.

This is the final pinout with USB painted as dark green.


Who would have thought, several years later I received desired eBay notification and managed to get this grip pretty cheap.

Finally, I could check my PTP findings. But first I was obviously curious what does USB PHY look like inside the grip.


Inside I found SMSC 2512b hub[21] right on the way from grip socket to Mini USB connector. Chip is operating in default mode because there is no EEPROM and SCL/SDA pins are pulled down. First downstream port is routed to the camera body socket, but second one is not connected to anything.

I am probably missing something but to me this solution doesn’t make a lot of sense. Looking through datasheet I found out that chip has “Fully integrated USB termination and Pull-up/Pull-down resistors”. Maybe Leica engineers decided not to implement their own USB PHY and used the one in a hub which is very well tested and works out of the box. Actually, I can’t blame them because I tried to do same myself earlier and it seems to be a tricky task. It can also be a feature protecting grip from counterfeit, who knows.

Anyway, if you are good at USB PHY and willing to help, feel free to ping me, it should be possible to make USB port work without grip :)

PTP again

As I have said it was time to play with Leica PTP extensions.

Luckily I found pretty cool C++ library to use instead of libptp - libEasyPTP[22]. It also didn’t take long to write tool based on this library since I already knew some constrains in Leica PTP interface.
An even if M240PTPTool is quite buggy it was good enough as PoC[23].

There are just two PTP requests used: GetDebugBuffer (0x900C) and DebugCommandString (0x900D).
By the way, in order to make modules to fill debug log it is necessary to set Debug Level to “Debug” or “Debug RAW” in Debug Menu.

Tool CLI provided several options:
exit quits the tool;
flush command dumps debug buffer from the camera:

M240> flush
I:[00:11:468]|01| DATE/TIME CORRECTED by 5921 sec
D:[00:12:079]|00| Send message from TID 0 to TID 1 over MBX 3 - length: 4 - MesgID: 0x22020103
D:[00:12:179]|00| Send message from TID 0 to TID 1 over MBX 3 - length: 4 - MesgID: 0x22020103
D:[00:12:282]|11| Message received from TID 0 for TID 1 over MBX 3
D:[00:12:283]|11| Message received from TID 0 for TID 1 over MBX 3
D:[00:12:301]|00| Send message from TID 0 to TID 1 over MBX 3 - length: 4 - MesgID: 0x22020103
D:[00:12:402]|00| Send message from TID 0 to TID 1 over MBX 3 - length: 4 - MesgID: 0x22020103
D:[00:12:502]|00| Send message from TID 0 to TID 1 over MBX 3 - length: 4 - MesgID: 0x22020103

any other text will be sent as Debug Command String to the camera. help for example outputs all possible commands with arguments:

M240> help
 ********* debug command description ********

 exposure request
 Description: requests a release from Sub CPU
 Parameter 1: Exposure Time TV

 still request
 Description: simulates the -still request- command flow of Sub CPU
 Parameter: no

 send Message;[Parameter1];[Parameter2];[Parameter2];...;...
 Description: Sending Message to Task
 Parameter 1: Receiver Task ID
 Parameter 2: Command ID
 Parameter 3: Command Data[0] (32 Bit)
 Parameter 4: Command Data[1] (32 Bit)
 Parameter 5:   .
 Parameter 6:   .
 use maximum 10 Parameter

The complete list is quite big, but wow, we can send raw Softune messages to any task! What is so interesting we could have sent there…

The other popular string to search in firmware - “dump”. Let’s take a look what we have got in our case.

$ strings IMG_LOKI-212_1.1.0.2.bin | rg -i dump
GUI: HEX DUMP: Address: %x, Length: %d
HSK: DBG_WRITE_ROM_DUMP_SD: File was properly opened, but it seems to be empty.
HSK: DBG_WRITE_ROM_DUMP_SD: Flushing Dump to ROM. Size %d
HSK: DBG_WRITE_ROM_DUMP_SD Command received!
HSK: DUMP failed, no cards inserted!
HSK: DUMP FlashROM to SD card.
HSK: DUMP FlashROM to CF card.
Dumping files to card

Apparently, it is possible to dump firmware to SD card. It is easy to find code responsible for that by reference to string “Dumping files to card”. It is located in giant message hander of System Task (pid 11 as we already know) and can be triggered by message 0x11080006 without arguments.

Type send Message;11;0x11080006 in M240PTPTool, hit enter and observe the screen.


Then remove SD card and check what is on it.


Here it is, full dump including firmware.

But what is more important, is it now possible to modify this firmware in any way and flash it back to camera using another debug command: send Message;11;0x11080012.

As you can see this opens up endless opportunities. For example, it should be possible to build tiny device with MCU supporting USB host and some buttons to perform complex message sequences via PTP. And if it is not enough - just flash camera with your own custom firmware…

And then we had our second child :)


There is usually a way to study devices you don’t want to break without opening body or soldering wires to its PCB. Below are my tips if you don’t mind:

  • get all the public information you can about the device: datasheets, tear downs, internal photos, videos from factory [24] ;)
  • dig into firmware if you have it in order to find hints about external outputs
  • always google for various magics and odd byte sequences you find in firmware files
  • measure GND/Potential/Resistance for all unknown exposed external pads
  • probe these pads with logic analyser
  • always remember about safety measures dealing with electronics
  • try to exclude pads not in your scope of interest (ground, power)
  • if you can’t recognize signal by its analogue view, try googling for the most popular ones (USB/UART/SPI/I2C/1Wire)
  • if you have some ideas about signal nature, try verifying yourself with similar consumer electronics
  • think three five times before trying to SEND data to device, like driving line low/high
  • and of course, do not hesitate to ask other people


@getorix | github.com/alexhude

Happy Hacking!


[1] https://github.com/alexhude/LeicaHacks/tree/master/Tools/pwadsplit
[2] http://doom.wikia.com/wiki/WAD
[3] https://en.wikipedia.org/wiki/LHA_(file_format)
[4] https://github.com/CatacombGames/CatacombArmageddon/blob/master/LZW.C
[5] https://github.com/krater/Blackfin-IDA-Pro-Plugin
[6] https://github.com/alexhude/LeicaHacks/tree/master/Tools/xortool
[7] https://en.wikipedia.org/wiki/List_of_file_signatures
[8] https://github.com/alexhude/LeicaHacks/tree/master/Tools/M240FwTool
[9] https://github.com/alexhude/LeicaHacks/tree/master/Tools/fr (requires IDA 6.4 SDK)
[10] https://en.wikipedia.org/wiki/Delay_slot
[11] https://edevice.fujitsu.com/fj/MANUAL/MANUALp/en-pdf/CM71-10158-1E.pdf (link is dead)
[12] https://en.wikipedia.org/wiki/Softune
[13] https://en.wikipedia.org/wiki/Relocatable_Object_Module_Format
[14] https://github.com/alexhude/LeicaHacks/tree/master/Tools/FRLibTool
[15] https://github.com/alexhude/LeicaHacks/tree/master/IDA/signatures/
[16] https://www.fujitsu.com/downloads/MICRO/fma/pdfmcu/resofrke-cm71-00321-3e.pdf
[17] https://github.com/alexhude/LeicaHacks/tree/master/IDA/scripts/softune.py
[18] https://github.com/alexhude/LeicaHacks/tree/master/Tools/M240UITool
[19] https://www.samtec.com/products/erm8-013-05.0-l-dv-tr
[20] https://www.samtec.com/products/ercd-013-05.00-ttr-ttr-1-d
[21] https://www.microchip.com/wwwproducts/en/USB2512B
[22] https://github.com/TrueJournals/libEasyPTP
[23] https://github.com/alexhude/LeicaHacks/tree/master/Tools/M240PTPTool
[24] https://youtu.be/p4t-OVIvuy8?t=196