- PVSM.RU - https://www.pvsm.ru -

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

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

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

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

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 записей, чего достаточно с огромным запасом.

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

Можно было еще привести все значения GUIDов, которые мне удалось найти в разных образах и сопоставить с типом данных, но это можно сделать буквально одним скриншотом из UEFITool NE [3], вот он:
Устройство NVRAM в UEFI-совместимых прошивках, часть третья - 3
Кроме 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];   // Двенадцать нулевых байт
};

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

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

CMDB

Следующий блок, на который ссылается карта — CMDB. Назначение его мне не очень понятно, скорее всего он использовался для выбора подходящей конфигурации в прошивках, подходящих сразу к нескольким платам, либо для заполнения таблиц SMBIOS, но на данный момент он уже не используется. Блок этот имеет формат, который иначе как «наркоманским» я назвать не могу, что думали его разработчики — тайна сия великая есть. Смотрите сами:

struct PHOENIX_CMDB_HEADER {
    UINT32 Signature;  // Сигнатура CMDB
    UINT32 HeaderSize; // Размер заголовка
    UINT32 TotalSize;  // Размер заголовка вместе с чанками
};

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

SLIC Pubkey и Marker

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

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 можно включать в заголовок, а можно считать данными
};

Скриншотом:
Устройство NVRAM в UEFI-совместимых прошивках, часть третья - 7
Тут у нас заголовок хранилища с типом 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 то же хранилище выглядит вот так:
Устройство NVRAM в UEFI-совместимых прошивках, часть третья - 8

Кроме уже рассмотренных типов 0xEC («хранилище») и 0xEC (иногда 0xE1, «GUID»), рассмотренных выше, существуют еще три — 0xEE (иногда 0xE2, «имя переменной»), 0xEF (иногда 0xE3, «данные») и 0x83 («удаленные данные»). Насколько я понял, удаление записей типа «GUID» и «имя переменной» возможно только при полной пересборке хранилища драйвером, выполняющем в нем сборку мусора.
Запись с типом «имя» выглядит так:

struct EVSA_NAME_ENTRY {
    EVSA_ENTRY_HEADER Header; // Заголовок записи
    UINT16 VarId;             // Идентификатор имени переменной
    //CHAR16 Name[];          // Имя переменной в кодировке UCS2
};

Картинкой:
Устройство NVRAM в UEFI-совместимых прошивках, часть третья - 9
Запись типа 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[];           // Данные
};

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

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

Заключение

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

Автор: CodeRush

Источник [7]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/formaty-danny-h/118153

Ссылки в тексте:

[1] В первой части: https://habrahabr.ru/post/281242/

[2] во второй: https://habrahabr.ru/post/281412/

[3] UEFITool NE: https://github.com/LongSoft/UEFITool/tree/new_engine

[4] вот сюда: https://github.com/LongSoft/UEFITool/issues/43

[5] RW Everything: http://rweverything.com/

[6] nvram.h: https://github.com/LongSoft/UEFITool/blob/new_engine/common/nvram.h

[7] Источник: https://habrahabr.ru/post/281469/