Header bannerHeader banner
November 29, 2024

LogoFAIL Exploited to Deploy Bootkitty, the first UEFI bootkit for Linux

Binarly REsearch

Binarly researchers find a direct connection between the newly discovered Bootkitty Linux bootkit and exploitation of the LogoFAIL image parsing vulnerabilities reported more than a year ago.

Summary

  • ESET documents the discovery of Bootkitty, a prototype UEFI bootkit uploaded to VirusTotal capable of infecting the Linux kernel. This is a significant evolution in firmware-based threats previously focused on the Windows ecosystem (e.g BlackLotus, FinSpy, MoonBounce).
  • Binarly research team discovers that Bootkitty exploits a vulnerability tied to LogoFAIL (CVE-2023-40238), the image parsing vulnerabilities reported a year ago affecting the entire UEFI firmware ecosystem. The Bootkitty LOGOfail exploit enables the execution of malicious shellcode through tampered BMP files in UEFI firmware.
  • The exploit uses embedded shellcode within a BMP image to bypass Secure Boot protections by injecting rogue certificates into the MokList variable.
  • Vulnerable devices include models from Acer, HP, Fujitsu, and Lenovo, with evidence suggesting the exploit may have been tailored for specific hardware configurations. While a patch from Insyde mitigates the vulnerability,  unpatched devices remain at risk.

Earlier this week, our friends at ESET Martin Smolar and Peter Strýček published technical documentation of a new UEFI bootkit called “Bootkitty” targeting certain Linux Ubuntu configurations.  While this appears to be a proof-of-concept rather than an active threat, Bootkitty  signals a major shift as attackers expand bootkit attacks beyond the Windows ecosystem – (BlackLotus and MoonBounce). The operating system bootloaders present a vast attack surface that is often overlooked by defenders, and the constant growth in complexity only makes it worse. The vulnerabilities discovered by Binarly REsearch, such as LogoFAIL and PKfail, only serve to demonstrate that.

Bootkitty, which was uploaded to VirusTotal earlier this month, is the first example of a bootkit capable of infecting the Linux kernel and suggests that threat actors may be actively developing Linux versions of the kinds of bootkits seen on Windows machines.

Separate from the ESET publication, a second article on Bootkitty was publicly shared by @MatheuzSecurity, triggering a full Binarly research investigation into this new threat.

@MatheuzSecurity posts 'An analysis of a Linux kernel rootkit by humzak711. "dropper.ko", "rootkit_loader.ko" that I found while threat hunting.
@MatheuzSecurity shares humzak711's article

This article complements the analysis released by ESET, focusing on the rootkit capabilities of the malware and mentions the discovery of an open directory with malware samples. 

The author added a screenshot of the open directory:

Open directory features two logofail images and the message "PWNED by IranuKit"
Screenshot of the open directory

One detail immediately raised eyebrows: The two image files – logofail.bmp and logofail_fake.bmp – are clearly named after our LogoFAIL research which was presented last year at BlackHat EU 23. The fact that the “fake” version was only 7.7KB, and the logofail.bmp file was 16MB was a lightbulb moment that this was something serious. 

We quickly located this server and to our surprise… it was still up! 

Open directory server shows same files as seen in the ESET article
Screenshot of the open directory server we visited. The server was taken down between November 27 and 28 (UTC).

We downloaded all the files and set off to answer the following questions: 

  • Why are there two files named after LogoFAIL in a folder with a freshly-discovered UEFI bootkit?
  • Could it be that Bootkitty is exploiting a LogoFAIL vulnerability to deploy its malicious payloads?

A closer look at the BMP files

Given that the articles from ESET and from humzak711 already covered the bootkit (.efi) and rootkit files (.ko and .so), we decided to focus on the BMP files. We quickly discarded the logofail_fake.bmp file, after a cursory analysis showed nothing unusual in its header and content, plus all the image viewers we tested were able to open and display this file without any problems.

Our attention instead focused on the logofail.bmp file, a 16MB file that revealed some unusual patterns just by looking at the hexdump:

Hexdump of logofail.bmp
Hexdump of logfail.bmp

This file looked suspicious for several reasons:

  1. The width and the height of BMP are 0xfffffd00 (-768) and 0x0, respectively.
  2. A big chunk in the center of the file contains a repeated pattern.
  3. A shellcode seems to be embedded at the end of the image.
  4. Some strings resemble the same certificate metadata information present in bootkit.efi.

We decided to set aside the first two suspicions  (we will come back to this later in the article) and  took a deep dive into the shellcode and the certificate strings as they appeared to be the more promising venues.

UEFI Shellcode

We loaded logofail.bmp in IDA and disassembled it from offset 0x1010186, where the 90 90 90.. in the hexdump indicated the presence of code (NOP sled). This revealed the following valid assembly code:

Shellcode disassembled from the logofail.bmp file
Shellcode disassembled from the logofail.bmp file

After checking all the references contained in the shellcode, we determined that the call [rax + 58h] at 0x10101FA corresponds to:

gRT->SetVariable(L"MokList", &MokDatabaseGuid, EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE, 0x3b3, &Data);

This means that the shellcode is setting the MokList variable with some rogue content (pointed by the Data variable above).

After dumping Data, we find that it matches the structure of the EFI_SIGNATURE_LIST, which is used in UEFI to store certificates and hashes (it’s usually found in the context of UEFI Secure Boot components). After parsing this structure and extracting the certificate it contained, we found that it matches the certificate extracted from WinCertificate data of bootkit.efi file found by ESET!

the certificate reported by ESET and found in bootkit.efi
the certificate reported by ESET and found in bootkit.efi
certificate extracted from logofail.bmp.
certificate extracted from logofail.bmp.

The variable MokList is set with a rogue certificate by the shellcode because this variable is used during the boot process (by shim) to verify the second stage bootloader, which exactly corresponds to bootkit.efi in the case of Bootkitty. 

In other words, the shellcode will be executed through this sequence of steps:

Steps 1-6 – Preparing the boot environment for the further exploitation

Steps 5-7 –Exploiting LogoFAIL vulnerability 

Steps 8-9 – Deploying malicious bootloader and replacing the boot logo

This version of the Bootkitty will pass the verification process (at Step 3) by design.

Diagram showing how Bootkitty passes the verification process
How this version of Bootkitty passes the verification process

LogoFAIL as the first step of the infection chain?

To summarize, we’ve identified a shellcode designed to  tamper with the contents of MokList, ensuring the bootkit is trusted by shim. However, one critical question remains: how is this shellcode executed in the first place? 

Going back to the filename of the image,  logofail.bmp is a clear reference to our LogoFAIL research that made international headlines last December.  In addition, the width and the height of the BMP file reminded us clearly of BRLY-LOGOFAIL-2023-002, one of the vulnerabilities we reported last year as part of our LogoFAIL research.  (See Insyde advisory).

This vulnerability affects the BmpDecoderDxe module shipped within Insyde-based UEFI firmware. As the image below shows, the root cause of BRLY-LOGOFAIL-2023-002 is that when PixelHeight is 0, the variable Blt will be initialized from &BltBuffer[PixelWidth * (-1)], meaning that Blt will point PixelWidth * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL) bytes before BltBuffer (which will be equal to PixelWidth * 4). Since Blt is then used as a target for a write (Blt->Red = BmpColorMap..) which depends on attacker-controlled data, the attacker can achieve an arbitrary write primitive from this vulnerability.

Image shows RLE8ToBlt() function pseudocode
RLE8ToBlt() function pseudocode

Given all the above, we just decided to run the malicious BMP file with the harness that we created during our LogoFAIL research and we got a crash!

After a quick investigation, we realise that our advisory only describes the root cause of the vulnerability and a simple trigger that allows an attacker to control the arbitrary write location below the BltBuffer. However, it is more difficult to achieve code execution this way, as the interesting data to overwrite, such as UEFI modules code and data, will be located above BltBuffer, which is itself located in the UEFI heap. 

In order to obtain a controllable positive offset relative to BltBuffer, the exploit repeatedly enters the block where SecondByte == 2. After the last execution of this block, the variable Blt will point outside BltBuffer, exactly at BltBuffer + 0x6b0e20c. By following the dynamic traces, we observe that the Blt variable is used for an arbitrary write just a few lines later (at lines 77-79). The written data depends on the BmpColorMap. When this block is entered, SecondByte is 3, so the inner loop will be executed three times, writing the values ff e2 90, 45 46 47 and 0 2 ff.

While the last two sets of writes appear to be just random data, disassembling the first three bytes reveals valid X86 code:

$ echo -ne "\xff\xe2\x90" | ndisasm -b64 -
00000000  FFE2              jmp rdx
00000002  90                nop

At this stage, we believe this is more than just a coincidence: BltBuffer + 0x6b0e20c must point to a memory region containing code and so the exploit is overwriting legitimate code with the trampoline instruction jmp rdx there. After considering where this memory region could be located, we made a key observation that made everything come together: the ultimate goal of the exploit is to jump to the shellcode stored in the BMP. Therefore, the jmp rdx trampoline must be placed at a point in the execution where rdx points to the shellcode. So we can assume that the code of the RLE8ToBlt function itself should be selected for such a patch.

As a side note, we pondered one key question here: how did the attackers manage to predict the offset relative to the BltBuffer (located in the UEFI heap) to get directly to the chosen location in the code? In our experience, we have repeatedly found that it is not difficult: the location of the buffer in the heap will be the same from boot to boot (with the same boot configuration). Just like the base address of the BmpDecoderDxe module.

As one possible option, the attackers could obtain the base address of the BmpDecoderDxe module via the EFI_BMP_DECODER_PROTOCOL protocol (e.g. by calling LocateProtocol() from UEFIShell), so they will obtain the address of the protocol interface located inside the module.

At the same time, they could scan the physical memory for the contents of the BMP file (since most data remains in physical memory after boot, and it can be scanned even in the early stages of the operating system's work!)

Back to the code, by looking at the assembly code where the write happens, we can see that after the loop is completed, rdx (which in source code is the RLE8Image) will actually point to the shellcode! So if the jmp rdx patch is written at that point, then the execution is correctly diverted to the shellcode.

 Assembly code found in to the BMP decoder, where the write happens
Assembly code found in to the BMP decoder, where the write happens

This finding is further corroborated by another fact: the first instructions executed by the shellcode are some "strange-looking” mov instructions (shown in the next screenshot). At this point r8 will hold Blt, which is the code address where the three writes just happened. The mov constants must then be instructions and by disassembling them, we see they match exactly the instructions that were just overwritten, which are present in the original BmpDecoder module (shown at offset 0x824 in the listing above).

Mov instructions in the shellcode that restore original code
Mov instructions in the shellcode that restore original code

In other words, the shellcode restores the original instructions. This operation ensures that the BmpDecoder code is restored to its original state, so that it will work correctly if executed  again after the exploit landed. Moreover, it also hides the exploit activity as it effectively clears all the hooking traces of the bootkit for BmpDecoderDxe code. 

The last clue that links the shellcode with the BmpDecoderDxe file is that the shellcode epilogue matches the epilogue of the vulnerable function:

Shellcode epilogue
Shellcode epilogue
Vulnerable function epilogue
Vulnerable function epilogue

This artifact can also be easily explained: since the execution is diverted to the shellcode with a jump instruction, a matching epilogue ensures that the shellcode can safely and directly return to the caller of the vulnerable function. 

Affected devices

It’s very important to note that the patch developed by Insyde Software (you can read our full analysis of LogoFAIL patches in this research report) is effective at stopping the exploitation. This patch correctly adds multiple checks for the size of BltBuffer and the BMP Height and Width so that the vulnerability is removed. 

However, we still need to understand if we can find unpatched devices where the exploit chain could work. To answer this question with certainty, we would need to run the exploit on each device to verify whether BltBuffer + 0x6b0e20c points to the expected address in the BmpDecoderDxe module, and this is nearly impossible without having access to the actual device. 

However, there is another offset hardcoded in the shellcode (0x748) which is used to calculate the address of the EFI_SYSTEM_TABLE structure.

seg000:0000000001010192             mov     dword ptr [r8-0Ch], 0EBC2FF48h
seg000:000000000101019A             mov     dword ptr [r8-8], 2B60F36h
seg000:00000000010101A2             mov     dword ptr [r8-4], 2568D48h
seg000:00000000010101AA             mov     r15, [r8+748h] ; gST
...
seg000:00000000010101CA             mov     rax, [r15+58h] ; gST->RuntimeServices
...
seg000:00000000010101FA             call    qword ptr [rax+58h] ; gRT->SetVariable

In summary, from the previous code, we can see that [r8 + 748h] must contain a pointer to the gST so SetVariable will be properly resolved. Based on this fact, we searched in our dataset of firmware images for all those BmpDecoderDxe modules where the code stored at 0x748 before the &gST exactly matches the code expected by the shellcode (48FFC2EB360FB602488D5602). 

This search resulted in two modules where this constraint is satisfied: 

Data section of the module compatible with the Bootkitty exploit
Data section of the module compatible with the Bootkitty exploit
Code of the module compatible with the Bootkitty exploit
Code of the module compatible with the Bootkitty exploit

These two modules were distributed in hundreds of firmware images for Acer, HP, Fujitsu and Lenovo devices. The full list of potentially affected devices was still quite long, so we wanted to find any other clues that could help us shorten it. 

To answer this question, we loaded the bootkit.efi file in IDA and immediately noticed that the main bootkit function references variable names and paths that are very familiar to us during our LogoFAIL research. These strings were found in Lenovo devices based on Insyde that were affected by LogoFAIL (more information can be found in the advisory BRLY-2023-006 under the section “Vulnerability description (Insyde firmware)”).

Image showing call to GetVariable guards the main bootkit functionality
Strings found in Lenovo devices based on Insyde that were affected by LogoFAIL
Screenshots from bootkit.efi highlighting the references to the LogoFAIL advisory BRLY-2023-006.
Screenshot from bootkit.efi highlighting the references to the LogoFAIL advisory BRLY-2023-006.

This added some new findings about Bootkitty that were not present in previous publications. In particular we can see two custom NVRAM variables name and an ESP path being referenced:

  1. LBLDVC which contains, amongst other things, the CRC32 of the logo
  2. LBLDESP contains instead other meta information about the logo, such as its resolution and the format
  3. EFI\lenovo\logo\mylogo_1920x1080.bmp which is the file containing the logo

From the second screenshot we can instead learn that after execution the bootkit will:

  1. Overwrite the variables LBLDVC and LBLDESP with new content
  2. Delete the logo stored on the ESP (which at this point is still the malicious BMP) and replace it with a new logo.
The logo that will be replaced by the bootkit and displayed on infected machines.
The logo that will be replaced by the bootkit and displayed on infected machines.

This new finding restricts the intended bootkit target device to Lenovo. We queried our dataset again, to find all devices that use these logo specific NVRAM variables and that contain a BmpDecoderDxe module matching the bootkit constraints. This query results in the following list of devices:

 

Latest version matching
bootkit constraints

Latest version
released by Lenovo

Device
Name

Version
number

Release
Date

Latest
Version?

Version
number

Release
Date

Vulnerable to
BRLY-LOGOFAIL-2023-002 ?

ideacentre-aio-3-24irh9

O6AKT1DA

8/9/2024

YES

O6AKT1DA

8/9/2024

YES

ideapad-1-14iau7

JKCN42WW

10/10/2023

NO

JKCN48WW

10/18/2024

NO

ideapad-1-15iru7

MCCN29WW

11/8/2024

YES

MCCN29WW

11/8/2024

YES

ideapad-5-14ial7

JLCN36WW

10/23/2023

NO

JLCN40WW

10/21/2024

NO

ideapad-5-15ial7

JBCN32WW

11/7/2023

NO

JBCN36WW

8/9/2024

NO

ideapad-5-pro-16iah7

J5CN33WW

11/16/2023

NO

J5CN37WW

10/15/2024

NO

ideapad-gaming-3-15iah7

JMCN44WW

10/25/2023

NO

JMCN48WW

10/18/2024

NO

ideapad-pro-5-14irh8

LJCN28WW

12/22/2023

NO

LJCN35WW

9/26/2024

NO

ideapad-pro-5-16irh8

KZCN46WW

11/8/2024

YES

KZCN46WW

11/8/2024

YES

ideapad-slim-3-14iah8

LTCN34WW

8/21/2024

YES

LTCN34WW

8/21/2024

YES

ideapad-slim-5-14iah8

LACN29WW

10/12/2023

NO

LACN37WW

11/7/2024

NO

legion-5-15iah7

J2CN56WW

12/7/2023

NO

J2CN57WW

4/23/2024

NO

legion-7-16iax7

K1CN48WW

8/12/2024

YES

K1CN48WW

8/12/2024

YES

legion-9-16irx8

MHCN37WW

11/16/2023

NO

MHME43WW

7/29/2024

NO

legion-9-16irx9

NXCN19WW

7/17/2024

YES

NXCN19WW

7/17/2024

YES

legion-pro-5-16irx8

KWCN42WW

11/15/2023

NO

KWCN46WW

8/2/2024

NO

legion-s7-16irh8

M0CN34WW

11/1/2023

NO

M0CN39WW

10/21/2024

NO

legion-slim-5-16irh8

M2CN29WW

11/7/2023

NO

M2CN35WW

11/8/2024

NO

lenovo-slim-7-14irp8

LGCN27WW

2/2/2024

NO

LGCN32WW

11/5/2024

NO

lenovo-slim-9-14iap7

J3CN54WW

10/26/2023

NO

J3CN57WW

7/24/2024

NO

lenovo-slim-pro-9-14irp8

MBCN32WW

8/30/2024

YES

MBCN32WW

8/30/2024

YES

lenovo-slim-pro-9-16irp8

KVCN37WW

10/23/2023

NO

KVCN42WW

10/14/2024

NO

lenovo-v14-g4-ian

KUCN32WW

12/7/2023

NO

KUCN37WW

8/20/2024

NO

loq-15iax9e

Q8CN12WW

9/10/2024

YES

Q8CN12WW

9/10/2024

YES

loq-15iax9

NECN23WW

12/4/2023

NO

NECN39WW

10/14/2024

NO

loq-15iax9i

NFCN21WW

12/1/2023

NO

NFCN39WW

10/21/2024

NO

loq-15irh8

LZCN33WW

12/27/2023

NO

LZCN39WW

11/19/2024

NO

slim-7-14iap7

JHCN33WW

11/27/2023

NO

JHCN37WW

8/22/2024

NO

slim-7-16iah7

KMCN19WW

11/16/2023

NO

KMCN23WW

10/15/2024

NO

slim-7-carbon-13iap7

K2CN38WW

11/16/2023

NO

K2CN41WW

6/7/2024

NO

slim-7-carbon-13irp8

LDCN19WW

6/7/2024

YES

LDCN19WW

6/7/2024

YES

slim-7-prox-14iah7

HMCN47WW

10/30/2023

NO

HMCN50WW

8/13/2024

NO

thinkbook-14-g4-plus-iap

HYCN47WW

10/12/2023

NO

HYCN51WW

11/1/2024

NO

thinkbook-16p-g4-irh

LRCN42WW

1/8/2024

NO

LRCN45WW

8/12/2024

NO

thinkbook-plus-g3-iap

K6CN33WW

10/30/2023

NO

K6CN37WW

10/31/2024

NO

thinkbook-plus-g4-iru

LUCN35WW

10/23/2023

NO

LUCN42WW

10/10/2024

NO

yoga-7-14ial7

J1CN41WW

12/11/2023

NO

J1CN46WW

11/21/2024

NO

yoga-7-14irl8

LHCN21WW

12/29/2023

NO

LHCN26WW

11/18/2024

NO

yoga-9-14iap7

HNCN46WW

12/8/2023

NO

HNCN50WW

8/20/2024

NO

yoga-9-14irp8

L4CN23WW

6/28/2024

NO

L4CN24WW

11/14/2024

YES

yoga-aio-9-32irh8

O62KT24A

8/8/2024

YES

O62KT24A

8/8/2024

YES

yoga-book-9-13iru8

KXCN35WW

10/30/2023

NO

KXME39WW

10/17/2024

NO

yoga-pro-7-14irh8

LWCN25WW

11/27/2023

NO

LWCN30WW

11/19/2024

NO

yoga-slim-6-14iap8

KTCN40WW

1/18/2024

NO

KTCN44WW

11/4/2024

NO

yoga-slim-6-14irh8

N1CN07WW

1/3/2024

NO

N1CN13WW

11/4/2024

NO

yoga-slim-7-pro-14iah7

KRCN20WW

2/8/2024

NO

KRCN24WW

9/4/2024

NO

In the left column, we report the version details of devices that meet the bootkit constraint and use logo-related variables. The majority of firmware images don’t correspond to the latest version released by Lenovo. As we can see in the right column, those latest versions are not vulnerable anymore to LogoFAIL, so on updated devices the bootkit would not work. 

There are however 10 devices for which the latest version affected by the bootkit is the same as the latest version released by Lenovo. In other words, these devices are still vulnerable to LogoFAIL and the bootkit would likely be functional there.

Detection

Customers using the Binarly Transparency Platform automatically receive code-guided detection to proactively mitigate both the LogoFAIL vulnerabilities and malicious components of Bootkitty.

Binarly Transparency platform scanning for Bootkitty

It’s been more than a year since we first sounded the alarm about LogoFAIL and yet, many affected parties remain vulnerable to one or more variants of the LogoFAIL vulnerabilities. Bootkitty serves as a stark reminder of the consequences of when these vulnerabilities are not adequately addressed or when fixes are not properly deployed to devices in the field.

What's lurking in your firmware?