From e99395c16f3001e1690fd32e22e774d16de8892f Mon Sep 17 00:00:00 2001 From: Will Greenberg Date: Thu, 8 Feb 2024 15:38:36 -0800 Subject: [PATCH] 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. --- Cargo.lock | 28 +++---- Cargo.toml | 2 +- lib/Cargo.toml | 2 +- lib/src/analysis/analyzer.rs | 34 ++++---- .../information_element.rs} | 37 ++++++--- lib/src/analysis/lte_downgrade.rs | 83 +++++++++++++++++++ lib/src/analysis/mod.rs | 2 + lib/src/lib.rs | 1 - lib/src/log_codes.rs | 2 +- {lte-parser => telcom-parser}/Cargo.toml | 2 +- {lte-parser => telcom-parser}/README.md | 9 +- .../specs/EUTRA-InterNodeDefinitions.asn | 0 .../specs/EUTRA-RRC-Definitions.asn | 0 .../specs/EUTRA-Sidelink-Preconf.asn | 0 .../specs/EUTRA-UE-Variables.asn | 0 .../specs/PC5-RRC-Definitions.asn | 0 {lte-parser => telcom-parser}/src/lib.rs | 0 {lte-parser => telcom-parser}/src/lte_rrc.rs | 0 .../tests/lte_rrc_test.rs | 0 19 files changed, 150 insertions(+), 52 deletions(-) rename lib/src/{lte_rrc.rs => analysis/information_element.rs} (70%) create mode 100644 lib/src/analysis/lte_downgrade.rs rename {lte-parser => telcom-parser}/Cargo.toml (94%) rename {lte-parser => telcom-parser}/README.md (82%) rename {lte-parser => telcom-parser}/specs/EUTRA-InterNodeDefinitions.asn (100%) rename {lte-parser => telcom-parser}/specs/EUTRA-RRC-Definitions.asn (100%) rename {lte-parser => telcom-parser}/specs/EUTRA-Sidelink-Preconf.asn (100%) rename {lte-parser => telcom-parser}/specs/EUTRA-UE-Variables.asn (100%) rename {lte-parser => telcom-parser}/specs/PC5-RRC-Definitions.asn (100%) rename {lte-parser => telcom-parser}/src/lib.rs (100%) rename {lte-parser => telcom-parser}/src/lte_rrc.rs (100%) rename {lte-parser => telcom-parser}/tests/lte_rrc_test.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 3ea3490..af42c7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -829,19 +829,6 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" -[[package]] -name = "lte-parser" -version = "0.1.0" -dependencies = [ - "asn1-codecs", - "asn1-compiler", - "asn1_codecs_derive", - "bitvec", - "log", - "serde", - "thiserror", -] - [[package]] name = "matchit" version = "0.7.3" @@ -1075,8 +1062,8 @@ dependencies = [ "env_logger", "libc", "log", - "lte-parser", "pcap-file", + "telcom-parser", "thiserror", ] @@ -1357,6 +1344,19 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "telcom-parser" +version = "0.1.0" +dependencies = [ + "asn1-codecs", + "asn1-compiler", + "asn1_codecs_derive", + "bitvec", + "log", + "serde", + "thiserror", +] + [[package]] name = "tempdir" version = "0.3.7" diff --git a/Cargo.toml b/Cargo.toml index 268ffcc..f078283 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,6 @@ members = [ "bin", "serial", "rootshell", - "lte-parser", + "telcom-parser", ] resolver = "2" diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 4ba867d..94a30ee 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -14,4 +14,4 @@ libc = "0.2.150" log = "0.4.20" pcap-file = "2.0.0" thiserror = "1.0.50" -lte-parser = { path = "../lte-parser" } +telcom-parser = { path = "../telcom-parser" } diff --git a/lib/src/analysis/analyzer.rs b/lib/src/analysis/analyzer.rs index 812ef7e..7291996 100644 --- a/lib/src/analysis/analyzer.rs +++ b/lib/src/analysis/analyzer.rs @@ -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; /// 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; - /// 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; + /// [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 { + None + } } diff --git a/lib/src/lte_rrc.rs b/lib/src/analysis/information_element.rs similarity index 70% rename from lib/src/lte_rrc.rs rename to lib/src/analysis/information_element.rs index e40314d..d9a0494 100644 --- a/lib/src/lte_rrc.rs +++ b/lib/src/analysis/information_element.rs @@ -1,19 +1,30 @@ -use lte_parser::{decode, lte_rrc}; +//! 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 super::gsmtap::{GsmtapType, LteRrcSubtype, GsmtapMessage}; +use crate::gsmtap::{GsmtapType, LteRrcSubtype, GsmtapMessage}; #[derive(Error, Debug)] -pub enum MessageParsingError { +pub enum InformationElementError { #[error("Failed decoding")] - DecodingError(#[from] lte_parser::ParsingError), - #[error("Unknown Gsmtap message type {0:?}")] - UnknownGsmtapType(GsmtapType), + DecodingError(#[from] telcom_parser::ParsingError), #[error("Unsupported LTE RRC subtype {0:?}")] - UnsupportedLteRrcSubtype(LteRrcSubtype), + UnsupportedGsmtapType(GsmtapType), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum InformationElement { + GSM, + UMTS, + LTE(LteInformationElement), + FiveG, } #[derive(Debug, Clone, PartialEq)] -pub enum Message { +pub enum LteInformationElement { DlCcch(lte_rrc::DL_CCCH_Message), DlDcch(lte_rrc::DL_DCCH_Message), UlCcch(lte_rrc::UL_CCCH_Message), @@ -41,13 +52,13 @@ pub enum Message { //ScMcchNb(), } -impl TryFrom<&GsmtapMessage> for Message { - type Error = MessageParsingError; +impl TryFrom<&GsmtapMessage> for LteInformationElement { + type Error = InformationElementError; fn try_from(gsmtap_msg: &GsmtapMessage) -> Result { if let GsmtapType::LteRrc(lte_rrc_subtype) = gsmtap_msg.header.gsmtap_type { use LteRrcSubtype as L; - use Message as R; + 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)?)), @@ -63,9 +74,9 @@ impl TryFrom<&GsmtapMessage> for Message { 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(MessageParsingError::UnsupportedLteRrcSubtype(subtype)), + subtype => Err(InformationElementError::UnsupportedGsmtapType(gsmtap_msg.header.gsmtap_type)), }; } - Err(MessageParsingError::UnknownGsmtapType(gsmtap_msg.header.gsmtap_type)) + Err(InformationElementError::UnsupportedGsmtapType(gsmtap_msg.header.gsmtap_type)) } } diff --git a/lib/src/analysis/lte_downgrade.rs b/lib/src/analysis/lte_downgrade.rs new file mode 100644 index 0000000..9d0f4d4 --- /dev/null +++ b/lib/src/analysis/lte_downgrade.rs @@ -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 { + Cow::from("LTE SIB 7 Downgrade") + } + + fn get_description(&self) -> Cow { + 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 { + 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 + } +} diff --git a/lib/src/analysis/mod.rs b/lib/src/analysis/mod.rs index aaf41a3..d830494 100644 --- a/lib/src/analysis/mod.rs +++ b/lib/src/analysis/mod.rs @@ -1 +1,3 @@ pub mod analyzer; +pub mod information_element; +pub mod lte_downgrade; diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 96dda62..dda6d21 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -7,5 +7,4 @@ pub mod log_codes; pub mod gsmtap; pub mod gsmtap_parser; pub mod pcap; -pub mod lte_rrc; pub mod analysis; diff --git a/lib/src/log_codes.rs b/lib/src/log_codes.rs index 7e90bed..6035980 100644 --- a/lib/src/log_codes.rs +++ b/lib/src/log_codes.rs @@ -1,4 +1,4 @@ -//! Enumerates some releVant diag log codes. Copied from QCSuper +//! Enumerates some relevant diag log codes. Copied from QCSuper // These are 2G-related log types. diff --git a/lte-parser/Cargo.toml b/telcom-parser/Cargo.toml similarity index 94% rename from lte-parser/Cargo.toml rename to telcom-parser/Cargo.toml index 45cf572..9d408e4 100644 --- a/lte-parser/Cargo.toml +++ b/telcom-parser/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "lte-parser" +name = "telcom-parser" version = "0.1.0" edition = "2021" diff --git a/lte-parser/README.md b/telcom-parser/README.md similarity index 82% rename from lte-parser/README.md rename to telcom-parser/README.md index 4819499..5953170 100644 --- a/lte-parser/README.md +++ b/telcom-parser/README.md @@ -1,6 +1,6 @@ -# Autogenerated LTE-RRC packet parsing +# Autogenerated telcom packet parsing -This crate contains [ASN.1](https://en.wikipedia.org/wiki/ASN.1) specs for LTE RRC message payloads, as well as autogenerated +This crate contains [ASN.1](https://en.wikipedia.org/wiki/ASN.1) specs for various telcom message payloads, as well as autogenerated Rust code for parsing these messages. We're using [hampi](https://github.com/ystero-dev/hampi/) as a parser generator, and it seems 3GPP protocols are encoded in the unaligned Packed Encoding Rules (or uPER) codec. @@ -12,10 +12,10 @@ To install the hampi compiler, run: > cargo install asn1-compiler ``` -To generate the parser, run: +To generate the parser for LTE RRC, run: ``` -> hampi-rs-asn1c --codec uper --derive clone --derive partial-eq --derive serialize --module src/lte_rrc.rs -- specs/* +> hampi-rs-asn1c --codec uper --derive clone --derive partial-eq --derive serialize --module src/lte_rrc.rs -- specs/EUTRA* specs/PC5-RRC-Definitions.asn ``` ## Sourcing the ASN.1 files @@ -23,5 +23,4 @@ To generate the parser, run: 3GPP, who develops the standards for 4G (and all the other G's) publishes ASN.1 specs for their protocols in these horrific Microsoft Word docs (e.g. [here](https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=2440)). The ASN.1 blocks are denoted by `--ASN1START` and `--ASN1STOP` text, so extracting them automatically is possible using a script like [hampi's](https://github.com/ystero-dev/hampi/blob/master/examples/specs/parse_spec.py). Instead of doing this ourselves, we just sourced ours from [these](https://obj-sys.com/products/asn1apis/lte_3gpp_apis.php#lte_4g_apis). # TODO -* document how to use them??? maybe toplevel doc comments in lib.rs? * implement proof of concept binary using this to parse QMDL, summarize the packets diff --git a/lte-parser/specs/EUTRA-InterNodeDefinitions.asn b/telcom-parser/specs/EUTRA-InterNodeDefinitions.asn similarity index 100% rename from lte-parser/specs/EUTRA-InterNodeDefinitions.asn rename to telcom-parser/specs/EUTRA-InterNodeDefinitions.asn diff --git a/lte-parser/specs/EUTRA-RRC-Definitions.asn b/telcom-parser/specs/EUTRA-RRC-Definitions.asn similarity index 100% rename from lte-parser/specs/EUTRA-RRC-Definitions.asn rename to telcom-parser/specs/EUTRA-RRC-Definitions.asn diff --git a/lte-parser/specs/EUTRA-Sidelink-Preconf.asn b/telcom-parser/specs/EUTRA-Sidelink-Preconf.asn similarity index 100% rename from lte-parser/specs/EUTRA-Sidelink-Preconf.asn rename to telcom-parser/specs/EUTRA-Sidelink-Preconf.asn diff --git a/lte-parser/specs/EUTRA-UE-Variables.asn b/telcom-parser/specs/EUTRA-UE-Variables.asn similarity index 100% rename from lte-parser/specs/EUTRA-UE-Variables.asn rename to telcom-parser/specs/EUTRA-UE-Variables.asn diff --git a/lte-parser/specs/PC5-RRC-Definitions.asn b/telcom-parser/specs/PC5-RRC-Definitions.asn similarity index 100% rename from lte-parser/specs/PC5-RRC-Definitions.asn rename to telcom-parser/specs/PC5-RRC-Definitions.asn diff --git a/lte-parser/src/lib.rs b/telcom-parser/src/lib.rs similarity index 100% rename from lte-parser/src/lib.rs rename to telcom-parser/src/lib.rs diff --git a/lte-parser/src/lte_rrc.rs b/telcom-parser/src/lte_rrc.rs similarity index 100% rename from lte-parser/src/lte_rrc.rs rename to telcom-parser/src/lte_rrc.rs diff --git a/lte-parser/tests/lte_rrc_test.rs b/telcom-parser/tests/lte_rrc_test.rs similarity index 100% rename from lte-parser/tests/lte_rrc_test.rs rename to telcom-parser/tests/lte_rrc_test.rs