mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-04-26 23:49:59 -07:00
Minimal version of the LTE downgrade analyzer
This also renames the lte_parser crate to telcom_parser, since it'll handle any 2G or 3G parsing going forward.
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
use crate::lte_rrc::Message as LteRrcMessage;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use super::information_element::InformationElement;
|
||||
|
||||
/// Qualitative measure of how severe a Warning event type is.
|
||||
/// The levels should break down like this:
|
||||
@@ -11,14 +13,14 @@ pub enum Severity {
|
||||
High,
|
||||
}
|
||||
|
||||
/// `QualitativeWarning` events will always be shown to the user in some manner,
|
||||
/// [QualitativeWarning] events will always be shown to the user in some manner,
|
||||
/// while `Informational` ones may be hidden based on user settings.
|
||||
pub enum EventType {
|
||||
Informational,
|
||||
QualitativeWarning(Severity),
|
||||
}
|
||||
|
||||
/// Events are user-facing signals that can be emitted by an `Analyzer` upon a
|
||||
/// Events are user-facing signals that can be emitted by an [Analyzer] upon a
|
||||
/// message being received. They can be used to signifiy an IC detection
|
||||
/// warning, or just to display some relevant information to the user.
|
||||
pub struct Event {
|
||||
@@ -26,24 +28,26 @@ pub struct Event {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
/// An `Analyzer` represents one type of heuristic for detecting an IMSI Catcher
|
||||
/// An [Analyzer] represents one type of heuristic for detecting an IMSI Catcher
|
||||
/// (IC). While maintaining some amount of state is useful, be mindful of how
|
||||
/// much memory your `Analyzer` uses at runtime, since rayhunter may run for
|
||||
/// many hours at a time with dozens of `Analyzer`s working in parallel.
|
||||
trait Analyzer {
|
||||
/// much memory your [Analyzer] uses at runtime, since rayhunter may run for
|
||||
/// many hours at a time with dozens of [Analyzers](Analyzer) working in parallel.
|
||||
pub trait Analyzer {
|
||||
/// Returns a user-friendly, concise name for your heuristic.
|
||||
fn get_name(&self) -> String;
|
||||
fn get_name(&self) -> Cow<str>;
|
||||
|
||||
/// Returns a user-friendly description of what your heuristic looks for,
|
||||
/// the types of `Event`s it may return, as well as possible false-positive
|
||||
/// conditions that may trigger an `Event`. If different `Event`s have
|
||||
/// the types of [Events](Event) it may return, as well as possible false-positive
|
||||
/// conditions that may trigger an [Event]. If different [Events](Event) have
|
||||
/// different false-positive conditions, consider including them in its
|
||||
/// `message` field.
|
||||
fn get_description(&self) -> String;
|
||||
fn get_description(&self) -> Cow<str>;
|
||||
|
||||
/// Processes a single `LteRrcMessage`, possibly returning an `Event` if your
|
||||
/// Analyze a single [InformationElement], possibly returning an [Event] if your
|
||||
/// heuristic deems it relevant. Again, be mindful of any state your
|
||||
/// `Analyzer` updates per message, since it may be run over hundreds or
|
||||
/// thousands of them alongside many other `Analyzer`s.
|
||||
fn process_lte_rrc_message(&mut self, message: &LteRrcMessage) -> Option<Event>;
|
||||
/// [Analyzer] updates per message, since it may be run over hundreds or
|
||||
/// thousands of them alongside many other [Analyzers](Analyzer).
|
||||
fn analyze_information_element(&mut self, ie: &InformationElement) -> Option<Event> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
82
lib/src/analysis/information_element.rs
Normal file
82
lib/src/analysis/information_element.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
//! The term "information element" is used by 3GPP to describe "structural
|
||||
//! elements containing single or multiple fields" in 2G/3G/4G/5G. We use
|
||||
//! the term to refer to a structured, fully parsed message in any telcom
|
||||
//! standard.
|
||||
|
||||
use telcom_parser::{decode, lte_rrc};
|
||||
use thiserror::Error;
|
||||
use crate::gsmtap::{GsmtapType, LteRrcSubtype, GsmtapMessage};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum InformationElementError {
|
||||
#[error("Failed decoding")]
|
||||
DecodingError(#[from] telcom_parser::ParsingError),
|
||||
#[error("Unsupported LTE RRC subtype {0:?}")]
|
||||
UnsupportedGsmtapType(GsmtapType),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum InformationElement {
|
||||
GSM,
|
||||
UMTS,
|
||||
LTE(LteInformationElement),
|
||||
FiveG,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum LteInformationElement {
|
||||
DlCcch(lte_rrc::DL_CCCH_Message),
|
||||
DlDcch(lte_rrc::DL_DCCH_Message),
|
||||
UlCcch(lte_rrc::UL_CCCH_Message),
|
||||
UlDcch(lte_rrc::UL_DCCH_Message),
|
||||
BcchBch(lte_rrc::BCCH_BCH_Message),
|
||||
BcchDlSch(lte_rrc::BCCH_DL_SCH_Message),
|
||||
PCCH(lte_rrc::PCCH_Message),
|
||||
MCCH(lte_rrc::MCCH_Message),
|
||||
ScMcch(lte_rrc::SC_MCCH_Message_r13),
|
||||
BcchBchMbms(lte_rrc::BCCH_BCH_Message_MBMS),
|
||||
BcchDlSchBr(lte_rrc::BCCH_DL_SCH_Message_BR),
|
||||
BcchDlSchMbms(lte_rrc::BCCH_DL_SCH_Message_MBMS),
|
||||
SbcchSlBch(lte_rrc::SBCCH_SL_BCH_Message),
|
||||
SbcchSlBchV2x(lte_rrc::SBCCH_SL_BCH_Message_V2X_r14),
|
||||
|
||||
// FIXME: unclear which message these "NB" types map to
|
||||
//DlCcchNb(),
|
||||
//DlDcchNb(),
|
||||
//UlCcchNb(),
|
||||
//UlDcchNb(),
|
||||
//BcchBchNb(),
|
||||
//BcchBchTddNb(),
|
||||
//BcchDlSchNb(),
|
||||
//PcchNb(),
|
||||
//ScMcchNb(),
|
||||
}
|
||||
|
||||
impl TryFrom<&GsmtapMessage> for LteInformationElement {
|
||||
type Error = InformationElementError;
|
||||
|
||||
fn try_from(gsmtap_msg: &GsmtapMessage) -> Result<Self, Self::Error> {
|
||||
if let GsmtapType::LteRrc(lte_rrc_subtype) = gsmtap_msg.header.gsmtap_type {
|
||||
use LteRrcSubtype as L;
|
||||
use LteInformationElement as R;
|
||||
return match lte_rrc_subtype {
|
||||
L::DlCcch => Ok(R::DlCcch(decode(&gsmtap_msg.payload)?)),
|
||||
L::DlDcch => Ok(R::DlDcch(decode(&gsmtap_msg.payload)?)),
|
||||
L::UlCcch => Ok(R::UlCcch(decode(&gsmtap_msg.payload)?)),
|
||||
L::UlDcch => Ok(R::UlDcch(decode(&gsmtap_msg.payload)?)),
|
||||
L::BcchBch => Ok(R::BcchBch(decode(&gsmtap_msg.payload)?)),
|
||||
L::BcchDlSch => Ok(R::BcchDlSch(decode(&gsmtap_msg.payload)?)),
|
||||
L::PCCH => Ok(R::PCCH(decode(&gsmtap_msg.payload)?)),
|
||||
L::MCCH => Ok(R::MCCH(decode(&gsmtap_msg.payload)?)),
|
||||
L::ScMcch => Ok(R::ScMcch(decode(&gsmtap_msg.payload)?)),
|
||||
L::BcchBchMbms => Ok(R::BcchBchMbms(decode(&gsmtap_msg.payload)?)),
|
||||
L::BcchDlSchBr => Ok(R::BcchDlSchBr(decode(&gsmtap_msg.payload)?)),
|
||||
L::BcchDlSchMbms => Ok(R::BcchDlSchMbms(decode(&gsmtap_msg.payload)?)),
|
||||
L::SbcchSlBch => Ok(R::SbcchSlBch(decode(&gsmtap_msg.payload)?)),
|
||||
L::SbcchSlBchV2x => Ok(R::SbcchSlBchV2x(decode(&gsmtap_msg.payload)?)),
|
||||
subtype => Err(InformationElementError::UnsupportedGsmtapType(gsmtap_msg.header.gsmtap_type)),
|
||||
};
|
||||
}
|
||||
Err(InformationElementError::UnsupportedGsmtapType(gsmtap_msg.header.gsmtap_type))
|
||||
}
|
||||
}
|
||||
83
lib/src/analysis/lte_downgrade.rs
Normal file
83
lib/src/analysis/lte_downgrade.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use super::analyzer::{Analyzer, Event, EventType, Severity};
|
||||
use super::information_element::{InformationElement, LteInformationElement};
|
||||
use telcom_parser::lte_rrc::{BCCH_DL_SCH_MessageType, BCCH_DL_SCH_MessageType_c1, CellReselectionPriority, SystemInformationBlockType7, SystemInformationCriticalExtensions, SystemInformation_r8_IEsSib_TypeAndInfo, SystemInformation_r8_IEsSib_TypeAndInfo_Entry};
|
||||
|
||||
/// Based on heuristic T7 from Shinjo Park's "Why We Cannot Win".
|
||||
pub struct LteSib7DowngradeAnalyzer {
|
||||
}
|
||||
|
||||
impl LteSib7DowngradeAnalyzer {
|
||||
fn unpack_system_information<'a>(&self, ie: &'a InformationElement) -> Option<&'a SystemInformation_r8_IEsSib_TypeAndInfo> {
|
||||
if let InformationElement::LTE(message) = ie {
|
||||
if let LteInformationElement::BcchDlSch(bcch_dl_sch_message) = message {
|
||||
if let BCCH_DL_SCH_MessageType::C1(BCCH_DL_SCH_MessageType_c1::SystemInformation(system_information)) = &bcch_dl_sch_message.message {
|
||||
if let SystemInformationCriticalExtensions::SystemInformation_r8(sib) = &system_information.critical_extensions {
|
||||
return Some(&sib.sib_type_and_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: keep track of SIB state to compare LTE reselection blocks w/ 2g/3g ones
|
||||
impl Analyzer for LteSib7DowngradeAnalyzer {
|
||||
fn get_name(&self) -> Cow<str> {
|
||||
Cow::from("LTE SIB 7 Downgrade")
|
||||
}
|
||||
|
||||
fn get_description(&self) -> Cow<str> {
|
||||
Cow::from("Tests for LTE cells broadcasting a SIB type 7 which include 2G/3G frequencies with higher priorities.")
|
||||
}
|
||||
|
||||
fn analyze_information_element(&mut self, ie: &InformationElement) -> Option<super::analyzer::Event> {
|
||||
let sibs = &self.unpack_system_information(ie)?.0;
|
||||
for sib in sibs {
|
||||
match sib {
|
||||
SystemInformation_r8_IEsSib_TypeAndInfo_Entry::Sib6(sib6) => {
|
||||
if let Some(carrier_info_list) = sib6.carrier_freq_list_utra_fdd.as_ref() {
|
||||
for carrier_info in &carrier_info_list.0 {
|
||||
if let Some(CellReselectionPriority(p)) = carrier_info.cell_reselection_priority {
|
||||
if p == 0 {
|
||||
return Some(Event {
|
||||
event_type: EventType::QualitativeWarning(Severity::High),
|
||||
message: "LTE cell advertised a 3G cell for priority 0 reselection".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(carrier_info_list) = sib6.carrier_freq_list_utra_tdd.as_ref() {
|
||||
for carrier_info in &carrier_info_list.0 {
|
||||
if let Some(CellReselectionPriority(p)) = carrier_info.cell_reselection_priority {
|
||||
if p == 0 {
|
||||
return Some(Event {
|
||||
event_type: EventType::QualitativeWarning(Severity::High),
|
||||
message: "LTE cell advertised a 3G cell for priority 0 reselection".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
SystemInformation_r8_IEsSib_TypeAndInfo_Entry::Sib7(SystemInformationBlockType7 { carrier_freqs_info_list: Some(carrier_info_list), .. }) => {
|
||||
for carrier_info in &carrier_info_list.0 {
|
||||
if let Some(CellReselectionPriority(p)) = carrier_info.common_info.cell_reselection_priority {
|
||||
if p == 0 {
|
||||
return Some(Event {
|
||||
event_type: EventType::QualitativeWarning(Severity::High),
|
||||
message: "LTE cell advertised a 2G cell for priority 0 reselection".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -1 +1,3 @@
|
||||
pub mod analyzer;
|
||||
pub mod information_element;
|
||||
pub mod lte_downgrade;
|
||||
|
||||
Reference in New Issue
Block a user