mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-07-31 03:03:06 -07:00
refactoring with no behavioral changes
This commit is contained in:
@@ -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);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|||||||
Reference in New Issue
Block a user