BlackLotus UEFI bootkit. Часть 1

Приветствую вас, дорогие читатели! Сегодня я хочу поделиться с вами своим опытом изучения BlackLotus UEFI bootkit. В этом исследование разберем следующие темы:

  1. Подготовка тестового стенда.

  2. Запуск CVE-2022–21894 (baton drop).

  3. Компиляция payload и компонентов для его выполнения.

  4. Добавление сертификата в базу данных MOK.

  5. Чтение и запись файлов в операционной системе Windows10 из файловой системы NTFS через grub.elf.

Давайте углубимся в эти интересные темы и разберемся, как функционирует одна из современных угроз безопасности.

1. Подготовка тестового стенда.

c9de7bdbfc07dfceed7370d581491996.png

Состав стенда:

  1. Windows 10×64 — рабочее место исследователя.

  2. VMware Workstation — платформа для виртуализации (Версия 17.0.0 build-20800274).

  3. VMware Workstation Менеджер — ПО для предоставления доступа к виртуальным машинам на VMware Workstation.

  4. Windows 10×64-BatonDrop — виртуальная машина, подготовленная для проведения исследований (Версия Windows 10 Pro 21H2 19044.1288). Настройки системы были выбраны стандартные.

    fb03a1271d3ef240cb19fe347bf01167.png

1.1 Попытка запуска CVE-2022–21894(BatonDrop).

Для начала попробуем запустить CVE-2022–21894. В этом исследование действия будут проводиться с файлом poc_amd64_19041.iso, который скачаем по ссылке https://github.com/Wack0/CVE-2022–21894/tree/main/pocs .

13b8ff02b7b782b28b7e380337dab0bc.png

Для начала необходимо примонтировать «Системный раздел EFI», для этого необходимо проделать следующие шаги.

688f506b9c5abb4f5c92070f8c437c86.png58bf0bcff3b31a6cf079ea23fda71e7b.png

Далее нужно скопировать файлы из poc_amd64_19041.iso в системный раздел. Для используем Total Commander (Total Commander нужно запускать с правами администратора).

4f77ba4c7ea8ff0e2aa51952ae0fc900.png

Сделаем копию оригинального BCD файла и назовём ее BCDR (Понадобиться потом).

6e3a608285d82f0ecc202bbb751b16b2.png

Создадим snapshot для виртуальной машины Windows 10×64-BatonDrop, так как в будущем потребуется к нему вернуться.

56aec5e194230157297cea2b620ee001.png

Импортируем bcd файл из poc_amd64_19041.iso с помощью bcdedit.exe.

fe24d4e3794b6b01e658c443199f7f16.png

После этого выключаем виртуальную машину Windows 10×64-BatonDrop и при включении возникает ошибка.

5d778f2bfefd705edaa5d5ed92e98b3c.png

2. Отладка CVE-2022–21894 (baton drop)

После появления ошибки 0xc0000010, обратимся к статье BlackLotus UEFI bootkit: Myth confirmed (welivesecurity.com), чтобы понять, какие файлы загружаются и в каком порядке. Эта информация важна для определения того, что именно требуется отладить. Только пройдя по этим файлам можно понять, где срабатывает ошибка 0xc0000010.

7e1d8cf6254ba7814d824d499238a8be.png

Замечание: При отладке Windows 10×64-BatonDrop необходимо использовать одно ядро.

7acb4511ed08022bf06a8e1af3e53b25.png

2.1 Настройка отладки

Для начала разберёмся, что именно мы будем настраивать и как это сделать. Мы изучили документы MSDN по следующим темам:

• Boot Parameters to Enable Debugging
• BCDEdit /bootdebug
• BCDEdit /dbgsettings

Теперь приступим к настройке отладки в системе Windows 10×64-BatonDrop. Возвращаемся к snapshot good.

faa9700c3283af44bdc0118d9affbcd2.png

Выполняем команды от имени администратора в виртуальной машине Windows 10×64-BatonDrop, затем выключим виртуальную машину Windows 10×64-BatonDrop.

e9662dce8339b5419fc05ae452bd8853.png

Добавляем Serial Port для Windows 10×64-BatonDrop.

1192e65b8ca17554f2d2656b372e3373.png

Потом изменим файл C:\Users\user\Documents\Virtual Machines\Windows 10×64-BatonDrop\Windows 10×64-BatonDrop.vmx.
Удалим строки.

bc2c7d910330b892e97763768e9c3337.png

Переименуем serial1 на serial0 и сохраняем файл Windows 10×64-BatonDrop.vmx.

68453621c0804b930ffee5a0898e30eb.png

Находясь на рабочем месте исследователя Windows 10×64, настроим Windbg.

4e44f885e8595cc3df60553b39fd5090.png

2.2 Отладка файла bootmgfw.elf.

После настройке отладки запускаем виртуальную машину Windows 10×64-BatonDrop и в Windbg (Windows 10×64) видим подключение.

9670b7dee3842bd82de68c25bbc8d6c1.png

Дальше срабатывает системное прерывание int 3.

ee36d297eedb54e5211bd0edc769d80b.png

После срабатывания системного прерывания ищем загруженные файлы.

e58a96f5953d3ee563ba452a555eb81b.png

У нас есть файл bootmgfw (с ним и начнем работать), у которого Imagebase 0×0000000010000000.

6dc94a3546a9788bf1d440dd8d6009c2.png

Теперь возвращаемся к snapshot good и импортируем bcd file.

dd765b7812065ca818c85087bdcbdac5.png

Выключаем виртуальную машину Windows 10×64-BatonDrop и изменяем файл C:\Users\user\Documents\Virtual Machines\Windows 10×64-BatonDrop\Windows 10×64-BatonDrop.vmx, чтобы отладить с помощью IDA Pro.

b92798d17ea498a850f6714d550264a9.png

Включаем виртуальную машину Windows 10×64-BatonDrop и смотрим файл C:\Users\user\Documents\Virtual Machines\Windows 10×64-BatonDrop\vmware.log, так как нам необходимо узнать порт для подключения с помощью IDA Pro.

0422d8bf5aebaa3371b954c2f6d7725e.png

После проделанных действий открываем IDA Pro и загружаем файл bootmgfw.efi (Файл bootmgfw.efi берётся из E:\EFI\Microsoft\Boot\bootmgfw.efi).

30c782090337219c0426fde99166dc4c.png

Добавляем bootmgfw.pdb файл в IDA PRO.

d91b15161c76881f9e7cf6262f21ed5f.png

Замечание: Файл bootmgfw.pdb можно скачать, воспользовавшись ссылкой http://msdl.microsoft.com/download/symbols/bootmgfw.pdb/C94B898929165E26611E4791B87F6B1B2/bootmgfw.pdb, где C94B898929165E26611E4791B87F6B1B2.

d6ff6b2f8e954001df5435d342552c89.png

Или можно использовать скрипт pdbdownload.py.

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()

Изменяем Imagebase на 0×0000000010000000 в IDA Pro.

16fdc1998d8081647fe8a1456e3a649c.png

Ставим breakpoint на начало функции BmMain.

62b39810341620bce35b411b955a1778.png

Настраиваем отладку в IDA Pro и запускаем отладку.

fbb733e3ddff7f8d189d9fa14ee12517.png

В функции BmMain мы можем увидеть срабатывание breakpoint.

fb6bf3a4c97bc5ee303d42c17e91d9a4.png

2.3 Переход из файла bootmgfw.elf в файл bootmgr.elf.

Теперь нам необходимо перейти из файла bootmgfw.elf в файл bootmgr.elf, однако возникает сложность: непонятно, как это сделать. Не ясно, какие функции исследовать в файле bootmgfw.elf, а изучать и отлаживать всё подряд — неэффективно. В такой ситуации Google становится нашим лучшим помощником. Во время поиска информации было найдено несколько полезных проектов:

  1. ReactOS: boot/freeldr/freeldr/bootmgr.c File Reference: В этот проекте есть детальная документация по структурам, функциям, их аргументам и многому другому для ОС Windows.

  2. GitHub — backengineering/Voyager: A Hyper-V Hacking Framework For Windows 10×64 (AMD & Intel): В этом проекте наибольшую ценность представляет фотография, на которой указаны функции BlImgLoadPEImageEx, BlImgLoadPEImage, BlImgAllocateImageBuffer, ImgArchStartBootApplication.

    78b9f650ec54214b430fbca0b4fe6c6f.png

Эти находки значительно упрощают задачу и помогают понять с чего начать исследование.

Начнем с восстановления читабельности функции BlImgLoadPEImageEx и получим следующие. Замечание: В этих двух проектах есть описание функции BlImgLoadPEImageEx.

d9f937a779185b8953c6c27cf4d95ef1.png

Поставим breakpoint на функцию BlImgLoadPEImageEx → ImgpOpenFile, чтобы детально проанализировать передаваемые в неё аргументы.

86f42c52bfa0076b17a2be78a9edb806.png

Запускаем отладку в IDA Pro и останавливаемся на функции BlImgLoadPEImageEx → ImgpOpenFile. Однако мы не можем просмотреть содержимое аргументов (регистров или памяти), потому что IDA Pro не может отобразить эту память — она находится вне области видимости IDA Pro.

87c71d497be1cbb5424017f5e7d35a81.png

Для решения этой проблемы напишем скрипт read_memory.py для 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()

Воспользуемся скриптом read_memory.py. При первом срабатывании breakpoint (BlImgLoadPEImageEx → ImgpOpenFile) видим открытие файла BOOTX64.ELF.MUI. Поскольку этот файл нас не интересует, просто нажимаем F9 и продолжаем выполнение.

f5f05d5c4d4d203996d26f6248e720ae.png

При втором срабатывании breakpoint (BlImgLoadPEImageEx → ImgpOpenFile) видим открытие файла bootmgr.elf, это тот файл который нам нужен.

cb38b01a6ed4d7b535d99e810127e0f1.png

Теперь перейдем к изучению функции BlImgLoadPEImageEx → ImgpLoadPEImage, откроем ссылку https://doxygen.reactos.org/d5/de2/boot_2environ_2lib_2misc_2image_8c.html#ac08b69e9461557cbbc6aa3e366856cd1
И после исследования кода по ссылке, мы приходим к выводу, что нам нужно получить адрес VirtualAddress.

5fa4b1203844f251106600144252191e.png

В IDA Pro устанавливаем breakpoint на функцию RtlImageNtHeaderEx (BlImgLoadPEImageEx → ImgpLoadPEImage → RtlImageNtHeaderEx).

05e306f5ffee0651e76c14e204865cb4.png

Запускаем отладку в IDA Pro, останавливаемся на функции BlImgLoadPEImageEx → ImgpLoadPEImage → RtlImageNtHeaderEx и мы видим начало файла bootmgr.elf, которое начинается с 'MZ'.

c72b15cce20af393ab5ebd6bd3772c50.png

Дальше перейдём к изучению функции ImgArchStartBootApplication и восстановим её читабельность. В проекте ReactOS: boot/freeldr/freeldr/bootmgr.c File Reference описание данной функции не было найдено, однако в GitHub — backengineering/Voyager: A Hyper-V Hacking Framework For Windows 10×64 (AMD & Intel) удалось обнаружить описание её аргументов. Это означает, что нам придётся провести отладку, чтобы разобраться в работе функции ImgArchStartBootApplication. После отладки можно сделать вывод, что функция ImgArchStartBootApplication работает следующим образом.

Сначала мы проходим по функциям.

0777154fb1674fcdf41bdae91838c614.png

В конце функции Archpx64TransferTo64BitApplicationAsm, после retfq необходимо еще немного отладить.

e43c39e10325019a7ef619dfceb9cc9c.png

В итоге, это приведет нас к call rax, при вызове которого происходит передача управления на функцию BmMain в файле bootmgr.elf.

4d339f02014a20c30e2ba2c376f570a9.png

2.4 Переход из файла bootmgr.elf в файл hvloader.efi.

После того как мы нашли Imagebase 0×0000000000613000 файла bootmgr.elf и понимания, что нужно отлаживать функцию Archpx64TransferTo64BitApplicationAsm. Переход из файла bootmgr.elf в файл hvloader.efi должен пройти без затруднений, так как принцип перехода такой же как переход из файла bootmgfw.elf в файл bootmgr.elf.

Откроем IDA Pro и загрузим файл bootmgr.elf (Файл bootmgr.elf берётся из E:\EFI\ minram\bootmgr.elf).

0fcbb7ac7c32f9e00ff37f8e99651984.png

Добавляем bootmgr.pdb файл в IDA Pro (Ссылка для скачивания http://msdl.microsoft.com/download/symbols/bootmgr.pdb/41D1F70CC2B018B3DA16F75E1BD2192E2/bootmgr.pdb).

cc18d2c771849f759b9244ce4fad77d7.png

Изменяем Imagebase на 0×0000000000613000 в IDA Pro.

9a8821220a9f9d98fdb0fbd000a0055a.png

Устанавливаем breakpoint в конец функции Archpx64TransferTo64BitApplicationAsm на retfq.

8bbcf04a9dec6adedc8e360def8aa4fd.png

Запускаем отладку в IDA Pro, останавливаемся в конец функции Archpx64TransferTo64BitApplicationAsm на retfq и после retfq еще немного отлаживаем.

3f9276958db138ace8f1b2fd4e1c3eaf.png

И это нас привело к call rax, при вызове которого происходит передача управления на функцию HvlMain в файле hvloader.efi.

ab0451386a20d7a047d1dcd57390e43c.png

Теперь вычислим Imagebase файла hvloader.efi, для этого отнимем от 0×106C84B8 число 0×24B8 и получим Imagebase 0×106C6000.

Число 0×24B8 можно вычислить следующим образом:
• Загрузить файл hvloader.efi в IDA PRO по дефолту Imagebase будет 0×140000000.

3c40a154cc7d373d004a2406559c3113.png

• Перейти в функцию HvlMain расположенная по адресу 0×1400024B8.

768cce6a8cba44a95e8f8560695ebc93.png

• Отнять от 0×1400024B8 число 0×140000000 получим 0×24B8.

2.5 Переход из файла hvloader.efi в файл mcupdate_…dll.

После того как мы нашли Imagebase 0×106C6000 файла hvloader.efi, перейдем к изучению перехода из файла hvloader.efi в файл mcupdate_…dll. Для этого обратимся к описанию файла hvloader.efi, найденному в интернете (https://hvinternals.blogspot.com/2015/10/hyper-v-debugging-for-beginners.html), и выделим одну важную функцию BtLoadUpdateDll, на которую следует обратить особое внимание. Данная функция загружает файл mcupdate_…dll. Теперь установим breakpoint на функцию BtLoadUpdateDll, чтобы убедиться, происходит ли переход в неё.

Откроем IDA Pro и загрузим файл hvloader.efi (Файл hvloader.efi берётся из E:\EFI\ maxram\hvloader.efi).

c0856fe3242129806390b78a2da14f7f.png

Добавляем hvloader.pdb файл в IDA Pro (Ссылка для скачивания http://msdl.microsoft.com/download/symbols/hvloader.pdb/A927BBB6F50142E0A0D3D59C7C075C3F2/hvloader.pdb).

9cdfc420b428dab6a22d11cc3b8a89b4.png

Изменяем Imagebase на 0×106C6000 в IDA Pro.

34f515dac9a701ea65b195c1c4a5a08e.png

Устанавливаем breakpoint на функции BtLoadUpdateDll.

5749b29a0ec6a90eb3135341ac5b87fc.png

Запускаем отладку в IDA Pro и breakpoint на функции BtLoadUpdateDll не срабатывает, тогда давайте установим breakpoint на test eax, eax (0×00000000106C8349).

c3c82a3bd5b61c45c2ccc80edb214fac.png

Запускаем отладку в IDA Pro и breakpoint на test eax, eax срабатывает.

2ec8ff1bc884c11bcc90576dfc12bcf1.png

Теперь нам необходимо понять почему не происходит переход с 0×00000000106C834B на 0×00000000106C835D и начнем с разбора функции HvlpSLATPresent.

cd5bbf1532b6c9aad8702c2697cab886.png

После поиска описания функции HvlpSLATPresent в интернете, можно сделать вывод, что SLAT — это технология виртуализации с аппаратным обеспечением, которая позволяет избежать накладных расходов, связанных с управляемыми программным обеспечением таблицами теневых страниц (https://ru.wikipedia.org/wiki/SLAT).

Мы можем предположить, что технология SLAT отключена у виртуальной машины Windows 10×64-BatonDrop и нам необходимо ее включить. По данной ссылке есть описание как это можно сделать https://www.quora.com/How-can-I-enable-virtualization-in-VMware-Virtual-Machine. Сначала отключим виртуальную машину Windows 10×64-BatonDrop. Дальше включим SLAT, проделав следующие шаги и посмотрим, что произойдет.

9c928cb37ca638cd65ebc8cac173525b.png

Запускаем отладку в IDA Pro и breakpoint на функции BtLoadUpdateDll срабатывает.

65fb8edf0e8d566b477076affb85d171.png

Теперь просто нажмем F9 и посмотрим, что произойдет. На экране можно увидеть выполнение CVE-2022–21894 (baton drop).

d5cb60367e6c4e034f38f25d55cfad39.png

Продолжим изучение кода, чтобы понять, где происходит переход из файла hvloader.efi в файл mcupdate_…dll.

Для этого изучим функцию BtLoadUpdateDll, на сайте https://www.welivesecurity.com/2023/03/01/blacklotus-uefi-bootkit-myth-confirmed/ есть описание данной функции BtLoadUpdateDll.

912d1bb6422e8db20cce5c0eebc0026b.png

Нас интересуют строки 26 и 27, где указаны названия ImageBase и AddressOfEntryPoint файла mcupdate_…dll. Установим breakpoints на этих строках (В IDA Pro это строки 47 и 48).

162140e0745881104f718e30859e83eb.png

Запускаем отладку в IDA Pro и в строчке 47 мы видим ImageBase файла mcupdate_…dll

bb605e00871ba1a20be4f3f604adbc3e.png

А в строке 48 мы можем увидеть AddressOfEntryPoint файла mcupdate_…dll. На фото изображен ассемблерный код, так как hex-reys не отображает эту информацию в строке 48.

c5eb424a73e99a56d0592641861a3a04.png

Теперь перейдем в функцию HvlpLoadMicrocode, так как в нее передается AddressOfEntryPoint (imageEP).

3af5821a1e712128920ca7c68503c047.png

В функции HvlpLoadMicrocode по адресу 0×00000000106C8D42(call rax) происходит переход в файл mcupdate_…dll.

2fffc38a102606666e8191d4c04a9063.png

Но у нас не получилось отладить mcupdate_…dll, как мы отлаживали hvloader.efi, bootmgr.elf, bootmgfw.elf. Потому что у файла mcupdate_…dll есть ASLR, так как при каждом запуске виртуальной машины меняется ImageBase.

71a68a35056f636bf9c6c1fa942e30b0.png

В таком случае, что бы отладить mcupdate_…dll, нам нужно сделать следующие шаги.

Делаем snapshot до перехода в mcupdate_…dll.

637376752f628886993c3a3cf85c276d.png

Узнаем ImageBase 0xFFFFF800D914D000.

14def3a3f12b48c26cf5445e86a5d7bb.png

Проваливаемся в call rax в функции HvlpLoadMicrocode.

ace4c499262955275940c13b3b2a2e33.png

Делаем snapshot после того, как провалились в call rax в функции HvlpLoadMicrocode.

21d60299b643e75702a3ed9c2bd7e651.png

Дальше завершаем процесс в IDA Pro.

11e22265672f91de83c3bd621436b0c5.png

Теперь возвращаемся к snapshot.

fe6d8111f1b7ee9bb90b029e1dec3a42.png

Открываем mcupdate…dll в IDA Pro и изменяем ImageBase (В нашем случае он 0xFFFFF800D914D000).

740d6bd10dbf5b086fe9e9e7d86d0bd5.png

Ставим breakpoint на функцию DriverEntry в файле mcupdate…dll и запускаем отладку в IDA Pro.

ec5f10096b0afa1cc587f3477daced2b.png

© Habrahabr.ru