diff --git a/daemon/web/src/lib/components/ConfigForm.svelte b/daemon/web/src/lib/components/ConfigForm.svelte
index 7187cc8..3b7b316 100644
--- a/daemon/web/src/lib/components/ConfigForm.svelte
+++ b/daemon/web/src/lib/components/ConfigForm.svelte
@@ -335,6 +335,17 @@
Test Heuristic (noisy!)
+
+
+
+
diff --git a/daemon/web/src/lib/utils.svelte.ts b/daemon/web/src/lib/utils.svelte.ts
index 915c7c0..13df13a 100644
--- a/daemon/web/src/lib/utils.svelte.ts
+++ b/daemon/web/src/lib/utils.svelte.ts
@@ -10,6 +10,7 @@ export interface AnalyzerConfig {
nas_null_cipher: boolean;
incomplete_sib: boolean;
test_analyzer: boolean;
+ imsi_attach_analyzer: boolean;
}
export enum enabled_notifications {
diff --git a/lib/src/analysis/analyzer.rs b/lib/src/analysis/analyzer.rs
index aa31625..af42640 100644
--- a/lib/src/analysis/analyzer.rs
+++ b/lib/src/analysis/analyzer.rs
@@ -4,6 +4,7 @@ use pcap_file_tokio::pcapng::blocks::enhanced_packet::EnhancedPacketBlock;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
+use crate::analysis::imsi_attach::ImsiAttachAnalyzer;
use crate::gsmtap::{GsmtapHeader, GsmtapMessage, GsmtapType};
use crate::util::RuntimeMetadata;
use crate::{diag::MessagesContainer, gsmtap_parser};
@@ -19,13 +20,14 @@ use super::{
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default)]
pub struct AnalyzerConfig {
- pub imsi_requested: bool,
+ pub imsi_attach: bool,
pub connection_redirect_2g_downgrade: bool,
pub lte_sib6_and_7_downgrade: bool,
pub null_cipher: bool,
pub nas_null_cipher: bool,
pub incomplete_sib: bool,
pub test_analyzer: bool,
+ pub imsi_requested: bool,
}
impl Default for AnalyzerConfig {
@@ -38,6 +40,7 @@ impl Default for AnalyzerConfig {
nas_null_cipher: true,
incomplete_sib: true,
test_analyzer: false,
+ imsi_attach: true,
}
}
}
@@ -346,6 +349,10 @@ impl Harness {
harness.add_analyzer(Box::new(TestAnalyzer {}))
}
+ if analyzer_config.imsi_attach {
+ harness.add_analyzer(Box::new(ImsiAttachAnalyzer {}));
+ }
+
harness
}
diff --git a/lib/src/analysis/imsi_attach.rs b/lib/src/analysis/imsi_attach.rs
new file mode 100644
index 0000000..3dc4b22
--- /dev/null
+++ b/lib/src/analysis/imsi_attach.rs
@@ -0,0 +1,131 @@
+use crate::analysis::analyzer::{Analyzer, Event, EventType};
+use crate::analysis::information_element::{InformationElement, LteInformationElement};
+use pycrate_rs::nas::NASMessage;
+use pycrate_rs::nas::emm::EMMMessage;
+use pycrate_rs::nas::generated::emm::emm_attach_reject::EMMCauseEMMCause as AttachRejectEMMCause;
+use pycrate_rs::nas::generated::emm::emm_detach_request_mt::EPSDetachTypeMTType;
+use pycrate_rs::nas::generated::emm::emm_identity_request::IDTypeV;
+use pycrate_rs::nas::generated::emm::emm_service_reject::EMMCauseEMMCause as ServiceRejectEMMCause;
+use pycrate_rs::nas::generated::emm::emm_tracking_area_update_reject::EMMCauseEMMCause as TAURejectEMMCause;
+use std::borrow::Cow;
+
+pub struct ImsiAttachAnalyzer;
+
+impl ImsiAttachAnalyzer {
+ pub fn new() -> Self {
+ ImsiAttachAnalyzer
+ }
+
+ fn is_imsi_exposing_nas(&self, nas_msg: &NASMessage) -> bool {
+ match nas_msg {
+ NASMessage::EMMMessage(emm_msg) => match emm_msg {
+ EMMMessage::EMMIdentityRequest(req) => req.id_type.inner == IDTypeV::IMSI,
+
+ EMMMessage::EMMTrackingAreaUpdateReject(reject) => {
+ matches!(
+ reject.emm_cause.inner,
+ TAURejectEMMCause::IllegalUE
+ | TAURejectEMMCause::IllegalME
+ | TAURejectEMMCause::EPSServicesNotAllowed
+ | TAURejectEMMCause::EPSServicesAndNonEPSServicesNotAllowed
+ | TAURejectEMMCause::TrackingAreaNotAllowed
+ | TAURejectEMMCause::EPSServicesNotAllowedInThisPLMN
+ | TAURejectEMMCause::RequestedServiceOptionNotAuthorizedInThisPLMN
+ )
+ }
+
+ EMMMessage::EMMAttachReject(reject) => {
+ matches!(
+ reject.emm_cause.inner,
+ AttachRejectEMMCause::IllegalUE
+ | AttachRejectEMMCause::IllegalME
+ | AttachRejectEMMCause::EPSServicesNotAllowed
+ | AttachRejectEMMCause::EPSServicesAndNonEPSServicesNotAllowed
+ | AttachRejectEMMCause::PLMNNotAllowed
+ | AttachRejectEMMCause::TrackingAreaNotAllowed
+ | AttachRejectEMMCause::RoamingNotAllowedInThisTrackingArea
+ | AttachRejectEMMCause::EPSServicesNotAllowedInThisPLMN
+ | AttachRejectEMMCause::NoSuitableCellsInTrackingArea
+ | AttachRejectEMMCause::RequestedServiceOptionNotAuthorizedInThisPLMN
+ )
+ }
+
+ EMMMessage::EMMDetachRequestMT(req) => {
+ // Original implementation: !(nas_eps.emm.detach_type_dl == 3)
+ req.eps_detach_type.inner.typ != EPSDetachTypeMTType::IMSIDetach
+ }
+
+ EMMMessage::EMMServiceReject(reject) => {
+ matches!(
+ reject.emm_cause.inner,
+ ServiceRejectEMMCause::IllegalUE
+ | ServiceRejectEMMCause::IllegalME
+ | ServiceRejectEMMCause::EPSServicesNotAllowed
+ | ServiceRejectEMMCause::UEIdentityCannotBeDerivedByTheNetwork
+ | ServiceRejectEMMCause::TrackingAreaNotAllowed
+ | ServiceRejectEMMCause::EPSServicesNotAllowedInThisPLMN
+ | ServiceRejectEMMCause::RequestedServiceOptionNotAuthorizedInThisPLMN
+ )
+ }
+
+ _ => false,
+ },
+ _ => false,
+ }
+ }
+}
+
+impl Analyzer for ImsiAttachAnalyzer {
+ fn get_name(&self) -> Cow<'_, str> {
+ "IMSI-Exposed Message Detector".into()
+ }
+
+ fn get_description(&self) -> Cow<'_, str> {
+ "Catches any and all messages that may expose IMSI. Can be quite noisy. \
+ Based on the detection logic from the Marlin paper (\"They Know Where You Are: Tracking Mobile \
+ Devices Using Cellular Infrastructure\"). Since we don't have traffic of many devices, we \
+ cannot implement the original exposure ratio calculation, and naively trigger an event on \
+ every exposure.".into()
+ }
+
+ fn get_version(&self) -> u32 {
+ 1
+ }
+
+ fn analyze_information_element(&mut self, ie: &InformationElement, _packet_num: usize) -> Option {
+ let lte_ie = match ie {
+ InformationElement::LTE(inner) => inner,
+ _ => return None,
+ };
+
+ match lte_ie.as_ref() {
+ LteInformationElement::NAS(nas_msg) => {
+ if self.is_imsi_exposing_nas(nas_msg) {
+ let message_type = match nas_msg {
+ NASMessage::EMMMessage(emm_msg) => match emm_msg {
+ EMMMessage::EMMIdentityRequest(_) => "EMM Identity Request (IMSI)",
+ EMMMessage::EMMTrackingAreaUpdateReject(_) => {
+ "EMM Tracking Area Update Reject"
+ }
+ EMMMessage::EMMAttachReject(_) => "EMM Attach Reject",
+ EMMMessage::EMMDetachRequestMT(_) => "EMM Detach Request (MT)",
+ EMMMessage::EMMServiceReject(_) => "EMM Service Reject",
+ _ => "Unknown EMM Message",
+ },
+ _ => "Unknown NAS Message",
+ };
+
+ Some(Event {
+ event_type: EventType::Informational,
+ message: format!(
+ "IMSI-exposing NAS message detected: {message_type}."
+ ),
+ })
+ } else {
+ None
+ }
+ }
+ _ => None,
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/src/analysis/mod.rs b/lib/src/analysis/mod.rs
index d6adf39..27e839d 100644
--- a/lib/src/analysis/mod.rs
+++ b/lib/src/analysis/mod.rs
@@ -1,5 +1,6 @@
pub mod analyzer;
pub mod connection_redirect_downgrade;
+pub mod imsi_attach;
pub mod imsi_requested;
pub mod incomplete_sib;
pub mod information_element;