mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-06-10 23:03:31 -07:00
Implement basic telnet shell for both orbic and tplink
This commit is contained in:
committed by
Will Greenberg
parent
676cd3c862
commit
5fbc540fa0
Generated
+14
-1
@@ -1802,6 +1802,7 @@ checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
@@ -1883,6 +1884,7 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
@@ -2455,7 +2457,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core 0.57.0",
|
||||
"windows-core 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2711,6 +2713,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"clap",
|
||||
"env_logger 0.11.8",
|
||||
"futures",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"md5 0.7.0",
|
||||
@@ -2719,6 +2722,7 @@ dependencies = [
|
||||
"reqwest",
|
||||
"serde",
|
||||
"sha2",
|
||||
"termios",
|
||||
"tokio",
|
||||
"tokio-retry2",
|
||||
"tokio-stream",
|
||||
@@ -5972,6 +5976,15 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termios"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
|
||||
@@ -31,6 +31,10 @@ sha2 = "0.10.8"
|
||||
tokio = { version = "1.44.2", features = ["io-util", "macros", "rt"], default-features = false }
|
||||
tokio-retry2 = "0.5.7"
|
||||
tokio-stream = "0.1.17"
|
||||
futures = "0.3"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
termios = "0.3"
|
||||
|
||||
[target.'cfg(all(target_os = "linux", not(target_os = "android")))'.dependencies.adb_client]
|
||||
git = "https://github.com/EFForg/adb_client.git"
|
||||
|
||||
@@ -126,6 +126,8 @@ enum UtilSubCommand {
|
||||
Uz801StartAdb(Uz801Args),
|
||||
/// Root the tplink and launch telnetd.
|
||||
TplinkStartTelnet(TplinkStartTelnet),
|
||||
/// Root the TP-Link and open an interactive shell.
|
||||
TplinkShell(TplinkStartTelnet),
|
||||
/// Root the Wingtech and launch telnetd.
|
||||
WingtechStartTelnet(WingtechArgs),
|
||||
/// Root the Wingtech and launch adb.
|
||||
@@ -138,6 +140,8 @@ enum UtilSubCommand {
|
||||
PinephoneStopAdb,
|
||||
/// Root the Orbic and launch telnetd.
|
||||
OrbicStartTelnet(OrbicNetworkArgs),
|
||||
/// Root the Orbic and open an interactive shell.
|
||||
OrbicShell(OrbicNetworkArgs),
|
||||
/// Send a file to the TP-Link device over telnet.
|
||||
///
|
||||
/// Before running this utility, you need to make telnet accessible with `installer util
|
||||
@@ -261,6 +265,9 @@ async fn run(args: Args) -> Result<(), Error> {
|
||||
UtilSubCommand::TplinkStartTelnet(options) => {
|
||||
tplink::start_telnet(&options.admin_ip).await?;
|
||||
}
|
||||
UtilSubCommand::TplinkShell(options) => {
|
||||
tplink::shell(&options.admin_ip).await.context("\nFailed to open shell on TP-Link device")?;
|
||||
}
|
||||
UtilSubCommand::TplinkSendFile(options) => {
|
||||
util::send_file(&options.admin_ip, &options.local_path, &options.remote_path).await?;
|
||||
}
|
||||
@@ -274,6 +281,7 @@ async fn run(args: Args) -> Result<(), Error> {
|
||||
#[cfg(not(target_os = "android"))]
|
||||
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, &args.admin_username, args.admin_password.as_deref()).await.context("\\nFailed to start telnet on the Orbic RC400L")?,
|
||||
UtilSubCommand::OrbicShell(args) => orbic_network::shell(&args.admin_ip, &args.admin_username, args.admin_password.as_deref()).await.context("\nFailed to open shell on Orbic RC400L")?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use tokio::time::sleep;
|
||||
|
||||
use crate::orbic_auth::{LoginInfo, LoginRequest, LoginResponse, encode_password};
|
||||
use crate::output::{eprintln, print, println};
|
||||
use crate::util::{telnet_send_command, telnet_send_file};
|
||||
use crate::util::{interactive_shell, telnet_send_command, telnet_send_file};
|
||||
use crate::{CONFIG_TOML, RAYHUNTER_DAEMON_INIT};
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
@@ -270,3 +270,16 @@ async fn setup_rayhunter(admin_ip: &str) -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Root the Orbic device and open an interactive shell
|
||||
pub async fn shell(
|
||||
admin_ip: &str,
|
||||
admin_username: &str,
|
||||
admin_password: Option<&str>,
|
||||
) -> Result<()> {
|
||||
start_telnet(admin_ip, admin_username, admin_password).await?;
|
||||
eprintln!(
|
||||
"This terminal is fairly limited. The shell prompt may not be visible, but it still accepts commands."
|
||||
);
|
||||
interactive_shell(admin_ip, 24, false).await
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ use tokio::time::sleep;
|
||||
|
||||
use crate::InstallTpLink;
|
||||
use crate::output::println;
|
||||
use crate::util::{telnet_send_command, telnet_send_file};
|
||||
use crate::util::{interactive_shell, telnet_send_command, telnet_send_file};
|
||||
|
||||
type HttpProxyClient = hyper_util::client::legacy::Client<HttpConnector, Body>;
|
||||
|
||||
@@ -383,6 +383,12 @@ fn get_rayhunter_daemon(sdcard_path: &str) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
/// Root the TP-Link device and open an interactive shell
|
||||
pub async fn shell(admin_ip: &str) -> Result<(), Error> {
|
||||
start_telnet(admin_ip).await?;
|
||||
interactive_shell(admin_ip, 23, true).await
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_rayhunter_daemon() {
|
||||
let s = get_rayhunter_daemon("/media/card");
|
||||
|
||||
@@ -11,6 +11,9 @@ use tokio::time::{sleep, timeout};
|
||||
|
||||
use crate::output::{print, println};
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::fd::AsRawFd;
|
||||
|
||||
pub async fn telnet_send_command_with_output(
|
||||
addr: SocketAddr,
|
||||
command: &str,
|
||||
@@ -225,3 +228,84 @@ pub fn open_usb_device(vid: u16, pid: u16) -> Result<Option<Device>> {
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Open an interactive shell to a device
|
||||
///
|
||||
/// Connects to a shell service on the device and forwards stdin/stdout bidirectionally.
|
||||
pub async fn interactive_shell(admin_ip: &str, shell_port: u16, raw_mode: bool) -> Result<()> {
|
||||
let shell_addr = SocketAddr::from_str(&format!("{admin_ip}:{shell_port}"))?;
|
||||
let mut stream = TcpStream::connect(shell_addr)
|
||||
.await
|
||||
.context("Failed to connect to shell. Make sure the device is reachable.")?;
|
||||
|
||||
let stdin = tokio::io::stdin();
|
||||
|
||||
#[cfg(unix)]
|
||||
let raw_terminal_guard = if raw_mode {
|
||||
Some(RawTerminal::new(stdin.as_raw_fd())?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// suppress "unused variable" lint
|
||||
#[cfg(not(unix))]
|
||||
let _used = raw_mode;
|
||||
|
||||
let mut stdio = tokio::io::join(stdin, tokio::io::stdout());
|
||||
let _ = tokio::io::copy_bidirectional(&mut stream, &mut stdio).await;
|
||||
|
||||
// hitting ctrl-d will not print a trailing newline on tplink at least, which messes up the
|
||||
// next prompt
|
||||
println!();
|
||||
|
||||
// The current_thread runtime in tokio will block forever until stdin receives a read error. To
|
||||
// work around this cleanup issue we just exit directly from here.
|
||||
//
|
||||
// This is documented as a flaw in tokio::io::stdin()'s own docs, but the recommended
|
||||
// workaround to spawn your own OS thread doesn't work.
|
||||
//
|
||||
// For some reason this only happens when the terminal is being put in raw mode (removing
|
||||
// RawTerminal fixes it)
|
||||
//
|
||||
// We have to drop the RawTerminal guard before exiting, otherwise we will
|
||||
// mess up the terminal.
|
||||
#[cfg(unix)]
|
||||
drop(raw_terminal_guard);
|
||||
std::process::exit(0)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
struct RawTerminal {
|
||||
fd: std::os::fd::RawFd,
|
||||
original_termios: termios::Termios,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl RawTerminal {
|
||||
fn new(fd: std::os::fd::RawFd) -> Result<Self> {
|
||||
// put terminal in raw mode so that arrow keys, tab etc are correctly forwarded to the
|
||||
// device's shell
|
||||
let mut original_termios = termios::Termios::from_fd(fd)?;
|
||||
let raw_termios = original_termios;
|
||||
|
||||
original_termios.c_lflag &= !(termios::ICANON | termios::ECHO | termios::ISIG);
|
||||
original_termios.c_iflag &= !(termios::IXON | termios::ICRNL);
|
||||
original_termios.c_oflag &= !termios::OPOST;
|
||||
original_termios.c_cc[termios::VMIN] = 1;
|
||||
original_termios.c_cc[termios::VTIME] = 0;
|
||||
|
||||
termios::tcsetattr(fd, termios::TCSANOW, &original_termios)?;
|
||||
|
||||
Ok(RawTerminal {
|
||||
fd,
|
||||
original_termios: raw_termios,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl Drop for RawTerminal {
|
||||
fn drop(&mut self) {
|
||||
let _ = termios::tcsetattr(self.fd, termios::TCSANOW, &self.original_termios);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user