mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-05-30 05:59:27 -07:00
PR #888 introduced more files that the installer needs to bundle. Those files in particular are annoying to deal with because now every developer needs a working C crosscompiler to get the installer working. This prompted me to do some other refactoring. Refactor install-dev to not build the wifi tools if there is no crosscompiler, and refactor the installer so that these files are loaded at runtime when built in debug mode. The build script only ever warns if files are missing, and depending on debug/release mode, the get_file!() macro either panics at runtime or fails compiling. Now the installer can be built again without any files, clippy can be run directly without any envvars, and the installer runs atleast for devices that don't need those files. The orbic installer will panic at runtime if the wifi tools haven't been built. Building the installer in release mode still requires all files. Another nicety of loading these files on runtime is that the installer does not need to be recompiled when the daemon has been rebuilt. This should make things like make.sh really obsolete, which bypass the installer for speed.
254 lines
8.3 KiB
Rust
254 lines
8.3 KiB
Rust
use std::path::Path;
|
|
/// 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, anyhow};
|
|
use md5::compute as md5_compute;
|
|
use tokio::time::sleep;
|
|
|
|
use crate::Uz801Args as Args;
|
|
use crate::output::{print, println};
|
|
|
|
pub async fn install(Args { admin_ip }: Args) -> Result<()> {
|
|
run_install(admin_ip).await
|
|
}
|
|
|
|
async fn run_install(admin_ip: String) -> Result<()> {
|
|
print!("Activating USB debugging backdoor... ");
|
|
activate_usb_debug(&admin_ip).await?;
|
|
println!("ok");
|
|
|
|
print!("Waiting for device reboot and ADB connection... ");
|
|
let mut adb_device = wait_for_adb().await?;
|
|
println!("ok");
|
|
|
|
print!("Installing rayhunter files... ");
|
|
install_rayhunter_files(&mut adb_device).await?;
|
|
println!("ok");
|
|
|
|
print!("Modifying startup script... ");
|
|
modify_startup_script(&mut adb_device).await?;
|
|
println!("ok");
|
|
|
|
print!("Rebooting the device... ");
|
|
let _ = adb_device.reboot(adb_client::RebootType::System);
|
|
println!("ok");
|
|
|
|
println!("Installation complete!");
|
|
println!("Please wait for the device to reboot (light will turn green)");
|
|
println!("Then access rayhunter 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}");
|
|
|
|
// Check if device is online
|
|
print!("Checking if device is online... ");
|
|
let client = reqwest::Client::builder()
|
|
.timeout(Duration::from_secs(5))
|
|
.build()?;
|
|
|
|
match client.get(&origin).send().await {
|
|
Ok(response) if response.status().is_success() => println!("ok"),
|
|
Ok(response) => anyhow::bail!(
|
|
"Device at {admin_ip} returned error status: {}",
|
|
response.status()
|
|
),
|
|
Err(e) => anyhow::bail!("Failed to reach device at {admin_ip}: {}", e),
|
|
}
|
|
|
|
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 = crate::get_file!("FILE_RAYHUNTER_DAEMON");
|
|
install_file(
|
|
adb_device,
|
|
"/data/rayhunter/rayhunter-daemon",
|
|
rayhunter_daemon_bin,
|
|
)?;
|
|
|
|
// 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(())
|
|
}
|
|
|
|
/// Transfer a file to the device's filesystem with adb push.
|
|
/// Validates the file sends successfully to /data/local/tmp
|
|
/// 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"))?
|
|
.to_str()
|
|
.ok_or_else(|| anyhow!("{dest}'s file name is not UTF8"))?
|
|
.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;
|
|
if let Err(e) = adb_device.push(&mut payload_copy, &push_tmp_path) {
|
|
if attempt == MAX_RETRIES {
|
|
return Err(e.into());
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Verify with md5sum
|
|
let mut buf = Vec::<u8>::new();
|
|
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
|
|
let mut buf = Vec::<u8>::new();
|
|
adb_device.shell_command(&["mv", &push_tmp_path, dest], &mut buf)?;
|
|
println!("ok");
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
// Verification failed, clean up and retry
|
|
if attempt < MAX_RETRIES {
|
|
println!("MD5 verification failed on attempt {attempt}, retrying...");
|
|
let mut buf = Vec::<u8>::new();
|
|
adb_device
|
|
.shell_command(&["rm", "-f", &push_tmp_path], &mut buf)
|
|
.ok();
|
|
}
|
|
}
|
|
|
|
anyhow::bail!("MD5 verification failed for {dest} after {MAX_RETRIES} attempts")
|
|
}
|
|
|
|
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(())
|
|
}
|