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

+34
View File
@@ -33,6 +33,12 @@ pub struct Handshake {
pub first_seen_unix: u64,
pub last_seen_unix: u64,
pub password: Option<String>,
/// Temporal Key (CCMP/AES) derivada quando `password` é preenchido.
/// Só pra TKIP (key_version 1) fica None: a decriptação aqui só cobre
/// CCMP. Serve pra enxergar o tráfego em claro desse par (BSSID,
/// estação) específico — não abre o tráfego de outros clientes, já que
/// a PTK é efêmera por associação.
pub tk: Option<[u8; 16]>,
}
#[derive(Debug, Clone, serde::Serialize)]
@@ -244,9 +250,17 @@ impl HandshakeTracker {
pub fn set_password(&mut self, bssid: &str, station: &str, password: &str) {
if let Some(hs) = self.get_mut(bssid, station) {
hs.password = Some(password.to_string());
hs.tk = derive_tk(hs, password);
}
}
/// TK já derivada pra esse par (BSSID, estação), se a senha da rede já
/// foi quebrada e o handshake tinha os dois nonces (EAPOL completo — um
/// handshake só de PMKID não dá pra derivar PTK/TK, só confirma a PSK).
pub fn tk_for(&self, bssid: &str, station: &str) -> Option<[u8; 16]> {
self.get(bssid, station).and_then(|hs| hs.tk)
}
pub fn len(&self) -> usize {
self.map.len()
}
@@ -256,6 +270,26 @@ impl HandshakeTracker {
}
}
/// Deriva a TK (bytes 32..48 da PTK) a partir da senha quebrada, pra poder
/// decifrar CCMP do tráfego desse cliente. Só funciona com EAPOL/CCMP
/// completo (key_version 2); PMKID sozinho não tem os dois nonces pra
/// derivar PTK, e TKIP (key_version 1) usa RC4 + Michael, não implementado.
fn derive_tk(hs: &Handshake, password: &str) -> Option<[u8; 16]> {
if hs.key_version != 2 {
return None;
}
let ssid = hs.ssid.as_deref().filter(|s| !s.is_empty())?;
let bssid = dot11::parse_mac(&hs.bssid)?;
let station = dot11::parse_mac(&hs.station)?;
let anonce = hs.anonce?;
let snonce = hs.snonce?;
let pmk = crate::crack::pmk_from_candidate(password, ssid.as_bytes())?;
let ptk = crate::crack::compute_ptk(&pmk, &bssid, &station, &anonce, &snonce);
let mut tk = [0u8; 16];
tk.copy_from_slice(&ptk[32..48]);
Some(tk)
}
fn apply_key(hs: &mut Handshake, key: &EapolKey) {
match key.message {
1 => {