mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-20 05:00:27 +02:00
227 lines
8.9 KiB
Python
227 lines
8.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""c2.dll live-debug harness (64-bit host / 32-bit WOW64 debuggee).
|
|
|
|
C2.Dll loads in-process in CL.Exe at its fixed preferred base 0x10700000
|
|
(verified: no relocation), so static VAs are literal live addresses.
|
|
|
|
Usage: import and call run(bp_vas, on_hit) where bp_vas is a list of live
|
|
c2 VAs and on_hit(ctx, mem, va, hitno) is a callback receiving the WOW64
|
|
register context and a memory reader. The harness plants INT3, and on each
|
|
hit reads the 32-bit context, invokes the callback, then transparently
|
|
restores the byte, single-steps over, and re-arms.
|
|
"""
|
|
import ctypes as C
|
|
from ctypes import wintypes as W
|
|
import json
|
|
|
|
k32 = C.WinDLL('kernel32', use_last_error=True)
|
|
DEBUG_ONLY_THIS_PROCESS = 0x00000002
|
|
CREATE_NEW_CONSOLE = 0x00000010
|
|
INFINITE = 0xFFFFFFFF
|
|
DBG_CONTINUE = 0x00010002
|
|
DBG_EXCEPTION_NOT_HANDLED = 0x80010001
|
|
EXCEPTION_DEBUG_EVENT = 1
|
|
CREATE_PROCESS_DEBUG_EVENT = 3
|
|
EXIT_PROCESS_DEBUG_EVENT = 5
|
|
LOAD_DLL_DEBUG_EVENT = 6
|
|
EXC_BREAKPOINT = 0x80000003
|
|
EXC_SINGLE_STEP = 0x80000004
|
|
LPVOID = C.c_void_p
|
|
ULPTR = C.c_uint64
|
|
C2_BASE = 0x10700000
|
|
|
|
# WOW64_CONTEXT (x86 CONTEXT) — flags
|
|
WOW64_CONTEXT_i386 = 0x00010000
|
|
CONTEXT_CONTROL = WOW64_CONTEXT_i386 | 0x1
|
|
CONTEXT_INTEGER = WOW64_CONTEXT_i386 | 0x2
|
|
CONTEXT_SEG = WOW64_CONTEXT_i386 | 0x4
|
|
CONTEXT_FLOAT = WOW64_CONTEXT_i386 | 0x8
|
|
CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEG
|
|
CONTEXT_ALL = CONTEXT_FULL | CONTEXT_FLOAT
|
|
|
|
|
|
class FLOATING_SAVE_AREA(C.Structure):
|
|
_fields_ = [("ControlWord", W.DWORD), ("StatusWord", W.DWORD),
|
|
("TagWord", W.DWORD), ("ErrorOffset", W.DWORD),
|
|
("ErrorSelector", W.DWORD), ("DataOffset", W.DWORD),
|
|
("DataSelector", W.DWORD), ("RegisterArea", C.c_ubyte * 80),
|
|
("Cr0NpxState", W.DWORD)]
|
|
|
|
|
|
class WOW64_CONTEXT(C.Structure):
|
|
_fields_ = [("ContextFlags", W.DWORD),
|
|
("Dr0", W.DWORD), ("Dr1", W.DWORD), ("Dr2", W.DWORD),
|
|
("Dr3", W.DWORD), ("Dr6", W.DWORD), ("Dr7", W.DWORD),
|
|
("FloatSave", FLOATING_SAVE_AREA),
|
|
("SegGs", W.DWORD), ("SegFs", W.DWORD), ("SegEs", W.DWORD),
|
|
("SegDs", W.DWORD), ("Edi", W.DWORD), ("Esi", W.DWORD),
|
|
("Ebx", W.DWORD), ("Edx", W.DWORD), ("Ecx", W.DWORD),
|
|
("Eax", W.DWORD), ("Ebp", W.DWORD), ("Eip", W.DWORD),
|
|
("SegCs", W.DWORD), ("EFlags", W.DWORD), ("Esp", W.DWORD),
|
|
("SegSs", W.DWORD), ("ExtendedRegisters", C.c_ubyte * 512)]
|
|
|
|
|
|
class STARTUPINFOW(C.Structure):
|
|
_fields_ = [("cb", W.DWORD), ("r0", W.LPWSTR), ("r1", W.LPWSTR),
|
|
("r2", W.LPWSTR), ("dwX", W.DWORD), ("dwY", W.DWORD),
|
|
("dwXSize", W.DWORD), ("dwYSize", W.DWORD), ("dwXCC", W.DWORD),
|
|
("dwYCC", W.DWORD), ("dwFill", W.DWORD), ("dwFlags", W.DWORD),
|
|
("wShow", W.WORD), ("cbR2", W.WORD), ("lpR2", LPVOID),
|
|
("hIn", W.HANDLE), ("hOut", W.HANDLE), ("hErr", W.HANDLE)]
|
|
|
|
|
|
class PROCESS_INFORMATION(C.Structure):
|
|
_fields_ = [("hProcess", W.HANDLE), ("hThread", W.HANDLE),
|
|
("dwPid", W.DWORD), ("dwTid", W.DWORD)]
|
|
|
|
|
|
class EXCEPTION_RECORD(C.Structure):
|
|
_fields_ = [("Code", W.DWORD), ("Flags", W.DWORD), ("Rec", ULPTR),
|
|
("Addr", ULPTR), ("nParams", W.DWORD), ("Info", ULPTR * 15)]
|
|
|
|
|
|
class EXCEPTION_DEBUG_INFO(C.Structure):
|
|
_fields_ = [("ExceptionRecord", EXCEPTION_RECORD), ("dwFirstChance", W.DWORD)]
|
|
|
|
|
|
class CREATE_PROCESS_DEBUG_INFO(C.Structure):
|
|
_fields_ = [("hFile", W.HANDLE), ("hProcess", W.HANDLE), ("hThread", W.HANDLE),
|
|
("lpBaseOfImage", ULPTR), ("off", W.DWORD), ("sz", W.DWORD),
|
|
("tlb", ULPTR), ("start", ULPTR), ("name", ULPTR), ("uni", W.WORD)]
|
|
|
|
|
|
class LOAD_DLL_DEBUG_INFO(C.Structure):
|
|
_fields_ = [("hFile", W.HANDLE), ("lpBaseOfDll", ULPTR), ("off", W.DWORD),
|
|
("sz", W.DWORD), ("name", ULPTR), ("uni", W.WORD)]
|
|
|
|
|
|
class EXIT_PROCESS_DEBUG_INFO(C.Structure):
|
|
_fields_ = [("dwExitCode", W.DWORD)]
|
|
|
|
|
|
class DBG_U(C.Union):
|
|
_fields_ = [("Exception", EXCEPTION_DEBUG_INFO),
|
|
("CreateProcessInfo", CREATE_PROCESS_DEBUG_INFO),
|
|
("LoadDll", LOAD_DLL_DEBUG_INFO),
|
|
("ExitProcess", EXIT_PROCESS_DEBUG_INFO),
|
|
("_raw", C.c_ubyte * 160)]
|
|
|
|
|
|
class DEBUG_EVENT(C.Structure):
|
|
_fields_ = [("code", W.DWORD), ("pid", W.DWORD), ("tid", W.DWORD), ("u", DBG_U)]
|
|
|
|
|
|
k32.OpenThread.restype = W.HANDLE
|
|
k32.OpenThread.argtypes = [W.DWORD, W.BOOL, W.DWORD]
|
|
k32.Wow64GetThreadContext.argtypes = [W.HANDLE, C.POINTER(WOW64_CONTEXT)]
|
|
k32.Wow64SetThreadContext.argtypes = [W.HANDLE, C.POINTER(WOW64_CONTEXT)]
|
|
|
|
|
|
def _rpm(hproc, addr, n):
|
|
buf = (C.c_ubyte * n)()
|
|
got = C.c_size_t(0)
|
|
k32.ReadProcessMemory(hproc, LPVOID(addr), buf, n, C.byref(got))
|
|
return bytes(buf[:got.value])
|
|
|
|
|
|
PAGE_EXECUTE_READWRITE = 0x40
|
|
k32.VirtualProtectEx.argtypes = [W.HANDLE, LPVOID, C.c_size_t, W.DWORD, C.POINTER(W.DWORD)]
|
|
|
|
|
|
def _wpm(hproc, addr, data):
|
|
n = len(data)
|
|
buf = (C.c_ubyte * n).from_buffer_copy(data)
|
|
wrote = C.c_size_t(0)
|
|
old = W.DWORD(0)
|
|
k32.VirtualProtectEx(hproc, LPVOID(addr), n, PAGE_EXECUTE_READWRITE, C.byref(old))
|
|
ok = k32.WriteProcessMemory(hproc, LPVOID(addr), buf, n, C.byref(wrote))
|
|
k32.VirtualProtectEx(hproc, LPVOID(addr), n, old.value, C.byref(old))
|
|
k32.FlushInstructionCache(hproc, LPVOID(addr), n)
|
|
return ok and wrote.value == n
|
|
|
|
|
|
def run(bp_vas, on_hit, max_hits_per_bp=3, timeout_events=200000):
|
|
cmd = json.load(open('dbg_cmd.json'))
|
|
argv = cmd['argv']; root = cmd['root']
|
|
cmdline = ' '.join(('"%s"' % a if (' ' in a and not a.startswith('"')) else a)
|
|
for a in argv)
|
|
si = STARTUPINFOW(); si.cb = C.sizeof(si)
|
|
pi = PROCESS_INFORMATION()
|
|
if not k32.CreateProcessW(argv[0], C.create_unicode_buffer(cmdline), None, None,
|
|
False, DEBUG_ONLY_THIS_PROCESS | CREATE_NEW_CONSOLE,
|
|
None, root, C.byref(si), C.byref(pi)):
|
|
raise OSError('CreateProcessW %d' % C.get_last_error())
|
|
hproc = None
|
|
orig = {} # va -> original byte
|
|
planted = set()
|
|
pending = {} # tid -> va to re-arm after single-step
|
|
hits = {va: 0 for va in bp_vas}
|
|
de = DEBUG_EVENT()
|
|
n = 0
|
|
while n < timeout_events:
|
|
n += 1
|
|
if not k32.WaitForDebugEvent(C.byref(de), INFINITE):
|
|
break
|
|
status = DBG_CONTINUE
|
|
code = de.code
|
|
if code == CREATE_PROCESS_DEBUG_EVENT:
|
|
hproc = de.u.CreateProcessInfo.hProcess
|
|
elif code == LOAD_DLL_DEBUG_EVENT:
|
|
if de.u.LoadDll.lpBaseOfDll == C2_BASE:
|
|
for va in bp_vas:
|
|
orig[va] = _rpm(hproc, va, 1)
|
|
_wpm(hproc, va, b'\xcc')
|
|
planted.add(va)
|
|
elif code == EXCEPTION_DEBUG_EVENT:
|
|
exc = de.u.Exception.ExceptionRecord
|
|
addr = exc.Addr & 0xffffffff
|
|
if exc.Code == EXC_BREAKPOINT and addr in planted:
|
|
hth = k32.OpenThread(0x1FFFFF, False, de.tid)
|
|
ctx = WOW64_CONTEXT(); ctx.ContextFlags = CONTEXT_ALL
|
|
k32.Wow64GetThreadContext(hth, C.byref(ctx))
|
|
hits[addr] += 1
|
|
try:
|
|
on_hit(ctx, lambda a, l: _rpm(hproc, a, l), addr, hits[addr])
|
|
except Exception as e:
|
|
print('on_hit error:', e)
|
|
# restore original, back up EIP, single-step to re-arm
|
|
_wpm(hproc, addr, orig[addr])
|
|
ctx.Eip = addr
|
|
if hits[addr] < max_hits_per_bp:
|
|
ctx.EFlags |= 0x100 # TF
|
|
pending[de.tid] = addr
|
|
ctx.ContextFlags = CONTEXT_ALL
|
|
k32.Wow64SetThreadContext(hth, C.byref(ctx))
|
|
k32.CloseHandle(hth)
|
|
elif exc.Code == EXC_SINGLE_STEP and de.tid in pending:
|
|
va = pending.pop(de.tid)
|
|
_wpm(hproc, va, b'\xcc') # re-arm
|
|
elif exc.Code in (EXC_BREAKPOINT, EXC_SINGLE_STEP):
|
|
pass # loader breakpoint etc.
|
|
else:
|
|
status = DBG_EXCEPTION_NOT_HANDLED
|
|
elif code == EXIT_PROCESS_DEBUG_EVENT:
|
|
k32.ContinueDebugEvent(de.pid, de.tid, status)
|
|
if de.pid == pi.dwPid:
|
|
break
|
|
continue
|
|
k32.ContinueDebugEvent(de.pid, de.tid, status)
|
|
return hits
|
|
|
|
|
|
if __name__ == '__main__':
|
|
INVOKE = 0x107b6938 # _InvokeCompilerPass@12 (backend entry cl.exe calls)
|
|
|
|
def dump(ctx, mem, va, hitno):
|
|
# args are at [esp+4],[esp+8],[esp+0xc] (stdcall @12 = 3 dwords)
|
|
stk = mem(ctx.Esp, 16)
|
|
a = struct_unpack(stk)
|
|
print('HIT InvokeCompilerPass #%d eip=%#x esp=%#x ret=%#x args=(%#x,%#x,%#x)'
|
|
% (hitno, ctx.Eip, ctx.Esp, a[0], a[1], a[2], a[3]))
|
|
h = run([INVOKE], dump, max_hits_per_bp=5)
|
|
print('hit counts:', {hex(k): v for k, v in h.items()})
|
|
|
|
|
|
def struct_unpack(b):
|
|
import struct
|
|
return struct.unpack_from('<4I', b + b'\0' * 16) |