Skip to content

Commit

Permalink
Improve the handle system and syscall logging
Browse files Browse the repository at this point in the history
  • Loading branch information
mrexodia committed Sep 20, 2022
1 parent 83b78eb commit c4136b0
Show file tree
Hide file tree
Showing 5 changed files with 338 additions and 45 deletions.
85 changes: 75 additions & 10 deletions src/dumpulator/dumpulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from unicorn.x86_const import *
from pefile import *

from .handles import HandleManager
from .handles import HandleManager, FileObject
from .native import *
from .details import *
from capstone import *
Expand Down Expand Up @@ -355,6 +355,8 @@ def _setup_syscalls(self):
self.write(patch_addr, KiFastSystemCall)
elif export.name == b"KiUserExceptionDispatcher":
self.KiUserExceptionDispatcher = ntdll.baseaddress + export.address
elif export.name == b"LdrLoadDll":
self.LdrLoadDll = ntdll.baseaddress + export.address

syscalls.sort()
for index, (rva, name) in enumerate(syscalls):
Expand Down Expand Up @@ -432,8 +434,19 @@ def handle_exception(self):
# CONTEXT_EX: 0x18 bytes (accessed by RtlpSanitizeContext)
# Alignment: 0x8 bytes (not overwritten by KiUserExceptionDispatcher)
# EXCEPTION_RECORD: 0x98 bytes
# Unknown: 0x198 bytes
# Unknown: 0x198 bytes (JustMagic: should be _MACHINE_FRAME?)
# 0x4f0 bytes sizeof(CONTEXT) + 0x20 unclear
""" JustMagic:
rsp in KiUserExceptionDispatcher:
CONTEXT @ rsp + 0 : 4d0
CONTEXT_EX @ rsp + 4d0 : 18
alignment @ rsp + 4e8 : 8
EXCEPTION_RECORD @ rsp + 4f0 : 98
alignment @ rsp + 588 : 8
MACHINE_FRAME @ rsp + 590 : 28 | alignas(16) from RSP in exception / xstate
alignment @ rsp + 5b8 : 8
xstate @ rsp + 5c0 : CONTEXT_EX.Xstate.Length | alignas(64) from RSP in exception
"""
allocation_size = 0x720
context_flags = 0x10005F
record_type = EXCEPTION_RECORD64
Expand Down Expand Up @@ -580,6 +593,7 @@ def start(self, begin, end=0xffffffffffffffff, count=0):
continue
else:
self.error(f'error: {err}, cip = {self.regs.cip:x}')
traceback.print_exc()
break

def stop(self, exit_code=None):
Expand Down Expand Up @@ -608,6 +622,28 @@ def NtCurrentProcess(self):
def NtCurrentThread(self):
return 0xFFFFFFFFFFFFFFFE if self._x64 else 0xFFFFFFFE

def load_dll(self, file_name: str, file_data: bytes):
self.handles.map_file("\\??\\" + file_name, FileObject(file_name, file_data))
argument_ptr = self.allocate(0x1000)
utf16 = file_name.encode("utf-16-le")
if self._x64:
argument_data = struct.pack("<IIQHHIQ", 0, 0, 0, len(utf16), len(utf16) + 2, 0, argument_ptr + 32)
argument_data += utf16
argument_data += b"\0"
search_path = argument_ptr + len(argument_data)
argument_data += b"Z:\\"
image_type = argument_ptr
image_base_address = image_type + 8
image_file_name = image_base_address + 8
else:
assert False # TODO
self.write(argument_ptr, argument_data)

print(f"LdrLoadDll({file_name})")
status = self.call(self.LdrLoadDll, [1, image_type, image_file_name, image_base_address])
print(f"status = {hex(status)}")
return self.read_ptr(image_base_address)

def _hook_code_exception(uc: Uc, address, size, dp: Dumpulator):
try:
dp.info(f"exception step: {address:x}[{size}]")
Expand Down Expand Up @@ -720,11 +756,14 @@ def _get_regs(instr, include_write=False):
return regs

def _hook_code(uc: Uc, address, size, dp: Dumpulator):
code = dp.read(address, size)
try:
code = dp.read(address, min(size, 15))
instr = next(dp.cs.disasm(code, address, 1))
except StopIteration:
instr = None # Unsupported instruction
except UcError:
instr = None # Likely invalid memory
code = []
address_name = dp.exports.get(address, "")

module = ""
Expand All @@ -751,21 +790,46 @@ def _hook_code(uc: Uc, address, size, dp: Dumpulator):
for reg in _get_regs(instr):
line += f"|{reg}=0x{dp.regs.__getattr__(reg):x}"
else:
line += f"??? ({code.hex()})"
line += f"??? (code: {code.hex()}, size: {hex(size)})"
line += "\n"
dp.trace.write(line)

def _unicode_string_to_string(dp: Dumpulator, arg: P(UNICODE_STRING)):
try:
return arg[0].read_str()
except UcError:
pass
return None

def _arg_to_string(arg):
def _object_attributes_to_string(dp: Dumpulator, arg: P(OBJECT_ATTRIBUTES)):
try:
return arg[0].ObjectName[0].read_str()
except UcError:
pass
return None

def _arg_to_string(dp: Dumpulator, arg):
if isinstance(arg, Enum):
return arg.name
elif isinstance(arg, HANDLE):
if dp.handles.valid(arg):
return f"{hex(arg)} /* {dp.handles.get(arg, None)} */"
else:
return hex(arg)
elif isinstance(arg, PVOID):
str = hex(arg.ptr)
tstr = None
if arg.type is OBJECT_ATTRIBUTES:
tstr = _object_attributes_to_string(dp, arg)
elif arg.type is UNICODE_STRING:
tstr = _unicode_string_to_string(dp, arg)
if tstr is not None:
str += f" /* {tstr} */"
return str
elif isinstance(arg, int):
return hex(arg)
elif isinstance(arg, PVOID):
return hex(arg.ptr)
raise NotImplemented()


def _arg_type_string(arg):
if isinstance(arg, PVOID) and arg.type is not None:
return arg.type.__name__ + "*"
Expand Down Expand Up @@ -841,7 +905,7 @@ def syscall_arg(index):
if i + 1 == argcount:
comma = ""

dp.info(f" {_arg_type_string(argvalue)} {argname} = {_arg_to_string(argvalue)}{comma}")
dp.info(f" {_arg_type_string(argvalue)} {argname} = {_arg_to_string(dp, argvalue)}{comma}")
dp.info(")")
try:
status = syscall_impl(dp, *args)
Expand All @@ -868,7 +932,8 @@ def syscall_arg(index):
dp.error(f"syscall index {index:x} out of range")
dp.raise_kill(IndexError())

def _hook_invalid(uc: Uc, address, dp: Dumpulator):
def _hook_invalid(uc: Uc, dp: Dumpulator):
address = dp.regs.cip
# HACK: unicorn cannot gracefully exit in all contexts
if dp.kill_me:
dp.error(f"terminating emulation...")
Expand Down
80 changes: 71 additions & 9 deletions src/dumpulator/handles.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,76 @@
from typing import Any, Type, TypeVar
from typing import Any, Dict, Optional, Type, TypeVar

T = TypeVar('T')

class FileHandleObj:
def __init__(self, path):
class FileObject:
def __init__(self, path: str, data: bytes = None):
self.path = path
self.data = data
self.file_offset = 0

def __str__(self):
return f"{type(self).__name__}(path: {self.path}, file_offset {self.file_offset})"

return f"{type(self).__name__}(path: {self.path}, file_offset: {self.file_offset})"

def read(self, size: Optional[int] = None) -> bytes:
if self.data is None:
# TODO: implement properly
with open(self.path, "rb") as f:
if size is None:
data = f.read()
self.file_offset += len(data)
else:
f.seek(self.file_offset)
data = f.read(size)
self.file_offset += size
else:
if size is None:
data = self.data
self.file_offset += len(data)
else:
assert False
return data

class SectionObject:
def __init__(self, file: FileObject):
self.file = file

def __str__(self):
return f"{type(self).__name__}({self.file})"

class SpecialFileHandleObj(FileHandleObj):
class SpecialFileObject(FileObject):
def __init__(self, path, special):
super().__init__(path)
self.special = special

class ProcessTokenObject:
def __init__(self, process_handle):
self.process_handle = process_handle

def __str__(self):
return f"{type(self).__name__}({hex(self.process_handle)})"

class DeviceObject:
def __str__(self):
return f"{type(self).__name__}"

def io_control(self, dp, code: int, data: bytes) -> bytes:
raise NotImplementedError()

class RegistryKeyObject:
def __init__(self, key: str, values: Dict[str, Any] = {}):
self.key = key
self.values = values

def __str__(self):
return f"{type(self).__name__}({self.key})"

class HandleManager:
def __init__(self):
self.handles = {}
self.free_handles = []
self.base_handle = 0x100
self.handle_count = 0

T = TypeVar('T')
self.mapped_files = {}

def __find_free_handle(self) -> int:
if not self.free_handles:
Expand Down Expand Up @@ -63,7 +110,8 @@ def valid(self, handle_value: int) -> bool:
def close(self, handle_value: int) -> bool:
if handle_value in self.handles.keys():
del self.handles[handle_value]
self.free_handles.append(handle_value)
# Make sure all handles are unique
# self.free_handles.append(handle_value)
return True
return False

Expand All @@ -74,3 +122,17 @@ def duplicate(self, handle_value: int) -> int:
new_handle_value = self.__find_free_handle()
self.handles[new_handle_value] = handle_object
return new_handle_value

def map_file(self, filename: str, handle_data: Any):
self.mapped_files[filename] = handle_data

def open_file(self, filename: str):
data = self.mapped_files.get(filename, None)
if data is None:
return None
return self.new(data)

def create_key(self, key: str, values: Dict[str, Any] = {}):
data = RegistryKeyObject(key, values)
self.mapped_files[key] = data
return data
52 changes: 52 additions & 0 deletions src/dumpulator/native.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
STATUS_ACCESS_DENIED = 0xC0000022
STATUS_PRIVILEGE_NOT_HELD = 0xC0000061
STATUS_SET_CONTEXT_DENIED = 0xC000060A # Return from NtContinue to int 29
STATUS_INFO_LENGTH_MISMATCH = 0xC0000004
STATUS_INVALID_PARAMETER = 0xC000000D
STATUS_OBJECT_NAME_NOT_FOUND = 0xC0000034

# Exceptions
DBG_PRINTEXCEPTION_C = 0x40010006
Expand All @@ -22,6 +25,9 @@
MEM_COMMIT = 0x1000
MEM_FREE = 0x10000
MEM_RESERVE = 0x2000
MEM_DECOMMIT = 0x4000
MEM_RELEASE = 0x8000
MEM_DIFFERENT_IMAGE_BASE_OK = 0x800000

# Memory type
MEM_IMAGE = 0x1000000
Expand Down Expand Up @@ -57,6 +63,12 @@
FILE_EXISTS = 0x00000004
FILE_DOES_NOT_EXIST = 0x00000005

# Section flags
IMAGE_SCN_MEM_SHARED = 0x10000000
IMAGE_SCN_MEM_EXECUTE = 0x20000000
IMAGE_SCN_MEM_READ = 0x40000000
IMAGE_SCN_MEM_WRITE = 0x80000000

def round_to_pages(size):
return (size + 0xFFF) & 0xFFFFFFFFFFFFF000

Expand Down Expand Up @@ -457,6 +469,34 @@ class CONTEXT_EX(ctypes.Structure):
]
assert ctypes.sizeof(CONTEXT_EX) == 0x18

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()

def _RTL_PROCESS_MODULES(arch: Architecture, count: int):
class _RTL_PROCESS_MODULES(ctypes.Structure):
_alignment_ = arch.alignment(),
_fields_ = [
("NumberOfModules", ctypes.c_uint32),
("Modules", type(_RTL_PROCESS_MODULE_INFORMATION(arch)) * count)
]
modules = _RTL_PROCESS_MODULES()
modules.NumberOfModules = count
return modules

def MEMORY_BASIC_INFORMATION(arch: Architecture):
class MEMORY_BASIC_INFORMATION(ctypes.Structure):
_alignment_ = arch.alignment()
Expand All @@ -483,6 +523,18 @@ class MEMORY_REGION_INFORMATION(ctypes.Structure):
]
return MEMORY_REGION_INFORMATION()

def FILE_BASIC_INFORMATION(arch: Architecture):
class FILE_BASIC_INFORMATION(ctypes.Structure):
_alignment_ = arch.alignment()
_fields_ = [
("CreationTime", ctypes.c_uint64),
("LastAccessTime", ctypes.c_uint64),
("LastWriteTime", ctypes.c_uint64),
("ChangeTime", ctypes.c_uint64),
("Flags", ctypes.c_uint32),
]
return FILE_BASIC_INFORMATION()

def P(t):
class P(PVOID):
def __init__(self, ptr, mem_read):
Expand Down
2 changes: 1 addition & 1 deletion src/dumpulator/ntprimitives.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def __ne__(self, other):
return self.ptr != other

def __str__(self):
return f"0x{self:X}"
return hex(self.ptr)

def read_byte_str(self, size):
return bytes(self.read(size))
Expand Down
Loading

0 comments on commit c4136b0

Please sign in to comment.