Pull to refresh

Как взломать дорогую камеру, чтобы вас не убила жена

Reading time31 min
Views70K
Original author: Alexander Hude
Дисклеймер: исследование началось в 2013 году, так что если считаете некоторые методы глупыми и опасными — вы правы, так и было. Тем не менее, я многому научился в процессе.

Вступление
Всё началось за несколько месяцев до рождения моего первого ребёнка. Мы с женой всегда хотели купить крутой фотоаппарат Leica и вдруг поняли, что если не купим сейчас, то уже долго не сможем это сделать. Поэтому мы заказали камеру M240 и… бум, нас поставили в очередь на полгода. Вскоре мне надоело ждать, и я начал изучать их сайт. Моё внимание сразу привлёк раздел с файлами. Ну, вы можете догадаться, почему… Прошивки!

Я увидел незашифрованный и несжатый файл (m8-2_005.upd), который начинается с магии PWAD. Узнаёте? Да, всё правильно, это формат Doom Patch WAD. Кажется, ребята любят классику. Формат очень хорошо документирован, так что распарсить его оказалось совсем несложно.

Файлы прошивки Leica


Прошивка Leica M8


На самом деле это очень смешно, потому что когда позже я изучал сжатый файл прошивки Leica T — то первым делом решил проверить методы сжатия, которые в прошлом использовала id Software.

Википедия говорит, что они использовали формат LHA, который по сути представляет собой LZW. Но распространённые декомпрессоры LZW не подошли, поэтому я начал искать конкретную реализацию программного обеспечения id — и вуаля, нашёл в исходниках Catacomb Armageddon. Должен признать, повезло.

В любом случае, вернёмся к M8. Вот структура прошивки:

RULES:      0x0000008C (    3036:0x00000BDC) - описание XML
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
GUI:        0x0011FEE0 ( 3574180:0x003689A4)
 TRANS:     0x0000005C (   59988:0x0000EA54) - локализация
 IMAGES:    0x0000EAB0 (  267433:0x000414A9)
  21_1PRT:  0x000000CC (   18411:0x000047EB) - изображение JFIF
  21_2GRP:  0x000048B8 (   23172:0x00005A84) - изображение JFIF
  21_3PAN:  0x0000A33C (   23034:0x000059FA) - изображение JFIF
  24_1PRT:  0x0000FD38 (   18489:0x00004839) - изображение JFIF
  24_2GRP:  0x00014574 (   23230:0x00005ABE) - изображение JFIF
  24_3PAN:  0x0001A034 (   22998:0x000059D6) - изображение JFIF
  28_1PRT:  0x0001FA0C (   22605:0x0000584D) - изображение JFIF
  28_2GRP:  0x0002525C (   23081:0x00005A29) - изображение JFIF
  28_3PAN:  0x0002AC88 (   23282:0x00005AF2) - изображение JFIF
  35_1PRT:  0x0003077C (   22496:0x000057E0) - изображение JFIF
  35_2GRP:  0x00035F5C (   23532:0x00005BEC) - изображение JFIF
  35_3PAN:  0x0003BB48 (   22881:0x00005961) - изображение JFIF
 FONT1:     0x0004FF5C ( 1522988:0x00173D2C)
 FONT2:     0x001C3C88 ( 1723676:0x001A4D1C)
 VERSION:   0x003689A4 (       0:0x00000000)
M16C:       0x00488884 (  130406:0x0001FD66) - семейство Renesas M16C (Motorola S-record)
FPGA:       0x004A85EC (  131604:0x00020214) - Xilinx Spartan 3
FSL:        0x004C8800 (     814:0x0000032E) - загрузчик первой стадии

IDA из коробки не поддерживает процессоры Blackfin, но есть сторонний плагин.

Прошивка Leica M9


Файл прошивки Leica M9 (m9-1_196.upd) выглядит зашифрованным: гистограмма показывает распределение около 0,45%.



Конец истории? Может и нет. Дело в том, что Leica использовала довольно слабые процессоры в камерах, а в то время в бытовой электронике часто применялось шифрование XOR, поэтому я решил написать простой инструмент для операции XOR, чтобы сравнить прошивку с собой и посчитать некоторую статистику.

Длина ключа определялась путём поиска самого длинного повторяющегося шаблона. Это имеет смысл, так как любая прошивка обычно включает в себя большие блоки повторяющихся данных, таких как набивка 0x00/0xFF или графика с пикселями LUT. Сам ключ вычисляется по частотности байтов в пределах длины ключа, где наиболее часто встречающийся байт идёт в буфер ключа. Результат работы программы явно указывал на шифрование XOR. Затем пришлось немного модифицировать инструмент, чтобы получить потенциальный ключ и расшифровать код. Это опять оказался файл PWAD.

Содержание PWAD выявило следующую структуру:

RULES:      0x0000007C (    2788:0x00000AE4) - описание XML
LUTS:       0x00000B60 ( 4060616:0x003DF5C8)
 PROCESS:   0x0000004C ( 3900572:0x003B849C)
  CREATE:   0x0000004C (      20:0x00000014) - метка времени
  LUTS:     0x00000060 (  427744:0x000686E0)
  GAINMAP:  0x00068740 (   20008:0x00004E28)
  LENS:     0x0006D568 ( 3452724:0x0034AF34)
 CCD:       0x003B84E8 (  148662:0x000244B6)
  CREATE:   0x0000004C (      20:0x00000014) - метка времени
  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) - основной процессор
 bf1:       0x0001CCA4 (  117826:0x0001CC42) - прошивка подпроцессора
 bf0.map:   0x000398E8 (   27072:0x000069C0) - карта прошивки основного процессора с символами :D
 bf1.map:   0x000402A8 (   26304:0x000066C0) - карта прошивки подпроцессора с символами :D
BODY:       0x00426A90 (  143280:0x00022FB0) - семейство Renesas M16C (Motorola S-record)
GUI:        0x00449A40 ( 3647624:0x0037A888)
 TRANS:     0x0000005C (  131656:0x00020248) - локализация
 IMAGES:    0x000202A4 (  267433:0x000414A9)
  21_1PRT:  0x000000CC (   18411:0x000047EB) - изображение JFIF
  21_2GRP:  0x000048B8 (   23172:0x00005A84) - изображение JFIF
  21_3PAN:  0x0000A33C (   23034:0x000059FA) - изображение JFIF
  24_1PRT:  0x0000FD38 (   18489:0x00004839) - изображение JFIF
  24_2GRP:  0x00014574 (   23230:0x00005ABE) - изображение JFIF
  24_3PAN:  0x0001A034 (   22998:0x000059D6) - изображение JFIF
  28_1PRT:  0x0001FA0C (   22605:0x0000584D) - изображение JFIF
  28_2GRP:  0x0002525C (   23081:0x00005A29) - изображение JFIF
  28_3PAN:  0x0002AC88 (   23282:0x00005AF2) - изображение JFIF
  35_1PRT:  0x0003077C (   22496:0x000057E0) - изображение JFIF
  35_2GRP:  0x00035F5C (   23532:0x00005BEC) - изображение JFIF
  35_3PAN:  0x0003BB48 (   22881:0x00005961) - изображение JFIF
 FONT1:     0x00061750 ( 1522988:0x00173D2C)
 USBLOGO:   0x001D547C (    1775:0x000006EF) - изображение JFIF
 FONT2:     0x001D5B6C ( 1723676:0x001A4D1C)
FPGA:       0x007C42C8 (  150176:0x00024AA0) - Xilinx Spartan 3A
BF547:      0x007E8D68 (  937576:0x000E4E68) - семейство процессоров Analog Devices Blackfin (FSL?)


Прошивка Leica M240


У меня вошло в привычку каждое утро проверять страницу загрузки с прошивками Leica. Скоро там появился новый файл: FW_M240_1_1_0_2.FW.

Он не выглядел зашифрованным, но был сжат…

Сжатие


Гистограмма показывает огромный всплеск на 0x9D.



Возможно, это какая-то магия сжатия. Поиск в интернете [9D+compression] ничего не дал, кроме того, что 0x1F9D используется в качестве подписи сжатия LZW. Если что, я разбираюсь в типах сжатия LZ и решил посмотреть на байты после 0x9D. И увидел четыре варианта:

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

Что ещё удалось подметить:

  • первый вариант всплывает лишь однажды по адресу 0x30: вероятно, используется в качестве индикатора сжатых данных;
  • XX никогда не превышает 0x7F;
  • последний байт YY в третьем и четвёртом случаях никогда не превышает 0x7F

Из того, что я знаю о LZ, это очень похоже на LZ77 или LZSS, где YY — шаг отступа, а XX — количество байтов для копирования. И второй вариант — частный случай выдачи 0x9D. Я написал простую функцию на C, которая реализует эту логику. Она подтвердила, что мы движемся в правильном направлении, но четвёртый вариант всё равно не вписывается в схему.

Я всячески пытался его интерпретировать, но ничего не получилось. Поэтому я обратился за советом к товарищам. Один парень заметил, что согласно моим собственным наблюдениям, четвёртый байт YY появляется только тогда, когда установлен самый высокий бит 0x8Y: это просто дополнительное расстояние для шага отступа. Мне было стыдно, всё оказалось настолько очевидно…

Наконец, декомпрессор начал выдавать валидный поток… пока не застрял посередине файла. Это произошло из-за неизвестной длины скользящего окна. Дополнительная отладка и тесты исправили ситуацию.

Так появился инструмент для парсинга прошивки M240.

Структура прошивки


Для работы с неизвестным форматом я не придумал ничего лучше, чем измерить некоторые смещения и размеры участков кода — и попытаться найти самые близкие значения в заголовке файла. Например, этот блок:

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


в итоге превратился в:

1E1CAF2E — выглядит как "LEICA FILE"
01010002 - 1.1.0.2
005C1AB1 — размер сжатого файла (big endian)
01291A7E — размер несжатого файла (big endian)
AE3873659C3D75B4342F446E13178E6B — хэш MD5
00000001 — количество полезных нагрузок
00000030 — смещение первой полезной нагрузки


По мере понимания структур прошивки я улучшал свой инструмент, и в конечном итоге он выдавал такое:

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!


Прошивка М240 включает один контейнер с девятью элементами:

IMG_LOKI-212.bin - прошивка прикладного процессора
IMG_LOKI-213.bin - прошивка прикладного процессора
CTRL_SYS-11.bin - прошивка процессора ввода-вывода
IMG-FPGA-212.bin - прошивка для обработки изображений (сенсор)
IMG-FPGA-213.bin - прошивка для обработки изображений (сенсор)
IMG-DSP-212.bin - прошивка DSP
IMG-DSP-213.bin - прошивка DSP
IMG-LENSDATA-212.bin - данные по оптике
IMG-LENSDATA-213.bin - данные по оптике


Как видим, в одной прошивке два набора файлов. Позже я узнал, что 212 — это версия микрсхемы для обработки изображений, а в производство пошло два варианта Leica M240. Данное исследование основано на версии 212.

Управление системой: CTRL_SYS-11.bin


Единственная общая часть — прошивка для чипа управления системой. Это реально большой бинарник, и по коду нетрудно догадаться, для чего он предназначен.

$ 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


Таким образом, перед нами процессор Renesas SH7216 (SH-2A), который отвечает за раннюю стадию загрузки, тесты ввода-вывода и обновление прошивки. IDA из коробки поддерживает этот тип процессора. Оставалось лишь найти правильный адрес базовой загрузки, известный по описанию разделов прошивки: это 0x0.

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


Я загрузил его в IDA и распознал все функции, но не особенно в нём не копался, потому что намного интереснее прошивка основного процессора.

Здесь можно ещё отметить, что UART этого чипа открывается на сервисный порт, куда выдаёт журнал загрузки. Мы вернёмся к этому позже.

Основной чип: IMG_LOKI-212.bin


Чтобы начать реверс-инжиниринг данной прошивки, необходимо сначала ответить на несколько вопросов:

  1. какой тип процессора
  2. каков адрес базовой загрузки
  3. на какой ОС он основан, если таковая имеется

Благодаря нашему инструменту мы уже знаем адрес базовой загрузки: это 0x100000.

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


Остальные ответы прошивка хранит в удобочитаемом виде. Например, эта строка:

$ 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
...


Таким образом, мы имеем дело с кастомным процессором Fujitsu FR (Leica называет его Maestro) и операционкой Softune REALOS. На самом деле, это намного лучше, чем Blackfin, потому что IDA из коробки поддерживает FR.

Модуль процессора FR


Реальность оказалась не такой яркой, потому что после загрузки файла прошивки программа IDA не показала никаких инструкций, внешних ссылок и т. д.

Я решил исправить это, но в итоге пришлось полностью переписать некоторые части прошивки. Вот результат:





Кроме исправлений в ana, ins и out, совершенно новый код emu умеет:

  • распознавать различные типы кода и внешние ссылки на данные;
  • распознавать операторы switch;
  • выполнять трассировку стека;
  • разделять аргументы стека и локальные переменные;
  • правильно распознавать функции.

Но самое большое изменение, как вы заметили, — это прописные буквы для инструкций :)

Хотите увидеть полный набор инструкций? Вот он:

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      

Вот так, просто и красиво.

Кстати, вы могли заметить, что некоторые инструкции не выровнены:

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

Это не ошибка в процессорном модуле, а фактически особенность семейства Fujitsu FR. Она называется слот задержки и довольно типична для RISC-процессоров.

Из руководства по процессору FR80 (примечание: ссылка больше не работает):

Инструкция, которая находится сразу после инструкции ветвления (её расположение называется «слотом задержки»), выполняется перед ветвлением, а инструкция по целевому адресу выполняется после ветвления. Поскольку инструкция в слоте задержки выполняется перед операцией ветвления, видимая скорость выполнения составляет 1 цикл.

Таким образом, это, по сути, оптимизация конвейера, и лучше помнить о ней, потому что она применяется повсюду в прошивке Leica.

Softune REALOS


Из вики:

Softune — это интегрированная среда разработки Fujitsu для семейств процессоров Fujitsu FR, FR-V и F2MC. Работает на ядре реального времени REALOS µITRON. Например, используется в цифровых зеркальных камерах Nikon (см. Nikon EXPEED) и некоторых камерах Pentax с байонетом K.

Так что это довольно популярная приличная RTOS с задачами, семафорами и другими вкусностями. Мне было интересно, можно ли распознать в прошивке Leica некоторые стандартные функции библиотеки.

Должен назвать первую часть исследования большой напрасной тратой времени, и вот почему.

Softune IDE оказалось очень трудно найти, но в конце концов мне удалось кое-что достать. Как и ожидалось, IDE включала в себя библиотеки. Там было четыре двоичных файла:

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

Не знаю почему, может по инерции, как я взламывал всё связанное с Leica, я опять начал реверс-инжиниринг формата. Да, очень хорошо документированного формата Object Module Format. И да, конечно, я написал для этого специальный инструмент:

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


С его помощью можно создавать файлы *.pat и использовать их в качестве входных данных в IDA FLAIR для генерации файлов сигнатур.

$ 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


После применения этой сигнатуры я наконец-то с радостью увидел соответствия в IMG_LOKI-212.idb.



Макет


Сразу обращает на себя внимание количество строк в прошивке. Многие функции именуются по своей функциональности. Это очень полезно в процессе обратной разработки, чтобы понять общий шаблон.

Также важно отметить, что некоторые части файла прошивки копируются на другой адрес в обработчике сброса (reset handler). Например, встроенный загрузчик во время выполнения перемещается выше по оперативной памяти.

Пришлось вручную создавать дополнительные разделы, в итоге получился такой макет:



Прерывания


Таблицу векторов прерываний можно найти по доступу к TBR (Table Base Register):

LDI:32 #int_table, R0
MOV R0, TBR


Обычно он происходит в обработчике сброса вектора в самом начале прошивки.

Адреса обработчиков в таблице хранятся в обратном порядке по формуле TBR + (0x3FC - 4 × inum), так что вектор сброса в конце таблицы со смещением 0x3FC.

Я нашёл большинство прерываний из руководства по FR и предположил, что у Leica Maestro аналогичная компоновка. Затем взял каждый обработчик и попытался найти строку или любой другой намёк, раскрывающий цель прерывания.

В итоге составил такой список:



Многие прерывания вполне ожидаемы, такие как AUDIO/SDIO/VIDEO/JPEG/RAW, но попробуйте определить самое загадочное из них? Я говорю о прерывании int_uart_in. Похоже, что камера поддерживает какой-то консольный режим UART CLI.

Системные вызовы


Как почти любая ОС, Softline REALOS использует системные вызовы. В ассемблере они выглядят так:



Фактический адрес обработчика системных вызовов вычисляется следующим образом. Начнём с поиска обработчика прерывания INT #0x40. В соответствии с описанием выше это

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

В обработчике легко найти ссылку на нижнюю часть таблицы системных вызовов с 16-битными словами. Конкретная запись в этой таблице вычисляется по формуле syscall_table_bottom + (num * 2):

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

Это не похоже на адрес, потому что фактический адрес обработчика системных вызовов вычисляется как syscall_table_bottom + offset. Весь процесс показан на диаграмме.



Все системные вызовы и их функциональность указаны в Руководстве по ядру Softline REALOS/FR, поэтому мне удалось восстановить все реализованные обработчики в таблице и ещё немного улучшить IDB.



Конечно, можно сделать код ещё красивее, определив типы системных вызовов в IDA.



Я написал скрипт на Python для автоматического поиска этих системных вызовов и прочего.

Задачи


В системном вызове sta_tsk я заметил, что в качестве параметра передаётся не основная функция, а pid. Это означает, что пришло время искать большой массив дескрипторов задач. И имеет смысл начать с самого sta_tsk.

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

В самом начале мы видим какие-то ссылки. Пришлось немного повозиться с типами данных, но в итоге части собрались вместе:

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
...

и так далее. Всего 15 задач. Было вопросом времени заглянуть в каждую основную функцию, определить имя и цель задачи (кроме последней). Вот полный список:

  1. SubCPU
    Эта задача, по-видимому, отвечает за операции захвата, такие как экспозиция, визирование по экрану и т. д.
  2. KeyManager
    Скорее всего, эта задача связана с аппаратными кнопками.
  3. GuiManager
    Довольно большая задача, в которой реализована машина состояний UI и отрисовка интерфейса.
  4. DebugManager
    Да, есть кое-что для отладки. Ням-ням.
  5. FileManager
    Эта задача — всё о файловых операциях.
  6. FamManager
    Я бы сказал, что задача отвечает за файлы и память, потому что она зависит от задач файлового менеджера и менеджера памяти.
  7. MemoryManager
    Никаких сюрпризов: операции с памятью, управление пулом и т. д.
  8. ImageManager
    Эта задача управляет процессами кодирования/декодирования и другими процессами обработки изображений.
  9. UsbManager
    Текущая задача — обработка коммуникаций по USB, которая включает в себя MassStorage, PTP и собственный протокол Leica.
  10. IOManager
    Похоже, эта задача управляет устройствами хранения данных, такими как SD и CF-карты (что? какие ещё CF? может, это из модели 213).
  11. Systemmanager
    Разные задачи вроде общих системных операций, управления питанием и т. д.
  12. SettingsManager
    Обрабатывает состояние камеры и настройки.
  13. MonitorManager
    Отслеживает изменения состояния камеры и информирует другие задачи.
  14. PeripheralManager
    Эта задача контролирует GPS, яркость и некоторые другие сенсоры.
  15. Неизвестно
    К сожалению, для неё я не нашел ничего существенного.

Интересно отметить, что после основного массива есть ещё один выдающийся дескриптор.

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


А функция задачи — просто ветвление самой себя.

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


На этот дескриптор стоит ссылка в конце функции start, которая отвечает за создание других задач и настройку встроенного ПО. Так что это, скорее всего, задача бездействия системы.

Модули и сообщения


Помимо задач, можно определить некоторые логические объекты, такие как IO и периферийные модули. Модули представлены в виде группы обработчиков сообщений в рамках одной из задач.

Группа IO, кажется, включает:

  • Менеджер IO
  • Подпроцессор
  • Менеджер USB
  • USB PTP
  • Протокол USB Leica
  • USB Mass Storage
  • Менеджер управления кнопками
  • Менеджер отладки
  • Менеджер объектива

А в периферийной группе:

  • Менеджер периферии
  • Датчик освещённости
  • Светодиоды
  • Динамик
  • Датчик угла наклона
  • Распознавание закрытия крышки
  • Модуль GPS
  • Модуль 3DAxis

Сама система обмена сообщениями использует стандартные структуры SOFTUNE:

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
}

Как и ожидалось, в IPC тоже есть несколько групп сообщений. Поскольку много сообщений обрабатывается в задачах и модулях, я смог восстановить только некоторые из этих групп:

0x1101xxxx - глобальные системные сообщения:
             0x11010002 = SYS_UPDATE_BOOTLOADER или
             0x11010005 = SYS_ERASE_SETTINGS
0x1102xxxx - сообщения, связанные с захватом изображения:
             0x11020001 = CMD_CAP_CAPTURE или
             0x11020008 = IMAGE_STATUS_CHANGED  
0x1104xxxx - сообщения о событиях, связанных с воспроизведением:  
             0x11040002 = PLY_DISABLE_PLAY_MODE или
             0x11040004 = PLY_IMAGE_READY  
0x1108xxxx - различные сообщения для отладки PTP и др.:
             0x11080002 = DBG_CHANGE_LEVEL или
             0x11080012 = DBG_WRITE_ROM_DUMP_SD  
0x2201xxxx - сообщения USB PTP
             0x22010108 = изменение настроек камеры или
             0x22010118 = запрос DebugObject  
0x2202xxxx - довольно большая группа сообщений SUBCPU:
             0x22020002 = E_SUBCPU_REQUEST_M_EXPOSURE_REQUEST  
             0x22020015 = E_IO_SUBCPU_COMMAND_CLEANING_SENSOR  
0x2203xxxx - некоторые другие сообщения отладки:
             0x22030001 = команда отладочной строки
0x2204xxxx - различные сообщения IO:
             0x2204000C = включение/отключение Mass Storage или
             0x22040012 = перезагрузка устройства  
0x330000xx - другая группа сообщений для UI:
             0x33000001 = нажатие клавиш
             0x33000007 = подключение объектива
0x440000xx - здесь мало информации, но похоже на обработку изображений
             0x44000013 = E_IMG_CMD_CHANGE_PINFO  
0x55xxxxxx — группы сообщений FAM:  
             0x558800xx = файл-менеджер FAM или
             0x558888xx = первая группа настройки меню FAM
0x6602xxxx — похоже на управляющие сообщения LED, например:
             0x66020001 - переключение LED с X Гц
             0x66020002 = включение непрерывного LED  
0x6604xxxx - управление бипером:
             0x66040001 = установка бипера
             0x66040007 = писк о заполненной карте
0x6611xxxx - сообщения отладки, связанные с памятью
0x6622xxxx - сообщения обработки сообщений, связанные с памятью
0x6660xxxx - некоторые другие сообщения, связанные с памяьтю:
             0x66600006 = HISTOGRAM  
             0x66600011 = RAWCOMP  
0x771100xx and 0x77AA00xx - сообщения, связанные с переключением режимов камеры

К сожалению, много других сообщений остаются неизвестными.

GUI


В файле прошивки посмотрим ещё на следующие разделы: CTRL_SYS-11, IMG-LOKI-212, IMG-DSP-212, IMG-FPGA-212 и IMG-LENSDATA-212.

Что меня удивило, так это полное отсутствие ресурсов GUI. Но они где-то должны быть и, скорее всего, встроены в IMG-LOKI-212.

Один из моих обычных подходов к обратной разработке прошивки — восстановить все возможные перекрёстные ссылки. Не только в коде, но и в разделе данных. Затем я просматриваю их, пытаясь найти некоторые шаблоны или ссылки на известные части кода.

Прошивка Leica не стала исключением. Там много похожих последовательностей данных с адресами на другие последовательности данных, которые обращаются дальше и т. д. Поднимаясь по иерархии ссылок, я в конце концов увидел знакомую функцию.

Например, я нашёл структуру данных без каких-либо ссылок:

g_data = { ... }

К ней обращалась другая структуры:

g_data_struct1 = { ... , &g_data }

К которой в свою очередь обращается ещё одна структура:

g_data_struct2 = { &g_data, ... }

На эту структуру данных есть ссылка из кода, и её передают в качестве параметра другой функции:

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

Однако func1() не вызывается напрямую из другой функции, а хранится в некотором массиве:

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

Посмотрев выше, я нашел в коде вызов g_func_list1:

func3() {
    g_func_list1[x]
}

И снова эта функция сохранена в массиве:

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

К самому массиву обращается какой-то другой код:

func4() {
    g_func_list2[x]
}

К счастью, на этот раз функция вызвана из другой функции, и так до gui_MADE_ApplicationRun.

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

В некоторых строках указано, что подсистема GUI называется “MADE”, а переходы страниц обрабатываются с помощью MADE_GetSysTri, что бы это ни значило. Машина состояний GUI, в основном, реализована в функции gui_Statemachine_DoStateChange. После сбора информации о GUI сложилась общая картина:



Как видите, основная функция для ресурсов GUI — gui_CopyImageDesc (хотя это не настоящее имя). У неё следующие аргументы:

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
)

Есть четыре типа описаний ресурсов:

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

У первого типа заголовок со ссылкой на массив записей. Каждая запись имеет координаты и адрес пиксельных данных. Текущий тип, похоже, описывает зависимые от состояния элементы, такие как значки, которые могут быть серыми или исчезать из UI.

Второй тип тоже начинается с заголовка и используется для локализации, описания строк или блоков текста.

Третий тип описывает карты символов для разных языков.

Последний тип отвечает за все другие статические ресурсы, такие как изображения, фоны и т. д.

Теперь взглянем на сами данные для изображений.

+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


Первые шесть байт выглядят как маленький заголовок, за которым следует какой-то повторяющийся шаблон, где каждый второй байт является либо 0xFF, либо 0x04. Логично предположить, что 0x0008 и 0x0014 — ширина и высота в представлении с прямым порядком байтов (big endian). В конце этого дампа мы видим начало другой последовательности 00 06 00 14 00 01. Скорее всего, это следующий ресурс (что подтверждается ссылкой на него). Таким образом, размер фактических данных изображения составляет 146 байт. Но размер изображения должен быть 0x8 * 0x14 = 0xA0 = 160. Ясно, что в данных не чисто пиксели и даже не 8-битная LUT, потому что она на 14 байт меньше. Тогда что? Вероятно, какое-то сжатие.

Глядя на этот шестнадцатеричный дамп, трудно поверить, что используется какая-то сложная схема. GUI у Leica не очень красочный, так что по моему опыту здесь лучше всего использовать таблицу LUT. В этом случае ресурсы UI будут полностью повторять индексы LUT вроде 03 03 03 или А1 А1 А1. Обычно компрессор пытается избавиться от дублирования данных, заменяя их ссылкой. Эти массивы индексов идеальны для сжатия даже простым методом вроде RLE [data][number]. Простая команда записать data (значение) number раз.

С учётом всего этого я предположил, что перед нами, скорее всего, простое изображение с двумя цветами LUT (0xFF и 0x04), а байт перед цветом — количество пикселей для рисования.

«А потом ты написал ещё один инструмент», — подумаете вы. Но нет, я взял ручку и бумагу и начал заполнять клетки. Забавно, что у меня до сих пор сохранился тот рисунок.



Где-то по пути я понял, что для этого изображения недостаточно 160 пикселей, так что 0x8 и 0x14 нужно умножить на два. Третье слово 0x0001 указывает, является ли изображение символом ASCII, так что окончательная структура ImageAsset выглядит следующим образом:

struct ImageAsset
{
    uint16_t    width;        // ширина/2 (big endian)
    uint16_t    height;       // высота/2 (big endian)
    uint16_t    ascii;        // 1, если символ ASCII
    struct      image_data {
        uint8_t number;       // количество пикселей для рендеринга
        uint8_t color;        // индекс цвета пикселя в LUT
    } data[];
}

Но одна часть всё ещё отсутствует: LUT.

Её было не так сложно найти, потому что многие ссылки и структуры уже восстановлены вручную, так что я медленно прокручивал разделы данных, ища массив на 256 элементов из 16-битных или 32-битных значений, пока не натолкнулся на это:

.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


Опять же, благодаря моей работе с Blackmagic Design я сразу распознал пиксели YUV (например, все значения с цифрами 8080).

Я не дурак, чтобы опять рисовать весь пользовательский интерфейс вручную на бумаге, так что да, я написал ещё один инструмент — M240UITool.

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


Кроме сброса всех ресурсов изображений из файла микропрограммы в BMP/PNG, этот инструмент умеет создавать IDC-скрипты в IDA для определения всех ресурсов UI.

Мы уже знаем, что из функции, которую создаёт одну страницу UI, несколько раз вызывается gui_CopyImageDesc. Я подумал, что будет здорово сделать браузер ресурсов UI и определить все функции рендеринга страниц. Для этого предназначена опция -c — она сооздаёт специальный контейнер для просмотра ресурсов.

И кто сказал, что браузер ресурсов пользовательского интерфейса не может выглядеть необычно?



Будучи интерактивным (полупрозрачные кнопки на скриншоте), этот инструмент позволяет не только прокручивать страницы меню EVF/LCD, но просматривать этапы рендеринга в пределах одной страницы.

К сожалению, исходники этого шедевра где-то потерялись, но заголовочные файлы всё ещё в коде M240UITool, поэтому технически можно воссоздать его с нуля.

Меню отладки


Какую строку мы ищем в первую очередь при реверс-инжиниринге? По-моему, это слово debug и производные от него.

В прошивке было много интересных строк, но эти особенные:

$ 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


Похоже, что в режим отладки можно войти с помощью какой-то комбинации клавиш. Все эти строки вызываются из одной гигантской функции ScanningForDebugWithKeyAndJoyStick, которая реализует машину состояний сканирования кнопок. Вот как это выглядит в IDA:



Не буду врать, потребовалось некоторое время, чтобы понять, как в прошивке обрабатываются аппаратные кнопки, а затем восстановить перечисляемые типы для кнопок и джойстика. Но когда я получил комбинацию, то с огорчением выяснил, что она ничего не делает. Вероятно, она работает только с какой-то конкретной страницы GUI. Еще пара вечеров ручной трассировки машины состояний GUI — и проблема решена, а также удалось найти страницу меню Reset.

Наконец, добро пожаловать в режим отладки.



Я много думал, огласить ли эту комбинацию, но решил воздержаться. Я уважаю тяжёлую работу, которую делает Leica, выпуская свои уникальные аппараты, и не хочу отвечать за то, что их сервисные центры заполнят сломанные тушки камер в результате некоторого бездумного любопытства.

Но всё-таки предоставлю некоторые перечисляемые типы, чтобы упростить обратную разработку для тех, кто готов идти по этому пути.

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


Думая над задачей USB, я определил три режима (что также подтверждается в меню отладки):

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

PTP наиболее интересен, потому что он хорошо документирован и позволяет управлять камерой.

В прошивке довольно легко найти обработчики PTP, потому что из этого кода много вызовов. Все вызовы PTP делятся на три группы: Legacy, Leica Extended (LE) и Production.

Отладочные сообщения помогли установить названия почти для всего кода.

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          

Сама реализация интерфейса PTP кажется стандартной, однако у некоторых команд есть ограничения, которые я намеренно опускаю здесь.

В любом случае, всё вышесказанное довольно увлекательно. Вы можете подумать: «Давайте просто подключим камеру по USB и начнём зондировать с помощью libptp». Всё верно.

Только блин…

У Leica M240 нет USB-порта.

Порт для рукоятки


Leica предлагает немного аксессуаров для этой камеры, но есть один особенно интересный. Речь идет о многофункциональной рукоятке Leica M (14495). Он заменяет нижнее металлическую часть корпуса, обеспечивает встроенный GPS и несколько разъёмов вроде USB, терминал вспышки SCA, DIN/ISO-X и гнездо для питания.



И вы опять скажете: «Здорово, теперь просто купим её, прикрепим к камере, подключим камеру по USB и начнём зондировать с помощью libptp». Всё верно.

Только блин…

Она стоит почти 900 долларов.

Это почти девятьсот причин, чтобы создать собственный адаптер. Тем не менее, на всякий случай я настроил уведомления eBay для этого аксессуара.

Разъём


Разъём на камере выглядит следующим образом:



Я попытался найти его в интернете, но серьёзно, как бы вы описали его в Google?

Немного отчаявшись, я начал думать о некоторых сумасшедших вещах, вроде приклеить фольгу или иголки к резиновому ластику. Но однажды на работе в Blackmagic Design, глядя на печатную плату камеры, я заметил, что у одного разъёма очень знакомая форма. На следующий день я привёз свою Leica M240 на работу — и да, она выглядела похожей, просто намного длиннее с большим количеством контактных площадок.

Осталось спросить номер детали у нашего менеджера по компонентам, а затем найти её в каталоге Samtec: ERM8-013-05.0-L-DV-TR.



Мы также спросили у представителей Samtec, можно ли получить образец, и они добросердечно согласились.



Немного работы паяльником, картона и изоленты — и готов мой собственный штекер (образец 2013 года).



Спустя пять лет, в 2018 году, я решил лично попросить Samtec прислать другой образец. Мне хотелось сделать что-то получше.

ERCD-013-05.00-TTR-TTR-1-D


Опять много работы паяльником, ругани, резки проволоки, ругани, снова работы паяльником, чтобы сделать новый, более симпатичный вариант:



Распиновка


В разъёме 26 контактов: по 13 с каждой стороны. Ещё до пайки своей детали я прозондировал разъём камеры мультиметром и логическим анализатором. Кстати, на датчик нижней крышки нужно поставить магнит, чтобы камера считала, что крышка на месте.

Земля (камера выключена, нет батареи)

Я всегда начинаю с земли, потому что это безопасно и её очень легко найти.



Таким образом, у нас восемь линий заземления (тёмно-серый).

Потенциал (камера включена)

Когда камера включена, можно измерить потенциал на каждом контакте и получить представление о логике и уровнях мощности.

alexhude.github.io/assets/2019/2019-01-24-hacking-leica-m240/probe2_potential.png

Показатели на контактах 8-9 и 11-13 слишком высоки для логических контактов, поэтому я определил их как питание (красный).

Сопротивление (камера выключена, нет батареи)

Полезно ещё измерить сопротивление. В некоторых случаях это помогает идентифицировать входы и сгруппировать некоторые линии.



Связанные выходы (камера выключена, нет батареи)

Затем я решил проверить все внешние контактные площадки на корпусе камеры, чтобы проверить, связаны ли они с сервисным портом.



Синхроконтакт вспышки оказался напрямую подключен к линии 10.

Логический анализатор (камера включена)

Данные для каждой строки записывались в следующей последовательности: включаем, камера должна быть в режиме LV, делаем снимок, начинаем видеозапись.



Две линии показывают передачу каких-то данных: 01 и 21.

01 — 115200, передача по 8 бит, 1 стоп-бит, бит чётности, LSB первый.



Каждые 500 мс, он отправляет какой-то счётчик 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 бит, 1 стоп-бит, нет бита проверки чётности, LSB первый.



Он отправляет лог загрузчика SH7216 (“Leica Camera AG” на скриншоте выше).

Давайте пометим их тёмно-синим. Довольно грустно, что лог Maestro не отдаётся наружу даже с максимальными настройками отладки в меню Debug.



На этих контактах сопротивление около 310kOhm.

Не знаю почему, но я предположил, что другие линии данных могут иметь аналогичное сопротивление или будут закрыты. Поэтому я определил линии ~300kOhm, ~200kOhm и ~100кОм тоже как линии данных (оттенки синего цвета на рисунке).

В целом нарисовалась следующая картина.



12 кандидатов на линии данных. Но как их проверить? После небольшой беседы со специалистами по железу об электрической защите интегральных схем я начал тыкать в контакты через резистор 4kOhm, уменьшающий ток до уровня, который не должен сжечь входы.

UART


Я сделал ещё одно предположение, что линия RX должна быть рядом с ТХ. Линии 02, 03 и 20 выглядят как хорошие кандидаты, потому что у обеих напряжение 3,3 В как у TX.

Изначально я пытался изучить эти линии с помощью Bus Pirate. К сожалению, результат оказался довольно грязным. Затем я взял кабели на основе SiLabs как более надёжные и ни с чем не конфликтующие на macOS.

Сначала я подключил кабель TX к контакту 20 и начал набирать help после загрузчика. Как и ожидалось, после небольшой задержки камера повторила символы.



Контакты 02 и 03 были следующими кандидатами на UART. К сожалению, не оказалось никаких признаков, что эти линии прослушиваются.

На диаграмме известные UART обозначены более тёмным оттенком зеленого.



USB


Всё началось с разреза USB-кабеля пополам с хедером посередине и резисторами на 4kOhm для зондирования. Целостность сигнала дифференциальной пары? Неа, тогда меня это не очень волновало. :)



Затем я дома прозондировал несколько бытовых устройств с USB, чтобы получить представление, как выглядят коммуникации по этому порту.

Камера Canon


Карманная видеокамера Blackmagic


Видеокамера Canon


Видеокамера JVC


Брелок


Камера KidiZoom


Они все немного разные, но начальное состояние D- D+ низкое. Что ж, будем знать, а теперь проверим, что у нас есть:

  • 22 — маловероятно, потому что D- D+ являются дифференциальной парой и должны быть довольно близки;
  • 04/05 — маловероятно, потому что у них разное сопротивление;
  • 14/15 — маловероятно, потому что у них разное сопротивление;
  • 15/16 — возможно, потому что они близки и имеют похожее сопротивление.

Поэтому я подсоединил USB D- D+ к пинам 15/16 и подключил к iMac…



На экране USB PTP, но камера не появлялась на хосте. Я пытался настроить разные варианты на макете электронной схемы, но ничего не работало. Beagle показывал много повреждённых пакетов и других ошибок. В конце концов я сдался и вернулся к обратной разработке прошивки.

Это окончательное распиновка, USB обозначен тёмно-зелёным цветом.



Кто бы мог подумать, что несколько лет спустя мне придёт то самое уведомление eBay и я довольно дёшево куплю желанный аксессуар.

Наконец, я могу проверить свои предположения о PTP. Но сначала было очень любопытно, как внутри гаджета выглядит USB PHY.



Внутри оказался хаб SMSC 2512b прямо на дороге от разъёма рукоятки к коннектору Mini USB. Чип работает в дефолтном режиме, потому что нет никакого EEPROM или контактов SCL/SDA. Первый нисходящий порт маршрутизируется в гнездо корпуса камеры, но второй ни к чему не подключен.

Вероятно, я что-то упустил, но для меня такое решение не имеет большого смысла. В техническом паспорте написано, что у чипа «полностью интегрированные USB-выводы, а также есть резисторы для повышения и понижения напряжения». Возможно, инженеры Leica решили не внедрять собственный USB PHY, а использовали тот что в хабе, который хорошо протестирован и работает из коробки. На самом деле, не могу их винить, потому что раньше я пытался сделать подобное, и это оказалось непростой задачей. Может, это фича для защиты от подделки, кто знает.

В любом случае, если вы разбираетесь в USB PHY и готовы помочь, не стесняйтесь написать мне: должна же быть возможность работать через USB-порт без этого фирменного аксессуара :)

Снова PTP


Как я уже сказал, пришло время поиграться с расширением Leica PTP.

К счастью, я нашёл довольно классную библиотеку C++ вместо libptp — это libEasyPTP. Также не заняло много времени написать инструмент на основе этой библиотеки: я уже знал некоторые ограничения в интерфейсе Leica PTP.

И хотя M240PTPTool порядочно глючит, он вполне подходит на роль доказательства концепции (код программы).

По PTP идёт только два запроса: GetDebugBuffer (0x900C) и DebugCommandString (0x900D). Кстати, чтобы модули заполняли журнал отладки, необходимо в меню установить Debug Level как “Debug” или “Debug RAW”.

В интерфейсе M240PTPTool есть несколько опций:

  • exit — закрыть инструмент;
  • flush — слить отладочный буфер с камеры:

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
...


Любой другой текст отправляется к камеру как отладочная команда. Например, help выводит все возможные команды с аргументами:

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

...


Полный список довольно большой, но смотрите, можно отправлять прямые сообщения Softune для любой задачи! Что бы такого интересного туда отправить…

Ещё одна популярная строка, которую часто ищут в прошивке — dump. Посмотрим, есть ли у нас такая.

$ 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


Видимо, можно сделать дамп прошивки на SD-карту. По ссылке на строку “Dumping files to card” легко найти код, отвечающий за это. Он расположен в гигантском блоке System Task (pid 11, как мы уже знаем) и вызывается сообщением 0x11080006 без аргументов.

Наберите send Message;11;0x11080006 в M240PTPTool, нажмите Enter и смотрите на экран.



Затем извлеките SD-карту и проверьте, что на ней.



Вот он, полный дамп, включая прошивку.

Это открывает бесконечные возможности. Например, можно сделать крошечное устройство с MCU, поддержкой USB-хоста и кнопками для запуска сложных последовательностей сообщений…

А потом у нас родился второй ребенок. :)

Эпилог


Если вы не хотите ломать устройство, обычно есть способ его изучить, не открывая корпус или припаивая провода к печатной плате. Ниже мои советы, если вам интересно:

  • найдите всю публичную информацию об устройстве: технические характеристики, данные о комплектующих, фотографии внутренностей, видео с завода ;)
  • если у вас есть прошивка, покопайтесь в ней и поищите подсказки о внешних выходах;
  • всегда гуглите разные магические и странные последовательности байтов, которые найдёте в файлах прошивки;
  • измерьте GND/потенциал/сопротивление для всех неизвестных контактов, выставленных наружу;
  • прозондируйте эти контакты анализатором логики;
  • всегда помните о мерах безопасности, связанных с электроникой;
  • попробуйте исключить контакты, которые вам не нужны (земля, питание);
  • если не можете распознать сигнал по аналоговому виду, попробуйте поискать в Google наиболее популярные (USB/UART/SPI/I2C/1Wire);
  • если возникли идеи о природе сигнала, попробуйте проверить на похожей бытовой электронике;
  • подумайте три пять раз, прежде чем отправлять данные на устройство;
  • и конечно, не стесняйтесь спрашивать совета.



github.com/alexhude

Удачных вам взломов!
Tags:
Hubs:
+219
Comments135

Articles