
Uma biblioteca fácil de usar para emular dumps de memória. Útil para análise de malware (extração de configuração, desempacotamento) e análise dinâmica em geral (sandboxing).
Nota: Este é um protótipo em andamento, por favor trate-o como tal. Pull requests são bem-vindos! Você pode começar com boas primeiras issues
Uma biblioteca fácil de usar para emular código em arquivos minidump. Aqui estão alguns links para posts/vídeos usando dumpulator:
Sinta-se à vontade para enviar um pull request para adicionar seu artigo aqui!
O exemplo abaixo abre StringEncryptionFun_x64.dmp (baixe uma cópia aqui), aloca alguma memória e chama a função de descriptografia em 0x140001000 para descriptografar a string em 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}'")
O StringEncryptionFun_x64.dmp é coletado no ponto de entrada do exemplo tests/StringEncryptionFun. Você pode obter os binários compilados para StringEncryptionFun aqui
from dumpulator import Dumpulator
dp = Dumpulator("StringEncryptionFun_x64.dmp", trace=True)
dp.start(dp.regs.rip)
Isso criará StringEncryptionFun_x64.dmp.trace com uma lista de instruções executadas e algumas indicações úteis ao alternar módulos, etc. Observe que o rastreamento diminui significativamente a emulação e é principalmente destinado à depuração.
from dumpulator import Dumpulator
dp = Dumpulator("my.dmp")
buf = dp.call(0x140001000)
dp.read_str(buf, encoding='utf-16')
Digamos que você tenha a seguinte função:
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]
Você deseja apenas executar o prólogo e configurar alguns registradores:
from dumpulator import Dumpulator
prolog_start = 0x00007FFFC81C06C0
# queremos parar na instrução após o prólogo
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)}")
A flag quiet suprime os logs sobre DLLs carregadas e regiões de memória configuradas (para uso em scripts onde você deseja reduzir o spam de logs).
Você pode (re)implementar syscalls usando o decorador @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
Todos os protótipos de funções de syscall podem ser encontrados em ntsyscalls.py. Há também muitos exemplos lá sobre como usar a API.
Para interceptar uma implementação de syscall existente, você pode fazer o seguinte:
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)
Desde a v0.2.0 há suporte para declarar suas próprias estruturas facilmente:
from dumpulator.native import *
class PROCESS_BASIC_INFORMATION(Struct):
ExitStatus: ULONG
PebBaseAddress: PVOID
AffinityMask: KAFFINITY
BasePriority: KPRIORITY
UniqueProcessId: ULONG_PTR
InheritedFromUniqueProcessId: ULONG_PTR
Para instanciar essas estruturas, você precisa usar uma instância de 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
Se você passar um valor de ponteiro como segundo argumento, a estrutura será lida da memória. Você pode declarar ponteiros com myptr: P[MY_STRUCT] e desreferenciá-los com myptr[0].
Existe um plugin simples para x64dbg chamado MiniDumpPlugin O comando minidump foi integrado ao x64dbg desde 2022-10-10. Para criar um dump, pause a execução e execute o comando MiniDump my.dmp.
python -m pip install dumpulator
Para instalar a partir do código-fonte:
python setup.py install
Instalar para um ambiente de desenvolvimento:
python setup.py develop
O que diferencia o dumpulator de sandboxes como speakeasy e qiling é que toda a memória do processo está disponível. Isso melhora o desempenho porque você pode emular grandes partes do malware sem nunca sair do unicorn. Além disso, apenas syscalls precisam ser emuladas para fornecer um ambiente Windows realista (já que tudo na verdade é um ambiente de processo legítimo).