Возвращаем оригинальные страницы меню в Phoenix SCT UEFI

в 8:09, , рубрики: DXE, HII, phoenix, radare2, SCT, UEFI, UEFITool, реверс-инжиниринг, системное программирование

Здравствуйте, уважаемые читатели Хабра.
С вами снова я и мы продолжаем копаться в различных реализациях UEFI во имя добра. Есть у меня один старый китайский GSM-модем, который на моем Dell Vostro 3360 определяется через раз, а на более старых ноутбуках — нормально. После нескольких экспериментов с подключением его через переходник к основному ПК выяснилось, что ему почему-то не нравится подключение через PCIe Gen2, и хотелось бы переключить порт на Gen1, но в UEFI Setup нужной настройки не оказалось. Печально, но не смертельно, ведь очень часто производители устройств не удаляют оригинальные меню производителя UEFI, а просто скрывают их, либо показывают на их месте свои, поэтому после небольшого реверс-инжиниринга оригинальное меню можно вернуть на место, что у меня и получилось. В этот раз одной IDA Demo уже не обойтись, т.к. DXE-драйверы в большинстве современных UEFI собираются для архитектуры x86-64, поэтому вместо нее будем использовать radare2.
На лавры первооткрывателя не претендую и подобным модификациям сто лет в обед, но постараюсь показать, как сделать подобную модификацию самостоятельно.
Если вам все еще интересно — добро пожаловать под кат.

Мотивация

Модификации меню — достаточно старый, известный и востребованный вид модификаций среди тех, кому доступного изначально меню по каким-то причинам мало. Чаще всего эти причины надуманные, «потому что можно», но бывает и так, что скрытыми оказываются важные настройки вроде возможности практически полностью отключить МЕ, включить отладку по USB (EHCI Debug Port), настроить режимы работы PCIe и т.п. Производителям железа бывает проще скрыть подобные пункты меню «не для всех», чем описывать их в документации и тратить деньги на их поддержку, но такие скрытые пункты чаще всего можно восстановить, чем и займемся. Но для начала — необходимая информация об устройстве Setup-меню.

Коротко об устройстве UEFI Setup

Меню Setup в UEFI устроено достаточно интересным образом и описано в спецификации UEFI Human Interface Infrastructure (главы 29 — 31), но обо всем в короткой статье не рассказать, поэтому если кому интересны подробности — пишите в комментариях.
Тем не менее, основы пояснить стоит. Состоит это самое меню из форм, описанных на языке VFR и Unicode-строк (правда, это не совсем честный Unicode, а лишь UCS-2), хранящихся отдельно. Формы связаны со строками через ID, что облегчает его локализацию.
Самый распространенный элемент меню, комбобокс, на VFR описывается примерно так:

oneof varid     = SETUP_DATA.PrimaryPcie,
    prompt      = STRING_TOKEN(STR_PRIMARY_PCIE),
    help        = STRING_TOKEN(STR_PRIMARY_PCIE_HELP),
    option text = STRING_TOKEN(STR_COMMON_AUTO),  value = 0, flags = DEFAULT | MANUFACTURING | RESET_REQUIRED;
    option text = STRING_TOKEN(STR_COMMON_PCIE1), value = 1, flags = RESET_REQUIRED;
    option text = STRING_TOKEN(STR_COMMON_PCIE2), value = 2, flags = RESET_REQUIRED;
    option text = STRING_TOKEN(STR_COMMON_PCIE3), value = 3, flags = RESET_REQUIRED;
    option text = STRING_TOKEN(STR_COMMON_PCIE4), value = 4, flags = RESET_REQUIRED;
    option text = STRING_TOKEN(STR_COMMON_PCIE5), value = 5, flags = RESET_REQUIRED;
    option text = STRING_TOKEN(STR_COMMON_PCIE6), value = 6, flags = RESET_REQUIRED;
    option text = STRING_TOKEN(STR_COMMON_PCIE7), value = 7, flags = RESET_REQUIRED;
endoneof;

А строки к нему — вот так:

#string STR_PRIMARY_PCIE #language eng "Primary PCIe"

Пояснения требует, наверное, только varid = SETUP_DATA.PrimaryPcie. Дело в том, что изнутри меню на 95% — просто интерфейс к переменным в NVRAM. Переменные могут находится в разных блоках (т.н. varstore), но настройки, к котором есть доступ из Setup, чаще всего хранятся в здоровенном блоке SETUP_DATA, который в свою очередь целиком хранится в переменной по имени Setup. Оставшиеся 5% — это интерактивные элементы меню вроде значений текущего времени, температуры компонентов, скорости вращения вентиляторов и т.п., они обрабатываются callback-функциями, привязанными к соответствующему элементу меню, но это уже другая история.
Элементы меню собираются в формы, затем формы компилируются во внутреннее представление (IFR), собираются в formset'ы и поступают на вход FormBrowser'а — движка, который и показывает пользователю все полученные формы в виде UI. Реализации FormBrowser'ов отличаются в некоторых деталях, и сильнее всех от эталонной реализации от Intel отошли в AMI, по простой причине — поначалу эталонная реализация дико тормозила, т.к. меню хранилось в десятке разных мест и его приходилось собирать при каждом вызове UI, поэтому AMI адаптировали свою реализацию TSE из AMIBIOS8 для UEFI, которую (с переменным успехом) поддерживают и поныне.
В моем же случае UEFI основан на платформе Phoenix SecureCore Tiano 2.3, в которой FormBrowser устроен почти стандартно: formset'ы для каждой вкладки (Main, Advanced, Security, Boot, Exit) хранятся в отдельных DXE-драйверах, а FormBrowser общается с ними через протоколы, которые те регистрируют. Осталось найти нужный драйвер (в котором лежит оригинальное меню Advanced) и пояснить FormBrowser'у, что нужно показывать именно его, а не то, что он показывает вместо нормального Advanced сейчас. Поехали!

Необходимые инструменты

Редактировать образ будем с помощью UEFITool, доставать формы — с помощью Universal IFR Extractor, дизассемблировать и исследовать драйверы formset'ов и сам FormBrowser — с помощью radare2, а прошивать модифицированный файл доверим китайскому программатору за пять баксов.

Поиск

Снимаем дамп прошивки, открываем в UEFITool и ищем то, что нам нужно в самом начале — настройку скорости PCIe-порта по имени «Gen1»:
Возвращаем оригинальные страницы меню в Phoenix SCT UEFI - 1
4 вхождения, три из которых — в драйвере по имени PlatfromHiiAdvancedDxe, готовый кандидат на доставание из него форм и дизассемблирование, извлекаем его через Extract body…
Запускаем Universal IFR Extractor, указываем путь до извлеченного файла, нажимаем Extract и получаем текстовый файл, в котором описана структура меню Advanced в том виде, в котором оно нам нужно:
Возвращаем оригинальные страницы меню в Phoenix SCT UEFI - 2
Ищем в этом файле «Gen1» и находим вот такую настройку:

0x0B018 Form Set: Advanced
...
0x44020  Setting: PCIe Speed, Variable: 0x25
0x44046       Default: 8 Bit, Value: 0x0
0x44053       Default: 8 Bit, Value: 0x0
0x44060       Option: Auto, Value: 0x0
0x4406E       Option: Gen1, Value: 0x1
0x4407C       Option: Gen2, Value: 0x2

Теперь сомнений не остается — это нужный файл, но настройки из него в UEFI Setup не видно.
Зато виден другой Advanced, который находится в файле DellSetupAdvancedDxe (найден поиском по строке Advanced в UEFITool), достаем из его исполняемую секцию для дальнейшего изучения:
Возвращаем оригинальные страницы меню в Phoenix SCT UEFI - 3
Ну вот, осталось исследовать разницу между файлами и понять, что и где нужно изменить, чтобы вместо второго отображался первый.

Исследование

Копируем оба файла в ВМ с Linux, собираем radare2 и открываем два терминала, в одном из которых запускаем r2 PlatfromHiiAdvancedDxe.bin, а в другом — r2 DellSetupAdvancedDxe.bin, а после запуска переходим в визуальный режим с дизассемблером командой Vp:
Возвращаем оригинальные страницы меню в Phoenix SCT UEFI - 4
Наблюдаем поразительное единодушие, нарушаемое только разными адресами переходов. Все говорит о том, что код сгенерирован из одного и того же шаблона, поэтому сильно он отличаться не будет. Зная архитектуру FormBrowser'а, можно предположить, что отличаются файлы тем, что публикуют протокол доступа к ним под различными GUID'ами. Протокол можно опубликовать через вызов gBS->InstallProtocolInterface, который в листинге будет выглядеть примерно так:

mov reg, offset gBS ; указатель на BootServices
lea rcx, offset Handle ; первый параметр - идентификатор протокола или NULL
lea rdx, offset ProtocolGuid ; второй параметр - GUID регистрируемого протокола
xor r8d, r8d ; третий параметр - тип интерфейса, сейчас он всегда 0
lea r9, offset Interface ; четвертый параметр - интерфейс регистрируемого протокола или NULL
call [reg + 80h] ; вызов gBS->InstallProtocolInterface

После непродолжительных поисков очень похожий шаблон находится в обоих файлах:

Возвращаем оригинальные страницы меню в Phoenix SCT UEFI - 5
Уже по комментарию radare2 напротив lea rdx ясно, что GUID'ы регистрируемых протоколов отличаются:
Возвращаем оригинальные страницы меню в Phoenix SCT UEFI - 6
Теперь можно попробовать заменить GUID в файле PlatfromHiiAdvancedDxe на GUID из DellSetupAdvancedDxe и удалить последний, но лучше поискать, кто именно использует протокол с GUID из DellSetupAdvancedDxe и заменить уже в нем. Вбиваем в поиск:
Возвращаем оригинальные страницы меню в Phoenix SCT UEFI - 7
Находим два вхождения, одно из которых нам уже известно, а другое находится в драйвере SystemFormBrowserCoreDxe по смещению 2C0h от начала. Осталось заменить и попробовать в деле.

Тестирование и заключение

Заменяем найденный GUID, сохраняем изменения, пересобираем образ и прошиваем на программаторе, после чего заходим в UEFI Setup, открываем Advanced и вуаля, оригинальные настройки как на ладони. Некоторые, понятно, лучше не трогать, некоторые другие не работают, но главное — наконец можно выставить ограничение скорости для PCIe Port 1, ради которого я и затеял эти танцы с бубном.
На самом деле, можно было ограничиться исследованием текстового файла с IFR и заменой одного байта в NVRAM на нужный, но раз уж получилось вернуть оригинальное меню — пусть будет так.
У других вендоров все может быть устроено иначе, так что не воспринимайте этот пост как универсальное руководство.
Спасибо за внимание и удачных вам модификаций.

Автор: CodeRush

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js