Disclaimer

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.





Prologue

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…

Compression

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:

9D 70 C4 9D 00 9D XX YY 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 - 1.1.0.2 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: 1.1.0.2 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:

what is the processor type what is the image base 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.

FLIRT

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 .

Layout

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.

Interrupts

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.

Syscalls

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]

Tasks

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:

SubCPU

This task seems to be responsible for capture operations like exposure, live view control etc. KeyManager

Most likely this task is handling hardware buttons. GuiManager

Pretty big task implementing UI state machine and interface drawing. DebugManager

Yeah, there is something for debug. Yum Yum. FileManager

This task is all about file operations. FamManager

I would say this one is responsible for file memory because it depends of File Manager and Memory Manager tasks MemoryManager

No surprises here, memory operations, pool control etc. ImageManager

This task is controlling encode/decode and other image workflows UsbManager

Current task is handling communication over USB which includes MassStorage, PTP and some Leica Custom protocol. 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). SystemManager

Various things like general system operations, power control etc. SettingsManager

This task is handling changes in camera state and settings. MonitorManager

The purpose of this task is to track changes in camera state and inform other tasks. PeripheralManager

GPS, luminance and some other sensors are controlled by this task. 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 PTP

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.

GUI

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

func1() ╰ 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() { g_func_list1[x] }

And again, this function was stored in array

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

Array itself was referenced from some other code

func4() { g_func_list2[x] }

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

gui_Statemachine_DoStateChange() ╰ 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:

gui_CopyImageDesc ( 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 };

PTP

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:

Pinout

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

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

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.

UARTs

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.

USB

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



Keyring



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. ROM_DUMP HSK: DBG_WRITE_ROM_DUMP_SD: Flushing Dump to ROM. Size %d SD:\ROM_DUMP.bin HSK: DBG_WRITE_ROM_DUMP_SD Command received! ROM_DUMP.bin 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 :)





Epilogue

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

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!





References

[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