A journey in reversing UEFI Lenovo Passwords Management

Rédigé par Bruno Pujos - 08/06/2020 - dans Reverse-engineering - Téléchargement
In this blog post the goal is to explain how I started looking at the Lenovo password. We will start by looking at how the reverse was started and the different kinds of passwords in the firmware, before having a more in depth look at two of them: the Power-On Password and the Bios Passwords. No vulnerability has been identified (yet) in the management of those passwords, but without further ado let get started.

In my last blogpost I spoke about a vulnerability in the System Management Mode (SMM) code of my Lenovo ThinkPad. At that time I got curious about how the UEFI passwords, which in particular are used to protect the BIOS interface, are handled. A few publications exist123, but I was interested to look at it from a software point of view and was not actually sure it was the same implementation (in practice it is the same). The handling of passwords is specific to each constructor, which means the code explained here is specific to Lenovo and more precisely to a few ThinkPad (this is mostly common to three different ThinkPad versions, so most of this will probably stay the same).

In this blogpost the goal is to explain how I started looking at the Lenovo password. We will start by looking at how the reverse was started and the different kinds of passwords in the firmware, before having a more in depth look at two of them: the Power-On Password and the Bios Passwords. No vulnerability has been identified (yet) in the management of those passwords, but without further ado let's get started.

Initial Reversing

Lenovo ThinkPad firmware proposes several kinds of passwords, initially the one I was interested in was the one protecting the BIOS settings interface. A few drivers in the firmware were containing reference to strings "passwords". The one I started to look at was LenovoSetupSecurityDxe since this driver appears to concentrate most of the code which allows to set and remove passwords using the user interface.

Reversing HII

In UEFI, interfacing with the user is made using the Human Interface Infrastructure (HII): a set of interfaces which allow to print and get value from the user. In particular the EFI_HII_STRING_PROTOCOL allows to retrieve strings from a "Database" using a StringId which is a simple number. This basically means that it is not possible to directly use an xref on the string when reversing. The strings found in databases can be easily identified: they are UTF-16 strings preceded by one byte with the value 0x14. This value indicates that this element of the HII database is a string. Before all those strings, a header is present. The header follows the EFI_HII_STRING_PACKAGE_HDR structure which contains, in particular, the StringInfoOffset field indicating the start of the string.

Once the header is found, it is possible to find the initialization of this database. In particular a call will be made to the function gEfiHiiDatabaseProtocolInterface->NewPackageList which allows to register the database strings with a handle. The rest of the code will then use this handle and the StringId to retrieve the strings, typically through a call to gEfiHiiStringProtocolInterface->GetString.

Tracing the string usage allows us to identify which part of the code is used for which activities and after some reverse a few interesting globals can be identified.

Lenovo Password Protocol

The globals which are used for manipulating and checking the passwords are in fact protocols. Those protocols are fetched by the following pseudo-code:

result = gBootServices->LocateHandleBuffer(ByProtocol, &LenovoPwdGuid, 0, &NoHandles, &ControllerHandle); // (1)
if (!EFI_ERROR(result))
{
  for (i = 0; i < NoHandles; ++i)
  {
    if (!EFI_ERROR(gBootServices->OpenProtocol(ControllerHandle[i], &LenovoPwdGuid, &Interface, AgentHandle, 0, 1)) { // (2)
        if (CmpGuid(Interface, Guid1)) { // (3)
            global1 = Interface; // (4)
        }
        // [...] : Same with other Guid and global
    }

  }
}
// [...]

This code does not search for one protocol but for several of them, all with the same GUID LenovoPwdGuid (2846b2a8-77c8-4432-86ec-199f205d37ca) (1). It is retrieving the interface for each of them (2) and comparing the beginning of the interface with hardcoded GUID (Guid1 in that case) (3). Depending on the GUID, the corresponding global is used to store that interface (4). Four different global variables are set this way.

This means several different protocols are all installed with the same GUID, those are then differentiated by comparing another GUID which is present at the beginning of the interface structure. Each of those interfaces represents a different kind of password, and, following that initial GUID, functions are present for manipulating them.

Password Types and Interface

By searching the hardcoded GUIDs used for comparing the structure, only a few binaries are found. Four drivers have an interesting name: LenovoPopManagerDxe, LenovoSvpManagerDxe, LenovoHdpManagerDxe and LenovoSmpManagerDxe. Looking at debug strings, it is pretty simple to guess the meaning of the abbreviation:

  • POP: Power-On Password
  • SVP: SuperVisor Password
  • HDP: HardDisk Password
  • SMP: System Management Password

It is also interesting to note that both the SVP and the HDP also have SMM drivers.

By reversing both the usage and the code from one driver, it is pretty straightforward to understand the usage of the first functions which follow the GUID:

result = gBootServices->LocateHandleBuffer(ByProtocol, &LenovoPwdGuid, 0, &NoHandles, &ControllerHandle); // (1)
if (!EFI_ERROR(result))
{
  for (i = 0; i < NoHandles; ++i)
  {
    if (!EFI_ERROR(gBootServices->OpenProtocol(ControllerHandle[i], &LenovoPwdGuid, &Interface, AgentHandle, 0, 1)) { // (2)
        if (CmpGuid(Interface, Guid1)) { // (3)
            global1 = Interface; // (4)
        }
        // [...] : Same with other Guid and global
    }

  }
}
// [...]

All functions of this interface, except get_status, make some actions and change a globally shared bitfield for indicating the resulting state: the status. The get_status function allows to retrieve the value of that bitfield allowing to determine if the password has been provided by the user.

Once this was understood, I could start looking on how all of this worked. The SMP and SVP passwords work pretty much the same way and are explained in more details later. The HDP is already documented in a blogpost3 and I was not that interested in it. Finally, there is the Power-On password.

Power-On Password

The POP is the password which can be set by the user and is asked each time the computer boots. It is handled by the LenovoPopManagerDxe driver which exposes the interface described previously.

Password hash & PCD

For looking at how is stored the password, the two functions set_pwd and check_pwd are the obvious choices. The function set_pwd starts by retrieving 0xC bytes from the pointer given in parameters before calculating the hashed password. The hash is calculated by using another protocol (73e47354-b0c5-4e00-a714-9d0d5a4fdbfd) implemented in the LenovoCryptService driver. The first function of this protocol allows to calculate a SHA256 and is the one used for hashing the password. The hash is salted and the salt is fetched through the Platform Configuration Database (PCD).

The PCD is a generic storage system which is transmitted between the UEFI PEI and DXE phases and shared between drivers, the implementation of the PCD protocol is open-source in edk2. PCD allows to define sharing memory buffers through a token ID which is automatically determined at the compilation of the firmware. A static storage is loaded by the drivers (one for PEI and one for DXE) and is present on the Firmware File System (FFS). This storage can be easily found by searching for the GUID PCD_DATA_BASE_SIGNATURE_GUID but is usually in the same "File" as the drivers. A dynamic storage is also provided by the protocol and can be used for sharing data between drivers.

In the case of the salt, the dynamic storage is used. The salt has a size of 0x20 bytes on recent firmwares but older ones have shorter size (0xA). The salt can be really easily retrieved from an UEFI shell just by asking the PCD protocol for the correct token ID. As the token ID is generated at compile time, it is necessary for an attacker to be able to determine this automatically or simply to reverse the driver for this particular firmware so as to find that ID.

An interesting point to note is that all the buffers used for storing the password and the salt in this driver are reset at 0x00 before being freed. Retrieving the hash of the password is not as easy as to simply dumping the memory just after the boot. So now let's get a look on how it is stored.

Storage

The password is stored through a function allowing to write a byte in the storage, the code of that function is pretty much self explaining:

UINT8 __fastcall write_rtc_storage(UINT8 pos, UINT8 val)
{
  UINT8 result;

  if ( pos >= 0x80u )
  {
    __outbyte(0x72u, pos + 0x80);
    result = val;
    __outbyte(0x73u, val);
  }
  else
  {
    __outbyte(0x70u, pos);
    result = val;
    __outbyte(0x71u, val);
  }
  return result;
}

One IOPort is used for indicating the offset at which to read or write, the other one is used for writing the value. The read works the same way except the write (out) of the value is replaced by a read (in). The four IOPorts 0x70 to 0x73 are well known and documented: they are used for interacting with the Real-Time Clock (RTC) device. The main goal of this device is allowing access to time but it also provides some storage space usually called CMOS. Those IOPorts are documented in the PCH datasheet but good resources also exist on the osdev wiki.

An interesting fact about RTC devices is that it must always be powered on in order not to lose the data in storage. Usually, a small batterie (different than the main one) is included in the computer for being sure this device is always on. What that means is an attacker with a physical access will be able to bypass this password by simply removing all sorts of power access. It happens that Lenovo is aware of this and that this is even documented.

After that quick look at the Power-On password, I was also interested to look at the other ones.

Bios Configuration Passwords

The one password I was really interested in was the password protecting the BIOS configuration, in practice both the SMP and SVP passwords work pretty much in the same way. Those two drivers expose the password interface explained before and use the same storage.

As for the POP, the simplest thing for understanding how the password is stored is to look at the set_pwd function. It starts by performing the calculation for the hash of the input with a SHA256 like for the POP. Interestingly this hash uses the same salt as the one for the POP, but the really interesting part is how the passwords are stored.

Emulated Eeprom

This storage is made using a protocol with the GUID 82b244dc-8503-454b-a96a-d0d2e00bf86a this protocol is registered by the driver EmulatedEepromDxe. Thanks to its explicit name we can deduce that this is probably a storage API, interestingly Lenovo seems to have embedded an eeprom in their computer in the past. Three functions are registered by this protocol but only the first two are used for the password management, meaning we probably have a read and write functions. The first function is used by both the functions for testing and setting the password, while the second one is used only in the function for setting the password: this seems a pretty strong indication that the first one allows to read while the second one is for writing. Now the really interesting question is where the EmulatedEepromDxe drivers actually store that data ?

The first function of the protocol has the following prototype:

EFI_STATUS EmulEeprom_Read(void *this, UINT64 unk_enum, UINT64 index, UINT8 *pRes)

The first argument (this) is simply the pointer on the protocol interface, the last one (pRes) is clearly used for retrieving the read value, the two others clearly indicate the storage space to use. The index is an offset in this storage space but the unk_enum was unclear. Eeprom, contrary to NOR or NAND flash, can have a fine granularity on the erase size. However, because the circuitry for handling small size erasure is taking space which could be used for more storage, the erase is usually made on several bytes regrouped in "banks". In practice this means that the programmatic interface is actually quite similar to NOR or NAND flash. This is one of the reasons most eeprom have been replaced with cheaper NOR or NAND flash. In our case the unk_enum is in practice a bank number in the emulated eeprom, in the code this bank number is translated and added to the index number for calculating an offset at which to read or write.

The EmulEeprom_Read function perform some checks on the provided values, and call another function, perform_read, with the bank_num, the index and the pRes. It is that function which actually perform the actual read. This function calls several other functions which read and write on IOPorts. This is the point in reversing a firmware where it generally becomes painful if the IOPorts are not documented. Three different IOPort are used, the first one is the IOPort 0x1808, it is used only in reading (in), in a loop and is followed by the pause x86 instruction. If that was not already obvious, it is a timer in particular the PM Timer. On Linux a simple dmesg will give you a pretty big hint: ACPI: PM-Timer IO Port: 0x1808. However, the two other IOPorts, 0x1630 and 0x1634, are not as easy to understand.

IOPort Reversing

Those two IOPorts are clearly those used for reading and writing data and each one is used for both reading (out) and writing (in). The IOPort 0x1634 is generally written with constant and do not depend on the offset, when it is read the result is usually checked as a bitfield. On the other end the IOPort 0x1630 is used for both writing the offset calculated previously and for reading the actual result. In at least one function, a read is performed on this IOPort and the result discarded. This is a typical pattern for interfacing with other hardware device: one IOPort is a "configuration" IOPort, used for checking the state of the other device, indicating the type of action performed and so on, in our case this is the IOPort 0x1634. The second IOPort (0x1630) is the one used for transmitting the data, both in reading and in writing. Reading on the IOPort can have side effects for the device, so one read was performed while discarding the result. This is a classic pattern for discussing with external devices using IOPorts, it basically works the same way for discussing with a SPI flash or for interfacing with a PCI device.

So at this point we know the hashes of those passwords are not stored on the SPI flash but on another device in our computer (again), so the question now is which one ? The two IOPorts used are variable (by opposition to fixed ones such as the one for PCI), meaning those port numbers depends on the configuration of the system. Searching by which device those IOPorts are used is usually complicated. In that case I started by searching for the IOPorts base declared by PCI devices (using lspci) but without any luck. The next step was to look at the variable IOPorts which are defined in the CPU and Platorm Controller Hub (PCH) datasheets. After some times enumerating those, I finally found the registration of those IOPorts in the Low Pin Count (LPC) controller as the LPC Generic IO Range 1 (LGIR1).

The Low Pin Count bus is used for communicating with several devices inside the computer. In particular, it is used for communication with a device called the Embedded Controller (EC), soberly referred as "Microcontroller 1" in the PCH datasheet. The EC is a microcontroller which is particularly known for being in charge of powering the laptop. At that point, I remembered reading the presentation Breaking Through Another Side by Alex Matrosov and Alexandre Gazet where they speak about the EC and its security impact. Looking back at their talk, I noticed that those two IOPorts are also referenced in it, so the hash of the password is stored in the EC.

The EC has its own firmware and looking at it was not part of this project. However, one thing I could do was to try to read the password the same way this was done by the driver. I implemented a little script using chipsec for interacting with the EC, but when trying to read the hash of the password I only got null bytes. As I am able to read the content of other emulated "banks" this seems to really be a protection mechanism: the firmware probably lock access to the hash after the boot phase is completed.

A last thing intrigued me: I mentioned before that a SMM driver LenovoSvpManagerSmm existed for the SVP password. As SMM is running in parallel to the OS, I was interested in looking at how it retrieved the hash of the password. After some reversing it appears that this driver uses the SMM alternative of the EmulatedEepromDxe driver: EmulatedEepromSmm. This driver works the same way as the EmulatedEepromDxe and performs the same action on the same IOPorts. However, the LenovoSvpManagerSmm is actually retrieving the hashes during its initialization and stores them in buffer located in SMRAM. This seems to indicate that a SMM vulnerability as the one explained in my previous blogpost should allow to retrieve those hashes.

The hashes of the firmware passwords for the BIOS are in practice stored in the Embedded Controller and seems to be locked after the boot is over. An attacker should be able to fetch those using an UEFI or SMM vulnerability but this is already a more complex task. Their security is still based on the security of the EC but this will be a research for another time.

Conclusion

In conclusion, the handling of the Lenovo passwords investigated here is pretty much well-made, an attacker with hardware access to the computer should be able to bypass those but it is not as easy as I expected initially.

The power-on password can be easily reset which is the most problematic thing, however it still is necessary to have a hardware access to do that (or a vulnerability in the firmware of course). The BIOS password is not stored on the SPI Flash but on the EC flash and read/write access seems to be locked after boot. This means a user of a computer should not be able to easily remove or change the BIOS password without physically opening the computer.

An interesting tendency can also be seen: the UEFI firmware which was considered the root of trust for the complete system is more and more replaced by other firmware. The EC seems to be used by Lenovo for some part of its security (not only its passwords4) and the Management Engine (ME) and Authenticated Code Module (ACM) are now acting as root of trust for the UEFI firmware. In practice, this makes life more difficult for an attacker but it also provides a potentially wider attack surface and changing the root of trust may just be changing the problem.