Здравствуй, читатель. За время моего почти десятилетнего отсутствия в мире 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-тома, и патчить модули из него не получится без предварительной замены чипсета или поиска гораздо более интересных уязвимостей чем "мы храним код проверяльщика на том же устройстве, которое проверяем".
