"""patch_v11_test.py [--revert] v11: NULL-check / crash-guard patches for two observed crash sites. Site 1 — delete_contents (Barris's crash at 0x0058712f) Function at 0x005870f0. Inner trim loop has a state inconsistency path that sets EDI=0 and falls through to MOV EAX,[EDI] → AV. Patch: change the `JMP +0x07` at 0x00587126 (`eb 07`) to `JMP +0x42` (`eb 42`) → jumps to function epilogue at 0x0058716a (pop edi; pop esi; ret). Bails on empty-bucket-pass instead of UAF. Site 2 — ~GXTri3Mesh slot 0 (Kolossos's crash at 0x005e565d) Destructor walks 5 subobject slots via vtable. The dereference at 0x005e565d (`MOV ECX,[EAX]`) crashes when slot pointer is freed. Patch: replace the 9-byte deref+call+clear sequence (`8b 08 50 ff 51 08 89 5e 08`) with `89 5e 08 90 90 90 90 90 90`. Clears the slot but skips the virtual call → trades subobject leak for no crash. This is the v11 patcher. Only patches the ONE slot we've observed crashing (slot 0 / +0x08 on the mesh). Other slots may need same treatment if they crash later. """ import argparse, ctypes, ctypes.wintypes as wt, sys SITES = [ ("site1 delete_contents JMP", 0x00587126, bytes([0xEB, 0x07]), bytes([0xEB, 0x42])), ("site2 ~GXTri3Mesh slot 0", 0x005E565D, bytes([0x8B, 0x08, 0x50, 0xFF, 0x51, 0x08, 0x89, 0x5E, 0x08]), bytes([0x89, 0x5E, 0x08, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90])), ] PROCESS_VM_READ = 0x10 PROCESS_VM_WRITE = 0x20 PROCESS_VM_OPERATION = 0x8 PROCESS_QUERY_INFORMATION = 0x400 PAGE_EXECUTE_READWRITE = 0x40 k32 = ctypes.windll.kernel32 OpenProcess = k32.OpenProcess OpenProcess.argtypes = [wt.DWORD, wt.BOOL, wt.DWORD]; OpenProcess.restype = wt.HANDLE CloseHandle = k32.CloseHandle CloseHandle.argtypes = [wt.HANDLE]; CloseHandle.restype = wt.BOOL WriteProcessMemory = k32.WriteProcessMemory WriteProcessMemory.argtypes = [wt.HANDLE, wt.LPVOID, wt.LPCVOID, ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t)] WriteProcessMemory.restype = wt.BOOL ReadProcessMemory = k32.ReadProcessMemory ReadProcessMemory.argtypes = [wt.HANDLE, wt.LPCVOID, wt.LPVOID, ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t)] ReadProcessMemory.restype = wt.BOOL VirtualProtectEx = k32.VirtualProtectEx VirtualProtectEx.argtypes = [wt.HANDLE, wt.LPVOID, ctypes.c_size_t, wt.DWORD, ctypes.POINTER(wt.DWORD)] VirtualProtectEx.restype = wt.BOOL def read_bytes(h, addr, n): buf = (ctypes.c_ubyte * n)() sz = ctypes.c_size_t(0) if not ReadProcessMemory(h, addr, buf, n, ctypes.byref(sz)): raise OSError(f"read 0x{addr:08x} err={ctypes.get_last_error()}") return bytes(buf[:sz.value]) def write_bytes(h, addr, data): old = wt.DWORD(0) if not VirtualProtectEx(h, addr, len(data), PAGE_EXECUTE_READWRITE, ctypes.byref(old)): raise OSError(f"VirtualProtectEx 0x{addr:08x} err={ctypes.get_last_error()}") sz = ctypes.c_size_t(0) ok = WriteProcessMemory(h, addr, data, len(data), ctypes.byref(sz)) err = ctypes.get_last_error() if not ok else 0 restored = wt.DWORD(0) VirtualProtectEx(h, addr, len(data), old.value, ctypes.byref(restored)) if not ok: raise OSError(f"write 0x{addr:08x} err={err}") def apply_or_revert(h, label, va, orig, patched, revert): cur = read_bytes(h, va, len(orig)) print(f" {label} @ 0x{va:08x}: current {cur.hex()}") if revert: if cur == orig: print(f" already original") elif cur == patched: write_bytes(h, va, orig) print(f" reverted; now: {read_bytes(h, va, len(orig)).hex()}") else: print(f" UNEXPECTED bytes — refusing"); sys.exit(3) return if cur == patched: print(f" already patched") elif cur == orig: write_bytes(h, va, patched) print(f" patched; now: {read_bytes(h, va, len(orig)).hex()}") else: print(f" UNEXPECTED bytes — refusing. Expected: {orig.hex()}") sys.exit(4) def main(): ap = argparse.ArgumentParser() ap.add_argument("pid", type=int) ap.add_argument("--revert", action="store_true") args = ap.parse_args() h = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION, False, args.pid) if not h: print(f"OpenProcess({args.pid}) err={ctypes.get_last_error()}"); sys.exit(2) print(f"PID {args.pid}") for label, va, orig, patched in SITES: apply_or_revert(h, label, va, orig, patched, args.revert) print(f" OK") CloseHandle(h) if __name__ == "__main__": main()