feat(wifi): extend wifi_eapol_pcap to the M2+M3 handshake pairing#75
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
wifi_eapol_pcap(v0.719) covered the M1+M2 case. The M2+M3 pairing was deferred three fires for one reason: the hashcatmessage_pairbyte couldn't be anchored offline. That blocker dissolved — the repo's own canonical, hashcat-anchored vector ininternal/hashcat/hc22000_test.gousesmessage_pair "a2", whose low three bits (0xa2 & 0x07 = 0x02) are hashcat's M2+M3 (EAPOL-from-M2) index, and whoseeapolfield is an M2 frame (key info0x010a). So hashcat's published example is an M2+M3 case, confirming: ANonce from M3, MIC + frame from M2,message_pairindex 2.How
internal/eapolcapnow 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 carriesmessage_pair0x02.finish()runs two passes — M1+M2 first, then M2+M3 — with a sharedaddHandshakehelper 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*02line withmessage_pair 02matches the hashcat-anchoredhashcat.EAPOLbuilder.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 vetclean ·golangci-lint run ./...→ 0 issues ·go test -race ./internal/eapolcap/ ./internal/tools/→okExtends
wifi_eapol_pcap; no new tool, registry unchanged.