Здравствуй, читатель. За время моего почти десятилетнего отсутствия в мире x86 и UEFI довольно много воды утекло, и то, что раньше считалось интересными, но мало кому нужными настройками прошивки (к примеру, Above 4G Decoding и Resizable BAR) теперь считается фичами первой необходимости (без которых современные видеокарты Nvidia и AMD теряют в производительности, а карты Intel могут и вовсе не работать). При этом прогресс не превратил мощные старые системы в совсем уж полный хлам, и потому есть смысл научить этих старых псов новым трюкам, если это возможно.
Во многих случаях вопрос с Resizable BAR довольно успешно решается добавлением драйвера ReBarDXE в DXE-том прошивки, и редактированием NVRAM для включения Above 4G Decoding. К сожалению, наш замечательный DXE-драйвер должен отработать в самом начале фазы PCI Enumeration, и потому не может быть запущен через какие-либо другие механизмы, кроме добавления в DXE-том прошивки.
К сожалению, мы здесь оказываемся с другой стороны баррикад относительно производителя системы, который должен со своей стороны препятствовать добавлению в прошивку своих устройств всяких непонятных DXE-драйверов с горы. Делают они это с переменным успехом, у некоторых получается лучше, у некоторых - хуже, а некоторые заранее отказываются от любой "безопасности".
В этот вот конкретном случае ко мне на верстак попала прошивка рабочей станции HP Z840 (материнская плата M60, версия 2.61), которая упрямо отказывалась стартовать после любых (даже минимальных и неинвазивных) модификаций DXE-тома, и при этом не была накрыта Intel BootGuard, т.е. являлась корнем доверия самой себе. В HP, конечно, прекрасно понимали, что цена такой "защиты" - грош, и потому на более новых системах реализовали и BootGuard, и собственную систему мониторинга и восстановления "неавторизованных" изменений в прошивке (маркетинговое название SureStart).
Чаще всего такое поведение - это результат работы PEI-модуля, который проверяет целостность DXE-тома либо по хешу, либо по криптографической подписи. Оба этих способа - не очень надежные, потому что и эталонный хеш, и подпись, и открытый ключ для проверки этой подписи, и сам код PEI-модуля, призванного проверять целостность чего-бы то ни было на SPI-чипе - все они хранятся на том же самом SPI-чипе, и могут быть модифицированы атакующим.
HP, как выяснилось, большие любители подобных защит, но в этот раз никаких приметных сигнатур они не оставили, и потому пришлось пойти несколько другим путем, которым я хочу поделиться в этой статье.
В поисках подозреваемого
Для того, чтобы разобрать прошивку подопытной системы и снять мешающую защиту, нам потребуются:
собственно прошивка (большое спасибо HP за использование LVFS)
IDA 9.x с плагином efiXplorer
немного интуиции и здравого смысла
Качаем, распаковываем UEFI-капсулу из CAB-архива, открываем M60_0261.cap в UEFITool NE, видим следующее:

Кроме UEFI-томов в этом файле также присутствуют непустые регионы (которые UEFITool называет Padding), в этой прошивки их целых пять. Чаще всего в них хранятся данные DMI, прошивка Embedded Controller'а, и прочее подобное, но и хеши\подписи следует тоже поискать в них в первую очередь.
Для того, чтобы проверить хеш\подпись какого-то региона прошивки, его требуется сначала найти, причем быстро (потому что проверка эта - довольно долгая процедура, каждый раз замедляющая загрузку). Скорее всего, где-то в прошивке имеется карта содержимого SPI-чипа, в которой перечислены все "интересные" места, включая и сам DXE-том, и место, где хранится его хеш\подпись. Чтобы проверить, если ли у прошивки такая карта на самом деле, достаточно поискать все вхождения адреса (либо смещения, либо размера) какого-либо тома, и посмотреть, где именно они найдутся. Так и поступим:

Внезапно, мы нашли не только PEI/DXE/SMM с говорящими названиями вроде FlashInfoPei, но и два последовательных (с разницей в 16 байт) вхождения адреса DXE-тома внутри одного из Padding'ов, присмотримся к этому вхождению повнимательнее:

Опа, здравствуйте, непонятный блок с высокой энтропией сразу после адреса и размера DXE-тома, вы подозрительно похожи на криптографическую подпись. Теперь нужно понять, кто именно проверяет эту подпись, и пояснить ему, что проверять нужно менее тщательно.
Для этого вернемся к уже упомянутому выше PEI-модулю с картой (FlashInfoPei, он же 9219007F-D094-4761-9EB5-C14CF9D716C0). Откроем его в IDA, обработаем плагином efiXplorer, и увидим, что все, что этот модуль делает - публикует PEI-to-PEI Interface (структура с указателями на функции, которые могут вызывать другие PEI-модули) с GUID 7BBE8ED7-9F34-4CFA-A586-E74F10C6C788 и единственной функцией GetFlashInfo(), которая после (тривиальной) декомпиляции и небольшой разметки выглядит вот так:
EFI_STATUS GetFlashInfo(UINT32 *a)
{
if ( !a )
return EFI_INVALID_PARAMETER;
a[0] = 0xFF600000; // Физический адрес региона BIOS
a[1] = 0xA00000; // Размер региона BIOS
a[2] = 0x1000000; // Полный размер SPI-чипа
// Основной PEI-том
a[3] = 0xFFEB8000; // Физический адрес
a[4] = 0x8B8000; // Смещение внутри региона BIOS
a[5] = 0x146000; // Размер
// Часть паддинга перед основным PEI-томом
a[6] = 0xFFEB0000;
a[7] = 0x8B0000;
a[8] = 0x7000;
// Паддинг перед томом с микрокодами
a[9] = 0xFFE7F000;
a[10] = 0x87F000;
a[11] = 0x1000;
// Часть паддинга перед DXE-томом
a[12] = 0xFF620000;
a[13] = 0x20000;
a[14] = 0x40000;
// Свободное место
a[15] = 0xFFEB7000;
a[16] = 0x8B7000;
a[17] = 0x1000;
// Запасной PEI-том
a[18] = 0xFFD39000;
a[19] = 0x739000;
a[20] = 0x146000;
// Часть паддинга перед запасным PEI-томом
a[21] = 0xFFD31000;
a[22] = 0x731000;
a[23] = 0x7000;
// Подпись основного PEI-тома
a[24] = 0xFFD00000;
a[25] = 0x700000;
a[26] = 0x1000;
// Прошивка EC
a[27] = 0xFF660000;
a[28] = 0x60000;
a[29] = 0x40000;
// Свободное место
a[30] = 0xFFD38000;
a[31] = 0x738000;
a[32] = 0x1000;
// Boot-блок (часть прошивки, которая не обновляется)
a[33] = 0xFFE7F000;
a[34] = 0x87F000;
a[35] = 0x181000;
// NVRAM-том
a[36] = 0xFFCA0000;
a[37] = 0x6A0000;
a[38] = 0x40000;
// Хранилище переменных VSS2
a[39] = 0xFFCA0000;
a[40] = 0x6A0000;
a[41] = 0x1E000;
// Часть паддинга перед DXE-томом
a[42] = 0xFF600000;
a[43] = 0;
a[44] = 0x10000;
// Подпись DXE-тома
a[45] = 0xFF6A0000;
a[46] = 0xA0000;
a[47] = 0x1000;
// DXE-том
a[48] = 0xFF6A1000;
a[49] = 0xA1000;
a[50] = 0x5FF000;
// Пустая запись
a[51] = 0;
a[52] = 0xFFFFFFFF;
a[53] = 0;
// Пустая запись
a[54] = 0;
a[55] = 0xFFFFFFFF;
a[56] = 0;
// Основной том с микрокодами
a[57] = 0xFFE80000;
a[58] = 0x880000;
a[59] = 0x30000;
// Запасной том с микрокодами
a[60] = 0xFFD01000;
a[61] = 0x701000;
a[62] = 0x30000;
// Всё
return 0;
}
Итого в карте, который FlashInfoPei предоставляет всем остальным PEI-модулям, нашлись и сам DXE-том, и блок по адресу 0xFF6A0000 со скриншота выше.
Теперь посмотрим, кто именно является потребителем этой важной информации, и выберем среди них модуль, который либо сам считает какие-либо хеши, либо предоставляет эту возможность другим модулям. Для этого поищем GUID нашего FlashInfoPpi:

Ага, значит у нас тут есть модуль, которому так нужна карта региона BIOS, что он не запустится, пока её не опубликуют. При этом его размер 140 Кб, а сам он (небольшой спойлер) реагирует на кличку PeiCrptDriver, и содержит в себе ASCII-строки вроде "SHA-256 part of OpenSSL 0.9.8w 23 Apr 2012". Вот он, наш подозреваемый!
Препарируем подозреваемого
Вынимаем PE32-образ из прошивки при помощи UEFITool NE, открываем его в IDA 9.x, обрабатываем плагином efiXplorer (который любезно подтянет нужные типы и определения автоматически), и после декомпиляции и небольшой разметки получаем следующее:

Оказывается, что драйвер этот сначала проверяет, что мы не находимся на пути просыпания из S3 Sleep (если так, то ничего ни проверять не требуется), затем производит некие проверки, а затем публикует PPI с GUID FF3D0A61-2245-424D-8DBE-7B6744B72E75, которым могут воспользоваться другие PEI-модули в дальнейшем.
Посмотрим, что там за проверки, и что за функции публикует подозреваемый в своем PPI.

Одну из этих непонятных функций мы уже обнаружили, это DoVerifications, которую подозреваемый и сам вызывает, и для остальных публикует. Хорошо, но нужно понять, что делают остальные.
После непродолжительного реверс-инжениринга, получаем примерно следующее:

Первые 8 функций - это просто примитивы для хеширования буферов произвольного размера и расположения алгоритмами SHA1 и SHA256, затем идет интересная HashAndCompare, а все последующие, в том числе упомянутая выше DoVerifications, так или иначе вызывают HashAndCompare, и их результат напрямую зависит от нее.
Таким образом, для того, чтобы этот драйвер перестал проверять какие-либо хеши\подписи и не мешал модификации DXE тома, нам нужно сделать так, чтобы HashAndCompare прекратила что-то там хешировать и сравнивать, и начала возвращать успех при любых входных данных.
Выглядит наша функция вот так:
HashAndCompare proc near
arg_0 = dword ptr 8
arg_4 = dword ptr 0Ch
arg_8 = dword ptr 10h
arg_C = dword ptr 14h
push ebp
mov ebp, esp
xor eax, eax ; Теперь в EAX лежит 0
cmp [ebp+arg_0], eax ; Сравним первый параметр с 0
jz short loc_error ; Если равно - это ошибка, выполняем переход
cmp [ebp+arg_4], eax ; Сравним второй паметр с 0
jz short loc_error ; И т.п.
cmp [ebp+arg_8], eax
jz short loc_error
cmp [ebp+arg_C], 7FFFFFFFh
ja short loc_error
cmp [ebp+arg_C], eax
jz short loc_error
;
; Здесь реальная работа функции, которая нас не интересует
;
retn
loc_error:
xor al, al ; Теперь в AL лежит 0, это возвращаемое значение
pop ebp
retn
HashAndCompare endp
Итого, при ошибке функция возвращает 0 в регистре AL, т.е. для того, чтобы она всегда возвращала 1, нам потребуется превратить ее в следующее:
push ebp
mov ebp, esp
xor eax, eax ; Теперь в EAX лежит 0
inc eax ; Теперь в AL лежит 1, это возвращаемое значение
pop ebp
retn
Команда cmp [ebp+8], eax в бинарном виде занимает 3 байта (39 45 08), и комбинация inc eax, pop ebp, retn тоже занимает 3 байта (40 5D C3), поэтому достаточно заменить одно на другое, и наш драйвер, и все пользователи его PPI - все будут твердо уверены в том, что прошивка прошла все проверки и не модифицирована никем, век воли не видать!
Итого: ищем в нашем драйвере последовательность 33 C0 39 45 08 74, меняем на 33 C0 40 5D C3 74, проверяем - загружается. Добавляем в DXE-том драйвер ReBarDXE - загружается. Настраиваем все остальное, вставляем видеокарту Intel Arc A770, которая раньше не работала - и теперь она работает!

Заключение
В этот раз нам повезло, и не пришлось выдумывать обход BootGuard или SureStart, и получилось достаточно быстро найти и нейтрализовать мешавший код, но на более современных машинах так просто уже не будет, потому что BootGuard не даст модифицировать содержимое PEI-тома, и патчить модули из него не получится без предварительной замены чипсета или поиска гораздо более интересных уязвимостей чем "мы храним код проверяльщика на том же устройстве, которое проверяем".
