Compare commits

...

19 Commits

Author SHA1 Message Date
Cooper Quintin ee83613757 update readme 2025-02-27 17:29:48 -08:00
Cooper Quintin 840f8ad8b0 stop before upload in case file is locked from writing by running process 2025-02-10 11:26:27 -08:00
Cooper Quintin c9ac834ca7 show warnings in web UI 2025-02-10 11:26:27 -08:00
Cooper Quintin 8629aacf6b switch default to not see trace messages, switch arg from quiet to verbose 2025-02-10 11:26:27 -08:00
Cooper Quintin a3fd1479f9 rename qmdl path so that downloaded files have a qmdl extension 2025-02-10 11:26:27 -08:00
Cooper Quintin 049c563f02 fix shortcodes on rayhunter_check 2025-02-10 11:26:27 -08:00
Cooper Quintin a33b5a3418 Update README.md
Co-authored-by: Will Greenberg <willg@eff.org>
2025-01-31 17:00:44 -08:00
Cooper Quintin 107ba58296 warn if running install scritps from git tree 2025-01-31 17:00:44 -08:00
Cooper Quintin d016279172 some tweaks to readme 2025-01-31 17:00:44 -08:00
Will Greenberg 5a084f1abb lib: set uplink flag for NAS 2025-01-30 11:33:14 -08:00
Will Greenberg 3619df32ab check: give qmdl-path a shorthand arg 2025-01-28 11:02:19 -08:00
Will Greenberg 34d87d1fd7 this macro isn't public, so docstrings won't work 2025-01-28 11:02:19 -08:00
Will Greenberg da4952e70f fix docstring code 2025-01-28 11:02:19 -08:00
Will Greenberg 30323b8329 Keep old 2G downgrade analyzer 2025-01-28 11:02:19 -08:00
Will Greenberg 28b0f409db fix attribution 2025-01-28 11:02:19 -08:00
Will Greenberg 12640cc878 Rewrite our 2G downgrade analyzer 2025-01-28 11:02:19 -08:00
Will Greenberg 26eda5904f Better wording on IMSI requested warning 2025-01-28 11:02:19 -08:00
Will Greenberg 3e26e61b05 check: don't count informational events as warnings, better logging 2025-01-28 11:02:19 -08:00
Will Greenberg 565c0f1e67 serial: fix UTF-8 panic on macOS 2025-01-26 17:05:42 -08:00
19 changed files with 267 additions and 60 deletions
Generated
+86
View File
@@ -482,6 +482,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "colored"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "core-foundation-sys" name = "core-foundation-sys"
version = "0.8.6" version = "0.8.6"
@@ -602,6 +612,15 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "deranged"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
dependencies = [
"powerfmt",
]
[[package]] [[package]]
name = "derive-into-owned" name = "derive-into-owned"
version = "0.2.0" version = "0.2.0"
@@ -1310,6 +1329,12 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]] [[package]]
name = "num-derive" name = "num-derive"
version = "0.4.2" version = "0.4.2"
@@ -1360,6 +1385,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "num_threads"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "object" name = "object"
version = "0.32.2" version = "0.32.2"
@@ -1487,6 +1521,12 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
] ]
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.17" version = "0.2.17"
@@ -1679,6 +1719,7 @@ dependencies = [
"rayhunter", "rayhunter",
"serde", "serde",
"serde_json", "serde_json",
"simple_logger",
"tempfile", "tempfile",
"thiserror", "thiserror",
"tokio", "tokio",
@@ -1901,6 +1942,18 @@ dependencies = [
"quote", "quote",
] ]
[[package]]
name = "simple_logger"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8c5dfa5e08767553704aa0ffd9d9794d527103c736aba9854773851fd7497eb"
dependencies = [
"colored",
"log",
"time",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.9" version = "0.4.9"
@@ -2065,6 +2118,39 @@ dependencies = [
"weezl", "weezl",
] ]
[[package]]
name = "time"
version = "0.3.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
dependencies = [
"deranged",
"itoa",
"libc",
"num-conv",
"num_threads",
"powerfmt",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
dependencies = [
"num-conv",
"time-core",
]
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.36.0" version = "1.36.0"
+10 -26
View File
@@ -1,46 +1,30 @@
![Rayhunter Logo - An Orca taking a bite out of a cellular signal bar](https://www.eff.org/files/styles/media_browser_preview/public/banner_library/rayhunter-banner.png)
# Rayhunter # Rayhunter
```
@@@@@@@ @@@@@@ @@@ @@@ @@@ @@@ @@@ @@@ @@@ @@@ @@@@@@@ @@@@@@@@ @@@@@@@
@@! @@@ @@! @@@ @@! !@@ @@! @@@ @@! @@@ @@!@!@@@ @@! @@! @@! @@@
@!@!!@! @!@!@!@! !@!@! @!@!@!@! @!@ !@! @!@@!!@! @!! @!!!:! @!@!!@!
!!: :!! !!: !!! !!: !!: !!! !!: !!! !!: !!! !!: !!: !!: :!!
: : : : : : .: : : : :.:: : :: : : : :: ::: : : :
_ _ _ _ _ _ _ _
)`'-.,_)`'-.,_)`'-.,_)`'-.,_)`'-.,_)`'-.,_)`'-.,_)`'-.,_
O .
O ' '
o ' .
o .'
__________.-' '...___
.-' ### '''...__
/ a### ## ''--.._ ______
'. # ######## ' .-'
'-._ ..**********#### ___...---'''\ '
'-._ __________...---''' \ l
\ | apc '._|
\__;
```
![Tests](https://github.com/EFForg/rayhunter/actions/workflows/check-and-test.yml/badge.svg) ![Tests](https://github.com/EFForg/rayhunter/actions/workflows/check-and-test.yml/badge.svg)
Rayhunter is an IMSI Catcher Catcher for the Orbic mobile hotspot. Rayhunter is an IMSI Catcher Catcher for the Orbic mobile hotspot.
**THIS CODE IS PROOF OF CONCEPT AND SHOULD NOT BE RELIED UPON IN HIGH RISK SITUATIONS** **THIS CODE IS PROOF OF CONCEPT AND SHOULD NOT BE RELIED UPON IN HIGH RISK SITUATIONS**
## The Hardware
Code is built and tested for the Orbic RC400L mobile hotspot, it may work on other orbics and other Code is built and tested for the Orbic RC400L mobile hotspot, it may work on other orbics and other
linux/qualcom devices but this is the only one we have tested on. Buy the orbic [using bezos bucks](https://www.amazon.com/gp/product/B09CLS6Z7X/) linux/qualcom devices but this is the only one we have tested on.
Buy the orbic [using bezos bucks](https://www.amazon.com/Orbic-Verizon-Hotspot-Connect-Enabled/dp/B08N3CHC4Y)
Or on [Ebay](https://www.ebay.com/sch/i.html?_nkw=orbic+rc400l)
## Setup ## Setup
*NOTE: We don't currently support automated installs on windows, you will have to follow the manual install instructions below* *NOTE: We don't currently support automated installs on windows, you will have to follow the manual install instructions below*
1. Download the latest [rayhunter release bundle](https://github.com/EFForg/rayhunter/releases) and extract it. 1. Download the latest [rayhunter release bundle](https://github.com/EFForg/rayhunter/releases) and extract it.
2. Run the install script inside the bundle corresponding to your platform (`install-linux.sh`, `install-mac.sh`). **If you are installing from the cloned github repository please see the development instructions below, running `install-linux.sh` from the git tree will not work.**
2. Run the install script inside the bundle corresponding to your platform (`install-linux.sh`, `install-mac.sh`). The Linux installer has only been tested on the latest version of Ubuntu. If it fails you will need to follow the install steps outlined in **Development** below.
3. Once finished, rayhunter should be running! You can verify this by visiting the web UI as described below. 3. Once finished, rayhunter should be running! You can verify this by visiting the web UI as described below.
## Usage ## Usage
Once installed, rayhunter will run automatically whenever your Orbic device is running. It serves a web UI that provides some basic controls, such as being able to start/stop recordings, download captures, and view heuristic analyses of captures. You can access this UI in one of two ways: Once installed, rayhunter will run automatically whenever your Orbic device is running. It serves a web UI that provides some basic controls, such as being able to start/stop recordings, download captures, and view heuristic analyses of captures. You can access this UI in one of two ways:
+1
View File
@@ -32,3 +32,4 @@ clap = { version = "4.5.2", features = ["derive"] }
serde_json = "1.0.114" serde_json = "1.0.114"
image = "0.25.1" image = "0.25.1"
tempfile = "3.10.1" tempfile = "3.10.1"
simple_logger = "5.0.0"
+43 -13
View File
@@ -1,5 +1,6 @@
use std::{collections::HashMap, future, path::PathBuf, pin::pin}; use std::{collections::HashMap, future, path::PathBuf, pin::pin};
use rayhunter::{analysis::analyzer::Harness, diag::DataType, gsmtap_parser, pcap::GsmtapPcapWriter, qmdl::QmdlReader}; use log::{info, warn};
use rayhunter::{analysis::analyzer::{EventType, Harness}, diag::DataType, gsmtap_parser, pcap::GsmtapPcapWriter, qmdl::QmdlReader};
use tokio::fs::{metadata, read_dir, File}; use tokio::fs::{metadata, read_dir, File};
use clap::Parser; use clap::Parser;
use futures::TryStreamExt; use futures::TryStreamExt;
@@ -9,10 +10,10 @@ mod dummy_analyzer;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(version, about)] #[command(version, about)]
struct Args { struct Args {
#[arg(short, long)] #[arg(short = 'p', long)]
qmdl_path: PathBuf, qmdl_path: PathBuf,
#[arg(short, long)] #[arg(short = 'c', long)]
pcapify: bool, pcapify: bool,
#[arg(long)] #[arg(long)]
@@ -20,6 +21,9 @@ struct Args {
#[arg(long)] #[arg(long)]
enable_dummy_analyzer: bool, enable_dummy_analyzer: bool,
#[arg(short, long)]
verbose: bool,
} }
async fn analyze_file(harness: &mut Harness, qmdl_path: &str, show_skipped: bool) { async fn analyze_file(harness: &mut Harness, qmdl_path: &str, show_skipped: bool) {
@@ -41,20 +45,37 @@ async fn analyze_file(harness: &mut Harness, qmdl_path: &str, show_skipped: bool
} }
for analysis in row.analysis { for analysis in row.analysis {
for maybe_event in analysis.events { for maybe_event in analysis.events {
if let Some(event) = maybe_event { let Some(event) = maybe_event else { continue };
warnings += 1; match event.event_type {
println!("{}: {:?}", analysis.timestamp, event); EventType::Informational => {
info!(
"{}: INFO - {} {}",
qmdl_path,
analysis.timestamp,
event.message,
);
}
EventType::QualitativeWarning { severity } => {
warn!(
"{}: WARNING (Severity: {:?}) - {} {}",
qmdl_path,
severity,
analysis.timestamp,
event.message,
);
warnings += 1;
}
} }
} }
} }
} }
if show_skipped && skipped > 0 { if show_skipped && skipped > 0 {
println!("{}: messages skipped:", qmdl_path); info!("{}: messages skipped:", qmdl_path);
for (reason, count) in skipped_reasons.iter() { for (reason, count) in skipped_reasons.iter() {
println!(" - {}: \"{}\"", count, reason); info!(" - {}: \"{}\"", count, reason);
} }
} }
println!("{}: {} messages analyzed, {} warnings, {} messages skipped", qmdl_path, total_messages, warnings, skipped); info!("{}: {} messages analyzed, {} warnings, {} messages skipped", qmdl_path, total_messages, warnings, skipped);
} }
async fn pcapify(qmdl_path: &PathBuf) { async fn pcapify(qmdl_path: &PathBuf) {
@@ -75,21 +96,30 @@ async fn pcapify(qmdl_path: &PathBuf) {
} }
} }
} }
println!("wrote pcap to {:?}", &pcap_path); info!("wrote pcap to {:?}", &pcap_path);
} }
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
env_logger::init();
let args = Args::parse(); let args = Args::parse();
let level = if args.verbose {
log::LevelFilter::Trace
} else {
log::LevelFilter::Warn
};
simple_logger::SimpleLogger::new()
.with_colors(true)
.without_timestamps()
.with_level(level)
.init().unwrap();
let mut harness = Harness::new_with_all_analyzers(); let mut harness = Harness::new_with_all_analyzers();
if args.enable_dummy_analyzer { if args.enable_dummy_analyzer {
harness.add_analyzer(Box::new(dummy_analyzer::TestAnalyzer { count: 0 })); harness.add_analyzer(Box::new(dummy_analyzer::TestAnalyzer { count: 0 }));
} }
println!("Analyzers:"); info!("Analyzers:");
for analyzer in harness.get_metadata().analyzers { for analyzer in harness.get_metadata().analyzers {
println!(" - {}: {}", analyzer.name, analyzer.description); info!(" - {}: {}", analyzer.name, analyzer.description);
} }
let metadata = metadata(&args.qmdl_path).await.expect("failed to get metadata"); let metadata = metadata(&args.qmdl_path).await.expect("failed to get metadata");
+3 -2
View File
@@ -27,9 +27,10 @@ pub struct ServerState {
} }
pub async fn get_qmdl(State(state): State<Arc<ServerState>>, Path(qmdl_name): Path<String>) -> Result<Response, (StatusCode, String)> { pub async fn get_qmdl(State(state): State<Arc<ServerState>>, Path(qmdl_name): Path<String>) -> Result<Response, (StatusCode, String)> {
let qmdl_idx = qmdl_name.trim_end_matches(".qmdl");
let qmdl_store = state.qmdl_store_lock.read().await; let qmdl_store = state.qmdl_store_lock.read().await;
let (entry_index, entry) = qmdl_store.entry_for_name(&qmdl_name) let (entry_index, entry) = qmdl_store.entry_for_name(&qmdl_idx)
.ok_or((StatusCode::NOT_FOUND, format!("couldn't find qmdl file with name {}", qmdl_name)))?; .ok_or((StatusCode::NOT_FOUND, format!("couldn't find qmdl file with name {}", qmdl_idx)))?;
let qmdl_file = qmdl_store.open_entry_qmdl(entry_index).await let qmdl_file = qmdl_store.open_entry_qmdl(entry_index).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("error opening QMDL file: {}", e)))?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("error opening QMDL file: {}", e)))?;
let limited_qmdl_file = qmdl_file.take(entry.qmdl_size_bytes as u64); let limited_qmdl_file = qmdl_file.take(entry.qmdl_size_bytes as u64);
+7 -2
View File
@@ -80,6 +80,11 @@ async function updateEntryAnalysisResult(entry) {
entry.analysis_result = `0 warnings!`; entry.analysis_result = `0 warnings!`;
} else { } else {
entry.analysis_result = `!!! ${entry.analysis.warnings.length} warnings !!!`; entry.analysis_result = `!!! ${entry.analysis.warnings.length} warnings !!!`;
for(warning of entry.analysis.warnings){
msg = `${warning.timestamp}: ${warning.warning.events[1].message}`
console.log(msg)
entry.analysis_result += `<br>${msg}`
}
} }
} }
@@ -136,11 +141,11 @@ function createEntryRow(entry, isCurrent) {
row.appendChild(pcapTd); row.appendChild(pcapTd);
const qmdlTd = document.createElement('td'); const qmdlTd = document.createElement('td');
qmdlTd.appendChild(createLink(`/api/qmdl/${entry.name}`, 'qmdl')); qmdlTd.appendChild(createLink(`/api/qmdl/${entry.name}.qmdl`, 'qmdl'));
row.appendChild(qmdlTd); row.appendChild(qmdlTd);
const analysisResult = document.createElement('td'); const analysisResult = document.createElement('td');
analysisResult.innerText = entry.analysis_result; analysisResult.innerHTML = entry.analysis_result;
if (entry.analysis.warnings.length > 0) { if (entry.analysis.warnings.length > 0) {
row.classList.add("warning"); row.classList.add("warning");
} }
+7 -1
View File
@@ -1,6 +1,13 @@
#!/bin/env bash #!/bin/env bash
set -e set -e
export SERIAL_PATH="./serial-ubuntu-latest/serial"
if [ ! -x "$SERIAL_PATH" ]; then
echo "The serial binary cannot be found at $SERIAL_PATH. If you are running this from the git tree please instead run it from the latest release bundle https://github.com/EFForg/rayhunter/releases"
exit 1
fi
if ! command -v adb &> /dev/null; then if ! command -v adb &> /dev/null; then
if [ ! -d ./platform-tools ] ; then if [ ! -d ./platform-tools ] ; then
echo "adb not found, downloading local copy" echo "adb not found, downloading local copy"
@@ -12,6 +19,5 @@ else
export ADB=`which adb` export ADB=`which adb`
fi fi
export SERIAL_PATH="./serial-ubuntu-latest/serial"
. "$(dirname "$0")"/install-common.sh . "$(dirname "$0")"/install-common.sh
install install
+7 -2
View File
@@ -1,6 +1,12 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -e set -e
export SERIAL_PATH="./serial-macos-latest/serial"
if [ ! -x "$SERIAL_PATH" ]; then
echo "The serial binary cannot be found at $SERIAL_PATH. If you are running this from the git tree please instead run it from the latest release bundle at https://github.com/EFForg/rayhunter/releases"
exit 1
fi
if ! command -v adb &> /dev/null; then if ! command -v adb &> /dev/null; then
if [ ! -d ./platform-tools ]; then if [ ! -d ./platform-tools ]; then
echo "adb not found, downloading local copy" echo "adb not found, downloading local copy"
@@ -12,6 +18,5 @@ else
export ADB=`which adb` export ADB=`which adb`
fi fi
export SERIAL_PATH="./serial-macos-latest/serial"
. "$(dirname "$0")"/install-common.sh . "$(dirname "$0")"/install-common.sh
install install
+4 -2
View File
@@ -7,7 +7,8 @@ use crate::{diag::MessagesContainer, gsmtap_parser};
use super::{ use super::{
imsi_requested::ImsiRequestedAnalyzer, imsi_requested::ImsiRequestedAnalyzer,
information_element::InformationElement, information_element::InformationElement,
lte_downgrade::LteSib6And7DowngradeAnalyzer, connection_redirect_downgrade::ConnectionRedirect2GDowngradeAnalyzer,
priority_2g_downgrade::LteSib6And7DowngradeAnalyzer,
null_cipher::NullCipherAnalyzer, null_cipher::NullCipherAnalyzer,
}; };
@@ -117,8 +118,9 @@ impl Harness {
pub fn new_with_all_analyzers() -> Self { pub fn new_with_all_analyzers() -> Self {
let mut harness = Harness::new(); let mut harness = Harness::new();
harness.add_analyzer(Box::new(LteSib6And7DowngradeAnalyzer{}));
harness.add_analyzer(Box::new(ImsiRequestedAnalyzer::new())); harness.add_analyzer(Box::new(ImsiRequestedAnalyzer::new()));
harness.add_analyzer(Box::new(ConnectionRedirect2GDowngradeAnalyzer{}));
harness.add_analyzer(Box::new(LteSib6And7DowngradeAnalyzer{}));
harness.add_analyzer(Box::new(NullCipherAnalyzer{})); harness.add_analyzer(Box::new(NullCipherAnalyzer{}));
harness harness
@@ -0,0 +1,42 @@
use std::borrow::Cow;
use super::analyzer::{Analyzer, Event, EventType, Severity};
use super::information_element::{InformationElement, LteInformationElement};
use telcom_parser::lte_rrc::{DL_DCCH_Message, DL_DCCH_MessageType, DL_DCCH_MessageType_c1, RRCConnectionReleaseCriticalExtensions, RRCConnectionReleaseCriticalExtensions_c1, RedirectedCarrierInfo};
use super::util::unpack;
// Based on HITBSecConf presentation "Forcing a targeted LTE cellphone into an
// eavesdropping network" by Lin Huang
pub struct ConnectionRedirect2GDowngradeAnalyzer {
}
// TODO: keep track of SIB state to compare LTE reselection blocks w/ 2g/3g ones
impl Analyzer for ConnectionRedirect2GDowngradeAnalyzer {
fn get_name(&self) -> Cow<str> {
Cow::from("Connection Release/Redirected Carrier 2G Downgrade")
}
fn get_description(&self) -> Cow<str> {
Cow::from("Tests if a cell releases our connection and redirects us to a 2G cell.")
}
fn analyze_information_element(&mut self, ie: &InformationElement) -> Option<Event> {
unpack!(InformationElement::LTE(lte_ie) = ie);
unpack!(LteInformationElement::DlDcch(DL_DCCH_Message { message }) = lte_ie);
unpack!(DL_DCCH_MessageType::C1(c1) = message);
unpack!(DL_DCCH_MessageType_c1::RrcConnectionRelease(release) = c1);
unpack!(RRCConnectionReleaseCriticalExtensions::C1(c1) = &release.critical_extensions);
unpack!(RRCConnectionReleaseCriticalExtensions_c1::RrcConnectionRelease_r8(r8_ies) = c1);
unpack!(Some(carrier_info) = &r8_ies.redirected_carrier_info);
match carrier_info {
RedirectedCarrierInfo::Geran(_carrier_freqs_geran) => Some(Event {
event_type: EventType::QualitativeWarning { severity: Severity::High },
message: format!("Detected 2G downgrade"),
}),
_ => Some(Event {
event_type: EventType::Informational,
message: format!("RRCConnectionRelease CarrierInfo: {:?}", carrier_info),
}),
}
}
}
+3 -3
View File
@@ -30,7 +30,7 @@ impl Analyzer for ImsiRequestedAnalyzer {
return None; return None;
}; };
// NAS identity request // NAS identity request, ID type IMSI
if payload == &[0x07, 0x55, 0x01] { if payload == &[0x07, 0x55, 0x01] {
if self.packet_num < PACKET_THRESHHOLD { if self.packet_num < PACKET_THRESHHOLD {
return Some(Event { return Some(Event {
@@ -38,7 +38,7 @@ impl Analyzer for ImsiRequestedAnalyzer {
severity: Severity::Medium severity: Severity::Medium
}, },
message: format!( message: format!(
"NAS IMSI request detected, however it was within \ "NAS IMSI identity request detected, however it was within \
the first {} packets of this analysis. If you just \ the first {} packets of this analysis. If you just \
turned your device on, this is likely a \ turned your device on, this is likely a \
false-positive.", false-positive.",
@@ -50,7 +50,7 @@ impl Analyzer for ImsiRequestedAnalyzer {
event_type: EventType::QualitativeWarning { event_type: EventType::QualitativeWarning {
severity: Severity::High severity: Severity::High
}, },
message: format!("NAS IMSI request detected"), message: format!("NAS IMSI identity request detected"),
}) })
} }
} }
+3 -1
View File
@@ -1,6 +1,8 @@
pub mod analyzer; pub mod analyzer;
pub mod information_element; pub mod information_element;
pub mod lte_downgrade; pub mod priority_2g_downgrade;
pub mod connection_redirect_downgrade;
pub mod imsi_provided; pub mod imsi_provided;
pub mod imsi_requested; pub mod imsi_requested;
pub mod null_cipher; pub mod null_cipher;
pub mod util;
+32
View File
@@ -0,0 +1,32 @@
// Unpacks a pattern, or returns None.
//
// # Examples
// You can use `unpack!` to unroll highly nested enums like this:
// ```
// enum Foo {
// A(Bar),
// B,
// }
//
// enum Bar {
// C(Baz)
// }
//
// struct Baz;
//
// fn get_bang(foo: Foo) -> Option<Baz> {
// unpack!(Foo::A(bar) = foo);
// unpack!(Bar::C(baz) = bar);
// baz
// }
// ```
//
macro_rules! unpack {
($pat:pat = $val:expr) => {
let $pat = $val else { return None; };
};
}
// this is apparently how you make a macro publicly usable from this module
pub(crate) use unpack;
+2 -2
View File
@@ -221,9 +221,9 @@ pub enum Nas4GMessageDirection {
// * 0xb0ec: plain EMM NAS message (incoming) // * 0xb0ec: plain EMM NAS message (incoming)
// * 0xb0ed: plain EMM NAS message (outgoing) // * 0xb0ed: plain EMM NAS message (outgoing)
#[deku(id_pat = "0xb0e2 | 0xb0ec")] #[deku(id_pat = "0xb0e2 | 0xb0ec")]
Inbound, Downlink,
#[deku(id_pat = "0xb0e3 | 0xb0ed")] #[deku(id_pat = "0xb0e3 | 0xb0ed")]
Outbound, Uplink,
} }
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)] #[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
+8 -1
View File
@@ -17,7 +17,7 @@ pub enum GsmtapType {
UmtsRlcMac, UmtsRlcMac,
UmtsRrc(UmtsRrcSubtype), UmtsRrc(UmtsRrcSubtype),
LteRrc(LteRrcSubtype), /* LTE interface */ LteRrc(LteRrcSubtype), /* LTE interface */
LteMac, /* LTE MAC interface */ LteMac, /* LTE MAC interface */
LteMacFramed, /* LTE MAC with context hdr */ LteMacFramed, /* LTE MAC with context hdr */
OsmocoreLog, /* libosmocore logging */ OsmocoreLog, /* libosmocore logging */
QcDiag, /* Qualcomm DIAG frame */ QcDiag, /* Qualcomm DIAG frame */
@@ -200,6 +200,11 @@ pub struct GsmtapHeader {
#[deku(update = "self.gsmtap_type.get_type()")] #[deku(update = "self.gsmtap_type.get_type()")]
pub packet_type: u8, pub packet_type: u8,
pub timeslot: u8, pub timeslot: u8,
#[deku(bits = 1)]
pub pcs_band_indicator: bool,
#[deku(bits = 1)]
pub uplink: bool,
#[deku(bits = 14)]
pub arfcn: u16, pub arfcn: u16,
pub signal_dbm: i8, pub signal_dbm: i8,
pub signal_noise_ratio_db: u8, pub signal_noise_ratio_db: u8,
@@ -222,6 +227,8 @@ impl GsmtapHeader {
header_len: 4, header_len: 4,
packet_type: gsmtap_type.get_type(), packet_type: gsmtap_type.get_type(),
timeslot: 0, timeslot: 0,
pcs_band_indicator: false,
uplink: false,
arfcn: 0, arfcn: 0,
signal_dbm: 0, signal_dbm: 0,
signal_noise_ratio_db: 0, signal_noise_ratio_db: 0,
+3 -3
View File
@@ -99,7 +99,6 @@ fn log_to_gsmtap(value: LogBody) -> Result<Option<GsmtapMessage>, GsmtapParserEr
_ => return Err(GsmtapParserError::InvalidLteRrcOtaExtHeaderVersion(ext_header_version)), _ => return Err(GsmtapParserError::InvalidLteRrcOtaExtHeaderVersion(ext_header_version)),
}; };
let mut header = GsmtapHeader::new(gsmtap_type); let mut header = GsmtapHeader::new(gsmtap_type);
// Wireshark GSMTAP only accepts 14 bits of ARFCN
header.arfcn = packet.get_earfcn().try_into().unwrap_or(0); header.arfcn = packet.get_earfcn().try_into().unwrap_or(0);
header.frame_number = packet.get_sfn(); header.frame_number = packet.get_sfn();
header.subslot = packet.get_subfn(); header.subslot = packet.get_subfn();
@@ -108,9 +107,10 @@ fn log_to_gsmtap(value: LogBody) -> Result<Option<GsmtapMessage>, GsmtapParserEr
payload: packet.take_payload(), payload: packet.take_payload(),
})) }))
}, },
LogBody::Nas4GMessage { msg, .. } => { LogBody::Nas4GMessage { msg, direction, .. } => {
// currently we only handle "plain" (i.e. non-secure) NAS messages // currently we only handle "plain" (i.e. non-secure) NAS messages
let header = GsmtapHeader::new(GsmtapType::LteNas(LteNasSubtype::Plain)); let mut header = GsmtapHeader::new(GsmtapType::LteNas(LteNasSubtype::Plain));
header.uplink = matches!(direction, Nas4GMessageDirection::Uplink);
Ok(Some(GsmtapMessage { Ok(Some(GsmtapMessage {
header, header,
payload: msg, payload: msg,
+2 -1
View File
@@ -1,4 +1,5 @@
#!/bin/sh #!/bin/sh
cargo build --release --target="armv7-unknown-linux-gnueabihf" #--features debug cargo build --release --target="armv7-unknown-linux-gnueabihf" #--features debug
adb shell '/bin/rootshell -c "/etc/init.d/rayhunter_daemon stop"'
adb push target/armv7-unknown-linux-gnueabihf/release/rayhunter-daemon /data/rayhunter/rayhunter-daemon adb push target/armv7-unknown-linux-gnueabihf/release/rayhunter-daemon /data/rayhunter/rayhunter-daemon
adb shell '/bin/rootshell -c "/etc/init.d/rayhunter_daemon restart"' adb shell '/bin/rootshell -c "/etc/init.d/rayhunter_daemon start"'
+4 -1
View File
@@ -78,7 +78,10 @@ fn send_command<T: UsbContext>(handle: &mut DeviceHandle<T>, command: &str) {
.read_bulk(0x82, &mut response, timeout) .read_bulk(0x82, &mut response, timeout)
.expect("Failed to read response"); .expect("Failed to read response");
let responsestr = str::from_utf8(&response).expect("Failed to parse response"); // For some reason, on macOS the response buffer gets filled with garbage data that's
// rarely valid UTF-8. Luckily we only care about the first couple bytes, so just drop
// the garbage with `from_utf8_lossy` and look for our expected success string.
let responsestr = String::from_utf8_lossy(&response);
if !responsestr.contains("\r\nOK\r\n") { if !responsestr.contains("\r\nOK\r\n") {
println!("Received unexpected response{0}", responsestr); println!("Received unexpected response{0}", responsestr);
std::process::exit(1); std::process::exit(1);