
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