Back to Home

NVRAM device in UEFI-compatible firmware, part two

UEFI · firmware · NVRAM · data formats · UEFITool · flush the firmware hub!

NVRAM device in UEFI-compatible firmware, part two

    We continue the conversation about NVRAM formats in UEFI-compatible firmware, which began in the first part . This time, Fsys block formats from Apple firmware, FTW block from firmware following the TianoCore project guidelines, and FDC block, which can be found in firmware based on the Insyde code base, are on the agenda.
    If you are wondering why we need and what non-NVRAM data looks like, which can be found next to NVRAM in the firmware of various manufacturers - welcome to Cat.

    Disclaimer No. 2


    As always, the author is not responsible for anything other than possible mistakes, all bugs are automatically recognized as features, you use the information at your own peril and risk, in short, you already know. If you still do not understand what is happening in this article, where to run, what to do, and who is to blame - read here and here , and come back. Returned or didn’t leave? Great, you can continue.

    Fsys block


    Let's start with the Fsys block format, in which Apple stores settings for a specific hardware model. These settings are then converted into SMBIOS data using the special DXE driver (those that can be read from the OS using the dmidecode utility ).

    The format, of course, is specific to Apple's firmware, and “has always been”, i.e. It is found in both the earliest and the newest firmware. A data block in this format is usually located immediately after the first two VSS repositories (primary and backup), and, in theory, should not be changed by the user, and the data from it is not accessible through UEFI runtime services, therefore I do not consider them NVRAM but if they were (un) lucky to lie in one volume with NVRAM, they also had to deal with them, especially since the format turned out to be trivial, and it can be shown almost all in one screenshot without any C-structures. The block header and variables look like this:

    A block starts with a four-byte signature, usually Fsys (on relatively old machines there was another second block of the same format with a Gaid signature, on more modern machines they put everything in one Fsys block). The signature is followed by 5 unknown bytes, in all the dumps that I have, they are 0x01 0x0E 0x00 0x00 0x00, but for you, of course, they may differ. They are followed by a two-byte total block size, immediately after which the variables begin, without any alignment and with maximum packing. The variable (it is better to call this entity "record", because Apple does not allow the end user to change this data) is stored as follows: a single-byte name length, an ASCII encoded name, a two-byte data length, and the data itself. It turns out that in the screenshot, besides the title, 3 and a half entries are visible - dckt , dckh, dck_ and overrides .
    Pay attention to the beginning of the data: BZ - signature, h - indication of the use of the Huffman code, 1 - indication of the block size, and then the Pi number generally encoded in BCD ... Ba, old friend, Bzip2 format ! We get it, unpack it, and get this:
    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”, type = “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", type = "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 ="? ")


    The entries in the block follow each other until a record with the speaking name EOF is found , after which zeros follow to the very end of the block, and the CRC32 checksum of the entire contents of the block is written in the last four bytes, except for the very last four bytes. Apple generally loves CRC32 very much, and they consider it literally for everything - for Fsys records, for VSS NVRAM variables, for EFI executables, for volumes and for the whole image as well. Integrity to the god of integrity, control to the throne of control!

    If there is no mood to disassemble manually, UEFITool NE comes to the rescue again , in which the Fsys block with the screenshot above looks like this:


    FTW block


    The next block on our preparation list is FTW , which is used to support transactional recording in NVRAM, and helps restore its integrity after a power outage during recording. Unfortunately (or, probably, fortunately), I haven’t come across any firmware dumps with any entries in this block, so it’s only possible to parse the header here, and you will have to go to the TianoCore project code for the content format . However, the theory is theory, but in practice, instead of one beautiful and pleasant title in the firmware, two almost identical ones suddenly appear, like this:
    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]; // Данные
    };
    

    And such:
    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]; // ~~~
    };
    

    Such an unexpected variety creates certain difficulties when trying to guess which version of the structure is in front of us. Fortunately, most often the total FTW block size is a multiple of 16 bytes, and therefore it is enough to check the WriteQueueSize value for divisibility entirely by 16, if it divides, we have the second option, if the remainder is 4, the first, if the remainder is something else, we found another version of this structure, cheers.

    In the screenshot, I will show only the second type of header, because the first is found only in some old firmware from the time of King Gorokh:

    Everything is according to the specification, GUID is FFF12B8D-7696-4C8B-A985-2747075B4F50, CRC32 is 0xB0458FB9, the state of the block is valid, the data size is 0xFE0, which is perfectly divisible by 16, so the last 4 bytes of the header are still a header, not already a piece of data.

    In UEFITool NE, the same block looks like this:


    FDC block


    After the UEFI Forum decided to store the keys for SecureBoot in NVRAM, it was necessary not only to seriously remake the VSS format (which I described in the first part ), but also to solve the problem of storing the "defaults" for these variables, and vendors were again allowed to solve it by yourself. One of such solutions from Insyde, namely the FDC unit , we will now analyze.
    The format there is very simple, but it is completely not clear to me what its developer was guided by. The block title is like this:
    struct FDC_VOLUME_HEADER {
        UINT32 Signature;                          // Сигнатура _FDC
        UINT32 Size;                               // Полный размер блока вместе с заголовком
        //EFI_FIRMWARE_VOLUME_HEADER VolumeHeader; // Заголовок NVRAM-тома, зачем он тут - совершенно непонятно
        //VSS_VARIABLE_STORE_HEADER VssHeader;     // Заголовок хранилища VSS, тоже нужен как собаке пятое колесо
                                                   // Еще и размер в нем указан неверный, чаще всего
    };
    

    In the screenshot, this whole nightmare looks like this:

    Total: the signature is _FDC, the total block size is 0x4000, the header of the NVRAM volume from which nothing is used at all, the signature of the VSS storage is unfilled in a formatted and healthy state, and the area with variables. It turns out that as many as 88 bytes were spent on headers that are generally not needed for anything, my internal optimizer is indignant.

    In UEFITool NE, I decided not to display all these unnecessary headers at all, and therefore the same FDC block in it looks like this:


    Conclusion


    Well, we decided on the format of all sorts of strange blocks stored in the middle of the NVRAM volume, EVSA and NVAR remained for dessert, which we will talk about in the third part. Thanks for attention.

    Read Next