initial commit
This commit is contained in:
114
src/main.rs
Normal file
114
src/main.rs
Normal file
@ -0,0 +1,114 @@
|
||||
use clap::Parser;
|
||||
use std::io;
|
||||
use std::net::{TcpStream, ToSocketAddrs, UdpSocket};
|
||||
use std::str::FromStr;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Protocol {
|
||||
Tcp,
|
||||
Udp,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct KnockTarget {
|
||||
port: u16,
|
||||
protocol: Protocol,
|
||||
}
|
||||
|
||||
impl FromStr for KnockTarget {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let parts: Vec<&str> = s.split('/').collect();
|
||||
if parts.len() != 2 {
|
||||
return Err("Invalid format. Expected PORT/PROTOCOL".to_string());
|
||||
}
|
||||
|
||||
let port = parts[0]
|
||||
.parse::<u16>()
|
||||
.map_err(|e| format!("Invalid port number: {}", e))?;
|
||||
let protocol = match parts[1].to_lowercase().as_str() {
|
||||
"tcp" => Protocol::Tcp,
|
||||
"udp" => Protocol::Udp,
|
||||
_ => return Err("Invalid protocol. Use 'tcp' or 'udp'".to_string()),
|
||||
};
|
||||
|
||||
Ok(KnockTarget { port, protocol })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Cli {
|
||||
/// The target to connect to after knocking, e.g., 127.0.0.1:22
|
||||
#[arg(required = true)]
|
||||
target_address: String,
|
||||
|
||||
/// Port knocking sequence, e.g., --knock 1000/tcp --knock 2000/udp
|
||||
#[arg(long, short, value_parser = clap::value_parser!(KnockTarget), required = true, env = "PORT_KNOCKING_SEQ", value_delimiter = ':', num_args = 1..)]
|
||||
knock: Vec<KnockTarget>,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
env_logger::builder().target(env_logger::Target::Stderr).init();
|
||||
|
||||
|
||||
let (target_host, _target_port) = {
|
||||
let parts: Vec<&str> = cli.target_address.split(':').collect();
|
||||
if parts.len() != 2 {
|
||||
return Err("Invalid target address format. Expected HOST:PORT".into());
|
||||
}
|
||||
(parts[0], parts[1].parse::<u16>()?)
|
||||
};
|
||||
|
||||
log::info!("Starting port knocking sequence...");
|
||||
|
||||
for knock_target in &cli.knock {
|
||||
let addr = format!("{}:{}", target_host, knock_target.port)
|
||||
.to_socket_addrs()?
|
||||
.next()
|
||||
.ok_or("Could not resolve host")?;
|
||||
|
||||
match knock_target.protocol {
|
||||
Protocol::Tcp => {
|
||||
log::debug!("Knocking on TCP port {}", knock_target.port);
|
||||
let timeout = Duration::from_secs(1);
|
||||
if TcpStream::connect_timeout(&addr, timeout).is_err() {
|
||||
// We expect errors here, like connection refused or timeout.
|
||||
// The goal is just to hit the port.
|
||||
}
|
||||
}
|
||||
Protocol::Udp => {
|
||||
log::debug!("Knocking on UDP port {}", knock_target.port);
|
||||
let socket = UdpSocket::bind("0.0.0.0:0")?;
|
||||
socket.send_to(&[], addr)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log::debug!("Knocking sequence finished. Connecting to {}...", cli.target_address);
|
||||
|
||||
let mut stream = TcpStream::connect(&cli.target_address)?;
|
||||
let mut stream_clone = stream.try_clone()?;
|
||||
|
||||
log::info!("Connection established. Proxying data...");
|
||||
|
||||
let stdin_thread = thread::spawn(move || {
|
||||
let mut stdin = io::stdin();
|
||||
io::copy(&mut stdin, &mut stream_clone).ok();
|
||||
});
|
||||
|
||||
let stdout_thread = thread::spawn(move || {
|
||||
let mut stdout = io::stdout();
|
||||
io::copy(&mut stream, &mut stdout).ok();
|
||||
});
|
||||
|
||||
stdin_thread.join().unwrap();
|
||||
stdout_thread.join().unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
Reference in New Issue
Block a user