⬢ DragonFlyBSD Kernel Audit
DF-3017 / hunt2.py
← back to finding ↓ download raw
#!/usr/bin/env python3
"""Lean hunt: panicking cpu=1, one big stack read, targeted cdev chase."""
import socket, time, sys
sys.path.insert(0, "/home/maxx/dfbsd/dfbsd/findings/poc/DF-3017")

_sock = socket.create_connection(("127.0.0.1", 1235), timeout=10)
_sock.settimeout(2.0)

def rsp(cmd, timeout=2.0):
    pkt = b"$" + cmd + b"#%02x" % (sum(cmd) & 0xFF)
    _sock.sendall(pkt)
    buf = b""
    t0 = time.time()
    while time.time() - t0 < timeout:
        try:
            d = _sock.recv(65536)
        except socket.timeout:
            break
        if not d:
            raise RuntimeError("eof")
        buf += d
        if buf.startswith(b"+$") or (buf.startswith(b"$") and b"#" in buf[1:]) \
           or (b"$" in buf and b"#" in buf[buf.index(b"$")+1:]):
            try: _sock.sendall(b"+")
            except Exception: pass
            i = buf.index(b"$"); j = buf.index(b"#", i + 1)
            return buf[i+1:j]
    raise RuntimeError("timeout: %r" % buf[:64])

# handshake
_sock.sendall(b"$qSupported#37")
time.sleep(0.3)
try: _sock.recv(65536)
except Exception: pass
_sock.sendall(b"+")

def rd(addr, n):
    body = rsp(b"m%x,%x" % (addr, n))
    if body.startswith(b"E"):
        raise RuntimeError("mem err " + body.decode())
    return bytes.fromhex(body.decode())

def rq(a):
    return int.from_bytes(rd(a, 8), "little")

rsp(b"Hg2")  # cpu1 (1-based)
g = rsp(b"g")
regs = {}
names = ["rax","rbx","rcx","rdx","rsi","rdi","rbp","rsp","r8","r9","r10",
         "r11","r12","r13","r14","r15","rip"]
for i, n in enumerate(names):
    regs[n] = int.from_bytes(bytes.fromhex(g[i*16:(i+1)*16].decode()), "little")
print("cpu1 rip=%x rbp=%x rsp=%x" % (regs["rip"], regs["rbp"], regs["rsp"]))

fp = regs["rbp"]
frames = []
for i in range(10):
    ret = rq(fp + 8)
    frames.append((fp, ret))
    fp = rq(fp)
    if fp == 0:
        break
for f, r in frames:
    print("  frame %x ret=%x" % (f, r))

# read the whole stack span once
lo = min(f for f, _ in frames) - 0x600
span = rd(lo, 0x1800)

DEAD = 0xffffffff81187fe0
def looks_cdev(q):
    try:
        ops = rq(q + 168)
        if ops in (DEAD, 0x40000000):
            return ops
        if 0xffffffff810e0000 <= ops < 0xffffffff810f0000:  # pts98_ops area
            return ops
    except Exception:
        pass
    return None

seen = set()
for off in range(0, len(span) - 8, 8):
    v = int.from_bytes(span[off:off+8], "little")
    if 0xfffff80000000000 <= v < 0xfffff81000000000:
        for d in (0, -0x140, -0x100, -0xc0, -0x80, -0x40, 0x40, 0x80, 0xc0, 0x100, 0x140):
            q = (v & ~7) + d
            if q in seen:
                continue
            seen.add(q)
            ops = looks_cdev(q)
            if ops:
                try:
                    nm = rd(q + 88, 12)
                    refw = rq(q + 232)
                    zbase = q & ~0x1ffff
                    zcpu = None
                    try:
                        zcpu = int.from_bytes(rd(zbase + 4, 4), "little")
                    except Exception:
                        pass
                    print("[CDEV] %x ops=%x name=%r refcnt=%x flags=%x zone=%x z_Cpu=%s"
                          % (q, ops, nm, refw & 0xffffffff, refw >> 32, zbase, zcpu))
                except Exception as e:
                    print("[CDEV] %x ops=%x (detail err %s)" % (q, ops, e))