From 5019f2a9d13cda5ad5c05f1cd9576c85bbc92fc6 Mon Sep 17 00:00:00 2001 From: Simon Fondrie-Teitler Date: Fri, 27 Jun 2025 17:46:19 -0400 Subject: [PATCH 001/141] Bump Rust edition to 2024 Includes new cargo fmt changes --- bin/Cargo.toml | 2 +- bin/src/analysis.rs | 8 +++++--- bin/src/check.rs | 10 ++++++---- bin/src/daemon.rs | 14 +++++++------- bin/src/diag.rs | 8 ++++---- bin/src/display/generic_framebuffer.rs | 4 ++-- bin/src/display/orbic.rs | 2 +- bin/src/display/tplink.rs | 2 +- bin/src/display/tplink_framebuffer.rs | 2 +- bin/src/display/wingtech.rs | 2 +- bin/src/dummy_analyzer.rs | 4 +++- bin/src/pcap.rs | 4 ++-- bin/src/qmdl_store.rs | 10 ++++++---- bin/src/server.rs | 12 ++++++------ bin/src/stats.rs | 2 +- lib/Cargo.toml | 2 +- lib/src/analysis/imsi_requested.rs | 2 +- lib/src/analysis/information_element.rs | 2 +- lib/src/analysis/priority_2g_downgrade.rs | 6 ++++-- lib/src/diag_device.rs | 4 ++-- lib/src/gsmtap_parser.rs | 12 ++++++------ lib/src/pcap.rs | 2 +- lib/src/qmdl.rs | 2 +- lib/tests/test_lte_parsing.rs | 4 +++- rootshell/Cargo.toml | 2 +- telcom-parser/Cargo.toml | 2 +- telcom-parser/src/lib.rs | 2 +- telcom-parser/tests/lte_rrc_test.rs | 2 +- 28 files changed, 71 insertions(+), 59 deletions(-) diff --git a/bin/Cargo.toml b/bin/Cargo.toml index 1b72da6..4aef0bc 100644 --- a/bin/Cargo.toml +++ b/bin/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rayhunter-daemon" version = "0.4.0" -edition = "2021" +edition = "2024" [features] # These feature flags are mutually exclusive, and exactly one must be enabled. diff --git a/bin/src/analysis.rs b/bin/src/analysis.rs index 2c9543b..226e73f 100644 --- a/bin/src/analysis.rs +++ b/bin/src/analysis.rs @@ -165,9 +165,11 @@ async fn perform_analysis( .expect("failed to get QMDL file metadata") .len(); let mut qmdl_reader = QmdlReader::new(qmdl_file, Some(file_size as usize)); - let mut qmdl_stream = pin::pin!(qmdl_reader - .as_stream() - .try_filter(|container| future::ready(container.data_type == DataType::UserSpace))); + let mut qmdl_stream = pin::pin!( + qmdl_reader + .as_stream() + .try_filter(|container| future::ready(container.data_type == DataType::UserSpace)) + ); info!("Starting analysis for {name}..."); while let Some(container) = qmdl_stream diff --git a/bin/src/check.rs b/bin/src/check.rs index eb45bf4..2e4a6f6 100644 --- a/bin/src/check.rs +++ b/bin/src/check.rs @@ -9,7 +9,7 @@ use rayhunter::{ qmdl::QmdlReader, }; use std::{collections::HashMap, future, path::PathBuf, pin::pin}; -use tokio::fs::{metadata, read_dir, File}; +use tokio::fs::{File, metadata, read_dir}; mod dummy_analyzer; @@ -44,9 +44,11 @@ async fn analyze_file(enable_dummy_analyzer: bool, qmdl_path: &str, show_skipped .expect("failed to get QMDL file metadata") .len(); let mut qmdl_reader = QmdlReader::new(qmdl_file, Some(file_size as usize)); - let mut qmdl_stream = pin!(qmdl_reader - .as_stream() - .try_filter(|container| future::ready(container.data_type == DataType::UserSpace))); + let mut qmdl_stream = pin!( + qmdl_reader + .as_stream() + .try_filter(|container| future::ready(container.data_type == DataType::UserSpace)) + ); let mut skipped_reasons: HashMap = HashMap::new(); let mut total_messages = 0; let mut warnings = 0; diff --git a/bin/src/daemon.rs b/bin/src/daemon.rs index c140388..ac5aa8c 100644 --- a/bin/src/daemon.rs +++ b/bin/src/daemon.rs @@ -11,26 +11,26 @@ mod server; mod stats; use std::net::SocketAddr; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; use crate::config::{parse_args, parse_config}; use crate::diag::run_diag_read_thread; use crate::error::RayhunterError; use crate::pcap::get_pcap; use crate::qmdl_store::RecordingStore; -use crate::server::{get_config, get_qmdl, get_zip, serve_static, set_config, ServerState}; +use crate::server::{ServerState, get_config, get_qmdl, get_zip, serve_static, set_config}; use crate::stats::{get_qmdl_manifest, get_system_stats}; use analysis::{ - get_analysis_status, run_analysis_thread, start_analysis, AnalysisCtrlMessage, AnalysisStatus, + AnalysisCtrlMessage, AnalysisStatus, get_analysis_status, run_analysis_thread, start_analysis, }; +use axum::Router; use axum::response::Redirect; use axum::routing::{get, post}; -use axum::Router; use diag::{ - delete_all_recordings, delete_recording, get_analysis_report, start_recording, stop_recording, - DiagDeviceCtrlMessage, + DiagDeviceCtrlMessage, delete_all_recordings, delete_recording, get_analysis_report, + start_recording, stop_recording, }; use log::{error, info}; use qmdl_store::RecordingStoreError; @@ -38,7 +38,7 @@ use rayhunter::diag_device::DiagDevice; use tokio::net::TcpListener; use tokio::select; use tokio::sync::mpsc::{self, Sender}; -use tokio::sync::{oneshot, RwLock}; +use tokio::sync::{RwLock, oneshot}; use tokio::task::JoinHandle; use tokio_util::task::TaskTracker; diff --git a/bin/src/diag.rs b/bin/src/diag.rs index 51ea09e..0454a27 100644 --- a/bin/src/diag.rs +++ b/bin/src/diag.rs @@ -3,8 +3,8 @@ use std::sync::Arc; use axum::body::Body; use axum::extract::{Path, State}; -use axum::http::header::CONTENT_TYPE; use axum::http::StatusCode; +use axum::http::header::CONTENT_TYPE; use axum::response::{IntoResponse, Response}; use futures::{StreamExt, TryStreamExt}; use log::{debug, error, info, warn}; @@ -13,8 +13,8 @@ use rayhunter::diag::DataType; use rayhunter::diag_device::DiagDevice; use rayhunter::qmdl::QmdlWriter; use tokio::fs::File; -use tokio::sync::mpsc::{Receiver, Sender}; use tokio::sync::RwLock; +use tokio::sync::mpsc::{Receiver, Sender}; use tokio_util::io::ReaderStream; use tokio_util::task::TaskTracker; @@ -209,13 +209,13 @@ pub async fn delete_recording( return Err(( StatusCode::BAD_REQUEST, format!("no recording with name {qmdl_name}"), - )) + )); } Err(e) => { return Err(( StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't delete recording: {e}"), - )) + )); } Ok(_) => {} } diff --git a/bin/src/display/generic_framebuffer.rs b/bin/src/display/generic_framebuffer.rs index 14d50ad..132e14f 100644 --- a/bin/src/display/generic_framebuffer.rs +++ b/bin/src/display/generic_framebuffer.rs @@ -1,4 +1,4 @@ -use image::{codecs::gif::GifDecoder, imageops::FilterType, AnimationDecoder, DynamicImage}; +use image::{AnimationDecoder, DynamicImage, codecs::gif::GifDecoder, imageops::FilterType}; use std::io::Cursor; use std::time::Duration; @@ -13,7 +13,7 @@ use tokio_util::task::TaskTracker; use std::thread::sleep; -use include_dir::{include_dir, Dir}; +use include_dir::{Dir, include_dir}; #[derive(Copy, Clone)] pub struct Dimensions { diff --git a/bin/src/display/orbic.rs b/bin/src/display/orbic.rs index 474ece5..787bb93 100644 --- a/bin/src/display/orbic.rs +++ b/bin/src/display/orbic.rs @@ -1,6 +1,6 @@ use crate::config; -use crate::display::generic_framebuffer::{self, Dimensions, GenericFramebuffer}; use crate::display::DisplayState; +use crate::display::generic_framebuffer::{self, Dimensions, GenericFramebuffer}; use tokio::sync::mpsc::Receiver; use tokio::sync::oneshot; diff --git a/bin/src/display/tplink.rs b/bin/src/display/tplink.rs index 74945e8..4c330ef 100644 --- a/bin/src/display/tplink.rs +++ b/bin/src/display/tplink.rs @@ -4,7 +4,7 @@ use tokio::sync::oneshot; use tokio_util::task::TaskTracker; use crate::config; -use crate::display::{tplink_framebuffer, tplink_onebit, DisplayState}; +use crate::display::{DisplayState, tplink_framebuffer, tplink_onebit}; use std::fs; diff --git a/bin/src/display/tplink_framebuffer.rs b/bin/src/display/tplink_framebuffer.rs index 2475e8e..95255eb 100644 --- a/bin/src/display/tplink_framebuffer.rs +++ b/bin/src/display/tplink_framebuffer.rs @@ -3,8 +3,8 @@ use std::io::Write; use std::os::fd::AsRawFd; use crate::config; -use crate::display::generic_framebuffer::{self, Dimensions, GenericFramebuffer}; use crate::display::DisplayState; +use crate::display::generic_framebuffer::{self, Dimensions, GenericFramebuffer}; use tokio::sync::mpsc::Receiver; use tokio::sync::oneshot; diff --git a/bin/src/display/wingtech.rs b/bin/src/display/wingtech.rs index be228cb..10fb54c 100644 --- a/bin/src/display/wingtech.rs +++ b/bin/src/display/wingtech.rs @@ -5,8 +5,8 @@ /// WT_PRODUCTION_VERSION=CT2MHS01_0.04.55 /// WT_HARDWARE_VERSION=89323_1_20 use crate::config; -use crate::display::generic_framebuffer::{self, Dimensions, GenericFramebuffer}; use crate::display::DisplayState; +use crate::display::generic_framebuffer::{self, Dimensions, GenericFramebuffer}; use tokio::sync::mpsc::Receiver; use tokio::sync::oneshot; diff --git a/bin/src/dummy_analyzer.rs b/bin/src/dummy_analyzer.rs index 66876b7..8b4ddc8 100644 --- a/bin/src/dummy_analyzer.rs +++ b/bin/src/dummy_analyzer.rs @@ -15,7 +15,9 @@ impl Analyzer for TestAnalyzer { } fn get_description(&self) -> Cow { - Cow::from("Always returns true, if you are seeing this you are either a developer or you are about to have problems.") + Cow::from( + "Always returns true, if you are seeing this you are either a developer or you are about to have problems.", + ) } fn analyze_information_element(&mut self, ie: &InformationElement) -> Option { diff --git a/bin/src/pcap.rs b/bin/src/pcap.rs index 1911aee..fde865c 100644 --- a/bin/src/pcap.rs +++ b/bin/src/pcap.rs @@ -3,8 +3,8 @@ use crate::ServerState; use anyhow::Error; use axum::body::Body; use axum::extract::{Path, State}; -use axum::http::header::CONTENT_TYPE; use axum::http::StatusCode; +use axum::http::header::CONTENT_TYPE; use axum::response::{IntoResponse, Response}; use log::error; use rayhunter::diag::DataType; @@ -12,7 +12,7 @@ use rayhunter::gsmtap_parser; use rayhunter::pcap::GsmtapPcapWriter; use rayhunter::qmdl::QmdlReader; use std::sync::Arc; -use tokio::io::{duplex, AsyncRead, AsyncWrite}; +use tokio::io::{AsyncRead, AsyncWrite, duplex}; use tokio_util::io::ReaderStream; // Streams a pcap file chunk-by-chunk to the client by reading the QMDL data diff --git a/bin/src/qmdl_store.rs b/bin/src/qmdl_store.rs index f04550d..389ebf8 100644 --- a/bin/src/qmdl_store.rs +++ b/bin/src/qmdl_store.rs @@ -6,7 +6,7 @@ use rayhunter::util::RuntimeMetadata; use serde::{Deserialize, Serialize}; use thiserror::Error; use tokio::{ - fs::{self, try_exists, File, OpenOptions}, + fs::{self, File, OpenOptions, try_exists}, io::AsyncWriteExt, }; @@ -369,9 +369,11 @@ mod tests { RecordingStore::read_manifest(dir.path()).await.unwrap(), store.manifest ); - assert!(store.manifest.entries[entry_index] - .last_message_time - .is_none()); + assert!( + store.manifest.entries[entry_index] + .last_message_time + .is_none() + ); store .update_entry_qmdl_size(entry_index, 1000) diff --git a/bin/src/server.rs b/bin/src/server.rs index edf16a9..179e688 100644 --- a/bin/src/server.rs +++ b/bin/src/server.rs @@ -1,21 +1,21 @@ use anyhow::Error; -use async_zip::tokio::write::ZipFileWriter; use async_zip::Compression; use async_zip::ZipEntryBuilder; +use async_zip::tokio::write::ZipFileWriter; +use axum::Json; use axum::body::Body; use axum::extract::Path; use axum::extract::State; use axum::http::header::{self, CONTENT_LENGTH, CONTENT_TYPE}; use axum::http::{HeaderValue, StatusCode}; use axum::response::{IntoResponse, Response}; -use axum::Json; -use include_dir::{include_dir, Dir}; +use include_dir::{Dir, include_dir}; use log::error; use std::sync::Arc; use tokio::fs::write; -use tokio::io::{copy, duplex, AsyncReadExt}; +use tokio::io::{AsyncReadExt, copy, duplex}; use tokio::sync::mpsc::Sender; -use tokio::sync::{oneshot, RwLock}; +use tokio::sync::{RwLock, oneshot}; use tokio_util::compat::FuturesAsyncWriteCompatExt; use tokio_util::io::ReaderStream; @@ -23,7 +23,7 @@ use crate::analysis::{AnalysisCtrlMessage, AnalysisStatus}; use crate::config::Config; use crate::pcap::generate_pcap_data; use crate::qmdl_store::RecordingStore; -use crate::{display, DiagDeviceCtrlMessage}; +use crate::{DiagDeviceCtrlMessage, display}; pub struct ServerState { pub config_path: String, diff --git a/bin/src/stats.rs b/bin/src/stats.rs index 6867e62..6fdcfb4 100644 --- a/bin/src/stats.rs +++ b/bin/src/stats.rs @@ -3,9 +3,9 @@ use std::sync::Arc; use crate::qmdl_store::ManifestEntry; use crate::server::ServerState; +use axum::Json; use axum::extract::State; use axum::http::StatusCode; -use axum::Json; use log::error; use rayhunter::util::RuntimeMetadata; use serde::Serialize; diff --git a/lib/Cargo.toml b/lib/Cargo.toml index a50eee2..d333cd5 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rayhunter" version = "0.4.0" -edition = "2021" +edition = "2024" description = "Realtime cellular data decoding and analysis for IMSI catcher detection" diff --git a/lib/src/analysis/imsi_requested.rs b/lib/src/analysis/imsi_requested.rs index 90c01f2..3861687 100644 --- a/lib/src/analysis/imsi_requested.rs +++ b/lib/src/analysis/imsi_requested.rs @@ -1,8 +1,8 @@ use std::borrow::Cow; +use pycrate_rs::nas::NASMessage; use pycrate_rs::nas::emm::EMMMessage; use pycrate_rs::nas::generated::emm::emm_identity_request::IDTypeV; -use pycrate_rs::nas::NASMessage; use super::analyzer::{Analyzer, Event, EventType, Severity}; use super::information_element::{InformationElement, LteInformationElement}; diff --git a/lib/src/analysis/information_element.rs b/lib/src/analysis/information_element.rs index f885c8d..fdd7ae7 100644 --- a/lib/src/analysis/information_element.rs +++ b/lib/src/analysis/information_element.rs @@ -86,7 +86,7 @@ impl TryFrom<&GsmtapMessage> for InformationElement { _ => { return Err(InformationElementError::UnsupportedGsmtapType( gsmtap_msg.header.gsmtap_type, - )) + )); } }; Ok(InformationElement::LTE(Box::new(lte))) diff --git a/lib/src/analysis/priority_2g_downgrade.rs b/lib/src/analysis/priority_2g_downgrade.rs index b408a53..2c36525 100644 --- a/lib/src/analysis/priority_2g_downgrade.rs +++ b/lib/src/analysis/priority_2g_downgrade.rs @@ -4,8 +4,8 @@ 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, + SystemInformationBlockType7, SystemInformationCriticalExtensions, }; /// Based on heuristic T7 from Shinjo Park's "Why We Cannot Win". @@ -41,7 +41,9 @@ impl Analyzer for LteSib6And7DowngradeAnalyzer { } fn get_description(&self) -> Cow { - Cow::from("Tests for LTE cells broadcasting a SIB type 6 and 7 which include 2G/3G frequencies with higher priorities.") + Cow::from( + "Tests for LTE cells broadcasting a SIB type 6 and 7 which include 2G/3G frequencies with higher priorities.", + ) } fn analyze_information_element( diff --git a/lib/src/diag_device.rs b/lib/src/diag_device.rs index 5f2d080..67686b3 100644 --- a/lib/src/diag_device.rs +++ b/lib/src/diag_device.rs @@ -1,6 +1,6 @@ use crate::diag::{ - build_log_mask_request, DataType, DiagParsingError, LogConfigRequest, LogConfigResponse, - Message, MessagesContainer, Request, RequestContainer, ResponsePayload, CRC_CCITT, + CRC_CCITT, DataType, DiagParsingError, LogConfigRequest, LogConfigResponse, Message, + MessagesContainer, Request, RequestContainer, ResponsePayload, build_log_mask_request, }; use crate::hdlc::hdlc_encapsulate; use crate::log_codes; diff --git a/lib/src/gsmtap_parser.rs b/lib/src/gsmtap_parser.rs index 2eb049b..b258626 100644 --- a/lib/src/gsmtap_parser.rs +++ b/lib/src/gsmtap_parser.rs @@ -47,7 +47,7 @@ fn log_to_gsmtap(value: LogBody) -> Result, GsmtapParserEr return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum( ext_header_version, pdu, - )) + )); } }, 0x09 | 0x0c => match packet.get_pdu_num() { @@ -63,7 +63,7 @@ fn log_to_gsmtap(value: LogBody) -> Result, GsmtapParserEr return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum( ext_header_version, pdu, - )) + )); } }, 0x0e..=0x10 => match packet.get_pdu_num() { @@ -79,7 +79,7 @@ fn log_to_gsmtap(value: LogBody) -> Result, GsmtapParserEr return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum( ext_header_version, pdu, - )) + )); } }, 0x13 | 0x1a | 0x1b => match packet.get_pdu_num() { @@ -102,7 +102,7 @@ fn log_to_gsmtap(value: LogBody) -> Result, GsmtapParserEr return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum( ext_header_version, pdu, - )) + )); } }, 0x14 | 0x18 | 0x19 => match packet.get_pdu_num() { @@ -125,13 +125,13 @@ fn log_to_gsmtap(value: LogBody) -> Result, GsmtapParserEr return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum( ext_header_version, pdu, - )) + )); } }, _ => { return Err(GsmtapParserError::InvalidLteRrcOtaExtHeaderVersion( ext_header_version, - )) + )); } }; let mut header = GsmtapHeader::new(gsmtap_type); diff --git a/lib/src/pcap.rs b/lib/src/pcap.rs index 3e0d59d..7d6f1d9 100644 --- a/lib/src/pcap.rs +++ b/lib/src/pcap.rs @@ -5,10 +5,10 @@ use crate::gsmtap::GsmtapMessage; use chrono::prelude::*; use deku::prelude::*; +use pcap_file_tokio::pcapng::PcapNgWriter; use pcap_file_tokio::pcapng::blocks::enhanced_packet::EnhancedPacketBlock; use pcap_file_tokio::pcapng::blocks::interface_description::InterfaceDescriptionBlock; use pcap_file_tokio::pcapng::blocks::section_header::{SectionHeaderBlock, SectionHeaderOption}; -use pcap_file_tokio::pcapng::PcapNgWriter; use pcap_file_tokio::{Endianness, PcapError}; use std::borrow::Cow; use thiserror::Error; diff --git a/lib/src/qmdl.rs b/lib/src/qmdl.rs index 0e89d62..386dee2 100644 --- a/lib/src/qmdl.rs +++ b/lib/src/qmdl.rs @@ -3,7 +3,7 @@ //! QmdlReader and QmdlWriter can read and write MessagesContainers to and from //! QMDL files. -use crate::diag::{DataType, HdlcEncapsulatedMessage, MessagesContainer, MESSAGE_TERMINATOR}; +use crate::diag::{DataType, HdlcEncapsulatedMessage, MESSAGE_TERMINATOR, MessagesContainer}; use futures::TryStream; use log::error; diff --git a/lib/tests/test_lte_parsing.rs b/lib/tests/test_lte_parsing.rs index f1e7a15..85ba292 100644 --- a/lib/tests/test_lte_parsing.rs +++ b/lib/tests/test_lte_parsing.rs @@ -133,7 +133,9 @@ fn test_lte_rrc_ota() { let (_, gsmtap_msg) = gsmtap_parser::parse(parsed).unwrap().unwrap(); assert_eq!( &gsmtap_msg.payload, - &[0x40, 0x85, 0x8e, 0xc4, 0xe5, 0xbf, 0xe0, 0x50, 0xdc, 0x29, 0x15, 0x16, 0x00,] + &[ + 0x40, 0x85, 0x8e, 0xc4, 0xe5, 0xbf, 0xe0, 0x50, 0xdc, 0x29, 0x15, 0x16, 0x00, + ] ); assert_eq!(gsmtap_msg.header.packet_type, 13); assert_eq!(gsmtap_msg.header.timeslot, 0); diff --git a/rootshell/Cargo.toml b/rootshell/Cargo.toml index 6703ba4..6b27ab6 100644 --- a/rootshell/Cargo.toml +++ b/rootshell/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rootshell" version = "0.4.0" -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/telcom-parser/Cargo.toml b/telcom-parser/Cargo.toml index 5c99a41..d3ea146 100644 --- a/telcom-parser/Cargo.toml +++ b/telcom-parser/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "telcom-parser" version = "0.4.0" -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/telcom-parser/src/lib.rs b/telcom-parser/src/lib.rs index 8fc1f27..b58ae02 100644 --- a/telcom-parser/src/lib.rs +++ b/telcom-parser/src/lib.rs @@ -1,4 +1,4 @@ -use asn1_codecs::{uper::UperCodec, PerCodecData, PerCodecError}; +use asn1_codecs::{PerCodecData, PerCodecError, uper::UperCodec}; use thiserror::Error; #[allow(warnings, unused, unreachable_patterns, non_camel_case_types)] pub mod lte_rrc; diff --git a/telcom-parser/tests/lte_rrc_test.rs b/telcom-parser/tests/lte_rrc_test.rs index c475eb5..e2340f1 100644 --- a/telcom-parser/tests/lte_rrc_test.rs +++ b/telcom-parser/tests/lte_rrc_test.rs @@ -1,4 +1,4 @@ -use asn1_codecs::{uper::UperCodec, PerCodecData}; +use asn1_codecs::{PerCodecData, uper::UperCodec}; use telcom_parser::lte_rrc::BCCH_DL_SCH_Message; fn hex_to_bin(hex: &str) -> Vec { From 55178e60fdb9866c0135ce2c3097424fd5659112 Mon Sep 17 00:00:00 2001 From: oopsbagel Date: Sat, 28 Jun 2025 00:40:40 -0700 Subject: [PATCH 002/141] cargo/config: strip debuginfo from release bins rustc -C strip=debuginfo leaves the symbol table intact, meaning RUST_BACKTRACE=1 on the installer still produces helpful output. This significantly reduces the binary size, eg the amd64 installer goes from 93M to 21M. Stripping the symbol table only reclaims a further ~2M. --- .cargo/config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 16ae724..c63a35f 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -28,6 +28,7 @@ rustflags = ["-C", "target-feature=+crt-static"] # keep line numbers in stack traces for non-firmware binaries [profile.release] debug = "limited" +strip = "debuginfo" # optimizations to reduce the binary size of firmware binaries [profile.firmware] @@ -38,4 +39,3 @@ lto = true codegen-units = 1 panic = "abort" debug = false - From a4f4e12a57871ff421a92eba3190d83b38e869da Mon Sep 17 00:00:00 2001 From: oopsbagel Date: Sat, 28 Jun 2025 00:54:44 -0700 Subject: [PATCH 003/141] ci: build everything when .cargo changes --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 62daa2f..21206c7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,7 +36,7 @@ jobs: lcommit=${{ github.event.pull_request.base.sha || 'origin/main' }} # If we are on main, or if these workflow files are being changed, run everything - if [ ${{ github.ref }} = 'refs/heads/main' ] || git diff --name-only $lcommit..HEAD | grep -qe ^.github/workflows/ + if [ ${{ github.ref }} = 'refs/heads/main' ] || git diff --name-only $lcommit..HEAD | grep -qe ^.github/workflows/ -e ^.cargo then echo "building everything" echo code_count=forced >> "$GITHUB_OUTPUT" From 1ee35dad710ec2dd05d2137070c01774a9b34169 Mon Sep 17 00:00:00 2001 From: oopsbagel Date: Sat, 28 Jun 2025 05:19:52 -0700 Subject: [PATCH 004/141] cargo/config: build release binaries with fat lto Reduce installer binary size with link-time optimisation. --- .cargo/config.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index c63a35f..e0ac604 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -25,9 +25,10 @@ rustflags = ["-C", "target-feature=+crt-static"] linker = "rust-lld" rustflags = ["-C", "target-feature=+crt-static"] -# keep line numbers in stack traces for non-firmware binaries [profile.release] +# keep line numbers in stack traces for non-firmware binaries debug = "limited" +lto = "fat" strip = "debuginfo" # optimizations to reduce the binary size of firmware binaries @@ -35,7 +36,7 @@ strip = "debuginfo" inherits = "release" strip = true opt-level = "z" -lto = true +lto = "fat" codegen-units = 1 panic = "abort" debug = false From 4d0427fe68b8e446331d3d909c0ad02e9ccbd22c Mon Sep 17 00:00:00 2001 From: oopsbagel Date: Sat, 28 Jun 2025 05:26:59 -0700 Subject: [PATCH 005/141] installer: fewer tokio and axum crate features --- Cargo.lock | 39 --------------------------------------- installer/Cargo.toml | 4 ++-- 2 files changed, 2 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1680d4e..db7777d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -309,7 +309,6 @@ checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" dependencies = [ "axum-core", "bytes", - "form_urlencoded", "futures-util", "http", "http-body", @@ -326,13 +325,11 @@ dependencies = [ "serde", "serde_json", "serde_path_to_error", - "serde_urlencoded", "sync_wrapper", "tokio", "tower", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -352,7 +349,6 @@ dependencies = [ "sync_wrapper", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -1990,29 +1986,6 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - [[package]] name = "paste" version = "1.0.15" @@ -2450,15 +2423,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "redox_syscall" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" -dependencies = [ - "bitflags 2.9.1", -] - [[package]] name = "regex" version = "1.11.1" @@ -3042,7 +3006,6 @@ dependencies = [ "bytes", "libc", "mio", - "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", @@ -3168,7 +3131,6 @@ dependencies = [ "tokio", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -3189,7 +3151,6 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ - "log", "pin-project-lite", "tracing-core", ] diff --git a/installer/Cargo.toml b/installer/Cargo.toml index 7783dac..3bfb9ae 100644 --- a/installer/Cargo.toml +++ b/installer/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [dependencies] aes = "0.8.4" anyhow = "1.0.98" -axum = "0.8.3" +axum = { version = "0.8.3", features = ["http1", "tokio"], default-features = false } base64_light = "0.1.5" block-padding = "0.3.3" bytes = "1.10.1" @@ -19,7 +19,7 @@ nusb = "0.1.13" reqwest = { version = "0.12.15", features = ["json"], default-features = false } serde = { version = "1.0.219", features = ["derive"] } sha2 = "0.10.8" -tokio = { version = "1.44.2", features = ["full"] } +tokio = { version = "1.44.2", features = ["io-util", "macros", "rt-multi-thread"], default-features = false } tokio-retry2 = "0.5.7" tokio-stream = "0.1.17" From 6efe83b36da25d4b5a9f2185fd1457ad575d0c8f Mon Sep 17 00:00:00 2001 From: oopsbagel Date: Sat, 28 Jun 2025 14:40:12 -0700 Subject: [PATCH 006/141] cargo/config: build release bins with opt-level z This yields a smaller binary and faster compile times than the default. cf 5.6M binary in 2m 12s vs. 4.7M in 1m 39s on my machine. --- .cargo/config.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/.cargo/config.toml b/.cargo/config.toml index e0ac604..39edc90 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -29,6 +29,7 @@ rustflags = ["-C", "target-feature=+crt-static"] # keep line numbers in stack traces for non-firmware binaries debug = "limited" lto = "fat" +opt-level = "z" strip = "debuginfo" # optimizations to reduce the binary size of firmware binaries From 28ead37111223222b0bc4c732decdbd752f95914 Mon Sep 17 00:00:00 2001 From: oopsbagel Date: Sat, 28 Jun 2025 15:25:15 -0700 Subject: [PATCH 007/141] cargo/config: drop inherited firmware profile opts These options are shared with the release profile. --- .cargo/config.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 39edc90..4ed54d3 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -36,8 +36,6 @@ strip = "debuginfo" [profile.firmware] inherits = "release" strip = true -opt-level = "z" -lto = "fat" codegen-units = 1 panic = "abort" debug = false From c3fd724ac108d55871b64e8e4718e4917dad2c16 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Tue, 24 Jun 2025 19:50:50 +0200 Subject: [PATCH 008/141] doc: Give guidance on which device to get --- doc/supported-devices.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/doc/supported-devices.md b/doc/supported-devices.md index 1be8dc9..682e837 100644 --- a/doc/supported-devices.md +++ b/doc/supported-devices.md @@ -1,15 +1,19 @@ # Supported devices -Rayhunter was built and tested primarily on the Orbic RC400L mobile hotspot, but the community has been working hard at adding support for other devices. Theoretically, if a device runs a Qualcomm modem and exposes a `/dev/diag` interface, Rayhunter may work on it. +## Recommended devices -If you have a device in mind which you'd like Rayhunter to support, please [open a discussion on our Github](https://github.com/EFForg/rayhunter/discussions)! - -## Tier 1 - Fully supported devices These devices have been extensively tested by the core developers and are widely used. We recommend running Rayhunter on one of these devices if you can. + - [Orbic RC400L](./orbic.md) - [TP-Link M7350](./tplink-m7350.md) -- [TP-Link M7310](./tplink-m7310.md) -## Tier 2 - Functional devices -Some developers have succeeded in getting Rayhunter running on these devices, but they have not received the same level of adoption as tier 1 devices. +If you are in the US, use the Orbic. TP-Link M7350 also works but is usually more expensive to obtain. Otherwise use TP-Link M7350. + +## Other devices + +These devices are also supported but less commonly used. We do not recommend getting them for this purpose unless you already have one. + +- [TP-Link M7310](./tplink-m7310.md) - [Wingtech CT2MHS01](./wingtech-ct2mhs01.md) + +Theoretically, if a device can be rooted, runs a Qualcomm modem and exposes a `/dev/diag` interface, Rayhunter may work on it. If you have a device in mind which you'd like Rayhunter to support, please [open a discussion on our Github](https://github.com/EFForg/rayhunter/discussions)! From ef7b8129efe32520b9141b9acb5533218eb3e2e0 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Tue, 24 Jun 2025 19:57:46 +0200 Subject: [PATCH 009/141] Fix version number in docs, 0.3.5 does not exist --- doc/tplink-m7310.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/tplink-m7310.md b/doc/tplink-m7310.md index 70946e2..2b7c369 100644 --- a/doc/tplink-m7310.md +++ b/doc/tplink-m7310.md @@ -1,6 +1,6 @@ # TP-Link M7310 -The TP-Link M7310 is **supported by Rayhunter since 0.3.5**. The device +The TP-Link M7310 is **supported by Rayhunter since 0.4.0**. The device works similarly to the [M7350](./tplink-m7350.md) and is essentially an older, more expensive version of it. Hardware version v1.0 has been successfully tested, later versions may work as well. From 8b44f604ea66ecfbc4203e689feb9f8f2f360ab3 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Thu, 26 Jun 2025 22:02:48 +0200 Subject: [PATCH 010/141] sort orbic to the top --- doc/SUMMARY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/SUMMARY.md b/doc/SUMMARY.md index f7de5e9..52c4a12 100644 --- a/doc/SUMMARY.md +++ b/doc/SUMMARY.md @@ -12,9 +12,9 @@ - [Rayhunter's heuristics](./heuristics.md) - [How we analyze a capture](./analyzing-a-capture.md) - [Supported devices](./supported-devices.md) + - [Orbic RC400L](./orbic.md) - [TP-Link M7350](./tplink-m7350.md) - [TP-Link M7310](./tplink-m7310.md) - - [Orbic RC400L](./orbic.md) - [Wingtech CT2MHS01](./wingtech-ct2mhs01.md) - [Support, feedback, and community](./support-feedback-community.md) - [Frequently Asked Questions](./faq.md) From bd074066c57c2deda6386ed5873b5499fd4721e9 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 29 Jun 2025 00:37:43 +0200 Subject: [PATCH 011/141] Adjust language around supported versions, to be similar across pages --- doc/tplink-m7310.md | 12 ++++++++---- doc/tplink-m7350.md | 4 +++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/doc/tplink-m7310.md b/doc/tplink-m7310.md index 2b7c369..7a46774 100644 --- a/doc/tplink-m7310.md +++ b/doc/tplink-m7310.md @@ -1,6 +1,10 @@ # TP-Link M7310 -The TP-Link M7310 is **supported by Rayhunter since 0.4.0**. The device -works similarly to the [M7350](./tplink-m7350.md) and is essentially an older, -more expensive version of it. Hardware version v1.0 has been successfully -tested, later versions may work as well. +Supported in Rayhunter since version 0.4.0. + +The TP-Link M7310 works similarly to the [M7350](./tplink-m7350.md) and is +essentially an older, more expensive version of it. The installation procedure +is identical, `./installer tplink`. + +Hardware version v1.0 has been successfully tested, later versions may work as +well. diff --git a/doc/tplink-m7350.md b/doc/tplink-m7350.md index a5e509d..41d1202 100644 --- a/doc/tplink-m7350.md +++ b/doc/tplink-m7350.md @@ -1,6 +1,8 @@ # TP-Link M7350 -The TP-Link M7350 is **supported by Rayhunter since 0.3.0**. TP-Link M7350 supports many more frequency bands than Orbic and therefore works in Europe and also in some Asian and African countries. +Supported in Rayhunter since version 0.3.0. + +The TP-Link M7350 supports many more frequency bands than Orbic and therefore works in Europe and also in some Asian and African countries. ## Hardware versions From 3889c89b5ab5d7f56819e2eb7f612c8ce6d38296 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 29 Jun 2025 00:51:01 +0200 Subject: [PATCH 012/141] Fix autolinks --- doc/installing-from-release-windows.md | 2 +- doc/tplink-m7350.md | 2 +- doc/using-rayhunter.md | 6 +++--- doc/wingtech-ct2mhs01.md | 9 +++++---- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/doc/installing-from-release-windows.md b/doc/installing-from-release-windows.md index af26a14..62bf042 100644 --- a/doc/installing-from-release-windows.md +++ b/doc/installing-from-release-windows.md @@ -4,7 +4,7 @@ Windows support in Rayhunter's installer is a work-in-progress. Depending on the ## TP-Link -1. Connect the device via WiFi or USB Tethering -- you should be able to view the TP-Link admin page on [http://192.168.0.1](http://192.168.0.1). +1. Connect the device via WiFi or USB Tethering -- you should be able to view the TP-Link admin page on . 2. Download the latest release (must be at least 0.3.0), and unpack the zipfile. 3. Open PowerShell or CMD in that extracted folder, and run the binary appropriate for your operating system: `./installer-windows-x86_64/installer tplink` 4. Follow the instructions on the screen, if there are any. diff --git a/doc/tplink-m7350.md b/doc/tplink-m7350.md index 41d1202..a91f6f0 100644 --- a/doc/tplink-m7350.md +++ b/doc/tplink-m7350.md @@ -28,7 +28,7 @@ You can get your TP-Link M7350 from: ## Installation & Usage -Follow the [release installation guide](./installing-from-release.md). Substitute `./installer orbic` for `./installer tplink` in other documentation. The Rayhunter UI will be available at [http://192.168.0.1:8080](http://192.168.0.1:8080). +Follow the [release installation guide](./installing-from-release.md). Substitute `./installer orbic` for `./installer tplink` in other documentation. The Rayhunter UI will be available at . ## Obtaining a shell diff --git a/doc/using-rayhunter.md b/doc/using-rayhunter.md index 60f4750..3ce54e2 100644 --- a/doc/using-rayhunter.md +++ b/doc/using-rayhunter.md @@ -11,15 +11,15 @@ It also serves a web UI that provides some basic controls, such as being able to You can access this UI in one of two ways: * **Connect over WiFi:** Connect your phone/laptop to your device's WiFi - network and visit [http://192.168.1.1:8080](http://192.168.1.1:8080) (orbic) - or [http://192.168.0.1:8080](http://192.168.0.1:8080) (tplink). + network and visit (orbic) + or (tplink). Click past your browser warning you about the connection not being secure, Rayhunter doesn't have HTTPS yet. On the **Orbic**, you can find the WiFi network password by going to the Orbic's menu > 2.4 GHz WIFI Info > Enter > find the 8-character password next to the lock 🔒 icon. On the **TP-Link**, you can find the WiFi network password by going to the TP-Link's menu > Advanced > Wireless > Basic Settings. -* **Connect over USB (Orbic):** Connect your device to your laptop via USB. Run `adb forward tcp:8080 tcp:8080`, then visit [http://localhost:8080](http://localhost:8080). +* **Connect over USB (Orbic):** Connect your device to your laptop via USB. Run `adb forward tcp:8080 tcp:8080`, then visit . * For this you will need to install the Android Debug Bridge (ADB) on your computer, you can copy the version that was downloaded inside the `releases/platform-tools/` folder to somewhere else in your path or you can install it manually. * You can find instructions for doing so on your platform [here](https://www.xda-developers.com/install-adb-windows-macos-linux/#how-to-set-up-adb-on-your-computer), (don't worry about instructions for installing it on a phone/device yet). * On MacOS, the easiest way to install ADB is with Homebrew: First [install Homebrew](https://brew.sh/), then run `brew install android-platform-tools`. diff --git a/doc/wingtech-ct2mhs01.md b/doc/wingtech-ct2mhs01.md index 00dc9ef..ab22012 100644 --- a/doc/wingtech-ct2mhs01.md +++ b/doc/wingtech-ct2mhs01.md @@ -5,11 +5,12 @@ Supported in Rayhunter since version 0.4.0. The Wingtech CT2MHS01 hotspot is a Qualcomm mdm9650-based device with a screen available for US$15-35. This device is often used as a base platform for white labeled versions like the T-Mobile TMOHS1. AT&T branded versions of the hotspot seem to be the most abundant. ## Hardware + Wingtechs are abundant on ebay and can also be found on Amazon: -- https://www.amazon.com/AT-Turbo-Hotspot-256-Black/dp/B09YWLXVWT -- https://www.ebay.com/itm/135205906535 -- https://www.ebay.com/itm/126987839936 -- https://www.ebay.com/itm/127147132518 +- +- +- +- Rayhunter has been tested on From e259417f35b8770c11efbbb9eb2d8d13b274776e Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 29 Jun 2025 01:57:28 +0200 Subject: [PATCH 013/141] Revert some of the changes in supported-devices index --- doc/supported-devices.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/doc/supported-devices.md b/doc/supported-devices.md index 682e837..1dd24dd 100644 --- a/doc/supported-devices.md +++ b/doc/supported-devices.md @@ -1,19 +1,17 @@ # Supported devices -## Recommended devices +Rayhunter was built and tested primarily on the Orbic RC400L mobile hotspot, but the community has been working hard at adding support for other devices. Theoretically, if a device runs a Qualcomm modem and exposes a `/dev/diag` interface, Rayhunter may work on it. +If you have a device in mind which you'd like Rayhunter to support, please [open a discussion on our Github](https://github.com/EFForg/rayhunter/discussions)! + +## Tier 1 - Fully supported devices These devices have been extensively tested by the core developers and are widely used. We recommend running Rayhunter on one of these devices if you can. - - [Orbic RC400L](./orbic.md) - [TP-Link M7350](./tplink-m7350.md) If you are in the US, use the Orbic. TP-Link M7350 also works but is usually more expensive to obtain. Otherwise use TP-Link M7350. -## Other devices - -These devices are also supported but less commonly used. We do not recommend getting them for this purpose unless you already have one. - -- [TP-Link M7310](./tplink-m7310.md) +## Tier 2 - Functional devices +Some developers have succeeded in getting Rayhunter running on these devices, but they have not received the same level of adoption as tier 1 devices. - [Wingtech CT2MHS01](./wingtech-ct2mhs01.md) - -Theoretically, if a device can be rooted, runs a Qualcomm modem and exposes a `/dev/diag` interface, Rayhunter may work on it. If you have a device in mind which you'd like Rayhunter to support, please [open a discussion on our Github](https://github.com/EFForg/rayhunter/discussions)! +- [TP-Link M7310](./tplink-m7310.md) From d03debe67c53083ab9c0fd4e0953eba40a98e048 Mon Sep 17 00:00:00 2001 From: oopsbagel Date: Sun, 29 Jun 2025 02:05:58 -0700 Subject: [PATCH 014/141] doc: add region table, map for recommended devices Implement reordering from untitaker's suggested changes. --- doc/device_regions.svg | 14708 +++++++++++++++++++++++++++++++++++++ doc/supported-devices.md | 36 +- 2 files changed, 14732 insertions(+), 12 deletions(-) create mode 100644 doc/device_regions.svg diff --git a/doc/device_regions.svg b/doc/device_regions.svg new file mode 100644 index 0000000..9ab9d82 --- /dev/null +++ b/doc/device_regions.svg @@ -0,0 +1,14708 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A + B + C + C + C + +Orbic RC400LTP-Link M7350 diff --git a/doc/supported-devices.md b/doc/supported-devices.md index 1dd24dd..f7ae676 100644 --- a/doc/supported-devices.md +++ b/doc/supported-devices.md @@ -1,17 +1,29 @@ # Supported devices +Be sure to check your location's [supported frequencies](https://www.frequencycheck.com/) against a device page before obtaining a device. + +## 1. Recommended devices +These devices have been extensively tested by the core developers and are widely used. **Use one of these devices if you can.** + +| Device | Region | +| ------ | ------ | +| [Orbic RC400L](./orbic.md) | Americas | +| [TP-Link M7350](./tplink-m7350.md) | Africa, Europe, Middle East | + +The TP-Link M7350 also works in the Americas but is usually more expensive. + +![device_regions](device_regions.svg) +_Derivative work of [this file](https://commons.wikimedia.org/wiki/File:International_Telecommunication_Union_regions_with_dividing_lines.svg) by [Maximillian Dörrbecker](https://de.wikipedia.org/wiki/User:Chumwa) licensed [CC BY-SA 2.5](https://creativecommons.org/licenses/by-sa/2.5)_ + +## 2. Functional devices +Rayhunter is confirmed to work on these devices. + +| Device | Region | +| ------ | ------ | +| [Wingtech CT2MHS01](./wingtech-ct2mhs01.md) | Americas | +| [TP-Link M7310](./tplink-m7310.md) | Africa, Europe, Middle East & Americas | + +## Adding new devices Rayhunter was built and tested primarily on the Orbic RC400L mobile hotspot, but the community has been working hard at adding support for other devices. Theoretically, if a device runs a Qualcomm modem and exposes a `/dev/diag` interface, Rayhunter may work on it. If you have a device in mind which you'd like Rayhunter to support, please [open a discussion on our Github](https://github.com/EFForg/rayhunter/discussions)! - -## Tier 1 - Fully supported devices -These devices have been extensively tested by the core developers and are widely used. We recommend running Rayhunter on one of these devices if you can. -- [Orbic RC400L](./orbic.md) -- [TP-Link M7350](./tplink-m7350.md) - -If you are in the US, use the Orbic. TP-Link M7350 also works but is usually more expensive to obtain. Otherwise use TP-Link M7350. - -## Tier 2 - Functional devices -Some developers have succeeded in getting Rayhunter running on these devices, but they have not received the same level of adoption as tier 1 devices. -- [Wingtech CT2MHS01](./wingtech-ct2mhs01.md) -- [TP-Link M7310](./tplink-m7310.md) From 664ffc8c758a7013e11377938de7e59d81a74c48 Mon Sep 17 00:00:00 2001 From: oopsbagel Date: Sun, 29 Jun 2025 02:07:22 -0700 Subject: [PATCH 015/141] doc: move wingtech developer details to the bottom --- doc/wingtech-ct2mhs01.md | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/doc/wingtech-ct2mhs01.md b/doc/wingtech-ct2mhs01.md index ab22012..efe270a 100644 --- a/doc/wingtech-ct2mhs01.md +++ b/doc/wingtech-ct2mhs01.md @@ -4,24 +4,6 @@ Supported in Rayhunter since version 0.4.0. The Wingtech CT2MHS01 hotspot is a Qualcomm mdm9650-based device with a screen available for US$15-35. This device is often used as a base platform for white labeled versions like the T-Mobile TMOHS1. AT&T branded versions of the hotspot seem to be the most abundant. -## Hardware - -Wingtechs are abundant on ebay and can also be found on Amazon: -- -- -- -- - -Rayhunter has been tested on - -```sh -WT_INNER_VERSION=SW_Q89323AA1_V057_M10_CRICKET_USR_MP -WT_PRODUCTION_VERSION=CT2MHS01_0.04.55 -WT_HARDWARE_VERSION=89323_1_20 -``` - -Please consider sharing the contents of your device's /etc/wt_version file here. - ## Supported bands There are likely variants of the device for all three ITU regions. @@ -39,6 +21,13 @@ According to FCC ID 2APXW-CT2MHS01 Test Report No. [I20N02441-RF-LTE](https://ap Note that Band 5 (850 MHz, CLR) is suitable for roaming in ITU regions 2 and 3. +## Hardware +Wingtechs are abundant on ebay and can also be found on Amazon: +- +- +- +- + ## Installing Connect to the Wingtech's network over wifi or usb tethering, then run the installer: @@ -68,3 +57,13 @@ similarly to the Orbic RC400L, although the userspace program Orbic. This causes the green line on the screen to subtly flicker and only be displayed during some frames. Subsequent work to fully control the display without removing the OEM interface is desired. + +Rayhunter has been tested on: + +```sh +WT_INNER_VERSION=SW_Q89323AA1_V057_M10_CRICKET_USR_MP +WT_PRODUCTION_VERSION=CT2MHS01_0.04.55 +WT_HARDWARE_VERSION=89323_1_20 +``` + +Please consider sharing the contents of your device's /etc/wt_version file here. From ee8bf0107a79a1893ae3996709f89398d28643cc Mon Sep 17 00:00:00 2001 From: QuietEngineer Date: Sun, 29 Jun 2025 12:17:18 -0600 Subject: [PATCH 016/141] docs: misc typo fixes --- doc/installing-from-release.md | 8 +++++--- doc/introduction.md | 2 +- doc/orbic.md | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/doc/installing-from-release.md b/doc/installing-from-release.md index 7797284..be3af75 100644 --- a/doc/installing-from-release.md +++ b/doc/installing-from-release.md @@ -18,10 +18,10 @@ Make sure you've got one of Rayhunter's [supported devices](./supported-devices. 4. Run the install script for your operating system: First, enter the correct subfolder for your operating system: - - for Ubuntu on x64 arhitecture: `cd installer-ubuntu-24` - - for Ubuntu on ARM64 arhitecture: `cd installer-ubuntu-24-aarch64` + - for Ubuntu on x64 architecture: `cd installer-ubuntu-24` + - for Ubuntu on ARM64 architecture: `cd installer-ubuntu-24-aarch64` - for MacOS on Intel (old macbooks) architecture: `cd installer-macos-intel` - - for MacOS on ARM (M1/M2 etc.) achitecture: `cd installer-macos-arm` + - for MacOS on ARM (M1/M2 etc.) architecture: `cd installer-macos-arm` - for Windows: `cd installer-windows-x86_64` ```bash @@ -45,5 +45,7 @@ Make sure you've got one of Rayhunter's [supported devices](./supported-devices. * On MacOS if you encounter an error that says "No Orbic device found," it may because you have the "Allow accessories to connect" security setting set to "Ask for approval." You may need to temporarily change it to "Always" for the script to run. Make sure to change it back to a more secure setting when you're done. +```bash ./installer --help ./installer util --help +``` diff --git a/doc/introduction.md b/doc/introduction.md index 6345152..8c7c487 100644 --- a/doc/introduction.md +++ b/doc/introduction.md @@ -4,7 +4,7 @@ Rayhunter is a project for detecting IMSI catchers, also known as cell-site simulators or stingrays. It's designed to run on a cheap mobile hotspot called the Orbic RC400L, but thanks to community efforts can [support some other devices as well](./supported-devices.md). -It's also designed to be as easy to install and use as possible, regardless of you level of technical skills. This guide should provide you all you need to acquire a compatible device, install Rayhunter, and start catching IMSI catchers. +It's also designed to be as easy to install and use as possible, regardless of your level of technical skills. This guide should provide you all you need to acquire a compatible device, install Rayhunter, and start catching IMSI catchers. To learn more about the aim of the project, and about IMSI catchers in general, please check out our [introductory blog post](https://www.eff.org/deeplinks/2025/03/meet-rayhunter-new-open-source-tool-eff-detect-cellular-spying). Otherwise, check out the [installation guide](./installation.md) to get started. diff --git a/doc/orbic.md b/doc/orbic.md index 885de80..905eba7 100644 --- a/doc/orbic.md +++ b/doc/orbic.md @@ -1,6 +1,6 @@ # Orbic RC400L -The Orbic RC400L is an inexpensive LTE modem primarily designed for the US marked, and the original device for which Rayhunter is developed. +The Orbic RC400L is an inexpensive LTE modem primarily designed for the US market, and the original device for which Rayhunter is developed. You can buy an Orbic [using bezos bucks](https://www.amazon.com/Orbic-Verizon-Hotspot-Connect-Enabled/dp/B08N3CHC4Y), From c0b1d4608a0c17d24ca73c7c0ad18ef206089c94 Mon Sep 17 00:00:00 2001 From: QuietEngineer Date: Sun, 29 Jun 2025 12:25:15 -0600 Subject: [PATCH 017/141] docs: standardize rayhunter casing Use uppercase Rayhunter throughout docs when refering to the project and software. Does not change pathnames. --- doc/installing-from-release-windows.md | 2 +- doc/installing-from-source.md | 2 +- doc/tplink-m7350.md | 2 +- doc/updating-rayhunter.md | 2 +- doc/wingtech-ct2mhs01.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/installing-from-release-windows.md b/doc/installing-from-release-windows.md index 62bf042..87d0ee4 100644 --- a/doc/installing-from-release-windows.md +++ b/doc/installing-from-release-windows.md @@ -25,7 +25,7 @@ Windows support in Rayhunter's installer is a work-in-progress. Depending on the 2. Download the latest `rayhunter-vX.X.X.zip` from the [Rayhunter releases page](https://github.com/EFForg/rayhunter/releases). The version you download will have numbers instead of X 3. Unzip `rayhunter-vX.X.X` . 1. Open a powershell terminal by pressing Win+R and typing `powershell` and hitting enter. -5. Type `cd ~\Downloads\rayhunter-v\installer-windows-x86_64` (**Replace with the rayhunter version you just unzipped**) and hit enter. +5. Type `cd ~\Downloads\rayhunter-v\installer-windows-x86_64` (**Replace with the Rayhunter version you just unzipped**) and hit enter. 5. Run the install script: `.\installer.exe orbic` and hit enter. - The device will restart multiple times over the next few minutes. - You will know it is done when you see terminal output that says `checking for rayhunter server...success!` diff --git a/doc/installing-from-source.md b/doc/installing-from-source.md index 2c59940..c41d211 100644 --- a/doc/installing-from-source.md +++ b/doc/installing-from-source.md @@ -15,7 +15,7 @@ Building Rayhunter from source, either for development or because the install sc [Install Rust the usual way](https://www.rust-lang.org/tools/install). Then, -- install the cross-compilation target for the device rayhunter will run on: +- install the cross-compilation target for the device Rayhunter will run on: ```sh rustup target add armv7-unknown-linux-musleabihf ``` diff --git a/doc/tplink-m7350.md b/doc/tplink-m7350.md index a91f6f0..95a70f6 100644 --- a/doc/tplink-m7350.md +++ b/doc/tplink-m7350.md @@ -61,7 +61,7 @@ add two port triggers. You can look at `Settings > NAT Settings > Port Triggers` in TP-Link's admin UI to see them. 1. One port trigger "rayhunter-root" to launch the telnet shell. This is only needed for installation, and can be removed after upgrade. You can reinstall it using `./installer util tplink-start-telnet`. -2. One port trigger "rayhunter-daemon" to auto-start rayhunter on boot. If you remove this, rayhunter will have to be started manually from shell. +2. One port trigger "rayhunter-daemon" to auto-start Rayhunter on boot. If you remove this, Rayhunter will have to be started manually from shell. ## Other links diff --git a/doc/updating-rayhunter.md b/doc/updating-rayhunter.md index 1ad7ae6..3f1d2ba 100644 --- a/doc/updating-rayhunter.md +++ b/doc/updating-rayhunter.md @@ -1,3 +1,3 @@ # Updating Rayhunter -Great news: if you've successfully installed rayhunter, you already know how to update it! Our update process is identical to the installation process: simply repeat the steps for installing Rayhunter via a [release](./installing-from-release.md) or from [source](./installing-from-source.md). +Great news: if you've successfully installed Rayhunter, you already know how to update it! Our update process is identical to the installation process: simply repeat the steps for installing Rayhunter via a [release](./installing-from-release.md) or from [source](./installing-from-source.md). diff --git a/doc/wingtech-ct2mhs01.md b/doc/wingtech-ct2mhs01.md index ab22012..647f005 100644 --- a/doc/wingtech-ct2mhs01.md +++ b/doc/wingtech-ct2mhs01.md @@ -47,7 +47,7 @@ Connect to the Wingtech's network over wifi or usb tethering, then run the insta ``` ## Obtaining a shell -Even when rayhunter is running, for security reasons the Wingtech will not have telnet or adb enabled during normal operation. +Even when Rayhunter is running, for security reasons the Wingtech will not have telnet or adb enabled during normal operation. Use either command below to enable telnet or adb access: From 8de4dcfd187b45331488523383c418aa19a7e043 Mon Sep 17 00:00:00 2001 From: oopsbagel Date: Sun, 29 Jun 2025 12:14:54 -0700 Subject: [PATCH 018/141] doc: add white background to map for dark mode --- doc/device_regions.svg | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/doc/device_regions.svg b/doc/device_regions.svg index 9ab9d82..dae8884 100644 --- a/doc/device_regions.svg +++ b/doc/device_regions.svg @@ -27,7 +27,7 @@ inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" inkscape:zoom="0.8773442" - inkscape:cx="749.99071" + inkscape:cx="750.56061" inkscape:cy="210.29375" inkscape:window-width="2560" inkscape:window-height="1392" @@ -35,7 +35,15 @@ inkscape:window-y="27" inkscape:window-maximized="1" inkscape:current-layer="svg9" /> - Date: Sun, 29 Jun 2025 12:16:07 -0700 Subject: [PATCH 019/141] doc: supported devices: say "recommended region" Individual device pages can go into detail about specific bands and roaming. This page is primarily for steering users towards which device to buy, and we only want to recommend devices that are affordable in their region. --- doc/supported-devices.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/supported-devices.md b/doc/supported-devices.md index f7ae676..51f5ca5 100644 --- a/doc/supported-devices.md +++ b/doc/supported-devices.md @@ -5,7 +5,7 @@ Be sure to check your location's [supported frequencies](https://www.frequencych ## 1. Recommended devices These devices have been extensively tested by the core developers and are widely used. **Use one of these devices if you can.** -| Device | Region | +| Device | Recommended region | | ------ | ------ | | [Orbic RC400L](./orbic.md) | Americas | | [TP-Link M7350](./tplink-m7350.md) | Africa, Europe, Middle East | @@ -18,10 +18,10 @@ _Derivative work of [this file](https://commons.wikimedia.org/wiki/File:Internat ## 2. Functional devices Rayhunter is confirmed to work on these devices. -| Device | Region | +| Device | Recommended region | | ------ | ------ | | [Wingtech CT2MHS01](./wingtech-ct2mhs01.md) | Americas | -| [TP-Link M7310](./tplink-m7310.md) | Africa, Europe, Middle East & Americas | +| [TP-Link M7310](./tplink-m7310.md) | Africa, Europe, Middle East | ## Adding new devices Rayhunter was built and tested primarily on the Orbic RC400L mobile hotspot, but the community has been working hard at adding support for other devices. Theoretically, if a device runs a Qualcomm modem and exposes a `/dev/diag` interface, Rayhunter may work on it. From 3a393fc29fd0575288c8219c66c66a3eb74f9114 Mon Sep 17 00:00:00 2001 From: oopsbagel Date: Tue, 1 Jul 2025 22:07:47 -0700 Subject: [PATCH 020/141] installer: tokio runtime flavor current_thread Slightly reduce binary size by using the smaller 'rt' feature and the current_thread runtime flavor in the installer, since there is no benefit to true multithreading. --- installer/Cargo.toml | 2 +- installer/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/installer/Cargo.toml b/installer/Cargo.toml index 3bfb9ae..a286494 100644 --- a/installer/Cargo.toml +++ b/installer/Cargo.toml @@ -19,7 +19,7 @@ nusb = "0.1.13" reqwest = { version = "0.12.15", features = ["json"], default-features = false } serde = { version = "1.0.219", features = ["derive"] } sha2 = "0.10.8" -tokio = { version = "1.44.2", features = ["io-util", "macros", "rt-multi-thread"], default-features = false } +tokio = { version = "1.44.2", features = ["io-util", "macros", "rt"], default-features = false } tokio-retry2 = "0.5.7" tokio-stream = "0.1.17" diff --git a/installer/src/main.rs b/installer/src/main.rs index de785b8..cc1cf7b 100644 --- a/installer/src/main.rs +++ b/installer/src/main.rs @@ -142,7 +142,7 @@ async fn run() -> Result<(), Error> { Ok(()) } -#[tokio::main] +#[tokio::main(flavor = "current_thread")] async fn main() { if let Err(e) = run().await { eprintln!("{e:?}"); From 1c51e5ed6f88e012ccf8c896c067f62f43c13d51 Mon Sep 17 00:00:00 2001 From: oopsbagel Date: Thu, 3 Jul 2025 00:26:47 -0700 Subject: [PATCH 021/141] ci: release platform independent installer zips Release one zip file for each operating system / architecture combination supported by the installer. --- .github/workflows/main.yml | 44 ++++++++++++++------------ doc/installing-from-release-windows.md | 10 +++--- doc/installing-from-release.md | 23 +++++++------- 3 files changed, 40 insertions(+), 37 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4ffdae8..92591da 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -157,10 +157,10 @@ jobs: strategy: matrix: platform: - - name: ubuntu-24 + - name: linux-x64 os: ubuntu-latest target: x86_64-unknown-linux-musl - - name: ubuntu-24-aarch64 + - name: linux-aarch64 os: ubuntu-24.04-arm target: aarch64-unknown-linux-musl - name: macos-arm @@ -261,10 +261,10 @@ jobs: strategy: matrix: platform: - - name: ubuntu-24 + - name: linux-x64 os: ubuntu-latest target: x86_64-unknown-linux-musl - - name: ubuntu-24-aarch64 + - name: linux-aarch64 os: ubuntu-24.04-arm target: aarch64-unknown-linux-musl - name: macos-arm @@ -284,7 +284,7 @@ jobs: with: targets: ${{ matrix.platform.target }} - uses: Swatinem/rust-cache@v2 - - run: cargo build --bin installer --release --target ${{ matrix.platform.target }} + - run: cargo build --package installer --bin installer --release --target ${{ matrix.platform.target }} - uses: actions/upload-artifact@v4 with: name: installer-${{ matrix.platform.name }} @@ -301,6 +301,14 @@ jobs: - build_rayhunter - build_rust_installer runs-on: ubuntu-latest + strategy: + matrix: + platform: + - linux-x64 + - linux-aarch64 + - macos-intel + - macos-arm + - windows-x86_64 steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 @@ -311,23 +319,19 @@ jobs: run: echo "VERSION=$(grep '^version' bin/Cargo.toml | head -n 1 | cut -d'"' -f2)" >> $GITHUB_ENV - name: Setup versioned release directory run: | - VERSIONED_DIR="rayhunter-v${{ env.VERSION }}" - mkdir "$VERSIONED_DIR" - mv rayhunter-daemon-* rootshell/rootshell installer-* dist/* installer/install.ps1 "$VERSIONED_DIR"/ - - name: Archive release directory as zip - run: | - VERSIONED_DIR="rayhunter-v${{ env.VERSION }}" - zip -r "$VERSIONED_DIR.zip" "$VERSIONED_DIR" - - name: Compute SHA256 of zip - run: | - VERSIONED_DIR="rayhunter-v${{ env.VERSION }}" - sha256sum "$VERSIONED_DIR.zip" > "$VERSIONED_DIR.zip.sha256" - # TODO: have this create a release directly + platform="${{ matrix.platform }}" + dest="rayhunter-v${{ env.VERSION }}-${{ matrix.platform }}" + mkdir "$dest" + mv installer-$platform/installer* "$dest"/installer + cp -r rayhunter-daemon-* rootshell/rootshell dist/* installer/install.ps1 "$dest"/ + zip -r "$dest.zip" "$dest" + sha256sum "$dest.zip" > "$dest.zip.sha256" + - name: Upload zip release and sha256 uses: actions/upload-artifact@v4 with: - name: rayhunter-v${{ env.VERSION }} + name: rayhunter-v${{ env.VERSION }}-${{ matrix.platform }} path: | - rayhunter-v${{ env.VERSION }}.zip - rayhunter-v${{ env.VERSION }}.zip.sha256 + rayhunter-v${{ env.VERSION }}-${{ matrix.platform }}.zip + rayhunter-v${{ env.VERSION }}-${{ matrix.platform }}.zip.sha256 if-no-files-found: error diff --git a/doc/installing-from-release-windows.md b/doc/installing-from-release-windows.md index 87d0ee4..a17bbdc 100644 --- a/doc/installing-from-release-windows.md +++ b/doc/installing-from-release-windows.md @@ -5,8 +5,8 @@ Windows support in Rayhunter's installer is a work-in-progress. Depending on the ## TP-Link 1. Connect the device via WiFi or USB Tethering -- you should be able to view the TP-Link admin page on . -2. Download the latest release (must be at least 0.3.0), and unpack the zipfile. -3. Open PowerShell or CMD in that extracted folder, and run the binary appropriate for your operating system: `./installer-windows-x86_64/installer tplink` +2. Download the latest release (must be at least 0.3.0) for windows-x86_64, and unpack the zipfile. +3. Open PowerShell or CMD in that extracted folder, the installer: `./installer tplink` 4. Follow the instructions on the screen, if there are any. ## Orbic @@ -22,10 +22,10 @@ Windows support in Rayhunter's installer is a work-in-progress. Depending on the ![Zadig](./zadig.png) 1. Click 'install driver' and wait for it to finish. -2. Download the latest `rayhunter-vX.X.X.zip` from the [Rayhunter releases page](https://github.com/EFForg/rayhunter/releases). The version you download will have numbers instead of X -3. Unzip `rayhunter-vX.X.X` . +2. Download the latest `rayhunter-vX.X.X-windows-x86_64.zip` from the [Rayhunter releases page](https://github.com/EFForg/rayhunter/releases). The version you download will have numbers instead of X +3. Unzip `rayhunter-vX.X.X-windows-x86_64` . 1. Open a powershell terminal by pressing Win+R and typing `powershell` and hitting enter. -5. Type `cd ~\Downloads\rayhunter-v\installer-windows-x86_64` (**Replace with the Rayhunter version you just unzipped**) and hit enter. +5. Type `cd ~\Downloads\rayhunter-v-windows-x86_64` (**Replace with the Rayhunter version you just unzipped**) and hit enter. 5. Run the install script: `.\installer.exe orbic` and hit enter. - The device will restart multiple times over the next few minutes. - You will know it is done when you see terminal output that says `checking for rayhunter server...success!` diff --git a/doc/installing-from-release.md b/doc/installing-from-release.md index be3af75..c87f87c 100644 --- a/doc/installing-from-release.md +++ b/doc/installing-from-release.md @@ -2,12 +2,18 @@ Make sure you've got one of Rayhunter's [supported devices](./supported-devices.md). These instructions have only been tested on macOS and Ubuntu 24.04. If they fail, you will need to [install Rayhunter from source](./installing-from-source.md). -1. Download the latest `rayhunter-vX.X.X.zip` from the [Rayhunter releases page](https://github.com/EFForg/rayhunter/releases) -2. Decompress the `rayhunter-vX.X.X.zip` archive. Open the terminal and navigate to the folder. (Be sure to replace X.X.X with the correct version number!) +1. Download the latest `rayhunter-vX.X.X-PLATFORM.zip` from the [Rayhunter releases page](https://github.com/EFForg/rayhunter/releases) for your platform: + - for Linux on x64 architecture: `linux-x64` + - for Linux on ARM64 architecture: `linux-aarch64` + - for MacOS on Intel (old macbooks) architecture: `macos-intel` + - for MacOS on ARM (M1/M2 etc.) architecture: `macos-arm` + - for Windows: `windows-x86_64` + +2. Decompress the `rayhunter-vX.X.X-PLATFORM.zip` archive. Open the terminal and navigate to the folder. (Be sure to replace X.X.X with the correct version number!) ```bash - unzip ~/Downloads/rayhunter-vX.X.X.zip - cd ~/Downloads/rayhunter-vX.X.X + unzip ~/Downloads/rayhunter-vX.X.X-PLATFORM.zip + cd ~/Downloads/rayhunter-vX.X.X-PLATFORM ``` 3. Turn on your device by holding the power button on the front. @@ -15,14 +21,7 @@ Make sure you've got one of Rayhunter's [supported devices](./supported-devices. * For the Orbic, connect the device using a USB-C cable. * For TP-Link, connect to its network using either WiFi or USB Tethering. -4. Run the install script for your operating system: - - First, enter the correct subfolder for your operating system: - - for Ubuntu on x64 architecture: `cd installer-ubuntu-24` - - for Ubuntu on ARM64 architecture: `cd installer-ubuntu-24-aarch64` - - for MacOS on Intel (old macbooks) architecture: `cd installer-macos-intel` - - for MacOS on ARM (M1/M2 etc.) architecture: `cd installer-macos-arm` - - for Windows: `cd installer-windows-x86_64` +4. Run the installer: ```bash # On MacOS, you must first remove the quarantine bit From b0d8307a14d49e1f13124484fc22b9ecb783ba14 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Thu, 3 Jul 2025 20:44:25 +0200 Subject: [PATCH 022/141] Add send-file utilities for wingtech and tplink Since we never turn on ADB, it's a bit cumbersome to send files to the device. --- installer/src/main.rs | 38 ++++++++++++++++++++++++++++++++++++++ installer/src/util.rs | 19 ++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/installer/src/main.rs b/installer/src/main.rs index de785b8..a975056 100644 --- a/installer/src/main.rs +++ b/installer/src/main.rs @@ -73,6 +73,16 @@ enum UtilSubCommand { WingtechStartTelnet(WingtechArgs), /// Root the Wingtech and launch adb. WingtechStartAdb(WingtechArgs), + /// Send a file to the TP-Link device over telnet. + /// + /// Before running this utility, you need to make telnet accessible with `installer util + /// tplink-start-telnet`. + TplinkSendFile(TplinkSendFile), + /// Send a file to the Wingtech device over telnet. + /// + /// Before running this utility, you need to make telnet accessible with `installer util + /// wingtech-start-telnet`. + WingtechSendFile(WingtechSendFile), } #[derive(Parser, Debug)] @@ -82,6 +92,28 @@ struct TplinkStartTelnet { admin_ip: String, } +#[derive(Parser, Debug)] +struct TplinkSendFile { + /// IP address for TP-Link admin interface, if custom. + #[arg(long, default_value = "192.168.0.1")] + admin_ip: String, + /// Local path to the file to send. + local_path: String, + /// Remote path where the file should be stored on the device. + remote_path: String, +} + +#[derive(Parser, Debug)] +struct WingtechSendFile { + /// IP address for Wingtech admin interface, if custom. + #[arg(long, default_value = "192.168.1.1")] + admin_ip: String, + /// Local path to the file to send. + local_path: String, + /// Remote path where the file should be stored on the device. + remote_path: String, +} + #[derive(Parser, Debug)] struct WingtechArgs { /// IP address for Wingtech admin interface, if custom. @@ -134,6 +166,12 @@ async fn run() -> Result<(), Error> { UtilSubCommand::TplinkStartTelnet(options) => { tplink::start_telnet(&options.admin_ip).await?; } + UtilSubCommand::TplinkSendFile(options) => { + util::send_file(&options.admin_ip, &options.local_path, &options.remote_path).await?; + } + UtilSubCommand::WingtechSendFile(options) => { + util::send_file(&options.admin_ip, &options.local_path, &options.remote_path).await?; + } UtilSubCommand::WingtechStartTelnet(args) => wingtech::start_telnet(&args.admin_ip, &args.admin_password).await.context("\nFailed to start telnet on the Wingtech CT2MHS01")?, UtilSubCommand::WingtechStartAdb(args) => wingtech::start_adb(&args.admin_ip, &args.admin_password).await.context("\nFailed to start adb on the Wingtech CT2MHS01")?, } diff --git a/installer/src/util.rs b/installer/src/util.rs index 1f7ce86..1974d90 100644 --- a/installer/src/util.rs +++ b/installer/src/util.rs @@ -1,8 +1,9 @@ use std::io::Write; use std::net::SocketAddr; +use std::str::FromStr; use std::time::Duration; -use anyhow::{Result, bail}; +use anyhow::{Context, Result, bail}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpStream; use tokio::time::{sleep, timeout}; @@ -88,3 +89,19 @@ pub async fn telnet_send_file(addr: SocketAddr, filename: &str, payload: &[u8]) println!("ok"); Ok(()) } + +pub async fn send_file(admin_ip: &str, local_path: &str, remote_path: &str) -> Result<()> { + let file_content = std::fs::read(local_path) + .with_context(|| format!("Failed to read local file: {}", local_path))?; + + println!("Connecting to {admin_ip}"); + let addr = SocketAddr::from_str(&format!("{admin_ip}:23")) + .with_context(|| format!("Invalid IP address: {}", admin_ip))?; + + telnet_send_file(addr, remote_path, &file_content) + .await + .with_context(|| format!("Failed to send file {} to {}", local_path, remote_path))?; + + println!("Successfully sent {} to {}", local_path, remote_path); + Ok(()) +} From 5e66c26e7034e5005836bd695b7ba591c9e45b53 Mon Sep 17 00:00:00 2001 From: oopsbagel Date: Fri, 4 Jul 2025 11:26:19 -0700 Subject: [PATCH 023/141] ci: build installer and rayhunter-check for armv7 Support installing on aarch32/armv7 linux systems. --- .github/workflows/main.yml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 92591da..0426294 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -160,6 +160,9 @@ jobs: - name: linux-x64 os: ubuntu-latest target: x86_64-unknown-linux-musl + - name: linux-armv7 + os: ubuntu-latest + target: armv7-unknown-linux-musleabihf - name: linux-aarch64 os: ubuntu-24.04-arm target: aarch64-unknown-linux-musl @@ -175,13 +178,16 @@ jobs: runs-on: ${{ matrix.platform.os }} steps: - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.platform.target }} - uses: Swatinem/rust-cache@v2 - name: Build rayhunter-check - run: cargo build --bin rayhunter-check --release + run: cargo build --bin rayhunter-check --release --target ${{ matrix.platform.target }} - uses: actions/upload-artifact@v4 with: name: rayhunter-check-${{ matrix.platform.name }} - path: target/release/rayhunter-check${{ matrix.platform.os == 'windows-latest' && '.exe' || '' }} + path: target/${{ matrix.platform.target }}/release/rayhunter-check${{ matrix.platform.os == 'windows-latest' && '.exe' || '' }} if-no-files-found: error build_rootshell: @@ -198,7 +204,7 @@ jobs: with: targets: armv7-unknown-linux-musleabihf - uses: Swatinem/rust-cache@v2 - - name: Build rootshell (arm32) + - name: Build rootshell (armv7) run: cargo build --bin rootshell --target armv7-unknown-linux-musleabihf --profile=firmware - uses: actions/upload-artifact@v4 with: @@ -227,7 +233,7 @@ jobs: with: targets: armv7-unknown-linux-musleabihf - uses: Swatinem/rust-cache@v2 - - name: Build rayhunter-daemon (arm32) + - name: Build rayhunter-daemon (armv7) run: | pushd bin/web npm install @@ -264,6 +270,9 @@ jobs: - name: linux-x64 os: ubuntu-latest target: x86_64-unknown-linux-musl + - name: linux-armv7 + os: ubuntu-latest + target: armv7-unknown-linux-musleabihf - name: linux-aarch64 os: ubuntu-24.04-arm target: aarch64-unknown-linux-musl @@ -306,6 +315,7 @@ jobs: platform: - linux-x64 - linux-aarch64 + - linux-armv7 - macos-intel - macos-arm - windows-x86_64 From 72d6c65f295dd477cc3d9912316abc993d50c861 Mon Sep 17 00:00:00 2001 From: oopsbagel Date: Sun, 6 Jul 2025 16:02:39 -0700 Subject: [PATCH 024/141] ci: use soft float target for armv7 Support more platforms by using a the soft float musl target for aarch32/armv7/v8. The installer is not performance bound by floating point operations. --- .cargo/config.toml | 4 ++++ .github/workflows/main.yml | 4 ++-- doc/installing-from-release.md | 1 + doc/installing-from-source.md | 3 ++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 4ed54d3..b35f67e 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -15,6 +15,10 @@ rustflags = ["-C", "target-feature=+crt-static"] linker = "rust-lld" rustflags = ["-C", "target-feature=+crt-static"] +[target.armv7-unknown-linux-musleabi] +linker = "rust-lld" +rustflags = ["-C", "target-feature=+crt-static"] + # Disable rust-lld for x86 macOS because the linker crashers when compiling # the installer in release mode with debug info on. # [target.x86_64-apple-darwin] diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0426294..b680142 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -162,7 +162,7 @@ jobs: target: x86_64-unknown-linux-musl - name: linux-armv7 os: ubuntu-latest - target: armv7-unknown-linux-musleabihf + target: armv7-unknown-linux-musleabi - name: linux-aarch64 os: ubuntu-24.04-arm target: aarch64-unknown-linux-musl @@ -272,7 +272,7 @@ jobs: target: x86_64-unknown-linux-musl - name: linux-armv7 os: ubuntu-latest - target: armv7-unknown-linux-musleabihf + target: armv7-unknown-linux-musleabi - name: linux-aarch64 os: ubuntu-24.04-arm target: aarch64-unknown-linux-musl diff --git a/doc/installing-from-release.md b/doc/installing-from-release.md index c87f87c..1cefe42 100644 --- a/doc/installing-from-release.md +++ b/doc/installing-from-release.md @@ -5,6 +5,7 @@ Make sure you've got one of Rayhunter's [supported devices](./supported-devices. 1. Download the latest `rayhunter-vX.X.X-PLATFORM.zip` from the [Rayhunter releases page](https://github.com/EFForg/rayhunter/releases) for your platform: - for Linux on x64 architecture: `linux-x64` - for Linux on ARM64 architecture: `linux-aarch64` + - for Linux on armv7/v8 (32-bit) architecture: `linux-armv7` - for MacOS on Intel (old macbooks) architecture: `macos-intel` - for MacOS on ARM (M1/M2 etc.) architecture: `macos-arm` - for Windows: `windows-x86_64` diff --git a/doc/installing-from-source.md b/doc/installing-from-source.md index c41d211..976d570 100644 --- a/doc/installing-from-source.md +++ b/doc/installing-from-source.md @@ -25,8 +25,9 @@ rustup target add armv7-unknown-linux-musleabihf # check which toolchain you have installed by default with rustup show # now install the correct variant for your host platform, one of: -rustup target add x86_64-unknown-linux-musl rustup target add aarch64-unknown-linux-musl +rustup target add armv7-unknown-linux-musleabi +rustup target add x86_64-unknown-linux-musl rustup target add aarch64-apple-darwin rustup target add x86_64-apple-darwin rustup target add x86_64-pc-windows-gnu From 3247d35b7eb7aa4c1bb684c640b07dc968f9a036 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Thu, 10 Jul 2025 01:05:02 +0200 Subject: [PATCH 025/141] Fix clippy lints https://github.com/EFForg/rayhunter/pull/451 is failing because we got auto-upgraded to a new clippy, which lints against more things --- installer/src/util.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/installer/src/util.rs b/installer/src/util.rs index 1974d90..64e45ba 100644 --- a/installer/src/util.rs +++ b/installer/src/util.rs @@ -92,16 +92,16 @@ pub async fn telnet_send_file(addr: SocketAddr, filename: &str, payload: &[u8]) pub async fn send_file(admin_ip: &str, local_path: &str, remote_path: &str) -> Result<()> { let file_content = std::fs::read(local_path) - .with_context(|| format!("Failed to read local file: {}", local_path))?; + .with_context(|| format!("Failed to read local file: {local_path}"))?; println!("Connecting to {admin_ip}"); let addr = SocketAddr::from_str(&format!("{admin_ip}:23")) - .with_context(|| format!("Invalid IP address: {}", admin_ip))?; + .with_context(|| format!("Invalid IP address: {admin_ip}"))?; telnet_send_file(addr, remote_path, &file_content) .await - .with_context(|| format!("Failed to send file {} to {}", local_path, remote_path))?; + .with_context(|| format!("Failed to send file {local_path} to {remote_path}"))?; - println!("Successfully sent {} to {}", local_path, remote_path); + println!("Successfully sent {local_path} to {remote_path}"); Ok(()) } From 0be2b02349ac7b14b673ab55ea187df39c4d3ca0 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sat, 28 Jun 2025 20:07:55 +0200 Subject: [PATCH 026/141] Define missing stop_recording/start_recording_classes These variables were referenced but not actually defined. Define them and make the button disabled when rayhunter is stopping/starting --- bin/web/src/lib/components/RecordingControls.svelte | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bin/web/src/lib/components/RecordingControls.svelte b/bin/web/src/lib/components/RecordingControls.svelte index 70c31c3..0f8c2c9 100644 --- a/bin/web/src/lib/components/RecordingControls.svelte +++ b/bin/web/src/lib/components/RecordingControls.svelte @@ -18,12 +18,18 @@ } const recording_button_classes = "text-white font-bold py-2 px-4 rounded-md flex flex-row gap-1"; + const stop_recording_classes = `${recording_button_classes} bg-red-500 opacity-50 cursor-not-allowed`; + const start_recording_classes = `${recording_button_classes} bg-blue-500 opacity-50 cursor-not-allowed`;
{#if waiting_for_server} - {:else if server_is_recording} \ No newline at end of file + diff --git a/bin/web/src/lib/components/AnalysisTable.svelte b/bin/web/src/lib/components/AnalysisTable.svelte index 36a397e..fa02dbe 100644 --- a/bin/web/src/lib/components/AnalysisTable.svelte +++ b/bin/web/src/lib/components/AnalysisTable.svelte @@ -1,14 +1,22 @@ +

Warnings and Informational Logs

{#if report.statistics.num_warnings === 0 && report.statistics.num_informational_logs === 0} @@ -42,19 +51,23 @@ {#each report.rows as row, row_idx} {#each row.analysis as analysis} {@const parsed_date = new Date(analysis.timestamp)} - {#each analysis.events.filter(e => e !== null) as event} + {#each analysis.events.filter((e) => e !== null) as event} - {#if event.type === EventType.Warning} - {@const severity = ['Low', 'Medium', 'High'][event.severity]} - {@const severity_class = ['bg-red-200', 'bg-red-400', 'bg-red-600'][event.severity]} - {date_formatter.format(parsed_date)} - {event.message} - {severity} - {:else if event.type === EventType.Informational} - {date_formatter.format(parsed_date)} - {event.message} - Info - {/if} + {#if event.type === EventType.Warning} + {@const severity = ['Low', 'Medium', 'High'][event.severity]} + {@const severity_class = [ + 'bg-red-200', + 'bg-red-400', + 'bg-red-600', + ][event.severity]} + {date_formatter.format(parsed_date)} + {event.message} + {severity} + {:else if event.type === EventType.Informational} + {date_formatter.format(parsed_date)} + {event.message} + Info + {/if} {/each} {/each} @@ -64,24 +77,27 @@ {/if}
{#if report.statistics.num_skipped_packets > 0} -
-

Unparsed Messages

-

These are due to a limitation or bug in Rayhunter's parser, and aren't ususally a problem.

- - - - - - - - - {#each skipped_messages.entries() as [message, count]} - - - +
+

Unparsed Messages

+

+ These are due to a limitation or bug in Rayhunter's parser, and aren't ususally a + problem. +

+
Total Msgs AffectedReason/Error
{count}{message}
+ + + + - {/each} - -
Total Msgs AffectedReason/Error
-
+ + + {#each skipped_messages.entries() as [message, count]} + + {count} + {message} + + {/each} + + +
{/if} diff --git a/bin/web/src/lib/components/AnalysisView.svelte b/bin/web/src/lib/components/AnalysisView.svelte index 0462f90..0b19fa2 100644 --- a/bin/web/src/lib/components/AnalysisView.svelte +++ b/bin/web/src/lib/components/AnalysisView.svelte @@ -1,22 +1,29 @@
{#if entry.analysis_report === undefined}

Report unavailable, try refreshing.

- {:else if typeof(entry.analysis_report) === 'string'} + {:else if typeof entry.analysis_report === 'string'}

Error getting analysis report: {entry.analysis_report}

{:else} {@const metadata: ReportMetadata = entry.analysis_report.metadata} @@ -27,17 +34,17 @@

No warnings to display!

{/if} {#if metadata !== undefined && metadata.rayhunter !== undefined} -
-

Metadata

-

Analysis by Rayhunter version {metadata.rayhunter.rayhunter_version}

-

Device system OS: {metadata.rayhunter.system_os}

-
-
-

Analyzers

- {#each metadata.analyzers as analyzer} -

{analyzer.name}: {analyzer.description}

- {/each} -
+
+

Metadata

+

Analysis by Rayhunter version {metadata.rayhunter.rayhunter_version}

+

Device system OS: {metadata.rayhunter.system_os}

+
+
+

Analyzers

+ {#each metadata.analyzers as analyzer} +

{analyzer.name}: {analyzer.description}

+ {/each} +
{:else}

N/A (analysis generated by an older version of rayhunter)

{/if} diff --git a/bin/web/src/lib/components/ConfigForm.svelte b/bin/web/src/lib/components/ConfigForm.svelte index 4c7819e..a1a1492 100644 --- a/bin/web/src/lib/components/ConfigForm.svelte +++ b/bin/web/src/lib/components/ConfigForm.svelte @@ -5,19 +5,19 @@ let loading = $state(false); let saving = $state(false); - let message = $state(""); - let messageType = $state<"success" | "error" | null>(null); + let message = $state(''); + let messageType = $state<'success' | 'error' | null>(null); let showConfig = $state(false); async function loadConfig() { try { loading = true; config = await get_config(); - message = ""; + message = ''; messageType = null; } catch (error) { message = `Failed to load config: ${error}`; - messageType = "error"; + messageType = 'error'; } finally { loading = false; } @@ -25,21 +25,21 @@ async function saveConfig() { if (!config) return; - + try { saving = true; await set_config(config); - message = "Config saved successfully! Rayhunter is restarting now. Reload the page in a few seconds."; - messageType = "success"; + message = + 'Config saved successfully! Rayhunter is restarting now. Reload the page in a few seconds.'; + messageType = 'success'; } catch (error) { message = `Failed to save config: ${error}`; - messageType = "error"; + messageType = 'error'; } finally { saving = false; } } - // Load config when first shown $effect(() => { if (showConfig && !config) { @@ -49,21 +49,33 @@
- - + {#if showConfig} {#if loading}
Loading config...
{:else if config} -
{ e.preventDefault(); saveConfig(); }}> + { + e.preventDefault(); + saveConfig(); + }} + >
-
@@ -109,7 +126,9 @@
-

Analyzer Heuristic Settings

+

+ Analyzer Heuristic Settings +

-
@@ -142,7 +164,10 @@ bind:checked={config.analyzers.lte_sib6_and_7_downgrade} class="h-4 w-4 text-rayhunter-blue focus:ring-rayhunter-blue border-gray-300 rounded" /> -
@@ -168,20 +193,35 @@ class="bg-blue-500 hover:bg-blue-700 disabled:opacity-50 text-white font-bold py-2 px-4 rounded-md flex flex-row gap-1 items-center" > {#if saving} -
+
Saving... {:else} - - + + Apply and restart {/if} -
{#if message} -
+
{message}
{/if} diff --git a/bin/web/src/lib/components/DeleteAllButton.svelte b/bin/web/src/lib/components/DeleteAllButton.svelte index a916678..11bf22e 100644 --- a/bin/web/src/lib/components/DeleteAllButton.svelte +++ b/bin/web/src/lib/components/DeleteAllButton.svelte @@ -1,10 +1,10 @@ diff --git a/bin/web/src/lib/components/DeleteButton.svelte b/bin/web/src/lib/components/DeleteButton.svelte index c56fe21..ecc336f 100644 --- a/bin/web/src/lib/components/DeleteButton.svelte +++ b/bin/web/src/lib/components/DeleteButton.svelte @@ -1,28 +1,33 @@ - diff --git a/bin/web/src/lib/components/DownloadLink.svelte b/bin/web/src/lib/components/DownloadLink.svelte index 4d6171a..8b58354 100644 --- a/bin/web/src/lib/components/DownloadLink.svelte +++ b/bin/web/src/lib/components/DownloadLink.svelte @@ -1,5 +1,9 @@ - diff --git a/bin/web/src/lib/components/ManifestCard.svelte b/bin/web/src/lib/components/ManifestCard.svelte index 0ccd984..285b8fe 100644 --- a/bin/web/src/lib/components/ManifestCard.svelte +++ b/bin/web/src/lib/components/ManifestCard.svelte @@ -1,11 +1,16 @@ -
+ +
{#if current}
Current Recording - +
{/if}
ID: {entry.name} {#if !current} - + {/if}
{entry.get_readable_qmdl_size()}
Start: {date_formatter.format(entry.start_time)} - Last Message: {entry.last_message_time && date_formatter.format(entry.last_message_time) || "N/A"} + Last Message: {(entry.last_message_time && + date_formatter.format(entry.last_message_time)) || + 'N/A'}
- - - + + + {#if current} {:else} diff --git a/bin/web/src/lib/components/ManifestTable.svelte b/bin/web/src/lib/components/ManifestTable.svelte index 1493531..20b8391 100644 --- a/bin/web/src/lib/components/ManifestTable.svelte +++ b/bin/web/src/lib/components/ManifestTable.svelte @@ -1,7 +1,7 @@
{#if waiting_for_server} - {:else if server_is_recording} - {:else} - {/if} diff --git a/bin/web/src/lib/components/SystemStatsTable.svelte b/bin/web/src/lib/components/SystemStatsTable.svelte index 6060650..2542d09 100644 --- a/bin/web/src/lib/components/SystemStatsTable.svelte +++ b/bin/web/src/lib/components/SystemStatsTable.svelte @@ -1,34 +1,33 @@ -
+

System Information

- + - + - + diff --git a/bin/web/src/lib/manifest.svelte.ts b/bin/web/src/lib/manifest.svelte.ts index 834e578..4779163 100644 --- a/bin/web/src/lib/manifest.svelte.ts +++ b/bin/web/src/lib/manifest.svelte.ts @@ -1,5 +1,5 @@ -import { get_report, type AnalysisReport } from "./analysis.svelte"; -import { AnalysisStatus, type AnalysisManager } from "./analysisManager.svelte"; +import { get_report, type AnalysisReport } from './analysis.svelte'; +import { AnalysisStatus, type AnalysisManager } from './analysisManager.svelte'; interface JsonManifest { entries: JsonManifestEntry[]; @@ -39,7 +39,7 @@ export class Manifest { if (this.current_entry) { try { this.current_entry.analysis_report = await get_report(this.current_entry.name); - } catch(err) { + } catch (err) { this.current_entry.analysis_report = `Err: failed to get analysis report: ${err}`; } @@ -47,11 +47,11 @@ export class Manifest { // analysis report is always available this.current_entry.analysis_status = AnalysisStatus.Finished; } - } + } } export class ManifestEntry { - public name = $state(""); + public name = $state(''); public start_time: Date; public last_message_time: Date | undefined = $state(undefined); public qmdl_size_bytes = $state(0); @@ -70,16 +70,16 @@ export class ManifestEntry { } get_readable_qmdl_size(): string { - if (this.qmdl_size_bytes === 0) return "0 Bytes"; + if (this.qmdl_size_bytes === 0) return '0 Bytes'; const k = 1024; const dm = 2 || 2; - const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; const i = Math.floor(Math.log(this.qmdl_size_bytes) / Math.log(k)); return `${Number.parseFloat((this.qmdl_size_bytes / k ** i).toFixed(dm))} ${sizes[i]}`; - } + } get_num_warnings(): number | undefined { - if (this.analysis_report === undefined || typeof(this.analysis_report) === 'string') { + if (this.analysis_report === undefined || typeof this.analysis_report === 'string') { return undefined; } return this.analysis_report.statistics.num_warnings; @@ -103,5 +103,5 @@ export class ManifestEntry { get_delete_url(): string { return `/api/delete-recording/${this.name}`; - } + } } diff --git a/bin/web/src/lib/ndjson.spec.ts b/bin/web/src/lib/ndjson.spec.ts index 69a3638..8431497 100644 --- a/bin/web/src/lib/ndjson.spec.ts +++ b/bin/web/src/lib/ndjson.spec.ts @@ -2,32 +2,32 @@ import { describe, it, expect } from 'vitest'; import { parse_ndjson } from './ndjson'; describe('parsing newline-deliminated json', () => { - it('parses normal JSON', () => { + it('parses normal JSON', () => { const json = JSON.stringify({ foo: 100 }); const result = parse_ndjson(json); expect(result).toHaveLength(1); expect(result[0]).toEqual({ foo: 100 }); - }); + }); - it('parses simple newline-deliminated json', () => { + it('parses simple newline-deliminated json', () => { const json_a = JSON.stringify({ a: 100 }); const json_b = JSON.stringify({ b: 200 }); const result = parse_ndjson(`${json_a}\n${json_b}`); expect(result).toHaveLength(2); expect(result[0]).toEqual({ a: 100 }); expect(result[1]).toEqual({ b: 200 }); - }) + }); - it('parses newline-deliminated json with escaped newlines within', () => { - const json_a = JSON.stringify({ a: 'this one has\n newlines and\nstuff' }); + it('parses newline-deliminated json with escaped newlines within', () => { + const json_a = JSON.stringify({ a: 'this one has\n newlines and\nstuff' }); const json_b = JSON.stringify({ b: 200 }); const result = parse_ndjson(`${json_a}\n${json_b}`); expect(result).toHaveLength(2); expect(result[0]).toEqual({ a: 'this one has\n newlines and\nstuff' }); expect(result[1]).toEqual({ b: 200 }); - }) + }); it('actually errors out on invalid ndjson', () => { - expect(() => parse_ndjson("invalid\njson")).toThrow(); + expect(() => parse_ndjson('invalid\njson')).toThrow(); }); }); diff --git a/bin/web/src/lib/systemStats.ts b/bin/web/src/lib/systemStats.ts index 4fe5fb3..081517b 100644 --- a/bin/web/src/lib/systemStats.ts +++ b/bin/web/src/lib/systemStats.ts @@ -5,22 +5,22 @@ export interface SystemStats { } export interface RuntimeMetadata { - rayhunter_version: string, - system_os: string, - arch: string, + rayhunter_version: string; + system_os: string; + arch: string; } export interface DiskStats { - partition: string, - total_size: string, - used_size: string, - available_size: string, - used_percent: string, - mounted_on: string, + partition: string; + total_size: string; + used_size: string; + available_size: string; + used_percent: string; + mounted_on: string; } export interface MemoryStats { - total: string, - used: string, - free: string, + total: string; + used: string; + free: string; } diff --git a/bin/web/src/lib/utils.svelte.ts b/bin/web/src/lib/utils.svelte.ts index 7fdc0bf..ca6f93d 100644 --- a/bin/web/src/lib/utils.svelte.ts +++ b/bin/web/src/lib/utils.svelte.ts @@ -1,5 +1,5 @@ -import { Manifest } from "./manifest.svelte"; -import type { SystemStats } from "./systemStats"; +import { Manifest } from './manifest.svelte'; +import type { SystemStats } from './systemStats'; export interface AnalyzerConfig { imsi_requested: boolean; @@ -46,9 +46,9 @@ export async function set_config(config: Config): Promise { headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify(config) + body: JSON.stringify(config), }); - + if (!response.ok) { const error = await response.text(); throw new Error(error); diff --git a/bin/web/src/routes/+layout.svelte b/bin/web/src/routes/+layout.svelte index 9b776b7..6d4b392 100644 --- a/bin/web/src/routes/+layout.svelte +++ b/bin/web/src/routes/+layout.svelte @@ -1,6 +1,6 @@ {@render children()} diff --git a/bin/web/src/routes/+page.svelte b/bin/web/src/routes/+page.svelte index 2d78f6c..2a7bba7 100644 --- a/bin/web/src/routes/+page.svelte +++ b/bin/web/src/routes/+page.svelte @@ -1,14 +1,14 @@
-{#if loaded} -
- {#if recording} - - {:else} -
- - - WARNING: Not Running - - Rayhunter is not currently running and will not detect abnormal behavior! -
- + {#if loaded} +
+ {#if recording} + + {:else} +
+ + + WARNING: Not Running + + Rayhunter is not currently running and will not detect abnormal behavior! +
+ +
-
- {/if} - -
-
- History - -
- - -{:else} -
- -

Loading...

-
-{/if} + {/if} + +
+
+ History + +
+ + + {:else} +
+ +

Loading...

+
+ {/if}
diff --git a/bin/web/svelte.config.js b/bin/web/svelte.config.js index 90036a1..7142ee2 100644 --- a/bin/web/svelte.config.js +++ b/bin/web/svelte.config.js @@ -9,12 +9,12 @@ export default { assets: 'build', fallback: undefined, precompress: false, - strict: true + strict: true, }), version: { // Use a deterministic version string for reproducible builds. // Without this option, SvelteKit will use a timestamp. - name: process.env.GITHUB_SHA || 'dev' - } - } + name: process.env.GITHUB_SHA || 'dev', + }, + }, }; diff --git a/bin/web/tailwind.config.ts b/bin/web/tailwind.config.ts index ab0b0e5..d9bf9e9 100644 --- a/bin/web/tailwind.config.ts +++ b/bin/web/tailwind.config.ts @@ -8,10 +8,10 @@ export default { colors: { 'rayhunter-blue': '#4e4eb1', 'rayhunter-dark-blue': '#3f3da0', - 'rayhunter-green': '#94ea18' - } - } + 'rayhunter-green': '#94ea18', + }, + }, }, - plugins: [] + plugins: [], } as Config; diff --git a/bin/web/vite.config.ts b/bin/web/vite.config.ts index 6492c8f..3f327ed 100644 --- a/bin/web/vite.config.ts +++ b/bin/web/vite.config.ts @@ -22,13 +22,12 @@ export default defineConfig({ req.url ); }); - } - } - } + }, + }, + }, }, plugins: [sveltekit()], - test: { - include: ['src/**/*.{test,spec}.{js,ts}'] - } + include: ['src/**/*.{test,spec}.{js,ts}'], + }, }); From 27408dd64aea50f8e5d0dae4e589dcbf81a01e4e Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sat, 28 Jun 2025 20:16:32 +0200 Subject: [PATCH 029/141] Add web frontend linter to CI --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b680142..e902f2a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,6 +24,7 @@ jobs: outputs: code_changed: ${{ steps.files_changed.outputs.code_count }} daemon_changed: ${{ steps.files_changed.outputs.daemon_count }} + web_changed: ${{ steps.files_changed.outputs.web_count }} docs_changed: ${{ steps.files_changed.outputs.docs_count }} installer_changed: ${{ steps.files_changed.outputs.installer_count }} rootshell_changed: ${{ steps.files_changed.outputs.rootshell_count }} @@ -42,12 +43,14 @@ jobs: echo "building everything" echo code_count=forced >> "$GITHUB_OUTPUT" echo daemon_count=forced >> "$GITHUB_OUTPUT" + echo web_count=forced >> "$GITHUB_OUTPUT" echo docs_count=forced >> "$GITHUB_OUTPUT" echo installer_count=forced >> "$GITHUB_OUTPUT" echo rootshell_count=forced >> "$GITHUB_OUTPUT" else echo "code_count=$(git diff --name-only $lcommit...HEAD | grep -e ^bin -e ^installer -e ^lib -e ^rootshell -e ^telcom-parser | wc -l)" >> "$GITHUB_OUTPUT" echo "daemon_count=$(git diff --name-only $lcommit...HEAD | grep -e ^bin -e ^lib -e ^telcom-parser | wc -l)" >> "$GITHUB_OUTPUT" + echo "web_count=$(git diff --name-only $lcommit...HEAD | grep -e ^bin/web | wc -l)" >> "$GITHUB_OUTPUT" echo "docs_count=$(git diff --name-only $lcommit...HEAD | grep -e ^book.toml -e ^doc | wc -l)" >> "$GITHUB_OUTPUT" echo "installer_count=$(git diff --name-only $lcommit...HEAD | grep -e ^installer | wc -l)" >> "$GITHUB_OUTPUT" echo "rootshell_count=$(git diff --name-only $lcommit...HEAD | grep -e ^rootshell | wc -l)" >> "$GITHUB_OUTPUT" @@ -126,6 +129,22 @@ jobs: run: | NO_FIRMWARE_BIN=true cargo clippy --verbose --no-default-features --features=${{ matrix.device.name }} + test_web_frontend: + needs: files_changed + if: needs.files_changed.outputs.web_changed != '0' + runs-on: ubuntu-latest + permissions: + contents: read + defaults: + run: + working-directory: bin/web + steps: + - uses: actions/checkout@v4 + - run: npm install + - run: npm run lint + - run: npm run check + - run: npm run test + windows_installer_check_and_test: needs: files_changed if: needs.files_changed.outputs.installer_changed != '0' From 97cbe62f42c884c3b5cd053891056ad5023a54c5 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sat, 28 Jun 2025 20:24:26 +0200 Subject: [PATCH 030/141] Fix all issues in npm run check --- bin/web/src/lib/components/AnalysisStatus.svelte | 2 +- bin/web/src/lib/components/ManifestCard.svelte | 6 +++--- bin/web/src/lib/components/ManifestTable.svelte | 4 ++-- bin/web/src/lib/manifest.svelte.ts | 2 +- bin/web/src/routes/+page.svelte | 16 ++++++++-------- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/bin/web/src/lib/components/AnalysisStatus.svelte b/bin/web/src/lib/components/AnalysisStatus.svelte index d3cfc89..6a9579f 100644 --- a/bin/web/src/lib/components/AnalysisStatus.svelte +++ b/bin/web/src/lib/components/AnalysisStatus.svelte @@ -51,7 +51,7 @@
- - - + + + {#if current} {:else} diff --git a/bin/web/src/lib/components/ManifestTable.svelte b/bin/web/src/lib/components/ManifestTable.svelte index 20b8391..122255c 100644 --- a/bin/web/src/lib/components/ManifestTable.svelte +++ b/bin/web/src/lib/components/ManifestTable.svelte @@ -32,7 +32,7 @@
- Rayhunter Version - Rayhunter Version {stats.runtime_metadata.rayhunter_version}
- Storage - Storage - {stats.disk_stats.used_percent} used ({stats.disk_stats.used_size} used / {stats.disk_stats.available_size} available) + {stats.disk_stats.used_percent} used ({stats.disk_stats.used_size} used / {stats + .disk_stats.available_size} available)
- Memory (RAM) - Memory (RAM) Free: {stats.memory_stats.free}, Used: {stats.memory_stats.used}
- {#each entries as entry, i} - + {#each entries as entry} + {/each}
diff --git a/bin/web/src/lib/manifest.svelte.ts b/bin/web/src/lib/manifest.svelte.ts index 4779163..bede636 100644 --- a/bin/web/src/lib/manifest.svelte.ts +++ b/bin/web/src/lib/manifest.svelte.ts @@ -72,7 +72,7 @@ export class ManifestEntry { get_readable_qmdl_size(): string { if (this.qmdl_size_bytes === 0) return '0 Bytes'; const k = 1024; - const dm = 2 || 2; + const dm = 2; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; const i = Math.floor(Math.log(this.qmdl_size_bytes) / Math.log(k)); return `${Number.parseFloat((this.qmdl_size_bytes / k ** i).toFixed(dm))} ${sizes[i]}`; diff --git a/bin/web/src/routes/+page.svelte b/bin/web/src/routes/+page.svelte index 2a7bba7..ea24ba1 100644 --- a/bin/web/src/routes/+page.svelte +++ b/bin/web/src/routes/+page.svelte @@ -12,7 +12,6 @@ let manager: AnalysisManager = new AnalysisManager(); let loaded = $state(false); - let recording = $state(false); let entries: ManifestEntry[] = $state([]); let current_entry: ManifestEntry | undefined = $state(undefined); let system_stats: SystemStats | undefined = $state(undefined); @@ -23,7 +22,6 @@ await new_manifest.set_analysis_status(manager); entries = new_manifest.entries; current_entry = new_manifest.current_entry; - recording = current_entry !== undefined; system_stats = await get_system_stats(); loaded = true; @@ -34,7 +32,8 @@
- + +
{#if loaded}
- {#if recording} - + {#if current_entry} + {:else}
Rayhunter is not currently running and will not detect abnormal behavior!
- +
{/if} @@ -125,13 +124,14 @@
History - +
{:else}
- + +

Loading...

{/if} From 5e328b889bcb27b2aff9607109f558553f2eebea Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sat, 28 Jun 2025 20:42:23 +0200 Subject: [PATCH 031/141] Fix eslint issues --- bin/web/src/lib/analysis.spec.svelte.ts | 9 ++------- bin/web/src/lib/analysisManager.svelte.ts | 1 - bin/web/src/lib/components/AnalysisTable.svelte | 12 ++---------- bin/web/src/lib/components/AnalysisView.svelte | 13 +------------ bin/web/src/lib/components/DeleteAllButton.svelte | 7 ------- bin/web/src/lib/components/DeleteButton.svelte | 1 - bin/web/src/lib/components/ManifestCard.svelte | 2 -- bin/web/src/lib/components/ManifestTable.svelte | 2 +- bin/web/src/lib/manifest.svelte.ts | 4 ++-- 9 files changed, 8 insertions(+), 43 deletions(-) diff --git a/bin/web/src/lib/analysis.spec.svelte.ts b/bin/web/src/lib/analysis.spec.svelte.ts index c9be994..225fb28 100644 --- a/bin/web/src/lib/analysis.spec.svelte.ts +++ b/bin/web/src/lib/analysis.spec.svelte.ts @@ -1,11 +1,6 @@ import { describe, it, expect } from 'vitest'; -import { - EventType, - parse_finished_report, - Severity, - type QualitativeWarning, -} from './analysis.svelte'; -import { parse_ndjson, type NewlineDeliminatedJson } from './ndjson'; +import { EventType, parse_finished_report, Severity } from './analysis.svelte'; +import { type NewlineDeliminatedJson } from './ndjson'; const SAMPLE_REPORT_NDJSON: NewlineDeliminatedJson = [ { diff --git a/bin/web/src/lib/analysisManager.svelte.ts b/bin/web/src/lib/analysisManager.svelte.ts index 151fbb1..e813f71 100644 --- a/bin/web/src/lib/analysisManager.svelte.ts +++ b/bin/web/src/lib/analysisManager.svelte.ts @@ -1,5 +1,4 @@ import { get_report, type AnalysisReport } from './analysis.svelte'; -import type { Manifest, ManifestEntry } from './manifest.svelte'; import { req } from './utils.svelte'; export enum AnalysisStatus { diff --git a/bin/web/src/lib/components/AnalysisTable.svelte b/bin/web/src/lib/components/AnalysisTable.svelte index fa02dbe..30b1d88 100644 --- a/bin/web/src/lib/components/AnalysisTable.svelte +++ b/bin/web/src/lib/components/AnalysisTable.svelte @@ -1,13 +1,5 @@
diff --git a/bin/web/src/lib/components/DeleteAllButton.svelte b/bin/web/src/lib/components/DeleteAllButton.svelte index 11bf22e..f6234d9 100644 --- a/bin/web/src/lib/components/DeleteAllButton.svelte +++ b/bin/web/src/lib/components/DeleteAllButton.svelte @@ -1,12 +1,5 @@
diff --git a/bin/web/src/lib/components/DeleteButton.svelte b/bin/web/src/lib/components/DeleteButton.svelte index ecc336f..5e12941 100644 --- a/bin/web/src/lib/components/DeleteButton.svelte +++ b/bin/web/src/lib/components/DeleteButton.svelte @@ -1,5 +1,4 @@ diff --git a/daemon/web/src/lib/components/ConfigForm.svelte b/daemon/web/src/lib/components/ConfigForm.svelte index 7696d86..9423b14 100644 --- a/daemon/web/src/lib/components/ConfigForm.svelte +++ b/daemon/web/src/lib/components/ConfigForm.svelte @@ -203,7 +203,7 @@ bind:checked={config.analyzers.incomplete_sib} class="h-4 w-4 text-rayhunter-blue focus:ring-rayhunter-blue border-gray-300 rounded" /> -
diff --git a/daemon/web/src/lib/components/ManifestCard.svelte b/daemon/web/src/lib/components/ManifestCard.svelte index ba5e4c0..df9e0ff 100644 --- a/daemon/web/src/lib/components/ManifestCard.svelte +++ b/daemon/web/src/lib/components/ManifestCard.svelte @@ -1,5 +1,6 @@ @@ -26,13 +28,13 @@ {#each entries as entry, i} - + {/each}
{#each entries as entry} - + {/each}
diff --git a/daemon/web/src/lib/components/ManifestTableRow.svelte b/daemon/web/src/lib/components/ManifestTableRow.svelte index ed106b2..f685d29 100644 --- a/daemon/web/src/lib/components/ManifestTableRow.svelte +++ b/daemon/web/src/lib/components/ManifestTableRow.svelte @@ -1,5 +1,6 @@ + + + {#snippet icon()} + + + + {/snippet} + diff --git a/daemon/web/src/lib/components/RecordingControls.svelte b/daemon/web/src/lib/components/RecordingControls.svelte index 3772fe2..e1b189c 100644 --- a/daemon/web/src/lib/components/RecordingControls.svelte +++ b/daemon/web/src/lib/components/RecordingControls.svelte @@ -1,100 +1,51 @@
- {#if waiting_for_server} - - {:else if server_is_recording} - + viewBox="0 0 24 24" + > + + + {/snippet} + {:else} - + + {#snippet icon()} + + {/snippet} + {/if}
- - diff --git a/daemon/web/src/lib/manifest.svelte.ts b/daemon/web/src/lib/manifest.svelte.ts index f9e60e5..2feedb5 100644 --- a/daemon/web/src/lib/manifest.svelte.ts +++ b/daemon/web/src/lib/manifest.svelte.ts @@ -102,4 +102,8 @@ export class ManifestEntry { get_delete_url(): string { return `/api/delete-recording/${this.name}`; } + + get_reanalyze_url(): string { + return `/api/analysis/${this.name}`; + } } diff --git a/daemon/web/src/routes/+page.svelte b/daemon/web/src/routes/+page.svelte index ea24ba1..b1f4793 100644 --- a/daemon/web/src/routes/+page.svelte +++ b/daemon/web/src/routes/+page.svelte @@ -87,7 +87,12 @@ {#if loaded}
{#if current_entry} - + {:else}
History - +
From c6aa53acd21a05f0599d994d3831fbd23e59fa75 Mon Sep 17 00:00:00 2001 From: Matej Kovacic <3339198+MatejKovacic@users.noreply.github.com> Date: Fri, 1 Aug 2025 21:00:25 +0000 Subject: [PATCH 137/141] Update heuristics.md More explanation of logic behind analysers. --- doc/heuristics.md | 53 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/doc/heuristics.md b/doc/heuristics.md index 153a113..0015b51 100644 --- a/doc/heuristics.md +++ b/doc/heuristics.md @@ -4,9 +4,50 @@ Rayhunter includes several analyzers to detect potential IMSI catcher activity. ## Available Analyzers -- **IMSI Requested**: Tests whether the eNodeB sends an IMSI Identity Request NAS message. This can sometimes happen under normal circumstances when the network doesn't already have a TMSI (Temporary Mobile Subscriber ID or GUTI in 5G terminology) for your device. This most often happens when you first turn the device on, especially after it has been off for a long time or if you are in an area where there is absolutely no connection to your service provider. This can also happen if you leave your device on while on an airplane and it suddenly connects to a new tower after being disconnected for a long time. However, if you get this warning at a time when you have been steadily connected to towers and the device has been on for a while it can be treated as suspcious. -- **Connection Release/Redirected Carrier 2G Downgrade**: Tests if a cell releases our connection and redirects us to a 2G cell. This heuristic mostly makes sense in the US or other countries where there are no more operating 2G base stations. In countries where 2G is still in service (such as most of EU), this heuristics may trigger a lot of false positives, so you may want to disable it. However it should be noted that many IMSI Catchers operate in a such way that they downgrade connection to 2G and also that this heuristics has been vastly improved to reduce false positive warnings. See [Wikipedia page on past 2G networks](https://en.wikipedia.org/wiki/2G#Past_2G_networks) for information about your country. -- **LTE SIB6/7 Downgrade**: Tests for LTE cells broadcasting a SIB type 6 and 7 messages which include 2G/3G frequencies with higher priorities. -- **Null Cipher**: Tests whether the cell suggests using a null cipher (EEA0) in the RRC layer (that means that encryption between your mobile device and base staation is turned off). -- **NAS Null Cipher**: Tests whether the security mode command at the NAS layer suggests using a null cipher (EEA0). This would usually only happen after a UE has successfully authenticated with the MME but still it shouldn't happen at all. This could be indicative of an attack though using SS7 to get key material from the HLR of the UE for a succesful authentication. It could also indicate an IMSI catcher which is connected to the mobile network MME and HLR through cooperation between government and telecom provider. Or it could be a false positive if the telecom provider is intending to use null ciphers (if encryption is illegal or they have some misconfiguration of the network), however this should be very rare case. -- **Incomplete SIB**: Tests whether the SIB1 message contains a complete SIB chain (SIB3, SIB5, etc.) A legitimate SIB1 mesage should contain timing information for at least 2 additional sibs (sib3, 4, and 5 being the most common) but a fake base station will often not bother to send additional SIBs beyond 1 and 2. On its own this might just be a misconfigured base station (though we have only seen it in the wild under suspicious circumstances) but combined with other heuristics such as **ISMI Requested** detection it should be considered a strong indicator of malicious activity. +### IMSI Requested + +This analyser tests whether the eNodeB sends an IMSI Identity Request NAS message. + +Mobile network primarily requests IMSI number from mobile device during initial network attachment or when the network cannot identify the mobile device by its temporary identification (TMSI - *Temporary Mobile Subscriber Identity* or GUTI - *Globally Unique Temporary Identifier* in 4G/5G terminology). + +IMSI request therefore usually happens when you first turn the device on especially after it has been off for a long time. Another possibility is, that you reboot your mobile device and your temporary ID expired. Sometimes temporary identification can expire if you have been in an area where there is absolutely no connection to your service provider or after you left your device on an airplane mode and then reconnect to the network (especially being disconnected for a long time). IMSI could also be requested when you connect to a new network (for instance for roaming), when you swap she SIM card or when your device moves to a new *Tracking Area* or *Location Area* and the network can not map the temporary identification to your device. IMSI number can also be requested after core network reboot. + +It should also be noted that the network periodically reassigns your device new temporary identification to enhance security and avoid tracking, but in that cases usually does not request IMSI. + +However, if you get this warning at a time when you have been steadily connected to towers and the device has been on for a while, this could be a sign of IMSI catcher use. + +### Connection Release/Redirected Carrier 2G Downgrade + +This analyser tests if a base station releases your device's connection and redirects your device to a 2G base station. This heuristics is useful, because many commercial IMSI catchers operate in a such way that they downgrade connection to 2G where they can intercept the communication (by performing man-in-the-middle attack). + +This heuristic is the most useful in the United States or other countries where there are no more operating 2G base stations. See [Wikipedia page on past 2G networks](https://en.wikipedia.org/wiki/2G#Past_2G_networks) for information about your country. In countries where 2G is still in service (such as most of EU), this heuristics may trigger false positives. In that case you should consider disabling it. However this heuristics has been vastly improved to reduce false positive warnings and new tests in European networks show that false positives are vastly reduced. + +### LTE SIB6/7 Downgrade + +This analyser tests if LTE base station is broadcasting a SIB type 6 and 7 messages which include 2G/3G frequencies with higher priorities. + +SIB (*System Information Block*) Type 6 and 7 are specific types of broadcast messages sent by the base station (eNodeB in 4G networks) to mobile devices. They contain essential radio-related configuration parameters to help mobile device perform cell reselection. + +IMSI catchers exploit the fact that many SIB broadcast messages are not encrypted or authenticated. This allows them to pretend to be a legitimate cell by broadcasting fake system information in order to force mobile devices to downgrade from more secure 4G (LTE) to less secure 2G (GSM) network and then steal IMSI and/or perform man-in-the-middle attack. That is why this is also called a downgrade attack. + +SIB6 is used for cell reselecion to CDMA2000 systems which are not supported by many modern mobile phones, and SIB7 Provides the mobile device with information to perform cell reselection to GSM/EDGE networks. Therefore SIB6 messages are quite rare, while malformed SIB7 messages are much more frequent in practice. + +### Null Cipher + +This analyser tests whether the cell suggests using a null cipher (EEA0) in the RRC layer. That means that encryption between your mobile device and base station is turned off. + +Normally this should never happen, because null cipher is used almost exclusively for testing and debugging in labs or in controlled environments. Sometimes null cipher is used if encryption negotiation fails or isn’t supported (however in most networks this should not be the case). Also, some regulations allow unencrypted communications in **specific** emergency cases. + +The general rule is, that null cipher should never be used in commercial deployments, except in very controlled conditions (e.g., test labs) or in a very specific regulatory-approved use cases. + +On the other hand, IMSI catchers often use null cipher to avoid setting up secure contexts (because they lack valid keys) and/or to trick mobile device into using unencrypted links (which makes eavesdropping easier). + +### NAS Null Cipher + +This analyser tests whether the security mode command at the NAS layer suggests using a null cipher (EEA0). This would usually only happen after a mobile device has successfully authenticated with the MME (*Mobility Management Entity* - core network component that handles signaling and control) but still it shouldn't happen at all. This could be indicative of an attack though using SS7 (*Signaling System 7* - a set of telecommunication protocols used to set up and manage calls and other services) to get key material from the HLR (*Home Location Register* - a database in mobile telecommunications networks that stores subscriber information) of the mobile phone for a successful authentication. + +It could also indicate an IMSI catcher which is connected to the mobile network MME and HLR through cooperation between government and telecom provider. Or it could be a false positive if the telecom provider is intending to use null ciphers (if encryption is illegal in some country, or they have some misconfiguration of the network), however this should be very rare case. + +### Incomplete SIB + +This analyser tests whether the SIB1 message contains a complete SIB chain (SIB3, SIB5, etc.). A legitimate SIB1 message should contain timing information for at least 2 additional SIBs (SIB3, 4, and 5 being the most common) but a fake base station will often not bother to send additional SIBs beyond 1 and 2. On its own this might just be a misconfigured base station (though we have only seen it in the wild under suspicious circumstances) but combined with other heuristics such as **IMSI Requested** detection it should be considered as a strong indicator of malicious activity. From 30bb18016edd480f4caff26e3013ee21504b3df0 Mon Sep 17 00:00:00 2001 From: Matej Kovacic <3339198+MatejKovacic@users.noreply.github.com> Date: Fri, 1 Aug 2025 21:13:21 +0000 Subject: [PATCH 138/141] Update heuristics.md --- doc/heuristics.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/heuristics.md b/doc/heuristics.md index 0015b51..ce98943 100644 --- a/doc/heuristics.md +++ b/doc/heuristics.md @@ -50,4 +50,6 @@ It could also indicate an IMSI catcher which is connected to the mobile network ### Incomplete SIB -This analyser tests whether the SIB1 message contains a complete SIB chain (SIB3, SIB5, etc.). A legitimate SIB1 message should contain timing information for at least 2 additional SIBs (SIB3, 4, and 5 being the most common) but a fake base station will often not bother to send additional SIBs beyond 1 and 2. On its own this might just be a misconfigured base station (though we have only seen it in the wild under suspicious circumstances) but combined with other heuristics such as **IMSI Requested** detection it should be considered as a strong indicator of malicious activity. +This analyser tests whether the SIB1 message contains a complete SIB chain (SIB3, SIB5, etc.). A legitimate SIB1 message should contain timing information for at least 2 additional SIBs (SIB3, 4, and 5 being the most common) but a fake base station will often not bother to send additional SIBs beyond 1 and 2 (i. e. some IMSI catchers send just SIB1 and *one additional* SIB). + +On its own this might just be a misconfigured base station (though we have only seen it in the wild under suspicious circumstances) but combined with other heuristics such as **IMSI Requested** detection it should be considered as a strong indicator of malicious activity. From f4522dbe3d1d2fc9cb135dd66da170e5a597e87b Mon Sep 17 00:00:00 2001 From: Andrej Date: Tue, 5 Aug 2025 14:21:19 -0400 Subject: [PATCH 139/141] cargo fmt run --- installer/src/uz801.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/installer/src/uz801.rs b/installer/src/uz801.rs index 607b024..0475ec7 100644 --- a/installer/src/uz801.rs +++ b/installer/src/uz801.rs @@ -161,7 +161,7 @@ async fn install_rayhunter_files(adb_device: &mut ADBUSBDevice) -> Result<()> { /// before overwriting the destination. fn install_file(adb_device: &mut ADBUSBDevice, dest: &str, payload: &[u8]) -> Result<()> { const MAX_RETRIES: u32 = 3; - + let file_name = Path::new(dest) .file_name() .ok_or_else(|| anyhow!("{dest} does not have a file name"))? @@ -170,7 +170,7 @@ fn install_file(adb_device: &mut ADBUSBDevice, dest: &str, payload: &[u8]) -> Re .to_owned(); let push_tmp_path = format!("/data/local/tmp/{file_name}"); let file_hash = md5_compute(payload); - + for attempt in 1..=MAX_RETRIES { // Push the file let mut payload_copy = payload; @@ -183,7 +183,10 @@ fn install_file(adb_device: &mut ADBUSBDevice, dest: &str, payload: &[u8]) -> Re // Verify with md5sum let mut buf = Vec::::new(); - if adb_device.shell_command(&["busybox", "md5sum", &push_tmp_path], &mut buf).is_ok() { + if adb_device + .shell_command(&["busybox", "md5sum", &push_tmp_path], &mut buf) + .is_ok() + { let output = String::from_utf8_lossy(&buf); if output.contains(&format!("{file_hash:x}")) { // Verification successful, move to final destination @@ -193,15 +196,20 @@ fn install_file(adb_device: &mut ADBUSBDevice, dest: &str, payload: &[u8]) -> Re return Ok(()); } } - + // Verification failed, clean up and retry if attempt < MAX_RETRIES { - println!("MD5 verification failed on attempt {}, retrying...", attempt); + println!( + "MD5 verification failed on attempt {}, retrying...", + attempt + ); let mut buf = Vec::::new(); - adb_device.shell_command(&["rm", "-f", &push_tmp_path], &mut buf).ok(); + adb_device + .shell_command(&["rm", "-f", &push_tmp_path], &mut buf) + .ok(); } } - + anyhow::bail!("MD5 verification failed for {dest} after {MAX_RETRIES} attempts") } @@ -231,4 +239,4 @@ async fn modify_startup_script(adb_device: &mut ADBUSBDevice) -> Result<()> { )?; Ok(()) -} \ No newline at end of file +} From 13877f7209a2f331f8006d774447ae49f3bece6b Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Tue, 5 Aug 2025 21:05:31 +0200 Subject: [PATCH 140/141] cargo clippy --- installer/src/uz801.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/installer/src/uz801.rs b/installer/src/uz801.rs index 0475ec7..4475911 100644 --- a/installer/src/uz801.rs +++ b/installer/src/uz801.rs @@ -200,8 +200,7 @@ fn install_file(adb_device: &mut ADBUSBDevice, dest: &str, payload: &[u8]) -> Re // Verification failed, clean up and retry if attempt < MAX_RETRIES { println!( - "MD5 verification failed on attempt {}, retrying...", - attempt + "MD5 verification failed on attempt {attempt}, retrying..." ); let mut buf = Vec::::new(); adb_device From 31bd60dea18df8129c75bec978071c536675aee1 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Tue, 5 Aug 2025 21:11:17 +0200 Subject: [PATCH 141/141] cargo fmt --- installer/src/uz801.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/installer/src/uz801.rs b/installer/src/uz801.rs index 4475911..2d66375 100644 --- a/installer/src/uz801.rs +++ b/installer/src/uz801.rs @@ -199,9 +199,7 @@ fn install_file(adb_device: &mut ADBUSBDevice, dest: &str, payload: &[u8]) -> Re // Verification failed, clean up and retry if attempt < MAX_RETRIES { - println!( - "MD5 verification failed on attempt {attempt}, retrying..." - ); + println!("MD5 verification failed on attempt {attempt}, retrying..."); let mut buf = Vec::::new(); adb_device .shell_command(&["rm", "-f", &push_tmp_path], &mut buf)