uz801: Add initial (experimental) support

This commit is contained in:
Andrej
2025-08-02 20:58:19 -04:00
parent fe6afac817
commit c697773244
7 changed files with 388 additions and 30 deletions
+18
View File
@@ -6,6 +6,7 @@ mod orbic;
mod pinephone;
mod tmobile;
mod tplink;
mod uz801;
mod util;
mod wingtech;
@@ -27,6 +28,8 @@ enum Command {
Orbic(InstallOrbic),
/// Install rayhunter on the TMobile TMOHS1.
Tmobile(TmobileArgs),
/// Install rayhunter on the Uz801.
Uz801(Uz801Args),
/// Install rayhunter on a PinePhone's Quectel modem.
Pinephone(InstallPinephone),
/// Install rayhunter on the TP-Link M7350.
@@ -82,6 +85,8 @@ enum UtilSubCommand {
TmobileStartAdb(TmobileArgs),
/// Root the Tmobile and launch telnetd.
TmobileStartTelnet(TmobileArgs),
/// Root the Uz801 and launch adb.
Uz801StartAdb(Uz801Args),
/// Root the tplink and launch telnetd.
TplinkStartTelnet(TplinkStartTelnet),
/// Root the Wingtech and launch telnetd.
@@ -115,6 +120,17 @@ struct TmobileArgs {
admin_password: String,
}
#[derive(Parser, Debug)]
struct Uz801Args {
/// IP address for Uz801 admin interface, if custom.
#[arg(long, default_value = "192.168.0.1")]
admin_ip: String,
/// Web portal admin password.
#[arg(long)]
admin_password: String,
}
#[derive(Parser, Debug)]
struct TplinkStartTelnet {
/// IP address for TP-Link admin interface, if custom.
@@ -168,6 +184,7 @@ async fn run() -> Result<(), Error> {
match command {
Command::Tmobile(args) => tmobile::install(args).await.context("Failed to install rayhunter on the Tmobile TMOHS1. Make sure your computer is connected to the hotspot using USB tethering or WiFi.")?,
Command::Uz801(args) => uz801::install(args).await.context("Failed to install rayhunter on the Uz801. Make sure your computer is connected to the hotspot using USB tethering or WiFi.")?,
Command::Tplink(tplink) => tplink::main_tplink(tplink).await.context("Failed to install rayhunter on the TP-Link M7350. Make sure your computer is connected to the hotspot using USB tethering or WiFi.")?,
Command::Pinephone(_) => pinephone::install().await
.context("Failed to install rayhunter on the Pinephone's Quectel modem")?,
@@ -195,6 +212,7 @@ async fn run() -> Result<(), Error> {
UtilSubCommand::Shell => orbic::shell().await.context("\nFailed to open shell on Orbic RC400L")?,
UtilSubCommand::TmobileStartTelnet(args) => wingtech::start_telnet(&args.admin_ip, &args.admin_password).await.context("\nFailed to start telnet on the Tmobile TMOHS1")?,
UtilSubCommand::TmobileStartAdb(args) => wingtech::start_adb(&args.admin_ip, &args.admin_password).await.context("\nFailed to start adb on the Tmobile TMOHS1")?,
UtilSubCommand::Uz801StartAdb(args) => uz801::activate_usb_debug(&args.admin_ip).await.context("\nFailed to activate USB debug on the Uz801")?,
UtilSubCommand::TplinkStartTelnet(options) => {
tplink::start_telnet(&options.admin_ip).await?;
}
+197
View File
@@ -0,0 +1,197 @@
/// Installer for the Uz801 hotspot.
///
/// Installation process:
/// 1. Use curl to activate USB debugging backdoor
/// 2. Wait for device reboot and ADB availability
/// 3. Use ADB to install rayhunter files
/// 4. Modify startup script to launch rayhunter on boot
use std::time::Duration;
use anyhow::Result;
use tokio::time::sleep;
use adb_client::{ADBDeviceExt, ADBUSBDevice, RustADBError};
use std::io::ErrorKind;
use crate::Uz801Args as Args;
use crate::util::echo;
pub async fn install(
Args {
admin_ip,
admin_password: _, // Not used for Uz801
}: Args,
) -> Result<()> {
run_install(admin_ip).await
}
async fn run_install(admin_ip: String) -> Result<()> {
echo!("Activating USB debugging backdoor... ");
activate_usb_debug(&admin_ip).await?;
println!("ok");
echo!("Waiting for device reboot and ADB connection... ");
let mut adb_device = wait_for_adb().await?;
println!("ok");
echo!("Installing rayhunter files... ");
install_rayhunter_files(&mut adb_device).await?;
println!("ok");
echo!("Modifying startup script... ");
modify_startup_script(&mut adb_device).await?;
println!("ok");
echo!("Starting rayhunter daemon... ");
start_rayhunter(&mut adb_device).await?;
println!("ok");
echo!("Testing rayhunter... ");
test_rayhunter(&admin_ip).await?;
println!("ok");
println!("rayhunter is running at http://{admin_ip}:8080");
Ok(())
}
pub async fn activate_usb_debug(admin_ip: &str) -> Result<()> {
let url = format!("http://{}/usbdebug.html", admin_ip);
let client = reqwest::Client::new();
let response = client.get(&url).send().await?;
if !response.status().is_success() {
anyhow::bail!("Failed to activate USB debug: HTTP {}", response.status());
}
Ok(())
}
async fn wait_for_adb() -> Result<ADBUSBDevice> {
const MAX_ATTEMPTS: u32 = 30; // 30 seconds
let mut attempts = 0;
// Wait a bit for the reboot to start
sleep(Duration::from_secs(5)).await;
loop {
if attempts >= MAX_ATTEMPTS {
anyhow::bail!("Timeout waiting for ADB connection after USB debug activation");
}
match ADBUSBDevice::new(0x05c6, 0x9025) { // Common Qualcomm ADB VID/PID
Ok(mut device) => {
// Test ADB connection
if test_adb_connection(&mut device).await.is_ok() {
return Ok(device);
}
}
Err(RustADBError::DeviceNotFound(_)) => {
// Device not ready yet, continue waiting
}
Err(e) => {
anyhow::bail!("ADB connection error: {}", e);
}
}
sleep(Duration::from_secs(1)).await;
attempts += 1;
}
}
async fn test_adb_connection(adb_device: &mut ADBUSBDevice) -> Result<()> {
let mut buf = Vec::<u8>::new();
adb_device.shell_command(&["echo", "test"], &mut buf)?;
let output = String::from_utf8_lossy(&buf);
if output.contains("test") {
Ok(())
} else {
anyhow::bail!("ADB connection test failed")
}
}
async fn install_rayhunter_files(adb_device: &mut ADBUSBDevice) -> Result<()> {
// Create rayhunter directory
let mut buf = Vec::<u8>::new();
adb_device.shell_command(&["mkdir", "-p", "/data/rayhunter"], &mut buf)?;
// Install rayhunter daemon binary
let rayhunter_daemon_bin = include_bytes!(env!("FILE_RAYHUNTER_DAEMON"));
let mut daemon_data = rayhunter_daemon_bin.as_slice();
adb_device.push(&mut daemon_data, "/data/rayhunter/rayhunter-daemon")?;
// Install config file
let config_content = crate::CONFIG_TOML
.replace("#device = \"orbic\"", "device = \"uz801\"");
let mut config_data = config_content.as_bytes();
adb_device.push(&mut config_data, "/data/rayhunter/config.toml")?;
// Make daemon executable
let mut buf = Vec::<u8>::new();
adb_device.shell_command(&["chmod", "755", "/data/rayhunter/rayhunter-daemon"], &mut buf)?;
Ok(())
}
async fn modify_startup_script(adb_device: &mut ADBUSBDevice) -> Result<()> {
// Pull the existing startup script
let mut script_content = Vec::<u8>::new();
adb_device.pull("/system/bin/initmifiservice.sh", &mut script_content)?;
// Convert to string and add our line
let mut script_str = String::from_utf8_lossy(&script_content).into_owned();
// Add rayhunter startup line if not already present
let rayhunter_line = "/data/rayhunter/rayhunter-daemon /data/rayhunter/config.toml &\n";
if !script_str.contains("/data/rayhunter/rayhunter-daemon") {
script_str.push_str(rayhunter_line);
}
// Push the modified script back
let mut modified_script = script_str.as_bytes();
adb_device.push(&mut modified_script, "/system/bin/initmifiservice.sh")?;
// Make sure it's executable
let mut buf = Vec::<u8>::new();
adb_device.shell_command(&["chmod", "755", "/system/bin/initmifiservice.sh"], &mut buf)?;
Ok(())
}
async fn start_rayhunter(adb_device: &mut ADBUSBDevice) -> Result<()> {
let mut buf = Vec::<u8>::new();
adb_device.shell_command(&[
"/data/rayhunter/rayhunter-daemon",
"/data/rayhunter/config.toml",
"&"
], &mut buf)?;
// Give it a moment to start
sleep(Duration::from_secs(3)).await;
Ok(())
}
async fn test_rayhunter(admin_ip: &str) -> Result<()> {
const MAX_FAILURES: u32 = 10;
let mut failures = 0;
let client = reqwest::Client::new();
while failures < MAX_FAILURES {
let url = format!("http://{}:8080/index.html", admin_ip);
if let Ok(response) = client.get(&url).send().await {
if response.status().is_success() {
if let Ok(body) = response.text().await {
if body.contains("html") {
return Ok(());
}
}
}
}
failures += 1;
sleep(Duration::from_secs(3)).await;
}
anyhow::bail!("timeout reached! failed to reach rayhunter, something went wrong :(")
}