Various fixes for TP-Link

* explicitly mount the SD card to improve reliability
* do not crash when the SD card is already mounted
* address some review feedback
This commit is contained in:
Markus Unterwaditzer
2025-05-07 12:50:30 +02:00
committed by Cooper Quintin
parent 4a7452806d
commit c285e2ca08
4 changed files with 75 additions and 25 deletions

View File

@@ -4,8 +4,8 @@ use clap::{Parser, Subcommand};
mod orbic;
mod tplink;
pub static CONFIG_TOML: &[u8] = include_bytes!("../../dist/config.toml.example");
pub static RAYHUNTER_DAEMON_INIT: &[u8] = include_bytes!("../../dist/scripts/rayhunter_daemon");
pub static CONFIG_TOML: &str = include_str!("../../dist/config.toml.example");
pub static RAYHUNTER_DAEMON_INIT: &str = include_str!("../../dist/scripts/rayhunter_daemon");
#[derive(Parser, Debug)]
#[command(version, about)]
@@ -42,13 +42,22 @@ struct InstallOrbic {}
#[derive(Parser, Debug)]
struct Util {
#[command(subcommand)]
command: UntilSubCommand,
command: UtilSubCommand,
}
#[derive(Subcommand, Debug)]
enum UntilSubCommand {
enum UtilSubCommand {
/// Send a serial command to the Orbic.
Serial(Serial),
/// Root the tplink and launch telnetd.
TplinkStartTelnet(TplinkStartTelnet),
}
#[derive(Parser, Debug)]
struct TplinkStartTelnet {
/// IP address for TP-Link admin interface, if custom.
#[arg(long, default_value = "192.168.0.1")]
admin_ip: String,
}
#[derive(Parser, Debug)]
@@ -65,7 +74,7 @@ async fn run_function() -> Result<(), Error> {
Command::Tplink(tplink) => tplink::main_tplink(tplink).await.context("Failed to install rayhunter on the TP-Link M7350. Make sure your computer is connected to the hotspot using USB tethering or WiFi.")?,
Command::Orbic(_) => orbic::install().await.context("Failed to install rayhunter on the Orbic RC400L")?,
Command::Util(subcommand) => match subcommand.command {
UntilSubCommand::Serial(serial_cmd) => {
UtilSubCommand::Serial(serial_cmd) => {
if serial_cmd.root {
if !serial_cmd.command.is_empty() {
eprintln!("You cannot use --root and specify a command at the same time");
@@ -83,6 +92,9 @@ async fn run_function() -> Result<(), Error> {
}
}
}
UtilSubCommand::TplinkStartTelnet(options) => {
tplink::start_telnet(&options.admin_ip).await?;
}
}
}

View File

@@ -116,14 +116,14 @@ async fn setup_rayhunter(
serial_interface,
&mut adb_device,
"/data/rayhunter/config.toml",
CONFIG_TOML,
CONFIG_TOML.as_bytes(),
)
.await?;
install_file(
serial_interface,
&mut adb_device,
"/etc/init.d/rayhunter_daemon",
RAYHUNTER_DAEMON_INIT,
RAYHUNTER_DAEMON_INIT.as_bytes(),
)
.await?;
install_file(

View File

@@ -23,20 +23,25 @@ use crate::InstallTpLink;
type HttpProxyClient = hyper_util::client::legacy::Client<HttpConnector, Body>;
pub async fn main_tplink(args: InstallTpLink) -> Result<(), Error> {
let InstallTpLink {
pub async fn main_tplink(
InstallTpLink {
skip_sdcard,
admin_ip,
} = args;
}: InstallTpLink,
) -> Result<(), Error> {
start_telnet(&admin_ip).await?;
tplink_run_install(skip_sdcard, admin_ip).await
}
#[derive(Deserialize)]
struct V3RootResponse {
result: u64,
}
pub async fn start_telnet(admin_ip: &str) -> Result<(), Error> {
let qcmap_web_cgi_endpoint = format!("http://{admin_ip}/cgi-bin/qcmap_web_cgi");
let client = reqwest::Client::new();
#[derive(Deserialize)]
struct RootResponse {
result: u64,
}
println!("Launching telnet on the device");
// https://github.com/advisories/GHSA-ffwq-9r7p-3j6r
@@ -48,9 +53,9 @@ pub async fn main_tplink(args: InstallTpLink) -> Result<(), Error> {
if response.status() == 404 {
println!("Got a 404 trying to run exploit for hardware revision v3, trying v5 exploit");
tplink_launch_telnet_v5(admin_ip.clone()).await?;
tplink_launch_telnet_v5(admin_ip).await?;
} else {
let RootResponse { result } = response.error_for_status()?.json().await?;
let V3RootResponse { result } = response.error_for_status()?.json().await?;
if result != 0 {
anyhow::bail!("Bad result code when trying to root device: {result}");
@@ -59,9 +64,10 @@ pub async fn main_tplink(args: InstallTpLink) -> Result<(), Error> {
println!("Detected hardware revision v3");
}
println!("Succeeded in rooting the device!");
tplink_run_install(skip_sdcard, admin_ip).await
println!(
"Succeeded in rooting the device! Now you can use 'telnet {admin_ip}' to get a root shell. Use './installer util tplink-start-telnet' to root again without installing rayhunter."
);
Ok(())
}
async fn tplink_run_install(skip_sdcard: bool, admin_ip: String) -> Result<(), Error> {
@@ -70,7 +76,14 @@ async fn tplink_run_install(skip_sdcard: bool, admin_ip: String) -> Result<(), E
if !skip_sdcard {
println!("Mounting sdcard");
telnet_send_command(addr, "mount /dev/mmcblk0p1 /media/card", "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")?;
if telnet_send_command(addr, "mount | grep -q /media/card", "exit code 0")
.await
.is_err()
{
telnet_send_command(addr, "mount /dev/mmcblk0p1 /media/card", "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")?;
} else {
println!("sdcard already mounted");
}
}
// there is too little space on the internal flash to store anything, but the initrd script
@@ -79,7 +92,12 @@ async fn tplink_run_install(skip_sdcard: bool, admin_ip: String) -> Result<(), E
telnet_send_command(addr, "mkdir -p /data", "exit code 0").await?;
telnet_send_command(addr, "ln -sf /media/card /data/rayhunter", "exit code 0").await?;
telnet_send_file(addr, "/media/card/config.toml", crate::CONFIG_TOML).await?;
telnet_send_file(
addr,
"/media/card/config.toml",
crate::CONFIG_TOML.as_bytes(),
)
.await?;
#[cfg(feature = "vendor")]
let rayhunter_daemon_bin = include_bytes!("../../rayhunter-daemon-tplink/rayhunter-daemon");
@@ -92,7 +110,7 @@ async fn tplink_run_install(skip_sdcard: bool, admin_ip: String) -> Result<(), E
telnet_send_file(
addr,
"/etc/init.d/rayhunter_daemon",
crate::RAYHUNTER_DAEMON_INIT,
get_rayhunter_daemon().as_bytes(),
)
.await?;
@@ -260,7 +278,7 @@ async fn handler(state: State<AppState>, mut req: Request) -> Result<Response, S
Ok(response)
}
async fn tplink_launch_telnet_v5(admin_ip: String) -> Result<(), Error> {
async fn tplink_launch_telnet_v5(admin_ip: &str) -> Result<(), Error> {
let client: HttpProxyClient =
hyper_util::client::legacy::Client::<(), ()>::builder(TokioExecutor::new())
.build(HttpConnector::new());
@@ -270,7 +288,7 @@ async fn tplink_launch_telnet_v5(admin_ip: String) -> Result<(), Error> {
.route("/{*path}", any(handler))
.with_state(AppState {
client,
admin_ip: admin_ip.clone(),
admin_ip: admin_ip.to_owned(),
});
let listener = tokio::net::TcpListener::bind("127.0.0.1:4000")
@@ -295,3 +313,21 @@ async fn tplink_launch_telnet_v5(admin_ip: String) -> Result<(), Error> {
Ok(())
}
fn get_rayhunter_daemon() -> String {
// Even though TP-Link eventually auto-mounts the SD card, it sometimes does so too late. And
// changing the order in which daemons are started up seems to not work reliably.
//
// This part of the daemon dynamically generated because we may have to eventually add logic
// specific to a particular hardware revision here.
crate::RAYHUNTER_DAEMON_INIT.replace(
"#RAYHUNTER-PRESTART",
"mount /dev/mmcblk0p1 /media/card || true",
)
}
#[test]
fn test_get_rayhunter_daemon() {
let s = get_rayhunter_daemon();
assert!(s.contains("mount /dev/mmcblk0p1 /media/card"));
}