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

Продовжуємо розмову про форматах NVRAM UEFI сумісних прошивках, розпочатий у першій частині. Цього разу на порядку денному формат блоків Fsys з прошивок компанії Apple, FTW з прошивок, наступних заповітам проекту TianoCore і FDC, який можна знайти в прошивках, заснованих на кодової базі компанії Insyde.
Якщо вам цікаво, навіщо потрібні і як виглядають не-NVRAM дані, які можна виявити поряд з NVRAM в прошивках різних виробників — ласкаво просимо під кат.

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

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

Блок Fsys

Почнемо з формату блоку Fsys, в якому Apple зберігає настройки для конкретної моделі заліза. Налаштування ці потім за допомогою спеціального DXE-драйвера перетворюються на дані SMBIOS (ті самі, які з ОС можна прочитати утилітою dmidecode).

Формат, ясна річ, специфічний для прошивок компанії Apple, і «завжди», тобто зустрічається як у ранніх, так і в нових прошивках. Блок даних в цьому форматі зазвичай знаходиться відразу за двома першими сховищами VSS (основним та резервним), і, по ідеї, не повинен змінюватися користувачем, а з нього дані не доступні через UEFI runtime-сервіси, тому я і не вважаю їх NVRAM'ом, але якщо вже їм (не) пощастило лежати з NVRAM в одному томі — довелося розібратися і з ними, тим більше, що формат виявився тривіальним, і його можна майже весь показати на одному скріншоті без всяких C-структур. Заголовок блоку і змінні виглядають ось так:

Починається блок з четырехбайтовой вірусні, зазвичай це Fsys (щодо старих машинах був ще другий блок того ж формату з сигнатурою Gaid, на більш сучасних всі кладуть в один блок Fsys). За сигнатурою слідують 5 невідомих байт, у всіх дампи, що є у мене, вони рівні 0x01 0x0E 0x00 0x00 0x00, але у вас вони, зрозуміло, можуть відрізнятися. За ними слід двобайтовий загальний розмір блоку, відразу за яким починаються змінні, без всякого вирівнювання і з максимальною упаковкою. Змінна (краще назвати цю сутність «записом», т. к. змінювати ці дані Apple кінцевому користувачеві не дозволяє) зберігається так: однобайтова ім'я, ім'я в кодуванні ASCII, двухбайтовая довжина даних, і самі дані. Виходить, що на скріншоті видно, крім заголовка, 3 з половиною запису dckt, dckh, dck_ overrides.
Зверніть увагу на початок даних останньої — сигнатура BZ, h — вказівка на використання коду Хаффмана, 1 — вказівка на розмір блоку, а потім і взагалі закодоване в BCD число Пі… Ба, старий знайомий, формат Bzip2! Дістаємо, розпаковуємо, і отримуємо ось таке:overrides.txtADD_DEVICE () [class=«USBPort»,type=«USB 2.0»,location=«right»,speed=«480»,uhci-id=«0xFA133000»,ehci-id=«0xFA130000»]
ADD_DEVICE () [class=«USBPort»,type=«USB 2.0»,location=«left»,speed=«480»,uhci-id=«0xFD113000»,ehci-id=«0xFD110000»]
ADD_DEVICE () [class=«SensorController»,location=«U5510»,model=«EMC1413»,device-key=«SensorController@U5510»]
ADD_DEVICE () [class=«SensorController»,location=«U5530»,model=«EMC1704»,device-key=«SensorController@U5530»]
ADD_DEVICE () [class=«ThunderboltPort»,location=«Left»,port1=«1»,port2=«2»,mcuaddr=«0x26»]
SET_PROPERTY (class=«Processor») [ptype=«iCore»]
SET_PROPERTY (class=«Battery») [cell-count=«2»]
SET_PROPERTY (class=«Sensor»&location=«VC0C») [low-limit=«0.0»,high-limit=«1.23»,type=«Voltage»,description=«VOLTAGE Sensor CPU 0 VCore»]
SET_PROPERTY (class=«Sensor»&location=«VP0R») [low-limit=«7.2»,high-limit=«8.9»,type=«Voltage»,description=«VOLTAGE Sensor PBus 0 Rail»]
SET_PROPERTY (class=«Sensor»&location=«VN0C») [low-limit=«0.0»,high-limit=«1.23»,type=«Voltage»,description=«VOLTAGE Sensor AGX 0 VCore»]
SET_PROPERTY (class=«Sensor»&location=«VD0R») [low-limit=«13.5»,high-limit=«15.5»,type=«Voltage»,description=«VOLTAGE Sensor DCIN»]
SET_PROPERTY (class=«Sensor»&location=«VC1R») [low-limit=«7.2»,high-limit=«8.9»,type=«Voltage»,description=«VOLTAGE Sensor CPU highside»]
SET_PROPERTY (class=«Sensor»&location=«ID0R») [low-limit=«0.0»,high-limit=«3.5»,type=«Current»,description=«CURRENT Sensor DC IN 0 Rail AMON»]
SET_PROPERTY (class=«Sensor»&location=«IB0R») [low-limit=«0.0»,high-limit=«10.0»,type=«Current»,description=«CURRENT Sensor CHGR 0 Rail BMON»]
SET_PROPERTY (class=«Sensor»&location=«IC0R») [low-limit=«0.0»,high-limit=«12.0»,type=«Current»,description=«CURRENT Sensor Chipset 0 INA Highside»]
SET_PROPERTY (class=«Sensor»&location=«IC1R») [low-limit=«0.0»,high-limit=«12.0»,type=«Current»,description=«CURRENT Sensor Chipset 0 SMBUS Highside»]
SET_PROPERTY (class=«Sensor»&location=«IC0C») [low-limit=«0.0»,high-limit=«25.0»,type=«Current»,description=«CURRENT Sensor CPU 0 VCore»]
SET_PROPERTY (class=«Sensor»&location=«IN0C») [low-limit=«0.0»,high-limit=«10.0»,type=«Current»,description=«CURRENT Sensor IG GFX VCore»]
SET_PROPERTY (class=«Sensor»&location=«IM0R») [low-limit=«0.0»,high-limit=«10.0»,type=«Current»,description=«CURRENT Sensor Memory Power»]
SET_PROPERTY (class=«Sensor»&location=«Ts0P») [noise-tolerance=«3.0»,low-limit=«10»,high-limit=«50»,type=«Temperature»,description=TEMP Sensor MLB»]
SET_PROPERTY (class=«Sensor»&location=«TPCD») [noise-tolerance=«3.0»,low-limit=«15»,high-limit=«100»,type=«Temperature»,description=TEMP Sensor PCH»]
SET_PROPERTY (class=«Sensor»&location=«TC0D») [noise-tolerance=«3.0»,low-limit=«10»,high-limit=«110»,type=«Temperature»,description=TEMP Sensor CPU 0 Die»]
SET_PROPERTY (class=«Sensor»&location=«TC0P») [noise-tolerance=«3.0»,low-limit=«20»,high-limit=«87»,type=«Temperature»,description=TEMP Sensor CPU 0 Proximity»]
SET_PROPERTY (class=«Sensor»&location=«TM0P») [noise-tolerance=«3.0»,low-limit=«20»,high-limit=«75»,type=«Temperature»,description=TEMP Sensor Inlet»]
SET_PROPERTY (class=«Sensor»&location=«Ta0P») [noise-tolerance=«3.0»,low-limit=«20»,high-limit=«80»,type=«Temperature»,description=TEMP Sensor Inlet»]
SET_PROPERTY (class=«Sensor»&location=«Tm1P») [noise-tolerance=«3.0»,low-limit=«10»,high-limit=«65»,type=«Temperature»,description=TEMP Sensor Inlet»]
SET_PROPERTY (class=«Sensor»&location=«Tm0P») [noise-tolerance=«3.0»,low-limit=«10»,high-limit=«65»,type=«Temperature»,description=TEMP Sensor Inlet»]
SET_PROPERTY (class=«Sensor»&location=«THSP») [noise-tolerance=«3.0»,low-limit=«10»,high-limit=«65»,type=«Temperature»,description=TEMP Sensor PCH Proximity»]
SET_PROPERTY (class=«Sensor»&location=«Th1H») [noise-tolerance=«3.0»,low-limit=«10»,high-limit=«65»,type=«Temperature»,description=TEMP Sensor Fin Stack»]
SET_PROPERTY (class=«Sensor»&location=«TB1T») [noise-tolerance=«1.0», що low-limit=«10»,high-limit=«50»,type=«Temperature»,description=TEMP Sensor BMU 1»]
SET_PROPERTY (class=«Sensor»&location=«TB2T») [noise-tolerance=«1.0», що low-limit=«10»,high-limit=«50»,type=«Temperature»,description=TEMP Sensor BMU 2»]
SET_PROPERTY (class=«Sensor»&location=«TB0T») [noise-tolerance=«1.0», що low-limit=«10»,high-limit=«50»,type=«Temperature»,description=TEMP Sensor Battery»]
SET_PROPERTY (class=«Sensor»&location=«TC0C») [noise-tolerance=«1.0», що low-limit=«15»,high-limit=«105»,type=«Temperature»,description=TEMP Sensor CPU Die — Digital Core 0»]
SET_PROPERTY (class=«Sensor»&location=«TC1C») [noise-tolerance=«1.0», що low-limit=«15»,high-limit=«105»,type=«Temperature»,description=TEMP Sensor CPU Die — Digital Core 1»]
SET_PROPERTY (class=«Sensor»&location=«PCPT») [noise-tolerance=«1.0», що low-limit=«0»,high-limit=«55»,type=«Power»,description=«POWER Sensor CPU Package Total Power»]
SET_PROPERTY (class=«Sensor»&location=«PCPG») [noise-tolerance=«1.0», що low-limit=«0»,high-limit=«22»,type=«Power»,description=«POWER Sensor CPU Package Gfx Power»]
SET_PROPERTY (class=«Sensor»&location=«PCPC») [noise-tolerance=«1.0», що low-limit=«0»,high-limit=«33»,type=«Power»,description=«POWER Sensor CPU Core Power Package»]
SET_PROPERTY (class=«Sensor»&location=«MO_X») [type=«Accelerometer»,description=«Motion Sensor»]
SET_PROPERTY (class=«Sensor»&location=«MSC0») [low-limit=«9750»,high-limit=«14500»,type=«CalibrationKeys»,description=«Calibration Key 0»]
SET_PROPERTY (class=«Sensor»&location=«MSLD») [type=«Magnetometer»,description=«Magnetometer»]
SET_PROPERTY (class=«HardDrive»&type=«SSD») [throttling-support=«TRUE»]
REMOVE_DEVICE (class=«Sensor») (class=«Sensor»&type="?")


Записи в блоці слідують один за одним, поки не зустрічається запис з промовистою назвою EOF, після якої до самого кінця блоку слідують нулі, а в останніх чотирьох байтах записана контрольна сума CRC32 всього вмісту блоку, крім тих самих останніх чотирьох байт. Apple взагалі дуже любить CRC32, і вважають вони її буквально для всього — для записів Fsys, для змінних VSS NVRAM, для виконуваних файлів EFI, для томів і для всього образу цілком теж. Цілісності богу цілісності, контролю до трону контролю!

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


Блок FTW

Наступний блок у нашому списку на препарування — FTW, який використовується для підтримки транзакційний запису в NVRAM, і допомагає відновити її цілісність після відключення живлення під час запису. На жаль (або мабуть на щастя), мені ще не траплялися дампи прошивки з якимись записами в цьому блоці, так що тут вийде розібрати тільки заголовок, а за форматом вмісту доведеться йти в код проекту TianoCore. Втім, теорія теорією, а на практиці замість одного вдалого і приємного заголовка в прошивках раптово зустрічається два майже однакових, ось такий:
struct EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER32 {
EFI_GUID Signature; // EFI_SYSTEM_NV_DATA_FV_GUID 
UINT32 Crc; // CRC32 від заголовка з порожніми полями Crc і State. 
// Значення порожнього байта визначається бітом ErasePolarity тома NVRAM
UINT8 State; // Стан блоку, валідний (0xFE або 0x01, в залежності від ErasePolarity) чи ні (решта значення)
UINT8 Reserved[3]; // Зарезервоване поле
UINT32 WriteQueueSize; // Розмір блоку даних, раптово UINT32
//UINT8 WriteQueue[WriteQueueSize]; // Дані
};

І такий:
struct EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER64 {
EFI_GUID Signature; // EFI_SYSTEM_NV_DATA_FV_GUID або EDKII_WORKING_BLOCK_SIGNATURE_GUID 
UINT32 Crc; // ~~~
UINT8 State; // ~~~
UINT8 Reserved[3]; // ~~~
UINT64 WriteQueueSize; // Нормальний UINT64, як написано в специфікації
//UINT8 WriteQueue[WriteQueueSize]; // ~~~
};

Таке несподіване різноманіття створює певні труднощі при спробі вгадати, який саме варіант структури перед нами. На щастя, найчастіше сумарний розмір блоку FTW кратний 16 байтам, і тому достатньо перевірити значення WriteQueueSize на подільність націло на 16, якщо ділиться — перед нами другий варіант, якщо в залишку 4 — перший, якщо в залишку щось інше — ми знайшли ще один варіант цієї структури, ура.

На скріншоті я покажу тільки другий тип заголовка, т. к. перший зустрічається лише в деяких старих прошивках часів царя Гороха:

Всі специфікації, GUID — FFF12B8D-7696-4C8B-A985-2747075B4F50, CRC32 — 0xB0458FB9, стан блоку — валідний, розмір даних — 0xFE0, що відмінно ділиться на 16, тому останні 4 байти заголовка — все-таки ще заголовок, а не вже шматок даних.

У UEFITool NE той самий блок виглядає ось так:


Блок FDC

Після того, як UEFI Forum вирішив зберігати ключі для SecureBoot в NVRAM, знадобилося не тільки серйозно переробити формат VSS (про який я розповідав в першої частини), але і вирішувати питання з зберіганням «замовчування» для цих змінних, причому вендорам знову дозволили вирішувати його самостійно. Одне з таких рішень від компанії Insyde, а саме блок FDC, ми зараз і розглянемо.
Формат там дуже простий, але мені абсолютно не зрозуміло, чим керувався його розробник. Заголовок блоку ось такий:
struct FDC_VOLUME_HEADER {
UINT32 Signature; // Сигнатура _FDC
UINT32 Size; // Повний розмір блоку разом з заголовком
//EFI_FIRMWARE_VOLUME_HEADER VolumeHeader; // Заголовок NVRAM-тома, навіщо він тут - абсолютно незрозуміло
//VSS_VARIABLE_STORE_HEADER VssHeader; // Заголовок сховища VSS, теж потрібен, як собаці п'яте колесо
// Ще й розмір в ньому вказана невірна, найчастіше
};

На скріншоті весь цей кошмар виглядає ось так:

Загальна: сигнатура — _FDC, загальний розмір блоку — 0x4000, заголовок NVRAM-тома, з якого не використовується взагалі нічого, сигнатура сховища VSS незаповненим розміром відформатованому здоровому стані, і область зі змінними. Виходить що цілих 88 байт витрачено на заголовки, які взагалі ні для чого не потрібні, мій внутрішній оптимізатор обурюється.

У UEFITool NE я вирішив не виводити всі ці непотрібні заголовки взагалі, і тому той же блок FDC в ньому виглядає ось так:


Висновок

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

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

0 коментарів

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