Оглавление
Стартуем с конца. Убираем цепочку сертификатов.
Проверяем хеш программных разделов
В шапке остались заголовок и пользовательские данные
Разбираем пользовательский раздел
Многие знакомы с 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».
Дальше идут данные стандартного заголовка. На его разборе в рамках данной статьи останавливаться не будем. Обращу внимание только на адреса 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.