Translate

Archives

Examining Intel Microcode in Lenovo Firmware Updates

Recently, I decided to examine the contents of a Lenovo T450 firmware update before installing the firmware update and noticed that it included a number of Intel processor microcode updates. This blog post explores what information you can glean from these microcode updates and confirms the existence of an additional undocumented header in Intel microcode updates which was initially described by Chen and Ahn in their December 2014 paper Security Analysis of x86 Processor Microcode.

Here is the contents of the latest firmware update (as of November 2016) for the Lenovo T450 laptop. It is a self extracting executable named jbuj62ww.exe.

The only two files of actual interest to us are highlighted by the red box. The rest of the files in the update do not contain actual firmware images. Note that I used a Linux utility, tree, running in the Linux subsystem for Windows 10 to provide the listing. That explains the odd path syntax.

Why two binary images? Lenovo Thinkpads have separate firmware for the “BIOS” and the Embedded Controller Program (ECP). A given “BIOS” version will require a specific version of a ECP. In this case, the bigger file, $0AJB000.FL1, is the “BIOS” image and $0AJB000.FL2 is the ECP image.

Lenovo firmware images conform to the UEFI firmware capsule specification and can be examined and modified using a graphical tool such as the excellent UEFItool by Nikolaj Schlej, or any number of command line utilities.

This tool understands the documented Intel microcode header.

You can view the contents of any component using the builtin hex viewer as shown below:

You can also extract and save the contents of any component either with or without it’s header. I used this feature to extract and save a copy of each of the 5 Intel microcode blobs into individual files with a “.ucd” extension.

So what exactly is an Intel microcode update? Many Intel processors have the capability to correct errata by loading an Intel-supplied data block into the processor. This data block is called a microcode update.

The platform firmware has a microcode update loader which is responsible for loading the update on the platform processor(s) during system initialization. There are two steps to this process: the first is to incorporate the microcode data blocks into the platform firmware; the second is to load the microcode data blocks into the platform processor(s). Note that modern versions of operating systems such as Linux, Microsoft Windows, and VMware ESXi also have similar facilities.

A microcode update consists of an Intel-supplied binary that contains one or more multibyte headers and a single encrypted data blob. No executable code resides within the update. Each microcode update is tailored for a specific list of processor signatures as specified in the header. A mismatch of a processor’s signature with the signature contained in the update will result in a failure to load. A processor signature includes the extended family, extended model, type, family, model, and stepping of the processor. A given microcode update may be associated with one of multiple processor signatures. See Section 9.11 of Intel 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A, for more details.

According to Section 9.11, only one header exists in a microcode update followed by encrypted data containing the actual code. This 48 byte header, which Intel calls an update header, is documented by Table 9.6 (Microcode Update Field Definitions) and Table 9.7 (shown below). The first 4 bytes of the update header contain the header version (currently 1). The update header and its reserved fields are interpreted by software based upon the header version.

An extended signature table is a structure that is appended to the end of the encrypted data when the encrypted data supports multiple processor steppings and/or models. It is optional when the encrypted data only supports a single processor signature.

Looking at the extracted microcode files, I immediately noticed that the headers contained more fields than documented by publicly available Intel documentation.

To investigate the additional undocumented header, I decided to create a binary template using Lowell and Graeme Sweets, 010 Editor. If you have never tried this tool, I urge you to download a trial copy and play with it. It is not without it’s flaws but for use cases such as parsing microcode headers it excels.

Here is the 010 binary template which I came up with after a few hours of experimentation:

//
//   Authors: Finnbarr P. Murphy
//   Version: 1.0   2016-11-20
// File Mask: *.ucd
//   Purpose: Parse Intel Sandy Bridge and later microcode headers in Lenovo firmware
//

typedef struct {
    uint32  HeaderVersion <comment="Should always be 1">;
    int32   UpdateRevision <format=hex, comment="Update revision number">;
    uint32  Date <comment=DateStr2>;
    uint32  Signature <format=hex, comment="CPU Family,Model,Stepping">;
    uint32  CheckSum <format=hex, comment=ChkSum>;
    uint32  LoaderVersion <comment="Should always be 1">;
    uint32  ProcessorFlags <comment="Match against MSR 17h bits 50-52">;
    uint32  DataSize;
    uint32  TotalSize;
    uint32  reserved[3] <comment="Should be empty">;
} INTEL_MICROCODE_HDR;

// based on work by Chen and Ahn
typedef struct {
    uint32  Unknown1;   
    uint32  MagicNumber <format=hex, comment="Should always be 'A1'">;             
    uint32  Unknown2 <format=hex, comment="Usually is '0x20001'">;                  
    int32   UpdateRevision <format=hex, comment="Update revision number">;
    uint32  Unknown3;
    uint32  Unknown4 <comment="Same as size member">;
    uint32  Date <comment=DateStr4>;
    uint32  UpdateSize;
    uint32  LoaderRevision <comment="Should always be 1">;
    uint32  ProcessorFlags <format=hex, comment=BinStr>;
    uint32  Unknown5[14] <comment="All zero">;
    uint32  Unknown6[4];
    uint32  RSAModulus[64];
    uint32  RSAExponent <format=hex>;
    uint32  RSASignature[64];
} INTEL_MICROCODE_EXTRA_HDR;


// entry point
LittleEndian();
struct INTEL_MICROCODE_HDR header1 <bgcolor=cLtRed>;
struct INTEL_MICROCODE_EXTRA_HDR header2 <bgcolor=cLtGreen>;
#define INTEL_MICROCODE_HDR_SIZE (sizeof header1)

string DateStr1(uint date)
{
    local string s;

    SPrintf(s, "%0.4X%0.2X%0.2X", date & 0xffff, date >> 24, (date >> 16) & 0xff);

    return s;
}

string DateStr2(uint date)
{
    local string s;

    SPrintf(s, "Date (Y-M-D): %0.4X-%0.2X-%0.2X", date & 0xffff, date >> 24, (date >> 16) & 0xff);

    return s;
}

string DateStr4(uint date)
{
    local string s;
    
    SPrintf(s, "Date (Y-M-D): %0.4X-%0.2X-%0.2X", date >> 16, (date >> 8) & 0xff, date & 0xff);
    
    return s;
}

uint CpuFamily(uint sig)
{
    local uint fam;

    fam = (sig >> 8) & 0xf; 
    if (fam == 0xf)
        fam += (sig >> 20) & 0xff;

    return fam;
}

uint CpuModel(uint sig)
{
    local uint fam, model;
    
    fam = CpuFamily(sig);
    model = (sig >> 4) & 0xf;
    if (fam >= 0x6)
       model += ((sig >> 16) & 0xf) << 4;

    return model;
}
 
uint CpuStepping(uint sig)
{
    return sig & 0xf;
}

string BinStr(uint pf)
{
    local string s;

    s = IntToBinaryStr(pf);

    return s;
}

//
// Used to verify the integrity of the update header and data. Checksum is
// correct when the summation of all the DWORDs (including the extended 
// Processor Signature Table) that comprise the microcode update result in 00000000H.
//
string ChkSum(int dummy)
{
    local int64 i;
    local int32 sum = 0;
    local string s;
    local int32 datasize;

    if (header1.DataSize > 0 )
        datasize = header1.DataSize;
    else
        datasize = 2000;

    i = (INTEL_MICROCODE_HDR_SIZE + datasize) / sizeof(int32);
    while (i--) {
        sum += ReadInt(i * 4);
    }

    if (!sum)    
       SPrintf(s, "Checksum validated. Update is not corrupted\n");
    else      
       SPrintf(s, "ERROR: Update corrupted: %d\n", sum);
    
    return s;
}

//
// Some sanity checks
//
if (header1.DataSize + INTEL_MICROCODE_HDR_SIZE > header1.TotalSize) {
    Warning("ERROR: Bad microcode data file size\n");
    return 1;
}
if (header1.LoaderVersion != 1 || header1.HeaderVersion != 1) {
    Warning("ERROR: Invalid or unknown microcode update format\n");
    return 1;
}

//
//  provides quick checksum of RSA signature fields
//
int64 ChecksumRSA()
{
    local int64 sum = 0;
     
    sum = Checksum( CHECKSUM_BYTE, 0xA0, 0x204,-1,-1);
    return sum;
}    


void PrintDetails(void)
{
    Printf("Documented Header:\n");
    Printf("  Header Version          = %d\n", header1.HeaderVersion);
    Printf("  Revision                = %d\n", header1.UpdateRevision);
    Printf("  Firmware Date (y/m/d)   = %s\n", DateStr1(header1.Date));
    Printf("  CPU Family              = %d\n", CpuFamily(header1.Signature));
    Printf("  CPU Model               = %d\n", CpuModel(header1.Signature));
    Printf("  CPU Stepping            = %d\n", CpuStepping(header1.Signature));
    Printf("  Integrety Checksum      = %0.8X\n", header1.CheckSum);
    Printf("  Loader Version          = %d\n", header1.LoaderVersion);
    Printf("  Total Size              = %d\n", header1.TotalSize);
    Printf("  Data Size               = %d\n", header1.DataSize);
    Printf("  Processor Flags         = %s\n", BinStr(header1.ProcessorFlags));
    Printf("\nExtended Header:\n");
    Printf("  Update Size             = %d\n", header2.UpdateSize);
    Printf("  RSA fields checksum     = %LX\n\n", ChecksumRSA());
 
}

PrintDetails();

return 0;

Here is what is outputted for the standard documented Intel microcode header by the 010 editor when I apply the above template to one of the extracted microcode files:

And here is what is outputted for the undocumented header:

The only unexpected output is the header revision number which Intel documents as an unsigned 32-bit integer. Three of the five extracted microcode files contain “FFFF” as part of their header revision field, the remaining two files do not. The revision fields in both the documented and undocumented headers match. I am not sure what is going on here nor am I sure that I fully understand the description Intel provides for the Revision field in Table 9.7 (see above).

The output from the 010 template confirms what Chen and Ahn observed in their analysis of Intel microcode. I have not done an exhaustive literature search but I have found extremely few references elsewhere to this undocumented header.

As you can see, a modern binary editor such as the 010 Editor can greatly assist the process of quickly understanding binary files such as Intel microcode updates which are embedded in firmware updates.

In a follow-on post I show you how to use an 010 Editor script and a different template to automate the process of outputting a line of useful information about each of the 5 Intel microcode blobs found in this particular Lenovo firmware update.

4 comments to Examining Intel Microcode in Lenovo Firmware Updates

  • The revision number is *signed*, please read the table 9.7 again. Negative revision microcodes are beta microcode or microcode for ES parts.

    The “LoaderRevision” field in your header2 is NOT a loader revision, nor a flag. It is a counter, and most of the time it will indeed be 1.

    • Oh, where does it say that in Table 9.7? As far as I can see Table 9.7 is silent on signedness

    • Where does Table 9.7 specify signedness? What is “microcode…”? Thank you for information about beta microcode having negative revision numbers.

  • anonymous

    Ok, I was just being lazy. Let’s call it by the acronym often used by Intel: MCU, since “Microcode Updates” update more than just the microcode nowadays, anyway.

    As for the “Update Revision” field, the important information is in table 9.6 (page 9-28 of the Intel64 and IA-32 SDM vol 3A), not table 9.7. My bad.