diff --git a/daemon/web/src/lib/utils.svelte.ts b/daemon/web/src/lib/utils.svelte.ts
index 0abb6a4..45727d6 100644
--- a/daemon/web/src/lib/utils.svelte.ts
+++ b/daemon/web/src/lib/utils.svelte.ts
@@ -7,6 +7,7 @@ export interface AnalyzerConfig {
lte_sib6_and_7_downgrade: boolean;
null_cipher: boolean;
nas_null_cipher: boolean;
+ incomplete_sib: boolean;
}
export interface Config {
diff --git a/dist/config.toml.in b/dist/config.toml.in
index d55344c..79d5b1d 100644
--- a/dist/config.toml.in
+++ b/dist/config.toml.in
@@ -31,3 +31,4 @@ connection_redirect_2g_downgrade = true
lte_sib6_and_7_downgrade = true
null_cipher = true
nas_null_cipher = true
+incomplete_sib = true
diff --git a/doc/heuristics.md b/doc/heuristics.md
index 3a4193e..f982811 100644
--- a/doc/heuristics.md
+++ b/doc/heuristics.md
@@ -21,3 +21,4 @@ Rayhunter includes several analyzers to detect potential IMSI catcher activity.
which include 2G/3G frequencies with higher priorities
- **Null Cipher**: Tests whether the cell suggests using a null cipher (EEA0) in the RRC layer.
- **NAS Null Cipher**: Tests whether the security mode command at the NAS layer suggests using a null cipher (EEA0). This would usually only happen after a UE has successfully authenticated with the MME but still it shouldn't happen at all, this could be indicative of an attack though using SS7 to get key material from the HLR of the UE for a succesful authentication. It could also indicate an IMSI catcher which is connected to the mobile network MME and HLR through cooperation between government and telco. Or it could be a false positive if the telco is intending to use null ciphers (if encryption is illegal or something.)
+- **Incomplete SIB**: Tests whether the Sib1 message contains a complete SIB chain (sib3, sib5, etc.) A legitimate SIB1 should contain timing information for at least 2 additional sibs (sib3, 4, and 5 being the most common) but a fake base station will often not bother to send additional SIBs beyond 1 and 2. On its own this might just be a misconfigured base station (though we have only seen it in the wild under suspicious circumstances) but combined with other heuristics such as **ISMI Requested** detection it should be considered a strong indicator of malicious activity.
\ No newline at end of file
diff --git a/lib/src/analysis/analyzer.rs b/lib/src/analysis/analyzer.rs
index e436cfc..e1c3794 100644
--- a/lib/src/analysis/analyzer.rs
+++ b/lib/src/analysis/analyzer.rs
@@ -9,9 +9,9 @@ use crate::{diag::MessagesContainer, gsmtap_parser};
use super::{
connection_redirect_downgrade::ConnectionRedirect2GDowngradeAnalyzer,
- imsi_requested::ImsiRequestedAnalyzer, information_element::InformationElement,
- nas_null_cipher::NasNullCipherAnalyzer, null_cipher::NullCipherAnalyzer,
- priority_2g_downgrade::LteSib6And7DowngradeAnalyzer,
+ imsi_requested::ImsiRequestedAnalyzer, incomplete_sib::IncompleteSibAnalyzer,
+ information_element::InformationElement, nas_null_cipher::NasNullCipherAnalyzer,
+ null_cipher::NullCipherAnalyzer, priority_2g_downgrade::LteSib6And7DowngradeAnalyzer,
};
#[derive(Debug, Clone, Deserialize, Serialize)]
@@ -22,6 +22,7 @@ pub struct AnalyzerConfig {
pub lte_sib6_and_7_downgrade: bool,
pub null_cipher: bool,
pub nas_null_cipher: bool,
+ pub incomplete_sib: bool,
}
impl Default for AnalyzerConfig {
@@ -32,6 +33,7 @@ impl Default for AnalyzerConfig {
lte_sib6_and_7_downgrade: true,
null_cipher: true,
nas_null_cipher: true,
+ incomplete_sib: true,
}
}
}
@@ -169,6 +171,10 @@ impl Harness {
harness.add_analyzer(Box::new(NasNullCipherAnalyzer::new()))
}
+ if analyzer_config.incomplete_sib {
+ harness.add_analyzer(Box::new(IncompleteSibAnalyzer::new()))
+ }
+
harness
}
diff --git a/lib/src/analysis/incomplete_sib.rs b/lib/src/analysis/incomplete_sib.rs
new file mode 100644
index 0000000..9637b0c
--- /dev/null
+++ b/lib/src/analysis/incomplete_sib.rs
@@ -0,0 +1,68 @@
+use std::borrow::Cow;
+
+use telcom_parser::lte_rrc::{BCCH_DL_SCH_MessageType, BCCH_DL_SCH_MessageType_c1};
+
+use super::analyzer::{Analyzer, Event, EventType, Severity};
+use super::information_element::{InformationElement, LteInformationElement};
+
+pub struct IncompleteSibAnalyzer {
+ packet_num: usize,
+}
+
+impl Default for IncompleteSibAnalyzer {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl IncompleteSibAnalyzer {
+ pub fn new() -> Self {
+ Self { packet_num: 0 }
+ }
+}
+
+impl Analyzer for IncompleteSibAnalyzer {
+ fn get_name(&self) -> Cow {
+ Cow::from("Incomplete SIB")
+ }
+
+ fn get_description(&self) -> Cow {
+ Cow::from("Tests whether a SIB1 message contains a full chain of followup sibs")
+ }
+
+ fn get_version(&self) -> u32 {
+ 1
+ }
+
+ fn analyze_information_element(&mut self, ie: &InformationElement) -> Option {
+ self.packet_num += 1;
+
+ let sch_msg = match ie {
+ InformationElement::LTE(lte_ie) => match &**lte_ie {
+ LteInformationElement::BcchDlSch(sch_msg) => sch_msg,
+ _ => return None,
+ },
+ _ => return None,
+ };
+ let BCCH_DL_SCH_MessageType::C1(BCCH_DL_SCH_MessageType_c1::SystemInformationBlockType1(
+ sib1,
+ )) = &sch_msg.message
+ else {
+ return None;
+ };
+
+ if sib1.scheduling_info_list.0.len() < 2 {
+ return Some(Event {
+ event_type: EventType::QualitativeWarning {
+ severity: Severity::Medium,
+ },
+ message: format!(
+ "SIB1 scheduling info list was malformed (packet {})",
+ self.packet_num
+ )
+ .to_string(),
+ });
+ }
+ None
+ }
+}
diff --git a/lib/src/analysis/mod.rs b/lib/src/analysis/mod.rs
index f8154ed..a8b7b7a 100644
--- a/lib/src/analysis/mod.rs
+++ b/lib/src/analysis/mod.rs
@@ -1,6 +1,7 @@
pub mod analyzer;
pub mod connection_redirect_downgrade;
pub mod imsi_requested;
+pub mod incomplete_sib;
pub mod information_element;
pub mod nas_null_cipher;
pub mod null_cipher;