mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-07-28 09:48:14 -07:00
imsi requested version 2; remove most false positives
This commit is contained in:
committed by
Will Greenberg
parent
578bc0d234
commit
e07b0b05e7
@@ -2,15 +2,31 @@ use std::borrow::Cow;
|
|||||||
|
|
||||||
use pycrate_rs::nas::NASMessage;
|
use pycrate_rs::nas::NASMessage;
|
||||||
use pycrate_rs::nas::emm::EMMMessage;
|
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::analyzer::{Analyzer, Event, EventType, Severity};
|
||||||
use super::information_element::{InformationElement, LteInformationElement};
|
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 {
|
pub struct ImsiRequestedAnalyzer {
|
||||||
packet_num: usize,
|
packet_num: usize,
|
||||||
|
state: State,
|
||||||
|
timeout_counter: usize,
|
||||||
|
flag: Option<Event>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ImsiRequestedAnalyzer {
|
impl Default for ImsiRequestedAnalyzer {
|
||||||
@@ -21,60 +37,148 @@ impl Default for ImsiRequestedAnalyzer {
|
|||||||
|
|
||||||
impl ImsiRequestedAnalyzer {
|
impl ImsiRequestedAnalyzer {
|
||||||
pub fn new() -> Self {
|
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 {
|
impl Analyzer for ImsiRequestedAnalyzer {
|
||||||
fn get_name(&self) -> Cow<str> {
|
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> {
|
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 {
|
fn get_version(&self) -> u32 {
|
||||||
1
|
2
|
||||||
}
|
}
|
||||||
|
|
||||||
fn analyze_information_element(&mut self, ie: &InformationElement) -> Option<Event> {
|
fn analyze_information_element(&mut self, ie: &InformationElement) -> Option<Event> {
|
||||||
self.packet_num += 1;
|
self.packet_num += 1;
|
||||||
let payload = match ie {
|
|
||||||
InformationElement::LTE(inner) => match &**inner {
|
if let InformationElement::LTE(inner) = ie {
|
||||||
LteInformationElement::NAS(payload) => payload,
|
match &**inner {
|
||||||
_ => return None,
|
LteInformationElement::NAS(payload) => match payload {
|
||||||
},
|
NASMessage::EMMMessage(EMMMessage::EMMExtServiceRequest(_))
|
||||||
_ => return None,
|
| 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 self.state == State::IdentityRequest {
|
||||||
if req.id_type.inner == IDTypeV::IMSI {
|
self.timeout_counter += 1;
|
||||||
if self.packet_num < PACKET_THRESHHOLD {
|
debug!(
|
||||||
return Some(Event {
|
"timeout: counter {}, packet: {}",
|
||||||
event_type: EventType::QualitativeWarning {
|
self.timeout_counter, self.packet_num
|
||||||
severity: Severity::Medium,
|
);
|
||||||
},
|
if self.timeout_counter >= TIMEOUT_THRESHHOLD {
|
||||||
message: format!(
|
self.flag = Some(Event {
|
||||||
"NAS IMSI identity request detected, however it was within \
|
event_type: EventType::Informational {},
|
||||||
the first {PACKET_THRESHHOLD} packets of this analysis. If you just \
|
message: format!(
|
||||||
turned your device on, this is likely a \
|
"Identity request happened without auth request followup (frame {})",
|
||||||
false-positive."
|
self.packet_num
|
||||||
),
|
)
|
||||||
});
|
.to_string(),
|
||||||
} else {
|
});
|
||||||
return Some(Event {
|
self.timeout_counter = 0;
|
||||||
event_type: EventType::QualitativeWarning {
|
|
||||||
severity: Severity::High,
|
|
||||||
},
|
|
||||||
message: format!(
|
|
||||||
"NAS IMSI identity request detected (packet {})",
|
|
||||||
self.packet_num
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
|
||||||
|
self.flag.take()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user