Skip to content

Intel Microcode Extra Undocumented Header

Plato Mavropoulos edited this page Apr 12, 2017 · 7 revisions

Introduction

Starting from 2006, Intel added a second Microcode header which to this day remains undocumented. This page is meant for those who are looking to understand how that header is structured. At MC Extractor (MCE) I call this header "Extra" and should not be confused with the Intel documented "Extended" optional header. Note that the "Main" and "Extended" headers are documented by Intel and thus not relevant to this research.

The foundations for the latter were built by Ben Hawkes (Ben) back in 2013 at his Notes on Intel Microcode Updates paper. Since various comparisons and references are made to Ben's findings, it is highly recommended to first read his paper.

Based on MCE's database, we are now able to determine the purpose of more fields and also potentially improve the ones suggested by Ben 4 years ago. The following observations derive from a collection of 878 microcodes which incorporate the Extra header from 03/2006 to 02/2017.

Extra Header Analysis

The Extra header starts at offset 0x30, exactly after the official documented Main header.

  • The 1st dword (0x30-0x34, Unknown1) is always zero so it may be reserved. It is more probable though for it to be a Header Type field as it is at Intel's Management Engine (ME) firmware which has a similar 0x284 sized header/manifest which also consists of RSA fields.

  • The 2nd dword (0x34-0x38, ExtraHeaderSize) is what Ben called "magic number", a way for the Microcode loader to determine that the correct structure was received. However, it seems to be the Extra header size counted in dwords, meaning 0xA1 * 4 = 0x284, which ends at the last byte of the RSA Signature. The same applies to ME firmware.

  • The 3rd dword (0x38-0x3C, Unknown2-3) is always equal to 00020001 and I believe that it consists of two uint16 fields which may represent some sort of version tags (0002 = 2 and 0001 = 1).

  • The 4th dword (0x3C-0x40, UpdateRevision) is the Update Revision.

  • From the 5th dword (0x40-0x44, VCN), only the most significant byte is used with the other 3 being reserved. At Ben's 2013 paper, Observation #5, he calls that byte a flags field and notices that changing its value resulted in lower update cycle count before failure. However, by comparing multiple Update revisions of the same CPUID it becomes clear that this field only gets incremented when processor issues are fixed. For example, if we take all 31 microcodes with CPUID 506E3 and Platform 36, we can see that Update Revisions 10-1E are 0, 20-24 are 1, 2E-3A are 2, 4A-76 are 3, 7C is 4, 82-88 are 5 and 8A-B2 are 6. The field in question gets incremented only at newer Update revisions, thus when important fixes are applied. Intel's ME firmware has a similar field called Version Control Number (VCN) which gets incremented if there is a security fix, a significant firmware change or a new feature addition. This byte is also used to control firmware upgrade/downgrade to versions which are known to be vulnerable or problematic. In the context of cpu microcodes, the 5th dword (or VCN as I decided to also call it) may be used in a similar fashion to block certain downgrades. Presumably at the OS level only compared to what got hard-loaded from the BIOS. Although the field does not seem like a flag to me, the lower cycle count he observed may be from the fact that the modified update got rejected faster after the VCN check failed. Meaning, the current loaded microcode had VCN = 1 but the mod attempt had VCN = 0, thus it was quickly deemed as an invalid upgrade/downgrade.

  • The 6th dword (0x44-0x48, Unknown4) is a weird one because it can be used for various purposes based on microcode generation. Sometimes it can be zero or equal the Update Size field (explained below) or some other type of dword counter (field * 4) starting from the Extra header and onward (0x30+) etc. At some older Extra headers that field may have been two uint16 instead of today's uint32. Due to that field's volatility, it's hard to pinpoint its exact purpose.

  • The 7th dword (0x48-0x4C, Day-Month-Year) consists of a date in the form of Day (uint8), Month (uint8) and Year (uint16). That date is always different (some days earlier) than the one found at the Main header. Maybe it's the date the microcode was built and the one at the Main header corresponds to the official release to Intel's partners.

  • The 8th dword (0x4C-0x50, UpdateSize) was described by Ben as the amount of dwords the actual Update consists of, starting from the Extra header (0x30+) and ending before the (encrypted) alignment padding. As it was proven at Ben's Observation #3, there is no denying that this field is indeed the actual microcode Update Size.

  • The 9th dword (0x50-0x54, ProcessorSignatureCount) was described by Ben's Observation #1 as a possible Loader version. That seemed like the most logical conclusion at the time. However, 2/878 scanned microcodes hold a value of 2 there instead of 1. Both of these microcode binaries also seem to target 2 CPUIDs, instead of the common 1. That shows that the 9th dword is actually a counter for the supported Processor Signatures which follow it. The ProcessorSignatureCount field probably allows a maximum value of 8 CPUIDs, judging by the reserved CPUID fields at the Extra header for that purpose, as explained below.

  • The next 8 dwords (0x54-0x74, ProcessorSignature0-7) hold the actual CPUIDs which are supported by a given microcode update. Each CPUID is an uint32 field which remains zero when not needed. The number 8 (0-7) for the maximum number of CPUIDs and thus ProcessorSignatureCount, is derived from multiple factors. All microcodes with ProcessorSignatureCount = 1 have the first dword populated with CPUID 0 and the following 7 empty with the next unrelated populated dword found 0x20 bytes (8 * 4 = 0x20) later. The 2 microcodes with ProcessorSignatureCount = 2 populate the first two dwords with CPUID 0-1 and leave the other 6 empty with the next unrelated populated dword found again after 0x20 bytes. Moreover, the Platform field found at the Main and Extra headers can also hold a maximum of 8 CPUs at the used most significant byte, as explained by Intel at their official Main header documentation. It is worth mentioning that the presence of multiple CPUIDs at the Extra header (ProcessorSignatureCount > 1) does not mean that the microcode features Intel's Extended header at the end of the data. However the opposite always apply, meaning that a microcode with an Extended header will also mention the multiple CPUIDs at the Extra header's ProcessorSignatureCount and ProcessorSignature0-7 fields. For example, microcode cpu000206F0_plat05_ver00000003_date15-06-2010 has ProcessorSignatureCount = 2 (CPUID 206F0 & 206F1) but no Extended header at the end of the data. On the other hand, microcode cpu000406A8_plat01_ver0000081F_date12-08-2014 is the only one I've found that incorporates an Extended header and in that case the two supported CPUIDs (406A8 & 406A9) can be also seen at its Extra header CPUID counter and data fields.

  • The 17th dword (0x74-0x78, Unknown5) is also a weird one because it can be used for various purposes based on microcode generation. For this one I have narrowed it down to 4 categories. It can be a) zero or b) equal to UpdateSize or c) equal to Platform or d) represent the amount of dwords the entire microcode consists of, starting from the Extra header (0x30+) but also including the (encrypted) alignment padding (contrary to UpdateSize). It is worth noting that 2/878 microcodes do not fall under any of the aforementioned 4 categories. These are cpu000206C0_plat13_verD1071606_date02-11-2009 and cpu00020655_plat12_verD1070214_date13-04-2010 which I consider anomalies because of their weird (clearly pre-production) Update revision & Platform as well as size (64KB compared to the usual 2-3KB) as seen at the rest of the microcodes from the same CPUIDs. In both these cases the Unknown5 field is clearly the amount of dwords for Update + Padding but it ends way before the end of data so maybe these microcodes are filled with excessive padding for some reason.

  • From the 18th dword (0x78-0x7C, SVN), only the most significant byte is used with the other 3 being reserved. By comparing multiple Update revisions of the same CPUID it becomes clear that this field only gets incremented when processor issues are fixed. For example, if we take all 31 microcodes with CPUID 506E3 and Platform 36, we can see that Update Revisions 10-1E are 0, 20-24 are 1, 2E-7C are 2, 82-88 are 3 and 8A-B2 are 4. The field in question gets incremented only at newer Update revisions, but at a slower pace compared to Version Control Number (VCN), thus only when very important fixes are applied. Intel's ME firmware has a similar field called Security Version Number (SVN) which gets incremented if there is a high or critical security fix. This byte is also used to control firmware upgrade/downgrade to versions which are known to be vulnerable or problematic. In the context of cpu microcodes, the 18th dword (or SVN as I decided to also call it) may be used in a similar fashion to block certain downgrades. Presumably at the OS level only compared to what got hard-loaded from the BIOS.

  • The next 5 dwords (0x7C-0x90, Unknown6-10) are always zero so probably reserved.

  • The next 8 dwords (0x90-0xB0, Unknown11) are always unique and seem to be some sort of 256-bit hash, maybe SHA-2. However, I haven't been able to determine for what data.

  • The next 64 dwords (0xB0-0x1B0, RSAPublicKey) hold the RSA Public Key.

  • The 96th dword (0x1B0-0x1B4, RSAExponent) is the RSA Public Key Exponent which is always 0x11 = 17.

  • The next 64 dwords (0x1B4-0x2B4, RSASignature) hold the RSA Signature.

Extra Header Structure

In the end we have this revised Intel Extra Microcode Header structure:

For example, here is the Extra header of cpu000406A8_plat01_ver0000081F_date12-08-2014:

Conclusion

We have now determined the purpose of a lot more Extra Header fields:

  • Unknown1 (Header Type possibly)
  • HeaderSize (dwords, 0x284)
  • Unknown2-3 (uint16 version tags possibly)
  • Version Control Number (VCN, possibly)
  • Unknown4 (weird multi-purpose)
  • ProcessorSignatureCount
  • ProcessorSignature0-7
  • Unknown5 (weird multi-purpose, 4 types)
  • Security Version Number (SVN, possibly)
  • Unknown6-10 (Reserved)
  • Unknown11 (256-bit hash of sorts)

Clone this wiki locally