Over the past two years, attacks on multiple targets in the semiconductor industry have consistently led to leaks of firmware source code. A compromised developer device could potentially give an attacker access to the source code repository, adding a major gap in the security of the software supply chain.
A few weeks ago, news emerged about a firmware code leak from Lenovo that includes Intel Alder Lake reference code from the most recent devices. There was a lot of unfounded speculation on the internet about the impact of the leak, including discussions on the Intel Boot Guard private key leak and whether it makes the security technology no longer effective.
In this blog, the Binarly REsearch team will provide a deep-dive to explain how Intel Boot Guard works, what exactly was leaked, and to provide an assessment of the leak’s real impact.
Our analysis of all publicly available firmware images from Lenovo devices resulted in the following key discoveries:
Let’s dive into Intel Boot Guard internals to better understand this technology and the impact of these kinds of firmware supply chain compromises. The Boot Guard is unfortunately not documented by Intel but some of the information was recovered by Binarly REsearchers in the past -- see this Black Hat 2017 presentation.
First, Intel Boot Guard is a hardware-based technology intended to protect PCs against executing non-genuine UEFI Firmware, which could happen in case a possible attacker has bypassed protection against modification of BIOS.
If Intel Boot Guard is enabled on the platform, upon powering on the platform and prior to execution of BIOS, the BootStrap Processor (BSP) wakes up to locate Firmware Interface Table (FIT) using a pointer stored at a fixed address 0xFFFFFFC0.
This table contains pointers to firmware objects related to startup and security procedures, including Boot Guard specific files:
The UEFITool automatically parses this data and display it in human readable format:
Intel Boot Guard (BG) Authentication Code Module (ACM) is a core of the technology that implements firmware validation. Like other types of Intel ACMs, this one is developed and signed by Intel via their RSA private key. The hash of the RSA public key is hardcoded into the other object pointed by FIT - Microcode Update (MCU). This is the reason Intel Boot Guard fails if no MCUs are present in the firmware.
In turn, the MCU capsule’s RSA public key hash is programmed into CPU Field Programmable Fuses (FPFs) and there is no way to replace it from hardware.
The scheme of ACM validation looks like this:
As a result, the trusted ACM is loaded into a secure memory inside the CPU called Authenticated Code RAM, which is basically L3 Cache. The ACM key modulus and signature sizes are 2048 bit, however starting from Boot Guard 2.0 version they moved to 3072 bits wide keys.
We can see its structure parsed by UEFITool here:
The Authentication Code Module (ACM) header information look like the following code structure:
struct {
UINT16 ModuleType; // 2
UINT16 ModuleSubType; // 1
UINT32 HeaderLen; // 0xE0 (in DWORDs)
UINT32 HeaderVersion; // 03.00 (BCD format)
UINT16 ChipsetId; // 0xB00C
UINT16 Flags; // 0x8000 (debug flag set)
UINT32 ModuleVendor; // 0x8086 (who else could it be?)
UINT32 Date; // 2021-11-11 (BCD format)
UINT32 Size; // 0x9400 (total size of ACM in DWORDs)
UINT16 AcmSvn; // 0
UINT16 SeAcmSvn; // 0
UINT32 CodeControl; // 0
UINT32 ErrorEntryPoint; // 0
UINT32 GdtLimit; // 0x20
UINT32 GdtBasePtr; // 0x3ED0
UINT32 SegSel; // 8
UINT32 EntryPoint; // 0xDAF4
UINT8 Rsvd2[64];
UINT32 KeySize; // 0x60 (in DWORDs)
UINT32 ScratchSize; // 0xD0 (in DWORDs)
UINT8 Rsa3072PubKey[384]; // 0x61 0x23 0xAD 0x70 ... (LSB format)
UINT8 Rsa3072Sig[384]; // 0xBD 0xFC 0xB3 0x67 ... (LSB format)
UINT8 Scratch[832]; // 0x00 0x00 0x00 0x00 ... (LSB format)
} ACM_HEADER;
Let’s look into the firmware dump how this data will be represented in actual ACM binary:
When executed, the BG ACM locates (via FIT) and verifies the BG manifests: KEYM and BPM.The hash of KEYM RSA public key is programmed into Intel ME FPFs during the manufacturing line without a capability of being replaced. ACM acquires this hash from Intel ME. The main purpose of KEYM is to store the hash of an RSA public key of the BPM which in turn contains the information on the Boot Policy, Initial Boot Block (IBB) description and its hash. IBB is also verified by the ACM.
As Initial Boot Blocks (IBB), vendors usually specify SEC and Pre-EFI (PEI) volumes, while the rest of the UEFI firmware (DXE and SMM) should be protected by Vendor-specific structure inside IBB (trusted boundaries of which are guaranteed by Intel Boot Guard trusted boot chain).
Let’s take a look on the example of extracted KEYM data structure from the firmware image:
KEYM structure in details
struct {
UINT8 StructureId[8]; // "__KEYM__"
UINT8 StructVersion; // 0x21
UINT8 reserved[3];
UINT16 KeySignatureOffset; // 0x44, offset to KEY_AND_SIGNATURE_STRUCT
UINT8 Reserved2[3];
UINT8 KeyManifestRevision; // 1
UINT8 KmSvn; // 1
UINT8 KeyManifestId; // 1
UINT16 KmPubKeyHashAlg; // 0x0C
UINT16 KeyCount; // 1
struct {
UINT64 Usage; // 0x01
struct {
UINT16 HashAlg; // 0x0B, SHA256
UINT16 Size; // 0x20
UINT8 HashBuffer[]; // 0x68 0x83 0x7D 0xD0 ... (LSB format)
} SHAX_HASH_STRUCTURE;
} SHAX_KMHASH_STRUCT;
struct {
UINT8 Version; // 0x10
UINT16 KeyAlg; // 1, RSA
struct {
UINT8 Version; // 0x10
UINT16 KeySizeBits; // 0x0C00, 3072 bits
UINT32 Exponent; // 0x010001
UINT8 Modulus[]; // 0xD9 0x03 0xFC 0x44 ... (LSB format)
} RSA_PUBLIC_KEY_STRUCT;
UINT16 SigScheme; // 0x16
struct {
UINT8 Version; // 0x10
UINT16 SigSizeBits; // 0x0C00, 3072 bits
UINT16 HashAlg; // 0x0C, SHA384
UINT8 Signature[]; // 0x0D 0xFF 0x31 0xD9 ... (LSB format)
} RSASSA_SIGNATURE_STRUCT;
} KEY_AND_SIGNATURE_STRUCT;
} KEY_MANIFEST_STRUCTURE;
Let’s take a look on the example of extracted Boot Policy Manifest data:
BPM structure in details
struct {
struct {
UINT8 StructureId[8]; // "__ACBP__"
UINT8 StructVersion; // 0x21
UINT8 HdrStructVersion; // 0x20
UINT16 HdrSize; // 0x14
UINT16 KeySignatureOffset; // 0x01A8, offset to KEY_AND_SIGNATURE_STRUCT
UINT8 BpmRevision; // 1
UINT8 BpmRevocation; // 1
UINT8 AcmRevocation; // 2
UINT8 Reserved;
UINT16 NemPages; // 3
} BOOT_POLICY_MANIFEST_HEADER;
struct {
UINT8 StructureId[8]; // "__IBBS__"
UINT8 StructVersion; // 0x20
UINT8 Reserved0;
UINT16 ElementSize; // 0x012C
UINT8 Reserved1;
UINT8 SetType; // 0
UINT8 Reserved;
UINT8 PbetValue; // 0x0F
UINT32 Flags; // 0x00000013
UINT64 IbbMchBar; // 0x00000000FED10000
UINT64 VtdBar; // 0x00000000FED91000
UINT32 DmaProtBase0; // 0x00100000
UINT32 DmaProtLimit0; // 0x00F00000
UINT64 DmaProtBase1; // 0x0000000000000000
UINT64 DmaProtLimit1; // 0x0000000001000000
// PostIbbHash
struct {
UINT16 HashAlg; // 0x10
UINT16 Size; // 0
// UINT8 HashBuffer[];
} SHAX_HASH_STRUCTURE;
UINT32 IbbEntryPoint; // 0xFFFFFFF0
// IbbHash
struct {
UINT16 Size; // 0x98
UINT16 Count; // 4
struct {
UINT16 HashAlg; // 0x0B, SHA256
UINT16 Size; // 0x20
UINT8 HashBuffer[]; // 0x30 0x3F 0x28 0xAF ... (LSB format)
} SHA256_HASH_STRUCTURE;
struct {
UINT16 HashAlg; // 0x04, SHA1
UINT16 Size; // 0x14
UINT8 HashBuffer[]; // 0x36 0xEA5 0x92 0x8F ... (LSB format)
} SHA1_HASH_STRUCTURE;
struct {
UINT16 HashAlg; // 0x0C, SHA384
UINT16 Size; // 0x30
UINT8 HashBuffer[]; // 0x69 0xFA 0xBC 0x1F ... (LSB format)
} SHA384_HASH_STRUCTURE;
struct {
UINT16 HashAlg; // 0x12, SM3
UINT16 Size; // 0x20
UINT8 HashBuffer[]; // 0x6A 0x46 0xCC 0xE8 ... (LSB format)
} SM3_HASH_STRUCTURE;
} MAX_HASH_LIST;
// ObbHash
struct {
UINT16 HashAlg; // 0x10
UINT16 Size; // 0
// UINT8 HashBuffer[];
} SHAX_HASH_STRUCTURE;
UINT8 Reserved2[3];
UINT8 SegmentCount; // 6
// Ibb
struct {
UINT16 Reserved;
UINT16 Flags; // 0
UINT32 Base; // 0xFFD3D000
UINT32 Size; // 0x00086000
} IBB_SEGMENT;
struct {
UINT16 Reserved;
UINT16 Flags; // 0
UINT32 Base; // 0xFFE72000
UINT32 Size; // 0x00150000
} IBB_SEGMENT;
struct {
UINT16 Reserved;
UINT16 Flags; // 0
UINT32 Base; // 0xFFFC2000
UINT32 Size; // 0x00010000
} IBB_SEGMENT;
struct {
UINT16 Reserved;
UINT16 Flags; // 0
UINT32 Base; // 0xFFFD2000
UINT32 Size; // 0x00001000
} IBB_SEGMENT;
struct {
UINT16 Reserved;
UINT16 Flags; // 0
UINT32 Base; // 0xFFFD3000
UINT32 Size; // 0x000271C0
} IBB_SEGMENT;
struct {
UINT16 Reserved;
UINT16 Flags; // 0
UINT32 Base; // 0xFFFFACC0
UINT32 Size; // 0x00005340
} IBB_SEGMENT;
} IBB_ELEMENT;
struct {
UINT8 StructureId[8]; // "__TXTS__"
UINT8 StructVersion; // 0x20
UINT8 Reserved0;
UINT16 ElementSize; // 0x28
UINT8 Reserved1;
UINT8 SetType; // 0
UINT16 Reserved;
UINT32 Flags; // 0x00000000
UINT16 PwrDownInterval; // 0x3E
UINT8 PttCmosOffset0; // 0xFE
UINT8 PttCmosOffset1; // 0xFF
UINT16 AcpiBaseOffset; // 0x0400
UINT16 Reserved2;
UINT32 PrwmBaseOffset; // 0xFE000000
struct {
UINT16 Size; // 0x04
UINT16 Count; // 0
// SHAX_HASH_STRUCTURE Digest[];
} HASH_LIST;
UINT8 Reserved3[3];
UINT8 SegmentCount; // 0
// IBB_SEGMENT* TxtSegment;
} TXT_ELEMENT;
struct {
UINT8 StructureId[8]; // "__PCDS__"
UINT8 StructVersion; // 0x20
UINT8 Reserved0;
UINT16 ElementSize; // 0x34
UINT16 Reserved1;
UINT16 SizeOfData; // 0x24
UINT8 Data[]; // Data[SizeofData] // PDRS
} PLATFORM_CONFIG_DATA_ELEMENT;
struct {
UINT8 StructureId[8]; // "__PMSG__"
UINT8 StructVersion; // 0x20
UINT8 Reserved[3];
struct {
UINT8 Version; // 0x10
UINT16 KeyAlg; // 1, RSA
struct {
UINT8 Version; // 0x10
UINT16 KeySizeBits; // 0x0B00, 2048 bits
UINT32 Exponent; // 0x010001
UINT8 Modulus[]; // 0xDD 0xD5 0xD1 0xEF ... (LSB format)
} RSA_PUBLIC_KEY_STRUCT;
UINT16 SigScheme; // 0x16
struct {
UINT8 Version; // 0x10
UINT16 SigSizeBits; // 0x0B00, 2048 bits
UINT16 HashAlg; // 0x0B, SHA256
UINT8 Signature[]; // 0x90 0xC3 0xC7 0x0F ... (LSB format)
} RSASSA_SIGNATURE_STRUCT;
} KEY_AND_SIGNATURE_STRUCT;
} BOOT_POLICY_MANIFEST_SIGNATURE_ELEMENT;
} BOOT_POLICY_MANIFEST_STRUCTURE;
Intel’s intention to make KEYM as a proxy between hardware and BPM lets vendors be flexible in configuring their build process for different product lines.
For example, the KEYM’s key could be used for the series of products, when different BPM’s keys could be used for different models in a product series. This also allows SOCs to react to key leakage incidents in case a BPM key is compromised. The following part of the trusted boot chain can not be trusted then, but this situation could be fixed only via issuing a firmware update with the nex BPM key (with its hash recalculated in KEYM).
Unfortunately both KEYM and BPM private keys were leaked for four different product lines (8 keys in total).
Since the Key Manifest (KM) hash in FPFs cannot be rewritten, for those 4 products the KEYM (hence Intel Boot Guard) is compromised forever, meaning in turn that Boot Guard can be easily bypassed on those devices: these systems should be considered as Boot Guard disabled systems.
Binarly’s FwHunt can be used to determine the impact industry wide, thanks to our first community contributed rule. Since the KEYM’s and BPM’s RSA public keys are stored in the firmware image strictly without any compression - the rule is simple enough just to search for the public key’s byte pattern (in Little Endian order, which Intel prefers to use inside their blobs).
Overall, our analysis revealed that no impacted firmwares or devices were discovered in the wild. Downloading firmware for impacted devices from the website of official support also shows no evidence that those keys ever existed in Lenovo devices.
If we take a closer look at the ACM’s header in the above mentioned example, we can see that the flags field is equal to 0x8000, meaning that Debug flag is set for this module.
Though not all Authentication Code Modules (ACMs) have this flag set, this makes us feel that the leaked Boot Guard keys are intended for debug building lines and most likely we will never see such devices in the wild. Nevertheless we’ll keep monitoring the firmware assets and let the industry know if this incident affects any production device..
AS USUAL:
Binarly team provides FwHunt rules to detect vulnerable devices at scale and help the industry recover from firmware security repeatable failures.
FwHunt Community Scanner: https://github.com/binarly-io/fwhunt-scan
FwHunt detection rules: https://github.com/binarly-io/FwHunt/tree/main/rules
The Binarly team is constantly working to protect the firmware supply chain and reduce the attack surfaces of our customers industry-wide by delivering innovative technologies to the market. Based on our experience we understand that fixing vulnerabilities for a single vendor is not enough. As a result of the complexity of the firmware supply chain, there are gaps that are difficult to close on the manufacturing end since it involves issues beyond the control of the device vendors.
Are you interested in learning more about Binarly Platform or other solutions? Don't hesitate to contact us at [email protected].