Олеся Ахметшина. Шедеврум.
Олеся Ахметшина. Шедеврум.

Оглавление

Заголовок файла

Начинаются отличия

Стартуем с конца. Убираем цепочку сертификатов.

Проверяем хеш программных разделов

В шапке остались заголовок и пользовательские данные

Разбираем пользовательский раздел

Увидимся

P.S. 

Многие знакомы с ELF-файлами и их структурой. На Хабре достаточно статей про это. Поговорим о программерах. Программер – это файл в формате ELF (расширение может быть BIN, MBN или ELF), который предназначен для  работы с памятью смартфонов на Android с процессорами от Qualcomm в режиме аварийной загрузки (EDL mode – emergency download, 9008). Также его некоторые называют «пожарный шланг» (от английского firehose) или просто «шланг». Файл представляет из себя контейнер с набором команд для базовой работы с памятью, которые подписаны цепочкой сертификатов. Иногда возникает необходимость подобрать для своего устройства подходящий программер. Очень мало программ, способных решить такой вопрос. Большинство из такого рода софта просто позволяет загрузить имеющийся программер в устройство, а вот выбрать из большого массива, скачанного из интернета или подтянуть из базы данных – крайне мало. Наиболее известный – edl (https://github.com/bkerler/edl). Другой удачный проект, к сожалению, с завершившейся поддержкой базы данных – Emmcdl_Gui (https://4pda.to/forum/index.php?showtopic=1020752). Программы для работы с устройствами в аварийном режиме, но без баз программеров – QFIL и emmdl от Qualcomm (устарели, теперь PCAT, но для неё требуется лицензия), ещё есть несколько программ от вендоров, которые специализируются на конкретных моделях. Можно просто опросить устройство программой QLM CPU Info (https://4pda.to/forum/index.php?showtopic=643084&st=8820#entry90626224) и по этим данным подбирать программер. Есть ещё платные варианты, но на них я, пожалуй, ссылаться не буду. Поэтому в 2020 я решил создать проект «Firehose Finder». В нём я попытался автоматизировать получение данных от устройства и пакетно обрабатывать массив программеров, которые раньше приходилось анализировать практически вручную, по одному. Также у пользователя есть возможность поделиться рабочим программером или идентификаторами своего устройства. Предложение о добавлении в базу данных успешно использованного программера или данных о новом, подключённом устройстве отправляется ботом в общий канал Телеграм и доступно сразу всем подписчикам. Основные моменты, как происходит подбор программера под требуемые параметры, я и хочу показать в этой статье. Ссылку на свой проект на GitHub оставлю в конце статьи.

Заголовок файла

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

Получение идентификаторов устройства происходит по протоколу Sahara, без использования программера. Обращение идёт напрямую к процессору и при успешном старте сессии (процессор должен отправить «Hello» при открытии порта) пользователь увидит идентификаторы, которые будут использованы для проверки корректности выбранного программера.

Основной идентификатор – OEM_PK_HASH – контрольная сумма (хеш) корневого сертификата вендора. Не менее важные:

●       HW_ID – hardware id – идентификатор железа, который состоит из трёх частей:

        – JTAG_ID – код процессора,

        – OEM_ID – код вендора,

       – MODEL_ID – код модели устройства;

●       ANTI_ROLLBACK_VERSION – номер версии программного обеспечения, ниже которого процессор не примет код.

Получив все эти идентификаторы можно начинать поиск подходящего программера. Взял, для примера, программер от Xiaomi для phoenix (Redmi K30). Как и все файлы формата ELF он начинается с magic number «7F45 4C46».

Признак ELF и заголовки
Признак ELF и заголовки

Дальше идут данные стандартного заголовка. На его разборе в рамках данной статьи останавливаться не будем. Обращу внимание только на адреса 0х34, 0х36 и 0х38. Значения по этим адресам рассказывают нам о том, что в файле собрано 0х12 (18) программных заголовков размером 0х38 (56) байт и начинаются они с адреса 0х40.

Начинаются отличия

Если использовать инструмент readelf в Linux с параметрами –l -W, то в ответе можно заметить, что у первых двух программных заголовков не отображаются флаги.

Пропущенные флаги
Пропущенные флаги

В хекс-редакторе адреса этих флагов 0х47-44 и 0х7F-7C, соответственно 0х07 и 0х022 без последних нулей.

Достаём флаги из кода
Достаём флаги из кода

Дальше приходится смотреть документы, описывающие специфические типы флагов[1]:

0x07000000: Access Type 0, Segment Type 7. Non Paged Segment.

0x02200000: Access Type 1, Segment Type 2. Paged segment. Type Hash Table Segmentsh Table

Как мы знаем, первый сегмент, который начинается с нулевого адреса и размером 0х430 (1072) байт – это наш заголовок 64 байта + 18 программных заголовков по 56 байт.

Самое интересное начинается с адреса 0х1000. Это второй программный сегмент, который используется для проверки неизменности кода – таблица хешей. Начиная с 6-й версии данные об устройстве, для которого программер предназначен (поля OU), перенесли в шапку раздела таблицы хешей. Получить их из сертификата стало невозможно, так что попробуем разобрать её элементы. Пользовательские данные должны лежать где-то между заголовком таблицы хешей и самой таблицей (согласно схеме).

Стандартная схема программера
Стандартная схема программера

Стартуем с конца. Убираем цепочку сертификатов.

Для удобства определения пользовательских полей в заголовке таблицы хешей начинаем двигаться от конца раздела таблицы к началу. Как нам известно, стартует раздел с адреса 0х1000 и имеет размер 0х1D08. Смотрим в хекс-редакторе адрес 0х2D08.

Завершение таблицы хешей в коде
Завершение таблицы хешей в коде

Видно, что заполнение условного пустого места байтами 0хFF по этому адресу закончилось и дальше пошли нули. Следующий раздел с ненулевыми данными стартует с адреса 0х3000.

Условное пустое место, которое заполнено 0хFF, начинается с адреса 0х2124. Проверяем окончание цепочки сертификатов. Удобно воспользоваться инструментом в Linux binwalk.

Поиск по маске известных частей кода
Поиск по маске известных частей кода

Адрес начала третьего (корневого) сертификата 0х1D31 + шапка 4 байта + размер 1007 = 0х2124 - конец совпал с началом 0хFF. Проверяем второй и первый сертификаты...

●       0х1913 + 4 + 1050 = 0х1D31 – Ok!

●       0x1508 + 4 + 1031 = 0x1913 – Ok!

Таким образом, получаем адреса самой таблицы хешей: начало 0х1000, конец 0х1507 = 1287 байт. Хешей у нас должно быть по количеству разделов, т.е. 18 штук + 1 общий хеш на всю эту таблицу. Чтобы понять каким методом проводился расчёт хеша, необходимо посмотреть алгоритм цифровой подписи любого из сертификатов. Статья по порядку кодирования ASN1-DER есть на Хабре в свободном доступе, так что просто опишу последовательность байт для поиска на нашем примере: (06092A864886F70D01010A = 1.2.840.113549.1.1.10):

●       06 - OBJECT IDENTIFIER;

●       09 – длина блока;

●       2A – 0x2A = 42. Первые два числа 1.2. (формула х1*40+х2);

●       8648 – 840. Первые 0х80 в уме. 6*128 = 768-840=72(0х48);

●       86F70D – 113549. 80 в уме. 6*128*128=98304. 0хF7-0x80=0x77(119)*128=15232. 98304+15232+0х0D(13)=113549;

●       01 – 1

●       01 – 1

●       0A - 10

В сети можно легко найти кодировки алгоритма цифровой подписи и в зависимости от полученного значения использовать расчёт хеша. В нашем случае это SHA384, что соответствует 384бит/8=48 байтам (96 знаков). К сожалению, для версии 6 я так и не смог найти документы, которые детально описывают алгоритм формирования заголовка таблицы хешей и пользовательские данные, поэтому будем пытаться их вычислить самостоятельно.

Проверяем хеш программных разделов

Зная, что в таблице хешей у нас должны лежать хеши 18 разделов в кодировке SHA384 (48 байт на один хеш) + один общий в конце, смотрим на любую подходящую последовательность байт в промежутке между 0х1000 и 0х1507. По программным заголовкам, где размер файла равен нулю, считаем, что хеш тоже будет равен нулю. Получаем такую вот последовательность, которую потом наложим на данные хекс-редактора. Сразу укажу в ней стартовый адрес и длину, чтоб удобно было вычислить хеш и потом сравнить с эталоном в таблице.

Стартовый адрес

Длина

Описание (комментарий)

Хеш (SHA384 – 48 байт – 96 знаков)

0

0х0

0х430

Заголовок файла

691D5E0F0BCA527007C57DDD6DCD7734E3DC198C26C9BD956ABC26E718F035FEE3C4FADA7EF2EC0D70EE59E7F16AFA3E

1

0х1000

0х1D08

Хеш таблица с заголовком (не считаем)

0х0 (96)

2

0х07BC90

0x0

Пока не считаем

0х0 (96)

3

0х058660

0х001DCC

Тут посчитали

B2D14CBC4449E38862046FF82AAC0F1F4C62F5E11C94E07B8211AEA7A525E24F62E61E7E91480FAADB0BAA3F8BDFA71F

4

0х05A430

0x0

Пока не считаем

0х0 (96)

5

0х07BC90

0x0

Опять не считаем

0х0 (96)

6

0х003000

0х 04E46C

Тут посчитали

173d1921f234b84ba3a4b886e974ddcc 0f1f967ef171ad373e63dd519f5aa191 45d708d0791f085cd1e636a68cbdfda9 (хеш от Тембласт, отличается от эталона) 

7

0х058660

0x0

Уже не считаем

0х0 (96)

8

0х05A430

0х021858

Теперь считаем

2E3B26DAAFE4DF2BA05FEC5BAA07D1074D48FE9C867A939E9AA42336DBF02587FC88B4F47AAEDE99FE718A997E4905EA

9

0х051470

0x0

Пока не считаем

0х0 (96)

10

0х051470

0х0071F0

Теперь считаем

7285c2f867a3886e7941b9e8577ebb38 8a7f7b8ad00f31019c0d18d53783be72 aedee8efc0e06b84f1fe5d54e475bcb4 (хеш от Тембласт, отличается от эталона) 

11

0х07BC90

0х0000AC

Теперь считаем

820DF4D64077328D99347FF924CB417FC228858A8AF4C9EE9B69D00E53F8B6C3CF50E59FBF0AB1F48ECEBC84CFA52BCC

12

0x07BD3C

0x00A890

 

32650B7F67130693C843B1C5278481EEDB99E158FE876E637B9400430BE17FFB9DD6C78BBE220ADE63E114183B62E452

13

0x0865CC

0x003404

 

B22357C1F29ABAF04E07B985DC8D70C8AE4A404771CECF2A5113925AA50F1178CE52B3BA101617E47F6C5CDAD95EE7F5

14

0x0899D0

0x003404

 

BD6589D4C242106C4A9D0A2F24A07C4FE21AAD67EF8A0533FFC6839BBBC883D1496C4BDC55CD1211905E129179573064

15

0x08CDD4

0x000010

 

AE40659DA1193CDEC8DF474B5E36416A82473B83D32DBBE1DD6DF8EC9499D24902CA08C334876BC8E69E818BEECC046A

16

0x08CDE4

0x017000

 

417F5424745EC880A0E102939214FAF9B1D2A6BB0CB347B996BBE47533B1F633D7123050281C250A364D3F0BF66C8237

17

0x058660

0x0

И тут уже не считаем

0х0 (96)

 

0х10A8

0x35F

Общий

???

Итак, сравнили таблицу с данными хекс-редактора и увидели, что первый хеш (запись 0) стартует с адреса 0х10A8, а последний хеш (запись17) заканчивается 0х1407. Для подписи самой таблицы остаётся места 0х1507-0х1407=0х100 (256 байт – 2048 бит). Как проверить этот общий хеш мне не понятно, так что пока оставим. Продолжим разбираться с шапкой.

В шапке остались заголовок и пользовательские данные

На заголовок таблицы хешей и пользовательские данные у нас осталось 0х10A7-0x1000=0xA8(168 байт). Вариантов разделения этой части кода несколько:

●       Разделы могут быть фиксированного размера на основании протокола. Тогда данных о начальном адресе и/или длине записано не будет;

●       Разделы могут быть «плавающего» размера. Тогда в коде должны будут присутствовать либо начальный адрес и размер, либо только размер, если разделы идут последовательно.

●       Может быть комбо из первого и второго вариантов.

Имеет смысл проверить все ненулевые, подходящие данные этой части кода, значения которых лежат в диапазоне от 0х1000 до 0х10A7 (начальные адреса) и от 0х4 (0+4) до 0хA4 (0xA8-4) (размер сектора - минимум 4 байта).

Номер версии и размер пользовательских данных
Номер версии и размер пользовательских данных

Чтоб долго вас не мучить, отмечу сразу. Ищем 4 байта с адреса 0х102С-2F = 0x00000078 (120 байт) – вот наш клиент! С адреса 0х1030 до 0х10A8 как раз и есть 0х78 (120 байт). Теперь мы точно знаем, что заголовок таблицы хешей у нас фиксированный, 48 байт (0х30), с адреса 0х1000 до 0х102F. Пользовательские данные – область плавающего размера, который определяется 4 байтами по адресу 0х102С. В заголовке нас интересует ещё один параметр – по адресу 0х1004 одним байтом (может двумя) описывается номер версии программера = 0х06. Как я писал выше, для версии программера 5 и ниже пользовательские данные лежали в полях OU первого сертификата, для версии 7 данных пока нет.

Разбираем пользовательский раздел

Теперь напишу то, что удалось определить опытным путём, анализируя данные пользовательского раздела программеров от разных вендоров и для разных процессоров на примере выбранного программера:

●       Идентификатор образа (SW_ID) – адрес 0х1038, 1 байт – 0х03 – firehose. По официальной документации - 4 байта;

●       Идентификатор процессора (JTAG_ID) – адрес 0х103C, 4 байта – 0х00000000;

●       Код вендора (OEM_ID) – адрес 0х1040, 2 байта – 0х0072 – Xiaomi. По официальной документации - 4 байта;

●       Идентификатор модели (MODEL_ID) – адрес 0х1044, 2 байта – 0х0000;

●       Версия образа (ANTI_ROLLBACK_VERSION) – адрес 0х10A4, 1 байт – 0х02

Вот такую информацию даёт официальная документация. С учётом данных из примера получается:

●       SW_ID – 32 бита – 4 байта – 8 знаков.

●       HW_ID (JTAG_ID) – 32 бита – 4 байта – 8 знаков.

●       OEM_ID – 32 бита – 4 байта – 8 знаков.

●       MODEL_ID – не указано. По расчёту, из-за флагов, остаётся 5 байт на это поле, что очень странно.

●       FLAGS_ID – 5 байт - 10 знаков – 7 флагов (1-1-1-1-2-2-2). В зависимости от флагов процессор выбирает какие данные получать. В примере все значения флагов 0, кроме 02 и 01. К каким флагам относятся эти значения я так и не разобрался.

●       SOCS_VERS – может содержать до 12 значений при установленном флаге 1. В примере указано только одно значение - 0х600С0000. Могу предположить, что используется либо только оно одно, либо свободное место до следующего поля заполнено нулями и при работе игнорируется.

●       MULTI_SERIAL_NUMBERS – может содержать до 8 серийных номеров, если хотя бы один из трёх последних флагов имеет значение 2. В примере последний флаг, скорее всего, имеет значение 0х02 - debug. В коде размещена какая-то последовательность ненулевых данных. Вполне вероятно, список каких-нибудь серийных номеров. По размеру 8 групп по 4 байта (размер одной группы соответствует правилу записи серийного номера).

●       MRC_INDEX – не указано. Считаем 4 байта.

●       ANTI_ROLLBACK_VERSION – не указано. Считаем 4 байта. В примере 0х00000002.

Увидимся

На этом пользовательские поля заканчиваются, и начинается таблица хешей. Некоторые данные из заголовков я так и не смог идентифицировать, так что, если есть желание и возможности дополнить этот разбор, то пишите свои предложения в комментариях на Хабре, в дискуссиях или предложениях на странице проекта на GitHub. С исходным кодом программы подбора программера под устройство на процессоре от Qualcomm – «Firehose Finder» - можно ознакомиться на GitHub (https://github.com/hoplik/Firehose-Finder). Там же лежат и релизы для установки на Windows х64.

P.S.

Пока рецензировал проект статьи, получил несколько вопросов.

1.      Вопрос: Для процессоров Snapdragon 8+ Gen1 используются программеры версии 7. Будет ли их поддержка в FhF?

Ответ: С уверенностью ответить не могу. По мере возможности я пытаюсь разобраться в алгоритмах новой версии, но дело осложняется тем, что кроме изменения алгоритмов программера новой версии, вендоры стали применять и новую версию протокола Sahara (v.3). Старый подход – получение данных процессора командами Sahara и сравнение их с парсингом программера, для новых процессоров не может быть применён. Некоторые необходимые команды протокола Sahara новый процессор отклоняет как неизвестные.

2.      Вопрос: Для программера из приведённого примера расчётные данные хешей проектом от Temblast (https://www.temblast.com/qcomview.htm) не совпадают с данными, записанными в таблицу хешей для 6 и 10 записи. Это говорит о том, что код программера был изменён после подписания таблицы цепочкой сертификатов?

Ответ: Скорее всего, нет. Полностью нельзя исключать такую возможность, но для данного примера верным будет изменённый алгоритм проверки хешей. В официальной документации есть упоминание о том, что кроме единичной подписи (описано выше) может присутствовать двойная подпись. И это, как раз, тот самый случай. При разборе файла командой binwalk было видно, что у файла два заголовка ELF и две цепочки сертификатов. По адресу начала второго заголовка видно, что он совпадает с адресом начала последней (16-й) записи. Таким образом, мы имеем как бы “виртуальную матрёшку”. Один эльф внутри сектора другого эльфа. Второй эльф разбирается по похожим алгоритмам, но готового решения у меня пока нет. Поэтому просто приведу перевод части официальной документации.[2] Кстати, именно во второй части пользовательских данных лежит идентификатор процессора, для которого этот программер и предназначен, то есть JTAG_ID 0х00000000 надо изменить на 0x00E060E1.

Схема двойной подписи
Схема двойной подписи

«Производители оборудования и QTI могут дважды подписывать образ. Образы с подписью QTI имеют другую таблицу метаданных с SW_ID и хэшем. Некоторые поля маскируются при проверке подлинности хэша сегмента во время проверки QTI. Если хэш в хэш-таблице не совпадает с хэш-сегментом, проверьте, подписано ли изображение, и проверьте с помощью устаревших методов.

Шаги проверки подлинности для изображения с двойной подписью следующие:

1.      Аутентифицируйте таблицу метаданных 1 с помощью OEM-ключа.

2.      Аутентифицируйте таблицу метаданных 2 с помощью ключа QTI.

3.      Проверьте соответствие хэша конкретного сегмента хэша и таблицы метаданных 1 и 2.

4.      Проверьте соответствие хэша конкретного сегмента ELF и хэш в сегменте хэша.»


[1] 80-NL239-45 Secure Boot Enablement User Guide. November 11, 2019

[2] 80-PG596-42 Secure Boot Enablement User Guide. July 29, 2019.

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