Продолжаем разговор о форматах 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! Достаем, распаковываем, и получаем вот такое:
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 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 со скришнота выше выглядит вот так:
Блок 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, о которых поговорим в третьей части. Спасибо за внимание.
Автор: CodeRush