
Простая в использовании библиотека для эмуляции дампов памяти. Полезна для анализа вредоносного ПО (извлечение конфигураций, распаковка) и динамического анализа в целом (песочница).
Примечание: Это прототип в разработке, пожалуйста, относитесь к нему соответственно. Pull request'ы приветствуются! Вы можете начать с хороших первых задач
Простая в использовании библиотека для эмуляции кода в minidump-файлах. Вот несколько ссылок на публикации/видео, использующие dumpulator:
Не стесняйтесь отправить pull request, чтобы добавить сюда свою статью!
В приведённом ниже примере открывается StringEncryptionFun_x64.dmp (скачать копию здесь), выделяется память и вызывается функция дешифрования по адресу 0x140001000 для расшифровки строки по адресу 0x140017000:
from dumpulator import Dumpulator
dp = Dumpulator("StringEncryptionFun_x64.dmp")
temp_addr = dp.allocate(256)
dp.call(0x140001000, [temp_addr, 0x140017000])
decrypted = dp.read_str(temp_addr)
print(f"decrypted: '{decrypted}'")
StringEncryptionFun_x64.dmp собран в точке входа примера tests/StringEncryptionFun. Скомпилированные бинарники StringEncryptionFun можно получить здесь
from dumpulator import Dumpulator
dp = Dumpulator("StringEncryptionFun_x64.dmp", trace=True)
dp.start(dp.regs.rip)
Это создаст StringEncryptionFun_x64.dmp.trace со списком выполненных инструкций и полезными указаниями при переключении модулей и т.п. Обратите внимание, что трассировка значительно замедляет эмуляцию и в основном предназначена для отладки.
from dumpulator import Dumpulator
dp = Dumpulator("my.dmp")
buf = dp.call(0x140001000)
dp.read_str(buf, encoding='utf-16')
Допустим, у вас есть следующая функция:
00007FFFC81C06C0 | mov qword ptr [rsp+0x10],rbx ; prolog_start
00007FFFC81C06C5 | mov qword ptr [rsp+0x18],rsi
00007FFFC81C06CA | push rbp
00007FFFC81C06CB | push rdi
00007FFFC81C06CC | push r14
00007FFFC81C06CE | lea rbp,qword ptr [rsp-0x100]
00007FFFC81C06D6 | sub rsp,0x200 ; prolog_end
00007FFFC81C06DD | mov rax,qword ptr [0x7FFFC8272510]
Вы хотите выполнить только пролог и установить некоторые регистры:
from dumpulator import Dumpulator
prolog_start = 0x00007FFFC81C06C0
# мы хотим остановиться на инструкции после пролога
prolog_end = 0x00007FFFC81C06D6 + 7
dp = Dumpulator("my.dmp", quiet=True)
dp.regs.rcx = 0x1337
dp.start(begin=prolog_start, end=prolog_end)
print(f"rsp: {hex(dp.regs.rsp)}")
Флаг quiet подавляет логи о загруженных DLL и настроенных областях памяти (используется в скриптах, где нужно уменьшить количество логов).
Вы можете (пере)определить системные вызовы с помощью декоратора @syscall:
from dumpulator import *
from dumpulator.native import *
from dumpulator.handles import *
from dumpulator.memory import *
@syscall
def ZwQueryVolumeInformationFile(dp: Dumpulator,
FileHandle: HANDLE,
IoStatusBlock: P[IO_STATUS_BLOCK],
FsInformation: PVOID,
Length: ULONG,
FsInformationClass: FSINFOCLASS
):
return STATUS_NOT_IMPLEMENTED
Все прототипы системных вызовов можно найти в ntsyscalls.py. Там же есть много примеров использования API.
Чтобы перехватить существующую реализацию системного вызова, можно сделать следующее:
import dumpulator.ntsyscalls as ntsyscalls
@syscall
def ZwOpenProcess(dp: Dumpulator,
ProcessHandle: Annotated[P[HANDLE], SAL("_Out_")],
DesiredAccess: Annotated[ACCESS_MASK, SAL("_In_")],
ObjectAttributes: Annotated[P[OBJECT_ATTRIBUTES], SAL("_In_")],
ClientId: Annotated[P[CLIENT_ID], SAL("_In_opt_")]
):
process_id = ClientId.read_ptr()
assert process_id == dp.parent_process_id
ProcessHandle.write_ptr(0x1337)
return STATUS_SUCCESS
@syscall
def ZwQueryInformationProcess(dp: Dumpulator,
ProcessHandle: Annotated[HANDLE, SAL("_In_")],
ProcessInformationClass: Annotated[PROCESSINFOCLASS, SAL("_In_")],
ProcessInformation: Annotated[PVOID, SAL("_Out_writes_bytes_(ProcessInformationLength)")],
ProcessInformationLength: Annotated[ULONG, SAL("_In_")],
ReturnLength: Annotated[P[ULONG], SAL("_Out_opt_")]
):
if ProcessInformationClass == PROCESSINFOCLASS.ProcessImageFileNameWin32:
if ProcessHandle == dp.NtCurrentProcess():
main_module = dp.modules[dp.modules.main]
image_path = main_module.path
elif ProcessHandle == 0x1337:
image_path = R"C:\Windows\explorer.exe"
else:
raise NotImplementedError()
buffer = UNICODE_STRING.create_buffer(image_path, ProcessInformation)
assert ProcessInformationLength >= len(buffer)
if ReturnLength.ptr:
dp.write_ulong(ReturnLength.ptr, len(buffer))
ProcessInformation.write(buffer)
return STATUS_SUCCESS
return ntsyscalls.ZwQueryInformationProcess(dp,
ProcessHandle,
ProcessInformationClass,
ProcessInformation,
ProcessInformationLength,
ReturnLength
)
Начиная с v0.2.0 поддерживается простое объявление собственных структур:
from dumpulator.native import *
class PROCESS_BASIC_INFORMATION(Struct):
ExitStatus: ULONG
PebBaseAddress: PVOID
AffinityMask: KAFFINITY
BasePriority: KPRIORITY
UniqueProcessId: ULONG_PTR
InheritedFromUniqueProcessId: ULONG_PTR
Чтобы создать экземпляр такой структуры, необходимо использовать экземпляр Dumpulator:
pbi = PROCESS_BASIC_INFORMATION(dp)
assert ProcessInformationLength == Struct.sizeof(pbi)
pbi.ExitStatus = 259 # STILL_ACTIVE
pbi.PebBaseAddress = dp.peb
pbi.AffinityMask = 0xFFFF
pbi.BasePriority = 8
pbi.UniqueProcessId = dp.process_id
pbi.InheritedFromUniqueProcessId = dp.parent_process_id
ProcessInformation.write(bytes(pbi))
if ReturnLength.ptr:
dp.write_ulong(ReturnLength.ptr, Struct.sizeof(pbi))
return STATUS_SUCCESS
Если передать значение указателя вторым аргументом, структура будет прочитана из памяти. Можно объявлять указатели с помощью myptr: P[MY_STRUCT] и разыменовывать их через myptr[0].
Существует простой плагин для x64dbg под названием MiniDumpPlugin Команда minidump встроена в x64dbg с 2022-10-10. Чтобы создать дамп, приостановите выполнение и выполните команду MiniDump my.dmp.
python -m pip install dumpulator
Установка из исходников:
python setup.py install
Установка для среды разработки:
python setup.py develop
Что отличает dumpulator от песочниц вроде speakeasy и qiling — это доступность полной памяти процесса. Это повышает производительность, поскольку можно эмулировать большие фрагменты вредоносного ПО, не покидая unicorn. Кроме того, эмулировать нужно только системные вызовы, чтобы обеспечить реалистичное окружение Windows (так как всё на самом деле является легитимным окружением процесса).