diff --git a/.devcontainer/Dockerfile.alpine b/.devcontainer/Dockerfile.alpine index 6504444..d63ebe5 100644 --- a/.devcontainer/Dockerfile.alpine +++ b/.devcontainer/Dockerfile.alpine @@ -17,3 +17,6 @@ FROM rust:1-alpine RUN apk add cmake make automake musl-dev autoconf libtool \ flex bison linux-headers openssl-dev lldb build-base libcap-dev + +RUN apk add mingw-w64-gcc mingw-w64-winpthreads mingw-w64-headers && \ + rustup target add x86_64-pc-windows-gnu diff --git a/Cargo.lock b/Cargo.lock index 39c5201..50003b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -674,6 +674,10 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "packets" +version = "0.1.0" + [[package]] name = "paste" version = "1.0.14" @@ -715,6 +719,7 @@ dependencies = [ "errno", "futures", "libc", + "packets", "tokio", "tokio-stream", ] @@ -997,6 +1002,7 @@ dependencies = [ "ed25519-dalek", "libc", "log", + "packets", "pcap-sys", "rand", "rmp-serde", diff --git a/Cargo.toml b/Cargo.toml index 5aa7e72..4edf42f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "pcap-sys", "nl-sys", + "packets", "examples/*/*", "sparse-protocol", "sparse-05/*", diff --git a/Makefile.toml b/Makefile.toml index cc9bd10..8aaaf47 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -23,6 +23,7 @@ args = ["run", "build", "build", "${@}"] workspace = false script = [ "docker-compose run build build --bin sparse-05-server ${@}", + "docker-compose run build build --bin sparse-05-server --target x86_64-pc-windows-gnu ${@}", "docker-compose run build build --bin sparse-05-client ${@}", ] diff --git a/packets/Cargo.toml b/packets/Cargo.toml new file mode 100644 index 0000000..ff4201f --- /dev/null +++ b/packets/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "packets" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/packets/src/error.rs b/packets/src/error.rs new file mode 100644 index 0000000..8413e5c --- /dev/null +++ b/packets/src/error.rs @@ -0,0 +1,40 @@ +// Copyright (C) 2023 Andrew Rioux +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use std::{error, fmt::Display}; + +#[derive(Debug)] +pub enum Error { + StringParse, + UnknownPacketType(u16), + PacketLengthInvalid, +} + +pub type Result = std::result::Result; + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::StringParse => write!(f, "unable to parse a string from pcap"), + Error::UnknownPacketType(ptype) => write!(f, "unknown packet type ({ptype})"), + Error::PacketLengthInvalid => write!( + f, + "received a packet with a length that mismatched the header" + ), + } + } +} + +impl error::Error for Error {} diff --git a/pcap-sys/src/packets.rs b/packets/src/lib.rs similarity index 99% rename from pcap-sys/src/packets.rs rename to packets/src/lib.rs index b852316..c25e73d 100644 --- a/pcap-sys/src/packets.rs +++ b/packets/src/lib.rs @@ -13,14 +13,15 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::error; use std::{ net::Ipv4Addr, sync::atomic::{AtomicU16, Ordering}, }; +pub mod error; + pub struct EthernetPkt<'a> { - pub(crate) data: &'a [u8], + pub data: &'a [u8], } impl<'a> EthernetPkt<'a> { diff --git a/packets/src/main.rs b/packets/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/packets/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/pcap-sys/Cargo.toml b/pcap-sys/Cargo.toml index 488d487..74e98e8 100644 --- a/pcap-sys/Cargo.toml +++ b/pcap-sys/Cargo.toml @@ -24,6 +24,7 @@ futures = "0.3.25" libc = "0.2.142" tokio = { version = "1.21.2", features = ["net", "rt", "macros", "rt-multi-thread" ] } tokio-stream = "0.1.14" +packets = { path = "../packets" } [build-dependencies] -cmake = "0.1" \ No newline at end of file +cmake = "0.1" diff --git a/pcap-sys/src/error.rs b/pcap-sys/src/error.rs index c978c9c..fad274f 100644 --- a/pcap-sys/src/error.rs +++ b/pcap-sys/src/error.rs @@ -122,3 +122,15 @@ impl From for Error { Error::Libc(err) } } + +impl From for Error { + fn from(err: packets::error::Error) -> Error { + use packets::error::Error as ExtE; + + match err { + ExtE::PacketLengthInvalid => Error::PacketLengthInvalid, + ExtE::UnknownPacketType(t) => Error::UnknownPacketType(t), + ExtE::StringParse => Error::StringParse, + } + } +} diff --git a/pcap-sys/src/lib.rs b/pcap-sys/src/lib.rs index d1110d4..47bf615 100644 --- a/pcap-sys/src/lib.rs +++ b/pcap-sys/src/lib.rs @@ -24,7 +24,7 @@ use std::{ pub mod error; mod ffi; -pub mod packets; +pub use packets; pub mod consts { pub use super::ffi::{ diff --git a/sparse-05/sparse-05-client/src/commands/generate.rs b/sparse-05/sparse-05-client/src/commands/generate.rs index 4922c16..7a28404 100644 --- a/sparse-05/sparse-05-client/src/commands/generate.rs +++ b/sparse-05/sparse-05-client/src/commands/generate.rs @@ -4,28 +4,51 @@ use ed25519_dalek::Keypair; use sparse_05_common::CONFIG_SEPARATOR; use tokio::{fs, io::AsyncWriteExt}; -use crate::configs::ClientConfig; +use crate::{configs::ClientConfig, options::TargetOs}; #[cfg(debug_assertions)] -pub const SPARSE_SERVER_BINARY: &'static [u8] = +pub const SPARSE_LINUX_SERVER_BINARY: &'static [u8] = include_bytes!("../../../../target/debug/sparse-05-server"); #[cfg(not(debug_assertions))] -pub const SPARSE_SERVER_BINARY: &'static [u8] = +pub const SPARSE_LINUX_SERVER_BINARY: &'static [u8] = include_bytes!("../../../../target/release/sparse-05-server"); +#[cfg(debug_assertions)] +pub const SPARSE_WINDOWS_SERVER_BINARY: &'static [u8] = + include_bytes!("../../../../target/x86_64-pc-windows-gnu/debug/sparse-05-server.exe"); +#[cfg(not(debug_assertions))] +pub const SPARSE_WINDOWS_SERVER_BINARY: &'static [u8] = + include_bytes!("../../../../target/x86_64-pc-windows-gnu/release/sparse-05-server.exe"); -pub async fn generate(mut name: PathBuf, port: u16) -> anyhow::Result<()> { +pub async fn generate(mut name: PathBuf, port: u16, target: TargetOs) -> anyhow::Result<()> { let mut csprng = rand::thread_rng(); let keypair = Keypair::generate(&mut csprng); let (enc_privkey, enc_pubkey) = ecies_ed25519::generate_keypair(&mut csprng); - let mut file = fs::OpenOptions::new() - .write(true) - .create(true) - .mode(0o755) - .open(&name) - .await?; + let mut file = fs::OpenOptions::new(); + file.write(true).create(true); + #[cfg(unix)] + file.mode(0o755); - file.write_all(SPARSE_SERVER_BINARY).await?; + #[cfg(windows)] + let old_file_part = name.file_name().unwrap().to_owned(); + + #[cfg(windows)] + { + let mut file_part = name.file_name().unwrap().to_owned(); + file_part.push(OsString::from(".exe")); + name.pop(); + name.push(file_part); + } + + let mut file = file.open(&name).await?; + + #[cfg(windows)] + { + name.pop(); + name.push(old_file_part); + } + + file.write_all(SPARSE_LINUX_SERVER_BINARY).await?; file.write_all(CONFIG_SEPARATOR).await?; file.write_all(&port.to_be_bytes()[..]).await?; file.write_all(keypair.public.as_bytes()).await?; diff --git a/sparse-05/sparse-05-client/src/main.rs b/sparse-05/sparse-05-client/src/main.rs index 6f5d2dc..361954d 100644 --- a/sparse-05/sparse-05-client/src/main.rs +++ b/sparse-05/sparse-05-client/src/main.rs @@ -11,7 +11,9 @@ async fn main() -> anyhow::Result<()> { let options = Options::from_args(); match options.command { - Command::Generate { name, port } => commands::generate::generate(name, port).await, + Command::Generate { name, port, target } => { + commands::generate::generate(name, port, target).await + } Command::Connect { config, ip } => commands::connect::connect(config, ip).await, } } diff --git a/sparse-05/sparse-05-client/src/options.rs b/sparse-05/sparse-05-client/src/options.rs index 025257e..9d8564a 100644 --- a/sparse-05/sparse-05-client/src/options.rs +++ b/sparse-05/sparse-05-client/src/options.rs @@ -14,14 +14,33 @@ fn to_socket_addr(src: &str) -> Result { )) } +pub enum TargetOs { + Linux, + Windows, +} + +impl std::str::FromStr for TargetOs { + type Err = &'static str; + fn from_str(input: &str) -> Result { + match input { + "linux" => Ok(Self::Linux), + "windows" => Ok(Self::Windows), + _ => Err("could not parse target operating system"), + } + } +} + #[derive(StructOpt)] pub enum Command { Generate { #[structopt(parse(from_os_str))] name: PathBuf, - #[structopt(default_value = "54248")] + #[structopt(long, short, default_value = "54248")] port: u16, + + #[structopt(long, short, default_value = "linux")] + target: TargetOs, }, Connect { #[structopt(parse(from_os_str))] diff --git a/sparse-05/sparse-05-server/Cargo.toml b/sparse-05/sparse-05-server/Cargo.toml index e3d090d..fd51161 100644 --- a/sparse-05/sparse-05-server/Cargo.toml +++ b/sparse-05/sparse-05-server/Cargo.toml @@ -4,7 +4,6 @@ version = "0.5.0" edition = "2021" [dependencies] -pcap-sys = { path = "../../pcap-sys" } anyhow = "1.0.70" ed25519-dalek = "1.0.1" rand = "0.7" @@ -16,6 +15,10 @@ rmp-serde = "1.1.2" catconf = "0.1.2" sparse-05-common = { version = "0.1.0", path = "../sparse-05-common" } ecies-ed25519 = { version = "0.5.1", features = ["serde"] } +packets = { path = "../../packets" } + +[target.'cfg(unix)'.dependencies] +pcap-sys = { path = "../../pcap-sys" } [build-dependencies] cc = "1.0" diff --git a/sparse-05/sparse-05-server/src/capabilities.rs b/sparse-05/sparse-05-server/src/capabilities.rs index 7612d6e..7676d25 100644 --- a/sparse-05/sparse-05-server/src/capabilities.rs +++ b/sparse-05/sparse-05-server/src/capabilities.rs @@ -23,6 +23,7 @@ struct cap_user_data_t { inheritable: u32, } +#[cfg(target_os = "linux")] fn get_username(uid: u32) -> anyhow::Result> { let passwd = std::fs::read_to_string("/etc/passwd")?; @@ -41,6 +42,12 @@ fn get_username(uid: u32) -> anyhow::Result> { })) } +#[cfg(target_os = "windows")] +fn get_username(uid: u32) -> anyhow::Result> { + Ok(std::env::var("USERPROFILE").ok()) +} + +#[cfg(target_os = "linux")] fn get_current_capabilities() -> anyhow::Result { let mut header = cap_user_header_t { version: 0x20080522, @@ -85,6 +92,21 @@ fn get_current_capabilities() -> anyhow::Result { }) } +#[cfg(target_os = "windows")] +fn get_current_capabilities() -> anyhow::Result { + let userent = get_username(0)?; + + Ok(Capabilities { + operating_system: OperatingSystem::Windows, + docker_container: false, + docker_breakout: false, + setuid: false, + root: userent.as_deref() == Some("Administrator"), + userent, + transport: TransportType::Udp, + }) +} + pub fn get_capabilities() -> anyhow::Result { get_current_capabilities() } diff --git a/sparse-05/sparse-05-server/src/connection.rs b/sparse-05/sparse-05-server/src/connection.rs index daf6727..68f0bbb 100644 --- a/sparse-05/sparse-05-server/src/connection.rs +++ b/sparse-05/sparse-05-server/src/connection.rs @@ -9,7 +9,7 @@ use std::{ use anyhow::{anyhow, bail, Context}; use ed25519_dalek::{Keypair, Signature, Signer, Verifier}; -use pcap_sys::packets::EthernetPacket; +use packets::EthernetPacket; use sparse_05_common::messages::{Capabilities, Command, Response, CONNECTED_MESSAGE}; #[derive(Clone)] @@ -39,7 +39,7 @@ struct ConnectionInformation { impl ConnectionInformation { fn format_udp_packet(&self, data: &[u8]) -> EthernetPacket { - use pcap_sys::packets::*; + use packets::*; let udp_packet = UDPPacket::construct(54248, self.srcport, data); let ip_packet = @@ -89,10 +89,8 @@ pub fn spawn_connection_handler( connection_packet: EthernetPacket, connection_killer: Sender<(Ipv4Addr, u16)>, ) -> anyhow::Result { - println!("received connection, starting to authenticate"); - let conninfo = { - use pcap_sys::packets::*; + use packets::*; let packet = connection_packet.pkt(); let Layer3Pkt::IPv4Pkt(ip_pkt) = packet.get_layer3_pkt()?; @@ -183,7 +181,7 @@ fn authenticate( match packet_handler.recv_timeout(std::time::Duration::from_millis(250)) { Ok(p) => { - use pcap_sys::packets::*; + use packets::*; let p = p.pkt(); let Layer3Pkt::IPv4Pkt(ip_pkt) = p.get_layer3_pkt()?; let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()?; @@ -206,8 +204,19 @@ fn authenticate( } } - println!("Connection made!"); + handle_full_connection(capabilities, packet_handler, conninfo, packet_sender, close) +} +fn handle_full_connection( + capabilities: Arc, + packet_handler: Receiver, + conninfo: ConnectionInformation, + packet_sender: Sender, + close: F, +) -> anyhow::Result<()> +where + F: Fn(), +{ close(); Ok(()) diff --git a/sparse-05/sparse-05-server/src/interface.rs b/sparse-05/sparse-05-server/src/interface.rs index 11267a5..f285019 100644 --- a/sparse-05/sparse-05-server/src/interface.rs +++ b/sparse-05/sparse-05-server/src/interface.rs @@ -5,10 +5,11 @@ use std::{ use anyhow::{anyhow, bail}; -use pcap_sys::packets::{self, EthernetPkt}; +use packets::{self, EthernetPkt}; use sparse_05_common::messages::TransportType; pub enum Interface { + #[cfg(target_os = "linux")] RawUdp(pcap_sys::Interface, u16), Udp(UdpSocket, u16), } @@ -16,6 +17,7 @@ pub enum Interface { impl Interface { pub fn new(ttype: TransportType, port: u16) -> anyhow::Result { match ttype { + #[cfg(target_os = "linux")] TransportType::RawUdp => { let mut interfaces = pcap_sys::PcapDevIterator::new()?; let interface_name = interfaces @@ -61,6 +63,10 @@ impl Interface { Ok(Interface::RawUdp(interface, port)) } + #[cfg(target_os = "windows")] + TransportType::RawUdp => { + panic!("transport not available!"); + } TransportType::Udp => Ok(Interface::Udp( UdpSocket::bind(&format!("0.0.0.0:{port}"))?, port, @@ -70,6 +76,7 @@ impl Interface { pub fn split(self) -> anyhow::Result<(InterfaceSender, InterfaceReceiver)> { match self { + #[cfg(target_os = "linux")] Self::RawUdp(interface, port) => { let arc = Arc::new(interface); Ok(( @@ -89,6 +96,7 @@ impl Interface { } pub enum InterfaceSender { + #[cfg(target_os = "linux")] RawUdp(Arc>), Udp(UdpSocket), } @@ -96,9 +104,10 @@ pub enum InterfaceSender { impl InterfaceSender { pub fn sendpacket(&self, packet: EthernetPkt) -> anyhow::Result<()> { match self { + #[cfg(target_os = "linux")] Self::RawUdp(interf) => Ok(interf.sendpacket(packet)?), Self::Udp(interf) => { - use pcap_sys::packets::*; + use packets::*; let Layer3Pkt::IPv4Pkt(ip_pkt) = packet.get_layer3_pkt()?; let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()?; @@ -113,6 +122,7 @@ impl InterfaceSender { } pub enum InterfaceReceiver { + #[cfg(target_os = "linux")] RawUdp(Arc>), Udp(UdpSocket, u16), } @@ -123,6 +133,7 @@ impl InterfaceReceiver { F: FnMut(packets::EthernetPacket) -> anyhow::Result<()>, { match self { + #[cfg(target_os = "linux")] Self::RawUdp(interf) => interf.listen( move |_, packet| { let _ = (f)(packet.to_owned()); @@ -132,7 +143,7 @@ impl InterfaceReceiver { -1, ), Self::Udp(interf, port) => loop { - use pcap_sys::packets::*; + use packets::*; let mut buf = [0u8; 2000]; diff --git a/sparse-05/sparse-05-server/src/main.rs b/sparse-05/sparse-05-server/src/main.rs index 73da0ca..4e49b01 100644 --- a/sparse-05/sparse-05-server/src/main.rs +++ b/sparse-05/sparse-05-server/src/main.rs @@ -8,7 +8,8 @@ use std::{ use anyhow::{bail, Context}; use connection::ConnectionHandle; -use pcap_sys::packets::EthernetPacket; +use packets::EthernetPacket; + use sparse_05_common::CONFIG_SEPARATOR; use crate::connection::spawn_connection_handler; @@ -65,7 +66,7 @@ fn main() -> anyhow::Result<()> { interface_receiver .listen(|pkt| { - use pcap_sys::packets::*; + use packets::*; let pkt = pkt.pkt();