Возвращаем оригинальные страницы меню в 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» и находим вот такую настройку:
    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), достаем из его исполняемую секцию для дальнейшего изучения:

    Ну вот, осталось исследовать разницу между файлами и понять, что и где нужно изменить, чтобы вместо второго отображался первый.

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


    Копируем оба файла в ВМ с Linux, собираем radare2 и открываем два терминала, в одном из которых запускаем r2 PlatfromHiiAdvancedDxe.bin, а в другом — r2 DellSetupAdvancedDxe.bin, а после запуска переходим в визуальный режим с дизассемблером командой Vp:

    Наблюдаем поразительное единодушие, нарушаемое только разными адресами переходов. Все говорит о том, что код сгенерирован из одного и того же шаблона, поэтому сильно он отличаться не будет. Зная архитектуру FormBrowser'а, можно предположить, что отличаются файлы тем, что публикуют протокол доступа к ним под различными GUID'ами. Протокол можно опубликовать через вызов gBS->InstallProtocolInterface, который в листинге будет выглядеть примерно так:
    mov reg, offset gBS ; указатель на BootServices
    lea rcx, Handle ; первый параметр - идентификатор протокола или NULL
    lea rdx, ProtocolGuid ; второй параметр - GUID регистрируемого протокола
    xor r8d, r8d ; третий параметр - тип интерфейса, сейчас он всегда 0
    lea r9, Interface ; четвертый параметр - интерфейс регистрируемого протокола или NULL
    call [reg + 80h] ; вызов gBS->InstallProtocolInterface
    

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


    Уже по комментарию radare2 напротив lea rdx ясно, что GUID'ы регистрируемых протоколов отличаются:

    Теперь можно попробовать заменить GUID в файле PlatfromHiiAdvancedDxe на GUID из DellSetupAdvancedDxe и удалить последний, но лучше поискать, кто именно использует протокол с GUID из DellSetupAdvancedDxe и заменить уже в нем. Вбиваем в поиск:

    Находим два вхождения, одно из которых нам уже известно, а другое находится в драйвере SystemFormBrowserCoreDxe по смещению 2C0h от начала. Осталось заменить и попробовать в деле.
    UPD: товарищ gorodianskyi докладывает, что на других версиях SCT в FormBrowser'е может быть куча GUIDов, и, даже если заменить их все, нужные вкладки все равно не появляются, зато имеющиеся пропадают. В его случае получилось заменить бесполезную страницу Info из SystemSetupInfoDxe на нужную страницу Advanced (которая там почему-то зовется Intel) из PlatformSetupAdvancedDxe путем замены в нем GUIDа на тот, который используется в SystemSetupInfoDxe и удаления этого ставшего ненужным драйвера.
    Было
    Стало


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


    Заменяем найденный GUID, сохраняем изменения, пересобираем образ и прошиваем на программаторе, после чего заходим в UEFI Setup, открываем Advanced и вуаля, оригинальные настройки как на ладони. Некоторые, понятно, лучше не трогать, некоторые другие не работают, но главное — наконец можно выставить ограничение скорости для PCIe Port 1, ради которого я и затеял эти танцы с бубном.
    На самом деле, можно было ограничиться исследованием текстового файла с IFR и заменой одного байта в NVRAM на нужный, но раз уж получилось вернуть оригинальное меню — пусть будет так.
    У других вендоров все может быть устроено иначе, так что не воспринимайте этот пост как универсальное руководство.
    Спасибо за внимание и удачных вам модификаций.
    Поделиться публикацией
    Комментарии 15
      +2
      Чтобы стрелки были ровными можно включить их отображение через Unicode: «e scr.utf8=true»
      Или же можно добавить эту команду в $HOME/.config/radare2/.radare2rc
        +1
        Стрелки то ладно, и такие пойдут, но за совет однозначно спасибо. Самый главный вопрос: можно ли сделать так, чтобы вместо call [eax + 0x80] отображалось call [eax + offset InstallProtocolInterface]? Я даже собрал минимально-достаточный заголовочный файл, чтобы получить структуры EFI_BOOT_SERVICES и EFI_RUNTIME_SERVICES, загрузил его командой to, но как сделать аналог apply structure offset в r2 — не понимаю. И можно ли вообще, а если нельзя — когда будет можно?
          +2
          Вообще можно (было), но мы сейчас переписываем analysis engine, так что надо проверять отдельно. Я сегодня ночью посмотрю, что можно сделать, не сильно перелопачивая код.
            0
            Буду ждать, эту фичу заиметь было бы очень здорово.
            Сразу вдогонку — с интерфейсом команды t надо что-то делать, а то там даже прокрутки нет, приходится угадывать названия структур, которые не влезают. И буфер преступно маленький, весь behemoth.h из ida-utils отказывается загружаться.
              0
              По второму понятно — а что имеется ввиду под первым? Можно сюда выложить запись?
                0
                Попробовал — не получается показать то, что мне нужно. Вот файл, который требуется для правильного определения вышеупомянутых структур, загружаем его через to small.h и даем команду t, чтобы посмотреть загруженное. В терминал вываливается 100500 строк текста, на которые не хватает прокрутки. Можно, конечно, буфер сделать больше у терминала, но лучше сделать список структур, а не просто дамп и возможность ходить по нему, выбрать структуру из списка и т.п.
                  +1
                  Ок, понял, подумаем, что можно сделать.
        +1
        Отличная статья! Сколько, нервов и стараний нужно, чтобы во всем разобратся! В моем случае, биос был кастрированый по самое не могу и заблокирован в legacy boot. Теперь, можно выставить uefi загрузку, что отркывает новые возможности, например, использование загрузчика Clover для Hackintosh, загрузку с GPT зарметкой. Доволен как слон! Спасибо!
          0
          Пожалуйста, тебе спасибо за тестирование. Нервов надо много только если разбираться с нуля, ничего в этом не понимая, а так — дело на час, и которых 40 минут — война с малознакомым radare2.
          0
          Есть такой же ноут и недавний разбор показал, что заявленный для более дорогих «комплектаций» модем на положенном месте (визуально наблюдал: слот для сим, разводка). После получения оригинального меню завёлся встроенный модем? Сам даже не пытался, мало времени ковырять сейчас((
            0
            Встроенного там нет, зато его можно вставить в полноразмерный порт mPCIe, и он даже без оригинального меню иногда работает. Более новый модем работал бы и без снижения ему скорости порта до Gen1 принудительно, это просто модем такой кривой попался. Насчет комплектации — она отличается наличием самого модема (у меня изначально стоял SSD на 32 Gb в этом слоте вместо него) и пластиковой задней крышкой с прорезью под SIM-карту, все остальное — такое же.
              0
              Спасибо! Разобрался что к чему, посмотрев под mSata. Надо будет заняться поисками модема хорошо поддерживаемого в linux.
            +2
            Как увидел скриншот, первая мысль: «Неужели нормальный дизассемблер под linux?» Спасибо, я очень долго искал хороший дизассемблер, но radare как-то обошел вниманием.
              0
              Могу еще порекомендовать HT editor, который намного попроще, зато радует знакомым с детства интерфейсом.
              Меня на radare2 навел один из его разработчиков, xvilka, и проект мне понравился, и хотя его интерфейс по умолчанию писали любители vim, но это его не портит. Очень жду, когда допилят Bokken до приличного состояния, и можно понемногу уходить с IDA в сторону открытых решений, а то уж больно дорого стоит нынче ее лицензия.
                +1
                Рекомендую почитать нашу книгу и блог, чтобы ознакомиться с возможностями.

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое