Michael Mikovsky 01959ce440 Docs
2026-02-16 13:50:20 -07:00
2026-01-30 14:05:07 -07:00
2026-01-30 14:05:07 -07:00
2026-02-16 12:52:46 -07:00
2026-02-16 13:50:20 -07:00
2026-02-16 13:50:20 -07:00
2026-02-16 13:50:20 -07:00
2026-01-26 09:13:46 -07:00
2026-02-16 13:50:20 -07:00
2026-02-16 12:52:46 -07:00
2026-02-16 12:52:46 -07:00
2025-08-24 11:02:13 -06:00
2026-02-16 12:31:59 -07:00
2026-02-16 13:50:20 -07:00

unshell

A fully modular, pluggable framework for building cross-platform endpoint agents that integrate with existing toolsets.

Design Goals

  • 100% Modular - Every component is replaceable at runtime. Nothing is hardcoded.
  • Tool Integration - Drop in Metasploit payloads, Cobalt Strike beacons, or any external implant
  • Cross-Platform - Full Rust cross-compilation support for Windows, Linux, macOS, and embedded targets
  • Minimal Footprint - Compile-time obfuscation and size optimization for stealthy payloads

Philosophy

Nothing is fixed. Every part of the system is a plugin:

  • Transports - TCP, HTTP, DNS, WebSocket, custom
  • Protocols - Encryption, encoding, framing - all swappable
  • Payloads - Metasploit, Cobalt Strike, custom - just load and run
  • Components - Any Rust struct can be a module
  • Communication - Tree-based routing with replaceable backends

Architecture

unshell/
├── src/tree/           # Hierarchical message routing
│   ├── component.rs    # Component trait (implement for any module)
│   ├── endpoint.rs     # Endpoint manager
│   ├── protocols/     # Pluggable protocol stack
│   └── tcp/           # Example transport implementations
├── ush-obfuscate/     # Compile-time string obfuscation
└── ush-payload/       # Test harness

Core Traits

Everything plugs into these abstractions:

Component - Any Module

use unshell::tree::Component;
use serde_json::Value;

pub trait Component: Send + Sync {
    fn name(&self) -> &str;
    fn status(&self) -> Value;
    fn init(&mut self, config: Value) -> Result<(), String>;
    fn shutdown(&mut self) -> Result<(), String>;
}

Protocol - Any Encoding Layer

use unshell::tree::protocols::Protocol;

pub trait Protocol: Send + Sync {
    fn name(&self) -> &'static str;
    fn encode(&self, data: &[u8]) -> Result<Vec<u8>, ProtocolError>;
    fn decode(&self, data: &[u8]) -> Result<Vec<u8>, ProtocolError>;
}

Transport - Any Connection

// Transports connect to networks - TCP, HTTP, DNS, custom
// Implement send/recv and register with the transport registry

Payload - Any External Implant

// External payloads (Metasploit, Cobalt Strike, etc.) load as components
// They expose the same interface as native components

Module System

use unshell::{ModuleRuntime, Manager};
use unshell::config::RuntimeConfig;

// Define a module
pub struct MyModule;

// Implement runtime lifecycle
impl ModuleRuntime for MyModule {
    fn init(&mut self, manager: Arc<Mutex<Manager>>) -> Result<()>;
    fn is_running(&self) -> bool;
    fn kill(self: Box<Self>);
}

// Export via FFI for dynamic loading
#[unsafe(no_mangle)]
pub fn get_components() -> Vec<NamedComponent> {
    vec![NamedComponent { name: "mymodule", ... }]
}

Load compiled .so/.dll modules at runtime using libloading or in-memory via memfd_create.

Protocol Stacking

Layer protocols arbitrarily:

let mut stack = ProtocolStack::new();
stack.push(&ProtocolConfig::Base64(Default::default())).unwrap();
stack.push(&ProtocolConfig::Http(Default::default())).unwrap();
stack.push(&ProtocolConfig::Tcp(Default::default())).unwrap();

Order determines encoding: app → base64 → http → tcp → network

Integration Examples

Load a Metasploit Payload

// Load precompiled Metasploit .so
let module = Module::new("meterpreter.so")?;
// Or load from raw bytes (in-memory execution)
let module = Module::new_bytes(&meterpreter_bytes)?;

Use Cobalt Strike Beacon

// Beacon loads as a component with standard interface
let beacon = CobaltBeacon::new(config);
component_registry.register(Box::new(beacon)).unwrap();
// Communicate via tree messages - same as any other component

Custom Transport

// Implement Protocol trait
pub struct DnsTransport { ... }
impl Protocol for DnsTransport {
    fn encode(&self, data: &[u8]) -> Result<Vec<u8>, ProtocolError> {
        // Encode as DNS TXT records
    }
    fn decode(&self, data: &[u8]) -> Result<Vec<u8>, ProtocolError> {
        // Decode DNS responses
    }
}
// Register and use
stack.push(&ProtocolConfig::Custom { name: "dns", config: ... });

Cross-Compilation

# Windows x64
rustup target add x86_64-pc-windows-gnu
cargo build --target x86_64-pc-windows-gnu

# ARM64 Linux
rustup target add aarch64-unknown-linux-gnu
cargo build --target aarch64-unknown-linux-gnu

# macOS
rustup target add x86_64-apple-darwin
cargo build --target x86_64-apple-darwin

Building

# Standard build (~500KB)
cargo build

# Size-optimized (~50KB)
cargo build --profile minimize

# With obfuscation
cargo build --features obfuscate

Testing

cd ush-payload
cargo run

Obfuscation

Compile-time string obfuscation to evade static analysis:

use ush_obfuscate::symbol;

const API_KEY: &str = symbol!("SuperSecretKey123");
const C2_URL: &str = symbol!("https://C2Server/endpoint");

Roadmap

  • Protocol registry for runtime registration
  • Payload loader for common frameworks
  • Transport abstraction layer
  • Hot-swap components at runtime

Dependencies

  • libloading - Dynamic library loading
  • serde_json - Serialization
  • crossbeam-channel - Message passing
  • base64 - Encoding
  • thiserror - Error handling

License

MIT / Apache-2.0

Implementation Notes

Obfuscation Macros

The ush-obfuscate crate provides two macros for string obfuscation:

  • sym!() - For static strings that are used at runtime. Uses AES encryption.
  • xor!() - For simple XOR obfuscation.

When the obfuscate feature is enabled, strings are encrypted at compile time. When disabled, they remain as plain strings.

Using Sym Constants

To avoid redundant sym!() calls, define constants in src/tree/symbols.rs:

use crate::obfuscate::sym;

pub const MY_CONSTANT: &'static str = sym!("MyString");

Then use the constant in your code:

use unshell::tree::symbols::*;

json!({ KEY_NAME: "value" })

Module Organization

  • src/tree/ - Core tree system (routing, components, messages)
  • ush-payload/src/ - Protocol implementations, TCP client/server, connection management

Protocol-specific and transport-specific code belongs in ush-payload, while the tree system handles hierarchical message routing only.

Building for Size

./build.sh  # Builds with minimize profile and strips debug sections

This produces a ~200KB binary (may vary with content).

S
Description
UnShell is an exploitation orchestration framework.
Readme 1.8 MiB
Languages
Rust 99.7%
Shell 0.3%