Skip to content

feat(wifi): extend wifi_eapol_pcap to the M2+M3 handshake pairing#75

Merged
xunholy merged 1 commit into
mainfrom
feat/wifi-eapol-pcap-m2m3
Jun 21, 2026
Merged

feat(wifi): extend wifi_eapol_pcap to the M2+M3 handshake pairing#75
xunholy merged 1 commit into
mainfrom
feat/wifi-eapol-pcap-m2m3

Conversation

@xunholy

@xunholy xunholy commented Jun 21, 2026

Copy link
Copy Markdown
Owner

What

wifi_eapol_pcap (v0.719) covered the M1+M2 case. The M2+M3 pairing was deferred three fires for one reason: the hashcat message_pair byte couldn't be anchored offline. That blocker dissolved — the repo's own canonical, hashcat-anchored vector in internal/hashcat/hc22000_test.go uses message_pair "a2", whose low three bits (0xa2 & 0x07 = 0x02) are hashcat's M2+M3 (EAPOL-from-M2) index, and whose eapol field is an M2 frame (key info 0x010a). So hashcat's published example is an M2+M3 case, confirming: ANonce from M3, MIC + frame from M2, message_pair index 2.

How

internal/eapolcap now also collects M3 frames (AP→STA, carrying the same ANonce the AP put in M1). When M1 was missed — common when a capture starts mid-handshake — an M2 is paired with its M3 by the same BSSID/STA and the replay-counter relationship M3.rc == M2.rc + 1 (the AP increments by one between message pairs). The emitted line takes its ANonce from the M3, MIC + MIC-zeroed frame from the M2, and carries message_pair 0x02.

finish() runs two passes — M1+M2 first, then M2+M3 — with a shared addHandshake helper that dedups by BSSID/STA/replay-counter/MIC, so M1+M2 wins when the full handshake is present and the M2 is never double-emitted.

Clean-capture lines carry no nonce-correction flags (0x10/0x20/0x80); M1+M4 / M3+M4 and the nonce-error-correction heuristics remain deferred (honest note in the tool description + gap-analysis).

Verification

Extends the existing oracle approach (I extract bytes, derive no crypto):

  • Synthetic capture M2(rc=N) + M3(rc=N+1), no M1 → one handshake, ANonce from M3, MIC from M2, WPA*02 line with message_pair 02 matches the hashcat-anchored hashcat.EAPOL builder.

  • M1+M2 preferred when full handshake present (single emit, message_pair 00).

  • M3 with non-consecutive replay counter → no pairing (no confidently-wrong output).

  • Pre-existing M1+M2 tests unchanged and still pass.

  • FuzzExtract (added M2+M3 seed) clean (~5.5M execs, 0 panics).

  • gofmt / go vet clean · golangci-lint run ./...0 issues · go test -race ./internal/eapolcap/ ./internal/tools/ok

Extends wifi_eapol_pcap; no new tool, registry unchanged.

v0.719 shipped wifi_eapol_pcap covering the M1+M2 case (ANonce from M1, MIC from
M2). The M2+M3, M1+M4 and M3+M4 pairings were deferred for one reason: the
hashcat message_pair byte couldn't be anchored offline, and a wrong value risks
confidently-wrong output. That blocker has now dissolved — the repo's own
canonical, hashcat-anchored vector in internal/hashcat/hc22000_test.go uses
message_pair "a2", whose low three bits (0xa2 & 0x07 = 0x02) are hashcat's
M2+M3-EAPOL-from-M2 index, and whose eapol field carries an M2 frame (key info
0x010a). So hashcat's published example IS an M2+M3 case, confirming: ANonce
from M3, MIC + MIC-bearing frame from M2, message_pair index 2.

This extends internal/eapolcap to that pairing. The M3 (AP -> STA) carries the
same ANonce the AP put in M1, so when M1 was missed — common when a capture
starts mid-handshake — an M2 can still be paired with its M3, identified by the
same BSSID/STA and the replay-counter relationship M3.rc == M2.rc + 1 (the AP
increments the counter by one between the two message pairs). The emitted line
is identical in form to the M1+M2 case (the M2 still supplies the MIC and the
MIC-zeroed EAPOL frame) but takes its ANonce from the M3 and carries
message_pair 0x02. Clean-capture lines carry no nonce-correction flags
(0x10/0x20/0x80); M1+M4 / M3+M4 and the nonce-error-correction heuristics remain
deferred.

finish() now runs two passes — M1+M2 first, then M2+M3 — and a shared
addHandshake helper dedups by BSSID/STA/replay-counter/MIC, so when the full
handshake is present M1+M2 wins and the M2 is never emitted twice.

Verification (extends the existing oracle approach): a synthetic capture with
M2 (rc=N) + M3 (rc=N+1) and no M1 extracts one handshake whose ANonce comes
from the M3, whose MIC comes from the M2, and whose WPA*02 line — with
message_pair 02 — matches the shipped, hashcat-anchored hashcat.EAPOL builder
fed the same fields. Plus: M1+M2-preferred-when-full-handshake-present (single
emit, message_pair 00), and an M3 with a non-consecutive replay counter does
not pair. The pre-existing M1+M2 tests are unchanged and still pass.
FuzzExtract (with an added M2+M3 seed) clean (~5.5M execs, 0 panics). gofmt /
go vet / golangci-lint (0 issues) / go test -race (eapolcap, tools) green.

Extends wifi_eapol_pcap (internal/eapolcap); no new tool, registry unchanged.
@xunholy
xunholy merged commit ae3c2d7 into main Jun 21, 2026
10 of 11 checks passed
@xunholy
xunholy deleted the feat/wifi-eapol-pcap-m2m3 branch June 21, 2026 10:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant