⬆️ ⬇️

UEFI BIOS File Device Part Two: UEFI Firmware Volume and its contents

Behind one and a half (the first , one and a half ) parts of this article, now finally the time has come to talk about the structure of the UEFI Firmware Volume and the format of the UEFI File System.



Introduction



I’ll say right away that in this part of the article I will describe the file formats of version 2, since they are used in all existing BIOSes. In the latest versions of the PI standard, a description of the formats of version 3 has been added, but they are needed for files larger than 16 MB, which are not on any motherboard yet, although Gigabyte has already come close to this line on its Z87 boards. The Descriptor region on all modern Intel motherboards supports no more than 2 chips with a capacity of no more than 16 MB, so the use of version 3 formats is postponed until the next generation of Intel chipsets at least.



As an example, take the BIOS region from the version 229 file for the Zotac Z77-ITX WiFi.

We will need the following programs:



Firmware Volume



The structure of the Firmware Volume and the PI FFS format are described in Volume 3 of the UEFI Platform Initializaton documentation.

Firmware Volume is a logical representation of flash content, with the following attributes:

  1. Name: FID and all its parts are their GUIDs
  2. Size: includes all data, headers and free space
  3. Format: file system type inside FV, different types have different GUIDs
  4. Allignment: requires that the first byte of FV be aligned to the specified boundary, a multiple of degree 2. The alignment of FV should not be weaker than the alignment of all files within it, unless the EFI_FVB_WEAK_ALIGNMENT flag is set in the header. In this case, alignment can be done on any power of 2, but this FV can no longer be moved.
  5. Attributes of protection from reading, writing, self-deactivating protection from reading or writing
  6. OEM attributes at manufacturer’s discretion


Regardless of the file system used inside the FV, the FV header is standardized and looks like this:

 typedef struct { UINT8 ZeroVector[16]; UINT8 FileSystemGuid[16]; UINT64 FvLength; UINT32 Signature; UINT32 Attributes; UINT16 HeaderLength; UINT16 Checksum; UINT16 ExtHeaderOffset; UINT8 Reserved[1]; UINT8 Revision; EFI_FV_BLOCK_MAP BlockMap[]; } EFI_FIRMWARE_VOLUME_HEADER; typedef struct { UINT32 NumBlocks; UINT32 Length; } EFI_FV_BLOCK_MAP 


ZeroVector : At the beginning of the FV, 16 bytes are reserved for compatibility with processors whose reset vector is at the zero address. By the presence of something other than zeros in this block, you can unmistakably distinguish the Boot Firmware Volume among the rest.

FileSystemGuid : defines the file system used in this FV.

FvLength : FV size including all headers.

Signature : used to search for FV and by standard is always 0x4856465F, i.e. {'_','F','V','H'} .

Attributes : the very attributes that we talked about above. They are there quite a lot, but the most important thing for us is above EFI_FVB_WEAK_ALIGNMENT , making FV unmovable, if not installed, one of a set EFI_FVB_ALIGNMENT from EFI_FVB_ALIGNMENT_1 to EFI_FVB_ALIGNMENT_2G and EFI_FVB_ERASE_POLARITY , indicating how it is a bit flash chip erase is performed. The rest are needed by the code that works with FV as a memory area, and are useless for us, so we will not list them.

HeaderLength : Header size excluding the extended header, about which below.

Checksum : 16-bit header checksum. A valid header should be summed at 0x0000.

ExtHeaderOffset : the start offset of the extended header. It can contain the GUID of the described FV, a list of OEM file types along with their GUIDs, as well as an electronic signature. At the moment I have not met a single FV with a filled extended header, so we will not consider it. If there is no additional header for FV - in this field 0x0000.

Reserved : reserved field, always 0x00.

Revision : The PI standard describes the structure of only one revision — the second, so this field is always 0x02.

BlockMap : block map stored as a list of EFI_FV_BLOCK_MAP structures, ending in the same structure with zeros in both fields. Since Since all modern flash chips are homogeneous (i.e. have blocks of the same size), the entire list usually consists of only two entries.



Let's check our example for compliance with the above.

Open our BIOS file in a hex editor and move to offset 0x500000, to the beginning of the BIOS region.



We see that there are 16 zeros at the beginning, the file system GUID is available, the size of this FV is 0x020000, the signature is in place, the attributes are set, the header is 0x48 bytes, the checksum is calculated, there is no extended header, the reserved fields are in place, and inside this FV has 20 blocks of 0x1000, which in total gives the size specified in the FvLength field.

Most often, the BIOS has several different FVs designed for different purposes, although this is not necessary and you can pack everything into one. The champion in the degree of nesting of FV and files into each other among all manufacturers of UEFI BIOSes is Intel, where nesting up to 12 levels is encountered.

Although theoretically for FV different file systems can be used, in practice only one is used - PI FFS, which we will talk about now.

')

Firmware File System



This is a flat file system without directories and hierarchies, all files of which are in the horse directory. Getting a list of files requires a pass through the file system from beginning to end. All files must have a standard-defined header. Files must be aligned along an eight-byte boundary relative to the beginning of the file system; a special filler file is provided for alignment along large boundaries.

Also, the standard describes a special VTF file that must be present at the end of each FV, but in practice it is present only at the end of the last FV in the BIOS image and is located so that its last byte is also the last in the entire chip. It contains the boot code needed for the SEC phase.



File header


The header of the FFS file is as follows:

 typedef struct { UINT8 Name[16]; UINT8 HeaderChecksum; UINT8 DataChecksum; UINT8 Type; UINT8 Attributes; UINT8 Size[3]; UINT8 State; } EFI_FFS_FILE_HEADER; 


Name : The GUID of the file acting as the name. In one FV can not be two files with the same GUID, if it is not PAD-files, about which below.

HeaderChecksum : eight-bit header checksum, excluding the DataChecksum field. A valid header should be summed at 0x00.

DataChecksum : an eight-bit checksum of the contents of the file, excluding the header. Its calculation is not always required, but only if the FFS_ATTRIB_CHECKSUM attribute is FFS_ATTRIB_CHECKSUM , otherwise this field is set to 0xAA.

Type : file type. The standard defines 13 standard file types (0x01 - 0x0D), 32 custom types for OEM files (0xC0 - 0xDF), 16 custom types for debugging (0xE0 - 0xEF) and 16 types specific to the current version of FFS (0xF0 - 0xFF) of which only 0xF0 is currently used - EFI_FV_FILETYPE_FFS_PAD for the placeholder file.

This special file can have any, including null GUID, null attributes, standard state and any size. According to the standard, the file must be empty, i.e. all its bits, except the header bits, must be set to EFI_FVB_ERASE_POLARITY . It is used to align the file following it on the border, more than the standard 8 bytes. The minimum size of the PAD-file is equal to the size of the header - 24 bytes.

We will return to the standard file types.

Attributes : important attributes for us are FFS_ATTRIB_FIXED , indicating that the file is not FFS_ATTRIB_FIXED inside FV and set FFS_ATTRIB_DATA_ALIGNMENT, indicating that the data (not the header) of the file is aligned along any boundary.

Size : The size of the file, along with the header, is stored as 24-bit UINT.

State : file state. This field is used after FV is loaded into memory and during file operations inside FV. The state of all valid files within the BIOS image is 0xF8.



We now return to the file types. I recommend that those who have not read the one-and-a-half of this article go and read , otherwise you risk not understanding anything.

As we already know, 13 standard file types are defined, here they are:
TitleType ofDescription
RAW0x01The structure of such a file is completely determined by its user, and nothing is known about it in advance.
Freeform0x02Such a file has a sectional structure, but nothing is known in advance about the contents of the sections.
SECURITY_CORE0x03Security kernel executing code in SEC phase
PEI_CORE0x04PEI core, it’s the PEI Foundation
DXE_CORE0x05DXE core, it’s also the DXE Foundation
PEIM0x06PEI module
DRIVER0x07DXE driver
COMBINED_PEIM_DRIVER0x08PEI / DXE Hybrid Module
APPLICATION0x09Application. It differs from the DXE driver in that it is not the DXE dispatcher that launches it, but the user. Applications are UEFI Setup, UEFI Shell, BIOS Update, etc.
SMM0x0ASMM module
FIRMWARE_VOLUME_IMAGE0x0BFV image. This is a special file that allows you to put one FV into another.
COMBINED_SMM_DXE0x0CSMM / DXE Hybrid Module
SMM_CORE0x0DSMM core, it is SMM Init


Check the above on our file:



The first 0x48 bytes of the FV header no longer interest us, but what is right behind them. It can be seen that there is a file with a GUID CEF5B9A3-476D-497F-9FDC-E98143E0422CN, a checksum of the header 0x36, a checksum of the data 0xAA, which indicates the removed attribute FFS_ATTRIB_CHECKSUM , type RAW (0x01), without attributes, 0x1FFa size, 0xFFa, without attributes, of size 0xFF, FFS_ATTRIB_CHECKSUM , FFS_ATTRIB_CHECKSUM , type RAW (0x01), without attributes, size 0x1KBFF, CHFKSKSUM; . It looks like the truth.



Now about the sections. All FFS files, except RAW, must be divided into sections, aligned at the border of 4 bytes from the beginning of the file data area. Each standard file type has its own requirements for the number and type of sections, but the list of requirements in this article will not be - it is too long and too boring. But the section headings, their purpose and types, we now consider.



Section Header


The minimum section header is:
 typedef struct { UINT8 Size[3]; UINT8 Type; } EFI_COMMON_SECTION_HEADER; 


Size : the size of the section in the same format as in the file header.

Type : section type.

Sections are divided into two subclasses - encapsulation and leaf. The first may contain sections of other types, and the second directly contain data. Sections of some types have extended headers, but the beginning always coincides with the general one.

For encapsulation sections, 3 types of content are defined:

0x01 - EFI_SECTION_COMPRESSION , indicates that the section is compressed by any algorithm.

The full EFI_COMPRESSION_SECTION header (the words in the title are not mixed up, just like that) looks like this:
 typedef struct { UINT8 Size[3]; UINT8 Type; UINT32 UncompressedSize; UINT8 CompressionType; } EFI_COMPRESSION_SECTION; 


UncompressedSize : the size of the unpacked data.

CompressionType : the compression algorithm used.

At the moment, there are 2 compression algorithms - Tiano (0x01) and LZMA (0x02). If the section was unpacked without changing the structure, then the compression type is set to 0x00, and the section size coincides with the size of the packed data. The Tiano algorithm is a combination of LZ77 and Huffman code by Intel, compression and unpacking code can be taken from the TianoCore project files under BSDL. The LZMA algorithm is too well known to tell about it again, the compression and decompression code is in the LZMA SDK.



Let's continue about the types of encapsulation sections:

0x02 - EFI_SECTION_GUID_DEFINED , indicates that the contents of the section should be viewed by the user of the file in accordance with the GUID recorded in it. It is in this section that various OEM data can be stored, as well as electronic signature of the section or the entire file.

0x03 - EFI_SECTION_DISPOSABLE , indicates a section in which data is not important for the operation of the file and can be deleted when reassembling to save space. In real files, the BIOS is not found.

For leaf sections 12 content types are defined:

TitleType ofDescription
PE320x1064-bit executable code in PE32 + format with all its headers. The main format of executable code in UEFI.
Pic0x1164-bit executable code that does not depend on position. Used only by some PEI modules, the format is the same as PE32 +, only relocation information is truncated.
TE0x1264-bit executable code used by modules and core PEI. It differs from PE32 + in the header, which is reduced to save space in the processor cache. Read the description in the half part of this article.
DXE_DEPEX0x13A section describing the dependencies of the DXE driver in which it is located. The format of this section is described in Volume 2.
VERSION0x14Contains file version and optionally Unicode string with full version. It is quite rare.
USER_INTERFACE0x15Contains a unicode-term file name. It is very convenient to search for files by the contents of this section. It is used frequently, but there are also BIOSes without a single file with such a section.
COMPATIBILITY160x1616-bit executable code for compatibility with older systems.
FIRMWARE_VOLUME_IMAGE0x17FV image section. Inside this FV there may be another file with such a section, and so on, until the space in the chip ends.
FREEFORM_SUBTYPE_GUID0x18The section contains data whose interpretation depends on the recorded GUID of its beginning. Used rarely.
RAW0x19Raw data section. What to do with them is determined by the one who opened this file.
PEI_DEPEX0x1BSection describing the dependencies of the PEI module in which it is located. The format of this section is described in Volume 1.
SMM_DEPEX0x1CThe section describing the dependencies of the SMM driver in which it is located matches the format of DXE_DEPEX.


Let's check the above with our example. The file of the RAW type considered last time does not contain any sections, so we take another file, namely, the PEI module named PchUsb, located at offset 0x7AD210:



It can be seen that this is really PEIM (file type - 0x06), which contains two sections. The first section of size 0x3A and type PEI_DEPEX (0x1B) contains information about its dependencies. The second section (aligned on the border of 4 bytes) has a size of 0x31A and a type COMPRESSED_SECTION (0x01), and the data in it are packed with the LZMA algorithm (0x02) and after unpacking they have a size of 0x558. If you can unpack LZMA in your mind, you have already guessed what is inside this section, but if not, I will tell you. There are 2 more sections: PE32 with the executable code of the module and USER_INTERFACE with the file name. You ask, how do I know this, if I can not unpack LZMA in my mind? There's an app for that!



PhoenixTool and Conclusion



This application is called PhoenixTool and its main task is to add SLIC to the UEFI BIOS files, but we are not law-abiding people and we will not add SLIC to our BIOS. Of all the functions of the program, we will be interested in the Structure button, available after specifying the BIOS, with which we will work. The structure opens in a separate window and presents the UEFI BIOS image as a tree. For each component on the right displays information about it and the available actions. The above file in this window looks like this:



The program allows limited editing of the file structure, but it is best not to change it, to avoid.



How exactly it is better to edit the executable modules of UEFI and what it can give in the next article.

Thanks for attention.



Literature



UEFI Platform Initialization Specification 1.2.1 Errata A, Documents by UEFI Forum

Source: https://habr.com/ru/post/185774/



All Articles