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://{}/ajax", admin_ip); let referer = format!("http://{}/usbdebug.html", admin_ip); 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 { 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"); } // 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::::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::::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 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::::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::::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::::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::::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 :(") }