Skip to content

Commit 98e3297

Browse files
committed
Improve rogue-attack logic and status info
1 parent 8938bb4 commit 98e3297

File tree

4 files changed

+149
-102
lines changed

4 files changed

+149
-102
lines changed

src/attack.rs

+81-66
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
// Attack! //
22

3-
use std::os::fd::AsRawFd;
3+
use std::{
4+
os::fd::AsRawFd,
5+
time::{Duration, SystemTime},
6+
};
47

58
use libwifi::frame::{
69
components::{MacAddress, RsnCipherSuite},
710
DeauthenticationReason, ProbeRequest,
811
};
12+
use rand::seq::SliceRandom;
913

1014
use crate::{
1115
status::{MessageType, StatusMessage},
@@ -333,13 +337,22 @@ pub fn rogue_m2_attack_directed(
333337
}
334338
let ssid = probe.station_info.ssid.unwrap();
335339

340+
// Check SSID against SSID targets
341+
if !oxide.stargets.is_empty() && !oxide.stargets.contains(&ssid) {
342+
return Ok(());
343+
}
344+
336345
// Grab the station from our unnasoc. clients list.
337346
let station = if let Some(dev) = oxide.unassoc_clients.get_device(&probe.header.address_2) {
338347
dev
339348
} else {
340349
return Ok(());
341350
};
342351

352+
if station.timer_interact.elapsed().unwrap() < Duration::from_secs(3) {
353+
return Ok(());
354+
}
355+
343356
// If we have an AP for this SSID, we will use as many of the same details as possible
344357
if let Some(ap) = oxide.access_points.get_device_by_ssid(&ssid) {
345358
// Make sure this AP is a target and that this AP is
@@ -361,6 +374,7 @@ pub fn rogue_m2_attack_directed(
361374
);
362375
write_packet(oxide.tx_socket.as_raw_fd(), &frx)?;
363376
station.interactions += 1;
377+
station.timer_interact = SystemTime::now();
364378
oxide.status_log.add_message(StatusMessage::new(
365379
MessageType::Info,
366380
format!("Direct Rogue AP Attack: {} ({})", station.mac_address, ssid),
@@ -389,86 +403,87 @@ pub fn rogue_m2_attack_undirected(
389403
return Ok(());
390404
};
391405

406+
if station.timer_interact.elapsed().unwrap() < Duration::from_secs(3) {
407+
return Ok(());
408+
}
409+
392410
if let Some(ssid) = probe.station_info.ssid {
393-
// In this case we have an SSID to send, which is good. If we have a target deck, and the SSID matches the SSID we have for that AP (in the target deck) then we send a response.
394-
if !oxide.targets.is_empty() {
395-
// We have a target deck
396-
if let Some(ap) = oxide.access_points.get_device_by_ssid(&ssid) {
397-
// AP exists
398-
399-
// Does the target deck contain our AP?
400-
if !oxide.targets.contains(&ap.mac_address) {
401-
return Ok(());
402-
}
403-
404-
// Do we already have a rogue-M2 from this station?
405-
if station.has_rogue_m2 {
406-
return Ok(());
407-
}
408-
409-
let frx = build_probe_response(
410-
&probe.header.address_2,
411-
&oxide.rogue_client,
412-
&ssid,
413-
oxide.counters.sequence3(),
414-
oxide.current_channel.get_channel_number(),
415-
);
416-
write_packet(oxide.tx_socket.as_raw_fd(), &frx)?;
417-
station.interactions += 1;
418-
oxide.status_log.add_message(StatusMessage::new(
419-
MessageType::Info,
420-
format!(
421-
"Indirect Rogue AP Attack: {} ({})",
422-
station.mac_address, ssid
423-
),
424-
));
425-
}
426-
} else {
427-
// no targets, just send a probe with the SSID back.
411+
// Target validation stuff
412+
let mut target_checks = false;
413+
let mut is_target = false;
414+
415+
if !oxide.targets.is_empty() || !oxide.stargets.is_empty() {
416+
target_checks = true;
417+
}
418+
419+
// Is the AP the SSID belongs to (based on our survey) in our MAC Targets?
420+
if oxide
421+
.access_points
422+
.get_device_by_ssid(&ssid)
423+
.is_some_and(|ap| oxide.targets.contains(&ap.mac_address))
424+
{
425+
is_target = true;
426+
}
427+
428+
// Is the SSID in our stargets?
429+
if oxide.stargets.contains(&ssid) {
430+
is_target = true;
431+
}
432+
433+
// Both checks fail, we shouldn't persue this.
434+
if target_checks && !is_target {
435+
return Ok(());
436+
}
437+
438+
// Do we already have a rogue-M2 from this station (for this SSID)
439+
if station.rogue_actions.get(&ssid).is_some_and(|f| *f) {
440+
return Ok(());
441+
}
442+
443+
let frx = build_probe_response(
444+
&probe.header.address_2,
445+
&oxide.rogue_client,
446+
&ssid,
447+
oxide.counters.sequence3(),
448+
oxide.current_channel.get_channel_number(),
449+
);
450+
write_packet(oxide.tx_socket.as_raw_fd(), &frx)?;
451+
station.interactions += 1;
452+
station.timer_interact = SystemTime::now();
453+
oxide.status_log.add_message(StatusMessage::new(
454+
MessageType::Info,
455+
format!(
456+
"Indirect Rogue AP Attack: {} ({})",
457+
station.mac_address, ssid
458+
),
459+
));
460+
} else {
461+
// We don't want to nest this...
462+
if !oxide.stargets.is_empty() {
463+
// Pick a random SSID from our targets and respond.
464+
let target = &oxide
465+
.stargets
466+
.choose(&mut rand::thread_rng())
467+
.unwrap_or(return Ok(()));
468+
428469
let frx = build_probe_response(
429470
&probe.header.address_2,
430471
&oxide.rogue_client,
431-
&ssid,
472+
target,
432473
oxide.counters.sequence3(),
433474
oxide.current_channel.get_channel_number(),
434475
);
435476
write_packet(oxide.tx_socket.as_raw_fd(), &frx)?;
436477
station.interactions += 1;
478+
station.timer_interact = SystemTime::now();
437479
oxide.status_log.add_message(StatusMessage::new(
438480
MessageType::Info,
439481
format!(
440-
"Indirect Rogue AP Attack: {} ({})",
441-
station.mac_address, ssid
482+
"Anonymous Rogue AP Attempt: {} ({})",
483+
station.mac_address, target
442484
),
443485
));
444486
}
445-
} else {
446-
// We don't want to nest this...
447-
if !oxide.targets.is_empty() {
448-
// We have targets, iterate through our targets list and if we have an AP seen for the SSID we can send our own Probe Response... maybe we can force a WPA2 authentication.
449-
for target in &oxide.targets {
450-
if let Some(ap) = oxide.access_points.get_device(target) {
451-
if let Some(ssid) = &ap.ssid {
452-
let frx = build_probe_response(
453-
&probe.header.address_2,
454-
&oxide.rogue_client,
455-
ssid,
456-
oxide.counters.sequence3(),
457-
oxide.current_channel.get_channel_number(),
458-
);
459-
write_packet(oxide.tx_socket.as_raw_fd(), &frx)?;
460-
station.interactions += 1;
461-
oxide.status_log.add_message(StatusMessage::new(
462-
MessageType::Info,
463-
format!(
464-
"Indirect Rogue AP Attack: {} ({})",
465-
station.mac_address, ssid
466-
),
467-
));
468-
}
469-
}
470-
}
471-
}
472487
}
473488

474489
Ok(())

src/auth.rs

+15-9
Original file line numberDiff line numberDiff line change
@@ -610,14 +610,13 @@ impl HandshakeStorage {
610610
client_mac: &MacAddress,
611611
new_key: EapolKey,
612612
essid: Option<String>,
613-
) -> Result<FourWayHandshake, &'static str> {
613+
) -> Result<&mut FourWayHandshake, &'static str> {
614614
let session_key = HandshakeSessionKey::new(*ap_mac, *client_mac);
615615

616616
let handshake_list = self.handshakes.entry(session_key).or_default();
617617

618618
// Sort the handshake list so that the most recent handshake is first.
619619
handshake_list.sort_by(|a, b| {
620-
// Compare the timestamps in reverse order (most recent first)
621620
b.last_msg
622621
.as_ref()
623622
.map_or(std::time::SystemTime::UNIX_EPOCH, |x| x.timestamp)
@@ -628,24 +627,31 @@ impl HandshakeStorage {
628627
)
629628
});
630629

631-
for handshake in &mut *handshake_list {
630+
let mut index_to_return: Option<usize> = None;
631+
632+
for (index, handshake) in handshake_list.iter_mut().enumerate() {
632633
if handshake.add_key(&new_key).is_ok() {
633634
handshake.mac_ap = Some(*ap_mac);
634635
handshake.mac_client = Some(*client_mac);
635-
handshake.essid = essid;
636-
return Ok(handshake.clone());
636+
handshake.essid = essid.clone();
637+
index_to_return = Some(index);
638+
break;
637639
}
638640
}
639641

640-
// If we don't find a suitable handshake, let's create a new one and add it (so we don't drop any EAPOL frames)
642+
if let Some(index) = index_to_return {
643+
return Ok(&mut handshake_list[index]);
644+
}
645+
646+
// If we don't find a suitable handshake, create a new one and add it.
641647
let mut new_handshake = FourWayHandshake::new();
642648
new_handshake.add_key(&new_key)?;
643649
new_handshake.mac_ap = Some(*ap_mac);
644650
new_handshake.mac_client = Some(*client_mac);
645651
new_handshake.essid = essid;
646-
let hs = new_handshake.clone();
647-
handshake_list.push(new_handshake.clone());
648-
Ok(hs)
652+
653+
handshake_list.push(new_handshake);
654+
Ok(handshake_list.last_mut().unwrap())
649655
}
650656

651657
pub fn get_table(

src/devices.rs

+26-15
Original file line numberDiff line numberDiff line change
@@ -301,10 +301,9 @@ pub struct Station {
301301
pub access_point: Option<MacAddress>,
302302
pub aid: u16,
303303
pub probes: Option<Vec<String>>,
304-
pub timer_auth: SystemTime,
305-
pub timer_assoc: SystemTime,
306-
pub timer_reassoc: SystemTime,
304+
pub timer_interact: SystemTime,
307305
pub has_rogue_m2: bool,
306+
pub rogue_actions: HashMap<String, bool>,
308307
}
309308

310309
impl WiFiDeviceType for Station {}
@@ -320,10 +319,9 @@ impl Default for Station {
320319
access_point: None,
321320
aid: 0,
322321
probes: None,
323-
timer_auth: SystemTime::now(),
324-
timer_assoc: SystemTime::now(),
325-
timer_reassoc: SystemTime::now(),
322+
timer_interact: SystemTime::UNIX_EPOCH,
326323
has_rogue_m2: false,
324+
rogue_actions: HashMap::new(),
327325
}
328326
}
329327
}
@@ -345,10 +343,9 @@ impl Station {
345343
access_point,
346344
aid: 0,
347345
probes: None,
348-
timer_auth: SystemTime::now(),
349-
timer_assoc: SystemTime::now(),
350-
timer_reassoc: SystemTime::now(),
346+
timer_interact: SystemTime::UNIX_EPOCH,
351347
has_rogue_m2: false,
348+
rogue_actions: HashMap::new(),
352349
}
353350
}
354351

@@ -368,10 +365,9 @@ impl Station {
368365
access_point: None,
369366
aid: 0,
370367
probes: Some(probes),
371-
timer_auth: SystemTime::now(),
372-
timer_assoc: SystemTime::now(),
373-
timer_reassoc: SystemTime::now(),
368+
timer_interact: SystemTime::UNIX_EPOCH,
374369
has_rogue_m2: false,
370+
rogue_actions: HashMap::new(),
375371
}
376372
}
377373

@@ -670,9 +666,19 @@ fn add_client_rows(ap_row: Vec<String>, client: &Station, last: bool) -> Vec<Str
670666
/// C) We actually got the M2.
671667
///
672668
/// Doing this will require refactoring the probe-storage so each Station's "Probe" is actually a struct that also has a RogueM2 bool.
673-
fn add_probe_rows(cl_row: Vec<String>, probe: &String, last: bool) -> Vec<String> {
669+
fn add_probe_rows(
670+
cl_row: Vec<String>,
671+
probe: &String,
672+
last: bool,
673+
rogue_collected: bool,
674+
) -> Vec<String> {
674675
let min_length = cl_row.len();
675676
let icon = if last { "└ " } else { "├ " };
677+
let check = if rogue_collected {
678+
"\u{2714}".to_string()
679+
} else {
680+
" ".to_string()
681+
};
676682

677683
let mut merged = Vec::with_capacity(min_length);
678684

@@ -693,7 +699,7 @@ fn add_probe_rows(cl_row: Vec<String>, probe: &String, last: bool) -> Vec<String
693699
merged.push(new_str);
694700

695701
// Rogue 4
696-
let new_str: String = cl_row[4].to_string();
702+
let new_str: String = format!("{}\n{}", cl_row[4], check);
697703
merged.push(new_str);
698704

699705
// Probes 5
@@ -796,7 +802,12 @@ impl WiFiDeviceList<Station> {
796802
if let Some(probes) = &station.probes {
797803
for (idx, probe) in probes.iter().enumerate() {
798804
let last = idx == probes.len() - 1;
799-
let merged = add_probe_rows(cl_row, probe, last);
805+
let merged = add_probe_rows(
806+
cl_row,
807+
probe,
808+
last,
809+
station.rogue_actions.get(probe).is_some_and(|f| *f),
810+
);
800811
cl_row = merged;
801812
height += 1;
802813
}

0 commit comments

Comments
 (0)