From 25948a17f422a0f59553a4ee0ab78fad2b09c8cb Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Sun, 17 Sep 2023 14:07:31 -0400 Subject: [PATCH] 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")