diff --git a/installer/src/main.rs b/installer/src/main.rs index 9cbbf86..b8fca4f 100644 --- a/installer/src/main.rs +++ b/installer/src/main.rs @@ -5,6 +5,7 @@ use env_logger::Env; mod orbic; mod tplink; mod wingtech; +mod util; pub static CONFIG_TOML: &str = include_str!("../../dist/config.toml.example"); pub static RAYHUNTER_DAEMON_INIT: &str = include_str!("../../dist/scripts/rayhunter_daemon"); @@ -79,6 +80,10 @@ enum UtilSubCommand { Shell(Shell), /// Root the tplink and launch telnetd. TplinkStartTelnet(TplinkStartTelnet), + /// Root the Wingtech and launch telnetd. + WingtechStartTelnet(WingtechArgs), + /// Root the Wingtech and launch adb. + WingtechStartAdb(WingtechArgs), } #[derive(Parser, Debug)] @@ -88,6 +93,17 @@ struct TplinkStartTelnet { admin_ip: String, } +#[derive(Parser, Debug)] +struct WingtechArgs { + /// IP address for Wingtech admin interface, if custom. + #[arg(long, default_value = "192.168.1.1")] + admin_ip: String, + + /// Web portal admin password. + #[arg(long)] + admin_password: String, +} + #[derive(Parser, Debug)] struct Serial { #[arg(long)] @@ -129,6 +145,8 @@ async fn run() -> Result<(), Error> { UtilSubCommand::TplinkStartTelnet(options) => { tplink::start_telnet(&options.admin_ip).await?; } + UtilSubCommand::WingtechStartTelnet(args) => wingtech::start_telnet(&args.admin_ip, &args.admin_password).await.context("\nFailed to start telnet on the Wingtech CT2MHS01")?, + UtilSubCommand::WingtechStartAdb(args) => wingtech::start_adb(&args.admin_ip, &args.admin_password).await.context("\nFailed to start telnet on the Wingtech CT2MHS01")?, } } diff --git a/installer/src/orbic.rs b/installer/src/orbic.rs index 566d909..3e5a38a 100644 --- a/installer/src/orbic.rs +++ b/installer/src/orbic.rs @@ -10,6 +10,7 @@ use sha2::{Digest, Sha256}; use tokio::time::sleep; use crate::{CONFIG_TOML, RAYHUNTER_DAEMON_INIT}; +use crate::util::echo; pub const ORBIC_NOT_FOUND: &str = r#"No Orbic device found. Make sure your device is plugged in and turned on. @@ -40,14 +41,6 @@ const RNDIS_INTERFACE: u8 = 0; #[cfg(not(target_os = "windows"))] const RNDIS_INTERFACE: u8 = 1; -macro_rules! echo { - ($($arg:tt)*) => { - print!($($arg)*); - let _ = std::io::stdout().flush(); - }; -} -pub(crate) use echo; - pub async fn install() -> Result<()> { let mut adb_device = force_debug_mode().await?; echo!("Installing rootshell... "); diff --git a/installer/src/tplink.rs b/installer/src/tplink.rs index 60c844a..63d7606 100644 --- a/installer/src/tplink.rs +++ b/installer/src/tplink.rs @@ -15,11 +15,10 @@ use bytes::{Bytes, BytesMut}; use hyper::StatusCode; use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor}; use serde::Deserialize; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use tokio::net::TcpStream; -use tokio::time::{sleep, timeout}; +use tokio::time::sleep; use crate::InstallTpLink; +use crate::util::{telnet_send_command, telnet_send_file}; type HttpProxyClient = hyper_util::client::legacy::Client; @@ -159,6 +158,7 @@ async fn tplink_run_install( rayhunter_daemon_bin, ) .await?; + telnet_send_file( addr, "/etc/init.d/rayhunter_daemon", @@ -195,99 +195,6 @@ async fn tplink_run_install( Ok(()) } -async fn telnet_send_file(addr: SocketAddr, filename: &str, payload: &[u8]) -> Result<(), Error> { - println!("Sending file {filename}"); - - // remove the old file just in case we are close to disk capacity. - telnet_send_command(addr, &format!("rm {filename}"), "").await?; - - { - let filename = filename.to_owned(); - let handle = tokio::spawn(async move { - telnet_send_command(addr, &format!("nc -l 0.0.0.0:8081 > {filename}.tmp"), "").await - }); - - sleep(Duration::from_millis(100)).await; - - let mut addr = addr; - addr.set_port(8081); - let mut stream = TcpStream::connect(addr).await?; - stream.write_all(payload).await?; - - handle.await??; - } - - let checksum = md5::compute(payload); - - telnet_send_command( - addr, - &format!("md5sum {filename}.tmp"), - &format!("{checksum:x} {filename}.tmp"), - ) - .await?; - - telnet_send_command( - addr, - &format!("mv {filename}.tmp {filename}"), - "exit code 0", - ) - .await?; - - Ok(()) -} - -pub async fn telnet_send_command( - addr: SocketAddr, - command: &str, - expected_output: &str, -) -> Result<(), Error> { - let stream = TcpStream::connect(addr).await?; - let (mut reader, mut writer) = stream.into_split(); - - loop { - let mut next_byte = 0; - reader - .read_exact(std::slice::from_mut(&mut next_byte)) - .await?; - if next_byte == b'#' { - break; - } - } - - writer.write_all(command.as_bytes()).await?; - writer.write_all(b"; echo exit code $?\r\n").await?; - - let mut read_buf = Vec::new(); - - let _ = timeout(Duration::from_secs(5), async { - let mut buf = [0; 4096]; - loop { - let Ok(bytes_read) = reader.read(&mut buf).await else { - break; - }; - let bytes = &buf[..bytes_read]; - if bytes.is_empty() { - continue; - } - - read_buf.extend(bytes); - - if read_buf.ends_with(b"/ # ") { - break; - } - } - }) - .await; - - let string = String::from_utf8_lossy(&read_buf); - - if !string.contains(expected_output) { - anyhow::bail!("{expected_output:?} not found in: {string}"); - } - - Ok(()) -} - #[derive(Clone)] struct AppState { client: HttpProxyClient, diff --git a/installer/src/util.rs b/installer/src/util.rs new file mode 100644 index 0000000..0280c7b --- /dev/null +++ b/installer/src/util.rs @@ -0,0 +1,90 @@ +use std::io::Write; +use std::net::SocketAddr; +use std::time::Duration; + +use anyhow::{bail, Result}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::TcpStream; +use tokio::time::{sleep, timeout}; + +macro_rules! echo { + ($($arg:tt)*) => { + print!($($arg)*); + let _ = std::io::stdout().flush(); + }; +} +pub(crate) use echo; + +pub async fn telnet_send_command( + addr: SocketAddr, + command: &str, + expected_output: &str, +) -> Result<()> { + let stream = TcpStream::connect(addr).await?; + let (mut reader, mut writer) = stream.into_split(); + loop { + let mut next_byte = 0; + reader + .read_exact(std::slice::from_mut(&mut next_byte)) + .await?; + if next_byte == b'#' { + break; + } + } + writer.write_all(command.as_bytes()).await?; + writer.write_all(b"; echo exit code $?\r\n").await?; + let mut read_buf = Vec::new(); + let _ = timeout(Duration::from_secs(5), async { + let mut buf = [0; 4096]; + loop { + let Ok(bytes_read) = reader.read(&mut buf).await else { + break; + }; + let bytes = &buf[..bytes_read]; + if bytes.is_empty() { + continue; + } + read_buf.extend(bytes); + if read_buf.ends_with(b"/ # ") { + break; + } + } + }) + .await; + let string = String::from_utf8_lossy(&read_buf); + if !string.contains(expected_output) { + bail!("{expected_output:?} not found in: {string}"); + } + Ok(()) +} + +pub async fn telnet_send_file(addr: SocketAddr, filename: &str, payload: &[u8]) -> Result<()> { + echo!("Sending file {filename} ... "); + { + let filename = filename.to_owned(); + let handle = tokio::spawn(async move { + telnet_send_command(addr, &format!("nc -l -p 8081 >{filename}.tmp"), "").await + }); + sleep(Duration::from_millis(100)).await; + let mut addr = addr; + addr.set_port(8081); + let mut stream = TcpStream::connect(addr).await?; + stream.write_all(payload).await?; + handle.await??; + } + let checksum = md5::compute(payload); + telnet_send_command( + addr, + &format!("md5sum {filename}.tmp"), + &format!("{checksum:x} {filename}.tmp"), + ) + .await?; + telnet_send_command( + addr, + &format!("mv {filename}.tmp {filename}"), + "exit code 0", + ) + .await?; + println!("ok"); + Ok(()) +} diff --git a/installer/src/wingtech.rs b/installer/src/wingtech.rs index 35aa2b1..576d0df 100644 --- a/installer/src/wingtech.rs +++ b/installer/src/wingtech.rs @@ -15,13 +15,10 @@ use anyhow::{Result, bail}; use base64_light::base64_encode_bytes; use block_padding::{Padding, Pkcs7}; use reqwest::Client; -use tokio::io::AsyncWriteExt; -use tokio::net::TcpStream; use tokio::time::sleep; use crate::InstallWingtech as Args; -use crate::orbic::echo; -use crate::tplink::telnet_send_command; +use crate::util::{echo, telnet_send_command, telnet_send_file}; pub async fn install( Args { @@ -45,7 +42,15 @@ fn encrypt_password(password: &[u8]) -> Result { Ok(base64_encode_bytes(&b)) } -pub async fn start_telnet(admin_ip: &str, admin_password: &str) -> Result { +pub async fn start_telnet(admin_ip: &str, admin_password: &str) -> Result<()> { + run_command(admin_ip, admin_password, "busybox telnetd -l /bin/sh").await +} + +pub async fn start_adb(admin_ip: &str, admin_password: &str) -> Result<()> { + run_command(admin_ip, admin_password, "/sbin/usb/compositions/9025").await +} + +async fn run_command(admin_ip: &str, admin_password: &str, cmd: &str) -> Result<()> { let qcmap_auth_endpoint = format!("http://{admin_ip}/cgi-bin/qcmap_auth"); let qcmap_web_cgi_endpoint = format!("http://{admin_ip}/cgi-bin/qcmap_web_cgi"); @@ -66,9 +71,8 @@ pub async fn start_telnet(admin_ip: &str, admin_password: &str) -> Result None => bail!("login did not return a token in response: {}", login), }; - let cmd = "busybox telnetd -l /bin/sh"; let telnet = client.post(&qcmap_web_cgi_endpoint) - .body(format!("page=setFWMacFilter&cmd=add&mode=0&mac=50:5A:CA:B5:05:AC||{cmd}&key=50:5A:CA:B5:05:AC&token={token}")) + .body(format!("page=setFWMacFilter&cmd=add&mode=0&mac=50:5A:CA:B5:05||{cmd}&key=50:5A:CA:B5:05:AC&token={token}")) .send() .await?; if telnet.status() != 200 { @@ -78,7 +82,7 @@ pub async fn start_telnet(admin_ip: &str, admin_password: &str) -> Result ); } - Ok(true) + Ok(()) } async fn wingtech_run_install(admin_ip: String, admin_password: String) -> Result<()> { @@ -130,71 +134,39 @@ async fn wingtech_run_install(admin_ip: String, admin_password: String) -> Resul telnet_send_command(addr, "reboot", "exit code 0").await?; sleep(Duration::from_secs(30)).await; - echo!("Testing rayhunter... "); - const MAX_FAILURES: u32 = 10; - let mut failures = 0; - let rayhunter_url = format!("http://{admin_ip}:8080/index.html"); - let client = Client::new(); - loop { - match client.get(&rayhunter_url).send().await { - Ok(test) => { - if test.status() == 200 { - println!("rayhunter is running at http://{admin_ip}:8080"); - return Ok(()); - } else { - bail!( - "request for url ({rayhunter_url}) failed with status code: {:?}", - test.status() - ); - } - } - Err(e) => { - if failures > MAX_FAILURES { - return Err(e.into()); - } else { - failures += 1; - sleep(Duration::from_secs(3)).await; - } - } - } - } + echo!("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"); + + Ok(()) } -async fn telnet_send_file(addr: SocketAddr, filename: &str, payload: &[u8]) -> Result<()> { - println!("Sending file {filename}"); - - { - let filename = filename.to_owned(); - let handle = tokio::spawn(async move { - telnet_send_command(addr, &format!("nc -l -p 8081 >{filename}.tmp"), "").await - }); - - sleep(Duration::from_millis(100)).await; - - let mut addr = addr; - addr.set_port(8081); - let mut stream = TcpStream::connect(addr).await?; - stream.write_all(payload).await?; - - handle.await??; +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, + }, + } + sleep(interval).await; } - let checksum = md5::compute(payload); - - telnet_send_command( - addr, - &format!("md5sum {filename}.tmp"), - &format!("{checksum:x} {filename}.tmp"), - ) - .await?; - - telnet_send_command( - addr, - &format!("mv {filename}.tmp {filename}"), - "exit code 0", - ) - .await?; - Ok(()) }