REsearch

REsearch

REsearch

Leaked Intel Boot Guard keys: What happened? How does it affect the software supply chain?

Binarly Team

The Binarly security research team conducts an analysis of the recent Intel and Lenovo source code leaks to model the potential impact.

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.

Figure 1

This table contains pointers to firmware objects related to startup and security procedures, including Boot Guard specific files:

Fig 2

The UEFITool automatically parses this data and display it in human readable format:

Fig 3

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:

Fig 4

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:

Fig 5

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:

Fig 6

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.

Fig 7

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:

Fig 8

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;

Fig 9

Let’s take a look on the example of extracted Boot Policy Manifest data:

Fig 10

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;

Fig 11

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).

Fig 12

Unfortunately both KEYM and BPM private keys were leaked for four different product lines (8 keys in total).

Fig 13

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.

Fig 14

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).

Fig 15

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.

Fig 16

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 fwhunt@binarly.io.

Tag list
Back to overview