Возвращаем оригинальные страницы меню в Phoenix SCT UEFI
Здравствуйте, уважаемые читатели Хабра.С вами снова я и мы продолжаем копаться в различных реализациях 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»: 4 вхождения, три из которых — в драйвере по имени PlatfromHiiAdvancedDxe, готовый кандидат на доставание из него форм и дизассемблирование, извлекаем его через Extract body…Запускаем Universal IFR Extractor, указываем путь до извлеченного файла, нажимаем Extract и получаем текстовый файл, в котором описана структура меню Advanced в том виде, в котором оно нам нужно: Ищем в этом файле «Gen1» и находим вот такую настройку: 0×0B018 Form Set: Advanced … 0×44020 Setting: PCIe Speed, Variable: 0×25 0×44046 Default: 8 Bit, Value: 0×0 0×44053 Default: 8 Bit, Value: 0×0 0×44060 Option: Auto, Value: 0×0 0×4406E Option: Gen1, Value: 0×1 0×4407C Option: Gen2, Value: 0×2 Теперь сомнений не остается — это нужный файл, но настройки из него в UEFI Setup не видно.Зато виден другой Advanced, который находится в файле DellSetupAdvancedDxe (найден поиском по строке Advanced в UEFITool), достаем из его исполняемую секцию для дальнейшего изучения: Ну вот, осталось исследовать разницу между файлами и понять, что и где нужно изменить, чтобы вместо второго отображался первый.Исследование Копируем оба файла в ВМ с Linux, собираем radare2 и открываем два терминала, в одном из которых запускаем r2 PlatfromHiiAdvancedDxe.bin, а в другом — r2 DellSetupAdvancedDxe.bin, а после запуска переходим в визуальный режим с дизассемблером командой Vp: Наблюдаем поразительное единодушие, нарушаемое только разными адресами переходов. Все говорит о том, что код сгенерирован из одного и того же шаблона, поэтому сильно он отличаться не будет. Зная архитектуру 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 После непродолжительных поисков очень похожий шаблон находится в обоих файлах: Уже по комментарию radare2 напротив lea rdx ясно, что GUID’ы регистрируемых протоколов отличаются: Теперь можно попробовать заменить GUID в файле PlatfromHiiAdvancedDxe на GUID из DellSetupAdvancedDxe и удалить последний, но лучше поискать, кто именно использует протокол с GUID из DellSetupAdvancedDxe и заменить уже в нем. Вбиваем в поиск: Находим два вхождения, одно из которых нам уже известно, а другое находится в драйвере SystemFormBrowserCoreDxe по смещению 2C0h от начала. Осталось заменить и попробовать в деле.
Тестирование и заключение Заменяем найденный GUID, сохраняем изменения, пересобираем образ и прошиваем на программаторе, после чего заходим в UEFI Setup, открываем Advanced и вуаля, оригинальные настройки как на ладони. Некоторые, понятно, лучше не трогать, некоторые другие не работают, но главное — наконец можно выставить ограничение скорости для PCIe Port 1, ради которого я и затеял эти танцы с бубном.На самом деле, можно было ограничиться исследованием текстового файла с IFR и заменой одного байта в NVRAM на нужный, но раз уж получилось вернуть оригинальное меню — пусть будет так.У других вендоров все может быть устроено иначе, так что не воспринимайте этот пост как универсальное руководство.Спасибо за внимание и удачных вам модификаций.