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

Здравствуйте, шановні читачі. Колись дуже давно, майже 3 роки тому, я написав пару статей про формати даних, використовуваних в UEFI-сумісних прошивках. З тих пір в цих форматах мало що змінилося, тому писати про них знову я не буду. Тим не менш, в тих статтях був досить серйозний пробіл — відсутні будь-які згадки про NVRAM і використовуваних для її зберігання форматах, т. к. тоді розбір NVRAM мені був просто нецікавий, бо ті ж дані можна отримати з UEFI Shell на працюючій системі буквально однією командою dmpstore.
По закінченні трьох років з'ясувалося, що сховище NVRAM вміє розвалюватися з різних причин, і найчастіше це подія призводить до «цеглини», тобто скористатися вищезгаданої командою вже не вийде, а дані (або те, що від них залишилося) треба діставати. Зібравши пару розвалених NVRAM'ів вручну в Hex-редакторі, я сказав "досить це терпіти!", додав підтримку розбору форматів NVRAM UEFITool NE, і вирішив написати цикл статей про цих форматах по гарячих слідах і свіжої пам'яті.
У першій частині поговоримо про те, що таке взагалі цей NVRAM, і розглянемо формат VSS і його варіації. Якщо цікаво — ласкаво просимо під кат.

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

Не сміючи порушувати вже усталену традицію, змушений написати, що всі відомості, які ви можете почерпнути, можуть привести при необережному або неправильному використанні до руйнування ваших NVRAM, прошивання, системи і віри в людство. Автор не несе відповідальності взагалі ні за що, використовуйте отримані знання на свій страх і ризик, робіть зарядку, добре їжте і більше спите.

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

Введення

Почнемо з того, що взагалі таке ця NVRAM і навіщо вона раптом знадобилася авторам специфікації UEFI, з урахуванням того, що до цього все спокійно користувалися для зберігання своїх налаштувань CMOS SRAM на батарейці і не дзижчали. Про «логічному» рівні NVRAM я вже розповідав трохи, а тут постараюся розповісти докладніше про «фізичну».
Отже, NVRAM — це така спеціальна область даних, в якій зберігаються ті UEFI-змінні, у яких встановлений атрибут Non-Volatile. Найпопулярніші змінні такого роду — це Setup, в якій зберігається велика частина поточних налаштувань з BIOS Setup, BootXXXX/BootOrder/BootNext, керуючі порядком завантаження, PK/KEK/db/dbx/dbt, що відповідають за роботу SecureBoot, MonotonicCounter, що захищає від replay-атак на попередню п'ятірку, і безліч інших, конкретний перелік залежить від вендора, моделі плати і версії прошивки.

Найчастіше NVRAM розташовують на тому ж SPI-чіпі, що і виконуваний код прошивки, по одній простій і банальної причини — це практично безкоштовно (бо 100-200 Кб на чіпі ємністю до 8 Мб можна знайти майже завжди, а окрема мікросхема CMOS SRAM на 128 Кб стоїть вельми відчутних грошей). Безоплатність ця приводить до кількох дуже серйозних ризиків:
  1. Якщо в драйвері NVRAM є помилка, то він може зруйнувати не тільки свої дані, але і дані сусідів, в тому числі і те, в якому зберігається код, тоді після перезавантаження машина встане колом, і відновити її з такого стану буде вельми непросто.
  2. Кожна запис в NVRAM (а їх зазвичай роблять кілька при кожному включенні і кожної перезавантаження) знижує ресурс SPI-чіпа, і при деяких умовах (наприклад, при постійно високій температурі, що не рідкість для промислових ПК) вже через 3-5 років цей ресурс повністю виробляється і система починає вести себе дуже дивно. При цьому ніяких аналогів SMART, EXT_CSD або автоматичного wear-out leveling'а виробники SPI-чіпів 25-ої серії не надають, і я вже пару раз бачив системи, на яких чіп просто «втомився» до повної непрацездатності і його довелося міняти.
  3. Неможливо скинути зруйнований або неправильний NVRAM перемичкою або вийманням батарейки, потрібно стирання за допомогою зовнішнього по відношенню до сховища SPI-пристрої. Деякий виробники імітують поведінку звичного користувачам джампера CLEAR_CMOS за допомогою спеціального DXE-драйвера, зберігаючи в CMOS SRAM (яка досі є, але тепер вона значно менше, т. к. зберігаються в ній тільки годинник і пара прапорів) прапор NVRAM_IS_VALID. Якщо при наступному завантаженні прапор цей виявляється скинутий, то виконується відновлення значень за промовчанням для змінних начебто Setup. На жаль, дуже часто це не допомагає, т. к. до завантаження драйвера була ціла фаза PEI, в якій теж були модулі із запитами до NVRAM, і якщо задовольнити запити не вийшло — то й відновити нічого не вийде, бо завантаження припиниться раніше.

Вимоги до NVRAM

При реалізації «фізичного» рівня NVRAM виробникам прошивок довелося вирішувати безліч питань: як забезпечити швидкий доступ до змінних на читання (читаються вони під час завантаження досить активно), як знизити навантаження на флеш-пам'ять при записі, як зберігати змінні таким чином, щоб не дублювати загальні для декількох змінних дані (vendor GUID'и, наприклад), як відновити хоча б частину даних після збою, і так далі. При цьому, запропонований Intel при випуску стандарту EFI 1.10 формат сховища даних NVRAM виявився хоч і простим, але задовольняє далеко не всім перерахованим вище вимогам, плюс його формат не був описаний в специфікації UEFI PI, тобто вибір реалізації NVRAM залишили кінцевим вендорам.

У результаті замість одного формату FFSv2, який хоч і отримав потім розширений заголовок і пару спірних полів ZeroVector, але залишився саме стандартом, для NVRAM вендори примудрилися реалізувати три принципово різних формату, що робить її розбір вельми захоплюючим заняттям.

Які бувають формати

Перш, ніж говорити про формати, поговоримо трохи про їх назвах. Кожен вендор, слідуючи давній традиції назвати свою країну «країною» або «землею», а її народ — «людьми», називає свій формат «форматом зберігання NVRAM», що трохи заважає їх розрізняти. Але нам пощастило: т. к. NVRAM зазвичай зберігається всередині спеціального томи щодо довільною структурою, то в заголовків сховищ є сигнатури, і ці сигнатури у кожного формату виявилися різними. Ось по сигнатурах я їх буду називати, хоча ця термінологія ще не усталилася.

Першим історично і поширеності виявився запропонований Intel на зорі розвитку EFI формат VSS, який у стандарті UEFI 2.3.1 C був розширений для підтримки захищених змінних, використовуваних для реалізації SecureBoot, а також отримав кілька розширень від компанії Apple, що використовуються тільки в їх прошивках. Поряд з даними у форматі VSS може зберігатися блок FTW, дані з якого допомагають відновити NVRAM у разі аварійно незакінченою запису (пам'ятаєте, що живлення пк можна вимкнути» в будь-яку секунду). Після впровадження SecureBoot знадобилося зберігати значення за промовчанням для змінних, для чого деякі вендори додали до того ж формату блок FDC (теж названий по сигнатурі), де ці «замовчування» та зберігаються.

Майже відразу виявилося, що зберігати NVRAM виключно форматі VSS зовсім не обов'язково, тому хтось з вендорів (не знаю точно, хто був першим, здається це був Phoenix) реалізував йому на заміну формат EVSA, в якому з'явилася дедупликация GUID'ів та імен змінних, зате зникли можливості FTW. Формат це не отримав особливого поширення, але іноді все-таки ні-ні, та зустрічається в старих прошивках часів UEFI 2.1. Для своїх сховищ EVSA використовують ті ж самі основний і додатковий тома NVRAM, що і VSS, тому розбір структури цих томів, як я вже говорив, заняття досить захоплююче.

В Apple пішли ще далі, і додали в ті ж багатостраждальні тома ще два блоки даних SVS, формат якого збігається зі звичайним VSS з точністю до сигнатури, і Fsys, формат якого в Apple придумали з нуля.

Останній у нашому списку формат NVAR, розроблений компанією AMI і використовується ними з самих перших реалізацій Aptio4, пережив з тих пір два оновлення, одне з яких додало контрольну суму для даних, що зберігаються в змінних, а друге — підтримку захищених змінних SecureBoot. Сам формат досить цікавий, використовує дедупликацию GUID', оптимізує розмір символу в іменах змінних (які, по специфікації, в кодуванні UCS2), якщо всі вони містяться в однобайтовую кодування, відносно стійкий до збоїв, але потребує періодичної «збирання сміття». На жаль, оновлення вплинули на нього не найкращим чином, і його розбір після них сильно ускладнився, а разом з ним збільшилася і ймовірність помилок, тому незрозуміло, чи виграли AMI що-небудь від рішення не використовувати VSS чи ні.

Список вийшов досить значним, але так зазвичай і буває, коли специфікація дає вендорам занадто багато свободи вибору, і вони цинічно користуються цією свободою.

Формат VSS і його варіації

Дані NVRAM у всіх бачених мною UEFI-сумісних прошивках, крім заснованих на коді AMI (про які я розповім в частині, присвяченій формату NVAR), зберігаються в одному або декількох томах з GUID FFF12B8D-7696-4C8B-A985-2747075B4F50 (він же EFI_SYSTEM_NV_DATA_FV_GUIDя його називаю «основним»), або з GUID 00504624-8A59-4EEB-BD0F-6B36E96128E0 (я його називаю «додатковим»).
Обидва томи мають розріджену структуру, тому доводиться переглядати їх байт за байтом у пошуках сигнатур сховищ і блоків. Заголовок сховища VSS виглядає наступним чином:
struct VSS_VARIABLE_STORE_HEADER {
UINT32 Signature; // Сигнатура
UINT32 Size; // Повний розмір сховища разом із заголовком
UINT8 Format; // Байт, який вказує на те, що з форматом сховища все добре (0x5A)
UINT8 State; // Байт, який вказує на те, що з даними в сховище все добре (0xFE)
UINT16 Unknown; // Невідоме поле використовується в заголовках Apple SVS
UINT32 : 32; // Зарезервоване поле
};

Не все поки ще вміють розбирати структури мови C на льоту, тому є сенс показати ту ж саму структуру на скріншоті:

Легко видно, що перед нами заголовок сховища VSS відповідної сигнатурою, загальним розміром 0xFFB8 байт, правильно відформатований з вірними даними.
Apple іноді використовує такий же заголовок, але з іншого сигнатурою $SVS. Навіщо так зроблено — не знаю, think different, мабуть.
Відразу після заголовка сховища починаються зберігаються в ньому змінні. Розташовуються вони один за одним, і на всіх архітектурах, крім IA64 (вона ж Itanium), для якої згадується вимога вирівнювання початку змінних за восьмибайтовой кордоні, але у мене просто немає образів прошивок для цієї архітектури, щоб перевірити це твердження.

Форматів змінних за десятирічну історію VSS накопичилося три штуки: старий, використовувався до UEFI 2.3.1 C, його розширення від Apple з додатковим полем для CRC32, і новий, впровадження якого було потрібно для підтримки SecureBoot. Можливо, є ще якісь інші, але знайти образи з ними мені поки не вдалося, може бути у читачів вийде.

Standard
Цей формат широко використовувався практично всіма виробниками UEFI-сумісних прошивок, крім AMI, протягом приблизно семи років, поки не знадобилося впровадження SecureBoot. Заголовок «стандартної» змінної виглядає так:
struct VSS_VARIABLE_HEADER {
UINT16 StartId; // Маркер початку змінної (0xAA 0x55)
UINT8 State; // Стан змінної
UINT8 : 8; // Зарезервоване поле
UINT32 Attributes; // Атрибут змінної
UINT32 NameSize; // Розмір імені змінної, яке зберігається як 0-терминированная рядок у UCS2
UINT32 DataSize; // Розмір даних, що зберігаються в змінної
EFI_GUID VendorGuid; // GUID змінної
};

На цей раз на скриншноте можна показати відразу декілька змінних:

Точніше кажучи, півтори: PchInit частина Setup. Вони мають стан 0x7F (VARIABLE_HEADER_VALID), атрибути 0x07 (NV+BS+RT), довжину імені 0x10 і 0x0C, довжину даних 0x04 і 0x2B0, і GUID E6C2F70A-B604-4877-85BA-DEEC89E117EB і 4DFBBAAB-1392-4FDE-ABB8-C41CC5AD7D5D відповідно.

Якщо розбирати вручну нічого не хочеться, можна скористатися останньою альфа-версією UEFITool NE, з нього те NVRAM зі скріншотів вище виглядає так:


Apple CRC
Приблизно пару років тому в Apple вирішили, що їх змінним не вистачає контрольної суми, і тому додали до заголовка вище ще одне поле, в якому зберігається CRC32-контрольна сума блоку даних змінної. Цей формат Apple використовує донині, і, швидше за все, продовжить використовувати в майбутньому. Заголовок його виглядає ось так:
struct VSS_APPLE_VARIABLE_HEADER {
UINT16 StartId; // Маркер початку змінної (0xAA 0x55)
UINT8 State; // Стан змінної
UINT8 : 8; // Зарезервоване поле
UINT32 Attributes; // Атрибути змінної
UINT32 NameSize; // Розмір імені змінної, яке зберігається як 0-терминированная рядок у UCS2
UINT32 DataSize; // Розмір даних, що зберігаються в змінної
EFI_GUID VendorGuid; // GUID змінної
UINT32 DataCrc32; // CRC32-контрольна сума даних
};

Скріншоти прикладати не буду, там все зовсім по аналогії, скажу тільки, що Apple використовує додатковий атрибут 0x80000000 (CRC_USED), щоб відрізняти свій заголовок від стандартного.

Authenticated
Після того, як UEFI Forum прийняв рішення використовувати NVRAM для зберігання ключів, використовуваних технологією SecureBoot, знадобилася доопрацювання формату. Нові змінні отримали заголовок наступного формату:
struct VSS_AUTH_VARIABLE_HEADER {
UINT16 StartId; // Маркер початку змінної (0xAA 0x55)
UINT8 State; // Стан змінної
UINT8 : 8; // Зарезервоване поле
UINT32 Attributes; // Атрибути змінної
UINT64 MonotonicCounter; // Лічильник, захищає від replay-атак
EFI_TIME Timestamp; // Тимчасова мітка, ще одна захист від replay-атак
UINT32 PubKeyIndex; // Індекс в БД публічних ключів, або 0, якщо така БД не використовується
UINT32 NameSize; // Розмір імені змінної, яке зберігається як 0-терминированная рядок у UCS2
UINT32 DataSize; // Розмір даних, що зберігаються в змінної
EFI_GUID VendorGuid; // GUID змінної
};

На скріншоті така мінлива виглядає приблизно так:

Маркер той же, що і у звичайних змінних, стан в даному випадку 0x3F (VARIABLE_ADDED), атрибути — 0x27 (BS+NV+RT+TA), лічильник не задіяний, зате задіяна тимчасова мітка у форматі EFI_TIME, індекс в БД публічних ключів також не задіяний, розмір імені — 0x08, розмір даних — 0x64D, GUID — D719B2CB-3D3A-4596-A3BC-DAD00E67656F, а звуть цю змінну dbx.

У UEFITool ця ж мінлива виглядає ось так:


Висновок

Ну от, з форматами VSS більш або менш розібралися, наступного разу поговоримо про форматах Fsys, EVSA і NVAR, а також про різних блоках даних, яких можна знайти поряд з основною NVRAM.
Сподіваюся, що вам сподобалася перша частина, велике спасибі за увагу і до зустрічі у другій частині.

Джерело: Хабрахабр

0 коментарів

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