Skip to content

Commit

Permalink
Minimal version of the LTE downgrade analyzer
Browse files Browse the repository at this point in the history
This also renames the lte_parser crate to telcom_parser, since it'll
handle any 2G or 3G parsing going forward.
  • Loading branch information
wgreenberg committed Feb 8, 2024
1 parent c5b455e commit e99395c
Show file tree
Hide file tree
Showing 19 changed files with 150 additions and 52 deletions.
28 changes: 14 additions & 14 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ members = [
"bin",
"serial",
"rootshell",
"lte-parser",
"telcom-parser",
]
resolver = "2"
2 changes: 1 addition & 1 deletion lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
34 changes: 19 additions & 15 deletions lib/src/analysis/analyzer.rs
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -11,39 +13,41 @@ 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 {
pub event_type: EventType,
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
}
}
37 changes: 24 additions & 13 deletions lib/src/lte_rrc.rs → lib/src/analysis/information_element.rs
Original file line number Diff line number Diff line change
@@ -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),
Expand Down Expand Up @@ -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<Self, Self::Error> {
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)?)),
Expand All @@ -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))
}
}
83 changes: 83 additions & 0 deletions lib/src/analysis/lte_downgrade.rs
Original file line number Diff line number Diff line change
@@ -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
}
}
2 changes: 2 additions & 0 deletions lib/src/analysis/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
pub mod analyzer;
pub mod information_element;
pub mod lte_downgrade;
1 change: 0 additions & 1 deletion lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
2 changes: 1 addition & 1 deletion lib/src/log_codes.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 1 addition & 1 deletion lte-parser/Cargo.toml → telcom-parser/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "lte-parser"
name = "telcom-parser"
version = "0.1.0"
edition = "2021"

Expand Down
9 changes: 4 additions & 5 deletions lte-parser/README.md → telcom-parser/README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand All @@ -12,16 +12,15 @@ 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

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
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 comments on commit e99395c

Please sign in to comment.