Reference for maintainers and contributors writing new Rustsploit modules.
Rustsploit uses a build-time code-generation approach — no manual registry:
-
build.rsscan — Before compilation,build.rsrecursively walkssrc/modules/looking for.rsfiles that are notmod.rs. -
Signature detection — A file that exposes
pub async fn run(is treated as a callable module. -
Name generation — Both a short name (
ssh_bruteforce) and a qualified path (creds/generic/ssh_bruteforce) are registered. -
Dispatcher emission — Generated files are written into
OUT_DIR(not the source tree):exploit_dispatch.rscreds_dispatch.rsscanner_dispatch.rsplugins_dispatch.rsmodule_registry.rs
Each dispatch file contains an exhaustive
matchmapping names →use crate::modules::...::run. The registry file provides a unified module listing across all categories. -
Shell + CLI resolution —
use exploits/fooor--module fooboth resolve through the dispatcher.
Because it's generated at build time, there is no manual registry drift as long as modules live in the correct folder and export run.
- No dead code. All code must be intentional and used. Do not leave unused functions, imports, or variables.
- No
unsafeblocks. Do not useunsafeRust anywhere in this codebase.
rustsploit/
├── Cargo.toml
├── build.rs # Generates dispatcher by scanning src/modules
├── src/
│ ├── main.rs # Entry point — CLI or shell mode, input validation
│ ├── cli.rs # Clap-based CLI parser and dispatcher
│ ├── shell.rs # Interactive shell loop + UX helpers
│ ├── api.rs # REST + WebSocket API server — PQ encryption, rate limiting
│ ├── ws.rs # PQ-encrypted WebSocket transport (/pq/ws)
│ ├── config.rs # Global config and target validation
│ ├── module_info.rs # ModuleInfo, CheckResult, ModuleRank types
│ ├── global_options.rs # Persistent global options (setg/unsetg)
│ ├── cred_store.rs # Credential store (JSON persistence)
│ ├── spool.rs # Console output logging
│ ├── workspace.rs # Host/service tracking + workspaces
│ ├── loot.rs # Loot/evidence management
│ ├── export.rs # JSON/CSV/summary report export
│ ├── jobs.rs # Background job management
│ ├── mcp/
│ │ ├── mod.rs # MCP server entry point (--mcp flag)
│ │ ├── server.rs # JSON-RPC stdio transport with binary-safe reads
│ │ └── tools.rs # 38 MCP tool implementations
│ ├── commands/
│ │ ├── mod.rs # Module discovery, fuzzy matching, multi-target dispatch
│ │ ├── exploit.rs
│ │ ├── scanner.rs
│ │ └── creds.rs
│ ├── modules/
│ │ ├── exploits/ # Exploit modules (183 modules, 21 categories)
│ │ ├── scanners/ # Scanner modules (27 modules)
│ │ ├── creds/ # Credential modules (29 modules)
│ │ └── plugins/ # Plugin modules (1 module)
│ ├── native/ # Native integrations
│ │ ├── mod.rs
│ │ ├── rdp.rs # Native RDP auth (X.224, TLS, CredSSP/NTLM)
│ │ ├── payload_engine.rs # Payload encoding/generation
│ │ ├── url_encoding.rs # URL encoding utilities
│ │ └── async_tls.rs # Async TLS helpers
│ └── utils/ # Shared helpers (directory module)
│ ├── mod.rs # Re-exports
│ ├── prompt.rs # Config-aware prompts (cfg_prompt_*)
│ ├── sanitize.rs # Input validation, length limits
│ ├── target.rs # Target normalization (IPv4/IPv6/CIDR/hostname)
│ ├── network.rs # HTTP client builders, TCP/UDP connect helpers
│ ├── privilege.rs # Root privilege check (require_root)
│ └── modules.rs # Module discovery helpers
├── docs/ # This wiki
├── lists/ # Wordlists and data files
└── README.md # Product overview
Every module must export:
use anyhow::Result;
pub async fn run(target: &str) -> Result<()> {
// ...
Ok(())
}Optional: also expose pub async fn run_interactive(target: &str) -> Result<()> for modules with multiple code paths.
Modules can optionally provide metadata and vulnerability check functions. These are auto-detected by build.rs alongside run():
use crate::module_info::{ModuleInfo, ModuleRank};
pub fn info() -> ModuleInfo {
ModuleInfo {
name: "My Exploit Module".to_string(),
description: "Exploits CVE-XXXX-YYYY in FooBar device firmware.".to_string(),
authors: vec!["Your Name".to_string()],
references: vec![
"CVE-XXXX-YYYY".to_string(),
"https://example.com/advisory".to_string(),
],
disclosure_date: Some("2025-01-15".to_string()),
rank: ModuleRank::Good,
}
}The info shell command and GET /api/module/{category}/{name} endpoint display this metadata.
Rank values: Excellent (reliable, no crash risk), Great, Good (default), Normal, Low, Manual.
use crate::module_info::CheckResult;
pub async fn check(target: &str) -> CheckResult {
// Non-destructive verification — do NOT exploit
match test_vulnerability(target).await {
Ok(true) => CheckResult::Vulnerable("Version 1.2.3 is affected".to_string()),
Ok(false) => CheckResult::NotVulnerable("Patched version detected".to_string()),
Err(e) => CheckResult::Error(format!("Check failed: {}", e)),
}
}The check shell command and POST /api/check endpoint run this without exploitation.
Modules can auto-store discovered data:
// Store a found credential
crate::cred_store::store_credential(host, port, "ssh", username, password,
crate::cred_store::CredType::Password, "creds/generic/ssh_bruteforce");
// Store loot (config file, hash dump, etc.)
crate::loot::store_loot(host, "config", "Router config dump", data.as_bytes(), "exploits/router_rce");
// Track a discovered host/service
crate::workspace::track_host(ip, Some("router.local"), Some("Linux 4.x"));
crate::workspace::track_service(ip, 22, "tcp", "ssh", Some("OpenSSH 8.9"));- Choose a location under
src/modules/{exploits,scanners,creds}.
Use subfolders for vendor families (e.g.,exploits/cisco/). - Create the
.rsfile with the requiredpub async fn runsignature. - Register in
mod.rs— addpub mod your_module;to the siblingmod.rs.
Without this,build.rsignores the file. - Run
cargo check— the dispatcher is regenerated automatically.
use anyhow::{Context, Result};
use colored::Colorize;
use crate::utils::{normalize_target, cfg_prompt_port, cfg_prompt_yes_no};
pub async fn run(target: &str) -> Result<()> {
let target = normalize_target(target)?;
let port = cfg_prompt_port("port", "Target port", 80).await?;
let verbose = cfg_prompt_yes_no("verbose", "Verbose output?", false).await?;
println!("{} Checking {}:{}", "[*]".cyan(), target, port);
let url = format!("http://{}:{}/status", target, port);
let body = reqwest::get(&url)
.await
.with_context(|| format!("Failed to reach {}", url))?
.text()
.await
.context("Failed to read response body")?;
if body.contains("vulnerable") {
println!("{} {} appears vulnerable", "[+]".green(), target);
} else {
if verbose {
println!("{} Response: {}", "[*]".cyan(), body);
}
println!("{} {} not vulnerable", "[-]".red(), target);
}
Ok(())
}| Prefix | Color | Meaning |
|---|---|---|
[+] |
Green | Success / found |
[-] |
Red | Not found / not vulnerable |
[!] |
Yellow | Warning |
[*] |
Cyan | Info / progress |
Use .green(), .red(), .yellow(), .cyan() from the colored crate. Keep messages short and actionable.
- Prefer
reqwest,tokio::net,tokio::processfor async work. - Wrap synchronous blocking calls with
tokio::task::spawn_blocking(see the SSH module for reference). - For concurrency:
tokio::sync::Semaphore(wrapped inArc) for async modules.threadpool+crossbeam-channelfor synchronous protocols (Telnet, POP3).
Bubble up errors using anyhow::Context so the shell/CLI surface meaningful messages:
.with_context(|| format!("Failed to connect to {}", target))?Avoid unwrap() and unwrap_or_default() in critical paths.
Store under lists/ and document them in lists/readme.md. Reference paths relative to the working directory.
The framework's command dispatcher (src/commands/mod.rs) automatically handles multiple target types for all modules. Module authors do not need to implement multi-target logic themselves -- the dispatcher wraps each module's run() function and handles:
- Comma-separated targets:
192.168.1.1,192.168.1.2,10.0.0.1-- splits and dispatches each entry individually. - CIDR subnets:
192.168.1.0/24-- expands the subnet and runs the module against each host IP. - File-based target lists: If the target string is a path to an existing file, each line is read and dispatched as a separate target.
- Random mass scan:
0.0.0.0,0.0.0.0/0, orrandom-- generates random public IPs in an infinite loop (Ctrl+C to stop).
This means a module that only handles a single host in its run() function automatically gains subnet scanning, file-based targeting, and mass-scan capability through the framework.
Modules supporting mass-scan accept 0.0.0.0, 0.0.0.0/0, or random as targets. When detected, the module enters an infinite loop generating random public IPs using:
fn generate_random_public_ip() -> Ipv4Addr { ... }
fn is_excluded_ip(ip: Ipv4Addr) -> bool { ... }The EXCLUDED_RANGES constant covers bogons, private, reserved, documentation CIDRs, and public DNS servers. Copy this pattern from an existing mass-scan module (e.g., telnet_hose or hikvision_rce).
Honeypot detection is disabled in mass-scan mode to avoid interactive prompts.