Giter Club home page Giter Club logo

dumpulator's Introduction

dumpulator

Note: This is a work-in-progress prototype, please treat it as such. Pull requests are welcome! You can get your feet wet with good first issues

An easy-to-use library for emulating code in minidump files. Here are some links to posts/videos using dumpulator:

Feel free to send a pull request to add your article here!

Examples

Calling a function

The example below opens StringEncryptionFun_x64.dmp (download a copy here), allocates some memory and calls the decryption function at 0x140001000 to decrypt the string at 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}'")

The StringEncryptionFun_x64.dmp is collected at the entry point of the tests/StringEncryptionFun example. You can get the compiled binaries for StringEncryptionFun here

Tracing execution

from dumpulator import Dumpulator

dp = Dumpulator("StringEncryptionFun_x64.dmp", trace=True)
dp.start(dp.regs.rip)

This will create StringEncryptionFun_x64.dmp.trace with a list of instructions executed and some helpful indications when switching modules etc. Note that tracing significantly slows down emulation and it's mostly meant for debugging.

Reading utf-16 strings

from dumpulator import Dumpulator

dp = Dumpulator("my.dmp")
buf = dp.call(0x140001000)
dp.read_str(buf, encoding='utf-16')

Running a snippet of code

Say you have the following function:

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]

You only want to execute the prolog and set up some registers:

from dumpulator import Dumpulator

prolog_start = 0x00007FFFC81C06C0
# we want to stop the instruction after the prolog
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)}")

The quiet flag suppresses the logs about DLLs loaded and memory regions set up (for use in scripts where you want to reduce log spam).

Custom syscall implementation

You can (re)implement syscalls by using the @syscall decorator:

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

All the syscall function prototypes can be found in ntsyscalls.py. There are also a lot of examples there on how to use the API.

To hook an existing syscall implementation you can do the following:

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
                                                )

Custom structures

Since v0.2.0 there is support for easily declaring your own structures:

from dumpulator.native import *

class PROCESS_BASIC_INFORMATION(Struct):
    ExitStatus: ULONG
    PebBaseAddress: PVOID
    AffinityMask: KAFFINITY
    BasePriority: KPRIORITY
    UniqueProcessId: ULONG_PTR
    InheritedFromUniqueProcessId: ULONG_PTR

To instantiate these structures you have to use a Dumpulator instance:

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

If you pass a pointer value as a second argument the structure will be read from memory. You can declare pointers with myptr: P[MY_STRUCT] and dereferences them with myptr[0].

Collecting the dump

There is a simple x64dbg plugin available called MiniDumpPlugin The minidump command has been integrated into x64dbg since 2022-10-10. To create a dump, pause execution and execute the command MiniDump my.dmp.

Installation

From PyPI (latest release):

python -m pip install dumpulator

To install from source:

python setup.py install

Install for a development environment:

python setup.py develop

Related work

  • Dumpulator-IDA: This project is a small POC plugin for launching dumpulator emulation within IDA, passing it addresses from your IDA view using the context menu.
  • wtf: Distributed, code-coverage guided, customizable, cross-platform snapshot-based fuzzer designed for attacking user and / or kernel-mode targets running on Microsoft Windows
  • speakeasy: Windows sandbox on top of unicorn.
  • qiling: Binary emulation framework on top of unicorn.
  • Simpleator: User-mode application emulator based on the Hyper-V Platform API.

What sets dumpulator apart from sandboxes like speakeasy and qiling is that the full process memory is available. This improves performance because you can emulate large parts of malware without ever leaving unicorn. Additionally only syscalls have to be emulated to provide a realistic Windows environment (since everything actually is a legitimate process environment).

Credits

dumpulator's People

Contributors

anthonyprintup avatar calastrophe avatar mrexodia avatar oopsmishap avatar regionuser avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dumpulator's Issues

Improve performance of the `LazyPageManager`

This is a very naïve implementation. It stores a map of page_address -> LazyPage in the PageManager. Initially none of the pages are committed, so once execution starts it raises a memory exception. Once this happens the page is committed and emulation resumed.

There is no optimization done on the data structure yet, so a 10GB RW page would create ~10 million dictionary entries. The speedup is still very significant though.

#36

  • Lazily load the data from the minidump structure to improve startup times even further
  • Switch to a region-based structure (like the MemoryManager)
  • Load the full region in handle_lazy_page to reduce the amount of page faults

Implement a handle manager

Handles are currently hacked in. Add a handle manager that allows something like this:

def ZwCreateFile_syscall(...):
    handle_data = (...)
    hFile = dp.handles.new(data)
    return hFile

def ZwReadFile_syscall(hFile: HANDLE, ...):
    handle_data = dp.handles.get(hFile, None)
    return

def ZwCloseHandle(hFile):
    dp.handles.close(hFile)

It should also support duplication (every handle points to refcounted data).

Incorrect width for Enums in 64bit

When a syscall is processes an enum with a 64bit dump the value is treated as a 64bit integer. Within Windows syscalls most enums are treated as 32bit values. I can't find the documentation for this but if you start going through syscalls that use enums you will notice the trend.

When a syscall with an enum is called Dumpulator processes it as the following resulting in a ValueError

Error: ValueError: 549755813892 is not a valid FSINFOCLASS

549755813892 = 0x8000000004

Previous argument in the call had the value: 0x8

I have tried to implement a CTypes style of python enum's to no avail.
The following hotfix is enough to fix the issue, but could run into issues if an enum size is anything other than 32bit wide, however I have yet to find one that isn't 32bit wide.

elif issubclass(argtype, Enum):
    try:
        argvalue = argtype(dp.args[i] & 0xFFFFFFFF)
    except KeyError as x:
        raise Exception(f"Unknown enum value {dp.args[i]} for {type(argtype)}")

Support fixed-size arrays in `Struct`

This can currently not be ported:

def _RTL_PROCESS_MODULE_INFORMATION(arch: Architecture):
    class _RTL_PROCESS_MODULE_INFORMATION(ctypes.Structure):
        _alignment_ = arch.alignment()
        _fields_ = [
            ("Section", arch.ptr_type()),
            ("MappedBase", arch.ptr_type()),
            ("ImageBase", arch.ptr_type()),
            ("ImageSize", ctypes.c_uint32),
            ("Flags", ctypes.c_uint32),
            ("LoadOrderIndex", ctypes.c_uint16),
            ("InitOrderIndex", ctypes.c_uint16),
            ("LoadCount", ctypes.c_uint16),
            ("OffsetToFileName", ctypes.c_uint16),
            ("FullPathName", ctypes.c_ubyte * 256),
        ]
    return _RTL_PROCESS_MODULE_INFORMATION()

allocate throwing error

Getting the following error with a simple allocation call.

Dumpulator Version 0.1.2

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-2-c4eb66b71b05> in <module>
----> 1 temp_addr = dp.allocate(256)

~/.pyenv/versions/3.9.5/lib/python3.9/site-packages/dumpulator/dumpulator.py in allocate(self, size, page_align)
    499         if not self._allocate_ptr:
    500             self._allocate_base = self.memory.find_free(self._allocate_size)
--> 501             self.memory.reserve(
    502                 start=self._allocate_base,
    503                 size=self._allocate_size,

~/.pyenv/versions/3.9.5/lib/python3.9/site-packages/dumpulator/memory.py in reserve(self, start, size, protect, type, info)
    163         assert isinstance(type, MemoryType)
    164         assert size > 0 and self.align_page(size) == size
--> 165         assert self.align_allocation(start) == start
    166         region = MemoryRegion(start, size, protect, type, info)
    167         if region.start < self._minimum or region.end > self._maximum:

~/.pyenv/versions/3.9.5/lib/python3.9/site-packages/dumpulator/memory.py in align_allocation(self, addr)
    146     def align_allocation(self, addr: int):
    147         mask = self._granularity - 1
--> 148         return (addr + mask) & ~mask
    149 
    150     def find_free(self, size: int):

TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

Reproduce

from dumpulator import Dumpulator
dp = Dumpulator("shell.dmp")
temp_addr = dp.allocate(256)

Zipped version shell.dmp (shell.dmp.zip) available on Malshare

Design ctypes equivalent for syscall implementation

Currently the type system for syscalls is very rough and you need to do a lot of manual work. A type system similar to ctypes needs to be implemented where you can set struct members, work with enums etc.

Once the type system is complete a pdb/header parser can be implemented to support all the native types.

Trace points

The idea would be to generate a unique number for each syscall invocation and print it in the log. The sequence should be deterministic for a given dumpulator version (although preferably across versions as well). Uses cases:

  • "Breakpoint" (stop emulation or literally a debugger breakpoint) at a certain ID you saw previously in the log for closer inspection.
  • Enable single step tracing (slow) only after/between trace points.
  • Dump the state after a certain trace point to avoid re-running the same code over and over again (needs #13)

x64 dump of x86 process fail to emulate

I have noticed if you do a dump on x64 OS of a x86 process (from task manager or procdump64 for example) dumpulator would try to emulate everything in x64 even though most of the dump is actually x86 causing some unexpected behavior, it doesn't happen if you do a x86 dump from x32dbg or from procdump(32).

Implement a memory manager

Currently there is no information about the mapped memory regions available. There should be a robust system to manage memory and allow syscalls to use it for memory queries:

dp.mem_protect(addr, size, Memory.ReadWrite)
info = dp.mem_find(addr)
dp.mem_allocate(size, addr=None)
dp.mem_free(addr)

Proper (extensible) testing

Currently the "tests" are just running the "getting-started" examples. This doesn't scale and makes it difficult to find regressions.

The test framework:

  • A single unified Visual Studio solution with test executables
  • Build the solution with GitHub Actions so new feature PRs can be tested in the same PR
  • A dump of a minimal Windows usermode environment (ntdll, kernel32, kernelbase, x64+x86)
  • A dump of a more "complete" environment (winsock, cryptapi, advapi32, shell32, user32, etc.)
  • Load the test executables with dp.map_module
  • Execute the (exported) test functions on a fresh Dumpulator instance each time
  • Calculate code coverage per test

Necessary tests:

All of these should use /NODEFAULTLIB to not have to care about the MSVC runtime initialization (which uses a lot of unimplemented syscalls)

  • ExceptionTest (for testing different exception scenarios)
  • LoadLibrary (to test map_module and the whole PEB loading chain)
  • StringEncryptionFun
  • More depending on coverage...

Better error when information is missing

   dp = Dumpulator("c:\\tmp\\dump1.dmp", trace=True)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Python311\Lib\site-packages\dumpulator\dumpulator.py", line 322, in __init__
    self._setup_memory()
  File "C:\Python311\Lib\site-packages\dumpulator\dumpulator.py", line 412, in _setup_memory
    for info in self._minidump.memory_info.infos:
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'infos'

This happens if you do not specify the MiniDumpWithFullMemoryInfo

Implement a module manager

Module information should be available with the PEB data (base, size, path, exports, etc.) with a simple interface

Saving/restoring state

Right now the state after a .call or .start persists. It would be nice to have a feature to roll back memory changes without having to reload the dump.

I think unicorn has a feature for this.

Fix loading of kernel32 on modern systems

SizeOfImage is not consecutive in memory. So unicorn fails to read the memory. Fix would be to read the memory in chunks of 0x1000 pages, or only pass the first page to pefile and load in pages when necessary.

Dumpulator takes a long time to load when handling large dumps

Feature Request: Please look into addressing the issue where large dump files aren't easily processed by dumpulator.

Description: Dumpulator is taking an excessively long time to load a dump file of 40MB. I was testing the decryption of an RC4 encrypted configuration block from gh0stRAT. I dumped out the process and it was 40MB. I fed this dump into dumpulator and it was still trying to load after 20 minutes.

Sample: https://www.virustotal.com/gui/file/0a9c881b857d4044cb6dfeba682190d7d9dc6ef94bc149cac77f3e0f65e9c69a

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.