Files
rayhunter/installer/src/uz801.rs
T
2025-08-03 20:03:01 -04:00

300 lines
9.6 KiB
Rust

use std::io::Write;
/// 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 adb_client::{ADBDeviceExt, ADBUSBDevice, RustADBError};
use anyhow::Result;
use tokio::time::sleep;
use crate::Uz801Args as Args;
use crate::util::echo;
pub async fn install(Args { admin_ip }: 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://{admin_ip}/ajax");
let referer = format!("http://{admin_ip}/usbdebug.html");
let origin = format!("http://{admin_ip}");
let _handle = tokio::spawn(async move {
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(5))
.build()
.unwrap();
let _response = client
.post(&url)
.header("Accept", "application/json, text/javascript, */*; q=0.01")
.header("Accept-Encoding", "gzip, deflate")
.header("Referer", &referer)
.header(
"Content-Type",
"application/x-www-form-urlencoded; charset=UTF-8",
)
.header("X-Requested-With", "XMLHttpRequest")
.header("Origin", &origin)
.body(r#"{"funcNo":2001}"#)
.send()
.await;
// Ignore any errors - the device will reboot and connection will be lost
});
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(10)).await;
loop {
if attempts >= MAX_ATTEMPTS {
anyhow::bail!("Timeout waiting for ADB connection after USB debug activation");
}
// UZ801 USB vendor and product IDs.
// TODO: Research if other variants use different IDs.
match ADBUSBDevice::new(0x05c6, 0x90b6) {
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)?;
// Remount system as writable
adb_device.shell_command(&["mount", "-o", "remount,rw", "/system"], &mut buf)?;
// Install rayhunter daemon binary with verification
let rayhunter_daemon_bin = include_bytes!(env!("FILE_RAYHUNTER_DAEMON"));
install_file_with_verification(
adb_device,
"/data/rayhunter/rayhunter-daemon",
rayhunter_daemon_bin,
)
.await?;
// 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 install_file_with_verification(
adb_device: &mut ADBUSBDevice,
dest_path: &str,
file_data: &[u8],
) -> Result<()> {
const MAX_RETRIES: u32 = 5;
let expected_size = file_data.len();
println!("Installing {} ({} bytes)...", dest_path, expected_size);
for attempt in 1..=MAX_RETRIES {
// Push the file
let mut data_copy = file_data;
match adb_device.push(&mut data_copy, &dest_path) {
Ok(_) => {
// Verify the file size
let mut buf = Vec::<u8>::new();
if let Ok(_) = adb_device.shell_command(&["ls", "-l", dest_path], &mut buf) {
let output = String::from_utf8_lossy(&buf);
// Parse the file size from ls output
if let Some(size_str) = output.split_whitespace().nth(3) {
if let Ok(actual_size) = size_str.parse::<usize>() {
if actual_size == expected_size {
println!(
"Successfully installed {} (verified {} bytes)",
dest_path, actual_size
);
return Ok(());
} else {
println!(
"Size mismatch on attempt {}: expected {}, got {}",
attempt, expected_size, actual_size
);
// Remove the incomplete file before retry
let mut buf = Vec::<u8>::new();
adb_device
.shell_command(&["rm", "-f", dest_path], &mut buf)
.ok();
if attempt < MAX_RETRIES {
println!("Retrying file transfer...");
sleep(Duration::from_secs(1)).await;
continue;
}
}
}
}
}
}
Err(e) => {
println!("Push failed on attempt {}: {}", attempt, e);
if attempt < MAX_RETRIES {
sleep(Duration::from_secs(1)).await;
continue;
}
return Err(e.into());
}
}
}
anyhow::bail!(
"Failed to install {} after {} attempts - size verification failed",
dest_path,
MAX_RETRIES
)
}
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://{admin_ip}:8080/index.html");
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;
// modified from 3 because MifiService has to set up the
// network routing and it takes a bit longer
sleep(Duration::from_secs(5)).await;
}
anyhow::bail!("timeout reached! failed to reach rayhunter, something went wrong :(")
}