Skip to content

Commit

Permalink
Merge pull request #55 from mrexodia/exceptions
Browse files Browse the repository at this point in the history
Improve exception support and tracing #27
  • Loading branch information
mrexodia authored Mar 5, 2023
2 parents b57acc0 + 9dda07f commit c858f31
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 21 deletions.
18 changes: 13 additions & 5 deletions src/dumpulator/dumpulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ class Dumpulator(Architecture):
def __init__(self, minidump_file, *, trace=False, quiet=False, thread_id=None, debug_logs=False):
self._quiet = quiet
self._debug = debug_logs
self.sequence_id = 0

# Load the minidump
self._minidump = minidump.MinidumpFile.parse(minidump_file)
Expand Down Expand Up @@ -465,6 +466,7 @@ def _setup_pebteb(self, thread):
self.stdin_handle = self.read_ptr(process_parameters + 0x20)
self.stdout_handle = self.read_ptr(process_parameters + 0x28)
self.stderr_handle = self.read_ptr(process_parameters + 0x30)
self.modules.main = self.read_ptr(self.peb + 0x10)
number_of_heaps = self.read_ulong(self.peb + 0xe8)
process_heaps_ptr = self.read_ptr(self.peb + 0xf0)
api_set_map = self.read_ptr(self.peb + 0x68)
Expand All @@ -485,6 +487,7 @@ def _setup_pebteb(self, thread):
self.stdin_handle = self.read_ptr(process_parameters + 0x18)
self.stdout_handle = self.read_ptr(process_parameters + 0x1c)
self.stderr_handle = self.read_ptr(process_parameters + 0x20)
self.modules.main = self.read_ptr(self.peb + 0x8)
number_of_heaps = self.read_ulong(self.peb + 0x88)
process_heaps_ptr = self.read_ptr(self.peb + 0x90)
api_set_map = self.read_ptr(self.peb + 0x38)
Expand Down Expand Up @@ -866,10 +869,11 @@ def handle_exception(self):
self.exception.handling = True

if self.exception.type == ExceptionType.ContextSwitch:
self.info(f"switching context, cip: {hex(self.regs.cip)}")
self.info(f"context switch, cip: {hex(self.regs.cip)}")
# Clear the pending exception
self.last_exception = self.exception
self.exception = ExceptionInfo()
# NOTE: the context has already been restored using context_restore in the caller
return self.regs.cip

self.info(f"handling exception...")
Expand Down Expand Up @@ -1320,14 +1324,14 @@ def _get_regs(instr, include_write=False):
return regs

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

module = ""
Expand All @@ -1353,6 +1357,8 @@ def _hook_code(uc: Uc, address, size, dp: Dumpulator):
line += instr.op_str
for reg in _get_regs(instr):
line += f"|{reg}=0x{dp.regs.__getattr__(reg):x}"
if instr.mnemonic in {"syscall", "sysenter"}:
line += f"|sequence_id=[{dp.sequence_id}]"
else:
line += f"??? (code: {code.hex()}, size: {hex(size)})"
line += "\n"
Expand Down Expand Up @@ -1459,7 +1465,7 @@ def syscall_arg(index):
return dp.regs.r10
return dp.args[index]

dp.info(f"syscall: {name}(")
dp.info(f"[{dp.sequence_id}] syscall: {name}(")
for i in range(0, argcount):
argname = argspec.args[1 + i]
argtype = argspec.annotations[argname]
Expand Down Expand Up @@ -1504,7 +1510,7 @@ def syscall_arg(index):
else:
dp.info(f"status = {status:x}")
dp.regs.cax = status
if dp._x64:
if dp.x64:
dp.regs.rcx = dp.regs.cip + 2
dp.regs.r11 = dp.regs.eflags
except UcError as err:
Expand All @@ -1513,6 +1519,8 @@ def syscall_arg(index):
traceback.print_exc()
dp.error(f"Exception thrown during syscall implementation, stopping emulation!")
dp.raise_kill(exc)
finally:
dp.sequence_id += 1
else:
dp.error(f"syscall index: {index:x} -> {name} not implemented!")
dp.raise_kill(NotImplementedError())
Expand All @@ -1524,7 +1532,7 @@ def _emulate_unsupported_instruction(dp: Dumpulator, instr: CsInsn):
if instr.id == X86_INS_RDRAND:
op: X86Op = instr.operands[0]
regname = instr.reg_name(op.reg)
if dp._x64 and op.size * 8 == 32:
if dp.x64 and op.size * 8 == 32:
regname = "r" + regname[1:]
print(f"emulated rdrand {regname}:{op.size * 8}, cip = {hex(instr.address)}+{instr.size}")
dp.regs[regname] = 42 # TODO: PRNG based on dmp hash
Expand Down
1 change: 1 addition & 0 deletions src/dumpulator/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class ModuleManager:
_memory: MemoryManager
_name_lookup: Dict[str, int] = field(default_factory=dict)
_modules: Dict[int, Module] = field(default_factory=dict)
main: int = 0

def add(self, pe: pefile.PE, path: str):
module = Module(pe, path)
Expand Down
25 changes: 25 additions & 0 deletions src/dumpulator/native.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
STATUS_NOT_FOUND = 0xC0000225
STATUS_MEMORY_NOT_ALLOCATED = 0xC00000A0
STATUS_CONFLICTING_ADDRESSES = 0xC0000018
STATUS_PORT_NOT_SET = 0xC0000353

# Exceptions
DBG_PRINTEXCEPTION_C = 0x40010006
Expand Down Expand Up @@ -571,6 +572,30 @@ class FILE_BASIC_INFORMATION(ctypes.Structure):
]
return FILE_BASIC_INFORMATION()

def SECTION_IMAGE_INFORMATION(arch: Architecture):
class SECTION_IMAGE_INFORMATION(ctypes.Structure):
_alignment_ = arch.alignment()
_fields_ = [
("TransferAddress", arch.ptr_type()),
("ZeroBits", ctypes.c_uint32),
("MaximumStackSize", arch.ptr_type()),
("CommittedStackSize", arch.ptr_type()),
("SubSystemType", ctypes.c_uint32),
("SubSystemMinorVersion", ctypes.c_uint16),
("SubSystemMajorVersion", ctypes.c_uint16),
("MajorOperatingSystemVersion", ctypes.c_uint16),
("MinorOperatingSystemVersion", ctypes.c_uint16),
("ImageCharacteristics", ctypes.c_uint16),
("DllCharacteristics", ctypes.c_uint16),
("Machine", ctypes.c_uint16),
("ImageContainsCode", ctypes.c_uint8),
("ImageFlags", ctypes.c_uint8),
("LoaderFlags", ctypes.c_uint32),
("ImageFileSize", ctypes.c_uint32),
("CheckSum", ctypes.c_uint32),
]
return SECTION_IMAGE_INFORMATION()

def P(t):
class P(PVOID):
def __init__(self, ptr, mem_read):
Expand Down
16 changes: 10 additions & 6 deletions src/dumpulator/ntprimitives.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,19 @@ class Architecture(object):
def __init__(self, x64: bool):
self._x64 = x64

@property
def x64(self):
return self._x64

def ptr_size(self):
return 8 if self._x64 else 4

def ptr_type(self, t=None): # TODO: implement type
return ctypes.c_uint64 if self._x64 else ctypes.c_uint32

def alignment(self):
return 16 if self._x64 else 8

def read(self, addr: int, size: int) -> bytes:
raise NotImplementedError()

Expand Down Expand Up @@ -74,12 +84,6 @@ def read_str(self, addr: int, encoding="utf-8") -> str:

return data.decode(encoding)

def ptr_type(self, t=None): # TODO: implement type
return ctypes.c_uint64 if self._x64 else ctypes.c_uint32

def alignment(self):
return 16 if self._x64 else 8

class PVOID:
def __init__(self, ptr: int, arch: Architecture):
self.ptr = ptr
Expand Down
66 changes: 56 additions & 10 deletions src/dumpulator/ntsyscalls.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def ZwAccessCheck(dp: Dumpulator,
GrantedAccess: Annotated[P(ACCESS_MASK), SAL("_Out_")],
AccessStatus: Annotated[P(NTSTATUS), SAL("_Out_")]
):
return STATUS_SUCCESS
raise NotImplementedError()

@syscall
def ZwAccessCheckAndAuditAlarm(dp: Dumpulator,
Expand Down Expand Up @@ -333,7 +333,7 @@ def ZwAllocateVirtualMemory(dp: Dumpulator,
dp.memory.reserve(base, size, protect)
dp.memory.commit(base, size)
else:
assert False
raise NotImplementedError()
return STATUS_SUCCESS

@syscall
Expand Down Expand Up @@ -634,7 +634,7 @@ def ZwCancelTimer(dp: Dumpulator,
TimerHandle: Annotated[HANDLE, SAL("_In_")],
CurrentState: Annotated[P(BOOLEAN), SAL("_Out_opt_")]
):
return STATUS_SUCCESS
raise NotImplementedError()

@syscall
def ZwCancelTimer2(dp: Dumpulator,
Expand Down Expand Up @@ -775,6 +775,7 @@ def ZwContinue(dp: Dumpulator,
ContextRecord: Annotated[P(CONTEXT), SAL("_In_")],
TestAlert: Annotated[BOOLEAN, SAL("_In_")]
):
# Trigger a context switch
assert not TestAlert
exception = ExceptionInfo()
exception.type = ExceptionType.ContextSwitch
Expand All @@ -784,6 +785,12 @@ def ZwContinue(dp: Dumpulator,
data = dp.read(ContextRecord.ptr, context_size)
context = context_type.from_buffer(data)
context.to_regs(dp.regs)
# Modifying fs/gs also appears to reset fs_base/gs_base
if dp.x64:
dp.regs.gs_base = dp.teb
else:
dp.regs.fs_base = dp.teb
dp.regs.gs_base = dp.teb - 2 * PAGE_SIZE
exception.context = dp._uc.context_save()
return exception

Expand Down Expand Up @@ -857,8 +864,14 @@ def ZwCreateEvent(dp: Dumpulator,
InitialState: Annotated[BOOLEAN, SAL("_In_")]
):
assert DesiredAccess == 0x1f0003
assert ObjectAttributes == 0
event = EventObject(EventType, InitialState)
if ObjectAttributes != 0:
attributes = ObjectAttributes[0]
assert attributes.ObjectName == 0
assert attributes.RootDirectory == 0
assert attributes.SecurityDescriptor == 0
assert attributes.SecurityQualityOfService == 0
assert attributes.Attributes == 2 # OBJ_INHERIT
event = EventObject(EventType, InitialState != 0)
handle = dp.handles.new(event)
EventHandle.write_ptr(handle)
return STATUS_SUCCESS
Expand Down Expand Up @@ -2465,7 +2478,7 @@ def ZwOpenProcessToken(dp: Dumpulator,
assert ProcessHandle == dp.NtCurrentProcess()
assert DesiredAccess == 0x20
# TODO: TokenHandle should be -6 or something
handle = dp.handles.new(ProcessTokenHandle(ProcessHandle))
handle = dp.handles.new(ProcessTokenObject(ProcessHandle))
print(f"process token: {hex(handle)}")
TokenHandle.write_ptr(handle)
return STATUS_SUCCESS
Expand Down Expand Up @@ -2495,7 +2508,7 @@ def ZwOpenSection(dp: Dumpulator,
DesiredAccess: Annotated[ACCESS_MASK, SAL("_In_")],
ObjectAttributes: Annotated[P(OBJECT_ATTRIBUTES), SAL("_In_")]
):
return STATUS_NOT_IMPLEMENTED
raise NotImplementedError()

@syscall
def ZwOpenSemaphore(dp: Dumpulator,
Expand Down Expand Up @@ -2953,13 +2966,19 @@ def ZwQueryInformationProcess(dp: Dumpulator,
ProcessInformationLength: Annotated[ULONG, SAL("_In_")],
ReturnLength: Annotated[P(ULONG), SAL("_Out_opt_")]
):
assert (ProcessHandle == dp.NtCurrentProcess())
if ProcessInformationClass in [PROCESSINFOCLASS.ProcessDebugPort, PROCESSINFOCLASS.ProcessDebugObjectHandle]:
assert ProcessHandle == dp.NtCurrentProcess()
if ProcessInformationClass == PROCESSINFOCLASS.ProcessDebugPort:
assert ProcessInformationLength == dp.ptr_size()
dp.write_ptr(ProcessInformation.ptr, 0)
if ReturnLength != 0:
dp.write_ulong(ReturnLength.ptr, dp.ptr_size())
return STATUS_SUCCESS
elif ProcessInformationClass == PROCESSINFOCLASS.ProcessDebugObjectHandle:
assert ProcessInformationLength == dp.ptr_size()
dp.write_ptr(ProcessInformation.ptr, 0)
if ReturnLength != 0:
dp.write_ulong(ReturnLength.ptr, dp.ptr_size())
return STATUS_PORT_NOT_SET
elif ProcessInformationClass == PROCESSINFOCLASS.ProcessDefaultHardErrorMode:
assert ProcessInformationLength == 4
dp.write_ulong(ProcessInformation.ptr, 1)
Expand All @@ -2972,6 +2991,33 @@ def ZwQueryInformationProcess(dp: Dumpulator,
if ReturnLength.ptr:
dp.write_ulong(ReturnLength.ptr, 4)
return STATUS_SUCCESS
elif ProcessInformationClass == PROCESSINFOCLASS.ProcessImageInformation:
sii = SECTION_IMAGE_INFORMATION(dp)
assert ProcessInformationLength == ctypes.sizeof(sii)
module = dp.modules[dp.modules.main]
pe = module.pe
opt = pe.OPTIONAL_HEADER
sii.TransferAddress = module.entry
sii.ZeroBits = 0
sii.MaximumStackSize = opt.SizeOfStackReserve
sii.CommittedStackSize = opt.SizeOfStackCommit # TODO: more might be committed, check PEB
sii.SubSystemType = opt.Subsystem
sii.SubSystemMinorVersion = opt.MinorSubsystemVersion
sii.SubSystemMajorVersion = opt.MajorSubsystemVersion
sii.MinorOperatingSystemVersion = opt.MinorOperatingSystemVersion
sii.MajorOperatingSystemVersion = opt.MajorOperatingSystemVersion
sii.ImageCharacteristics = pe.FILE_HEADER.Characteristics # TODO
sii.DllCharacteristics = opt.DllCharacteristics # TODO
sii.Machine = pe.FILE_HEADER.Machine
sii.ImageContainsCode = 1
sii.ImageFlags = 1 # TODO
sii.LoaderFlags = 0 # TODO
sii.ImageFileSize = module.size # TODO: best we can do?
sii.CheckSum = opt.CheckSum
ProcessInformation.write(bytes(sii))
if ReturnLength.ptr:
dp.write_ulong(ReturnLength.ptr, ctypes.sizeof(sii))
return STATUS_SUCCESS
raise NotImplementedError()

@syscall
Expand Down Expand Up @@ -4437,7 +4483,7 @@ def ZwTerminateThread(dp: Dumpulator,
ExitStatus: Annotated[NTSTATUS, SAL("_In_")]
):
assert ThreadHandle == dp.NtCurrentThread()
return STATUS_NOT_IMPLEMENTED
raise NotImplementedError()

@syscall
def ZwTestAlert(dp: Dumpulator
Expand Down

0 comments on commit c858f31

Please sign in to comment.