github init

This commit is contained in:
deadjakk
2021-01-25 15:17:15 -06:00
commit 2cd78cbd9f
9 changed files with 597 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
[workspace]
members=[
"client",
"server"
]
Executable
+18
View File
@@ -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).
+8
View File
@@ -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]
+10
View File
@@ -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
+383
View File
@@ -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
})
}
}
+21
View File
@@ -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),
};
}
}
+9
View File
@@ -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]
+13
View File
@@ -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.
+130
View File
@@ -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..");
}
}
});
}