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

495c2138929d45f8a7c4252e50281888.jpg Продолжаем разговор о форматах NVRAM в UEFI-совместимых прошивках, начатый в первой части. На этот раз на повестке дня формат блоков Fsys из прошивок компании Apple, FTW из прошивок, следующих заветам проекта TianoCore и FDC, который можно найти в прошивках, основанных на кодовой базе компании Insyde.
Если вам интересно, зачем нужны и как выглядят не-NVRAM данные, которые можно обнаружить рядом с NVRAM в прошивках различных производителей — добро пожаловать под кат.

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


Как всегда, автор не несет ответственности ни за что, кроме возможных очепяток, все баги автоматически признаются фичами, вы используете полученные сведения на свой страх и риск, короче, вы уже и так в курсе. Если вам все еще непонятно, что в этой статье происходит, куда бежать, что делать, и кто виноват — почитайте тут и здесь, и возвращайтесь. Вернулись или не уходили? Отлично, можно продолжать.

Блок Fsys


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

Формат, понятное дело, специфичен для прошивок компании Apple, и «был всегда», т.е. встречается как в самых ранних, так и в самых новых прошивках. Блок данных в этом формате обычно находится сразу за первыми двумя хранилищами VSS (основным и резервным), и, по идее, не должен изменяться пользователем, а данные из него не доступны через UEFI runtime-сервисы, поэтому я и не считаю их NVRAM’ом, но если уж им (не) повезло лежать с NVRAM в одном томе — пришлось разобраться и с ними, тем более, что формат оказался тривиальным, и его можно почти весь показать на одном скриншоте без всяких C-структур. Заголовок блока и переменные выглядят вот так:
b3f4c4abb9214ad780e12cdfe6274942.png
Начинается блок с четырехбайтовой сигнатуры, обычно это Fsys (на относительно старых машинах был еще второй блок того же формата с сигнатурой Gaid, на более современных все кладут в один блок Fsys). За сигнатурой следуют 5 неизвестных байт, во всех дампах, что есть у меня, они равны 0×01 0×0E 0×00 0×00 0×00, но у вас они, понятно, могут отличаться. За ними следует двухбайтовый общий размер блока, сразу за которым начинаются переменные, без всякого выравнивания и с максимальной упаковкой. Переменная (лучше назвать эту сущность «записью», т.к. изменять эти данные Apple конечному пользователю не разрешает) хранится так: однобайтовая длина имени, имя в кодировке ASCII, двухбайтовая длина данных, и сами данные. Получается, что на скриншоте видны, кроме заголовка, 3 с половиной записи — dckt, dckh, dck_ и overrides.
Обратите внимание на начало данных последней — сигнатура BZ, h — указание на использование кода Хаффмана, 1 — указание на размер блока, а затем и вообще закодированное в BCD число Пи… Ба, старый знакомый, формат Bzip2! Достаем, распаковываем, и получаем вот такое:

overrides.txt

ADD_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=»0×26»]
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 Package Core Power»]
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 со скришнота выше выглядит вот так:
74c2278d26474877ae455c13b3ca6d30.png

Блок 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 — первый, если в остатке что-то другое — мы нашли еще один вариант этой структуры, ура.

На скриншоте я покажу только второй тип заголовка, т.к. первый встречается лишь в некоторых старых прошивках времен царя Гороха:
936318187af240aab287e7b369784958.png
Все по спецификации, GUID — FFF12B8D-7696–4C8B-A985–2747075B4F50, CRC32 — 0xB0458FB9, состояние блока — валидный, размер данных — 0xFE0, что отлично делится на 16, поэтому последние 4 байта заголовка — все-таки еще заголовок, а не уже кусок данных.

В UEFITool NE тот же самый блок выглядит вот так:
c2e14913b0e440a7881413e47f0db0c5.png

Блок 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, тоже нужен как собаке пятое колесо
                                               // Еще и размер в нем указан неверный, чаще всего
};


На скриншоте весь этот кошмар выглядит вот так:
0717a633cd1a48159c30ff421696a3a4.png
Итого: сигнатура — _FDC, общий размер блока — 0×4000, заголовок NVRAM-тома, из которого не используется вообще ничего, сигнатура хранилища VSS с незаполненным размером в отформатированном и здоровом состоянии, и область с переменными. Получается что целых 88 байт потрачено на заголовки, которые вообще ни для чего не нужны, мой внутренний оптимизатор негодует.

В UEFITool NE я решил не выводить все эти ненужные заголовки вообще, и потому тот же блок FDC в нем выглядит вот так:
298fb93a683745439af4e854741a8a11.png

Заключение


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

© Habrahabr.ru