Skip to content

Commit c858f31

Browse files
authored
Merge pull request #55 from mrexodia/exceptions
Improve exception support and tracing #27
2 parents b57acc0 + 9dda07f commit c858f31

File tree

5 files changed

+105
-21
lines changed

5 files changed

+105
-21
lines changed

src/dumpulator/dumpulator.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ class Dumpulator(Architecture):
251251
def __init__(self, minidump_file, *, trace=False, quiet=False, thread_id=None, debug_logs=False):
252252
self._quiet = quiet
253253
self._debug = debug_logs
254+
self.sequence_id = 0
254255

255256
# Load the minidump
256257
self._minidump = minidump.MinidumpFile.parse(minidump_file)
@@ -465,6 +466,7 @@ def _setup_pebteb(self, thread):
465466
self.stdin_handle = self.read_ptr(process_parameters + 0x20)
466467
self.stdout_handle = self.read_ptr(process_parameters + 0x28)
467468
self.stderr_handle = self.read_ptr(process_parameters + 0x30)
469+
self.modules.main = self.read_ptr(self.peb + 0x10)
468470
number_of_heaps = self.read_ulong(self.peb + 0xe8)
469471
process_heaps_ptr = self.read_ptr(self.peb + 0xf0)
470472
api_set_map = self.read_ptr(self.peb + 0x68)
@@ -485,6 +487,7 @@ def _setup_pebteb(self, thread):
485487
self.stdin_handle = self.read_ptr(process_parameters + 0x18)
486488
self.stdout_handle = self.read_ptr(process_parameters + 0x1c)
487489
self.stderr_handle = self.read_ptr(process_parameters + 0x20)
490+
self.modules.main = self.read_ptr(self.peb + 0x8)
488491
number_of_heaps = self.read_ulong(self.peb + 0x88)
489492
process_heaps_ptr = self.read_ptr(self.peb + 0x90)
490493
api_set_map = self.read_ptr(self.peb + 0x38)
@@ -866,10 +869,11 @@ def handle_exception(self):
866869
self.exception.handling = True
867870

868871
if self.exception.type == ExceptionType.ContextSwitch:
869-
self.info(f"switching context, cip: {hex(self.regs.cip)}")
872+
self.info(f"context switch, cip: {hex(self.regs.cip)}")
870873
# Clear the pending exception
871874
self.last_exception = self.exception
872875
self.exception = ExceptionInfo()
876+
# NOTE: the context has already been restored using context_restore in the caller
873877
return self.regs.cip
874878

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

13221326
def _hook_code(uc: Uc, address, size, dp: Dumpulator):
1327+
code = b""
13231328
try:
13241329
code = dp.read(address, min(size, 15))
13251330
instr = next(dp.cs.disasm(code, address, 1))
13261331
except StopIteration:
13271332
instr = None # Unsupported instruction
13281333
except IndexError:
13291334
instr = None # Likely invalid memory
1330-
code = b""
13311335
address_name = dp.exports.get(address, "")
13321336

13331337
module = ""
@@ -1353,6 +1357,8 @@ def _hook_code(uc: Uc, address, size, dp: Dumpulator):
13531357
line += instr.op_str
13541358
for reg in _get_regs(instr):
13551359
line += f"|{reg}=0x{dp.regs.__getattr__(reg):x}"
1360+
if instr.mnemonic in {"syscall", "sysenter"}:
1361+
line += f"|sequence_id=[{dp.sequence_id}]"
13561362
else:
13571363
line += f"??? (code: {code.hex()}, size: {hex(size)})"
13581364
line += "\n"
@@ -1459,7 +1465,7 @@ def syscall_arg(index):
14591465
return dp.regs.r10
14601466
return dp.args[index]
14611467

1462-
dp.info(f"syscall: {name}(")
1468+
dp.info(f"[{dp.sequence_id}] syscall: {name}(")
14631469
for i in range(0, argcount):
14641470
argname = argspec.args[1 + i]
14651471
argtype = argspec.annotations[argname]
@@ -1504,7 +1510,7 @@ def syscall_arg(index):
15041510
else:
15051511
dp.info(f"status = {status:x}")
15061512
dp.regs.cax = status
1507-
if dp._x64:
1513+
if dp.x64:
15081514
dp.regs.rcx = dp.regs.cip + 2
15091515
dp.regs.r11 = dp.regs.eflags
15101516
except UcError as err:
@@ -1513,6 +1519,8 @@ def syscall_arg(index):
15131519
traceback.print_exc()
15141520
dp.error(f"Exception thrown during syscall implementation, stopping emulation!")
15151521
dp.raise_kill(exc)
1522+
finally:
1523+
dp.sequence_id += 1
15161524
else:
15171525
dp.error(f"syscall index: {index:x} -> {name} not implemented!")
15181526
dp.raise_kill(NotImplementedError())
@@ -1524,7 +1532,7 @@ def _emulate_unsupported_instruction(dp: Dumpulator, instr: CsInsn):
15241532
if instr.id == X86_INS_RDRAND:
15251533
op: X86Op = instr.operands[0]
15261534
regname = instr.reg_name(op.reg)
1527-
if dp._x64 and op.size * 8 == 32:
1535+
if dp.x64 and op.size * 8 == 32:
15281536
regname = "r" + regname[1:]
15291537
print(f"emulated rdrand {regname}:{op.size * 8}, cip = {hex(instr.address)}+{instr.size}")
15301538
dp.regs[regname] = 42 # TODO: PRNG based on dmp hash

src/dumpulator/modules.py

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ class ModuleManager:
8787
_memory: MemoryManager
8888
_name_lookup: Dict[str, int] = field(default_factory=dict)
8989
_modules: Dict[int, Module] = field(default_factory=dict)
90+
main: int = 0
9091

9192
def add(self, pe: pefile.PE, path: str):
9293
module = Module(pe, path)

src/dumpulator/native.py

+25
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
STATUS_NOT_FOUND = 0xC0000225
2020
STATUS_MEMORY_NOT_ALLOCATED = 0xC00000A0
2121
STATUS_CONFLICTING_ADDRESSES = 0xC0000018
22+
STATUS_PORT_NOT_SET = 0xC0000353
2223

2324
# Exceptions
2425
DBG_PRINTEXCEPTION_C = 0x40010006
@@ -571,6 +572,30 @@ class FILE_BASIC_INFORMATION(ctypes.Structure):
571572
]
572573
return FILE_BASIC_INFORMATION()
573574

575+
def SECTION_IMAGE_INFORMATION(arch: Architecture):
576+
class SECTION_IMAGE_INFORMATION(ctypes.Structure):
577+
_alignment_ = arch.alignment()
578+
_fields_ = [
579+
("TransferAddress", arch.ptr_type()),
580+
("ZeroBits", ctypes.c_uint32),
581+
("MaximumStackSize", arch.ptr_type()),
582+
("CommittedStackSize", arch.ptr_type()),
583+
("SubSystemType", ctypes.c_uint32),
584+
("SubSystemMinorVersion", ctypes.c_uint16),
585+
("SubSystemMajorVersion", ctypes.c_uint16),
586+
("MajorOperatingSystemVersion", ctypes.c_uint16),
587+
("MinorOperatingSystemVersion", ctypes.c_uint16),
588+
("ImageCharacteristics", ctypes.c_uint16),
589+
("DllCharacteristics", ctypes.c_uint16),
590+
("Machine", ctypes.c_uint16),
591+
("ImageContainsCode", ctypes.c_uint8),
592+
("ImageFlags", ctypes.c_uint8),
593+
("LoaderFlags", ctypes.c_uint32),
594+
("ImageFileSize", ctypes.c_uint32),
595+
("CheckSum", ctypes.c_uint32),
596+
]
597+
return SECTION_IMAGE_INFORMATION()
598+
574599
def P(t):
575600
class P(PVOID):
576601
def __init__(self, ptr, mem_read):

src/dumpulator/ntprimitives.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,19 @@ class Architecture(object):
88
def __init__(self, x64: bool):
99
self._x64 = x64
1010

11+
@property
12+
def x64(self):
13+
return self._x64
14+
1115
def ptr_size(self):
1216
return 8 if self._x64 else 4
1317

18+
def ptr_type(self, t=None): # TODO: implement type
19+
return ctypes.c_uint64 if self._x64 else ctypes.c_uint32
20+
21+
def alignment(self):
22+
return 16 if self._x64 else 8
23+
1424
def read(self, addr: int, size: int) -> bytes:
1525
raise NotImplementedError()
1626

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

7585
return data.decode(encoding)
7686

77-
def ptr_type(self, t=None): # TODO: implement type
78-
return ctypes.c_uint64 if self._x64 else ctypes.c_uint32
79-
80-
def alignment(self):
81-
return 16 if self._x64 else 8
82-
8387
class PVOID:
8488
def __init__(self, ptr: int, arch: Architecture):
8589
self.ptr = ptr

src/dumpulator/ntsyscalls.py

+56-10
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def ZwAccessCheck(dp: Dumpulator,
3737
GrantedAccess: Annotated[P(ACCESS_MASK), SAL("_Out_")],
3838
AccessStatus: Annotated[P(NTSTATUS), SAL("_Out_")]
3939
):
40-
return STATUS_SUCCESS
40+
raise NotImplementedError()
4141

4242
@syscall
4343
def ZwAccessCheckAndAuditAlarm(dp: Dumpulator,
@@ -333,7 +333,7 @@ def ZwAllocateVirtualMemory(dp: Dumpulator,
333333
dp.memory.reserve(base, size, protect)
334334
dp.memory.commit(base, size)
335335
else:
336-
assert False
336+
raise NotImplementedError()
337337
return STATUS_SUCCESS
338338

339339
@syscall
@@ -634,7 +634,7 @@ def ZwCancelTimer(dp: Dumpulator,
634634
TimerHandle: Annotated[HANDLE, SAL("_In_")],
635635
CurrentState: Annotated[P(BOOLEAN), SAL("_Out_opt_")]
636636
):
637-
return STATUS_SUCCESS
637+
raise NotImplementedError()
638638

639639
@syscall
640640
def ZwCancelTimer2(dp: Dumpulator,
@@ -775,6 +775,7 @@ def ZwContinue(dp: Dumpulator,
775775
ContextRecord: Annotated[P(CONTEXT), SAL("_In_")],
776776
TestAlert: Annotated[BOOLEAN, SAL("_In_")]
777777
):
778+
# Trigger a context switch
778779
assert not TestAlert
779780
exception = ExceptionInfo()
780781
exception.type = ExceptionType.ContextSwitch
@@ -784,6 +785,12 @@ def ZwContinue(dp: Dumpulator,
784785
data = dp.read(ContextRecord.ptr, context_size)
785786
context = context_type.from_buffer(data)
786787
context.to_regs(dp.regs)
788+
# Modifying fs/gs also appears to reset fs_base/gs_base
789+
if dp.x64:
790+
dp.regs.gs_base = dp.teb
791+
else:
792+
dp.regs.fs_base = dp.teb
793+
dp.regs.gs_base = dp.teb - 2 * PAGE_SIZE
787794
exception.context = dp._uc.context_save()
788795
return exception
789796

@@ -857,8 +864,14 @@ def ZwCreateEvent(dp: Dumpulator,
857864
InitialState: Annotated[BOOLEAN, SAL("_In_")]
858865
):
859866
assert DesiredAccess == 0x1f0003
860-
assert ObjectAttributes == 0
861-
event = EventObject(EventType, InitialState)
867+
if ObjectAttributes != 0:
868+
attributes = ObjectAttributes[0]
869+
assert attributes.ObjectName == 0
870+
assert attributes.RootDirectory == 0
871+
assert attributes.SecurityDescriptor == 0
872+
assert attributes.SecurityQualityOfService == 0
873+
assert attributes.Attributes == 2 # OBJ_INHERIT
874+
event = EventObject(EventType, InitialState != 0)
862875
handle = dp.handles.new(event)
863876
EventHandle.write_ptr(handle)
864877
return STATUS_SUCCESS
@@ -2465,7 +2478,7 @@ def ZwOpenProcessToken(dp: Dumpulator,
24652478
assert ProcessHandle == dp.NtCurrentProcess()
24662479
assert DesiredAccess == 0x20
24672480
# TODO: TokenHandle should be -6 or something
2468-
handle = dp.handles.new(ProcessTokenHandle(ProcessHandle))
2481+
handle = dp.handles.new(ProcessTokenObject(ProcessHandle))
24692482
print(f"process token: {hex(handle)}")
24702483
TokenHandle.write_ptr(handle)
24712484
return STATUS_SUCCESS
@@ -2495,7 +2508,7 @@ def ZwOpenSection(dp: Dumpulator,
24952508
DesiredAccess: Annotated[ACCESS_MASK, SAL("_In_")],
24962509
ObjectAttributes: Annotated[P(OBJECT_ATTRIBUTES), SAL("_In_")]
24972510
):
2498-
return STATUS_NOT_IMPLEMENTED
2511+
raise NotImplementedError()
24992512

25002513
@syscall
25012514
def ZwOpenSemaphore(dp: Dumpulator,
@@ -2953,13 +2966,19 @@ def ZwQueryInformationProcess(dp: Dumpulator,
29532966
ProcessInformationLength: Annotated[ULONG, SAL("_In_")],
29542967
ReturnLength: Annotated[P(ULONG), SAL("_Out_opt_")]
29552968
):
2956-
assert (ProcessHandle == dp.NtCurrentProcess())
2957-
if ProcessInformationClass in [PROCESSINFOCLASS.ProcessDebugPort, PROCESSINFOCLASS.ProcessDebugObjectHandle]:
2969+
assert ProcessHandle == dp.NtCurrentProcess()
2970+
if ProcessInformationClass == PROCESSINFOCLASS.ProcessDebugPort:
29582971
assert ProcessInformationLength == dp.ptr_size()
29592972
dp.write_ptr(ProcessInformation.ptr, 0)
29602973
if ReturnLength != 0:
29612974
dp.write_ulong(ReturnLength.ptr, dp.ptr_size())
29622975
return STATUS_SUCCESS
2976+
elif ProcessInformationClass == PROCESSINFOCLASS.ProcessDebugObjectHandle:
2977+
assert ProcessInformationLength == dp.ptr_size()
2978+
dp.write_ptr(ProcessInformation.ptr, 0)
2979+
if ReturnLength != 0:
2980+
dp.write_ulong(ReturnLength.ptr, dp.ptr_size())
2981+
return STATUS_PORT_NOT_SET
29632982
elif ProcessInformationClass == PROCESSINFOCLASS.ProcessDefaultHardErrorMode:
29642983
assert ProcessInformationLength == 4
29652984
dp.write_ulong(ProcessInformation.ptr, 1)
@@ -2972,6 +2991,33 @@ def ZwQueryInformationProcess(dp: Dumpulator,
29722991
if ReturnLength.ptr:
29732992
dp.write_ulong(ReturnLength.ptr, 4)
29742993
return STATUS_SUCCESS
2994+
elif ProcessInformationClass == PROCESSINFOCLASS.ProcessImageInformation:
2995+
sii = SECTION_IMAGE_INFORMATION(dp)
2996+
assert ProcessInformationLength == ctypes.sizeof(sii)
2997+
module = dp.modules[dp.modules.main]
2998+
pe = module.pe
2999+
opt = pe.OPTIONAL_HEADER
3000+
sii.TransferAddress = module.entry
3001+
sii.ZeroBits = 0
3002+
sii.MaximumStackSize = opt.SizeOfStackReserve
3003+
sii.CommittedStackSize = opt.SizeOfStackCommit # TODO: more might be committed, check PEB
3004+
sii.SubSystemType = opt.Subsystem
3005+
sii.SubSystemMinorVersion = opt.MinorSubsystemVersion
3006+
sii.SubSystemMajorVersion = opt.MajorSubsystemVersion
3007+
sii.MinorOperatingSystemVersion = opt.MinorOperatingSystemVersion
3008+
sii.MajorOperatingSystemVersion = opt.MajorOperatingSystemVersion
3009+
sii.ImageCharacteristics = pe.FILE_HEADER.Characteristics # TODO
3010+
sii.DllCharacteristics = opt.DllCharacteristics # TODO
3011+
sii.Machine = pe.FILE_HEADER.Machine
3012+
sii.ImageContainsCode = 1
3013+
sii.ImageFlags = 1 # TODO
3014+
sii.LoaderFlags = 0 # TODO
3015+
sii.ImageFileSize = module.size # TODO: best we can do?
3016+
sii.CheckSum = opt.CheckSum
3017+
ProcessInformation.write(bytes(sii))
3018+
if ReturnLength.ptr:
3019+
dp.write_ulong(ReturnLength.ptr, ctypes.sizeof(sii))
3020+
return STATUS_SUCCESS
29753021
raise NotImplementedError()
29763022

29773023
@syscall
@@ -4437,7 +4483,7 @@ def ZwTerminateThread(dp: Dumpulator,
44374483
ExitStatus: Annotated[NTSTATUS, SAL("_In_")]
44384484
):
44394485
assert ThreadHandle == dp.NtCurrentThread()
4440-
return STATUS_NOT_IMPLEMENTED
4486+
raise NotImplementedError()
44414487

44424488
@syscall
44434489
def ZwTestAlert(dp: Dumpulator

0 commit comments

Comments
 (0)