Устройство NVRAM в UEFI-совместимых прошивках, часть третья

    Перед вами третья часть моего повествования о форматах NVRAM, используемых UEFI-совместимыми прошивками различных производителей. В первой части я рассказывал об NVRAM вообще и о «стандартном» формате VSS, во второй — об интересных блоках, которые можно найти рядом с NVRAM в этом формате, а в этой речь пойдет о целой россыпи различных форматов, используемых в прошивках на платформе Phoenix SCT: FlashMap, EVSA, Intel uCode, CMDB, SLIC pubkey и SLIC marker.
    Если вам интересно, что умудрились напридумывать на замену VSS разработчики из Phoenix — добро пожаловать под кат, только предупреждаю сразу, статья получилась достаточно длинной.

    Отказ от ответственности №3


    Даже не знаю, что сюда писать после первых двух «отказов», кроме того, что автор в очередной раз снимает с себя любую и всякую ответственность за потерю работоспособности вашей NVRAM, прошивки, системы и всего остального. Используйте сведения на свой страх и риск, ведите себя хорошо, и все будет хорошо.
    Если вы случайно наткнулись на эту статью и вам не ясно, что тут происходит, почему автор ныряет в какие-то форматы с головой, ничего не поясняя, и да как он посмел вообще — первая и вторая части ждут вас. Остальные — за мной!

    Phoenix Flash Map


    Впервые открыв содержимое основного тома NVRAM из прошивки имеющегося у меня старенького Dell Vostro 3360 на Ivy Bridge, я был сильно удивлен. Чего там только не найти — сначала целый набор интеловских микрокодов, после него какой-то блок с строками, добрую половину которых составляют копии строки NoLongerUsed, затем смутно знакомые блоки с RSA-сигнатурами, штук пять хранилищ EVSA, и под занавес — структура с говорящей сигнатурой _FLASH_MAP. Короче говоря, ребята из Phoenix умудрились свалить в том NVRAM такую кучу всего, что без карты стало не разобраться. Вот с нее и начнем.
    Заголовок карты занимает 16 байт и выглядит вот так:
    struct PHOENIX_FLASH_MAP_HEADER {
        UINT8  Signature[10]; // Сигнатура _FLASH_MAP
        UINT16 NumEntries;    // Количество записей
        UINT32 : 32;          // Зарезервированное поле
    };

    Сразу за заголовком без дополнительного выравнивания вплотную друг к другу следуют записи вот такого вида:
    struct PHOENIX_FLASH_MAP_ENTRY {
        EFI_GUID Guid;          // GUID записи, помогает определить тип данных, на который эта запись указывает
        UINT16 DataType;        // Тип данных, я видел два - том (0) и данные в томе (1)
        UINT16 EntryType;       // Тип записи, могут быть различными, точно назначение мне пока не известно
        UINT64 PhysicalAddress; // Физический адрес данных, на которые указывает запись
        UINT32 Size;            // Размер данных, на которые указывает запись
        UINT32 Offset;          // Смещение данных, на которые указывает запись, относительно данных, на которые указывает первая запись
    };

    Максимальный размер карты определяется её положением, а т.к. располагается она за 0x1000 байт от конца основного тома NVRAM, максимально в ней может быть ровно 113 записей, чего достаточно с огромным запасом.

    На скриншоте карта выглядит вот так:

    Сразу виден заголовок с сигнатурой и количеством записей, сразу за ним идет запись с нулевым GUID'ом, типом данных 0 (т.е. это том), типом записи 6, физическим адресом 0xFF980000, размером данных 0x20000 и с нулевым смещением (еще бы, относительно самого себя первый том никуда не смещен, иначе что-то сильно не так или с файлом, или с метрикой пространства).

    Можно было еще привести все значения GUIDов, которые мне удалось найти в разных образах и сопоставить с типом данных, но это можно сделать буквально одним скриншотом из UEFITool NE, вот он:

    Кроме GUIDов, также виднеется пара спойлеров: смутно знакомые блоки с RSA оказались публичным ключом и маркером для таблицы SLIC, а блок со строками назвался почему-то CMDB. Ко всем этим вещам мы еще вернемся, а вот с картой все понятно, осталось научиться разбирать форматы всех этих перечисленных блоков, и можно считать, что структуру NVRAM в прошивке на базе Phoenix SCT мы более или менее понимаем. Поехали!

    Intel Microcode


    Первым по списку у нас блок с микрокодами. У меня, к сожалению, нет образа с микрокодами AMD в NVRAM, поэтому рассматривать будем только интеловские. Несмотря на то, что в заголовке микрокода целых 12 свободных байт, места даже под какую-нибудь захудалую сигнатуру не нашлось, и потому поиск такого блока данных среди содержимого тома NVRAM — та еще задача. Будете разрабатывать свой процессор — задумайтесь о сигнатуре для его микрокода, пожалуйста! В любом случае, основной заголовок интеловского микрокода документирован и не менялся, на моей памяти, вообще никогда. Вот он:
    struct INTEL_MICROCODE_HEADER {
        UINT32 Version;        // Версия структуры (1)
        UINT32 Revision;       // Ревизия микрокода
        UINT32 Date;           // Дата релиза
        UINT32 CpuSignature;   // Тип процессора
        UINT32 Checksum;       // Контрольная сумма, корректный микрокод вместе с заголовком и данными должен суммироваться в 0
        UINT32 LoaderRevision; // Ревизия загрузчика микрокодов, для который предназначен микрокод
        UINT32 CpuFlags;       // Флаги процессора
        UINT32 DataSize;       // Размер данных без заголовка
        UINT32 TotalSize;      // Размер данных вместе с заголовком
        UINT8  Reserved[12];   // Двенадцать нулевых байт
    };
    

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

    Версия действительно 1, ревизия — 0x28, дата релиза — 24.04.2012, тип процессора — 0x206A7, контрольная сумма — 0xF3E9935D, ревизия загрузчика — 1, флаги процессора — 0x12, размер данных — 0x23D0, общий размер — 0x2400.

    Тот же самый микрокод в UEFITool NE, видно что таких блоков с микродом оказалось 5 штук:


    CMDB


    Следующий блок, на который ссылается карта — CMDB. Назначение его мне не очень понятно, скорее всего он использовался для выбора подходящей конфигурации в прошивках, подходящих сразу к нескольким платам, либо для заполнения таблиц SMBIOS, но на данный момент он уже не используется. Блок этот имеет формат, который иначе как «наркоманским» я назвать не могу, что думали его разработчики — тайна сия великая есть. Смотрите сами:
    struct PHOENIX_CMDB_HEADER {
        UINT32 Signature;  // Сигнатура CMDB
        UINT32 HeaderSize; // Размер заголовка
        UINT32 TotalSize;  // Размер заголовка вместе с чанками
    };
    

    Заголовок не выглядит каким-то уж очень странным, ну нет общего размера, ну и ладно, конец блока можно найти при разборе, веселье начинается с чанков, которые проще показать на скриншоте сразу:

    После заголовка с сигнатурой CMDB, размером 0x0C и общим размером 0x23 идет нулевой чанк из трех байт: стартовый байт 0x42 (до сих пор борюсь с желанием назвать его TheAnswer), смещение первой строки после заголовка (которое всегда 0) и смещение начала блока со строками (которое всегда TotalSize — HeaderSize). Итого — три поля из трех не используются вообще, зачем нужен этот чанк — решительно непонятно. За нулевым чанком следуют остальные, эти состоят уже из пяти байт: знакомого нам стартового 0x42, смещения строки-ключа, непонятного двухбайтового поля, которое всегда 0x205 и смещения строки-значения. Длины обеих строк при этом нигде не хранятся, и, видимо, даже не вычисляются. В блоке со строками отдельно хранится строка-заголовок BiosInfo, на которую ссылается нулевой чанк, а затем все оставшиеся строки, на которые ссылаются остальные чанки. Общий размер блока — всегда 0x100, поэтому он нигде и не хранится. Хочется спросить, что курил человек, который это придумал?
    Т.к. структура давно уже не используется, и при этом глазами разбирается мгновенно, добавлять поддержку ее разбора в UEFITool NE я не стал. Если вдруг вам это нужно — напишите мне в комментариях или вот сюда.

    SLIC Pubkey и Marker


    Сразу за CMDB друг за другом следуют нужные для OEM-активации Windows Vista/7/2008 блоки Pubkey и Marker, которые потом специальным драйвером переносятся в ACPI-таблицу SLIC. Формат этих блоков я описывать не буду, дабы предотвратить возможный DMCA takedown этой статьи, но разобран он достаточно давно, описание всех полей показывает утилита RW Everything, плюс UEFITool NE их поддерживает, так что если форматы этих блоков вам все-таки необходимы, подсмотрите их в nvram.h.

    EVSA


    Последний формат в нашей карте, который (наконец-то!) используется для хранения переменных NVRAM. По сравнению с VSS формат использует место в NVRAM чуть более эффективно за счет дедупликации имен переменных и их GUID'ов, но и испортить хранилище EVSA намного проще, и практически все мои случаи восстановления данных из NVRAM вручную приходятся именно на него, несмотря на массированное использование этим форматом контрольных сумм. Данные (в том числе и заголовок самого хранилища) хранятся в виде записей с общим заголовком и отличающимися дополнительными полями, вот так:
    struct EVSA_ENTRY_HEADER {
        UINT8  Type;     // Тип записи
        UINT8  Checksum; // Контрольная сумма 
        UINT16 Size;     // Размер записи, если не используется расширенный заголовок
    };
    
    struct EVSA_STORE_ENTRY {
        EVSA_ENTRY_HEADER Header; // Заголовок записи
        UINT32 Signature;         // Сигнатура EVSA
        UINT32 Attributes;        // Атрибуты хранилища
        UINT32 StoreSize;         // Размер хранилища вместе с заголовком
        UINT32 : 32;              // Зарезервированное поле
    };
    
    struct EVSA_GUID_ENTRY {
        EVSA_ENTRY_HEADER Header; // Заголовок записи
        UINT16 GuidId;            // Идентификатор GUIDа
        // EFI_GUID Guid          // Сам GUID можно включать в заголовок, а можно считать данными
    };
    

    Скриншотом:

    Тут у нас заголовок хранилища с типом 0xEC («заголовок хранилища»), однобайтовой контрольной суммой 0x2C, размером 0x14 правильной сигнатурой EVSA, атрибутами 0x01 («тут лежат значения по умолчанию») и размером хранилища 0x2B65. Сразу после заголовка без какого-либо выравнивания идут две записи типа 0xED («GUID»), с контрольными суммами 0x35 и 0xB3 соответственно, размером 0x16, идентификаторами 0 и 1, и GUIDами 4FEE3D67-18F4-4217-BA7B-BC538148382A и 1E1F1797-2CCE-49D6-A6CE-4012F338A76E соответственно.
    В UEFITool NE то же хранилище выглядит вот так:


    Кроме уже рассмотренных типов 0xEC («хранилище») и 0xEC (иногда 0xE1, «GUID»), рассмотренных выше, существуют еще три — 0xEE (иногда 0xE2, «имя переменной»), 0xEF (иногда 0xE3, «данные») и 0x83 («удаленные данные»). Насколько я понял, удаление записей типа «GUID» и «имя переменной» возможно только при полной пересборке хранилища драйвером, выполняющим в нем сборку мусора.
    Запись с типом «имя переменной» выглядит так:
    struct EVSA_NAME_ENTRY {
        EVSA_ENTRY_HEADER Header; // Заголовок записи
        UINT16 VarId;             // Идентификатор имени переменной
        //CHAR16 Name[];          // Имя переменной в кодировке UCS2
    };
    

    Картинкой:

    Запись типа 0xEE, с контрольной суммой 0x39, длиной 0x20 и идентификатором 0, в которой лежит строка DellVariable в UCS2. Показывать в UEFITool NE смысла нет — и так все понятно.

    Осталось рассмотреть последний тип записи — данные. На самом деле, форматов там два, вот такие:
    struct EVSA_DATA_ENTRY {
        EVSA_ENTRY_HEADER Header;  // Заголовок записи
        UINT16 GuidId;             // Идентификатор GUIDа
        UINT16 VarId;              // Идентификатор имени
        UINT32 Attributes;         // Атрибуты
        // UINT8 Data[];           // Данные
    } EVSA_DATA_ENTRY;
    
    struct EVSA_DATA_ENTRY_EXTENDED {
        EVSA_ENTRY_HEADER Header;  // Заголовок записи
        UINT16 GuidId;             // Идентификатор GUIDа
        UINT16 VarId;              // Идентификатор имени
        UINT32 Attributes;         // Атрибуты, специальный атрибут 0x10000000 указывает на наличие дополнительного поля
        UINT32 DataSize;           // Настоящий размер данных, вместо того, что в заголовке
        // UINT8 Data[];           // Данные
    };
    

    Покажу на скриншоте только первый, ибо второй достаточно редко встречается, и там просто еще одно дополнительное поле:

    Тут у нас две записи типа 0xEF, первая из которых имеет контрольную сумму 0x84, размер 0x5F, идентификатор GUIDа 0, идентификатор имени 0 и атрибуты 0x03 (NV+BS), а вторая — 0xEA, 0x11, 1, 1 и 0x03 соответственно. В первой, получается, как раз и хранятся данные той самой вышеупомянутой переменной DellVariable с GUIDом 4FEE3D67-18F4-4217-BA7B-BC538148382A.

    В UEFITool NE:


    Заключение


    Ну вот, теперь формат данных, которые прошивки на кодовой базе Phoenix SCT могут хранить в томах NVRAM, стал немного яснее. Осталось поговорить о формате NVAR, который используется в AMI Aptio, о нем я расскажу в следующей, заключительной части этой статьи.
    Большое спасибо за внимание, высылайте в Л/С замеченные очепятки, и пусть рандом избавит вас от восстановления NVRAM хоть вручную, хоть еще как.
    Поделиться публикацией
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 9
      +1
      Такой порнографии я не ожидал от феникса, пускай даже они давно уже как главных порноделов из авард купили.
        0
        Да я и сам под впечатлением до сих пор, а парсер CMDB даже не смог заставить себя написать, т.к. гадость. Я подозреваю, что они просто раньше всех начали, еще со времен чипсетов пятой серии, и потому на тот момент просто не знали, как правильно пользоваться технологиями EFI вроде банального ReadFile. Поэтому пришлось реализовать свой собственный набор велосипедов, и с тех пор они перекатываются из одного reference tree в другой почти без изменений. Нужно отделить переменные SecureBoot от остальных — вот вам еще пара EVSA store специально под них. Нужны дефолты — вот еще два таких хранилища. Микрокоды надо грузить пораньше — и их в том NVRAM, единственный, содержимое которого можно выдумывать как угодно, и никакая спецификация PI тебе не указ!
        Очень надеюсь, что они одумаются и откажутся от своих тюнингованных шоссейников в пользуй решений из EDKx, когда-нибудь. А пока имеем то что имеем — цирк-шапито на конной тяге.
          +1
          Брр, походу феникс полностью перенял от аварда тягу к костылям на паровой тяге.
        +1
        Зачем нужна дедупликация? Имена переменных не уникальны, а хранятся в дереве?
          0
          Уникальными должна быть комбинация «GUID + имя», но если дедупликация GUIDов еще имеет какой-то смысл (т.к. переменных с одним и тем же GUIDом довольно много, и это совершенно нормально), то с именами этот смысл уже практически не найти.
          +1
          и пусть рандом избавит вас от восстановления NVRAM хоть вручную, хоть еще как.

          К сожалению, не миновал. Хотя восстановлением это не назовешь.
          На работе сосед снял планку с другого компа (работающий за ним в отпуск ушел, а памяти оказалось нужно больше установленной штатно) и поставил к себе. Включает комп — черный экран. пару раз пощелкал — пустое. Подключили динамик (по умолчанию не запаян) — тишина…
          В итоге пришлось банально перепрошивать EFI-Flash (оба банка) — сдули чипы с платы и быстренько залили даже более новую версию. Спасло то, что никаких SecureBoot не было активно, система после такого фокуса запустилась нормально.
          Перед прошивкой сдампил обе флешки и с коллегой восстановили что могли. В итоге оказалось, что при запуске с основной система не запустилась (хз даже почему, повторять неохота, нет свободных машин для таких опытов), после чего попыталась запуститься с резервной, где оказались побиты все переменные. Сама прошивка была целая — все совпало со скачанной с сайта производителя. А вот переменные почему-то «протухли».
            +1
            С основной могла не запуститься потому, что воткнули новую память, понадобилось записать новые SPDData и MemCeiling, и на них не хватило места. Почему «протухла» резервная — понятно непросто, может сама SPI-микросхема попалась глючная, может еще что.
            Мне как-то пришлось именно восстанавливать, т.к. на машине была куча крайне дорогого диагностического софта от нескольких автопроизводителей, который привязывается к данным DMI и упорно отказывается даже страртовать на «другой» машине. А потом на этом ноутбуке неожиданно развалился EVSA NVRAM, в котором половина данных DMI и хранилась, и софт отказался признавать ноутбук за сфоего после восстановления на программаторе. Повезло, что ребята сняли оригинальный дамп, с которым они ко мне и пришли. Посидел с часок, часть данных достал, нужны оказались System UUID и Motherboard Serial Number, которые получилось найти и перенести, после чего софт заработал снова.
              +1
              insyde uefi на 96х на компаловской ноутной платформе, который подходит ко всем ноутам на данной платформе.
              картинки, настройки сетапа, дерево устройств, даже slic выбирается переменной в nvram
              и этот nvram упал… что удивительно, nvram пересобрался сам, только с дефолными настройками: uuid и mb sn занулись, ноут стал идентифицироваться другим производителем, слетела лицензия на винде, ибо slic поменялся, новое дерево устройств с отсутствующими (нераспаянными) устройствами.
              но в моём случае бэкапа не было… и ноут так и остался с дефолным nvram…
                +1
                Обычно хранилищ NVRAM делают два — основное и резервное, и в данном случае прошивка обнаружила повреждение основного хранилища, и переключилась на резервное. Ошибка же производителя в том, что «личные» данные конкретной платы в основное хранилище записали при начальной настройке, а в дополнительное — почему-то забыли. Восстановление после такого можно попробовать провернуть, сняв резервную копию сразу же и попытавшись вытащить данные из основного хранилища, возможно это получилось бы сделать, но через пару-тройку перезагрузок сработает сборщик мусора и тогда все, плакали наши данные.

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

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