Если вам интересно, что умудрились напридумывать на замену 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 хоть вручную, хоть еще как.
Комментарии (0)