Пристрій NVRAM UEFI сумісних прошивках, частина третя

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

Відмова від відповідальності №3

Навіть не знаю, що сюди писати після перших двох «відмов», крім того, що автор в черговий раз знімає з себе будь-яку і будь-яку відповідальність за втрату працездатності вашої NVRAM, прошивання, системи і всього іншого. Використовуйте відомості на свій страх і ризик, ведіть себе добре, і все буде добре.
Якщо ви випадково натрапили на цю статтю і вам не ясно, що тут відбувається, чому автор поринає в якісь формати з головою, нічого не пояснюючи, і так як він посмів взагалі — перша і друга частини чекають вас. Решта — за мною!

Phoenix Флеш 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 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.