
NVRAM device in UEFI-compatible firmware, part one

After three years, it turned out that the NVRAM storage can fall apart for various reasons, and most often this event leads to a "brick", i.e. it’s impossible to take advantage of the aforementioned command, and the data (or what is left of them) must be obtained. Having assembled a couple of collapsed NVRAMs manually in the Hex editor, I said “ stop it! ”, Added support for parsing NVRAM formats in UEFITool NE , and decided to write a series of articles about these formats in hot pursuit and fresh memory.
In the first part, we’ll talk about what this NVRAM is all about and consider the VSS format and its variations. If interested, welcome to cat.
Denial of responsibility
Without daring to violate an already established tradition, I am forced to write that all the information that you can gather here can lead to the destruction of your NVRAM, firmware, system, and faith in humanity, through careless or improper use. The author is not responsible at all for anything, use the knowledge at your own peril and risk, do exercises, eat well and sleep more.
All the information described in this article was obtained by reverse engineering many different images of UEFI-compatible firmware, and therefore do not claim to be 100% complete or true. If you find a mistake - report it in the comments, I will be glad to fix it.
Introduction
To begin with, what is this NVRAM all about and why did the authors of the UEFI specification suddenly need it, given that before that everyone calmly used to store their CMOS SRAM settings on a battery and did not buzz. I already talked a little about the "logical" level of NVRAM , but here I will try to tell in more detail about the "physical".
So, NVRAM is such a special data area that stores those UEFI variables that have the Non-Volatile attribute set. The most popular variables of this kind are Setup , which stores most of the current settings from BIOS Setup, BootXXXX / BootOrder / BootNext , which control the boot order, PK / KEK / db / dbx / dbt , which are responsible for the operation of SecureBoot, MonotonicCounter, which protects against replay attacks on the previous five, and many others, the specific list depends on the vendor, the model of the board, and the version of its firmware.
Most often, NVRAM is located on the same SPI chip as the executable firmware code, for one simple and banal reason - it is almost free (because 100-200 Kb on an 8 MB chip can be found almost always, and a separate CMOS SRAM chip on 128 Kb costs quite tangible money). This for free leads to several very serious risks:
- If the NVRAM driver has an error, then it can destroy not only its data, but also the data of its neighbors, including the one in which the code is stored, then after rebooting the machine will get a stake, and it will be very difficult to restore it from this state.
- Each entry in NVRAM (and they are usually done a few each time you turn it on and each reboot) reduces the resource of the SPI chip, and under some conditions (for example, at a constantly high temperature, which is not uncommon for industrial PCs), after 3-5 years, the resource this one is fully developed and the system begins to behave very strange. At the same time, manufacturers of SPI chips of the 25th series do not provide any analogues of SMART, EXT_CSD or automatic wear-out leveling, and I have already seen a couple of times systems on which the chip was just "tired" to complete inoperability and had to be changed.
- It is impossible to reset a damaged or incorrect NVRAM with a jumper or by removing the battery; erasing is necessary using an external SPI device in relation to the storage. Some manufacturers imitate the behavior of the CLEAR_CMOS jumper familiar to users using a special DXE driver, storing the CMR SRAM flag (which is still there, but is now much smaller, because it only stores a clock and a couple of flags), NVRAM_IS_VALID. If at the next boot this flag is cleared, then restoring the default values for variables like Setup. Unfortunately, very often this does not help, because Before loading this driver, there was a whole PEI phase, in which there were also modules with requests to NVRAM, and if the requests could not be satisfied, then nothing will be restored, because the download will stop earlier.
NVRAM Requirements
When implementing the “physical” level of NVRAM, firmware manufacturers had to solve many questions: how to provide quick access to variables for reading (they are read quite actively during boot), how to reduce the load on flash memory during recording, how to store variables in such a way that duplicate data common to several variables (vendor GUIDs, for example), how to recover at least some of the data after a failure, and so on. At the same time, the Intel NVRAM data storage format proposed by Intel during the release of the EFI 1.10 standard turned out to be simple, but it did not satisfy all of the above requirements, plus its format was not described in the UEFI PI specification , i.e. The choice of NVRAM implementation was left to the final vendors.
As a result, instead of a single FFSv2 format, which, although it later received an extended title and a couple of controversial fields in ZeroVector , remained the standard, the vendors managed to implement three fundamentally different formats for NVRAM, which makes it very interesting to parse it.
What are the formats
Before talking about formats, let's talk a little about their names. Each vendor, following a long tradition of calling their country "country" or "land", and its people - "people", calls its format "NVRAM storage format", which makes it difficult to distinguish between them. But we were lucky: NVRAM is usually stored inside a special volume with a relatively arbitrary structure, then the storage headers have signatures, and these signatures for each format turned out to be different. Here by signatures I will call them, although this terminology has not yet been established.
The first historically and in terms of prevalence was the Intel proposed VSS format at the dawn of EFI, which in the UEFI 2.3.1C standard was expanded to support the protected variables used to implement SecureBoot, and also received a couple of extensions from Apple that are used only in their firmware. An FTW block can be stored next to the data in the VSS format , the data from which helps to restore the NVRAM in the event of an abnormally unfinished recording (remember that “you can turn off the computer's power” at any second). After implementing SecureBoot, it was necessary to store the default values for its variables, for which some vendors added the FDC block (also named by signature) to the same format, where these "defaults" are stored.
Almost immediately, it turned out that it was not necessary to store NVRAM exclusively in the VSS format, so one of the vendors (I don’t know exactly who was the first, in my opinion it was Phoenix) replaced it with the EVSA format , in which there appeared deduplication of GUIDs and variable names, but FTW capabilities have disappeared. This format has not received much distribution, but sometimes it is still no, no, it can be found in older firmware from the time of UEFI 2.1. EVSA uses the same primary and secondary NVRAM volumes for its storages as VSS, so the analysis of the structure of these volumes, as I have already said, is a very interesting exercise.
Apple went a step further and added two more data blocks to the same long-suffering volumes - SVS , the format of which matches the regular VSS to the signature, andFsys , the format of which Apple came up with from scratch.
The last format on our list is NVAR , developed by AMIand used by them from the very first implementations of Aptio4, it has survived two updates since then, one of which added a checksum for the data stored in the variable, and the second - support for SecureBoot protected variables. The format itself is quite interesting, uses GUID deduplication, optimizes the character size in variable names (which, according to the specification, are encoded in UCS2), if all of them fit in a single-byte encoding, it is relatively resistant to crashes, but needs periodic garbage collection. Unfortunately, the updates did not affect him in the best way, and his analysis after them became much more complicated, and the likelihood of errors increased with it, so it is unclear whether AMI benefited from the decision not to use VSS or not.
The list turned out to be quite impressive, but it usually happens when a specification gives vendors too much freedom of choice, and they cynically use this freedom.
VSS format and its variations
NVRAM data in all UEFI-compatible firmware I have seen, except based on AMI code (which I will discuss in the part devoted to the NVAR format), is stored in one or more volumes with GUID FFF12B8D-7696-4C8B-A985-2747075B4F50 (aka EFI_SYSTEM_NV_DATA , I call it “primary”), or with GUID 00504624-8A59-4EEB-BD0F-6B36E96128E0 (I call it “optional”).
Both volumes have a sparse structure, so you have to look at them byte by byte in search of signatures for storages and blocks. The VSS repository header is as follows:
struct VSS_VARIABLE_STORE_HEADER {
UINT32 Signature; // Сигнатура
UINT32 Size; // Полный размер хранилища вместе с заголовком
UINT8 Format; // Байт, указывающий на то, что с форматом хранилища все хорошо (0x5A)
UINT8 State; // Байт, указывающий на то, что с данными в хранилище все хорошо (0xFE)
UINT16 Unknown; // Неизвестное поле, используется только в заголовках Apple SVS
UINT32 : 32; // Зарезервированное поле
};
Not everyone still knows how to parse C language structures on the fly, so it makes sense to show the same structure in the screenshot:

It is easy to see that we have the VSS repository header with the corresponding signature, with a total size of 0xFFB8 bytes, correctly formatted and with the correct data.
Apple sometimes uses the same header, but with a different signature - $ SVS . Why is this done ? I don’t know, think different , apparently.
Immediately after the storage header, the variables stored in it begin. They are located one after another, and on all architectures, except for IA64 (aka Itanium), for which the requirement of aligning the beginning of variables along an eight-byte boundary is mentioned, but I just do not have firmware images for this architecture to verify this statement.
Three different types of variable formats have accumulated over the ten-year history of VSS: the old one, which was used before UEFI 2.3.1C, its extension from Apple with an additional field for CRC32, and the new one, the implementation of which was required to support SecureBoot. Perhaps there are some others, but so far I have not managed to find images with them, maybe the readers will succeed.
Standard
This format was widely used by almost all manufacturers of UEFI-compatible firmware, except for AMI, for about seven years, until the introduction of SecureBoot was required. The heading of the "standard" variable looks like this:struct VSS_VARIABLE_HEADER {
UINT16 StartId; // Маркер начала переменной (0xAA 0x55)
UINT8 State; // Состояние переменной
UINT8 : 8; // Зарезервированное поле
UINT32 Attributes; // Аттрибуты переменной
UINT32 NameSize; // Размер имени переменной, которое хранится как 0-терминированная строка в UCS2
UINT32 DataSize; // Размер данных, хранящихся в переменной
EFI_GUID VendorGuid; // GUID переменной
};
This time, several variables can be shown in the screenshot at once:

More precisely, one and a half: PchInit and the Setup part. They have state 0x7F (VARIABLE_HEADER_VALID), attributes 0x07 ( NV + BS + RT ), name length 0x10 and 0x0C, data length 0x04 and 0x2B0, and GUID E6C2F70A-B604-4877-85BA-DEEC89E117EB and 4DFBBADE-1392-4BB-ABB-1392-4BBABAB-1392-4BBABAB-1392-4BBABAB-1392BBABAB-1392 C41CC5AD7D5D respectively.
If you don’t want to manually disassemble anything, you can use the latest alpha version of UEFITool NE, from which the NVRAM volume from the screenshots above looks like this:

Apple CRC
About a couple of years ago, Apple decided that their variables lacked a checksum, and therefore added another field to the header above, which stores the CRC32 checksum of the variable data block. Apple uses this format to this day, and is likely to continue to use it in the future. Its title looks like this:struct VSS_APPLE_VARIABLE_HEADER {
UINT16 StartId; // Маркер начала переменной (0xAA 0x55)
UINT8 State; // Состояние переменной
UINT8 : 8; // Зарезервированное поле
UINT32 Attributes; // Атрибуты переменной
UINT32 NameSize; // Размер имени переменной, которое хранится как 0-терминированная строка в UCS2
UINT32 DataSize; // Размер данных, хранящихся в переменной
EFI_GUID VendorGuid; // GUID переменной
UINT32 DataCrc32; // CRC32-контрольная сумма данных
};
I will not apply screenshots, everything is completely analogous to it, I can only say that Apple uses the additional attribute 0x80000000 (CRC_USED) to distinguish its title from the standard one.
Authenticated
After the UEFI Forum decided to use NVRAM to store keys used by SecureBoot technology , the format needed refinement. The new variables have a header like this:struct VSS_AUTH_VARIABLE_HEADER {
UINT16 StartId; // Маркер начала переменной (0xAA 0x55)
UINT8 State; // Состояние переменной
UINT8 : 8; // Зарезервированное поле
UINT32 Attributes; // Атрибуты переменной
UINT64 MonotonicCounter; // Счетчик, защищающий от replay-атак
EFI_TIME Timestamp; // Временная метка, еще одна защита от replay-атак
UINT32 PubKeyIndex; // Индекс в БД публичных ключей, или 0, если такая БД не используется
UINT32 NameSize; // Размер имени переменной, которое хранится как 0-терминированная строка в UCS2
UINT32 DataSize; // Размер данных, хранящихся в переменной
EFI_GUID VendorGuid; // GUID переменной
};
In the screenshot, such a variable looks something like this:

The marker is the same as for ordinary variables, the state in this case is 0x3F (VARIABLE_ADDED), the attributes are 0x27 (BS + NV + RT + TA ), the counter is not used, but the time stamp in EFI_TIME format is involved , the index in the public key database is also not involved, the name size is 0x08, the data size is 0x64D, the GUID is D719B2CB-3D3A-4596-A3BC-DAD00E67656F, and this variable name is dbx.
In UEFITool, this same variable looks like this:

Conclusion
Well, we’ve more or less figured out the VSS formats, next time we'll talk about the Fsys, EVSA and NVAR formats, as well as about the various data blocks that can be found next to the main NVRAM.I hope that you liked the first part, thank you very much for your attention and see you in the second part.