Files
sabia/crates/sabia-core/src/hashcat.rs
T
Lucas RayanandClaude Sonnet 5 55e19a8e5b
CI / Format and clippy (push) Successful in 1m13s
CI / Dependency audit (push) Successful in 13s
CI / Tests (Linux) (push) Successful in 1m35s
Release / Create release (push) Successful in 7s
CI / Check desktop app (Linux) (push) Successful in 3m19s
Release / CLI (linux-aarch64) (push) Successful in 1m3s
Release / CLI (linux-x86_64) (push) Successful in 1m28s
Release / Desktop app (linux-aarch64) (push) Successful in 7m16s
Release / Desktop app (linux-x86_64) (push) Successful in 10m17s
Release / Desktop app (windows) (push) Successful in 4m54s
chore(i18n): finish translating code comments and scripts to English
Completes the English-only standardization for code, comments, scripts
and CI templates (UI/CLI-facing strings stay bilingual pt-BR/en,
untouched). Covers the remaining crates/, apps/gui/ui/ and
apps/gui/src-tauri/ comments, .gitea/ workflow and template comments,
.githooks/ hooks, and the scripts/ source comments for the files
already renamed in a prior commit (check.sh, generate-logo.py,
install-hooks.sh).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VAyr8d7HuAdUGB6aA5xmDD
2026-10-30 20:44:39 -03:00

87 lines
2.4 KiB
Rust

//! Exports captured WPA/WPA2 material in hashcat `.hc22000` format (`-m 22000`).
//!
//! Emits both record types supported by `hcxtools` / `hashcat`:
//!
//! * `WPA*01*PMKID*AP*STA*SSID***`
//! * `WPA*02*MIC*AP*STA*SSID*ANONCE*EAPOL*MESSAGEPAIR`
//!
//! The exported EAPOL field contains the full message 2 frame (802.1X header +
//! key descriptor) expected by hashcat's `EAPOL_CLIENT` field.
use crate::eapol::MIC_OFFSET;
use crate::handshake::Handshake;
fn mac_hex(mac: &str) -> Option<String> {
let bytes: Vec<String> = mac
.split(':')
.map(|part| part.trim().to_ascii_lowercase())
.collect();
if bytes.len() != 6
|| !bytes
.iter()
.all(|b| b.len() == 2 && b.chars().all(|c| c.is_ascii_hexdigit()))
{
return None;
}
Some(bytes.concat())
}
fn ssid_hex(ssid: &str) -> String {
ssid.as_bytes().iter().map(|b| format!("{b:02x}")).collect()
}
fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{b:02x}")).collect()
}
pub fn hashcat_line(hs: &Handshake) -> Option<String> {
let ssid = hs.ssid.as_deref().filter(|s| !s.is_empty())?;
let ap = mac_hex(&hs.bssid)?;
let sta = mac_hex(&hs.station)?;
let ssid = ssid_hex(ssid);
if let Some(pmkid) = hs.pmkid {
return Some(format!("WPA*01*{}*{ap}*{sta}*{ssid}***", hex(&pmkid)));
}
let eapol = hs.eapol_msg2.as_deref()?;
let anonce = hs.anonce?;
let mic = eapol.get(MIC_OFFSET..MIC_OFFSET + 16)?;
Some(format!(
"WPA*02*{}*{ap}*{sta}*{ssid}*{}*{}*00",
hex(mic),
hex(&anonce),
hex(eapol),
))
}
/// Formats deduplicated `.hc22000` lines for all crackable handshakes.
pub fn export<'a>(handshakes: impl IntoIterator<Item = &'a Handshake>) -> Vec<String> {
let mut lines: Vec<String> = handshakes.into_iter().filter_map(hashcat_line).collect();
lines.sort();
lines.dedup();
lines
}
#[cfg(test)]
mod tests {
use super::*;
use crate::handshake::Handshake;
#[test]
fn exports_pmkid_line() {
let hs = Handshake {
bssid: "00:11:22:33:44:55".into(),
station: "66:77:88:99:aa:bb".into(),
ssid: Some("Casa".into()),
pmkid: Some([0xcd; 16]),
..Default::default()
};
let line = hashcat_line(&hs).expect("linha");
assert_eq!(
line,
"WPA*01*cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd*001122334455*66778899aabb*43617361***"
);
}
}