mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-04-27 16:09:58 -07:00
feat: support Tmobile TMOHS1 hotspot
Add support for the Tmobile TMOHS1, a Wingtech CT2MHS01-based hotspot with a Qualcomm mdm9607. The TMOHS1 has no screen, only 5 LEDs, two of which are RGB.
This commit is contained in:
committed by
Cooper Quintin
parent
f23cc07652
commit
b7636386fc
187
installer/src/tmobile.rs
Normal file
187
installer/src/tmobile.rs
Normal file
@@ -0,0 +1,187 @@
|
||||
/// Installer for the TMobile TMOHS1 hotspot.
|
||||
///
|
||||
/// Tested on (from `/etc/wt_version`):
|
||||
/// WT_INNER_VERSION=SW_Q89527AA1_V045_M11_TMO_USR_MP
|
||||
/// WT_PRODUCTION_VERSION=TMOHS1_00.05.20
|
||||
/// WT_HARDWARE_VERSION=89527_1_11
|
||||
use std::io::Write;
|
||||
use std::net::SocketAddr;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use aes::Aes128;
|
||||
use aes::cipher::{BlockEncrypt, KeyInit, generic_array::GenericArray};
|
||||
use anyhow::{Result, bail};
|
||||
use base64_light::base64_encode_bytes;
|
||||
use block_padding::{Padding, Pkcs7};
|
||||
use reqwest::Client;
|
||||
use tokio::time::sleep;
|
||||
|
||||
use crate::TmobileArgs as Args;
|
||||
use crate::util::{echo, telnet_send_command, telnet_send_file};
|
||||
|
||||
pub async fn install(
|
||||
Args {
|
||||
admin_ip,
|
||||
admin_password,
|
||||
}: Args,
|
||||
) -> Result<()> {
|
||||
run_install(admin_ip, admin_password).await
|
||||
}
|
||||
|
||||
const KEY: &[u8] = b"abcdefghijklmn12";
|
||||
|
||||
/// Returns password encrypted in AES128 ECB mode with the key b"abcdefghijklmn12",
|
||||
/// with Pkcs7 padding, encoded in base64.
|
||||
fn encrypt_password(password: &[u8]) -> Result<String> {
|
||||
let c = Aes128::new_from_slice(KEY)?;
|
||||
let mut b = GenericArray::from([0u8; 16]);
|
||||
b[..password.len()].copy_from_slice(password);
|
||||
Pkcs7::pad(&mut b, password.len());
|
||||
c.encrypt_block(&mut b);
|
||||
Ok(base64_encode_bytes(&b))
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
let encrypted_pw = encrypt_password(admin_password.as_bytes()).ok().unwrap();
|
||||
|
||||
let client = Client::new();
|
||||
let login = client
|
||||
.post(&qcmap_auth_endpoint)
|
||||
.body(format!(
|
||||
"type=login&pwd={encrypted_pw}&timeout=60000&user=admin"
|
||||
))
|
||||
.send()
|
||||
.await?
|
||||
.text()
|
||||
.await?;
|
||||
let token = match login.find("token") {
|
||||
Some(n) => &login[n + 8..n + 8 + 16],
|
||||
None => bail!("login did not return a token in response: {}", login),
|
||||
};
|
||||
|
||||
let telnet = client.post(&qcmap_web_cgi_endpoint)
|
||||
.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 {
|
||||
bail!(
|
||||
"starting telnet failed with status code: {:?}",
|
||||
telnet.status()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_install(admin_ip: String, admin_password: String) -> Result<()> {
|
||||
echo!("Starting telnet ... ");
|
||||
start_telnet(&admin_ip, &admin_password).await?;
|
||||
println!("ok");
|
||||
|
||||
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?;
|
||||
println!("ok");
|
||||
|
||||
telnet_send_command(addr, "mount -o remount,rw /", "exit code 0").await?;
|
||||
|
||||
telnet_send_file(
|
||||
addr,
|
||||
"/data/rayhunter/config.toml",
|
||||
crate::CONFIG_TOML.as_bytes(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let rayhunter_daemon_bin = include_bytes!(env!("FILE_RAYHUNTER_DAEMON_TMOBILE"));
|
||||
telnet_send_file(
|
||||
addr,
|
||||
"/data/rayhunter/rayhunter-daemon",
|
||||
rayhunter_daemon_bin,
|
||||
)
|
||||
.await?;
|
||||
telnet_send_command(
|
||||
addr,
|
||||
"chmod 755 /data/rayhunter/rayhunter-daemon",
|
||||
"exit code 0",
|
||||
)
|
||||
.await?;
|
||||
telnet_send_file(
|
||||
addr,
|
||||
"/etc/init.d/misc-daemon",
|
||||
include_bytes!("../../dist/scripts/misc-daemon"),
|
||||
)
|
||||
.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(),
|
||||
)
|
||||
.await?;
|
||||
telnet_send_command(
|
||||
addr,
|
||||
"chmod 755 /etc/init.d/rayhunter_daemon",
|
||||
"exit code 0",
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!("Rebooting device and waiting 30 seconds for it to start up.");
|
||||
telnet_send_command(addr, "reboot", "exit code 0").await?;
|
||||
sleep(Duration::from_secs(30)).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 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;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_password() {
|
||||
let p = b"80536913";
|
||||
let s = encrypt_password(p).ok();
|
||||
let expected = Some("5brvd8xl732cSoFTAy67ig==".to_string());
|
||||
assert_eq!(s, expected);
|
||||
}
|
||||
Reference in New Issue
Block a user