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"
|
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"
|
||||||
|
|||||||
@@ -1,46 +1,30 @@
|
|||||||
|

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

|

|
||||||
|
|
||||||
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:
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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);
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+7
-1
@@ -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
|
||||||
|
|||||||
Vendored
+7
-2
@@ -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
|
||||||
|
|||||||
@@ -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),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
// * 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
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user