feat: starting the TCP client proof of concept

This commit is contained in:
Andrew Rioux 2023-09-17 14:07:31 -04:00
parent 0ef459bcfe
commit 25948a17f4
Signed by: andrew.rioux
GPG Key ID: 9B8BAC47C17ABB94
8 changed files with 271 additions and 8 deletions

16
Cargo.lock generated
View File

@ -1296,10 +1296,12 @@ dependencies = [
name = "tcp-test" name = "tcp-test"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"nl-sys", "nl-sys",
"pcap-sys", "pcap-sys",
"rand 0.8.5", "rand 0.8.5",
"tokio", "tokio",
"tokio-stream",
] ]
[[package]] [[package]]
@ -1412,6 +1414,20 @@ dependencies = [
"futures-core", "futures-core",
"pin-project-lite", "pin-project-lite",
"tokio", "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]] [[package]]

View File

@ -34,6 +34,13 @@ script = [
"docker-compose run --entrypoint=setcap build cap_net_raw=eip /workspaces/sparse/target/debug/tcp-test" "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] [tasks.fmt]
command = "cargo" command = "cargo"
args = ["fmt"] args = ["fmt"]

View File

@ -44,3 +44,9 @@ services:
expose: expose:
- "54248/udp" - "54248/udp"
command: /workspaces/sparse/target/debug/ex-revshell-server command: /workspaces/sparse/target/debug/ex-revshell-server
tcptest_client:
image: alpine
volumes:
- ./target:/workspaces/sparse/target
command: /workspaces/sparse/target/debug/tcp-target

View File

@ -146,7 +146,13 @@ impl<'a> IPv4Pkt<'a> {
pub fn get_layer4_packet(&self) -> error::Result<Layer4Pkt<'a>> { pub fn get_layer4_packet(&self) -> error::Result<Layer4Pkt<'a>> {
match self.protocol() { 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)..], data: &self.data[(self.header_len() as usize)..],
})), })),
p => Err(error::Error::UnknownPacketType(p.into())), p => Err(error::Error::UnknownPacketType(p.into())),
@ -156,12 +162,14 @@ impl<'a> IPv4Pkt<'a> {
pub enum Layer4Pkt<'a> { pub enum Layer4Pkt<'a> {
UDP(UDPPkt<'a>), UDP(UDPPkt<'a>),
TCP(TCPPkt<'a>),
} }
impl<'a> Layer4Pkt<'a> { impl<'a> Layer4Pkt<'a> {
pub fn len(&self) -> u16 { pub fn len(&self) -> u16 {
match self { match self {
Layer4Pkt::UDP(pkt) => pkt.len(), 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)] #[derive(Debug, Clone)]
pub struct EthernetPacket { pub struct EthernetPacket {
data: Vec<u8>, data: Vec<u8>,
@ -252,7 +342,8 @@ impl IPv4Packet {
let ttl: u8 = 64; let ttl: u8 = 64;
let protocol: u8 = match packet { 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()); let source_upper = u16::from_be_bytes(source.octets()[0..2].try_into().unwrap());
@ -290,6 +381,7 @@ impl IPv4Packet {
&dest.octets(), &dest.octets(),
match packet { match packet {
Layer4Packet::UDP(pkt) => &*pkt.data, Layer4Packet::UDP(pkt) => &*pkt.data,
Layer4Packet::TCP(pkt) => &*pkt.data,
}, },
] ]
.concat(); .concat();
@ -306,12 +398,14 @@ impl IPv4Packet {
#[derive(Clone)] #[derive(Clone)]
pub enum Layer4Packet { pub enum Layer4Packet {
UDP(UDPPacket), UDP(UDPPacket),
TCP(TCPPacket),
} }
impl Layer4Packet { impl Layer4Packet {
pub fn pkt(&'_ self) -> Layer4Pkt<'_> { pub fn pkt(&'_ self) -> Layer4Pkt<'_> {
match self { match self {
Layer4Packet::UDP(pkt) => Layer4Pkt::UDP(pkt.pkt()), Layer4Packet::UDP(pkt) => Layer4Pkt::UDP(pkt.pkt()),
Layer4Packet::TCP(pkt) => Layer4Pkt::TCP(pkt.pkt()),
} }
} }
} }
@ -345,3 +439,98 @@ impl UDPPacket {
UDPPkt { data: &self.data } UDPPkt { data: &self.data }
} }
} }
#[derive(Clone)]
pub struct TCPPacket {
data: Vec<u8>,
}
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<u8>,
}
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<u8>) -> Self {
self.options = opts;
self
}
pub fn build<I1, I2>(self, srcip: I1, dstip: I2, data: Vec<u8>) -> TCPPacket
where
I1: Into<Ipv4Addr>,
I2: Into<Ipv4Addr>,
{
let source = srcip.into();
let dest = dstip.into();
let protocol = &[0x00u8, 0x06u8];
let tcp_length = data.len() + self.options.len() + 32;
}
}

1
rustfmt.toml Normal file
View File

@ -0,0 +1 @@
edition = "2021"

View File

@ -10,3 +10,5 @@ pcap-sys = { path = "../../pcap-sys" }
nl-sys = { path = "../../nl-sys" } nl-sys = { path = "../../nl-sys" }
rand = "0.8.5" rand = "0.8.5"
tokio = { version = "1.32.0", features = ["full"] } tokio = { version = "1.32.0", features = ["full"] }
anyhow = "1.0.75"
tokio-stream = { version = "0.1.14", features = ["full"] }

View File

@ -1,4 +1,44 @@
use std::net::Ipv4Addr;
use anyhow::anyhow;
use nl_sys::{netlink, route};
#[tokio::main] #[tokio::main]
async fn main() { async fn main() -> anyhow::Result<()> {
println!("Hello, world!"); let ip = std::env::args()
.skip(1)
.next()
.ok_or(anyhow!("could not get target IP"))?
.parse::<Ipv4Addr>()?;
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(())
} }

View File

@ -7,7 +7,9 @@ server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("0.0.0.0", 54248)) server.bind(("0.0.0.0", 54248))
server.listen(32) server.listen(32)
while True:
client, addr = server.accept() client, addr = server.accept()
print('Got connection')
with client: with client:
print(client.recv(24)) print(client.recv(24))
client.sendall(b"pong") client.sendall(b"pong")