BlackLotus UEFI bootkit. Часть 1

от автора

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

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

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

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

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

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

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

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

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

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

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

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

  4. 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 10×64-BatonDrop необходимо использовать одно ядро.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Включаем виртуальную машину Windows 10×64-BatonDrop и смотрим файл C:\Users\user\Documents\Virtual Machines\Windows 10×64-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 становится нашим лучшим помощником. Во время поиска информации было найдено несколько полезных проектов:

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

  2. 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 10×64-BatonDrop и нам необходимо ее включить. По данной ссылке есть описание как это можно сделать. Сначала отключим виртуальную машину Windows 10×64-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.


ссылка на оригинал статьи https://habr.com/ru/articles/844720/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *