Skip to content

Commit 9cfd9ee

Browse files
authored
Merge pull request #69 from mrexodia/better-stopping
Fix Ctrl+C while tracing
2 parents b542dc6 + 6f045df commit 9cfd9ee

File tree

1 file changed

+78
-69
lines changed

1 file changed

+78
-69
lines changed

src/dumpulator/dumpulator.py

+78-69
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,8 @@ def __init__(self, minidump_file, *, trace=False, quiet=False, thread_id=None, d
321321
self.handles = HandleManager()
322322
self._setup_handles()
323323
self._setup_registry()
324-
self.kill_me = None
324+
self.stopped = False
325+
self.kill_exception = None
325326
self.exit_code = None
326327
self.exports = self._all_exports()
327328
self._exception = UnicornExceptionInfo()
@@ -457,28 +458,26 @@ def _setup_pebteb(self, thread):
457458
self.teb = thread.Teb & 0xFFFFFFFFFFFFF000
458459

459460
# Handle WoW64 support
460-
def patch_wow64(patch_addr):
461-
# See: https://opcode0x90.wordpress.com/2007/05/18/kifastsystemcall-hook/
462-
# mov edx, esp; sysenter; ret
463-
KiFastSystemCall = b"\x8B\xD4\x0F\x34\x90\x90\xC3"
464-
self.write(patch_addr, KiFastSystemCall)
465-
self.wow64 = True
466-
467461
ntdll = self.modules["ntdll.dll"]
468462
Wow64Transition = ntdll.find_export("Wow64Transition")
469463
ZwWow64ReadVirtualMemory64 = ntdll.find_export("ZwWow64ReadVirtualMemory64")
470464
if Wow64Transition:
471465
# This exists from Windows 10 1607 (Build: 14393)
472466
patch_addr = self.read_ptr(Wow64Transition.address)
473467
self.info(f"Patching Wow64Transition: [{hex(Wow64Transition.address)}] -> {hex(patch_addr)}")
474-
patch_wow64(patch_addr)
468+
# See: https://opcode0x90.wordpress.com/2007/05/18/kifastsystemcall-hook/
469+
# sysenter; nop; nop; ret
470+
self.write(patch_addr, b"\x0F\x34\x90\x90\xC3")
471+
self.wow64 = True
475472
elif ZwWow64ReadVirtualMemory64:
476473
# This function exists since Windows XP
477474
# TODO: Implement by finding EA ???????? 3300 in wow64cpu.dll instead
478475
# Reference: https://github.com/x64dbg/ScyllaHide/blob/a727ac39/InjectorCLI/RemoteHook.cpp#L354-L434
479476
patch_addr = self.read_ptr(self.teb + 0xC0)
480477
self.error(f"Unsupported WoW64 OS version detected, trampoline: {hex(patch_addr)}")
481-
patch_wow64(patch_addr)
478+
# sysenter; nop; nop; jmp [esp]
479+
self.write(patch_addr, b"\x0F\x34\x90\x90\xFF\x24\x24")
480+
self.wow64 = True
482481
else:
483482
self.wow64 = False
484483

@@ -945,6 +944,8 @@ def handle_exception(self):
945944

946945
if self._exception_hook is not None:
947946
hook_result = self._exception_hook(self._exception)
947+
if self.stopped:
948+
return None
948949
if hook_result is not None:
949950
# Clear the pending exception
950951
self._last_exception = self._exception
@@ -1075,12 +1076,16 @@ def write_stack(cur_ptr: int, data: bytes):
10751076
return self.KiUserExceptionDispatcher
10761077

10771078
def start(self, begin, end=0xffffffffffffffff, count=0) -> None:
1079+
# Clear stop state
1080+
self.stopped = False
1081+
self.kill_exception = None
1082+
self.exit_code = None
10781083
# Clear exceptions before starting
10791084
self._exception = UnicornExceptionInfo()
10801085
emu_begin = begin
10811086
emu_until = end
10821087
emu_count = count
1083-
while True:
1088+
while not self.stopped:
10841089
try:
10851090
if self._exception.type != ExceptionType.NoException:
10861091
if self._exception.final:
@@ -1095,6 +1100,8 @@ def start(self, begin, end=0xffffffffffffffff, count=0) -> None:
10951100

10961101
try:
10971102
emu_begin = self.handle_exception()
1103+
if self.stopped:
1104+
break
10981105
except Exception:
10991106
traceback.print_exc()
11001107
self.error(f"exception during exception handling (stack overflow?)")
@@ -1119,15 +1126,14 @@ def start(self, begin, end=0xffffffffffffffff, count=0) -> None:
11191126
emu_count = self._exception.tb_icount + 1
11201127

11211128
self.info(f"emu_start({hex(emu_begin)}, {hex(emu_until)}, {emu_count})")
1122-
self.kill_me = None
11231129
self._uc.emu_start(emu_begin, until=emu_until, count=emu_count)
11241130
self.info(f'emulation finished, cip = {hex(self.regs.cip)}')
11251131
if self.exit_code is not None:
11261132
self.info(f"exit code: {hex(self.exit_code)}")
11271133
break
11281134
except UcError as err:
1129-
if self.kill_me is not None and type(self.kill_me) is not UcError:
1130-
raise self.kill_me
1135+
if self.kill_exception is not None and type(self.kill_exception) is not UcError:
1136+
raise self.kill_exception from None
11311137
if self._exception.type != ExceptionType.NoException:
11321138
# Handle the exception outside of the except handler
11331139
continue
@@ -1144,17 +1150,17 @@ def stop(self, exit_code=None) -> None:
11441150
except Exception:
11451151
traceback.print_exc()
11461152
self.error("Invalid type passed to exit_code!")
1153+
self.stopped = True
11471154
self._uc.emu_stop()
11481155

11491156
def raise_kill(self, exc=None):
11501157
# HACK: You need to use this to exit from hooks (although it might not always work)
11511158
self.regs.cip = FORCE_KILL_ADDR
1152-
self.kill_me = exc
1153-
if exc is not None:
1154-
return exc
1155-
else:
1156-
self.kill_me = True
1157-
self._uc.emu_stop()
1159+
self.stop()
1160+
if exc is None:
1161+
exc = Exception()
1162+
self.kill_exception = exc
1163+
return exc
11581164

11591165
def NtCurrentProcess(self):
11601166
return 0xFFFFFFFFFFFFFFFF if self._x64 else 0xFFFFFFFF
@@ -1294,12 +1300,12 @@ def _hook_code_exception(uc: Uc, address, size, dp: Dumpulator):
12941300

12951301
def _hook_mem(uc: Uc, access, address, size, value, dp: Dumpulator):
12961302
if dp._pages.handle_lazy_page(address, min(size, PAGE_SIZE)):
1297-
dp.debug(f"committed lazy page {hex(address)}[{hex(size)}]")
1303+
dp.debug(f"committed lazy page {hex(address)}[{hex(size)}] (cip: {hex(dp.regs.cip)})")
12981304
return True
12991305

13001306
fetch_accesses = [UC_MEM_FETCH, UC_MEM_FETCH_PROT, UC_MEM_FETCH_UNMAPPED]
1301-
if access == UC_MEM_FETCH_UNMAPPED and FORCE_KILL_ADDR - 0x10 <= address <= FORCE_KILL_ADDR + 0x10 and dp.kill_me is not None:
1302-
dp.error(f"forced exit memory operation {access} of {hex(address)}[{hex(size)}] = {hex(value)}")
1307+
if dp.stopped and access == UC_MEM_FETCH_UNMAPPED and FORCE_KILL_ADDR - 0x10 <= address <= FORCE_KILL_ADDR + 0x10:
1308+
dp.error(f"force exit fetch of {hex(address)}[{hex(size)}]")
13031309
return False
13041310
if dp._exception.final and access in fetch_accesses:
13051311
dp.info(f"fetch from {hex(address)}[{size}] already reported")
@@ -1370,7 +1376,7 @@ def _hook_mem(uc: Uc, access, address, size, value, dp: Dumpulator):
13701376
dp._exception.final = True
13711377

13721378
# Stop emulation (we resume it on KiUserExceptionDispatcher later)
1373-
dp.stop()
1379+
dp._uc.emu_stop()
13741380
return False
13751381

13761382
# There should not be an exception active
@@ -1389,7 +1395,7 @@ def _hook_mem(uc: Uc, access, address, size, value, dp: Dumpulator):
13891395
dp._exception = exception
13901396

13911397
# Stop emulation (we resume execution later)
1392-
dp.stop()
1398+
dp._uc.emu_stop()
13931399
return False
13941400
except AssertionError as err:
13951401
traceback.print_exc()
@@ -1423,50 +1429,53 @@ def _get_regs(instr, include_write=False):
14231429
return regs
14241430

14251431
def _hook_code(uc: Uc, address, size, dp: Dumpulator):
1426-
code = b""
14271432
try:
1428-
code = dp.read(address, min(size, 15))
1429-
instr = next(dp.cs.disasm(code, address, 1))
1430-
except StopIteration:
1431-
instr = None # Unsupported instruction
1432-
except IndexError:
1433-
instr = None # Likely invalid memory
1434-
1435-
address_name = dp.exports.get(address, "")
1433+
code = b""
1434+
try:
1435+
code = dp.read(address, min(size, 15))
1436+
instr = next(dp.cs.disasm(code, address, 1))
1437+
except StopIteration:
1438+
instr = None # Unsupported instruction
1439+
except IndexError:
1440+
instr = None # Likely invalid memory
1441+
address_name = dp.exports.get(address, "")
14361442

1437-
module = ""
1438-
if dp.last_module and address in dp.last_module:
1439-
# same module again
1440-
pass
1441-
else:
1442-
# new module
1443-
dp.last_module = dp.modules.find(address)
1444-
if dp.last_module:
1445-
module = dp.last_module.name
1446-
1447-
if address_name:
1448-
address_name = " " + address_name
1449-
elif module:
1450-
address_name = " " + module
1451-
1452-
line = f"{hex(address)}{address_name}|"
1453-
if instr is not None:
1454-
line += instr.mnemonic
1455-
if instr.op_str:
1456-
line += " "
1457-
line += instr.op_str
1458-
for reg in _get_regs(instr):
1459-
line += f"|{reg}={hex(dp.regs.__getattr__(reg))}"
1460-
if instr.mnemonic == "call":
1461-
# print return address
1462-
ret_address = address + instr.size
1463-
line += f"|return_address={hex(ret_address)}"
1464-
if instr.mnemonic in {"syscall", "sysenter"}:
1465-
line += f"|sequence_id=[{dp.sequence_id}]"
1466-
else:
1467-
line += f"??? (code: {code.hex()}, size: {hex(size)})"
1468-
line += "\n"
1469-
dp.trace.write(line)
1443+
module = ""
1444+
if dp.last_module and address in dp.last_module:
1445+
# same module again
1446+
pass
1447+
else:
1448+
# new module
1449+
dp.last_module = dp.modules.find(address)
1450+
if dp.last_module:
1451+
module = dp.last_module.name
1452+
1453+
if address_name:
1454+
address_name = " " + address_name
1455+
elif module:
1456+
address_name = " " + module
1457+
1458+
line = f"{hex(address)}{address_name}|"
1459+
if instr is not None:
1460+
line += instr.mnemonic
1461+
if instr.op_str:
1462+
line += " "
1463+
line += instr.op_str
1464+
for reg in _get_regs(instr):
1465+
line += f"|{reg}={hex(dp.regs.__getattr__(reg))}"
1466+
if instr.mnemonic == "call":
1467+
# print return address
1468+
ret_address = address + instr.size
1469+
line += f"|return_address={hex(ret_address)}"
1470+
elif instr.mnemonic in {"syscall", "sysenter"}:
1471+
line += f"|sequence_id=[{dp.sequence_id}]"
1472+
else:
1473+
line += f"??? (code: {code.hex()}, size: {hex(size)})"
1474+
line += "\n"
1475+
dp.trace.write(line)
1476+
except (KeyboardInterrupt, SystemExit) as e:
1477+
dp.stop()
1478+
raise e
14701479

14711480
def _unicode_string_to_string(dp: Dumpulator, arg: P[UNICODE_STRING]):
14721481
try:
@@ -1629,7 +1638,7 @@ def syscall_arg(index):
16291638
if isinstance(status, ExceptionInfo):
16301639
print("context switch, stopping emulation")
16311640
dp._exception = status
1632-
raise dp.raise_kill(UcError(UC_ERR_EXCEPTION)) from None
1641+
raise UcError(UC_ERR_EXCEPTION)
16331642
else:
16341643
dp.info(f"status = {hex(status)}")
16351644
dp.regs.cax = status
@@ -1671,7 +1680,7 @@ def _hook_invalid(uc: Uc, dp: Dumpulator):
16711680
if dp.trace:
16721681
dp.trace.flush()
16731682
# HACK: unicorn cannot gracefully exit in all contexts
1674-
if dp.kill_me:
1683+
if dp.stopped:
16751684
dp.error(f"terminating emulation...")
16761685
return False
16771686
dp.error(f"invalid instruction at {hex(address)}")

0 commit comments

Comments
 (0)