BlackLotus UEFI bootkit. Part 1

Hello, dear readers! Today I want to share with you my experience of studying BlackLotus UEFI bootkitIn this study we will examine the following topics:

  1. Preparing the test bench.

  2. Launch of CVE-2022-21894 (baton drop).

  3. Compiling the payload and components to execute it.

  4. Adding a certificate to the MOK database.

  5. Reading and writing files in the Windows10 operating system from the NTFS file system via grub.elf.

Let's dive into these interesting topics and understand how one of the modern security threats functions.

1. Preparing the test bench.

Composition of the stand:

  1. Windows 10×64 – Researcher's Workplace.

  2. VMware Workstation – virtualization platform (Version 17.0.0 build-20800274).

  3. VMware Workstation Manager – software for providing access to virtual machines on VMware Workstation.

  4. Windows 10×64-BatonDrop – a virtual machine prepared for research (Windows 10 Pro 21H2 19044.1288). The system settings were selected as standard.

1.1 Attempted to launch CVE-2022-21894(BatonDrop).

First, let's try to run CVE-2022-21894. In this study, actions will be carried out with the file poc_amd64_19041.iso, which we will download from the link https://github.com/Wack0/CVE-2022-21894/tree/main/pocs .

First, you need to mount the “EFI System Partition”, to do this, you need to do the following steps.

Next, you need to copy the files from poc_amd64_19041.iso to the system partition. For this, we use Total Commander (Total Commander must be launched with administrator rights).

Let's make a copy of the original BCD file and call it BCDR (We'll need it later).

Let's create a snapshot for the Windows 10 x64-BatonDrop virtual machine, as we will need to return to it in the future.

Import bcd file from poc_amd64_19041.iso using bcdedit.exe.

After this, we turn off the Windows 10 x64-BatonDrop virtual machine and when turning it on, an error occurs.

2. Debugging CVE-2022-21894 (baton drop)

After the error 0xc0000010 appears, let's refer to the article BlackLotus UEFI bootkit: Myth confirmed (welivesecurity.com)to understand which files are loaded and in what order. This information is important for determining what exactly needs to be debugged. Only by going through these files can you understand where the 0xc0000010 error is triggered.

Comment: When debugging Windows 10×64-BatonDrop, you must use a single core.

2.1 Setting up debugging

First, let's figure out what exactly we're going to configure and how to do it. We studied the MSDN documents on the following topics:

Boot Parameters to Enable Debugging
BCDEdit /bootdebug
BCDEdit /dbgsettings

Now let's proceed to setting up debugging in the Windows 10 x64-BatonDrop system. Returning to snapshot good.

Run the commands as administrator in the Windows 10×64-BatonDrop virtual machine, then turn off the Windows 10×64-BatonDrop virtual machine.

Adding Serial Port for Windows 10×64-BatonDrop.

Then we will change the file C:\Users\user\Documents\Virtual Machines\Windows 10×64-BatonDrop\Windows 10×64-BatonDrop.vmx.
Let's delete the lines.

Rename serial1 to serial0 and save the Windows 10×64-BatonDrop.vmx file.

While on the Windows 10×64 explorer workstation, let's set up Windbg.

2.2 Debugging the bootmgfw.elf file.

After setting up debugging, we launch the Windows 10×64-BatonDrop virtual machine and in Windbg (Windows 10×64) we see the connection.

Then the system interrupt int 3 is triggered.

After the system interrupt is triggered, we look for downloaded files.

We have a bootmgfw file (we'll start working with it), which has Imagebase 0x0000000010000000.

Now we go back to snapshot good and import the bcd file.

Turn off the Windows 10×64-BatonDrop virtual machine and edit the file C:\Users\user\Documents\Virtual Machines\Windows 10×64-BatonDrop\Windows 10×64-BatonDrop.vmx to debug with IDA Pro.

We turn on the Windows 10×64-BatonDrop virtual machine and look at the file C:\Users\user\Documents\Virtual Machines\Windows 10×64-BatonDrop\vmware.log, since we need to find out the port for connection using IDA Pro.

After completing the steps, open IDA Pro and load the bootmgfw.efi file (The bootmgfw.efi file is taken from E:\EFI\Microsoft\Boot\bootmgfw.efi).

Add bootmgfw.pdb file to IDA PRO.

Comment: The bootmgfw.pdb file can be downloaded using the link http://msdl.microsoft.com/download/symbols/bootmgfw.pdb/C94B898929165E26611E4791B87F6B1B2/bootmgfw.pdbwhere C94B898929165E26611E4791B87F6B1B2.

Or you can use the pdbdownload.py script.

pdbdownload.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
import os
import re
import binascii
import pefile
import struct

def to_pdb(filename):
    return re.sub(r'.[^.]+$', '.pdb', os.path.basename(filename))

def build_url(filename):
    guid = ""
    pdb = to_pdb(filename)
    pe = pefile.PE(filename)

    for dbg in pe.DIRECTORY_ENTRY_DEBUG:
        if dbg.struct.Type == 2:  # IMAGE_DEBUG_TYPE_CODEVIEW
            guid = '%s%s%s%s%s%s%s' % (
                binascii.hexlify(struct.pack('>I', dbg.entry.Signature_Data1)).decode('ascii').upper(),
                binascii.hexlify(struct.pack('>H', dbg.entry.Signature_Data2)).decode('ascii').upper(),
                binascii.hexlify(struct.pack('>H', dbg.entry.Signature_Data3)).decode('ascii').upper(),
                binascii.hexlify(dbg.entry.Signature_Data4).decode('ascii').upper() if isinstance(dbg.entry.Signature_Data4, bytes) else struct.pack('H', dbg.entry.Signature_Data4).hex().upper(),
                binascii.hexlify(dbg.entry.Signature_Data5).decode('ascii').upper() if isinstance(dbg.entry.Signature_Data5, bytes) else struct.pack('H', dbg.entry.Signature_Data5).hex().upper(),
                binascii.hexlify(dbg.entry.Signature_Data6).decode('ascii').upper() if isinstance(dbg.entry.Signature_Data6, bytes) else struct.pack('I', dbg.entry.Signature_Data6).hex().upper(),
                dbg.entry.Age)

            break

    return 'http://msdl.microsoft.com/download/symbols/%s/%s/%s' % (pdb, guid, pdb)

def main():
    if len(sys.argv) < 2:
        print("Usage: %s /tmp/notepad.exe /tmp/kernel32.dll" % (sys.argv[0]))
        return

    for filename in sys.argv[1:]:
        downurl = build_url(filename)
        destfile = os.path.dirname(os.path.abspath(filename)) + "/" + to_pdb(filename)

        print("Saving %s to %s" % (downurl, destfile))
        os.system("curl -L %s -o %s" % (downurl, destfile))

if __name__ == '__main__':
    main()

Change Imagebase to 0x0000000010000000 in IDA Pro.

We put a breakpoint at the beginning of the BmMain function.

We set up debugging in IDA Pro and start debugging.

In the BmMain function we can see the breakpoint being triggered.

2.3 Transition from bootmgfw.elf file to bootmgr.elf file.

Now we need to move from the bootmgfw.elf file to the bootmgr.elf file, but there is a difficulty: it is not clear how to do this. It is not clear which functions to examine in the bootmgfw.elf file, and studying and debugging everything in a row is ineffective. In such a situation, Google becomes our best assistant. During the search for information, several useful projects were found:

  1. ReactOS: boot/freeldr/freeldr/bootmgr.c File Reference: This project has detailed documentation on structures, functions, their arguments and more for Windows OS.

  2. GitHub – backengineering/Voyager: A Hyper-V Hacking Framework For Windows 10 x64 (AMD & Intel): In this project, the most valuable thing is the photo that shows the functions BlImgLoadPEImageEx, BlImgLoadPEImage, BlImgAllocateImageBuffer, ImgArchStartBootApplication.

These findings greatly simplify the task and help to understand where to start the research.

Let's start by restoring the readability of the BlImgLoadPEImageEx function and get the following. Comment: These two projects contain a description of the BlImgLoadPEImageEx function.

Let's put a breakpoint on the BlImgLoadPEImageEx -> ImgpOpenFile function to analyze in detail the arguments passed to it.

We start debugging in IDA Pro and stop at the function BlImgLoadPEImageEx -> ImgpOpenFile. However, we cannot view the contents of the arguments (registers or memory), because IDA Pro cannot display this memory – it is outside the scope of IDA Pro.

To solve this problem, let's write a read_memory.py script for IDA Pro.

read_memory.py
import idc
import sys

def read_memory_debug(ea, size):
    memory_data = []
    for i in range(size):
        byte = idc.DbgByte(ea + i)
        if byte == 0xFF and not idc.isLoaded(ea + i):
            print("Не удалось прочитать байт по адресу 0x{0:X}".format(ea + i))
            break
        memory_data.append(byte)
    return memory_data

def main():
    if len(sys.argv) != 3:
        print("Использование: script.py <адрес> <размер>")
        return

    try:
        start_ea = int(sys.argv[1], 16)  # преобразование адреса из строки в число
        size = int(sys.argv[2])
    except ValueError:
        print("Ошибка: убедитесь, что адрес и размер введены корректно.")
        return

    # Чтение и вывод данных
    offset = 0
    for i in range(0, size, 16):
        start_ea_offset = start_ea + offset
        data = read_memory_debug(start_ea_offset, 16)
        if data:
            hex_data = " ".join("{:02X}".format(byte) for byte in data)
            str_data = "".join(chr(byte) if 32 <= byte <= 126 else '.' for byte in data)
            print("0x{0:X} | ".format(start_ea_offset) + "{0:<47} | ".format(hex_data) + "{0}".format(str_data))
        offset += 16

if __name__ == "__main__":
    main()

Let's use the read_memory.py script. At the first breakpoint (BlImgLoadPEImageEx -> ImgpOpenFile) we see the opening of the BOOTX64.ELF.MUI file. Since we are not interested in this file, we simply press F9 and continue execution.

At the second breakpoint activation (BlImgLoadPEImageEx -> ImgpOpenFile) we see the opening of the bootmgr.elf file, this is the file we need.

Now let's move on to studying the function BlImgLoadPEImageEx -> ImgpLoadPEImage, open the link https://doxygen.reactos.org/d5/de2/boot_2environ_2lib_2misc_2image_8c.html#ac08b69e9461557cbbc6aa3e366856cd1
And after examining the code in the link, we come to the conclusion that we need to get the VirtualAddress.

In IDA Pro, set a breakpoint on the RtlImageNtHeaderEx function (BlImgLoadPEImageEx -> ImgpLoadPEImage -> RtlImageNtHeaderEx).

We start debugging in IDA Pro, stop at the function BlImgLoadPEImageEx -> ImgpLoadPEImage -> RtlImageNtHeaderEx and we see the beginning of the bootmgr.elf file, which begins with 'MZ'.

Next, we will move on to studying the ImgArchStartBootApplication function and restore its readability. In the project ReactOS: boot/freeldr/freeldr/bootmgr.c File Reference Description of this function was not found, however in GitHub – backengineering/Voyager: A Hyper-V Hacking Framework For Windows 10 x64 (AMD & Intel) managed to find a description of its arguments. This means that we will have to debug to understand how the ImgArchStartBootApplication function works. After debugging, we can conclude that the ImgArchStartBootApplication function works as follows.

First we go through the functions.

At the end of the Archpx64TransferTo64BitApplicationAsm function, after retfq, some more debugging is needed.

Ultimately, this will lead us to call rax, which, when called, transfers control to the BmMain function in the bootmgr.elf file.

2.4 Transition from bootmgr.elf file to hvloader.efi file.

After we found Imagebase 0x0000000000613000 of bootmgr.elf file and understood that we need to debug Archpx64TransferTo64BitApplicationAsm function. Transition from bootmgr.elf file to hvloader.efi file should pass without difficulties, since the principle of transition is the same as transition from bootmgfw.elf file to bootmgr.elf file.

Let's open IDA Pro and load the bootmgr.elf file (The bootmgr.elf file is taken from E:\EFI\ minram\bootmgr.elf).

Add bootmgr.pdb file to IDA Pro (Download link http://msdl.microsoft.com/download/symbols/bootmgr.pdb/41D1F70CC2B018B3DA16F75E1BD2192E2/bootmgr.pdb).

Change Imagebase to 0x0000000000613000 in IDA Pro.

Set a breakpoint at the end of the Archpx64TransferTo64BitApplicationAsm function on retfq.

We start debugging in IDA Pro, stop at the end of the Archpx64TransferTo64BitApplicationAsm function on retfq and after retfq we debug a little more.

And this led us to call rax, which, when called, transfers control to the HvlMain function in the hvloader.efi file.

Now let's calculate the Imagebase of the hvloader.efi file, to do this we subtract the number 0x24B8 from 0x106C84B8 and get Imagebase 0x106C6000.

The number 0x24B8 can be calculated as follows:
• Load the hvloader.efi file into IDA PRO by default Imagebase will be 0x140000000.

• Go to the HvlMain function located at 0x1400024B8.

• Subtract the number 0x140000000 from 0x1400024B8 to get 0x24B8.

2.5 Transition from hvloader.efi file to mcupdate_….dll file.

After we have found Imagebase 0x106C6000 of the hvloader.efi file, let's move on to studying the transition from the hvloader.efi file to the mcupdate_….dll file. To do this, let's turn to the description of the hvloader.efi file found on the Internet (https://hvinternals.blogspot.com/2015/10/hyper-v-debugging-for-beginners.html), and we will highlight one important function BtLoadUpdateDll, which deserves special attention. This function loads the file mcupdate_….dll. Now we will set a breakpoint on the function BtLoadUpdateDll to make sure that the transition to it occurs.

Let's open IDA Pro and load the hvloader.efi file (The hvloader.efi file is taken from E:\EFI\ maxram\hvloader.efi).

Add hvloader.pdb file to IDA Pro (Download link http://msdl.microsoft.com/download/symbols/hvloader.pdb/A927BBB6F50142E0A0D3D59C7C075C3F2/hvloader.pdb).

Change Imagebase to 0x106C6000 in IDA Pro.

Set a breakpoint on the BtLoadUpdateDll function.

We start debugging in IDA Pro and the breakpoint on the BtLoadUpdateDll function does not work, then let's set a breakpoint on test eax, eax(0x00000000106C8349).

We start debugging in IDA Pro and breakpoint on test eax, eax works.

Now we need to understand why the transition from 0x00000000106C834B to 0x00000000106C835D does not occur and we will start by analyzing the HvlpSLATPresent function.

After searching the description of the HvlpSLATPresent function on the Internet, it can be concluded that SLAT is a hardware-assisted virtualization technology that avoids the overhead associated with software-managed shadow page tables (https://ru.wikipedia.org/wiki/SLAT).

We can assume that SLAT technology is disabled in the Windows 10×64-BatonDrop virtual machine and we need to enable it. This link describes how to do this https://www.quora.com/How-can-I-enable-virtualization-in-VMware-Virtual-Machine. First, let's turn off the Windows 10×64-BatonDrop virtual machine. Then, let's turn on SLAT by following these steps and see what happens.

We start debugging in IDA Pro and the breakpoint on the BtLoadUpdateDll function is triggered.

Now just press F9 and see what happens. You can see CVE-2022-21894 (baton drop) being executed on the screen.

Let's continue studying the code to understand where the transition from the hvloader.efi file to the mcupdate_….dll file occurs.

To do this, we will study the BtLoadUpdateDll function on the website https://www.welivesecurity.com/2023/03/01/blacklotus-uefi-bootkit-myth-confirmed/ There is a description of this function BtLoadUpdateDll.

We are interested in lines 26 and 27, where the names ImageBase and AddressOfEntryPoint of the mcupdate_….dll file are specified. Let's set breakpoints on these lines (in IDA Pro these are lines 47 and 48).

We start debugging in IDA Pro and in line 47 we see the ImageBase file mcupdate_….dll

And in line 48 we can see AddressOfEntryPoint of mcupdate_….dll file. The picture shows the assembler code, because hex-reys does not display this information in line 48.

Now let's move on to the HvlpLoadMicrocode function, since AddressOfEntryPoint (imageEP) is passed to it.

In the HvlpLoadMicrocode function, at address 0x00000000106C8D42 (call rax), a transition to the mcupdate_….dll file occurs.

But we couldn't debug mcupdate_….dll, as we debugged hvloader.efi, bootmgr.elf, bootmgfw.elf. Because the mcupdate_….dll file has ASLR, since ImageBase changes every time the virtual machine starts.

In this case, to debug mcupdate_….dll, we need to do the following steps.

We make a snapshot before moving to mcupdate_….dll.

Find out ImageBase 0xFFFFF800D914D000.

We fail in call rax in the HvlpLoadMicrocode function.

We take a snapshot after we failed in the call rax in the HvlpLoadMicrocode function.

Next we complete the process in IDA Pro.

Now back to snapshot.

Open mcupdate…dll in IDA Pro and change ImageBase (in our case it is 0xFFFFF800D914D000).

We set a breakpoint on the DriverEntry function in the mcupdate…dll file and start debugging in IDA Pro.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *