Intel Boot Of Trust 2020

danny odler
11 min readNov 9, 2020

--

TL; DR

Recently I tried to open some relatively new UEFI image with UEFITool and realized that the ‘security tab’ is showing some misaligned info about the Intel Boot Guard components. Shortly it was clear that Boot Guard for 10th generation CPUs had some major redesign and its structures have been changed. After reversing the main flow I reconstructed the new structures and updated UEFITool so it is capable to parse this new Boot Guard design. There are two excellent researches done during 2017 by Alex Matrosov(1) and Alex Ermolov(2) so it’s definitely worth looking first at their work.

Intel Boot Guard is a very powerful technology which establishes strong HW root of trust before a single byte is executed from UEFI and so if applied correctly by the motherboard vendors will eliminate any UEFI attack such as the recent MosaicRegressor and Lojax.

Intel Boot Overview

Intel root of trust starts in the Intel ME phase. This phase is executed very early long before you turned on your PC which means that Intel ME code runs on a dedicated processor with its own memory. Intel ME is a whole topic to discuss and is out of scope for this article but here is what you need to know:

  • ME code has different roles such as remote PC management and updates but its main role is to establish very strong root of trust for the system.
  • Intel ME code is stored on the main SPI flash along with the UEFI image. Most of the code is encrypted and very hard to be reverse statically.
  • ME code is loaded and executed by a dedicated CPU inside the main chipset. Nowadays it is a x86 core which is equivalent to Pentium 486 CPU. Intel ME has it own RAM memory(~2MB) and if needed more it can use the main RAM.
  • ME boot ROM serves as the HW root of trust so that it holds key hash of a ME signed manifest which in turn holds keys to different ME partitions. Each partition is validated so that ME code is fully trusted.
  • ME has access to different kind of HW fuses which part of them reserved for ME keys cryptography and part of them are available for the motherboard manufacturers — these called FPF, field programmable fuses. The FPFs are one time writable memory that cannot be re-written and have important role in the Intel Boot Guard implementation.
  • In a well configured UEFI image the main CPU cannot access the ME region on the SPI flash; no write or read is allowed.

Once the ME finished its execution it holds the main CPU in a reset state until the user powers on his PC. When power switched on, the CPU microcode which is the CPU firmware, starts some CPU low level silicon initialization and locates the FIT table pointer from a pre-defined mapped location in the UEFI SPI flash — 0xFFFFFFC0. The FIT table is generated in the UEFI build process and includes various pointers to critical boot structures such as microcode updates, BootGuard manifests, Bootguard ACM or TPM/TXT policy records. We will look at FIT table in the next section. Once the FIT table parsed by the Microcode it will load the Bootgurad ACM module to a special cache region that serves as Authenticated Code RAM which will also act in No Eviction as it was RAM and also disabling DMA access to it. In this article I only refer to Verified Boot logic meaning TPM is almost not involved.

Boot Guard FPFs — this is the HW root of trust that set by the mother board vendor at the end of manufacturing. The vendor needs to permanently write to FPFs the BootGuard profile and the hash of the KeyManifest public key. Boot Guard Profile tells Boot guard ACM whether Measured or Verified boot logics need to be executed and what to do if verification failure occurs — a delayed or immediate shutdown is taken. Vendor also writes to Boot Guard FPFs the KeyManifest ID and minimum security version number (SVN) to avoid revoked manifests. FPFs are one time storage that once written cannot be changed.

Once Boot Guard ACM loaded it will start executing 4 main steps:

  1. Getting Platform info that was set by Intel ME. This stage will read some of the FPFs (for verified boot) and some TPM info for (measured boot).
  2. Parse Key Manifest — as we will see later the manifest has public key and signature. The Sha256 hash of the public key is verified against the value burned in bootguard profile FPFs. If this check passed then the signature is verified to check manifest content integrity.
  3. Parse IBB Manifest — this manifest also has a public key and signature. The Sha256 hash of the public key is stored in the Key Manifest, and once we already validated Key Manifest we can trust the data in it. If the IBBM public key hash stored in the Key Manifest matches the actual hash of the IBB Manifest public key then the signature is checked to verify the manifest content integrity.
  4. IBB Manifest has usually 3 to 6 ‘descriptor regions’ which cover the SEC and PEI modules of the UEFI image, this is the IBB — Initial Boot Block. The Sha256 hash of these regions is stored in the manifest and is verified against the actual hash of these regions calculated on the fly.

The separation for Key and IBB manifest is mainly for the reason that IBB manifests used to different lines of production and if specific IBBM private key stolen then it affects only this specific line. However, if Key Manifest private key is compromised then it will affect all vendor production.

https://medium.com/@matrosov/bypass-intel-boot-guard-cc05edfca3a9

If all 4 steps pass successfully then only now the famous reset vector at 0XFFFFFFF0 will be executed which means that till now no single byte from the UEFI code was executed yet. At this point Intel BootGuard ACM finished its role and now we are sure that the SEC and PEI modules are verified. The OEM vendor must have its own module which is located among the PEI modules, such module will calculate hash on the DXE modules and verify it to expected hash.

FIT Table

At this post i use Gigabyte Z490 UEFI image for 10th generation CPUs. The FIT (firmware interface) table is part of the UEFI image and a pointer to its location can be found at mapped address 0xFFFFFFC0 (offset 0x1FFFFC0 in my UEFI image). Its main purpose is to indicate to the Microcode where the location of the BootGuard ACM module is. Moreover, it also tells the boot guard ACM where is the location of the Key and IBB manifests. The table itself has several entries as can be seen in picture (1). Each entry is 0x10 bytes long and has 3 main fields:

  1. Type — indicates whether it is a Microcode/ACM/Bios StartUp Module/Txt Policy/Key Manifest/IBB Manifest (some other entries may exist).
  2. Size — size of the actual module in the UEFI image. Sizes are usually provided for Key and IBB manifests.

3. Location — address of this module location in the UEFI image.

For each entry there is also a checksum field but ironically it is never checked.

Intel Bootguard ACM

Boot guard ACM was used to be 32KB size but during 2019 along with the introduction of PCH(chipset) 400 series the size of ACM has grown up to 100KB and a lot of redesign was made in integrating Boot guard code with Intel TXT and TPM. In the new design a lot of measurements are taken during the Bootguard ACM phase so later it can be used in the Static/Dynamic root of trust when the Kernel/Hypervisor starts, I will not cover this stuff in the article. BG ACM is signed with Intel’s private key and the hash of the public key is stored in the Microcode, meaning that Microcode will verify BG ACM to be authentic.

The main BG ACM flow starts with some kind of Init state which takes the info generated by the Intel ME phase. You can see at offset 0x107C6 that ACM reads MSR registers 130–136 (undocumented) and stores the values at some global memory. These values are the HW root of trust which were set by the vendor in the FPFs. This is actually the BG profile data which is used later when validating the KeyM signature.

The flow is calling to offset 0x1056C which is a function that parses and validates the FIT table. The function copies FIT table from flash to the stack and validates several metadata such as NumOfEntries is not more than max value and so on. Then it makes Sha256 hash of the table and stores it in global var, this value will be check for integrity in later steps, probably to avoid some double fetch issues. Then the flow checks if entry for KeyM or IBBM manifests exist in FIT and if yes then it validates each entry (0x10 bytes). You can see that the max and min size values for KeyM are 0x7DA and 0xC9. This is one of the new changes — in previous versions KeyM size is constant value of 0x241. For IBBM the max and min size values for are 0xA3D and 0x26D. The reason for this is that new hash lengths introduced to this version and the RSA public key can also be 3072 bit instead of 2048 in previous versions. After this TXT policy entry is searched and parsed and some TXT private space is initialized. Finally KeyM and IBBM sizes and locations (from the FIT entry) are stored for later use as it will be needed when copying these manifests for parsing.

At offset 0xF954 there is ParseManifests() function which calls separately to ParseKeyManifest() and ParseIBBManifest().

Key Manifest Parsing

The function starts at 0xFB2D and MemCpy the KeyM to global memory 0x13C0. Then it try to find the hash of IBBM public key, it goes to offset +0x16 in KeyM and verify that no more than 4 hashes exist, this is also a new change from previous version — probably the new design allows the vendor to load different hash keys, that is actually why the total size of KeyM has max and min sizes. The flow takes from offset +0x20 the Hash_Struct for the IBBM public key. You can see at offset 0xFBDA that a check is done to the Hash_Struct first field can be 0xB or 0XC and the second field 0x20 or 0x30, these fields correlate later with Hash Type and Hash Size, meaning 0xB type is for Sha256 and 0xC type is for Sha384. Then an BG_RSAEntry struct is located via GetValueFromManifest(Manifest,Value) function at 0xE0AC. BG_RSAEntry is validated for some RSA values and you can also see that the signature of the KeyM can be hashed with Sha256 or Sha384.

After these validations the main cryptographic function ComparePubKeyHashAndVerifySignature() is called at 0xF9D3. This function takes 5 arguments — ManifestStart, Size to sign, BG_RSAEntry, ExpectedVal, ExponentHashed(1=KeyM 2=IBBM). The last flag, ExponentHahed , indicates whether the hash func is done on only the public key or also an exponent is added to the hashed blob. In KeyM parsing it takes the public key+exponent from BG_RSAEntry and makes Sha256 (or Sha384) on it, then the hash is compared to the HW Fuses Data(expected val) hash that the vendor burned permanently. As previously mentioned HW Fuses hash and BG policy is reflected in MSRs 130–136. If this check succeeded so the public key is authentic and we can use it to decrypt the RSA signature. The signed size is actually all the KeyM data until the BG_RSAEntry location which is at offset +0x44. Sha256 is made on the first 0x44 bytes data and this hash is compared to the decrypted signature. If this check passes we can now use the verifies KeyManifest. Now the IBBM pub key from KeyM is copied to global memory so it can be used as the expected verified value when parsing the IBBM.

Key Manifest Structure 10th Generation CPU

IBB Manifest Parsing

This manifest consist of up to 5 elements — IBBS, TXTS, PCDS, PDRS, PMSG, the first and the last are mandatory. ParseIBBM() function starts at 0xFE2D and copies the manifest to global memory. Then it searches the manifest for each element to check its presence and validity. After this it takes BG_RSAEntry and validates it, *(IBBM+0xC) = 0x184 = BG_RSAEntry offset from IBBM start. This is the same validation as done in the KeyM parsing — checking constant values such as RSA 2048/3072 key size, exponent (0x10001), Sha256/Sha384 for the signature decryption. Then again ComparePubKeyHashAndVerifySignature() is called but this time the last argument is 2, meaning the exponent of IBBM pub key is not hashed. The signed area is from the manifest start till the BG_RSAEntry struct.

Once the IBBM signature is verified BG allowed to use it as a root of trust, as you already know IBBM relies on KeyM to be root of trust and KeyM relies on HW read only fuses set by vendor to be root of trust.

At this stage IBBM manifest is used to verify the SEC+PEI sections in the UEFI flash. At offset 0x146B8 there is a function CheckIBBHashToExpected() which goes to the _IBBS_ element and takes the IBB descriptors which describe the regions to be verified. Sha256 hash is applied on the concatenated IBB regions and compared to expected hash hardcoded in the IBBM manifest. This is the responsibility of the vendor to set in IBBM the IBB descriptors ranges and the expected hash.

Part of the new redesign is that several hashes might be included in the _IBBS_ element, each hash is of different type probably to allow vendors flexibility to choose their hash type. Obviously only one hash is used from the list which includes Sha256, Sha1, Sha384 and additional unknown hash type. Each IBB descriptor is used to describe a start address and length in the SEC/PEI modules, the reason for several descriptors is that the SEC/PEI regions also include data that is not verified by BG such as the ACM and manifests themselves as they already verified before. If the calculated hash of SEC+PEI modules matches the expected hash then BG finished the Verified Boot stage and allows the CPU to jump the reset vector 0xFFFFFFF0.

IBB Manifest Structure 10th Generation CPU

References:

  1. https://medium.com/@matrosov/bypass-intel-boot-guard-cc05edfca3a9
  2. https://2016.zeronights.ru/wp-content/uploads/2017/03/Intel-BootGuard.pdf
  3. https://edk2-docs.gitbook.io/understanding-the-uefi-secure-boot-chain/secure_boot_chain_in_uefi/intel_boot_guard

--

--