fix: captura de handshake real (endianness EAPOL) e enriquece o parsing 802.11

- eapol.rs: key_info/replay_counter/key_data_len liam little-endian; são
  big-endian (802.1X segue ordem de rede), então handshakes reais nunca
  fechavam mesmo com deauth funcionando. testutil.rs e os geradores de
  quadro de teste corrigidos junto, mais um teste contra bytes de wire
  reais (não só contra o próprio encoder).
- inject.rs: deauth/disassoc agora grava Sequence Control crescente por
  quadro — reenviar o mesmo quadro era descartado como duplicata pelo
  driver/AP depois do primeiro envio.
- dot11.rs/scan.rs: parsing de AP/cliente bem mais completo (PMF
  obrigatório, WPS detalhado, fabricante por OUI, país, largura de banda,
  padrão PHY, privacidade) e histórico de sinal/eventos por cliente.
- handshake.rs/ccmp.rs: deriva a Temporal Key (CCMP) quando a senha é
  quebrada, o suficiente pra decifrar o tráfego em claro do par
  (BSSID, estação) cujo handshake foi capturado.
This commit is contained in:
Lucas Rayan committed 2026-10-24 14:53:32 -03:00
1 parent dab86d1fa7
commit 5477b8fb03
10 files changed
+2523 -19

No files matched your search

+40 -7
View File
@@ -55,7 +55,14 @@ pub fn parse(buf: &[u8]) -> Option<EapolKey> {
if descriptor_type != 2 && descriptor_type != 254 {
return None;
}
let key_info = u16::from_le_bytes([frame[5], frame[6]]);
// Todo campo multi-byte do descritor EAPOL-Key (Key Information, Key
// Length, Replay Counter, Key Data Length) é big-endian — é 802.1X,
// segue a mesma ordem de rede do cabeçalho EAPOL em si. Ler como
// little-endian (bug anterior) embaralhava os bits de ack/mic/secure e
// fazia praticamente todo handshake real cair em message=0, descartado
// silenciosamente — só não aparecia nos testes porque o gerador de
// quadro sintético cometia o mesmo engano, simetricamente.
let key_info = u16::from_be_bytes([frame[5], frame[6]]);
let key_version = (key_info & 0x0007) as u8;
let pairwise = key_info & KEY_INFO_PAIRWISE != 0;
let ack = key_info & KEY_INFO_ACK != 0;
@@ -75,12 +82,12 @@ pub fn parse(buf: &[u8]) -> Option<EapolKey> {
0
};
let replay_counter = u64::from_le_bytes(frame[9..17].try_into().ok()?);
let replay_counter = u64::from_be_bytes(frame[9..17].try_into().ok()?);
let mut nonce = [0u8; 32];
nonce.copy_from_slice(&frame[17..49]);
let mut mic_bytes = [0u8; 16];
mic_bytes.copy_from_slice(&frame[MIC_OFFSET..MIC_OFFSET + 16]);
let key_data_len = u16::from_le_bytes([frame[97], frame[98]]) as usize;
let key_data_len = u16::from_be_bytes([frame[97], frame[98]]) as usize;
let data_end = KEY_DATA_OFFSET.checked_add(key_data_len)?;
if data_end > frame.len() {
return None;
@@ -144,14 +151,14 @@ mod tests {
f[1] = EAPOL_TYPE_KEY;
f[2..4].copy_from_slice(&((total - 4) as u16).to_be_bytes());
f[4] = 2; // RSN descriptor
f[5..7].copy_from_slice(&key_info.to_le_bytes());
f[7..9].copy_from_slice(&16u16.to_le_bytes());
f[9..17].copy_from_slice(&1u64.to_le_bytes());
f[5..7].copy_from_slice(&key_info.to_be_bytes());
f[7..9].copy_from_slice(&16u16.to_be_bytes());
f[9..17].copy_from_slice(&1u64.to_be_bytes());
f[17..49].copy_from_slice(&nonce);
for (i, b) in f[MIC_OFFSET..MIC_OFFSET + 16].iter_mut().enumerate() {
*b = i as u8;
}
f[97..99].copy_from_slice(&(key_data.len() as u16).to_le_bytes());
f[97..99].copy_from_slice(&(key_data.len() as u16).to_be_bytes());
f[KEY_DATA_OFFSET..].copy_from_slice(key_data);
f
}
@@ -176,6 +183,32 @@ mod tests {
}
}
/// Byte a byte, independente do helper `key_frame` (que usa a mesma
/// codificação do parser): confere que um quadro Mensagem 1 real — key
/// info 0x008a (Ack=1, versão CCMP=2, pairwise), big-endian como é no
/// fio de verdade — é lido como message=1/key_version=2, não message=0.
/// É exatamente o valor citado pela documentação de ferramentas de
/// quebra de WPA pra mensagem 1 com CCMP.
#[test]
fn parses_real_wire_message1_key_info_as_big_endian() {
let mut frame = vec![0u8; MIN_KEY_FRAME_LEN];
frame[0] = 2; // versão EAPOL
frame[1] = EAPOL_TYPE_KEY;
frame[2..4].copy_from_slice(&((MIN_KEY_FRAME_LEN - 4) as u16).to_be_bytes());
frame[4] = 2; // descritor RSN
frame[5..7].copy_from_slice(&0x008au16.to_be_bytes()); // Ack=1, versão=2(CCMP), pairwise=1
frame[7..9].copy_from_slice(&16u16.to_be_bytes()); // key length
frame[9..17].copy_from_slice(&1u64.to_be_bytes()); // replay counter
frame[17..49].fill(0xaa); // nonce
frame[97..99].copy_from_slice(&0u16.to_be_bytes()); // sem key data
let parsed = parse(&frame).expect("mensagem 1 real deveria parsear");
assert_eq!(parsed.message, 1);
assert_eq!(parsed.key_version, 2);
assert!(parsed.pairwise);
assert_eq!(parsed.replay_counter, 1);
}
#[test]
fn extracts_pmkid_from_key_data() {
let mut data = vec![0xdd, 20, 0x00, 0x0f, 0xac, 0x04];