Иногда хочется заглянуть в код прошивки какого-нибудь устройства. Но кроме самой прошивки, которая зашифрована, ничего нет. И как реверсеру с этим жить? В статье рассмотрена реальная ситуация, когда при помощи базовых знаний в 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 ☻ 00000010: 02 00 02 F8 0F F8 03 00 20 00 40 00 00 00 00 00 ☻ ☻ш☼ш @ 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 ♫▼ 00000040: BE 5B 7C AC 22 C0 74 0B 56 B4 0E BB 07 00 CD 10 ѕ[|¬"Аt♂Vґ♫»• Н► 00000050: 5E EB F0 32 E4 CD 16 CD 19 EB FE 54 68 69 73 20 ^лр2дН▬Н↓лю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 and♪◙pr 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 ... ♪◙ 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 ☻CD001 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 ┐@┐@ ,к┐@☻ 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 ☼ 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 ☼ 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 ┐@┐@ ,к┐@¶☻Н☼ 000000A0: 41 69 00 6E 00 64 00 65 00 78 00 0F 00 33 2E 00 Ai n d e x ☼ 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 ┐@┐@ ,к┐@▬☻╧¶ 000000E0: 41 6E 00 65 00 74 00 77 00 6F 00 0F 00 98 72 00 An e t w o ☼ Ш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 ┐@┐@ ,к┐@↓☻ ♪ 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
Автор: Positive Technologies