mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-04-27 16:09:58 -07:00
feat(installer/util): telnet functions in util.rs
Add installer util wingtech-start-telnet command. Add installer util wingtech-start-adb command.
This commit is contained in:
@@ -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")?,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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... ");
|
||||
|
||||
@@ -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<HttpConnector, Body>;
|
||||
|
||||
@@ -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,
|
||||
|
||||
90
installer/src/util.rs
Normal file
90
installer/src/util.rs
Normal file
@@ -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(())
|
||||
}
|
||||
@@ -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<String> {
|
||||
Ok(base64_encode_bytes(&b))
|
||||
}
|
||||
|
||||
pub async fn start_telnet(admin_ip: &str, admin_password: &str) -> Result<bool> {
|
||||
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<bool>
|
||||
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<bool>
|
||||
);
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user