Added range feature and and dns resolving
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Justine Pelletreau 2023-07-14 11:58:00 +02:00
parent 4edb7b08ab
commit 2c8ef69905
3 changed files with 60 additions and 16 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "portnut" name = "portnut"
version = "0.1.0" version = "0.1.1"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -34,16 +34,17 @@ Stressing 127.0.0.1 using 100 concurrent threads per port for 30s
See the help : See the help :
``` ```
1648 justine@portnut > target/release/portnut -h justine@portnut > target/release/portnut -h
A simple TCP port scanner / stresser. If stressing, you can pass a payload via a pipe A simple TCP port scanner / stresser. If stressing, you can pass a payload via a pipe
Usage: portnut [OPTIONS] --address <ADDRESS> Usage: portnut [OPTIONS] --address <ADDRESS>
Options: Options:
-a, --address <ADDRESS> IP address to scan -a, --address <ADDRESS> IP address or hostname to scan
-t, --timeout <TIMEOUT> Timeout for each connection in seconds [default: 1] -t, --timeout <TIMEOUT> Timeout for each connection in seconds [default: 1]
-w, --wait <WAIT> Number of milliseconds to wait in between scans when scanning OR duration of stress when stress testing (in seconds) [default: 30] -w, --wait <WAIT> Number of milliseconds to wait in between scans when scanning OR duration of stress when stress testing (in seconds) [default: 30]
-p, --ports <PORTS>... Ports to stress / scan, separated by commas (22,80) -p, --ports <PORTS>... Ports to stress / scan, separated by commas (22,80)
-r, --range Set this flag to treat the ports as a range rather than a list
-s, --stress Set this flag to stress the ports instead of scanning them -s, --stress Set this flag to stress the ports instead of scanning them
-c, --cthreads <CTHREADS> How many threads per port when stressing (Concurrent Threads) [default: 5] -c, --cthreads <CTHREADS> How many threads per port when stressing (Concurrent Threads) [default: 5]
-h, --help Print help -h, --help Print help

View File

@ -1,10 +1,11 @@
use clap::Parser; use clap::Parser;
use std::io::prelude::*; use std::io::prelude::*;
use std::io::{self, Read}; use std::io::{self, Read};
use std::net::{SocketAddr, TcpStream}; use std::net::{SocketAddr, TcpStream, ToSocketAddrs};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use std::thread; use std::thread;
use atty::Stream; use atty::Stream;
use std::io::BufReader;
fn main() -> std::io::Result<()> { fn main() -> std::io::Result<()> {
@ -12,7 +13,7 @@ fn main() -> std::io::Result<()> {
let address = args.address; let address = args.address;
let timeout = Duration::from_secs(args.timeout); let timeout = Duration::from_secs(args.timeout);
let sleep = Duration::from_millis(args.wait); let sleep = Duration::from_millis(args.wait);
let ports = args.ports; let ports = handle_ports(args.ports.clone(), &args.range);
let threads = args.cthreads; let threads = args.cthreads;
//Reading the payload from stdin (if applicable) //Reading the payload from stdin (if applicable)
@ -34,17 +35,33 @@ fn main() -> std::io::Result<()> {
} }
//Returns a correct list of ports, according to range.
//If range is true, we send a range of ports. Otherwise
//we send the original back
fn handle_ports(ports: Vec<u32>, range: &bool) -> Vec<u32> {
if *range {
if ports.iter().count() != 2 {
panic!("A range must be given only a start and an end value.");
}
let ret: Vec<u32> = (ports[0]..ports[1]).collect();
ret
} else {
ports
}
}
///Scan the ports on address by attempting a tcp connection. ///Scan the ports on address by attempting a tcp connection.
fn tcp_scan(address: String, timeout: Duration, sleep: Duration, ports: Vec<u32>) -> std::io::Result<()> { fn tcp_scan(address: String, timeout: Duration, sleep: Duration, ports: Vec<u32>) -> std::io::Result<()> {
println!("Scanning {}", address); println!("Scanning {}", address);
for i in ports { for i in ports {
let socket = format!("{}:{}", &address, i); let socket = resolve_add(&address.as_str(), &i);
let socket = socket.parse::<SocketAddr>().unwrap();
match TcpStream::connect_timeout(&socket, timeout) { match TcpStream::connect_timeout(&socket, timeout) {
Ok(mut s) => { Ok(mut _s) => {
println!("TCP/{} => ACCEPT", i); println!("TCP/{} => ACCEPT", i);
s.write(b"Some padding")?; //s.write(b"Some padding")?;
} }
Err(_) => { Err(_) => {
println!("TCP/{} => REJECT", i); println!("TCP/{} => REJECT", i);
@ -58,8 +75,18 @@ fn tcp_scan(address: String, timeout: Duration, sleep: Duration, ports: Vec<u32>
Ok(()) Ok(())
} }
///Try to stress the remote machine by attempting to every given port at once and sending random ///Takes either an hostname or an IP address and port, and resolve to a socket address.
///data ///DNS recordings can lead to multiple entries : we take the first one.
fn resolve_add(host: &str, port: &u32) -> SocketAddr {
let details = format!("{}:{}", host, port);
let server: Vec<_> = details
.to_socket_addrs()
.expect("Can't resolve your address")
.collect();
return server[0];
}
///Try to stress the remote machine by attempting to every given port at once and sending payload
fn tcp_stress(payload: String, address: String, timeout: Duration, interval: Duration, ports: Vec<u32>, threads: u32) -> std::io::Result<()> { fn tcp_stress(payload: String, address: String, timeout: Duration, interval: Duration, ports: Vec<u32>, threads: u32) -> std::io::Result<()> {
let mut handles = vec![]; let mut handles = vec![];
@ -72,20 +99,33 @@ fn tcp_stress(payload: String, address: String, timeout: Duration, interval: Dur
let pay = payload.clone(); let pay = payload.clone();
let int = interval * 1000; let int = interval * 1000;
let end_time = Instant::now() + int; let end_time = Instant::now() + int;
//pay.push_str("\r\n");
handles.push(thread::spawn( move || { handles.push(thread::spawn( move || {
let socket = format!("{}:{}", &add, &port); let socket = resolve_add(&add.as_str(), &port);
let socket = socket.parse::<SocketAddr>().unwrap();
loop { loop {
//Check if it is time to stop //Check if it is time to stop
if Instant::now() >= end_time { break; } if Instant::now() >= end_time { break; }
match TcpStream::connect_timeout(&socket, timeout) { match TcpStream::connect_timeout(&socket, timeout) {
Ok(mut s) => { Ok(mut s) => {
s.write(pay.as_bytes()).unwrap(); //Remote port answers
//Writing
s.write_all(pay.as_bytes()).unwrap();
s.flush().unwrap(); s.flush().unwrap();
//s.read(&mut [0; 128]).unwrap();
//Reading
s.set_read_timeout(Some(Duration::from_millis(500))).unwrap();
let mut reader = BufReader::new(s.try_clone().unwrap());
let mut answer = String::new();
let count = reader.read_line(&mut answer).unwrap_or(0);
if count > 0 {
println!("Read {} bytes for port {} -> {}", count, &port, answer);
}
//Sleeping
thread::sleep(Duration::from_millis(10)); thread::sleep(Duration::from_millis(10));
}, },
Err(_) => (), Err(_) => (),
} }
@ -107,7 +147,7 @@ fn tcp_stress(payload: String, address: String, timeout: Duration, interval: Dur
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
struct Args { struct Args {
///IP address to scan ///IP address or hostname to scan
#[arg(short, long)] #[arg(short, long)]
address: String, address: String,
///Timeout for each connection in seconds ///Timeout for each connection in seconds
@ -120,6 +160,9 @@ struct Args {
///Ports to stress / scan, separated by commas (22,80) ///Ports to stress / scan, separated by commas (22,80)
#[arg(short, long, value_parser, num_args=1.., value_delimiter=',')] #[arg(short, long, value_parser, num_args=1.., value_delimiter=',')]
ports: Vec<u32>, ports: Vec<u32>,
///Set this flag to treat the ports as a range rather than a list
#[arg(long, short, action, default_value_t = false)]
range: bool,
///Set this flag to stress the ports instead of scanning them ///Set this flag to stress the ports instead of scanning them
#[arg(long, short, action, default_value_t = false)] #[arg(long, short, action, default_value_t = false)]
stress: bool, stress: bool,