imsi requested version 2; remove most false positives

This commit is contained in:
Cooper Quintin
2025-07-17 17:03:55 -07:00
committed by Will Greenberg
parent 578bc0d234
commit e07b0b05e7

View File

@@ -2,15 +2,31 @@ use std::borrow::Cow;
use pycrate_rs::nas::NASMessage;
use pycrate_rs::nas::emm::EMMMessage;
use pycrate_rs::nas::generated::emm::emm_identity_request::IDTypeV;
use super::analyzer::{Analyzer, Event, EventType, Severity};
use super::information_element::{InformationElement, LteInformationElement};
use log::debug;
const PACKET_THRESHHOLD: usize = 150;
use telcom_parser::lte_rrc::{
DL_DCCH_MessageType, DL_DCCH_MessageType_c1, UL_CCCH_MessageType, UL_CCCH_MessageType_c1,
};
const TIMEOUT_THRESHHOLD: usize = 50;
#[derive(PartialEq, Debug)]
pub enum State {
Unattached,
AttachRequest,
IdentityRequest,
AuthAccept,
Disconnect,
}
pub struct ImsiRequestedAnalyzer {
packet_num: usize,
state: State,
timeout_counter: usize,
flag: Option<Event>,
}
impl Default for ImsiRequestedAnalyzer {
@@ -21,60 +37,148 @@ impl Default for ImsiRequestedAnalyzer {
impl ImsiRequestedAnalyzer {
pub fn new() -> Self {
Self { packet_num: 0 }
Self {
packet_num: 0,
state: State::Unattached,
timeout_counter: 0,
flag: None,
}
}
fn transition(&mut self, next_state: State) {
match (&self.state, &next_state) {
// Reset timeout on successful auth
(_, State::AuthAccept) => {
debug!(
"reset timeout counter at {} due to auth accept (frame {})",
self.timeout_counter, self.packet_num
);
self.timeout_counter = 0;
}
// Unexpected IMSI without AttachRequest
(current, State::IdentityRequest) if *current != State::AttachRequest => {
self.flag = Some(Event {
event_type: EventType::QualitativeWarning {
severity: Severity::High,
},
message: format!(
"Identity requested without Attach Request (frame {})",
self.packet_num
)
.to_string(),
});
}
// IMSI to Disconnect without AuthAccept
(State::IdentityRequest, State::Disconnect) => {
self.flag = Some(Event {
event_type: EventType::QualitativeWarning {
severity: Severity::High,
},
message: format!(
"Disconnected after Identity Request without Auth Accept (frame {})",
self.packet_num
)
.to_string(),
});
self.timeout_counter = 0;
}
// All other transitions proceeed
_ => {
debug!(
"Transition from {:?} to {:?} at {}",
self.state, next_state, self.packet_num
);
}
}
self.state = next_state;
}
}
impl Analyzer for ImsiRequestedAnalyzer {
fn get_name(&self) -> Cow<str> {
Cow::from("IMSI Requested")
Cow::from("Identity (IMSI or IMEI) requested in suspicious manner")
}
fn get_description(&self) -> Cow<str> {
Cow::from("Tests whether the ME sends an IMSI Identity Request NAS message")
Cow::from(
"Tests whether the ME sends an Identity Request NAS message without either an associated attach request or auth accept message",
)
}
fn get_version(&self) -> u32 {
1
2
}
fn analyze_information_element(&mut self, ie: &InformationElement) -> Option<Event> {
self.packet_num += 1;
let payload = match ie {
InformationElement::LTE(inner) => match &**inner {
LteInformationElement::NAS(payload) => payload,
_ => return None,
},
_ => return None,
if let InformationElement::LTE(inner) = ie {
match &**inner {
LteInformationElement::NAS(payload) => match payload {
NASMessage::EMMMessage(EMMMessage::EMMExtServiceRequest(_))
| NASMessage::EMMMessage(EMMMessage::EMMAttachRequest(_)) => {
self.transition(State::AttachRequest);
}
NASMessage::EMMMessage(EMMMessage::EMMIdentityRequest(_)) => {
self.transition(State::IdentityRequest);
}
NASMessage::EMMMessage(EMMMessage::EMMAuthenticationResponse(_)) => {
self.transition(State::AuthAccept);
}
NASMessage::EMMMessage(EMMMessage::EMMServiceReject(_))
| NASMessage::EMMMessage(EMMMessage::EMMAttachReject(_))
| NASMessage::EMMMessage(EMMMessage::EMMDetachRequestMO(_))
| NASMessage::EMMMessage(EMMMessage::EMMDetachRequestMT(_))
| NASMessage::EMMMessage(EMMMessage::EMMTrackingAreaUpdateReject(_)) => {
self.transition(State::Disconnect);
}
_ => {}
},
LteInformationElement::UlCcch(rrc_payload) => match rrc_payload.message {
UL_CCCH_MessageType::C1(UL_CCCH_MessageType_c1::RrcConnectionRequest(_))
| UL_CCCH_MessageType::C1(
UL_CCCH_MessageType_c1::RrcConnectionReestablishmentRequest(_),
) => {
self.transition(State::AttachRequest);
}
_ => {}
},
LteInformationElement::DlDcch(rrc_payload) => {
if let DL_DCCH_MessageType::C1(DL_DCCH_MessageType_c1::RrcConnectionRelease(
_,
)) = rrc_payload.message
{
self.transition(State::Disconnect)
}
}
_ => {}
}
};
if let NASMessage::EMMMessage(EMMMessage::EMMIdentityRequest(req)) = payload {
if req.id_type.inner == IDTypeV::IMSI {
if self.packet_num < PACKET_THRESHHOLD {
return Some(Event {
event_type: EventType::QualitativeWarning {
severity: Severity::Medium,
},
message: format!(
"NAS IMSI identity request detected, however it was within \
the first {PACKET_THRESHHOLD} packets of this analysis. If you just \
turned your device on, this is likely a \
false-positive."
),
});
} else {
return Some(Event {
event_type: EventType::QualitativeWarning {
severity: Severity::High,
},
message: format!(
"NAS IMSI identity request detected (packet {})",
self.packet_num
),
});
}
if self.state == State::IdentityRequest {
self.timeout_counter += 1;
debug!(
"timeout: counter {}, packet: {}",
self.timeout_counter, self.packet_num
);
if self.timeout_counter >= TIMEOUT_THRESHHOLD {
self.flag = Some(Event {
event_type: EventType::Informational {},
message: format!(
"Identity request happened without auth request followup (frame {})",
self.packet_num
)
.to_string(),
});
self.timeout_counter = 0;
}
}
None
self.flag.take()
}
}