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:
Will Greenberg
2024-02-08 15:38:36 -08:00
parent d570ad3cb1
commit 5d7caba1a6
19 changed files with 150 additions and 52 deletions
Generated
+14 -14
View File
@@ -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"
+1 -1
View File
@@ -5,6 +5,6 @@ members = [
"bin",
"serial",
"rootshell",
"lte-parser",
"telcom-parser",
]
resolver = "2"
+1 -1
View File
@@ -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" }
+19 -15
View File
@@ -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
}
}
@@ -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 Message {
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),
@@ -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)?)),
@@ -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
View 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
}
}
+2
View File
@@ -1 +1,3 @@
pub mod analyzer;
pub mod information_element;
pub mod lte_downgrade;
-1
View File
@@ -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;
+1 -1
View File
@@ -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.
@@ -1,5 +1,5 @@
[package]
name = "lte-parser"
name = "telcom-parser"
version = "0.1.0"
edition = "2021"
@@ -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