Расшифровка обновлений одного популярного сотового модема: метод Дмитрия Склярова
Иногда хочется заглянуть в код прошивки какого-нибудь устройства. Но кроме самой прошивки, которая зашифрована, ничего нет. И как реверсеру с этим жить? В статье рассмотрена реальная ситуация, когда при помощи базовых знаний в 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 бита.
На первом уровне вложенности в файле присутствует только тег 0×7240, и данные для него занимают весь файл. На втором уровне вложенности (внутри данных тега 0×7240) расположен тег 0×0003 (0×0A байт), потом 0×0000 (0×4BDE0E байт), потом теги 0×0001и 0×0002 (на скриншоте не поместились). На третьем уровне вложенности (внутри данных тега 0×0003) инкапсулирован тег 0×0002, хранящий 4-байтовый номер версии файла 030704FF (если отбросить все FF, то получится 3.7.4).
Внутри остальных тегов, расположенных на втором уровне вложенности (теги 0×0000, 0×0001 и 0×0002), хранятся описания отдельных файлов, «упакованных» в один образ.
Для каждого файла указано имя (тег 0×0001), флаги (тег 0×0002), размер (тег 0×0003), некоторое 16-байтовое значение (тег 0×0004) и собственно данные файла (тег 0×0005).
Если разобрать теги на всех трех уровнях вложенности, получится примерно такая структура:
Таким образом, из файлов прошивок можно извлечь зашифрованные данные для всех составляющих (CPUImage
, AutoInstall
и WebUI
). Как оказалось, содержимое AutoInstall
во всех трех версиях прошивки совпадает, внутренности WebUI
одинаковы для v3.7 и v3.7.4, но отличаются в v2.8, а CPUImage
уникален для каждой версии.
2. Догадки по алгоритмам
Тег 0×0004 на третьем уровне вложенности содержит 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
, что соответствует значению тега 0×0004 для этого файла.
Теперь остается по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» (0×213) можно узнать из корневой директории.
Размеры файлов (3981==0xF8D, 5327==0×14CF и 3328==0xD00 соответственно) узнаем из веб-интерфейса.
Номера стартовых кластеров для файлов придется угадывать, но это не очень сложно. Согласно информации в boot-секторе, каждый кластер занимает 4 сектора или 2048 байт. Для директории «ru» достаточно одного кластера, для файлов Manualupdate.html
и Network.html
— двух, и для Index.html
понадобится три кластера. Поскольку кластеры при записи на пустой диск обычно выделяются последовательно, файлы будут начинаться в кластерах 0×214, 0×216 и 0×219 соответственно. Восстановленные данные для директории «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» и содержимое всех файлов, полученных из веб-интерфейса, в образ диска (с учетом того, что первый кластер соответствует сектору 0×23), мы получаем plain-text-версию файла WebUI, значение MD5 для которого совпадает с 48D1C3194E45472D28ABFBEB6BBF1CC6
из заголовка обновления.
Теперь у нас есть расшифрованные файлы AutoInstall и WebUI, и мы знаем первые 2 093 056 байт гаммы.
6. Заглянем в CPUImage
После того как мы расшифровали первые 2 мегабайта CPUImage, наконец имеет смысл запустить дизассемблер. После определения системы команд процессора (ARM Little-Endian), базового адреса загрузки (необходимо выкинуть первые 0×34C байт файла) и локализации места расшифровки обновлений можно увидеть вот такой код:
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
Это не что иное, как загрузка ключа шифрования, расположенного по адресу 0×2ADC60 и имеющего длину 0×15 байт, в алгоритм 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