SMM unchecked pointer vulnerability

Mon 30 May 2016 by Bruno

TL;DR

This article explains the exploitation of an SMM unchecked pointer vulnerability present in several firmwares. As this vulnerability is a memory corruption, it only applies to firmwares including the unpatched vulnerable DXE driver.

It first explains the SMM mode and some of its mechanisms, then the reversing of the UEFI driver in which the vulnerability is present, then the exploitation of the vulnerability in it-self and finally a little conclusion about the impact of the vulnerability.

This work was conducted on Intel Core i7-3770.

System Management Mode

Basic functioning

System Management Mode (SMM) is an Intel mode which has the particularity to be transparent to the Operating System (OS). It is initialized by the firmware during the boot to handle the hardware and the security of the computer.

Intel Modes Of Operation (Intel V.3 C.2 P.2)

As we can see on the Intel mode schematic, entering in SMM is made by triggering a particular interruption: the System Management Interrupt (SMI). When an SMI is triggered the processor saves the context, disables interruptions and switches to a different address space called System Management RAM (SMRAM). To return from SMM the rsm (Return from System Management) instruction is used, restoring the previous state. This instruction can only be executed from SMM. All of those mechanisms allow the SMM to be transparent to the OS.

During the boot the BIOS will begin by initializing the SMRAM range by reserving a space in our classical RAM, then it will setup the SMM code in this range before protecting it. The first thing that is made to initialize the SMRAM is setup its base address: SMBASE.

During the boot the firmware can install SMI handlers and in particular SoftWare SMI (SWSMI) handlers which will be triggered from the OS allowing to accomplish more privileged operations than the OS can.

mov dx, 0xB2
mov ax, SMINumber
out dx, ax

When an SMI is received the processor waits for all instructions to retire and for all stores to complete. The current state is then saved in the SMRAM at SMBASE + 0xFC00, the processor enters SMM and executes the code at SMBASE + 0x8000. As SMM is a mode, several processors can potentially enter it simultaneously, for this reason the SMBASE has to be different for each processor. If this is not the case the second SMI will overwrite the saved state of the first one.

smram.png

The saved state contains most of the registers allowing the SMI handler to access and modify them. It also contains the value of the SMBASE and an SMI handler will be able to change it. If the handler modifies it when the rsm instruction is executed the new value will be set as SMBASE for the processor: the next SMI will use that value as SMBASE. This is the only way to change the SMBASE. At boot time it is fixed to a specific value defined by the chipset.

When entering SMM the processor is in a mode which looks like the real-mode (16bits) with a few exceptions. In practice when entering in SMM the first thing that is done is to go back to 64bits (in an environment close to extended mode) with a mapping in 1:1 allowing to access the entire physical memory.

The SMM mode has full access to the whole memory and physical devices of the system. The IOs made in SMM can not be intercepted by an hypervisor. Even if SMM is not a privileged ring it is in practice more privileged than the kernel and the hypervisor. This leads to calling the SMM "ring-2".

SMM Security

As the SMM is critical to several security elements in the computer architecture, it is itself protected. The critical part of the SMM is obviously the SMRAM in which the SMM code is located. Several mechanisms have been implemented to protect this memory zone.

The first protection is obviously against memory accesses from the "standard" mode. This is done with the System-Management Range Register (SMRR). This protection seems to have been previously handled by the System Management RAM Control/Compatible (SMRAMC) register from the DRAM controller. Even if the SMRAMC does not protect against access to the SMRAM it is still important to lock it to avoid introducing another vulnerability [3] . The SMRR are Model-specific register (MSR) per core (which means that there must be set for each core). They were initially introduced to protect against SMM cache poisoning attacks [4] [5] . When set, they protect read and write access when not in SMM. In practice write will be ignore and read will return only 0xFF .

Other mechanisms protect the SMRAM in particular the TSEG/BGSM range and the TOLUD register. The range TSEG/BGSM which is made to protect against DMA access should cover the exact same range than the SMRR. Those protections give us some information about the localisation of the SMBASE.

If one of these protections is not set, several attacks [6] may allow to take control in SMM. A good way to check those configuration is to use Chipsec . However these protections do not cover possible memory corruption in the SMI handler.

Classic memory protection like NX should still be possible to setup, however they generally are not. Intel also introduces a new MSR MSR_SMM_FEATURE_CONTROL. This MSR is common to all cores and is the equivalent of Supervisor Mode Execution Protection (SMEP) for SMM. When in SMM and executing code outside of SMRAM this will trigger an unrecoverable Machine Code Exception (MCE). Interestingly this MSR can only be read and set from SMM and it can be locked. However as the more common protections against memory corruption type vulnerabilities this feature does not seems to be activated in most firmwares.

SMM capability

For an attacker there are several interests in gaining code execution. The first and most obvious one is the possibility to have full control over the computer while being "invisible" to the OS.

The second big advantage of gaining code execution in SMM is the possibility to bypass some protection of the flash which contains the firmware code. The content of the SPI Flash is protected by several mechanisms including the BIOS Write Enable (BIOSWE) flag and the SMM BIOS Write Protected (SMM_BWP) flag from the BIOS Control (BIOS_CNTL) register. If the BIOSWE flag is not set, only read cycle to the flash will be allowed. When setting BIOSWE with the BIOS Lock Enable (BLE) flag set, it will trigger an SMI, at that point the code running on SMM can decide if the setting of BIOSWE is legitimate or not. Because of a multi-cpu race condition called speed-racer [2] , Intel added the SMM_BWP flag. if set this flag allows write cycle to be generated only if all the processors are in SMM. An attacker gaining code execution in SMM will be able to bypass these two protections, but other protections could still protect the flash such as the Protected Range (PR) registers.

Finally even if the flash is correctly protected using the PR registers the control in SMM can give access to a wider attack surface, for example by using the BootScriptTable vulnerability if the BootScriptTable is stored in the SMRAM space [1] .

SMIFlash vulnerability

The vulnerability was discovered during the Reverse Engineering (RE) of the firmware of a Lenovo ThinkCentre M92p . The RE in this article is made on the 9SKT91A version of the firmware.

In this part generalities about Unified Extensible Firmware Interface (UEFI) and the handling of SMM in this specification will first be explained, then the RE of the part which interests us in the driver, and finally the vulnerability and its exploitation.

Generality about UEFI and SMM protocols

UEFI is a specification defined by the UEFI Forum with the goal to define a common interface for developing firmwares. There is an open-source firmware development environment called edk2 . There was previously an implementation called edk for the EFI specification. Most of the firmwares are based on one of these implementations.

The EFI Architecture Specification splits the boot in 7 different phases. One of those is the Driver Execution Environment (DXE) phase. It is the moment where most of the system initialization is performed. In particular most of the SMIs are initialized in this phase, generally done in extended mode. It is composed of several components: the DXE Foundation which provides services, the DXE dispatcher which discovers and executes the drivers, and finally a set of DXE drivers.

Each of these DXE drivers are provided with a set of services which will allow them to have some abstraction on top of some "basic" services. Two main types of services exist: the runtime services and the boot services. The only difference between them is whether they are available after the OS is loaded. These services allow different possibilities from setting the time to getting variables, registering events to allocating memory. These services are provided to the driver through the EFI_BOOT_SERVICES and EFI_RUNTIME_SERVICES tables. These tables can be accessed through the EFI_SYSTEM_TABLE which is passed as an argument to the driver.

EFI System Table (UEFI PI Specification)

One of the important set of services provided by the DXE Foundation is the declaration and request of protocols. The protocols have a software interface between the drivers, each protocol will be identified by a Global Unique ID (GUID). The different specifications for UEFI define GUID and protocols which allow compatibility between drivers, EDK2 and the constructors add new ones.

The specification is hardware independent but the "UEFI Platform Initialization" specification adds several pieces of information for the Intel platform: several protocols, the flash filesystem description and an optional phase for SMM.

The part on SMM describes how the firmware should initialize the SMRAM and the management of the SMI. In particular it introduces the System Management System Table (SMST) which is the equivalent of the EFI_SYSTEM_TABLE for the SMM drivers. It provides basic services such as allocation, installation of protocols in SMM and registration of SMI handlers, it also provides access to other pieces of information such as the cpu state before entering SMM or the SmmConfigurationTable which allows to register a table associated to a GUID.

Several DXE drivers containing references to SMI or SMM in their name were found in the flash filesystem. One of them seems really interesting: SMIFlash.efi .

Reverse Engineering SMIFlash.efi

Initialization

The first thing the driver does is to setup a few global variables in particular the SystemTable, the BootServices table, the RuntimeServices table and the ImageHandle. This initialization of the global variables is a classical behaviour as it allows to use the services for the callback functions. The ida-efiutils plugin for IDA will try to locate automatically these global variables. The driver will then call an initialization function init with the ImageHandle, the SystemTable and two function pointers.

// main_smm is a function pointer at 0x180001254
// fun2 is a function pointer at 0x180001E78
EFI_STATUS EFIAPI SMIFlashEntryPoint (
    IN EFI_HANDLE           ImageHandle,
    IN EFI_SYSTEM_TABLE     *SystemTable)
{
    init_global(ImageHandle, SystemTable);

    return init(ImageHandle, SystemTable, main_smm, fun2);
}

// initialized 4 global variables
void EFIAPI init_global(
    IN EFI_HANDLE           ImageHandle,
    IN EFI_SYSTEM_TABLE     *SystemTable)
{
    if (gSystemTable)
        return;

    gSystemTable = SystemTable;
    gBootServices = SystemTable->BootServices;
    gRuntimeServices = SystemTable->RuntimeServices;
    gImageHandle = ImageHandle;
}

The init function will call again the function setting up the global variables, then it will look for the EFI_SMM_BASE_PROTOCOL using the LocateProtocol boot service. Interestingly this protocol is not the one defined in the "UEFI Platform Initialization (PI) Specification", but in the Platform Innovation Framework for EFI System Management Mode Core Interface Specification :

#define EFI_SMM_BASE_PROTOCOL_GUID { 0x1390954D, 0xda95, 0x4227, 0x93, 0x28, 0x72, 0x82, 0xc2, 0x17, 0xda, 0xa8 }

typedef struct _EFI_SMM_BASE_PROTOCOL {
    EFI_SMM_REGISTER_HANDLER Register;
    EFI_SMM_UNREGISTER_HANDLER UnRegister;
    EFI_SMM_COMMUNICATE Communicate;
    EFI_SMM_CALLBACK_SERVICE RegisterCallback;
    EFI_SMM_INSIDE_OUT InSmm;
    EFI_SMM_ALLOCATE_POOL SmmAllocatePool;
    EFI_SMM_FREE_POOL SmmFreePool;
    EFI_SMM_GET_SMST_LOCATION GetSmstLocation;
} EFI_SMM_BASE_PROTOCOL;

If this protocol has been located it will then call the InSmm function. The driver will be loaded twice, first in a "standard" mode during the DXE phase and then in SMM. This call allows to know in which mode it is running. In our case we are only interested in the SMM path.

It will then get the SMST and register it in a global variable too with the addition of some other global variables and the retrieval of another protocol before calling the first function pointer given as argument main_smm .

gEfiSmmBaseProtocolGuid = EFI_SMM_BASE_PROTOCOL_GUID;

EFI_STATUS EFIAPI SMIFlashEntryPoint (
    IN EFI_HANDLE           ImageHandle,
    IN EFI_SYSTEM_TABLE     *SystemTable,
    EFI_STATUS (*fun1)(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable),
    void (*fun2)()
    )
{
    EFI_STATUS Status;
    CHAR InSmm;

    init_global(ImageHandle, SystemTable);
    Status = gBootServices->LocateProtocol(gEfiSmmBaseProtocolGuid, 0, gEfiSmmBase);
    if (EFI_ERROR(Status))
        return Status;
    gEfiSmmBase->InSmm(gEfiSmmBase, &InSmm);

    // check if in Smm
    if (InSmm)
    {
        // Init the Smst
        gEfiSmmBase->GetSmstLocation(gEfiSmmBase, &gSmst);

        // some other init
        // ...

        // In practice call main_smm in this driver
        return fun1(ImageHandle, SystemTable);
    }
    else
    {
        // This is the initialization when not in SMM...
    }
}

The main_smm function is the one which interests us mostly because it is the one which registers the SWSMI. To register it the driver uses the EFI_SMM_SW_DISPATCH_PROTOCOL protocol defined in the same specification than the EFI_SMM_BASE_PROTOCOL protocol.

#define EFI_SMM_SW_DISPATCH_PROTOCOL_GUID { 0xe541b773, 0xdd11, 0x420c, 0xb0, 0x26, 0xdf, 0x99, 0x36, 0x53, 0xf8, 0xbf}

typedef struct _EFI_SMM_SW_DISPATCH_PROTOCOL {
    EFI_SMM_SW_REGISTER Register;
    EFI_SMM_SW_UNREGISTER UnRegister;
    UINTN MaximumSwiValue;
} EFI_SMM_SW_DISPATCH_PROTOCOL;

As we are interested by the code executed in SMM we can directly look where the Register function is called. The main_smm function registers 6 different SWSMI with the same function SwSMIDispatchFunction as handler.

EFI_STATUS EFIAPI main_smm (
    IN EFI_HANDLE           ImageHandle,
    IN EFI_SYSTEM_TABLE     *SystemTable)
{
    EFI_STATUS Status;
    EFI_HANDLE DispatchHandle;
    EFI_SMM_SW_DISPATCH_CONTEXT DispatchContext;
    EFI_SMM_SW_DISPATCH_PROTOCOL *EfiSmmSwDispatchInterface;
    // some initialization ...

    // get the EFI_SMM_SW_DISPATCH_PROTOCOL
    gBootServices->LocateProtocol(gEfiSmmSwDispatchProtocolGuid, 0, &EfiSmmSwDispatchInterface);

    // Register the SWSMI from 0x20 to 0x26 with the SwSMIDispatchFunction function
    for (int i = 0x20; i < 0x26; i++)
    {
        DispatchContext->SwSmiInputValue = i;
        Status = EfiSmmSwDispatchInterface->Register(EfiSmmSwDispatchInterface, SwSMIDispatchFunction);
        if (EFI_ERROR(Status))
            return 0;
    }

    // register the interface and other finalization...

    return 0;
}

SWSMI handler

The 6 SWSMI from 0x20 to 0x25 are handled by the SwSMIDispatchFunction function. This function begins by looking for a particular GUID in the SmmConfigurationTable inside the SMST. Once it has been found, it will get a single value from the table associated with the GUID. It will then check that this value is not -1 in which case it fails, otherwise it will use this value with the CpuSaveState for retrieving the registers ecx and ebx. The value retrieved from the SmmConfigurationTable seems to be the CPU number for getting the registers from the correct CPU.

#define SmmConfigurationTableCpu {0xE4D535BB, 0x5DEA, 0x42F8, 0xB5, 0xF8, 0xD6, 0xB8, 0x13, 0xFF, 0x6B, 0x46}
gSmmConfigurationTableCpuGuid = SmmConfigurationTableCpu;

void EFIAPI SwSMIDispatchFunction(EFI_HANDLE DispatchHandle, EFI_SMM_SW_DISPATCH_CONTEXT *DispatchContext)
{
    int nte = gSmst->NumberOfTableEntries;
    int CpuId = gSmst->CurrentlyExecutingCpu - 1; // default value

    int counter = 0;
    for (; counter < nte; counter++)
    {
        // check if the correct table have been found
        if (!memcmp(gSmst->SmmConfigurationTable + counter, gSmmConfigurationTableCpuGuid, sizeof(EFI_GUID)))
            break;
    }

    // check that the table have been found and retrieve the value
    //  if needed
    if (counter != nte)
        CpuId = *(((EFI_CONFIGURATION_TABLE *)(gSmst->SmmConfigurationTable + counter))->VendorTable);

    if (CpuId == -1)
        return;

    int ecx = (( EFI_SMI_CPU_SAVE_STATE *)(gSmst->CpuSaveState + CpuId))->_ECX;
    int ebx = (( EFI_SMI_CPU_SAVE_STATE *)(gSmst->CpuSaveState + CpuId))->_EBX;

    // Another SmmConfigurationTable is searched at this point and if
    // found it calls another function from this table. If the
    // search failed it seems able to continue without it.

    // ...
}

The function continues by using these two registers for computing the address of a pointer. It will then do some initializations before making a switch depending of the SWSMI which has been triggered. In this case the SWSMI number which interests us is the SWSMI number 0x21 which just calls a function with the created pointer as argument.

void EFIAPI SwSMIDispatchFunction(EFI_HANDLE DispatchHandle, EFI_SMM_SW_DISPATCH_CONTEXT *DispatchContext)
{
    // ...
    struct smiflash_arg *pointer; // see bellow for the struct
    int smi_number = DispatchContext->SwSmiInputValue;

    // Retrieve a pointer from user provided value
    pointer = ecx << 32 | ebx;

    if (smi_number != 0x25)
        pointer->unknown = 0;

    // some init ...

    switch (smi_number) {
        case 0x20:
            // ...
            break;
        case 0x21:
            swsmi_handler21(pointer);
            break;
        case 0x22:
            // ...
            break;
        case 0x23:
            // ...
            break;
        case 0x24:
            // ...
            break;
        case 0x25:
            // ...
            break;
        default:
            break;
    }

    // some ending ...

    return;
}

The handler of the SWSMI 0x21 is pretty simple it calls a function of a protocol which is set during the main_smm . This protocol is defined in another driver and provides an interface for abstracting the hardware, it allows to make different actions on the Flash (read, write, manipulating protections...). The other SWSMI allows to use different functionalities of this interface too. The SWSMI 0x21 fetches elements inside a structure provided by the user pointer derived from the ebx and the ecx registers. It then passes them to one of the functions of the protocol which is in charge of reading from the flash.

struct smiflash_arg {
    void    *addr_buf; // 0x0
    int32_t offset_bios; // 0x8
    int32_t size; // 0xC
    char    unknown; // 0x10
};

void swsmi_handler21(struct smiflash_arg *pointer)
{
    EFI_STATUS val = FlashProtocol->ReadFlash(pointer->offset_bios + 0xFFB80000,
        pointer->size, pointer->addr_buf);

    // This global variable is false at initialization of the driver
    // and stays that way if no other SWSMI in this driver is triggered
    if (global_var)
    {
        // ...
    }

    // This function returns but the return value is not used by
    // SwSMIDispatchFunction
    return EFI_ERROR(val);
}

As the pointer passed in argument to the swsmi_handler21 function is provided by the user and is not sanitized the vulnerability is quite obvious. An attacker can trigger the SWSMI with a structure containing an addr_buf pointing to an SMRAM location and rewrite it with the content of the flash: an almost arbitrary write in SMM.

Exploiting SMIFlash.efi

The goal of the exploitation is to gain code execution in SMM, for that the easier way is to rewrite the SMBASE address to an address outside the SMRAM where we have previously mapped our own code, this code will be executed in SMM which will allow us to deactivate the protections of the SMRAM such as the SMRR.

smiflash_read_flash.png

The first step of the exploitation will be to allocate a fake SMRAM zone (FSMRAM) below the 4Gb (step 1). We then need to find a valid fake SMBASE in our flash (FSMBASE): an address where the +0x8000 to +0x10000 will be in our allocated FSMRAM (step 2). Unexpectedly the SMBASE does not have to be aligned which allows us to find one really easily. For finding this FSMBASE it is better to directly use the SWSMI because the implementation and the offset calculation can change between two different firmwares.

Once we find our FSMBASE we map our shellcode at our FSMBASE + 0x8000 which is the place where the processor will resume execution upon entering SMM once we have changed the SMBASE (step 3). When entering SMM the processor switch to a "real-mode" like (there is some differences with the normal real-mode) in 16 bits. The following shellcode will disable the SMRR for the processor which executes it allowing direct access from outside SMM. It then restores the previous SMBASE allowing to restore the standard behaviour.

mov ecx, 0x1F3
xor edx, edx
xor eax, eax
wrmsr
mov eax, $realsmbase
mov ebx, ($fakesmbase + 0xFEF8)
mov [ebx], eax
rsm

At that point we have prepared a full fake environment for when we have changed the SMBASE. Now we can just trigger the SWSMI 0x21 with the offset of our FSMBASE we have found at step 2, and the address to which the SMBASE is stored (SMBASE + 0xFEF8) (step 4). The SWSMI handler reads our FSMBASE (step 4.b) and rewrites the stored SMBASE (step 4.c), when executing the rsm instruction the new SMBASE becomes our FSMBASE. Finally triggering the SWSMI will execute our shellcode (step 5).

As we have an arbitrary write it should be possible to directly write a shellcode inside SMM. More precisely we should be able to rewrite a handler for a SWSMI that we would trigger later or directly rewrite the code at SMBASE + 0x8000. There is some problems with that approach. The first one is that by default we do not know the address to which we want to write our shellcode and writing at a bad position will likely result in a crash. This problem can be solved by having a leak or just reversing the way the SMI handler are loaded and stored in memory. Another potential problem is retrieving our value from the Flash. However we can only write one byte at a time so this should not really be a problem as long as we do not rewrite the code necessary for using the SWSMI. This technique has a big advantage: it allows to bypass the MSR_SMM_FEATURE_CONTROL and it will probably be more stealthy.

One of the problem left out is how to get the SMBASE address ? This problem is not a new one and in 2010 the "System Management Mode Design and Security Issues" [7] presentation proposed a technique allowing an attacker to obtain the SMBASE address using a cache technique. However the introduction of the SMRR blocked this technique leaving us with a few choices: reversing the firmware to find out its exact place, parsing a dump of the SMRAM or guessing it. The two first methods are obviously the more reliable ones but RE is time consuming and for obtaining an SMRAM dump it is necessary to use another vulnerability or to modify the firmware. The guessing is interesting because we have some indications about our memory zone with the SMRR range. We know that the code executed will begin at SMBASE+0x8000 and the state will be stored just before SMBASE+0x10000. From that we can deduce that our SMBASE will be included between the top of our SMRAM - 0x10000 and the bottom of our SMRAM - 0x8000. Even if not necessary aligned the SMBASE will probably be and will have a granularity of at least 0x1000 because of the saved state. As we have one SMBASE by CPU we can have a step of 0x1000*number_CPU. This technique will probably crash the computer several times before succeeding. However once the SMBASE for a version of a firmware is found all the other computers with that firmware will have the same SMBASE which makes the exploit reliable.

Conclusion

This vulnerability was initially found on two different firmwares of different OEM, both of them seem to have a lot in common. Their firmware were based on one version of the EDK implementation by Intel with several new features added. After some research it appears that both were using code provided by American Megatrends Inc. (AMI) . We contacted AMI and the OEM and got quick responses from them. We would like to thank them for working with us, especially Lenovo for coordinating with us. You may find their advisory at the following address: https://support.lenovo.com/fr/en/product_security/len_4710.

This vulnerability allows to gain code execution in SMM. In the case of both studied firmwares the flash was not protected by the Protected Range (PR) registers, code execution in SMM allows rewriting the flash and potentially the setup of a persistent bootkit.

On January 2016 VirusTotal (VT) began to provide information on firmware images as described in their blog post . We used this for finding firmware which includes the SMIFlash driver. In total we found approximately 900 different firmwares (type:rom) which contains it, 468 of those had different versions, however it is likely that a lot of these firmwares are just different versions of one another. We have gathered the Vendor identification provided by VT for each of those firmware and got approximately 10 different constructors however 84% of the firmwares have AMI as vendor.

Updates:

[1]https://twitter.com/d_olex/status/734626026005901312
[2]https://bromiumlabs.files.wordpress.com/2015/01/speed_racer_whitepaper.pdf
[3]http://fawlty.cs.usfca.edu/~cruse/cs630f06/duflot.pdf
[4]https://cansecwest.com/csw09/csw09-duflot.pdf
[5]http://invisiblethingslab.com/resources/misc09/smm_cache_fun.pdf
[6]https://media.defcon.org/DEF%20CON%2022/DEF%20CON%2022%20presentations/Bulygin,%20Bazhaniul,%20Furtak,%20and%20Loucaides%20-%20Updated/DEFCON-22-Bulygin-Bazhaniul-Furtak-Loucaides-Summary-of-attacks-against-BIOS-UPDATED.pdf
[7]http://www.ssi.gouv.fr/uploads/IMG/pdf/IT_Defense_2010_final.pdf