feat: orquestra as novas funções no engine, CLI e app desktop; resiliência de rede
- engine.rs: expõe Evil Twin (PSK/Enterprise), crack com regras/máscara,
relatório e exportação .hc22000 pros três front-ends (CLI, servidor web
da UI, Tauri). set_monitor() agora solta a interface do NetworkManager
antes de ativar modo monitor e devolve ao desativar — sem isso o NM
reafirma a própria conexão por cima e a captura cai sozinha pouco
depois ("está em modo managed"). Evil Twin grava um arquivo de estado
em disco antes de soltar as interfaces; se o processo morrer no meio
(Ctrl+C sem handler, kill -9, queda de energia), o próximo início do
engine desfaz tudo sozinho (self_heal_eviltwin_leftovers) — sem isso as
duas interfaces ficavam presas em managed=no pro NetworkManager pra
sempre e o Wi-Fi inteiro sumia do painel do sistema. hostapd/hostapd-wpe
que morrem na hora de subir agora reportam a saída real capturada em
log, em vez de um processo "<defunct>" mudo. Novo
ScanSnapshot.warning: avisa quando a sessão roda >12s sem receber
nenhum quadro — sintoma de placa que aceita ativar modo monitor sem
entregar captura de verdade (comum em Wi-Fi interno de notebook).
- sabia-cli (main.rs/web/mod.rs) e o app desktop (Tauri main.rs) ganham
as rotas/comandos equivalentes pra tudo isso.
This commit is contained in:
1 parent
7b39c67830
commit
a3ca73e610
5 files changed
+3471
-32
No files matched your search
+533
-11
@@ -1,7 +1,7 @@
|
|||||||
// Evita a janela de console extra no Windows em release.
|
// Evita a janela de console extra no Windows em release.
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
use sabia_core::engine::Engine;
|
use sabia_core::engine::{Engine, ScanOptions};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
struct State(Arc<Engine>);
|
struct State(Arc<Engine>);
|
||||||
@@ -19,7 +19,10 @@ fn list_ifaces(state: tauri::State<'_, State>) -> Result<serde_json::Value, Stri
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn set_monitor(state: tauri::State<'_, State>, iface: String, enable: bool) -> Result<(), String> {
|
fn set_monitor(state: tauri::State<'_, State>, iface: String, enable: bool) -> Result<(), String> {
|
||||||
state.0.set_monitor(&iface, enable).map_err(|e| e.to_string())
|
state
|
||||||
|
.0
|
||||||
|
.set_monitor(&iface, enable)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -33,10 +36,465 @@ fn stop_scan(state: tauri::State<'_, State>, iface: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn scan_results(state: tauri::State<'_, State>, iface: String) -> Result<serde_json::Value, String> {
|
fn scan_results(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: String,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
serde_json::to_value(state.0.scan_results(&iface)).map_err(|e| e.to_string())
|
serde_json::to_value(state.0.scan_results(&iface)).map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn scan_snapshot(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: String,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
|
let snapshot = state.0.scan_snapshot(&iface).map_err(|e| e.to_string())?;
|
||||||
|
serde_json::to_value(snapshot).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn scan_clients(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: String,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
|
serde_json::to_value(state.0.scan_clients(&iface)).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn handshakes(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: Option<String>,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
|
serde_json::to_value(state.0.handshakes(iface.as_deref())).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn export_handshake(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: String,
|
||||||
|
bssid: String,
|
||||||
|
station: String,
|
||||||
|
out: String,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
|
let report = state
|
||||||
|
.0
|
||||||
|
.export_handshake(&iface, &bssid, &station, &out)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
serde_json::to_value(report).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn export_all_handshakes(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: String,
|
||||||
|
out: String,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
|
let report = state
|
||||||
|
.0
|
||||||
|
.export_all_handshakes(&iface, &out)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
serde_json::to_value(report).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn start_crack(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: String,
|
||||||
|
wordlist: Option<String>,
|
||||||
|
bssid: Option<String>,
|
||||||
|
station: Option<String>,
|
||||||
|
rules: Option<bool>,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let rules = rules.unwrap_or(false);
|
||||||
|
let result = match wordlist.filter(|w| !w.trim().is_empty()) {
|
||||||
|
Some(wordlist) => state.0.start_crack(
|
||||||
|
&iface,
|
||||||
|
bssid.as_deref(),
|
||||||
|
station.as_deref(),
|
||||||
|
&wordlist,
|
||||||
|
rules,
|
||||||
|
),
|
||||||
|
None => state
|
||||||
|
.0
|
||||||
|
.start_crack_default(&iface, bssid.as_deref(), station.as_deref(), rules),
|
||||||
|
};
|
||||||
|
result.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn start_crack_mask(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: String,
|
||||||
|
mask: String,
|
||||||
|
bssid: Option<String>,
|
||||||
|
station: Option<String>,
|
||||||
|
max_candidates: Option<u64>,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
state
|
||||||
|
.0
|
||||||
|
.start_crack_mask(
|
||||||
|
&iface,
|
||||||
|
bssid.as_deref(),
|
||||||
|
station.as_deref(),
|
||||||
|
&mask,
|
||||||
|
max_candidates.unwrap_or(50_000_000),
|
||||||
|
)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn crack_job(state: tauri::State<'_, State>, id: String) -> Result<serde_json::Value, String> {
|
||||||
|
match state.0.crack_job(&id) {
|
||||||
|
Some(job) => serde_json::to_value(job).map_err(|e| e.to_string()),
|
||||||
|
None => Err("job não encontrado".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn crack_jobs(state: tauri::State<'_, State>) -> Result<serde_json::Value, String> {
|
||||||
|
serde_json::to_value(state.0.crack_jobs()).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn deauth(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: String,
|
||||||
|
bssid: String,
|
||||||
|
station: Option<String>,
|
||||||
|
count: Option<u32>,
|
||||||
|
reason: Option<u16>,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
|
let report = state
|
||||||
|
.0
|
||||||
|
.deauth(
|
||||||
|
&iface,
|
||||||
|
&bssid,
|
||||||
|
station.as_deref(),
|
||||||
|
count.unwrap_or(5),
|
||||||
|
reason.unwrap_or(7),
|
||||||
|
)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
serde_json::to_value(report).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn deauth_directional(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: String,
|
||||||
|
bssid: String,
|
||||||
|
station: Option<String>,
|
||||||
|
count: Option<u32>,
|
||||||
|
reason: Option<u16>,
|
||||||
|
direction: Option<String>,
|
||||||
|
frame_kind: Option<String>,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
|
let report = state
|
||||||
|
.0
|
||||||
|
.deauth_directional(
|
||||||
|
&iface,
|
||||||
|
&bssid,
|
||||||
|
station.as_deref(),
|
||||||
|
count.unwrap_or(5),
|
||||||
|
reason.unwrap_or(7),
|
||||||
|
direction.as_deref().unwrap_or("ap"),
|
||||||
|
frame_kind.as_deref().unwrap_or("deauth"),
|
||||||
|
)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
serde_json::to_value(report).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn start_deauth_job(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: String,
|
||||||
|
bssid: String,
|
||||||
|
station: Option<String>,
|
||||||
|
reason: Option<u16>,
|
||||||
|
direction: Option<String>,
|
||||||
|
frame_kind: Option<String>,
|
||||||
|
interval_ms: Option<u64>,
|
||||||
|
max_secs: Option<u64>,
|
||||||
|
stop_on_handshake: Option<bool>,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
state
|
||||||
|
.0
|
||||||
|
.start_deauth_job(
|
||||||
|
&iface,
|
||||||
|
&bssid,
|
||||||
|
station.as_deref(),
|
||||||
|
reason.unwrap_or(7),
|
||||||
|
direction.as_deref().unwrap_or("both"),
|
||||||
|
frame_kind.as_deref().unwrap_or("deauth"),
|
||||||
|
interval_ms.unwrap_or(120),
|
||||||
|
max_secs,
|
||||||
|
stop_on_handshake.unwrap_or(false),
|
||||||
|
)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn stop_deauth_job(state: tauri::State<'_, State>, id: String) -> Result<(), String> {
|
||||||
|
state.0.stop_deauth_job(&id).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn deauth_job(state: tauri::State<'_, State>, id: String) -> Result<serde_json::Value, String> {
|
||||||
|
match state.0.deauth_job(&id) {
|
||||||
|
Some(job) => serde_json::to_value(job).map_err(|e| e.to_string()),
|
||||||
|
None => Err("job de deauth não encontrado".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn deauth_jobs(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: Option<String>,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
|
let jobs = state.0.deauth_jobs();
|
||||||
|
let jobs: Vec<_> = match iface {
|
||||||
|
Some(iface) => jobs
|
||||||
|
.into_iter()
|
||||||
|
.filter(|j| j.iface.eq_ignore_ascii_case(&iface))
|
||||||
|
.collect(),
|
||||||
|
None => jobs,
|
||||||
|
};
|
||||||
|
serde_json::to_value(jobs).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn capture_handshake(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: String,
|
||||||
|
bssid: String,
|
||||||
|
station: Option<String>,
|
||||||
|
seconds: Option<u64>,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
state
|
||||||
|
.0
|
||||||
|
.capture_handshake(&iface, &bssid, station.as_deref(), seconds)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn report(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: String,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
|
let model = state.0.report(&iface).map_err(|e| e.to_string())?;
|
||||||
|
serde_json::to_value(model).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn report_html(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: String,
|
||||||
|
inline: Option<bool>,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let model = state.0.report(&iface).map_err(|e| e.to_string())?;
|
||||||
|
let options = sabia_core::report::ReportOptions {
|
||||||
|
inline_pagedjs: inline.unwrap_or(false),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
Ok(sabia_core::report::render(&model, options))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn export_hc22000(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: String,
|
||||||
|
bssid: String,
|
||||||
|
station: String,
|
||||||
|
out: String,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
|
let report = state
|
||||||
|
.0
|
||||||
|
.export_hc22000(&iface, &bssid, &station, &out)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
serde_json::to_value(report).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn export_all_hc22000(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: String,
|
||||||
|
out: String,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
|
let report = state
|
||||||
|
.0
|
||||||
|
.export_all_hc22000(&iface, &out)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
serde_json::to_value(report).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn pmkid_hunt(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: String,
|
||||||
|
bssid: String,
|
||||||
|
attempts: Option<u32>,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
|
let report = state
|
||||||
|
.0
|
||||||
|
.pmkid_hunt(&iface, &bssid, attempts.unwrap_or(5))
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
serde_json::to_value(report).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn pmf_test(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: String,
|
||||||
|
bssid: String,
|
||||||
|
station: String,
|
||||||
|
reason: Option<u16>,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
|
let report = state
|
||||||
|
.0
|
||||||
|
.pmf_test(&iface, &bssid, &station, reason.unwrap_or(7))
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
serde_json::to_value(report).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn wps_tools(state: tauri::State<'_, State>) -> Result<serde_json::Value, String> {
|
||||||
|
serde_json::to_value(state.0.wps_tools()).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn start_wps(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: String,
|
||||||
|
bssid: String,
|
||||||
|
channel: u32,
|
||||||
|
tool: String,
|
||||||
|
pin: Option<String>,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
state
|
||||||
|
.0
|
||||||
|
.start_wps(&iface, &bssid, channel, &tool, pin.as_deref())
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn wps_job(state: tauri::State<'_, State>, id: String) -> Result<serde_json::Value, String> {
|
||||||
|
match state.0.wps_job(&id) {
|
||||||
|
Some(job) => serde_json::to_value(job).map_err(|e| e.to_string()),
|
||||||
|
None => Err("job WPS não encontrado".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn wps_jobs(state: tauri::State<'_, State>) -> Result<serde_json::Value, String> {
|
||||||
|
serde_json::to_value(state.0.wps_jobs()).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn stop_wps(state: tauri::State<'_, State>, id: String) -> Result<(), String> {
|
||||||
|
state.0.stop_wps(&id).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn start_evil_twin(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: String,
|
||||||
|
ap_iface: String,
|
||||||
|
bssid: String,
|
||||||
|
portal_port: Option<u16>,
|
||||||
|
deauth: Option<bool>,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
state
|
||||||
|
.0
|
||||||
|
.start_evil_twin(
|
||||||
|
&iface,
|
||||||
|
&ap_iface,
|
||||||
|
&bssid,
|
||||||
|
portal_port.unwrap_or(8080),
|
||||||
|
deauth.unwrap_or(false),
|
||||||
|
)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn eviltwin_job(state: tauri::State<'_, State>, id: String) -> Result<serde_json::Value, String> {
|
||||||
|
match state.0.eviltwin_job(&id) {
|
||||||
|
Some(job) => serde_json::to_value(job).map_err(|e| e.to_string()),
|
||||||
|
None => Err("job de Evil Twin não encontrado".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn eviltwin_jobs(state: tauri::State<'_, State>) -> Result<serde_json::Value, String> {
|
||||||
|
serde_json::to_value(state.0.eviltwin_jobs()).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn stop_evil_twin(state: tauri::State<'_, State>, id: String) -> Result<(), String> {
|
||||||
|
state.0.stop_evil_twin(&id).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn start_evil_twin_enterprise(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: String,
|
||||||
|
ap_iface: String,
|
||||||
|
bssid: String,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
state
|
||||||
|
.0
|
||||||
|
.start_evil_twin_enterprise(&iface, &ap_iface, &bssid)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn eviltwin_enterprise_job(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
id: String,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
|
match state.0.enterprise_twin_job(&id) {
|
||||||
|
Some(job) => serde_json::to_value(job).map_err(|e| e.to_string()),
|
||||||
|
None => Err("job de Evil Twin Enterprise não encontrado".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn eviltwin_enterprise_jobs(state: tauri::State<'_, State>) -> Result<serde_json::Value, String> {
|
||||||
|
serde_json::to_value(state.0.enterprise_twin_jobs()).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn stop_evil_twin_enterprise(state: tauri::State<'_, State>, id: String) -> Result<(), String> {
|
||||||
|
state
|
||||||
|
.0
|
||||||
|
.stop_evil_twin_enterprise(&id)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn start_scan_opts(
|
||||||
|
state: tauri::State<'_, State>,
|
||||||
|
iface: String,
|
||||||
|
channel: Option<u32>,
|
||||||
|
band: Option<String>,
|
||||||
|
hop_ms: Option<u64>,
|
||||||
|
pcap_out: Option<String>,
|
||||||
|
seconds: Option<u64>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let options = ScanOptions {
|
||||||
|
channel,
|
||||||
|
band,
|
||||||
|
hop_ms: hop_ms.unwrap_or(350),
|
||||||
|
pcap_out: pcap_out.map(Into::into),
|
||||||
|
duration_secs: seconds,
|
||||||
|
};
|
||||||
|
state
|
||||||
|
.0
|
||||||
|
.start_scan_with(&iface, options)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn update_check(state: tauri::State<'_, State>) -> Result<serde_json::Value, String> {
|
fn update_check(state: tauri::State<'_, State>) -> Result<serde_json::Value, String> {
|
||||||
let status = state.0.update_check().map_err(|e| e.to_string())?;
|
let status = state.0.update_check().map_err(|e| e.to_string())?;
|
||||||
@@ -85,7 +543,10 @@ fn do_relaunch_elevated(app: tauri::AppHandle) -> Result<(), String> {
|
|||||||
let exe = elevated_exe()?;
|
let exe = elevated_exe()?;
|
||||||
let log = std::env::temp_dir().join(format!("sabia-elevate-{}.log", std::process::id()));
|
let log = std::env::temp_dir().join(format!("sabia-elevate-{}.log", std::process::id()));
|
||||||
let _ = std::fs::write(&log, b"");
|
let _ = std::fs::write(&log, b"");
|
||||||
let out = std::fs::OpenOptions::new().append(true).open(&log).map_err(|e| e.to_string())?;
|
let out = std::fs::OpenOptions::new()
|
||||||
|
.append(true)
|
||||||
|
.open(&log)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
let err = out.try_clone().map_err(|e| e.to_string())?;
|
let err = out.try_clone().map_err(|e| e.to_string())?;
|
||||||
let pid = std::process::id().to_string();
|
let pid = std::process::id().to_string();
|
||||||
|
|
||||||
@@ -93,7 +554,14 @@ fn do_relaunch_elevated(app: tauri::AppHandle) -> Result<(), String> {
|
|||||||
let mut cmd = {
|
let mut cmd = {
|
||||||
let mut c = std::process::Command::new("pkexec");
|
let mut c = std::process::Command::new("pkexec");
|
||||||
c.arg("/usr/bin/env");
|
c.arg("/usr/bin/env");
|
||||||
for k in ["DISPLAY", "WAYLAND_DISPLAY", "XAUTHORITY", "XDG_RUNTIME_DIR", "DBUS_SESSION_BUS_ADDRESS", "GDK_BACKEND"] {
|
for k in [
|
||||||
|
"DISPLAY",
|
||||||
|
"WAYLAND_DISPLAY",
|
||||||
|
"XAUTHORITY",
|
||||||
|
"XDG_RUNTIME_DIR",
|
||||||
|
"DBUS_SESSION_BUS_ADDRESS",
|
||||||
|
"GDK_BACKEND",
|
||||||
|
] {
|
||||||
if let Ok(v) = std::env::var(k) {
|
if let Ok(v) = std::env::var(k) {
|
||||||
c.arg(format!("{k}={v}"));
|
c.arg(format!("{k}={v}"));
|
||||||
}
|
}
|
||||||
@@ -104,14 +572,23 @@ fn do_relaunch_elevated(app: tauri::AppHandle) -> Result<(), String> {
|
|||||||
};
|
};
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
let mut cmd = {
|
let mut cmd = {
|
||||||
let shell = format!("SABIA_REPLACE_PID={} '{}' >/dev/null 2>&1 &", pid, exe.display());
|
let shell = format!(
|
||||||
let script = format!("do shell script \"{}\" with administrator privileges", shell.replace('\\', "\\\\").replace('"', "\\\""));
|
"SABIA_REPLACE_PID={} '{}' >/dev/null 2>&1 &",
|
||||||
|
pid,
|
||||||
|
exe.display()
|
||||||
|
);
|
||||||
|
let script = format!(
|
||||||
|
"do shell script \"{}\" with administrator privileges",
|
||||||
|
shell.replace('\\', "\\\\").replace('"', "\\\"")
|
||||||
|
);
|
||||||
let mut c = std::process::Command::new("osascript");
|
let mut c = std::process::Command::new("osascript");
|
||||||
c.args(["-e", &script]);
|
c.args(["-e", &script]);
|
||||||
c
|
c
|
||||||
};
|
};
|
||||||
cmd.stdout(out).stderr(err);
|
cmd.stdout(out).stderr(err);
|
||||||
let mut child = cmd.spawn().map_err(|e| format!("não foi possível pedir privilégios: {e}"))?;
|
let mut child = cmd
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| format!("não foi possível pedir privilégios: {e}"))?;
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let status = child.wait();
|
let status = child.wait();
|
||||||
if matches!(&status, Ok(s) if s.success()) {
|
if matches!(&status, Ok(s) if s.success()) {
|
||||||
@@ -122,7 +599,10 @@ fn do_relaunch_elevated(app: tauri::AppHandle) -> Result<(), String> {
|
|||||||
let msg = match status.ok().and_then(|s| s.code()) {
|
let msg = match status.ok().and_then(|s| s.code()) {
|
||||||
Some(126) => "Autorização cancelada.".to_string(),
|
Some(126) => "Autorização cancelada.".to_string(),
|
||||||
Some(127) => "Não foi possível autenticar como administrador.".to_string(),
|
Some(127) => "Não foi possível autenticar como administrador.".to_string(),
|
||||||
_ => format!("Não foi possível reiniciar como administrador. {}", detail.lines().last().unwrap_or("").trim()),
|
_ => format!(
|
||||||
|
"Não foi possível reiniciar como administrador. {}",
|
||||||
|
detail.lines().last().unwrap_or("").trim()
|
||||||
|
),
|
||||||
};
|
};
|
||||||
let _ = app.emit("elevate-failed", msg);
|
let _ = app.emit("elevate-failed", msg);
|
||||||
let _ = std::fs::remove_file(&log);
|
let _ = std::fs::remove_file(&log);
|
||||||
@@ -135,7 +615,10 @@ fn do_relaunch_elevated(app: tauri::AppHandle) -> Result<(), String> {
|
|||||||
/// usa o próprio arquivo `.AppImage` (variável `APPIMAGE`).
|
/// usa o próprio arquivo `.AppImage` (variável `APPIMAGE`).
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
fn elevated_exe() -> Result<std::path::PathBuf, String> {
|
fn elevated_exe() -> Result<std::path::PathBuf, String> {
|
||||||
if let Some(p) = std::env::var_os("APPIMAGE").map(std::path::PathBuf::from).filter(|p| p.is_file()) {
|
if let Some(p) = std::env::var_os("APPIMAGE")
|
||||||
|
.map(std::path::PathBuf::from)
|
||||||
|
.filter(|p| p.is_file())
|
||||||
|
{
|
||||||
return Ok(p);
|
return Ok(p);
|
||||||
}
|
}
|
||||||
std::env::current_exe().map_err(|e| e.to_string())
|
std::env::current_exe().map_err(|e| e.to_string())
|
||||||
@@ -148,7 +631,10 @@ fn main() {
|
|||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
// Quando abrimos elevados a pedido do app normal, ele fecha o app antigo.
|
// Quando abrimos elevados a pedido do app normal, ele fecha o app antigo.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
if let Some(pid) = std::env::var("SABIA_REPLACE_PID").ok().and_then(|p| p.parse::<i32>().ok()) {
|
if let Some(pid) = std::env::var("SABIA_REPLACE_PID")
|
||||||
|
.ok()
|
||||||
|
.and_then(|p| p.parse::<i32>().ok())
|
||||||
|
{
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
std::thread::sleep(std::time::Duration::from_millis(1200));
|
std::thread::sleep(std::time::Duration::from_millis(1200));
|
||||||
// SAFETY: só envia SIGTERM ao processo anterior do próprio usuário.
|
// SAFETY: só envia SIGTERM ao processo anterior do próprio usuário.
|
||||||
@@ -180,6 +666,42 @@ fn main() {
|
|||||||
start_scan,
|
start_scan,
|
||||||
stop_scan,
|
stop_scan,
|
||||||
scan_results,
|
scan_results,
|
||||||
|
scan_snapshot,
|
||||||
|
scan_clients,
|
||||||
|
handshakes,
|
||||||
|
export_handshake,
|
||||||
|
export_all_handshakes,
|
||||||
|
start_crack,
|
||||||
|
start_crack_mask,
|
||||||
|
crack_job,
|
||||||
|
crack_jobs,
|
||||||
|
deauth,
|
||||||
|
deauth_directional,
|
||||||
|
start_deauth_job,
|
||||||
|
stop_deauth_job,
|
||||||
|
deauth_job,
|
||||||
|
deauth_jobs,
|
||||||
|
capture_handshake,
|
||||||
|
report,
|
||||||
|
report_html,
|
||||||
|
export_hc22000,
|
||||||
|
export_all_hc22000,
|
||||||
|
pmkid_hunt,
|
||||||
|
pmf_test,
|
||||||
|
wps_tools,
|
||||||
|
start_wps,
|
||||||
|
wps_job,
|
||||||
|
wps_jobs,
|
||||||
|
stop_wps,
|
||||||
|
start_evil_twin,
|
||||||
|
eviltwin_job,
|
||||||
|
eviltwin_jobs,
|
||||||
|
stop_evil_twin,
|
||||||
|
start_evil_twin_enterprise,
|
||||||
|
eviltwin_enterprise_job,
|
||||||
|
eviltwin_enterprise_jobs,
|
||||||
|
stop_evil_twin_enterprise,
|
||||||
|
start_scan_opts,
|
||||||
update_check,
|
update_check,
|
||||||
update_apply,
|
update_apply,
|
||||||
is_elevated,
|
is_elevated,
|
||||||
|
|||||||
@@ -72,6 +72,10 @@ enum Cmd {
|
|||||||
bssid: Option<String>,
|
bssid: Option<String>,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
station: Option<String>,
|
station: Option<String>,
|
||||||
|
/// Aplica mutações comuns a cada palavra (maiúscula, sufixos
|
||||||
|
/// numéricos, leet simples) — multiplica o tempo de verdade.
|
||||||
|
#[arg(long)]
|
||||||
|
rules: bool,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
json: bool,
|
json: bool,
|
||||||
},
|
},
|
||||||
@@ -275,6 +279,7 @@ fn run(cli: Cli) -> sabia_core::Result<()> {
|
|||||||
wordlist,
|
wordlist,
|
||||||
bssid,
|
bssid,
|
||||||
station,
|
station,
|
||||||
|
rules,
|
||||||
json,
|
json,
|
||||||
} => {
|
} => {
|
||||||
let mut last = 0u64;
|
let mut last = 0u64;
|
||||||
@@ -283,6 +288,7 @@ fn run(cli: Cli) -> sabia_core::Result<()> {
|
|||||||
&wordlist,
|
&wordlist,
|
||||||
bssid.as_deref(),
|
bssid.as_deref(),
|
||||||
station.as_deref(),
|
station.as_deref(),
|
||||||
|
rules,
|
||||||
|tried| {
|
|tried| {
|
||||||
if tried.saturating_sub(last) >= 4096 {
|
if tried.saturating_sub(last) >= 4096 {
|
||||||
eprint!("\r{tried} senhas testadas…");
|
eprint!("\r{tried} senhas testadas…");
|
||||||
@@ -412,7 +418,7 @@ fn crack_first(engine: &Engine, iface: &str, wordlist: &PathBuf) -> sabia_core::
|
|||||||
hs.bssid,
|
hs.bssid,
|
||||||
wordlist.display()
|
wordlist.display()
|
||||||
);
|
);
|
||||||
let id = engine.start_crack(iface, Some(&hs.bssid), Some(&hs.station), wordlist)?;
|
let id = engine.start_crack(iface, Some(&hs.bssid), Some(&hs.station), wordlist, false)?;
|
||||||
loop {
|
loop {
|
||||||
let job = engine.crack_job(&id).expect("job");
|
let job = engine.crack_job(&id).expect("job");
|
||||||
if job.status == "running" {
|
if job.status == "running" {
|
||||||
|
|||||||
@@ -15,13 +15,29 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
|||||||
const INDEX_HTML: &str = include_str!("../../../../apps/gui/ui/index.html");
|
const INDEX_HTML: &str = include_str!("../../../../apps/gui/ui/index.html");
|
||||||
const APP_CSS: &str = include_str!("../../../../apps/gui/ui/app.css");
|
const APP_CSS: &str = include_str!("../../../../apps/gui/ui/app.css");
|
||||||
const APP_JS: &str = include_str!("../../../../apps/gui/ui/app.js");
|
const APP_JS: &str = include_str!("../../../../apps/gui/ui/app.js");
|
||||||
|
const I18N_JS: &str = include_str!("../../../../apps/gui/ui/i18n.js");
|
||||||
|
const I18N_EN_JS: &str = include_str!("../../../../apps/gui/ui/i18n-en.js");
|
||||||
const LOGO_PNG: &[u8] = include_bytes!("../../../../apps/gui/ui/sabia.png");
|
const LOGO_PNG: &[u8] = include_bytes!("../../../../apps/gui/ui/sabia.png");
|
||||||
|
const PAGED_JS: &str = include_str!("../../../../apps/gui/ui/paged.polyfill.min.js");
|
||||||
|
|
||||||
pub fn serve(port: u16, audit_path: std::path::PathBuf, open: bool) -> sabia_core::Result<()> {
|
pub fn serve(port: u16, audit_path: std::path::PathBuf, open: bool) -> sabia_core::Result<()> {
|
||||||
let token = gen_token();
|
let token = gen_token();
|
||||||
let listener = TcpListener::bind(("127.0.0.1", port))?;
|
let listener = TcpListener::bind(("127.0.0.1", port))?;
|
||||||
let engine = Arc::new(Engine::with_audit_path(audit_path));
|
let engine = Arc::new(Engine::with_audit_path(audit_path));
|
||||||
|
|
||||||
|
// Sem isso, Ctrl+C (ou um `kill` normal) durante um Evil Twin ativo
|
||||||
|
// mata o processo sem rodar a limpeza: hostapd/dnsmasq ficam órfãos
|
||||||
|
// e as duas interfaces ficam presas em `managed no` pro
|
||||||
|
// NetworkManager — o painel de Wi-Fi do sistema some por completo até
|
||||||
|
// o próximo início do engine se auto-curar.
|
||||||
|
{
|
||||||
|
let engine = engine.clone();
|
||||||
|
let _ = ctrlc::set_handler(move || {
|
||||||
|
engine.shutdown_cleanup();
|
||||||
|
std::process::exit(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
println!("Sabiá UI em http://127.0.0.1:{port}/?token={token}");
|
println!("Sabiá UI em http://127.0.0.1:{port}/?token={token}");
|
||||||
if open {
|
if open {
|
||||||
let _ = open_browser(&format!("http://127.0.0.1:{port}/?token={token}"));
|
let _ = open_browser(&format!("http://127.0.0.1:{port}/?token={token}"));
|
||||||
@@ -225,7 +241,48 @@ fn handle(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.path == "/api/report.html" {
|
||||||
|
if req.token.as_deref() != Some(expected_token) {
|
||||||
|
return respond(&mut stream, 401, "text/plain", b"token invalido");
|
||||||
|
}
|
||||||
|
let Some(iface) = req.text("iface") else {
|
||||||
|
return respond(&mut stream, 400, "text/plain", b"iface obrigatoria");
|
||||||
|
};
|
||||||
|
return match engine.report(&iface) {
|
||||||
|
Ok(model) => {
|
||||||
|
let download = req.bool("download");
|
||||||
|
let mut html = sabia_core::report::render(
|
||||||
|
&model,
|
||||||
|
sabia_core::report::ReportOptions {
|
||||||
|
inline_pagedjs: download,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if download {
|
||||||
|
html = html.replace(
|
||||||
|
"<!--SABIA_PAGEDJS-->",
|
||||||
|
&format!("<script>{PAGED_JS}</script>"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
respond(
|
||||||
|
&mut stream,
|
||||||
|
200,
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
html.as_bytes(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Err(e) => respond(&mut stream, 400, "text/plain", e.to_string().as_bytes()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
match req.path.as_str() {
|
match req.path.as_str() {
|
||||||
|
"/paged.polyfill.min.js" => {
|
||||||
|
return respond(
|
||||||
|
&mut stream,
|
||||||
|
200,
|
||||||
|
"application/javascript",
|
||||||
|
PAGED_JS.as_bytes(),
|
||||||
|
)
|
||||||
|
}
|
||||||
"/app.css" => return respond(&mut stream, 200, "text/css", APP_CSS.as_bytes()),
|
"/app.css" => return respond(&mut stream, 200, "text/css", APP_CSS.as_bytes()),
|
||||||
"/app.js" => {
|
"/app.js" => {
|
||||||
return respond(
|
return respond(
|
||||||
@@ -235,6 +292,22 @@ fn handle(
|
|||||||
APP_JS.as_bytes(),
|
APP_JS.as_bytes(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
"/i18n.js" => {
|
||||||
|
return respond(
|
||||||
|
&mut stream,
|
||||||
|
200,
|
||||||
|
"application/javascript",
|
||||||
|
I18N_JS.as_bytes(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"/i18n-en.js" => {
|
||||||
|
return respond(
|
||||||
|
&mut stream,
|
||||||
|
200,
|
||||||
|
"application/javascript",
|
||||||
|
I18N_EN_JS.as_bytes(),
|
||||||
|
)
|
||||||
|
}
|
||||||
"/sabia.png" => return respond(&mut stream, 200, "image/png", LOGO_PNG),
|
"/sabia.png" => return respond(&mut stream, 200, "image/png", LOGO_PNG),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -408,13 +481,210 @@ fn api(engine: &Arc<Engine>, req: &Request) -> (u16, String) {
|
|||||||
Err(e) => err(400, e),
|
Err(e) => err(400, e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
("POST", "/api/handshakes/export-hc22000") => {
|
||||||
|
let (Some(iface), Some(bssid), Some(station), Some(out)) = (
|
||||||
|
req.text("iface"),
|
||||||
|
req.text("bssid"),
|
||||||
|
req.text("station"),
|
||||||
|
req.text("out"),
|
||||||
|
) else {
|
||||||
|
return err(400, "iface, bssid, station e out são obrigatórios");
|
||||||
|
};
|
||||||
|
match engine.export_hc22000(&iface, &bssid, &station, &out) {
|
||||||
|
Ok(report) => ok(&report),
|
||||||
|
Err(e) => err(400, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("POST", "/api/handshakes/export-all-hc22000") => {
|
||||||
|
let (Some(iface), Some(out)) = (req.text("iface"), req.text("out")) else {
|
||||||
|
return err(400, "iface e out são obrigatórios");
|
||||||
|
};
|
||||||
|
match engine.export_all_hc22000(&iface, &out) {
|
||||||
|
Ok(report) => ok(&report),
|
||||||
|
Err(e) => err(400, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("GET", "/api/report") => {
|
||||||
|
let Some(iface) = req.text("iface") else {
|
||||||
|
return err(400, "iface obrigatória");
|
||||||
|
};
|
||||||
|
match engine.report(&iface) {
|
||||||
|
Ok(model) => ok(&model),
|
||||||
|
Err(e) => err(400, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("GET", "/api/wps/tools") => ok(&engine.wps_tools()),
|
||||||
|
("POST", "/api/wps") => {
|
||||||
|
if !req.bool("confirm") {
|
||||||
|
return err(400, "confirme a autorização do teste (confirm=true)");
|
||||||
|
}
|
||||||
|
let (Some(iface), Some(bssid)) = (req.text("iface"), req.text("bssid")) else {
|
||||||
|
return err(400, "iface e bssid são obrigatórios");
|
||||||
|
};
|
||||||
|
let tool = req.text("tool").unwrap_or_else(|| "reaver".into());
|
||||||
|
let channel = req.u32("channel").unwrap_or(0);
|
||||||
|
let pin = req.text("pin");
|
||||||
|
match engine.start_wps(&iface, &bssid, channel, &tool, pin.as_deref()) {
|
||||||
|
Ok(id) => ok_json(serde_json::json!({ "ok": true, "job": id })),
|
||||||
|
Err(e) => err(400, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("GET", "/api/wps") => {
|
||||||
|
let Some(id) = req.text("id") else {
|
||||||
|
return err(400, "id obrigatório");
|
||||||
|
};
|
||||||
|
match engine.wps_job(&id) {
|
||||||
|
Some(job) => ok(&job),
|
||||||
|
None => err(404, "job não encontrado"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("GET", "/api/wps/jobs") => ok(&engine.wps_jobs()),
|
||||||
|
("POST", "/api/wps/stop") => {
|
||||||
|
let Some(id) = req.text("id") else {
|
||||||
|
return err(400, "id obrigatório");
|
||||||
|
};
|
||||||
|
match engine.stop_wps(&id) {
|
||||||
|
Ok(()) => ok_json(serde_json::json!({ "ok": true })),
|
||||||
|
Err(e) => err(400, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("POST", "/api/eviltwin") => {
|
||||||
|
if !req.bool("confirm") {
|
||||||
|
return err(400, "confirme a autorização do teste (confirm=true)");
|
||||||
|
}
|
||||||
|
let (Some(iface), Some(ap_iface), Some(bssid)) =
|
||||||
|
(req.text("iface"), req.text("ap_iface"), req.text("bssid"))
|
||||||
|
else {
|
||||||
|
return err(400, "iface, ap_iface e bssid são obrigatórios");
|
||||||
|
};
|
||||||
|
let portal_port = req.u16("portal_port").unwrap_or(8080);
|
||||||
|
let deauth = req.bool("deauth");
|
||||||
|
match engine.start_evil_twin(&iface, &ap_iface, &bssid, portal_port, deauth) {
|
||||||
|
Ok(id) => ok_json(serde_json::json!({ "ok": true, "job": id })),
|
||||||
|
Err(e) => err(400, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("GET", "/api/eviltwin") => {
|
||||||
|
let Some(id) = req.text("id") else {
|
||||||
|
return err(400, "id obrigatório");
|
||||||
|
};
|
||||||
|
match engine.eviltwin_job(&id) {
|
||||||
|
Some(job) => ok(&job),
|
||||||
|
None => err(404, "job não encontrado"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("GET", "/api/eviltwins") => ok(&engine.eviltwin_jobs()),
|
||||||
|
("POST", "/api/eviltwin/stop") => {
|
||||||
|
let Some(id) = req.text("id") else {
|
||||||
|
return err(400, "id obrigatório");
|
||||||
|
};
|
||||||
|
match engine.stop_evil_twin(&id) {
|
||||||
|
Ok(()) => ok_json(serde_json::json!({ "ok": true })),
|
||||||
|
Err(e) => err(400, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("POST", "/api/eviltwin-enterprise") => {
|
||||||
|
if !req.bool("confirm") {
|
||||||
|
return err(400, "confirme a autorização do teste (confirm=true)");
|
||||||
|
}
|
||||||
|
let (Some(iface), Some(ap_iface), Some(bssid)) =
|
||||||
|
(req.text("iface"), req.text("ap_iface"), req.text("bssid"))
|
||||||
|
else {
|
||||||
|
return err(400, "iface, ap_iface e bssid são obrigatórios");
|
||||||
|
};
|
||||||
|
match engine.start_evil_twin_enterprise(&iface, &ap_iface, &bssid) {
|
||||||
|
Ok(id) => ok_json(serde_json::json!({ "ok": true, "job": id })),
|
||||||
|
Err(e) => err(400, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("GET", "/api/eviltwin-enterprise") => {
|
||||||
|
let Some(id) = req.text("id") else {
|
||||||
|
return err(400, "id obrigatório");
|
||||||
|
};
|
||||||
|
match engine.enterprise_twin_job(&id) {
|
||||||
|
Some(job) => ok(&job),
|
||||||
|
None => err(404, "job não encontrado"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("GET", "/api/eviltwins-enterprise") => ok(&engine.enterprise_twin_jobs()),
|
||||||
|
("POST", "/api/eviltwin-enterprise/stop") => {
|
||||||
|
let Some(id) = req.text("id") else {
|
||||||
|
return err(400, "id obrigatório");
|
||||||
|
};
|
||||||
|
match engine.stop_evil_twin_enterprise(&id) {
|
||||||
|
Ok(()) => ok_json(serde_json::json!({ "ok": true })),
|
||||||
|
Err(e) => err(400, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("POST", "/api/pmkid") => {
|
||||||
|
if !req.bool("confirm") {
|
||||||
|
return err(400, "confirme a autorização do teste (confirm=true)");
|
||||||
|
}
|
||||||
|
let (Some(iface), Some(bssid)) = (req.text("iface"), req.text("bssid")) else {
|
||||||
|
return err(400, "iface e bssid são obrigatórios");
|
||||||
|
};
|
||||||
|
let attempts = req.u32("attempts").unwrap_or(5);
|
||||||
|
match engine.pmkid_hunt(&iface, &bssid, attempts) {
|
||||||
|
Ok(report) => ok(&report),
|
||||||
|
Err(e) => err(400, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("POST", "/api/pmf") => {
|
||||||
|
if !req.bool("confirm") {
|
||||||
|
return err(400, "confirme a autorização do teste (confirm=true)");
|
||||||
|
}
|
||||||
|
let (Some(iface), Some(bssid), Some(station)) =
|
||||||
|
(req.text("iface"), req.text("bssid"), req.text("station"))
|
||||||
|
else {
|
||||||
|
return err(400, "iface, bssid e station são obrigatórios");
|
||||||
|
};
|
||||||
|
let reason = req.u16("reason").unwrap_or(7);
|
||||||
|
match engine.pmf_test(&iface, &bssid, &station, reason) {
|
||||||
|
Ok(report) => ok(&report),
|
||||||
|
Err(e) => err(400, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
("POST", "/api/crack") => {
|
("POST", "/api/crack") => {
|
||||||
let (Some(iface), Some(wordlist)) = (req.text("iface"), req.text("wordlist")) else {
|
let Some(iface) = req.text("iface") else {
|
||||||
return err(400, "iface e wordlist são obrigatórios");
|
return err(400, "iface é obrigatória");
|
||||||
};
|
};
|
||||||
let bssid = req.text("bssid");
|
let bssid = req.text("bssid");
|
||||||
let station = req.text("station");
|
let station = req.text("station");
|
||||||
match engine.start_crack(&iface, bssid.as_deref(), station.as_deref(), &wordlist) {
|
let rules = req.bool("rules");
|
||||||
|
let result = match req.text("wordlist") {
|
||||||
|
Some(wordlist) if !wordlist.trim().is_empty() => engine.start_crack(
|
||||||
|
&iface,
|
||||||
|
bssid.as_deref(),
|
||||||
|
station.as_deref(),
|
||||||
|
&wordlist,
|
||||||
|
rules,
|
||||||
|
),
|
||||||
|
_ => {
|
||||||
|
engine.start_crack_default(&iface, bssid.as_deref(), station.as_deref(), rules)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match result {
|
||||||
|
Ok(id) => ok_json(serde_json::json!({ "ok": true, "job": id })),
|
||||||
|
Err(e) => err(400, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("POST", "/api/crack/mask") => {
|
||||||
|
let Some(iface) = req.text("iface") else {
|
||||||
|
return err(400, "iface é obrigatória");
|
||||||
|
};
|
||||||
|
let Some(mask) = req.text("mask").filter(|m| !m.trim().is_empty()) else {
|
||||||
|
return err(400, "máscara é obrigatória");
|
||||||
|
};
|
||||||
|
let bssid = req.text("bssid");
|
||||||
|
let station = req.text("station");
|
||||||
|
let max_candidates = req.u64("max_candidates").unwrap_or(50_000_000);
|
||||||
|
match engine.start_crack_mask(
|
||||||
|
&iface,
|
||||||
|
bssid.as_deref(),
|
||||||
|
station.as_deref(),
|
||||||
|
&mask,
|
||||||
|
max_candidates,
|
||||||
|
) {
|
||||||
Ok(id) => ok_json(serde_json::json!({ "ok": true, "job": id })),
|
Ok(id) => ok_json(serde_json::json!({ "ok": true, "job": id })),
|
||||||
Err(e) => err(400, e),
|
Err(e) => err(400, e),
|
||||||
}
|
}
|
||||||
@@ -441,17 +711,94 @@ fn api(engine: &Arc<Engine>, req: &Request) -> (u16, String) {
|
|||||||
};
|
};
|
||||||
let count = req.u32("count").unwrap_or(5);
|
let count = req.u32("count").unwrap_or(5);
|
||||||
let reason = req.u16("reason").unwrap_or(7);
|
let reason = req.u16("reason").unwrap_or(7);
|
||||||
match engine.deauth(
|
let direction = req.text("direction").unwrap_or_else(|| "ap".into());
|
||||||
|
let frame_kind = req.text("frame").unwrap_or_else(|| "deauth".into());
|
||||||
|
match engine.deauth_directional(
|
||||||
&iface,
|
&iface,
|
||||||
&bssid,
|
&bssid,
|
||||||
req.text("station").as_deref(),
|
req.text("station").as_deref(),
|
||||||
count,
|
count,
|
||||||
reason,
|
reason,
|
||||||
|
&direction,
|
||||||
|
&frame_kind,
|
||||||
) {
|
) {
|
||||||
Ok(report) => ok(&report),
|
Ok(report) => ok(&report),
|
||||||
Err(e) => err(400, e),
|
Err(e) => err(400, e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
("POST", "/api/deauth/start") => {
|
||||||
|
if !req.bool("confirm") {
|
||||||
|
return err(
|
||||||
|
400,
|
||||||
|
"confirme a autorização do teste (confirm=true) antes de desautenticar",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let (Some(iface), Some(bssid)) = (req.text("iface"), req.text("bssid")) else {
|
||||||
|
return err(400, "iface e bssid são obrigatórios");
|
||||||
|
};
|
||||||
|
let reason = req.u16("reason").unwrap_or(7);
|
||||||
|
let direction = req.text("direction").unwrap_or_else(|| "both".into());
|
||||||
|
let frame_kind = req.text("frame").unwrap_or_else(|| "deauth".into());
|
||||||
|
let interval_ms = req.u64("interval_ms").unwrap_or(120);
|
||||||
|
let max_secs = req.u64("max_secs");
|
||||||
|
let stop_on_handshake = req.bool("stop_on_handshake");
|
||||||
|
match engine.start_deauth_job(
|
||||||
|
&iface,
|
||||||
|
&bssid,
|
||||||
|
req.text("station").as_deref(),
|
||||||
|
reason,
|
||||||
|
&direction,
|
||||||
|
&frame_kind,
|
||||||
|
interval_ms,
|
||||||
|
max_secs,
|
||||||
|
stop_on_handshake,
|
||||||
|
) {
|
||||||
|
Ok(id) => ok_json(serde_json::json!({ "ok": true, "job": id })),
|
||||||
|
Err(e) => err(400, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("POST", "/api/deauth/stop") => {
|
||||||
|
let Some(id) = req.text("id") else {
|
||||||
|
return err(400, "id obrigatório");
|
||||||
|
};
|
||||||
|
match engine.stop_deauth_job(&id) {
|
||||||
|
Ok(()) => ok_json(serde_json::json!({ "ok": true })),
|
||||||
|
Err(e) => err(400, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("GET", "/api/deauth/job") => {
|
||||||
|
let Some(id) = req.text("id") else {
|
||||||
|
return err(400, "id obrigatório");
|
||||||
|
};
|
||||||
|
match engine.deauth_job(&id) {
|
||||||
|
Some(job) => ok(&job),
|
||||||
|
None => err(404, "job não encontrado"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("GET", "/api/deauth/jobs") => {
|
||||||
|
let jobs = engine.deauth_jobs();
|
||||||
|
match req.text("iface") {
|
||||||
|
Some(iface) => ok(&jobs
|
||||||
|
.into_iter()
|
||||||
|
.filter(|j| j.iface.eq_ignore_ascii_case(&iface))
|
||||||
|
.collect::<Vec<_>>()),
|
||||||
|
None => ok(&jobs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("POST", "/api/handshakes/capture") => {
|
||||||
|
if !req.bool("confirm") {
|
||||||
|
return err(400, "confirme a autorização do teste (confirm=true)");
|
||||||
|
}
|
||||||
|
let (Some(iface), Some(bssid)) = (req.text("iface"), req.text("bssid")) else {
|
||||||
|
return err(400, "iface e bssid são obrigatórios");
|
||||||
|
};
|
||||||
|
let seconds = req.u64("seconds");
|
||||||
|
match engine.capture_handshake(&iface, &bssid, req.text("station").as_deref(), seconds)
|
||||||
|
{
|
||||||
|
Ok(id) => ok_json(serde_json::json!({ "ok": true, "job": id })),
|
||||||
|
Err(e) => err(400, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
("POST", "/api/analyze") => {
|
("POST", "/api/analyze") => {
|
||||||
let pcap = if !req.body.is_empty() {
|
let pcap = if !req.body.is_empty() {
|
||||||
serde_json::from_slice::<AnalyzeBody>(&req.body)
|
serde_json::from_slice::<AnalyzeBody>(&req.body)
|
||||||
|
|||||||
+2571
-16
File diff suppressed because it is too large.
Load diff
@@ -3,22 +3,31 @@
|
|||||||
//! gráfica são só consumidoras disto — a parte complexa (radiotap, 802.11,
|
//! gráfica são só consumidoras disto — a parte complexa (radiotap, 802.11,
|
||||||
//! EAPOL, captura e injeção) fica inteira aqui dentro.
|
//! EAPOL, captura e injeção) fica inteira aqui dentro.
|
||||||
|
|
||||||
|
pub mod analysis;
|
||||||
pub mod audit;
|
pub mod audit;
|
||||||
|
pub mod ccmp;
|
||||||
pub mod crack;
|
pub mod crack;
|
||||||
pub mod doctor;
|
pub mod doctor;
|
||||||
pub mod dot11;
|
pub mod dot11;
|
||||||
pub mod eapol;
|
pub mod eapol;
|
||||||
pub mod engine;
|
pub mod engine;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod eviltwin;
|
||||||
|
pub mod eviltwin_enterprise;
|
||||||
pub mod handshake;
|
pub mod handshake;
|
||||||
|
pub mod hashcat;
|
||||||
pub mod iface;
|
pub mod iface;
|
||||||
pub mod inject;
|
pub mod inject;
|
||||||
pub mod netlink;
|
pub mod netlink;
|
||||||
|
pub mod oui;
|
||||||
pub mod pcapreader;
|
pub mod pcapreader;
|
||||||
pub mod pcapwriter;
|
pub mod pcapwriter;
|
||||||
pub mod radiotap;
|
pub mod radiotap;
|
||||||
|
pub mod report;
|
||||||
|
pub mod rules;
|
||||||
pub mod scan;
|
pub mod scan;
|
||||||
pub mod update;
|
pub mod update;
|
||||||
|
pub mod wps;
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub mod capture;
|
pub mod capture;
|
||||||
|
|||||||
Reference in new issue
Block a user