Иногда хочется заглянуть в код прошивки какого-нибудь устройства. Но кроме самой прошивки, которая зашифрована, ничего нет. И как реверсеру с этим жить? В статье рассмотрена реальная ситуация, когда при помощи базовых знаний в computer science и логики удалось решить почти бесперспективную задачу.

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

1. Определение структуры


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

  • v2.8_image.bin
  • v3.7_image.bin
  • v3.7.4_image.bin

При внимательном взгляде все три файла имеют внутри структуру, описываемую схемой TLV (Tag-Length-Value). Например, для v3.7.4_image.bin:



Все значения Little-endian, Tag имеет длину 16 бит, Length — 32 бита.
На первом уровне вложенности в файле присутствует только тег 0x7240, и данные для него занимают весь файл. На втором уровне вложенности (внутри данных тега 0x7240) расположен тег 0x0003 (0x0A байт), потом 0x0000 (0x4BDE0E байт), потом теги 0x0001и 0x0002 (на скриншоте не поместились). На третьем уровне вложенности (внутри данных тега 0x0003) инкапсулирован тег 0x0002, хранящий 4-байтовый номер версии файла 030704FF (если отбросить все FF, то получится 3.7.4).

Внутри остальных тегов, расположенных на втором уровне вложенности (теги 0x0000, 0x0001 и 0x0002), хранятся описания отдельных файлов, «упакованных» в один образ.
Для каждого файла указано имя (тег 0x0001), флаги (тег 0x0002), размер (тег 0x0003), некоторое 16-байтовое значение (тег 0x0004) и собственно данные файла (тег 0x0005).

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



Таким образом, из файлов прошивок можно извлечь зашифрованные данные для всех составляющих (CPUImage, AutoInstall и WebUI). Как оказалось, содержимое AutoInstall во всех трех версиях прошивки совпадает, внутренности WebUI одинаковы для v3.7 и v3.7.4, но отличаются в v2.8, а CPUImage уникален для каждой версии.

2. Догадки по алгоритмам


Тег 0x0004 на третьем уровне вложенности содержит 16-байтовый набор данных с высокой энтропией. Вполне возможно это значение хеша, а самый популярный 128-битовый хеш — MD5.

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



Однако если попытаться найти одинаковые последовательности в рамках одного файла — длинных повторов не встретится.

Подобный эффект возникает при использовании шифрования путем наложения константной гаммы очень большой длины. И самый популярный алгоритм шифрования, который именно так работает, — RC4.

3. Атака на потоковый шифр с константным ключом


Если несколько сообщений зашифрованы c использованием одного и того же ключа (а значит, и гаммы), можно попытаться раскрыть фрагменты этих сообщений, поXORив их между собой. И в тех местах, где в одном из сообщений были нулевые байты, в другом проявится открытый текст.

Взяв файлы AutoInstall и WebUI, получим интересные результаты:

00000000: EB 3C 90 6D 6B 64 6F 73 66 73 00 00 02 04 01 00  л<ђmkdosfs  O
00000010: 02 00 02 F8 0F F8 03 00 20 00 40 00 00 00 00 00  O Oш0ш   @
00000020: 00 00 00 00 00 00 29 6E 1F 3B 15 47 43 54 2D 4C        )nЎ;§GCT-L
00000030: 54 45 20 20 20 20 46 41 54 31 32 20 20 20 0E 1F  TE    FAT12   dЎ
00000040: BE 5B 7C AC 22 C0 74 0B 56 B4 0E BB 07 00 CD 10  ѕ[|¬"Аt>Vґd»• Н>
00000050: 5E EB F0 32 E4 CD 16 CD 19 EB FE 54 68 69 73 20  ^лр2дН-НvлюThis
00000060: 69 73 20 6E 6F 74 20 61 20 62 6F 6F 74 61 62 6C  is not a bootabl
00000070: 65 20 64 69 73 6B 2E 20 20 50 6C 65 61 73 65 20  e disk.  Please
00000080: 69 6E 73 65 72 74 20 61 20 62 6F 6F 74 61 62 6C  insert a bootabl
00000090: 65 20 66 6C 6F 70 70 79 20 61 6E 64 0D 0A 70 72  e floppy andd0pr
000000A0: 65 73 73 20 61 6E 79 20 6B 65 79 20 74 6F 20 74  ess any key to t
000000B0: 72 79 20 61 67 61 69 6E 20 2E 2E 2E 20 0D 0A 00  ry again ... d0
000000C0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
...
00008800: 02 43 44 30 30 31 01 00 00 20 00 20 00 20 00 20  OCD001
00008810: 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 

4. Получение начала гаммы


Современные сотовые модемы любят при подключении создавать виртуальный CD-ROM, с которого можно установить драйверы или сопутствующее ПО. Вероятно, и тут использована та же идея.

Правда, при подключении модема к современным операционным системам (Windows 7/8, Linux, MacOS X) этот CD-ROM или не появляется вообще, или присутствует всего долю секунды, после чего исчезает. Пришлось найти ноутбук 2002 года с Windows XP, где при подключении модема CD-ROM тоже исчезал вскоре после появления, но это занимало целых 5 секунд. За это время можно было успеть прочитать все сектора логического тома и получить образ размером 606 208 == 0x94000 байт, что соответствует размеру файла AutoInstall. И значение MD5 от прочитанного образа оказалось равно 897279F34B7629801D839A3E18DA0345, что соответствует значению тега 0x0004 для этого файла.

Теперь остается поXORить прочитанный образ с содержимым AutoInstall и получить первые 600 килобайт гаммы. Этой гаммой мы можем расшифровать начала файлов CPUImage и WebUI (имеющих длину 4 971 976 и 2 093 056 байт соответственно).

5. Реконструкция образа FDD


Если расшифровать начало файла WebUI (первые 606 208 байт), а остальное заполнить нулями, и проинтерпретировать все как образ FAT, будет видна структура файловой системы и даже доступно содержимое некоторых файлов:

          Name           | Size |  Date  |Time
bru                      |Folder|31.05.12|22:17
cgi-bin                  |Folder|31.05.12|22:17
cors                     |Folder|31.05.12|22:17
css                      |Folder|31.05.12|22:17
eng                      |Folder|31.05.12|22:17
img                      |Folder|31.05.12|22:17
js                       |Folder|31.05.12|22:17
ru                       |Folder|31.05.12|22:17
name.html                |  2248|31.05.12|22:17
easyXDM.js               |101924|31.05.12|22:17
easyXDM.debug.js         |113900|31.05.12|22:17
easyXDM.min.js           | 19863|31.05.12|22:17
easyXDM.Widgets.js       | 11134|31.05.12|22:17
easyXDM.Widgets.debug.js | 11134|31.05.12|22:17
easyXDM.Widgets.min.js   |  3114|31.05.12|22:17
json2.js                 | 17382|31.05.12|22:17
easyxdm.swf              |  1758|31.05.12|22:17
MIT-license.txt          |  1102|31.05.12|22:17

Если при подключенном модеме зайти браузером на веб-интерфейс по адресу http://<имя хоста для модема>/dir, то будет видна в точности такая же файловая система, и любой файл можно будет скачать.

Таким образом, чтобы восстановить образ диска WebUI, надо разложить скачанные через веб-интерфейс файлы по тем местам, где они должны быть в соответствии с данными в boot-секторе, таблице FAT и описаниях директорий. Единственная сложность — папка «ru» в корне. Кластер с описанием файлов в этой папке не попадает в первые 606 208 байт, и его содержимое надо воссоздавать вручную.

Согласно информации из веб-интерфейса, в директории «ru» должны быть следующие файлы:

          Name           | Size |  Date  |Time
Manualupdate.html        |  3981|31.05.12|22:17
Index.html               |  5327|31.05.12|22:17
Network.html             |  3328|31.05.12|22:17

К счастью, в корне есть папка «eng», в которой располагаются файлы с такими же именами и датами создания. И чтобы получить правильные данные для папки «ru» надо всего лишь исправить:

  • номер стартового кластера текущей директории,
  • размер каждого файла,
  • номера стартовых кластеров каждого файла.

Номер кластера директории «ru» (0x213) можно узнать из корневой директории.
Размеры файлов (3981==0xF8D, 5327==0x14CF и 3328==0xD00 соответственно) узнаем из веб-интерфейса.

Номера стартовых кластеров для файлов придется угадывать, но это не очень сложно. Согласно информации в boot-секторе, каждый кластер занимает 4 сектора или 2048 байт. Для директории «ru» достаточно одного кластера, для файлов Manualupdate.html и Network.html — двух, и для Index.html понадобится три кластера. Поскольку кластеры при записи на пустой диск обычно выделяются последовательно, файлы будут начинаться в кластерах 0x214, 0x216 и 0x219 соответственно. Восстановленные данные для директории «ru» будут выглядеть так:

00000000: 2E 20 20 20 20 20 20 20 20 20 20 10 00 00 2C AA  .          >  ,к
00000010: BF 40 BF 40 00 00 2C AA BF 40 13 02 00 00 00 00  ¬@¬@  ,к¬@O
00000020: 2E 2E 20 20 20 20 20 20 20 20 20 10 00 00 2C AA  ..         >  ,к
00000030: BF 40 BF 40 00 00 2C AA BF 40 00 00 00 00 00 00  ¬@¬@  ,к¬@
00000040: 42 68 00 74 00 6D 00 6C 00 00 00 0F 00 56 FF FF  Bh t m l   0 V  
00000050: FF FF FF FF FF FF FF FF FF FF 00 00 FF FF FF FF                  
00000060: 01 6D 00 61 00 6E 00 75 00 61 00 0F 00 56 6C 00  m a n u a 0 Vl
00000070: 75 00 70 00 64 00 61 00 74 00 00 00 65 00 2E 00  u p d a t   e .
00000080: 4D 41 4E 55 41 4C 7E 31 48 54 4D 20 00 00 2C AA  MANUAL~1HTM   ,к
00000090: BF 40 BF 40 00 00 2C AA BF 40 14 02 8D 0F 00 00  ¬@¬@  ,к¬@¶OН0
000000A0: 41 69 00 6E 00 64 00 65 00 78 00 0F 00 33 2E 00  Ai n d e x 0 3.
000000B0: 68 00 74 00 6D 00 6C 00 00 00 00 00 FF FF FF FF  h t m l         
000000C0: 49 4E 44 45 58 7E 31 20 48 54 4D 20 00 00 2C AA  INDEX~1 HTM   ,к
000000D0: BF 40 BF 40 00 00 2C AA BF 40 16 02 CF 14 00 00  ¬@¬@  ,к¬@-O¦¶
000000E0: 41 6E 00 65 00 74 00 77 00 6F 00 0F 00 98 72 00  An e t w o 0 Шr
000000F0: 6B 00 2E 00 68 00 74 00 6D 00 00 00 6C 00 00 00  k . h t m   l
00000100: 4E 45 54 57 4F 52 7E 31 48 54 4D 20 00 00 2C AA  NETWOR~1HTM   ,к
00000110: BF 40 BF 40 00 00 2C AA BF 40 19 02 00 0D 00 00  ¬@¬@  ,к¬@vO d
00000120: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

Аккуратно собрав реконструированную папку «ru» и содержимое всех файлов, полученных из веб-интерфейса, в образ диска (с учетом того, что первый кластер соответствует сектору 0x23), мы получаем plain-text-версию файла WebUI, значение MD5 для которого совпадает с 48D1C3194E45472D28ABFBEB6BBF1CC6 из заголовка обновления.

Теперь у нас есть расшифрованные файлы AutoInstall и WebUI, и мы знаем первые 2 093 056 байт гаммы.

6. Заглянем в CPUImage



После того как мы расшифровали первые 2 мегабайта CPUImage, наконец имеет смысл запустить дизассемблер. После определения системы команд процессора (ARM Little-Endian), базового адреса загрузки (необходимо выкинуть первые 0x34C байт файла) и локализации места расшифровки обновлений можно увидеть вот такой код:

ROM:0008ADD0 loc_8ADD0 
ROM:0008ADD0                 LDR             R1, =byte_2ADC60
ROM:0008ADD4                 LDRB            R2, [R1,R0]
ROM:0008ADD8                 LDRB            R1, [R4]
ROM:0008ADDC                 ADD             R0, R0, #1
ROM:0008ADE0                 ADD             R2, R2, R1
ROM:0008ADE4                 ADD             R2, R2, R6
ROM:0008ADE8                 AND             R6, R2, #0xFF
ROM:0008ADEC                 LDRB            R2, [R10,R6]
ROM:0008ADF0                 STRB            R2, [R4],#1
ROM:0008ADF4                 STRB            R1, [R10,R6]
ROM:0008ADF8                 MOV             R1, #0x15
ROM:0008ADFC                 BL              sub_27C0EC
ROM:0008AE00                 SUBS            R11, R11, #1
ROM:0008AE04                 AND             R0, R1, #0xFF
ROM:0008AE08                 BNE             loc_8ADD0

Это не что иное, как загрузка ключа шифрования, расположенного по адресу 0x2ADC60 и имеющего длину 0x15 байт, в алгоритм RC4. Однако 0x2ADC60==2’808’928, то есть ключ, располагается за пределами области, для которой нам известна гамма.

Правда, у нас целых три версии обновлений. Вдруг в v3.7 или v2.8 ключ в более удачном месте? Увы, в более ранних прошивках адрес расположения ключа будет 0x2AD70C и 0x2A852C, что тоже за пределами расшифрованной области :(

7. И снова XOR


Если поXORить между собой CPUImage от обновлений v3.7 и v3.7.4, то по адресу 0x34C + 0x2AD70C == 0x2ADA58 мы обнаружим строчку «SungKook «James» Shin», которая и является тем самым ключом RC4, на котором зашифрованы все файлы, входящие в обновления.

Остается убедиться, что гамма на выходе RC4, инициализированного этим ключом, совпадает с той, что мы получили ранее из AutoInstall и WebUI, и что MD5 от расшифрованного CPUImage совпадает со значением из заголовка обновления.

Теперь можно приступать к анализу собственно прошивки, но это уже совсем другая история.

Автор: Дмитрий Скляров, руководитель отдела исследований приложений Positive Technologies

Комментарии (14)