refactoring with no behavioral changes

This commit is contained in:
Brad Warren
2026-07-30 12:27:56 -07:00
parent 166b953e87
commit 58bda5cd64
+57 -38
View File
@@ -5,7 +5,7 @@ use pycrate_rs::nas::emm::EMMMessage;
use super::analyzer::{Analyzer, Event, EventType}; use super::analyzer::{Analyzer, Event, EventType};
use super::information_element::{InformationElement, LteInformationElement}; use super::information_element::{InformationElement, LteInformationElement};
use log::debug; use log::{debug, error};
use pycrate_rs::nas::generated::emm::emm_attach_reject::EMMCauseEMMCause as AttachRejectEMMCause; use pycrate_rs::nas::generated::emm::emm_attach_reject::EMMCauseEMMCause as AttachRejectEMMCause;
use pycrate_rs::nas::generated::emm::emm_attach_request::TAI; use pycrate_rs::nas::generated::emm::emm_attach_request::TAI;
@@ -25,14 +25,15 @@ pub enum State {
IdentityRequest, IdentityRequest,
AuthAccept, AuthAccept,
Disconnect, Disconnect,
LikelyValidAttachReject,
} }
pub struct ImsiRequestedAnalyzer { pub struct ImsiRequestedAnalyzer {
state: State, state: State,
timeout_counter: usize, timeout_counter: usize,
flag: Option<Event>, flag: Option<Event>,
likely_enb_plmn: String, likely_enb_plmns: Vec<String>,
likely_ue_plmn: String, likely_ue_plmn: Option<String>,
} }
impl Default for ImsiRequestedAnalyzer { impl Default for ImsiRequestedAnalyzer {
@@ -47,10 +48,8 @@ impl ImsiRequestedAnalyzer {
state: State::Unattached, state: State::Unattached,
timeout_counter: 0, timeout_counter: 0,
flag: None, flag: None,
// You will likely wonder why this isn't an Option<PLMN{mcc: u32, mnc: u32}> likely_enb_plmns: vec![],
// The answer is that I like strings. likely_ue_plmn: None,
likely_enb_plmn: "Unknown".to_string(),
likely_ue_plmn: "Unknown".to_string(),
} }
} }
@@ -81,19 +80,36 @@ impl ImsiRequestedAnalyzer {
}); });
} }
// Expected AttachReject for inactive SIMs
(_, State::LikelyValidAttachReject) => {
self.flag = Some(Event {
event_type: EventType::Low,
message: "Identity requested without authentication but its likely a false positive unless your SIM card has an active plan".to_string(),
});
}
// IMSI to Disconnect without AuthAccept // IMSI to Disconnect without AuthAccept
(State::IdentityRequest, State::Disconnect) => { (State::IdentityRequest, State::Disconnect) => {
if self.likely_ue_plmn != "Unknown" && self.likely_enb_plmn == self.likely_ue_plmn { if self.likely_ue_plmn.as_ref().is_some_and(|p| {
self.likely_enb_plmns.len() == 1 && self.likely_enb_plmns.contains(p)
}) {
self.flag = Some(Event { self.flag = Some(Event {
event_type: EventType::High, event_type: EventType::High,
message: "Disconnected after Identity Request without Auth Accept on home network!".to_string(), message: "Disconnected after Identity Request without Auth Accept on home network!".to_string(),
}); });
} else { } else {
let enb_plmn_string = if self.likely_enb_plmns.is_empty() {
"Unknown"
} else {
&self.likely_enb_plmns.join(", ")
};
let ue_plmn_string = self.likely_ue_plmn.as_deref().unwrap_or("Unknown");
self.flag = Some(Event { self.flag = Some(Event {
event_type: EventType::Low, event_type: EventType::Low,
message: format!( message: format!(
"Disconnected after Identity Request without Auth Accept, but this could be a false positive roaming issue - Tower PLMN: {}, UE PLMN: {}", "Disconnected after Identity Request without Auth Accept, but this could be a false positive roaming issue - Tower PLMN: {}, UE PLMN: {}",
self.likely_enb_plmn, self.likely_ue_plmn enb_plmn_string, ue_plmn_string,
), ),
}); });
} }
@@ -111,22 +127,28 @@ impl ImsiRequestedAnalyzer {
); );
} }
} }
self.state = next_state;
// LikelyValidAttachReject is a special case of Disconnect so after handling any special
// behavior above, we transition to the standard Disconnect state.
if next_state == State::LikelyValidAttachReject {
self.state = State::Disconnect;
} else {
self.state = next_state;
}
} }
// Sometimes an ENB can have multiple PLMNS // Sometimes an ENB can have multiple PLMNS
fn format_plmn_list(&mut self, plmn_list: &PLMN_IdentityList) -> String { fn format_plmn_list(&self, plmn_list: &PLMN_IdentityList) -> Vec<String> {
plmn_list plmn_list
.0 .0
.iter() .iter()
.map(|info| self.plmn_identity_to_str(&info.plmn_identity)) .map(|info| self.plmn_identity_to_str(&info.plmn_identity))
.collect::<Vec<_>>() .collect()
.join(", ")
} }
// PLMN is represented in two very different ways in the LTE spec so we need // PLMN is represented in two very different ways in the LTE spec so we need
// two very different functions to decode them. I hate this. // two very different functions to decode them. I hate this.
fn plmn_identity_to_str(&mut self, plmn: &PLMN_Identity) -> String { fn plmn_identity_to_str(&self, plmn: &PLMN_Identity) -> String {
let mcc_digits: String = plmn let mcc_digits: String = plmn
.mcc .mcc
.as_ref() .as_ref()
@@ -148,14 +170,20 @@ impl ImsiRequestedAnalyzer {
format!("{}-{}", mcc_digits, mnc_digits) format!("{}-{}", mcc_digits, mnc_digits)
} }
fn plmn_vec_to_str(&mut self, bytes: &[u8]) -> String { fn tai_to_plmn_str(&self, maybe_tai: Option<&TAI>) -> Option<String> {
let mcc_digit1 = bytes[0] & 0x0F; let plmn = &maybe_tai?.plmn;
let mcc_digit2 = (bytes[0] >> 4) & 0x0F; if plmn.len() != 3 {
let mcc_digit3 = bytes[1] & 0x0F; error!("TAI.plmn vector has unexpected length of {}", plmn.len());
return None;
}
let mnc_digit1 = bytes[2] & 0x0F; let mcc_digit1 = plmn[0] & 0x0F;
let mnc_digit2 = (bytes[2] >> 4) & 0x0F; let mcc_digit2 = (plmn[0] >> 4) & 0x0F;
let mnc_digit3 = (bytes[1] >> 4) & 0x0F; let mcc_digit3 = plmn[1] & 0x0F;
let mnc_digit1 = plmn[2] & 0x0F;
let mnc_digit2 = (plmn[2] >> 4) & 0x0F;
let mnc_digit3 = (plmn[1] >> 4) & 0x0F;
let mcc = mcc_digit1 as u32 * 100 + mcc_digit2 as u32 * 10 + mcc_digit3 as u32; let mcc = mcc_digit1 as u32 * 100 + mcc_digit2 as u32 * 10 + mcc_digit3 as u32;
@@ -169,14 +197,7 @@ impl ImsiRequestedAnalyzer {
) )
}; };
format!("{}-{}", mcc_str, mnc_str) Some(format!("{}-{}", mcc_str, mnc_str))
}
fn extract_plmn(&mut self, old_tai: &Option<TAI>) -> String {
match old_tai {
Some(t) => self.plmn_vec_to_str(&t.plmn),
None => "Unknown".to_string(),
}
} }
} }
@@ -208,7 +229,7 @@ impl Analyzer for ImsiRequestedAnalyzer {
&& let BCCH_DL_SCH_MessageType_c1::SystemInformationBlockType1(sib1) = c1 && let BCCH_DL_SCH_MessageType_c1::SystemInformationBlockType1(sib1) = c1
{ {
let plmn = &sib1.cell_access_related_info.plmn_identity_list; let plmn = &sib1.cell_access_related_info.plmn_identity_list;
self.likely_enb_plmn = self.format_plmn_list(plmn); self.likely_enb_plmns = self.format_plmn_list(plmn);
return None; return None;
} }
@@ -217,9 +238,9 @@ impl Analyzer for ImsiRequestedAnalyzer {
match &**inner { match &**inner {
LteInformationElement::NAS(payload) => match payload { LteInformationElement::NAS(payload) => match payload {
NASMessage::EMMMessage(EMMMessage::EMMAttachRequest(request)) => { NASMessage::EMMMessage(EMMMessage::EMMAttachRequest(request)) => {
let plmn = self.extract_plmn(&request.old_tai.inner); let maybe_plmn = self.tai_to_plmn_str(request.old_tai.inner.as_ref());
if plmn != "Unknown" { if maybe_plmn.is_some() {
self.likely_ue_plmn = plmn; self.likely_ue_plmn = maybe_plmn;
} }
self.transition(State::AttachRequest, packet_num); self.transition(State::AttachRequest, packet_num);
} }
@@ -240,14 +261,12 @@ impl Analyzer for ImsiRequestedAnalyzer {
self.transition(State::Disconnect, packet_num); self.transition(State::Disconnect, packet_num);
} }
NASMessage::EMMMessage(EMMMessage::EMMAttachReject(reject)) => { NASMessage::EMMMessage(EMMMessage::EMMAttachReject(reject)) => {
self.transition(State::Disconnect, packet_num);
if reject.emm_cause.inner if reject.emm_cause.inner
== AttachRejectEMMCause::EPSServicesAndNonEPSServicesNotAllowed == AttachRejectEMMCause::EPSServicesAndNonEPSServicesNotAllowed
{ {
self.flag = Some(Event { self.transition(State::LikelyValidAttachReject, packet_num);
event_type: EventType::Low, } else {
message: "Identity requested without authentication but its likely a false positive unless your SIM card has an active plan".to_string(), self.transition(State::Disconnect, packet_num);
});
} }
} }
_ => {} _ => {}