mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-05-31 18:23:35 -07:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ee83613757 | |||
| 840f8ad8b0 | |||
| c9ac834ca7 | |||
| 8629aacf6b | |||
| a3fd1479f9 | |||
| 049c563f02 | |||
| a33b5a3418 | |||
| 107ba58296 | |||
| d016279172 | |||
| 5a084f1abb | |||
| 3619df32ab | |||
| 34d87d1fd7 | |||
| da4952e70f | |||
| 30323b8329 | |||
| 28b0f409db | |||
| 12640cc878 | |||
| 26eda5904f | |||
| 3e26e61b05 | |||
| 565c0f1e67 |
Generated
+86
@@ -482,6 +482,16 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.6"
|
||||
@@ -602,6 +612,15 @@ dependencies = [
|
||||
"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]]
|
||||
name = "derive-into-owned"
|
||||
version = "0.2.0"
|
||||
@@ -1310,6 +1329,12 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.4.2"
|
||||
@@ -1360,6 +1385,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.32.2"
|
||||
@@ -1487,6 +1521,12 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
@@ -1679,6 +1719,7 @@ dependencies = [
|
||||
"rayhunter",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"simple_logger",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
@@ -1901,6 +1942,18 @@ dependencies = [
|
||||
"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]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
@@ -2065,6 +2118,39 @@ dependencies = [
|
||||
"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]]
|
||||
name = "tokio"
|
||||
version = "1.36.0"
|
||||
|
||||
@@ -1,46 +1,30 @@
|
||||

|
||||
# Rayhunter
|
||||
|
||||
```
|
||||
@@@@@@@ @@@@@@ @@@ @@@ @@@ @@@ @@@ @@@ @@@ @@@ @@@@@@@ @@@@@@@@ @@@@@@@
|
||||
@@! @@@ @@! @@@ @@! !@@ @@! @@@ @@! @@@ @@!@!@@@ @@! @@! @@! @@@
|
||||
@!@!!@! @!@!@!@! !@!@! @!@!@!@! @!@ !@! @!@@!!@! @!! @!!!:! @!@!!@!
|
||||
!!: :!! !!: !!! !!: !!: !!! !!: !!! !!: !!! !!: !!: !!: :!!
|
||||
: : : : : : .: : : : :.:: : :: : : : :: ::: : : :
|
||||
|
||||
|
||||
_ _ _ _ _ _ _ _
|
||||
)`'-.,_)`'-.,_)`'-.,_)`'-.,_)`'-.,_)`'-.,_)`'-.,_)`'-.,_
|
||||
|
||||
O .
|
||||
O ' '
|
||||
o ' .
|
||||
o .'
|
||||
__________.-' '...___
|
||||
.-' ### '''...__
|
||||
/ a### ## ''--.._ ______
|
||||
'. # ######## ' .-'
|
||||
'-._ ..**********#### ___...---'''\ '
|
||||
'-._ __________...---''' \ l
|
||||
\ | apc '._|
|
||||
\__;
|
||||
```
|
||||

|
||||
|
||||
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**
|
||||
|
||||
|
||||
## The Hardware
|
||||
|
||||
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
|
||||
|
||||
*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.
|
||||
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.
|
||||
|
||||
|
||||
## 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:
|
||||
|
||||
@@ -32,3 +32,4 @@ clap = { version = "4.5.2", features = ["derive"] }
|
||||
serde_json = "1.0.114"
|
||||
image = "0.25.1"
|
||||
tempfile = "3.10.1"
|
||||
simple_logger = "5.0.0"
|
||||
|
||||
+43
-13
@@ -1,5 +1,6 @@
|
||||
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 clap::Parser;
|
||||
use futures::TryStreamExt;
|
||||
@@ -9,10 +10,10 @@ mod dummy_analyzer;
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about)]
|
||||
struct Args {
|
||||
#[arg(short, long)]
|
||||
#[arg(short = 'p', long)]
|
||||
qmdl_path: PathBuf,
|
||||
|
||||
#[arg(short, long)]
|
||||
#[arg(short = 'c', long)]
|
||||
pcapify: bool,
|
||||
|
||||
#[arg(long)]
|
||||
@@ -20,6 +21,9 @@ struct Args {
|
||||
|
||||
#[arg(long)]
|
||||
enable_dummy_analyzer: bool,
|
||||
|
||||
#[arg(short, long)]
|
||||
verbose: 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 maybe_event in analysis.events {
|
||||
if let Some(event) = maybe_event {
|
||||
warnings += 1;
|
||||
println!("{}: {:?}", analysis.timestamp, event);
|
||||
let Some(event) = maybe_event else { continue };
|
||||
match event.event_type {
|
||||
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 {
|
||||
println!("{}: messages skipped:", qmdl_path);
|
||||
info!("{}: messages skipped:", qmdl_path);
|
||||
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) {
|
||||
@@ -75,21 +96,30 @@ async fn pcapify(qmdl_path: &PathBuf) {
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("wrote pcap to {:?}", &pcap_path);
|
||||
info!("wrote pcap to {:?}", &pcap_path);
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
env_logger::init();
|
||||
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();
|
||||
if args.enable_dummy_analyzer {
|
||||
harness.add_analyzer(Box::new(dummy_analyzer::TestAnalyzer { count: 0 }));
|
||||
}
|
||||
println!("Analyzers:");
|
||||
info!("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");
|
||||
|
||||
+3
-2
@@ -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)> {
|
||||
let qmdl_idx = qmdl_name.trim_end_matches(".qmdl");
|
||||
let qmdl_store = state.qmdl_store_lock.read().await;
|
||||
let (entry_index, entry) = qmdl_store.entry_for_name(&qmdl_name)
|
||||
.ok_or((StatusCode::NOT_FOUND, format!("couldn't find qmdl file with 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_idx)))?;
|
||||
let qmdl_file = qmdl_store.open_entry_qmdl(entry_index).await
|
||||
.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);
|
||||
|
||||
@@ -80,6 +80,11 @@ async function updateEntryAnalysisResult(entry) {
|
||||
entry.analysis_result = `0 warnings!`;
|
||||
} else {
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
const analysisResult = document.createElement('td');
|
||||
analysisResult.innerText = entry.analysis_result;
|
||||
analysisResult.innerHTML = entry.analysis_result;
|
||||
if (entry.analysis.warnings.length > 0) {
|
||||
row.classList.add("warning");
|
||||
}
|
||||
|
||||
Vendored
+7
-1
@@ -1,6 +1,13 @@
|
||||
#!/bin/env bash
|
||||
|
||||
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 [ ! -d ./platform-tools ] ; then
|
||||
echo "adb not found, downloading local copy"
|
||||
@@ -12,6 +19,5 @@ else
|
||||
export ADB=`which adb`
|
||||
fi
|
||||
|
||||
export SERIAL_PATH="./serial-ubuntu-latest/serial"
|
||||
. "$(dirname "$0")"/install-common.sh
|
||||
install
|
||||
|
||||
Vendored
+7
-2
@@ -1,6 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
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 [ ! -d ./platform-tools ]; then
|
||||
echo "adb not found, downloading local copy"
|
||||
@@ -12,6 +18,5 @@ else
|
||||
export ADB=`which adb`
|
||||
fi
|
||||
|
||||
export SERIAL_PATH="./serial-macos-latest/serial"
|
||||
. "$(dirname "$0")"/install-common.sh
|
||||
install
|
||||
|
||||
@@ -7,7 +7,8 @@ use crate::{diag::MessagesContainer, gsmtap_parser};
|
||||
use super::{
|
||||
imsi_requested::ImsiRequestedAnalyzer,
|
||||
information_element::InformationElement,
|
||||
lte_downgrade::LteSib6And7DowngradeAnalyzer,
|
||||
connection_redirect_downgrade::ConnectionRedirect2GDowngradeAnalyzer,
|
||||
priority_2g_downgrade::LteSib6And7DowngradeAnalyzer,
|
||||
null_cipher::NullCipherAnalyzer,
|
||||
};
|
||||
|
||||
@@ -117,8 +118,9 @@ impl Harness {
|
||||
|
||||
pub fn new_with_all_analyzers() -> Self {
|
||||
let mut harness = Harness::new();
|
||||
harness.add_analyzer(Box::new(LteSib6And7DowngradeAnalyzer{}));
|
||||
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
|
||||
|
||||
@@ -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),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ impl Analyzer for ImsiRequestedAnalyzer {
|
||||
return None;
|
||||
};
|
||||
|
||||
// NAS identity request
|
||||
// NAS identity request, ID type IMSI
|
||||
if payload == &[0x07, 0x55, 0x01] {
|
||||
if self.packet_num < PACKET_THRESHHOLD {
|
||||
return Some(Event {
|
||||
@@ -38,7 +38,7 @@ impl Analyzer for ImsiRequestedAnalyzer {
|
||||
severity: Severity::Medium
|
||||
},
|
||||
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 \
|
||||
turned your device on, this is likely a \
|
||||
false-positive.",
|
||||
@@ -50,7 +50,7 @@ impl Analyzer for ImsiRequestedAnalyzer {
|
||||
event_type: EventType::QualitativeWarning {
|
||||
severity: Severity::High
|
||||
},
|
||||
message: format!("NAS IMSI request detected"),
|
||||
message: format!("NAS IMSI identity request detected"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
pub mod analyzer;
|
||||
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_requested;
|
||||
pub mod null_cipher;
|
||||
pub mod util;
|
||||
|
||||
@@ -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
@@ -221,9 +221,9 @@ pub enum Nas4GMessageDirection {
|
||||
// * 0xb0ec: plain EMM NAS message (incoming)
|
||||
// * 0xb0ed: plain EMM NAS message (outgoing)
|
||||
#[deku(id_pat = "0xb0e2 | 0xb0ec")]
|
||||
Inbound,
|
||||
Downlink,
|
||||
#[deku(id_pat = "0xb0e3 | 0xb0ed")]
|
||||
Outbound,
|
||||
Uplink,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
|
||||
|
||||
+8
-1
@@ -17,7 +17,7 @@ pub enum GsmtapType {
|
||||
UmtsRlcMac,
|
||||
UmtsRrc(UmtsRrcSubtype),
|
||||
LteRrc(LteRrcSubtype), /* LTE interface */
|
||||
LteMac, /* LTE MAC interface */
|
||||
LteMac, /* LTE MAC interface */
|
||||
LteMacFramed, /* LTE MAC with context hdr */
|
||||
OsmocoreLog, /* libosmocore logging */
|
||||
QcDiag, /* Qualcomm DIAG frame */
|
||||
@@ -200,6 +200,11 @@ pub struct GsmtapHeader {
|
||||
#[deku(update = "self.gsmtap_type.get_type()")]
|
||||
pub packet_type: 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 signal_dbm: i8,
|
||||
pub signal_noise_ratio_db: u8,
|
||||
@@ -222,6 +227,8 @@ impl GsmtapHeader {
|
||||
header_len: 4,
|
||||
packet_type: gsmtap_type.get_type(),
|
||||
timeslot: 0,
|
||||
pcs_band_indicator: false,
|
||||
uplink: false,
|
||||
arfcn: 0,
|
||||
signal_dbm: 0,
|
||||
signal_noise_ratio_db: 0,
|
||||
|
||||
@@ -99,7 +99,6 @@ fn log_to_gsmtap(value: LogBody) -> Result<Option<GsmtapMessage>, GsmtapParserEr
|
||||
_ => return Err(GsmtapParserError::InvalidLteRrcOtaExtHeaderVersion(ext_header_version)),
|
||||
};
|
||||
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.frame_number = packet.get_sfn();
|
||||
header.subslot = packet.get_subfn();
|
||||
@@ -108,9 +107,10 @@ fn log_to_gsmtap(value: LogBody) -> Result<Option<GsmtapMessage>, GsmtapParserEr
|
||||
payload: packet.take_payload(),
|
||||
}))
|
||||
},
|
||||
LogBody::Nas4GMessage { msg, .. } => {
|
||||
LogBody::Nas4GMessage { msg, direction, .. } => {
|
||||
// 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 {
|
||||
header,
|
||||
payload: msg,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/sh
|
||||
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 shell '/bin/rootshell -c "/etc/init.d/rayhunter_daemon restart"'
|
||||
adb shell '/bin/rootshell -c "/etc/init.d/rayhunter_daemon start"'
|
||||
|
||||
+4
-1
@@ -78,7 +78,10 @@ fn send_command<T: UsbContext>(handle: &mut DeviceHandle<T>, command: &str) {
|
||||
.read_bulk(0x82, &mut response, timeout)
|
||||
.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") {
|
||||
println!("Received unexpected response{0}", responsestr);
|
||||
std::process::exit(1);
|
||||
|
||||
Reference in New Issue
Block a user