Приветствую вас, дорогие читатели! Сегодня я хочу поделиться с вами своим опытом изучения BlackLotus UEFI bootkit. В этом исследование разберем следующие темы:
Подготовка тестового стенда.
Запуск CVE-2022–21 894 (baton drop).
Компиляция payload и компонентов для его выполнения.
Добавление сертификата в базу данных MOK.
Чтение и запись файлов в операционной системе Windows10 из файловой системы NTFS через grub.elf.
Давайте углубимся в эти интересные темы и разберемся, как функционирует одна из современных угроз безопасности.
1. Подготовка тестового стенда.
Состав стенда:
Windows 10×64 — рабочее место исследователя.
VMware Workstation — платформа для виртуализации (Версия 17.0.0 build-20 800 274).
VMware Workstation Менеджер — ПО для предоставления доступа к виртуальным машинам на VMware Workstation.
-
Windows 10×64-BatonDrop — виртуальная машина, подготовленная для проведения исследований (Версия Windows 10 Pro 21H2 19 044.1288). Настройки системы были выбраны стандартные.
1.1 Попытка запуска CVE-2022-21894(BatonDrop).
Для начала попробуем запустить CVE-2022-21894. В этом исследование действия будут проводиться с файлом poc_amd64_19041.iso, который скачаем по ссылке.
Для начала необходимо примонтировать «Системный раздел EFI», для этого необходимо проделать следующие шаги.
Далее нужно скопировать файлы из poc_amd64_19041.iso в системный раздел. Для используем Total Commander (Total Commander нужно запускать с правами администратора).
Сделаем копию оригинального BCD файла и назовём ее BCDR(Понадобиться потом).
Создадим snapshot для виртуальной машины Windows 10 x64-BatonDrop, так как в будущем потребуется к нему вернуться.
Импортируем bcd файл из poc_amd64_19041.iso с помощью bcdedit.exe.
После этого выключаем виртуальную машину Windows 10 x64-BatonDrop и при включении возникает ошибка.
2. Отладка CVE-2022-21894 (baton drop)
После появления ошибки 0xc0 000 010, обратимся к статье BlackLotus UEFI bootkit: Myth confirmed (welivesecurity.com), чтобы понять, какие файлы загружаются и в каком порядке. Эта информация важна для определения того, что именно требуется отладить. Только пройдя по этим файлам можно понять, где срабатывает ошибка 0xc0 000 010.
Замечание: При отладке Windows 10x64-BatonDrop необходимо использовать одно ядро.
2.1 Настройка отладки
Для начала разберёмся, что именно мы будем настраивать и как это сделать. Мы изучили документы MSDN по следующим темам:
Теперь приступим к настройке отладки в системе Windows 10 x64-BatonDrop. Возвращаемся к snapshot good.
Выполняем команды от имени администратора в виртуальной машине Windows 10x64-BatonDrop, затем выключим виртуальную машину Windows 10x64-BatonDrop.
Добавляем Serial Port для Windows 10x64-BatonDrop.
Потом изменим файл C:\Users\user\Documents\Virtual Machines\Windows 10x64-BatonDrop\Windows 10x64-BatonDrop.vmx. Удалим строки.
Переименуем serial1 на serial0 и сохраняем файл Windows 10x64-BatonDrop.vmx.
Находясь на рабочем месте исследователя Windows 10x64, настроим Windbg.
2.2 Отладка файла bootmgfw.elf.
После настройке отладки запускаем виртуальную машину Windows 10x64-BatonDrop и в Windbg(Windows 10x64) видим подключение.
Дальше срабатывает системное прерывание int 3.
После срабатывания системного прерывания ищем загруженные файлы.
У нас есть файл bootmgfw (с ним и начнем работать), у которого Imagebase 0x0000000010000000.
Теперь возвращаемся к snapshot good и импортируем bcd file.
Выключаем виртуальную машину Windows 10x64-BatonDrop и изменяем файл C:\Users\user\Documents\Virtual Machines\Windows 10x64-BatonDrop\Windows 10x64-BatonDrop.vmx, чтобы отладить с помощью IDA Pro.
Включаем виртуальную машину Windows 10x64-BatonDrop и смотрим файл C:\Users\user\Documents\Virtual Machines\Windows 10x64-BatonDrop\vmware.log, так как нам необходимо узнать порт для подключения с помощью IDA Pro.
После проделанных действий открываем IDA Pro и загружаем файл bootmgfw.efi(Файл bootmgfw.efi берётся из E:\EFI\Microsoft\Boot\bootmgfw.efi).
Добавляем bootmgfw.pdb файл в IDA PRO.
Замечание: Файл bootmgfw.pdb можно скачать, воспользовавшись ссылкой http://msdl.microsoft.com/download/symbols/bootmgfw.pdb/C94B898929165E26611E4791B87F6B1B2/bootmgfw.pdb, где C94B898929165E26611E4791B87F6B1B2.
Или можно использовать скрипт 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 на 0x0000000010000000 в IDA Pro.
Ставим breakpoint на начало функции BmMain.
Настраиваем отладку в IDA Pro и запускаем отладку.
В функции BmMain мы можем увидеть срабатывание breakpoint.
2.3 Переход из файла bootmgfw.elf в файл bootmgr.elf.
Теперь нам необходимо перейти из файла bootmgfw.elf в файл bootmgr.elf, однако возникает сложность: непонятно, как это сделать. Не ясно, какие функции исследовать в файле bootmgfw.elf, а изучать и отлаживать всё подряд — неэффективно. В такой ситуации Google становится нашим лучшим помощником. Во время поиска информации было найдено несколько полезных проектов:
ReactOS: boot/freeldr/freeldr/bootmgr.c File Reference: В этот проекте есть детальная документация по структурам, функциям, их аргументам и многому другому для ОС Windows.
-
GitHub - backengineering/Voyager: A Hyper-V Hacking Framework For Windows 10 x64 (AMD & Intel): В этом проекте наибольшую ценность представляет фотография, на которой указаны функции BlImgLoadPEImageEx, BlImgLoadPEImage, BlImgAllocateImageBuffer, ImgArchStartBootApplication.
Эти находки значительно упрощают задачу и помогают понять с чего начать исследование.
Начнем с восстановления читабельности функции BlImgLoadPEImageEx и получим следующие. Замечание: В этих двух проектах есть описание функции BlImgLoadPEImageEx.
Поставим breakpoint на функцию BlImgLoadPEImageEx -> ImgpOpenFile , чтобы детально проанализировать передаваемые в неё аргументы.
Запускаем отладку в IDA Pro и останавливаемся на функции BlImgLoadPEImageEx -> ImgpOpenFile. Однако мы не можем просмотреть содержимое аргументов (регистров или памяти), потому что IDA Pro не может отобразить эту память — она находится вне области видимости IDA Pro.
Для решения этой проблемы напишем скрипт 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 и продолжаем выполнение.
При втором срабатывании breakpoint(BlImgLoadPEImageEx → ImgpOpenFile) видим открытие файла bootmgr.elf, это тот файл который нам нужен.
Теперь перейдем к изучениюфункции BlImgLoadPEImageEx → ImgpLoadPEI mage, откроем ссылку. И после исследования кода по ссылке, мы приходим к выводу, что нам нужно получить адрес VirtualAddress.
В IDA Pro устанавливаем breakpoint на функцию RtlImageNtHeaderEx (BlImgLoadPEImageEx → ImgpLoadPEImage → RtlImageNtHeaderEx).
Запускаем отладку в IDA Pro, останавливаемся на функции BlImgLoadPEImageEx -> ImgpLoadPEImage -> RtlImageNtHeaderEx и мы видим начало файла bootmgr.elf, которое начинается с 'MZ'.
Дальше перейдём к изучению функции ImgArchStartBootApplication и восстановим её читабельность. В проекте ReactOS: boot/freeldr/freeldr/bootmgr.c File Reference описание данной функции не было найдено, однако в GitHub - backengineering/Voyager: A Hyper-V Hacking Framework For Windows 10 x64 (AMD & Intel) удалось обнаружить описание её аргументов. Это означает, что нам придётся провести отладку, чтобы разобраться в работе функции ImgArchStartBootApplication. После отладки можно сделать вывод, что функция ImgArchStartBootApplication работает следующим образом.
Сначала мы проходим по функциям.
В конце функции Archpx64TransferTo64BitApplicationAsm, после retfq необходимо еще немного отладить.
В итоге, это приведет нас к call rax, при вызове которого происходит передача управления на функцию BmMain в файле bootmgr.elf.
2.4 Переход из файла bootmgr.elf в файл hvloader.efi.
После того как мы нашли Imagebase 0x0000000000613000 файла bootmgr.elf и понимания, что нужно отлаживать функцию Archpx64TransferTo64BitApplicationAsm. Переход из файла bootmgr.elf в файл hvloader.efi должен пройти без затруднений, так как принцип перехода такой же как переход из файла bootmgfw.elf в файл bootmgr.elf.
Откроем IDA Pro и загрузим файл bootmgr.elf (Файл bootmgr.elf берётся из E:\EFI\ minram\bootmgr.elf).
Добавляем bootmgr.pdb файл в IDA Pro (Ссылка для скачивания).
Изменяем Imagebase на 0x0000000000613000 в IDA Pro.
Устанавливаем breakpoint в конец функции Archpx64TransferTo64BitApplicationAsm на retfq.
Запускаем отладку в IDA Pro, останавливаемся в конец функции Archpx64TransferTo64BitApplicationAsm на retfq и после retfq еще немного отлаживаем.
И это нас привело к call rax, при вызове которого происходит передача управления на функцию HvlMain в файле hvloader.efi.
Теперь вычислим Imagebase файла hvloader.efi, для этого отнимем от 0x106C84B8 число 0x24B8 и получим Imagebase 0x106C6000.
Число 0x24B8 можно вычислить следующим образом:
• Загрузить файл hvloader.efi в IDA PRO по дефолту Imagebase будет 0x140000000.
• Перейти в функцию HvlMain расположенная по адресу 0x1400024B8.
• Отнять от 0x1400024B8 число 0x140000000 получим 0x24B8.
2.5 Переход из файла hvloader.efi в файл mcupdate_....dll.
После того как мы нашли Imagebase 0x106C6000 файла hvloader.efi, перейдем к изучению перехода из файла hvloader.efi в файл mcupdate_....dll. Для этого обратимся к описанию файла hvloader.efi, найденному в интернете, и выделим одну важную функцию BtLoadUpdateDll, на которую следует обратить особое внимание. Данная функция загружает файл mcupdate_....dll. Теперь установим breakpoint на функцию BtLoadUpdateDll, чтобы убедиться, происходит ли переход в неё.
Откроем IDA Pro и загрузим файл hvloader.efi (Файл hvloader.efi берётся из E:\EFI\ maxram\hvloader.efi).
Добавляем hvloader.pdb файл в IDA Pro (Ссылка для скачивания).
Изменяем Imagebase на 0x106C6000 в IDA Pro.
Устанавливаем breakpoint на функции BtLoadUpdateDll.
Запускаем отладку в IDA Pro и breakpoint на функции BtLoadUpdateDll не срабатывает, тогда давайте установим breakpoint на test eax, eax(0x00000000106C8349).
Запускаем отладку в IDA Pro и breakpoint на test eax, eax срабатывает.
Теперь нам необходимо понять почему не происходит переход с 0x00000000106C834B на 0x00000000106C835D и начнем с разбора функции HvlpSLATPresent.
После поиска описания функции HvlpSLATPresent в интернете, можно сделать вывод, что SLAT - это технология виртуализации с аппаратным обеспечением, которая позволяет избежать накладных расходов, связанных с управляемыми программным обеспечением таблицами теневых страниц (https://ru.wikipedia.org/wiki/SLAT).
Мы можем предположить, что технология SLAT отключена у виртуальной машины Windows 10x64-BatonDrop и нам необходимо ее включить. По данной ссылке есть описание как это можно сделать. Сначала отключим виртуальную машину Windows 10x64-BatonDrop. Дальше включим SLAT, проделав следующие шаги и посмотрим, что произойдет.
Запускаем отладку в IDA Pro и breakpoint на функции BtLoadUpdateDll срабатывает.
Теперь просто нажмем F9 и посмотрим, что произойдет. На экране можно увидеть выполнение CVE-2022-21894 (baton drop).
Продолжим изучение кода, чтобы понять, где происходит переход из файла hvloader.efi в файл mcupdate_....dll.
Для этого изучим функцию BtLoadUpdateDll, на сайте есть описание данной функции BtLoadUpdateDll.
Нас интересуют строки 26 и 27, где указаны названия ImageBase и AddressOfEntryPoint файла mcupdate_....dll. Установим breakpoints на этих строках (В IDA Pro это строки 47 и 48).
Запускаем отладку в IDA Pro и в строчке 47 мы видим ImageBase файла mcupdate_....dll
А в строке 48 мы можем увидеть AddressOfEntryPoint файла mcupdate_....dll. На фото изображен ассемблерный код, так как hex-reys не отображает эту информацию в строке 48.
Теперь перейдем в функцию HvlpLoadMicrocode, так как в нее передается AddressOfEntryPoint (imageEP).
В функции HvlpLoadMicrocode по адресу 0x00000000106C8D42(call rax) происходит переход в файл mcupdate_....dll.
Но у нас не получилось отладить mcupdate_....dll, как мы отлаживали hvloader.efi, bootmgr.elf, bootmgfw.elf. Потому что у файла mcupdate_....dll есть ASLR, так как при каждом запуске виртуальной машины меняется ImageBase.
В таком случае, что бы отладить mcupdate_....dll, нам нужно сделать следующие шаги.
Делаем snapshot до перехода в mcupdate_....dll.
Узнаем ImageBase 0xFFFFF800D914D000.
Проваливаемся в call rax в функции HvlpLoadMicrocode.
Делаем snapshot после того, как провалились в call rax в функции HvlpLoadMicrocode.
Дальше завершаем процесс в IDA Pro.
Теперь возвращаемся к snapshot.
Открываем mcupdate...dll в IDA Pro и изменяем ImageBase (В нашем случае он 0xFFFFF800D914D000).
Ставим breakpoint на функцию DriverEntry в файле mcupdate...dll и запускаем отладку в IDA Pro.
Комментарии (5)
NickDoom
22.09.2024 00:55как функционирует одна из современных угроз безопасности
UEFI-то? Ну его продавил M$, ясно же. Его молитвами главная угроза безопасности и функционирует.
d00m911
22.09.2024 00:55Ахахаха) Главная угроза безопасности- это pch-микросхема и её прошивка, а uefi - это вполне себе годная идея, если бы не закрытый код инициализации платформы.
NutsUnderline
22.09.2024 00:55это восхитительно, но уровень статьи явно "сложный" и требует соотв. квалификации. совсем по хорошему не хватает введения что да как это вобще такое. Ну или хотя бы краткого но конкретного: какая задача ставится и какой результат получен.
d00m911
22.09.2024 00:55+1Не соглашусь. При всем уважении к проделанной работе, автор очень сильно все усложнил, в итоге, почти все, что он сделал, сводится к одной команде windbg.
d00m911
Эммм... не совсем понимаю, а почему нельзя просто в windbg сделать sxe ld вместо всей этой эзотерики?