Pull to refresh

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

Reading time8 min
Views15K
Продолжаем разговор о форматах 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.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=«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, о которых поговорим в третьей части. Спасибо за внимание.
Tags:
Hubs:
+20
Comments17

Articles

Change theme settings