SilentMoonWalk weaponization

Unwinder has been completely rewritten to become in a crate offering an easy to use full SilentMoonWalk weaponization.
This commit is contained in:
Kudaes
2023-11-28 16:58:35 +01:00
parent 04439b105a
commit 98792e93bc
21 changed files with 1590 additions and 1339 deletions
+163 -40
View File
@@ -1,46 +1,169 @@
# Content
- [Description](#Decription)
- [Credits](#Credits)
- [Usage](#usage)
- [call_function!() macro](#call_function!()-macro)
- [indirect_syscall!() macro](#indirect_syscall!()-macro)
- [Parameter passing](#Parameter-passing)
- [Examples](#examples)
- [Calling kernel32.dll!Sleep()](#Calling-kernel32.dll!Sleep())
- [Calling kernel32.dll!OpenProcess()](#Calling-kernel32.dll!Openprocess())
- [Calling NtDelayExecution() as indirect syscall](#Calling-NtDelayExecution()-as-indirect-syscall)
- [Concatenate macro calls](#Concatenate-macro-calls)
- [Considerations](#Considerations)
- [Initial frame](#Initial-frame)
- [PoC](#PoC)
# Description
This [Twitter thread](https://twitter.com/namazso/status/1442313752767045635?s=20&t=wxBHvf95-XtkPEevjcgbPg) inspired the creation of this tool.
Unwinder is a full weaponization of [SilentMoonWalk](https://github.com/klezVirus/SilentMoonwalk) technique, allowing to obtain complete and stable call stack spoofing in Rust.
Unwinder is a PoC of how to parse PE's UNWIND_INFO structs in order to achieve "proper" thread stack spoofing from the point of view of the [x64 calling convention](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170).
For more detailed information about how thread stack is walked in x64 check the official [x64 exception handling documentation](https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64?view=msvc-170).
The spoofing process overview is as follows:
* We obtain the stack's memory address where the first return address is located. This return address is replaced by the memory address of a randomly selected Windows API function (let's call it FunctionA).
* We walk FunctionA's Unwind codes array in order to dynamically determine where the next return address is expected in the stack.
* The word contained in the stack address obtained in the previous step is replaced by the memory address of another randomly selected Windows API function.
* The steps 2 and 3 are repeated a random number of times, obtaining a different thread stack each iteration of the PoC. All of this thread stacks are correct from the point of view of the x64 unwinding process.
* Once the spoof is completed, the tool calls Sleeps to allow the inpection of the thread stack (I use Process Hacker for this step).
This process repeats indefinitely.
As it can be seen in the following images, we are able to spoof the thread stack in multiple ways. Since the number of spoofing functions and the functions themselves are randomly selected from a pool of functions each iteration will create a different thread stack.
![Thread stack spoofed.](/images/spoof1.png "Thread stack spoofed")
![Thread stack spoofed.](/images/spoof2.png "Thread stack spoofed")
![Thread stack spoofed.](/images/spoof3.png "Thread stack spoofed")
Additional spoofing functions can be added to the pool by enlarging the **FUNCTIONS** array located in **src::main.rs**.
# Disclaimer
From the previous images it can be concluded that this tool **is not trying to create logical stack calls** for multiple reasons. For example, some of the thread stacks shown before don't start with ntdll.dll!RtlUserThreadStart and I've never seen kernelbase!GetCalendarInfoEx calling kernelbase.dll!DsFreeNameResultW even thought this tools allows it. The main purpose of this tool is to show how unwind codes walking allows us to effectively and malleably spoof the stack.
To use this technique in real environments and tools, it is required to analyze valid stack secuences in order to mimic real behaviours.
On the other hand, im just spoofing some frames of the stack. If you want to fully spoof the stack a little bit of extra work have to be done, even thought it should be relatively easy to implement. Also, im not trying to restore the original values of the stack after each iteration.
Finally, not all the unwind codes have been implemented. Although I encourage anyone to add extra spoofing functions to the **FUNCTIONS** array, take into account that you may end up parsing unwind codes not covered by this tool, which may lead to errors in the spoofing process.
# Compilation
We need [Rust Nightly](https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/book/second-edition/ch01-03-how-rust-is-made-and-nightly-rust.html) to compile this project. Once it has been installed, simply compile the code and run the tool:
C:\Users\User\Desktop\unwinder> cargo build
C:\Users\User\Desktop\unwinder\target\debug> unwinder.exe
This crate comes with the following characteristics:
* Support to run any arbitrary function with up to 11 parameters.
* Support to run indirect syscalls (no additional heap allocations) with up to 11 parameters.
* The crate allows to retrieve the value returned by the functions called through it.
* The spoofing process can be concatenated any number of times without increasing the call stack size.
* TLS is used to increase efficiency during the spoofing process.
* [dinvoke_rs](https://crates.io/crates/dinvoke_rs) is used to make any Windows API call required by the crate.
# Credits
kudos to the creators of the SilentMoonWalk technique:
* [@mariuszbit](https://twitter.com/mariuszbit) for his [ThreadStackSpoofer](https://github.com/mgeeky/ThreadStackSpoofer) project that inspired me to create this tool.
* [@namazso](https://twitter.com/namazso) for pointing me to the right direction.
* [KlezVirus][https://twitter.com/KlezVirus]
* [Waldo-IRC][https://twitter.com/waldoirc]
* [Trickster0][https://twitter.com/trickster012]
And of course a huge shoutout to [namazso](https://twitter.com/namazso) for the [Twitter thread](https://twitter.com/namazso/status/1442313752767045635?s=20&t=wxBHvf95-XtkPEevjcgbPg) that inspired this whole project.
# Usage
Import this crate into your project by adding the following line to your `cargo.toml`:
```rust
[dependencies]
unwinder = "0.1.0"
```
The main functionality of this crate has been wrapped in two macros:
* The `call_function!()` macro allows to run any arbitrary function with a clean call stack.
* The `indirect_syscall()` macro executes the specified (indirect) syscall with a clean call stack.
To use any of these macros it is required to import `std::ffi::c_void` data type.
Both macros return a `PVOID` that can be used to retrieve the value returned by the function executed. More detailed information in the examples section.
## call_function!() macro
This macro expects the following parameters:
* The first parameter is the memory address to call after spoofing the call stack. This parameter should be passed as a `usize`, `isize` or a pointer.
* The following parameters are those arguments to send to the final function.
## indirect_syscall!() macro
This macro expects the following parameters:
* The first parameter is a string that contains the name of the NT function whose syscall you want to execute.
* The following parameters are those arguments to send to the NT function.
## Parameter passing
In order to pass arguments of different types to these two macros, the following considerations must be taken into account:
* Any basic data type that can be converted to `usize` (u8-u64, i8-i64, bool, etc.) can be passed directly to the macros.
* Structs and unions of size 8, 16, 32, or 64 bits are passed as if they were integers of the same size.
* Structures and unions with a size larger than 64 bits must be passed as a pointer.
* Strings (&str and String) must be passed as a pointer.
* floating-point and double-precision parameter are not currently supported.
* Any other data type must be passed as a pointer.
# Examples
## Calling kernel32.dll!Sleep()
```rust
let k32 = dinvoke_rs::dinvoke::get_module_base_address("kernel32.dll");
let sleep = dinvoke_rs::dinvoke::get_function_address(k32, "Sleep"); // Memory address of kernel32.dll!Sleep()
let miliseconds = 1000i32;
unwinder::call_function!(sleep, seconds);
```
## Calling kernel32.dll!OpenProcess()
```rust
let k32 = dinvoke_rs::dinvoke::get_module_base_address(&lc!("kernel32.dll"));
let open_process: isize = dinvoke_rs::dinvoke::get_function_address(k32, "Openprocess");
let desired_access: u32 = 0x1000;
let inherit = 0i32;
let pid = 20628i32;
let handle: PVOID = call_function!(open_process, desired_access, inherit, pid);
let handle: HANDLE = std::mem::transmute(handle);
println!("Handle id: {:x}", handle.0);
```
Notice that the macro returns a `PVOID` that can be directly converted to a `HANDLE` since both data types has the same size. This allows to access to the value returned by `OpenProcess`, which is the new handle to the target process.
## Calling NtDelayExecution() as indirect syscall
```rust
let large = 0x8000000000000000 as u64; // Sleep indefinitely
let large: *mut i64 = std::mem::transmute(&large);
let alertable = false;
let ntstatus: PVOID = unwinder::indirect_syscall!("NtDelayExecution", alertable, large);
println!("ntstatus: {:x}", ntstatus as usize);
```
Notice that the macro returns a `PVOID` that can be used to retrieve the `NTSTATUS` returned by `NtDelayExecution`.
## Concatenate macro calls
The spoofing process can be concatenated any number of times without an abnormal call stack size increment. The execution flow will be preserved as well. The following code is an example of this:
```rust
fn function_a()
{
unsafe
{
let func_b = function_b as usize;
call_function!(func_b);
println!("function_a done.");
}
}
fn function_b()
{
unsafe
{
let func_c = function_c as usize;
call_function!(func_c);
println!("function_b done.")
}
}
fn function_c()
{
unsafe
{
let large = 0x0000000000000000 as u64; // Don't sleep so we return to function_b, allowing to check the execution flow preservation.
let large: *mut i64 = std::mem::transmute(&large);
let alertable = false;
let ntstatus = indirect_syscall!("NtDelayExecution", alertable, large);
println!("ntstatus: {:x}", ntstatus as usize);
}
}
```
# Considerations
## Initial frame
By default, the spoofing process will try to keep the thread start address' frame in the call stack to increase legitimacy.
![Call stack spoofed keeping the main module.](/images/main_kept.png "Call stack spoofed keeping the main module")
Sometimes, the thread's start function does not perform a `call` to a subsequent function (a `jmp` instruction is executed instead), meaning there is not return address pushed to the stack. In that scenario, the spoofed call stack will start at BaseThreadInitThunk's frame.
![Call stack spoofed without main module.](/images/no_main.png "Call stack spoofed without main module")
## PoC
In order to test the implementation of the technique, [PE-sieve](https://github.com/hasherezade/pe-sieve) has been used with the flag [`/threads`](https://github.com/hasherezade/pe-sieve/wiki/4.9.-Scan-threads-callstack-(threads)). The results of the test shows how the inpection of the call stack does not reveal the pressence of the payload when this crate's functionalities are used. As it can be seen in the second image, the payload is detected when unwinder is not used.
![PE-sieve results when unwinder is used.](/images/spoofed.png "PE-sieve results when unwinder is used")
![PE-sieve results when unwinder is not used.](/images/not_spoofed.png "PE-sieve results when unwinder is used")
Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

+1
View File
@@ -0,0 +1 @@
rustflags = ["--remap-path-prefix", "C:\\Users\\=", "--remap-path-prefix", "unwinder="]
+31 -6
View File
@@ -3,11 +3,36 @@ name = "unwinder"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.dev]
debug-assertions = false
[profile.release]
debug-assertions = false # required to avoid misaligned pointer dereference panics
strip = true
[dependencies]
data = {path = "data"}
bindings = {path = "bindings"}
dinvoke = {path = "dinvoke"}
bitreader = "0.3.6"
rand = "0.8.5"
dinvoke_rs = "0.1.2"
bitreader = "0.3.8"
nanorand = "0.7.0"
winapi = "0.3.9"
litcrypt2 = "0.1.0"
[build-dependencies]
cc = "1.0.83"
[dependencies.windows]
version = "0.51"
features = [
"Win32_Foundation",
"Win32_Security",
"Win32_System",
"Win32_System_IO",
"Win32_System_Kernel",
"Win32_System_Diagnostics_Debug",
"Win32_System_Diagnostics_ToolHelp",
"Win32_System_WindowsProgramming",
"Wdk_Foundation",
"Win32_Storage_FileSystem",
"Win32_System_Memory",
"Win32_System_SystemInformation"
]
-15
View File
@@ -1,15 +0,0 @@
[package]
name = "bindings"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.dev.package.bindings]
opt-level = 3
debug = 0
[dependencies]
windows = "0.19"
[build-dependencies]
windows = "0.19"
-13
View File
@@ -1,13 +0,0 @@
fn main() {
windows::build!(
Windows::Win32::System::Diagnostics::Debug::{GetThreadContext,SetThreadContext,IMAGE_FILE_HEADER,IMAGE_OPTIONAL_HEADER32,IMAGE_SECTION_HEADER,
IMAGE_DATA_DIRECTORY,IMAGE_OPTIONAL_HEADER_MAGIC,IMAGE_SUBSYSTEM},
Windows::Win32::System::Memory::{VIRTUAL_ALLOCATION_TYPE,PAGE_PROTECTION_FLAGS},
Windows::Win32::Foundation::{HANDLE,HINSTANCE,PSTR,BOOL},
Windows::Win32::System::Threading::{GetCurrentProcess,GetCurrentThread,PROCESS_BASIC_INFORMATION},
Windows::Win32::System::SystemServices::{IMAGE_BASE_RELOCATION,IMAGE_IMPORT_DESCRIPTOR,IMAGE_THUNK_DATA32,IMAGE_THUNK_DATA64,OVERLAPPED},
Windows::Win32::System::WindowsProgramming::{PUBLIC_OBJECT_TYPE_INFORMATION,IO_STATUS_BLOCK},
Windows::Win32::Security::SECURITY_ATTRIBUTES,
Windows::Win32::System::Kernel::UNICODE_STRING,
);
}
-1
View File
@@ -1 +0,0 @@
windows::include_bindings!();
+7
View File
@@ -0,0 +1,7 @@
fn main()
{
// Use the `cc` crate to build a C file and statically link it.
cc::Build::new()
.file("src/gateway.asm")
.compile("gateway");
}
-12
View File
@@ -1,12 +0,0 @@
[package]
name = "data"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.dev.package.data]
opt-level = 3
debug = 0
[dependencies]
bindings = { path = "../bindings" }
-210
View File
@@ -1,210 +0,0 @@
use std::{ffi::c_void};
use bindings::Windows::Win32::{Foundation::{BOOL, HANDLE, HINSTANCE, PSTR}, System::{Diagnostics::Debug::{IMAGE_DATA_DIRECTORY,
IMAGE_OPTIONAL_HEADER32, IMAGE_SECTION_HEADER},Kernel::UNICODE_STRING}};
pub type PVOID = *mut c_void;
pub type DWORD = u32;
pub type Sleep = unsafe extern "system" fn (u32);
pub type EntryPoint = extern "system" fn (HINSTANCE, u32, *mut c_void) -> BOOL;
pub type LoadLibraryA = unsafe extern "system" fn (PSTR) -> HINSTANCE;
pub type GetLastError = unsafe extern "system" fn () -> u32;
pub type CloseHandle = unsafe extern "system" fn (HANDLE) -> i32;
pub type LdrGetProcedureAddress = unsafe extern "system" fn (PVOID, *mut String, u32, *mut PVOID) -> i32;
pub type NtQueryInformationProcess = unsafe extern "system" fn (HANDLE, u32, PVOID, u32, *mut u32) -> i32;
pub type RtlAdjustPrivilege = unsafe extern "system" fn (u32, u8, u8, *mut u8) -> i32;
pub type RtlInitUnicodeString = unsafe extern "system" fn (*mut UNICODE_STRING, *const u16) -> () ;
pub const DLL_PROCESS_DETACH: u32 = 0;
pub const DLL_PROCESS_ATTACH: u32 = 1;
pub const DLL_THREAD_ATTACH: u32 = 2;
pub const DLL_THREAD_DETACH: u32 = 3;
pub const PAGE_READONLY: u32 = 0x2;
pub const PAGE_READWRITE: u32 = 0x4;
pub const PAGE_EXECUTE_READWRITE: u32 = 0x40;
pub const PAGE_EXECUTE_READ: u32 = 0x20;
pub const PAGE_EXECUTE: u32 = 0x10;
pub const MEM_COMMIT: u32 = 0x1000;
pub const MEM_RESERVE: u32 = 0x2000;
pub const SECTION_MEM_READ: u32 = 0x40000000;
pub const SECTION_MEM_WRITE: u32 = 0x80000000;
pub const SECTION_MEM_EXECUTE: u32 = 0x20000000;
// Access mask
pub const GENERIC_READ: u32 = 0x80000000;
pub const GENERIC_WRITE: u32 = 0x40000000;
pub const GENERIC_EXECUTE: u32 = 0x20000000;
pub const GENERIC_ALL: u32 = 0x10000000;
pub const SECTION_ALL_ACCESS: u32 = 0x10000000;
pub const PROCESS_QUERY_LIMITED_INFORMATION: u32 = 0x1000;
pub const THREAD_ALL_ACCESS: u32 = 0x000F0000 | 0x00100000 | 0xFFFF;
//File share flags
pub const FILE_SHARE_NONE: u32 = 0x0;
pub const FILE_SHARE_READ: u32 = 0x1;
pub const FILE_SHARE_WRITE: u32 = 0x2;
pub const FILE_SHARE_DELETE: u32 = 0x4;
//File access flags
pub const DELETE: u32 = 0x10000;
pub const FILE_READ_DATA: u32 = 0x1;
pub const FILE_READ_ATTRIBUTES: u32 = 0x80;
pub const FILE_READ_EA: u32 = 0x8;
pub const READ_CONTROL: u32 = 0x20000;
pub const FILE_WRITE_DATA: u32 = 0x2;
pub const FILE_WRITE_ATTRIBUTES: u32 = 0x100;
pub const FILE_WRITE_EA: u32 = 0x10;
pub const FILE_APPEND_DATA: u32 = 0x4;
pub const WRITE_DAC: u32 = 0x40000;
pub const WRITE_OWNER: u32 = 0x80000;
pub const SYNCHRONIZE: u32 = 0x100000;
pub const FILE_EXECUTE: u32 = 0x20;
// File open flags
pub const FILE_SYNCHRONOUS_IO_NONALERT: u32 = 0x20;
pub const FILE_NON_DIRECTORY_FILE: u32 = 0x40;
pub const SEC_IMAGE: u32 = 0x1000000;
#[derive(Clone)]
#[repr(C)]
pub struct PeMetadata {
pub pe: u32,
pub is_32_bit: bool,
pub image_file_header: IMAGE_FILE_HEADER,
pub opt_header_32: IMAGE_OPTIONAL_HEADER32,
pub opt_header_64: IMAGE_OPTIONAL_HEADER64,
pub sections: Vec<IMAGE_SECTION_HEADER>
}
impl Default for PeMetadata {
fn default() -> PeMetadata {
PeMetadata {
pe: u32::default(),
is_32_bit: false,
image_file_header: IMAGE_FILE_HEADER::default(),
opt_header_32: IMAGE_OPTIONAL_HEADER32::default(),
opt_header_64: IMAGE_OPTIONAL_HEADER64::default(),
sections: Vec::default(),
}
}
}
#[repr(C)]
#[derive(Copy, Clone, Default, PartialEq, Debug, Eq)]
pub struct ApiSetNamespace {
pub unused: [u8;12],
pub count: i32, // offset 0x0C
pub entry_offset: i32, // offset 0x10
}
#[repr(C)]
#[derive(Copy, Clone, Default, PartialEq, Debug, Eq)]
pub struct ApiSetNamespaceEntry {
pub unused1: [u8;4],
pub name_offset: i32, // offset 0x04
pub name_length: i32, // offset 0x08
pub unused2: [u8;4],
pub value_offset: i32, // offset 0x10
pub value_length: i32, // offset 0x14
}
#[repr(C)]
#[derive(Copy, Clone, Default, PartialEq, Debug, Eq)]
pub struct ApiSetValueEntry {
pub flags: i32, // offset 0x00
pub name_offset: i32, // offset 0x04
pub name_count: i32, // offset 0x08
pub value_offset: i32, // offset 0x0C
pub value_count: i32, // offset 0x10
}
#[derive(Copy, Clone, Default, PartialEq, Debug, Eq)]
#[repr(C)]
pub struct IMAGE_FILE_HEADER {
pub machine: u16,
pub number_of_sections: u16,
pub time_data_stamp: u32,
pub pointer_to_symbol_table: u32,
pub number_of_symbols: u32,
pub size_of_optional_header: u16,
pub characteristics: u16,
}
#[derive(Copy, Clone,Default)]
#[repr(C)] // required to keep fields order, otherwise Rust may change that order randomly
pub struct IMAGE_OPTIONAL_HEADER64 {
pub magic: u16,
pub major_linker_version: u8,
pub minor_linker_version: u8,
pub size_of_code: u32,
pub size_of_initialized_data: u32,
pub size_of_unitialized_data: u32,
pub address_of_entry_point: u32,
pub base_of_code: u32,
pub image_base: u64,
pub section_alignment: u32,
pub file_alignment: u32,
pub major_operating_system_version: u16,
pub minor_operating_system_version: u16,
pub major_image_version: u16,
pub minor_image_version: u16,
pub major_subsystem_version: u16,
pub minor_subsystem_version: u16,
pub win32_version_value: u32,
pub size_of_image: u32,
pub size_of_headers: u32,
pub checksum: u32,
pub subsystem: u16,
pub dll_characteristics: u16,
pub size_of_stack_reserve: u64,
pub size_of_stack_commit: u64,
pub size_of_heap_reserve: u64,
pub size_of_heap_commit: u64,
pub loader_flags: u32,
pub number_of_rva_and_sizes: u32,
pub datas_directory: [IMAGE_DATA_DIRECTORY; 16],
}
#[derive(Copy, Clone, Default, PartialEq, Debug, Eq)]
#[repr(C)]
pub struct GUID
{
pub data1: u32,
pub data2: u16,
pub data3: u16,
pub data4: [u8; 8],
}
#[repr(C)]
pub struct SYSTEM_HANDLE_INFORMATION {
pub number_of_handles: u32,
pub handles: Vec<SYSTEM_HANDLE_TABLE_ENTRY_INFO>,
}
#[repr(C)]
pub struct SYSTEM_HANDLE_TABLE_ENTRY_INFO {
pub process_id: u16,
pub creator_back_trace_index: u16,
pub object_type_index: u8,
pub handle_attributes: u8,
pub handle_value: u16,
pub object: PVOID,
pub granted_access: u32,
}
#[repr(C)]
pub struct CLIENT_ID {
pub unique_process: HANDLE,
pub unique_thread: HANDLE,
}
#[repr(C)]
#[derive(Copy, Clone, Default)]
pub struct RUNTIME_FUNCTION {
pub begin_addr: u32,
pub end_addr: u32,
pub unwind_addr: u32
}
-14
View File
@@ -1,14 +0,0 @@
[package]
name = "dinvoke"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.dev.package.dinvoke]
opt-level = 3
debug = 0
[dependencies]
bindings = { path = "../bindings" }
data = { path = "../data" }
winproc = "0.6.4"
-642
View File
@@ -1,642 +0,0 @@
use std::mem::size_of;
use std::panic;
use std::{collections::HashMap, ptr};
use std::ffi::CString;
use bindings::Windows::Win32::System::Threading::PROCESS_BASIC_INFORMATION;
use bindings::Windows::Win32::{Foundation::{HANDLE, HINSTANCE, PSTR}, {System::Threading::{GetCurrentProcess}}};
use data::{ApiSetNamespace, ApiSetNamespaceEntry, ApiSetValueEntry, DLL_PROCESS_ATTACH, EntryPoint, PVOID, PeMetadata};
use std::{ffi::c_void};
use winproc::Process;
/// Retrieves the base address of a module loaded in the current process.
///
/// In case that the module can't be found in the current process, it will
/// return 0.
///
/// # Examples
///
/// ```
/// let ntdll = dinvoke::get_module_base_address("ntdll.dll");
///
/// if ntdll != 0
/// {
/// println!("The base address of ntdll.dll is 0x{:X}.", ntdll);
/// }
/// ```
pub fn get_module_base_address (module_name: &str) -> isize
{
let process = Process::current();
let modules = process.module_list().unwrap();
for m in modules
{
if m.name().unwrap().to_lowercase() == module_name.to_ascii_lowercase()
{
let handle = m.handle();
return handle as isize;
}
}
0
}
/// Retrieves the address of an exported function from the specified module.
///
/// This functions is analogous to GetProcAddress from Win32. The exported
/// function's address is obtained by walking and parsing the EAT of the
/// specified module.
///
/// In case that the function's address can't be retrieved, it will return 0.
///
/// # Examples
///
/// ```
/// let ntdll = dinvoke::get_module_base_address("ntdll.dll");
///
/// if ntdll != 0
/// {
/// let addr = dinvoke::get_function_address(ntdll, "NtCreateThread");
/// println!("The address where NtCreateThread is located at is 0x{:X}.", addr);
/// }
/// ```
pub fn get_function_address(module_base_address: isize, function: &str) -> isize {
unsafe
{
let mut function_ptr:*mut i32 = ptr::null_mut();
let pe_header = *((module_base_address + 0x3C) as *mut i32);
let opt_header: isize = module_base_address + (pe_header as isize) + 0x18;
let magic = *(opt_header as *mut i16);
let p_export: isize;
if magic == 0x010b
{
p_export = opt_header + 0x60;
}
else
{
p_export = opt_header + 0x70;
}
let export_rva = *(p_export as *mut i32);
let ordinal_base = *((module_base_address + export_rva as isize + 0x10) as *mut i32);
let number_of_names = *((module_base_address + export_rva as isize + 0x18) as *mut i32);
let functions_rva = *((module_base_address + export_rva as isize + 0x1C) as *mut i32);
let names_rva = *((module_base_address + export_rva as isize + 0x20) as *mut i32);
let ordinals_rva = *((module_base_address + export_rva as isize + 0x24) as *mut i32);
for x in 0..number_of_names
{
let address = *((module_base_address + names_rva as isize + x as isize * 4) as *mut i32);
let mut function_name_ptr = (module_base_address + address as isize) as *mut u8;
let mut function_name: String = "".to_string();
while *function_name_ptr as char != '\0' // null byte
{
function_name.push(*function_name_ptr as char);
function_name_ptr = function_name_ptr.add(1);
}
if function_name.to_lowercase() == function.to_lowercase()
{
let function_ordinal = *((module_base_address + ordinals_rva as isize + x as isize * 2) as *mut i16) as i32 + ordinal_base;
let function_rva = *(((module_base_address + functions_rva as isize + (4 * (function_ordinal - ordinal_base)) as isize )) as *mut i32);
function_ptr = (module_base_address + function_rva as isize) as *mut i32;
function_ptr = get_forward_address(function_ptr as *mut u8) as *mut i32;
break;
}
}
let mut ret: isize = 0;
if function_ptr != ptr::null_mut()
{
ret = function_ptr as isize;
}
ret
}
}
fn get_forward_address(function_ptr: *mut u8) -> isize {
unsafe
{
let mut c = 100;
let mut ptr = function_ptr.clone();
let mut forwarded_names = "".to_string();
loop
{
if *ptr as char != '\0'
{
forwarded_names.push(*ptr as char);
}
else
{
break;
}
ptr = ptr.add(1);
c = c - 1;
// Assume there wont be an exported address with len > 100
if c == 0
{
return function_ptr as isize;
}
}
let values: Vec<&str> = forwarded_names.split(".").collect();
if values.len() != 2
{
return function_ptr as isize;
}
let mut forwarded_module_name = values[0].to_string();
let forwarded_export_name = values[1].to_string();
let api_set = get_api_mapping();
let prev_hook = panic::take_hook();
panic::set_hook(Box::new(|_| {}));
let result = panic::catch_unwind(|| {
format!("{}{}",&forwarded_module_name[..forwarded_module_name.len() - 2], ".dll");
});
panic::set_hook(prev_hook);
if result.is_err()
{
return function_ptr as isize;
}
let lookup_key = format!("{}{}",&forwarded_module_name[..forwarded_module_name.len() - 2], ".dll");
if api_set.contains_key(&lookup_key)
{
forwarded_module_name = match api_set.get(&lookup_key) {
Some(x) => x.to_string(),
None => {forwarded_module_name}
};
}
else
{
forwarded_module_name = forwarded_module_name + ".dll";
}
let mut module = get_module_base_address(&forwarded_module_name);
// If the module is not already loaded, we try to load it dynamically calling LoadLibraryA
if module == 0
{
module = load_library_a(&forwarded_module_name);
}
if module != 0
{
return get_function_address(module, &forwarded_export_name);
}
function_ptr as isize
}
}
pub fn get_api_mapping() -> HashMap<String,String> {
unsafe
{
let handle = GetCurrentProcess();
let process_information: *mut c_void = std::mem::transmute(&PROCESS_BASIC_INFORMATION::default());
let _ret = nt_query_information_process(
handle,
0,
process_information,
size_of::<PROCESS_BASIC_INFORMATION>() as u32,
ptr::null_mut());
let _r = close_handle(handle);
let process_information_ptr: *mut PROCESS_BASIC_INFORMATION = std::mem::transmute(process_information);
let api_set_map_offset:usize;
if size_of::<usize>() == 4
{
api_set_map_offset = 0x38;
}
else
{
api_set_map_offset = 0x68;
}
let mut api_set_dict: HashMap<String,String> = HashMap::new();
let api_set_namespace_ptr = *(((*process_information_ptr).PebBaseAddress as usize + api_set_map_offset) as *mut isize);
let api_set_namespace_ptr: *mut ApiSetNamespace = std::mem::transmute(api_set_namespace_ptr);
let namespace = *api_set_namespace_ptr;
for i in 0..namespace.count
{
let set_entry_ptr = (api_set_namespace_ptr as usize + namespace.entry_offset as usize + (i * size_of::<ApiSetNamespaceEntry>() as i32) as usize) as *mut ApiSetNamespaceEntry;
let set_entry = *set_entry_ptr;
let mut api_set_entry_name_ptr = (api_set_namespace_ptr as usize + set_entry.name_offset as usize) as *mut u8;
let mut api_set_entry_name: String = "".to_string();
let mut j = 0;
while j < (set_entry.name_length / 2 )
{
let c = *api_set_entry_name_ptr as char;
if c != '\0' // Esto se podria meter en una funcion aparte
{
api_set_entry_name.push(c);
j = j + 1;
}
api_set_entry_name_ptr = api_set_entry_name_ptr.add(1);
}
let api_set_entry_key = format!("{}{}",&api_set_entry_name[..api_set_entry_name.len()-2], ".dll");
let mut set_value_ptr: *mut ApiSetValueEntry = ptr::null_mut();
if set_entry.value_length == 1
{
let value = (api_set_namespace_ptr as usize + set_entry.value_offset as usize) as *mut u8;
set_value_ptr = std::mem::transmute(value);
}
else if set_entry.value_length > 1
{
for x in 0..set_entry.value_length
{
let host_ptr = (api_set_entry_name_ptr as usize + set_entry.value_offset as usize + size_of::<ApiSetValueEntry>() as usize * x as usize) as *mut u8;
let mut c: u8 = u8::default();
let mut host: String = "".to_string();
while c as char != '\0'
{
c = *host_ptr;
if c as char != '\0'
{
host.push(c as char);
}
}
if host != api_set_entry_name
{
set_value_ptr = (api_set_namespace_ptr as usize + set_entry.value_offset as usize + size_of::<ApiSetValueEntry>() as usize * x as usize) as *mut ApiSetValueEntry;
}
}
if set_value_ptr == ptr::null_mut()
{
set_value_ptr = (api_set_namespace_ptr as usize + set_entry.value_offset as usize) as *mut ApiSetValueEntry;
}
}
let set_value = *set_value_ptr;
let mut api_set_value: String = "".to_string();
if set_value.value_count != 0
{
let mut value_ptr = (api_set_namespace_ptr as usize + set_value.value_offset as usize) as *mut u8;
let mut r = 0;
while r < (set_value.value_count / 2 )
{
let c = *value_ptr as char;
if c != '\0'
{
api_set_value.push(c);
r = r + 1;
}
value_ptr = value_ptr.add(1);
}
}
api_set_dict.insert(api_set_entry_key, api_set_value);
}
api_set_dict
}
}
/// Calls the module's entry point with the option DLL_ATTACH_PROCESS.
///
/// # Examples
///
/// ```ignore
/// let pe = manualmap::read_and_map_module("c:\\some\\random\\file.dll").unwrap();
/// let ret = dinvoke::call_module_entry_point(pe.0, pe.1);
///
/// match ret
/// {
/// Ok(()) => println!("Module entry point successfully executed."),
/// Err(e) => println!("Error ocurred: {}", e)
/// }
/// ```
pub fn call_module_entry_point(pe_info: PeMetadata, module_base_address: isize) -> Result<(), &'static str> {
let entry_point;
if pe_info.is_32_bit
{
entry_point = module_base_address + pe_info.opt_header_32.AddressOfEntryPoint as isize;
}
else
{
entry_point = module_base_address + pe_info.opt_header_64.address_of_entry_point as isize;
}
unsafe
{
let main: EntryPoint = std::mem::transmute(entry_point);
let module = HINSTANCE {0: entry_point as isize};
let ret = main(module, DLL_PROCESS_ATTACH, ptr::null_mut());
if !ret.as_bool()
{
return Err("[x] Failed to call module's entry point (DllMain -> DLL_PROCESS_ATTACH).");
}
Ok(())
}
}
/// Retrieves the address of an exported function from the specified module by its ordinal.
///
/// In case that the function's address can't be retrieved, it will return 0.
///
/// This functions internally calls LdrGetProcedureAddress.
///
/// # Examples
///
/// ```
/// let ntdll = dinvoke::get_module_base_address("ntdll.dll");
///
/// if ntdll != 0
/// {
/// let ordinal: u32 = 8;
/// let addr = dinvoke::get_function_address_ordinal(ntdll, ordinal);
/// if addr != 0
/// {
/// println!("The function with ordinal 8 is located at 0x{:X}.", addr);
/// }
/// }
/// ```
pub fn get_function_address_by_ordinal(module_base_address: isize, ordinal: u32) -> isize {
let ret = ldr_get_procedure_address(module_base_address, "", ordinal);
ret
}
/// Retrieves the address of an exported function from the specified module either by its name
/// or by its ordinal number.
///
/// This functions internally calls LdrGetProcedureAddress.
///
/// In case that the function's address can't be retrieved, it will return 0.
///
/// # Examples
///
/// ```
/// let ntdll = dinvoke::get_module_base_address("ntdll.dll");
///
/// if ntdll != 0
/// {
/// let ordinal: u32 = 8; // Ordinal 8 represents the function RtlDispatchAPC
/// let addr = dinvoke::ldr_get_procedure_address(ntdll,"", 8);
/// if addr != 0
/// {
/// println!("The function with ordinal 8 is located at 0x{:X}.", addr);
/// }
/// }
/// ```
pub fn ldr_get_procedure_address (module_handle: isize, function_name: &str, ordinal: u32) -> isize {
unsafe
{
let ret: Option<i32>;
let func_ptr: data::LdrGetProcedureAddress;
let hmodule: PVOID = std::mem::transmute(module_handle);
let return_address: *mut c_void = std::mem::transmute(&usize::default());
let return_address: *mut PVOID = std::mem::transmute(return_address);
let mut fun_name: *mut String = std::mem::transmute(&String::default());
if function_name == ""
{
fun_name = ptr::null_mut();
}
else
{
*fun_name = function_name.to_string();
}
let module_base_address = get_module_base_address("ntdll.dll");
dynamic_invoke!(module_base_address,"LdrGetProcedureAddress",func_ptr,ret,hmodule,fun_name,ordinal,return_address);
match ret {
Some(x) =>
{
if x == 0
{
return *return_address as isize;
}
else
{
return 0;
}
},
None => return 0,
}
}
}
/// Loads and retrieves a module's base address by dynamically calling LoadLibraryA.
///
/// It will return either the module's base address or an Err with a descriptive error message.
///
/// # Examples
///
/// ```
/// let ret = dinvoke::load_library_a("ntdll.dll");
///
/// if ret != 0 {println!("ntdll.dll base address is 0x{:X}.", addr);
/// ```
pub fn load_library_a(module: &str) -> isize {
unsafe
{
let ret: Option<HINSTANCE>;
let func_ptr: data::LoadLibraryA;
let name = CString::new(module.to_string()).expect("CString::new failed");
let function_name = PSTR{0: name.as_ptr() as *mut u8};
let module_base_address = get_module_base_address("kernel32.dll");
dynamic_invoke!(module_base_address,"LoadLibraryA",func_ptr,ret,function_name);
match ret {
Some(x) => return x.0 as isize,
None => return 0,
}
}
}
/// Closes a HANDLE object.
///
/// It will return either a boolean value or an Err with a descriptive error message. If the function
/// fails the bool value returned will be false.
///
/// # Examples
///
/// ```
/// let pid = 792u32;
/// let handle = dinvoke::open_process(0x0040, 0, pid); //PROCESS_DUP_HANDLE access right.
///
/// if handle.0 != 0 && handle.0 != -1
/// {
/// let r = dinvoke::close_handle(handle);
/// if r
/// {
/// println!("Handle to process with id {} closed.", pid);
/// }
/// }
/// ```
pub fn close_handle(handle: HANDLE) -> bool {
unsafe
{
let ret: Option<i32>;
let func_ptr: data::CloseHandle;
let ntdll = get_module_base_address("kernel32.dll");
dynamic_invoke!(ntdll,"CloseHandle",func_ptr,ret,handle);
match ret {
Some(x) =>
{
if x == 0
{
return false;
}
else
{
return true;
}
},
None => return false,
}
}
}
/// Dynamically calls NtQueryInformationProcess.
///
/// It will return the NTSTATUS value returned by the call.
pub fn nt_query_information_process (handle: HANDLE, process_information_class: u32, process_information: PVOID, length: u32, return_length: *mut u32) -> i32 {
unsafe
{
let ret;
let func_ptr: data::NtQueryInformationProcess;
let ntdll = get_module_base_address("ntdll.dll");
dynamic_invoke!(ntdll,"NtQueryInformationProcess",func_ptr,ret,handle,process_information_class,process_information,length,return_length);
match ret {
Some(x) => return x,
None => return -1,
}
}
}
/// Dynamically calls an exported function from the specified module.
///
/// This macro will use the dinvoke crate functions to obtain an exported
/// function address of the specified module in the runtime by walking process structures
/// and PE headers.
///
/// In case that this macro is used to call a dll entry point (DllMain), it will return true
/// or false (using the 3rd argument passed to the macro) depending on the success of the call.
/// In any other case, it will return the same data type that the called function would return
/// using the 4th argument passed to the macro.
///
/// # Example - Calling a dll entry point
///
/// ```ignore
/// let a = manualmap::read_and_map_module("c:\\some\\random\\file.dll").unwrap();
/// let ret: bool = false;
/// dinvoke::dynamic_invoke(&a.0, a.1, ret); // dinvoke::dynamic_invoke(&PeMetadata, isize, bool)
/// if ret { println!("Entry point successfully called.");}
/// ```
/// # Example - Dynamically calling LoadLibraryA
///
/// ```ignore
/// let kernel32 = manualmap::read_and_map_module("c:\\windows\\system32\\kernel32.dll").unwrap();
/// let mut ret:Option<HINSTANCE>;
/// let function_ptr: data::LoadLibraryA;
/// let name = CString::new("ntdll.dll").expect("CString::new failed");
/// let module_name = PSTR{0: name.as_ptr() as *mut u8};
/// //dinvoke::dynamic_invoke(isize,&str,<function_type>,Option<return_type>,[arguments])
/// dinvoke::dynamic_invoke(a.1, "LoadLibraryA", function_ptr, ret, module_name);
///
/// match ret {
/// Some(x) => if x.0 == 0 {println!("ntdll base address is 0x{:X}",x.0);},
/// None => println!("Error calling LdrGetProcedureAddress"),
/// }
/// ```
/// # Example - Dynamically calling with referenced arguments
///
/// ```ignore
/// let ptr = dinvoke::get_module_base_address("ntdll.dll");
/// let function_ptr: LdrGetProcedureAddress;
/// let ret: Option<i32>;
/// let hmodule: PVOID = std::mem::transmute(ptr);
/// let fun_name: *mut String = ptr::null_mut();
/// let ordinal = 8 as u32;
/// let return_address: *mut c_void = std::mem::transmute(&usize::default());
/// let return_address: *mut PVOID = std::mem::transmute(return_address);
/// //dinvoke::dynamic_invoke(isize,&str,<function_type>,Option<return_type>,[arguments])
/// dinvoke::dynamic_invoke!(ptr,"LdrGetProcedureAddress",function_ptr,ret,hmodule,fun_name,ordinal,return_address);
///
/// match ret {
/// Some(x) => if x == 0 {println!("RtlDispatchAPC is located at the address: 0x{:X}",*return_address as usize);},
/// None => println!("Error calling LdrGetProcedureAddress"),
/// }
/// ```
#[macro_export]
macro_rules! dynamic_invoke {
($a:expr, $b:expr, $c:expr) => {
let ret = $crate::call_module_entry_point(&$a,$b);
match ret {
Ok(_) => $c = true,
Err(_) => $c = false,
}
};
($a:expr, $b:expr, $c:expr, $d:expr, $($e:tt)*) => {
let function_ptr = $crate::get_function_address($a, $b);
if function_ptr != 0
{
$c = std::mem::transmute(function_ptr);
$d = Some($c($($e)*));
}
else
{
$d = None;
}
};
}
+225
View File
@@ -0,0 +1,225 @@
; Great portions of the asm code below has been obtained from https://github.com/klezVirus.
; All credits to @KlezVirus @trickster012 @waldoirc and @namazso for developing the original PoC of this technique.
.data
SPOOFER STRUCT
FirstFrameFunctionPointer DQ 1
SecondFrameFunctionPointer DQ 1
JmpRbxGadget DQ 1
AddRspXGadget DQ 1
FirstFrameSize DQ 1
SecondFrameSize DQ 1
JmpRbxGadgetFrameSize DQ 1
AddRspXGadgetFrameSize DQ 1
StackOffsetWhereRbpIsPushed DQ 1
SpoofFunctionPointer DQ 1
ReturnAddress DQ 1
Nargs DQ 1
Arg01 DQ 1
Arg02 DQ 1
Arg03 DQ 1
Arg04 DQ 1
Arg05 DQ 1
Arg06 DQ 1
Arg07 DQ 1
Arg08 DQ 1
Arg09 DQ 1
Arg10 DQ 1
Arg11 DQ 1
Sys DD 0
SysId DD 0
SPOOFER ENDS
.code
get_current_rsp proc
mov rax, rsp
add rax, 8
ret
get_current_rsp endp
spoof_call proc
; ------------------------------------------------------------------------------------
; Saving non-vol registers
; ------------------------------------------------------------------------------------
mov [rsp+08h], rbp
mov [rsp+10h], rbx
; ------------------------------------------------------------------------------------
; Prolog
; RBP -> Keeps track of original Stack
; RSP -> Desync Stack for Unwinding Info
; ------------------------------------------------------------------------------------
; Note: Everything between RSP and RBP is our new stack frame for unwinding
; ------------------------------------------------------------------------------------
mov rbp, rsp
; ------------------------------------------------------------------------------------
; Creating stack pointer to Restore PROC
; ------------------------------------------------------------------------------------
lea rax, restore
push rax
; Now RBX contains the stack pointer to Restore PROC
; -> Will be called by the JMP [RBX] gadget
lea rbx, [rsp]
; ------------------------------------------------------------------------------------
; Starting Frames Tampering
; ------------------------------------------------------------------------------------
; First Frame (Fake origin)
; ------------------------------------------------------------------------------------
push [rcx].SPOOFER.FirstFrameFunctionPointer
mov rax, [rcx].SPOOFER.ReturnAddress
sub rax, [rcx].SPOOFER.FirstFrameSize
sub rsp, [rcx].SPOOFER.SecondFrameSize
mov r10, [rcx].SPOOFER.StackOffsetWhereRbpIsPushed
mov [rsp+r10], rax
; ------------------------------------------------------------------------------------
; ROP Frames
; ------------------------------------------------------------------------------------
push [rcx].SPOOFER.SecondFrameFunctionPointer
; ------------------------------------------------------------------------------------
; 1. JMP [RBX] Gadget
; ------------------------------------------------------------------------------------
sub rsp, [rcx].SPOOFER.JmpRbxGadgetFrameSize
push [rcx].SPOOFER.JmpRbxGadget
sub rsp, [rcx].SPOOFER.AddRspXGadgetFrameSize
; ------------------------------------------------------------------------------------
; 2. Stack PIVOT (To restore original Control Flow Stack)
; ------------------------------------------------------------------------------------
push [rcx].SPOOFER.AddRspXGadget
; ------------------------------------------------------------------------------------
; Set the pointer to the function to call in R11
; ------------------------------------------------------------------------------------
mov r11, [rcx].SPOOFER.SpoofFunctionPointer
jmp parameter_handler
spoof_call endp
restore proc
mov rsp, rbp
mov rbp, [rsp+08h]
mov rbx, [rsp+10h]
ret
restore endp
parameter_handler proc
cmp [rcx].SPOOFER.Nargs, 11
je handle_eleven
cmp [rcx].SPOOFER.Nargs, 10
je handle_ten
cmp [rcx].SPOOFER.Nargs, 9
je handle_nine
cmp [rcx].SPOOFER.Nargs, 8
je handle_eight
cmp [rcx].SPOOFER.Nargs, 7
je handle_seven
cmp [rcx].SPOOFER.Nargs, 6
je handle_six
cmp [rcx].SPOOFER.Nargs, 5
je handle_five
cmp [rcx].SPOOFER.Nargs, 4
je handle_four
cmp [rcx].SPOOFER.Nargs, 3
je handle_three
cmp [rcx].SPOOFER.Nargs, 2
je handle_two
cmp [rcx].SPOOFER.Nargs, 1
je handle_one
cmp [rcx].SPOOFER.Nargs, 0
je handle_none
parameter_handler endp
handle_eleven proc
push r15
mov r15, [rcx].SPOOFER.Arg11
mov [rsp+60h], r15
pop r15
jmp handle_ten
handle_eleven endp
handle_ten proc
push r15
mov r15, [rcx].SPOOFER.Arg10
mov [rsp+58h], r15
pop r15
jmp handle_nine
handle_ten endp
handle_nine proc
push r15
mov r15, [rcx].SPOOFER.Arg09
mov [rsp+50h], r15
pop r15
jmp handle_eight
handle_nine endp
handle_eight proc
push r15
mov r15, [rcx].SPOOFER.Arg08
mov [rsp+48h], r15
pop r15
jmp handle_seven
handle_eight endp
handle_seven proc
push r15
mov r15, [rcx].SPOOFER.Arg07
mov [rsp+40h], r15
pop r15
jmp handle_six
handle_seven endp
handle_six proc
push r15
mov r15, [rcx].SPOOFER.Arg06
mov [rsp+38h], r15
pop r15
jmp handle_five
handle_six endp
handle_five proc
push r15
mov r15, [rcx].SPOOFER.Arg05
mov [rsp+30h], r15
pop r15
jmp handle_four
handle_five endp
handle_four proc
mov r9, [rcx].SPOOFER.Arg04
jmp handle_three
handle_four endp
handle_three proc
mov r8, [rcx].SPOOFER.Arg03
jmp handle_two
handle_three endp
handle_two proc
mov rdx, [rcx].SPOOFER.Arg02
jmp handle_one
handle_two endp
handle_one proc
cmp [rcx].SPOOFER.Sys, 0
jne execute_syscall
mov rcx, [rcx].SPOOFER.Arg01
jmp handle_none
handle_one endp
handle_none proc
jmp execute
handle_none endp
execute proc
jmp qword ptr r11
execute endp
execute_syscall proc
mov r10, [rcx].SPOOFER.Arg01
mov eax, [rcx].SPOOFER.SysId
mov rcx, [rcx].SPOOFER.Arg01
jmp qword ptr r11
execute_syscall endp
end
+1163
View File
File diff suppressed because it is too large Load Diff
-386
View File
@@ -1,386 +0,0 @@
#![feature(link_llvm_intrinsics)]
use std::{ffi::c_void, ptr, mem::transmute};
use rand::Rng;
use bindings::Windows::Win32::System::Diagnostics::Debug::{IMAGE_SECTION_HEADER, IMAGE_OPTIONAL_HEADER32};
use bitreader::BitReader;
use data::{PeMetadata, IMAGE_FILE_HEADER, IMAGE_OPTIONAL_HEADER64, RUNTIME_FUNCTION};
extern {
#[link_name = "llvm.addressofreturnaddress"]
fn return_address() -> *const u8;
}
///
/// List of functions used to spoof the stack.
/// You can add any Windows APi function that you
/// want to use, taking into account that not all
/// unwind codes have been implemented in this PoC.
///
const FUNCTIONS: [&str; 12] = [
"kernelbase.dll!WriteProcessMemory",
"kernelbase.dll!ReadProcessMemory",
"kernelbase.dll!CloseHandle",
"kernelbase.dll!DsFreeNameResultW",
"kernelbase.dll!K32EnumProcessModulesEx",
"ntdll.dll!NtCreateProcessEx",
"ntdll.dll!LdrLoadDll",
"ntdll.dll!RtlAdjustPrivilege",
"kernelbase.dll!VirtualFree",
"kernelbase.dll!GetCalendarInfoEx",
"ntdll.dll!RtlUserThreadStart",
"kernel32.dll!BaseThreadInitThunk"
];
fn main() {
unsafe
{
let k32 = dinvoke::get_module_base_address("kernel32.dll");
loop {
let mut rng = rand::thread_rng();
// The number of functions and the functions themselves used to spoof the stack are generated randomly.
// This allows to obtain a different thread stack each iteration.
let spoofing_count = rng.gen_range(1..FUNCTIONS.len());
let mut return_addr = return_address() as *mut isize;
for _ in 0..spoofing_count
{
let index = rng.gen_range(0..FUNCTIONS.len());
let function: Vec<&str> = FUNCTIONS[index].split("!").collect();
let spoofing_data = get_stack_size(function[0], function[1]);
if spoofing_data.0 != -1
{
*return_addr = spoofing_data.1;
return_addr = return_addr.add(spoofing_data.0 as usize + 1);
*return_addr = 0;
}
else
{
println!("[x] Function not found: {}", function[1]);
}
}
println!("[+] Thread stack spoofed!");
let f:data::Sleep;
let _r: Option<()>;
println!("[ZzZ] Sleeping... Check the stack!");
dinvoke::dynamic_invoke!(k32,"Sleep",f,_r,15000);
println!("--------------------------");
}
}
}
/// This is the function where the main logic is implemented.
/// get_stack_size() is in charge of parsing the unwind information of the
/// selected spoofing function, allowing the tool to dynamically obtain the stack offset
/// where the return address is expected.
///
/// The unwind info is not always parsed from the beginning of the struct since in order to add an extra
/// layer of randomness to the spoofing process a random generated offset is added to the function's base address (this offset is always
/// within the range [function's base address, function's base address + prolog size].
///
/// It returns the stack offset where the return address is expected and the memory address that will be set in that stack location (function base addres + offset).
///
fn get_stack_size(dll_name: &str, function_name: &str) -> (i32,isize)
{
unsafe
{
let module = dinvoke::get_module_base_address(dll_name);
if module == 0
{
return (-1,-1);
}
let func_base_addr = dinvoke::get_function_address(module, function_name);
if func_base_addr == 0
{
return (-1,-1);
}
let module_metadata = get_pe_metadata(module as *const u8);
if !module_metadata.is_ok()
{
return (-1,-1);
}
let metadata = module_metadata.unwrap();
let mut runtime_table = get_runtime_table(&metadata, module as *mut c_void);
// Iterate over the PE RUNTIME FUNCTION until we get the unwind information of the spoofing function.
while (*runtime_table).begin_addr != 0
{
if ((*runtime_table).begin_addr as isize + module ) == func_base_addr
{
break;
}
runtime_table = runtime_table.add(1);
}
// In case that a leaf function has been selected, it will not have unwind information.
if (*runtime_table).begin_addr == 0
{
return (0,func_base_addr);
}
// Here we start the parsing of the UNWIND_INFO struct
let unwind_info = (module + (*runtime_table).unwind_addr as isize) as *mut u8;
let version_and_flags = (*unwind_info).to_le_bytes().clone();
let mut reader = BitReader::new(&version_and_flags);
// We don't care about the version, we just need the flags to check if there is an Unwind Chain.
let flags = reader.read_u8(5).unwrap();
let unwind_codes_count = *(unwind_info.add(2));
let mut start = 0;
if unwind_codes_count > 1
{
// In case that the function has more than one unwind code,
// we select a random offset from where we will start the parsing of the
// Unwind codes array.
let mut rng = rand::thread_rng();
start = rng.gen_range(1..unwind_codes_count);
}
// We skip 4 bytes corresponding to Version + flags, Size of prolog, Count of unwind codes
// and Frame Register + Frame Register offset.
// This way we reach the Unwind codes array.
let unwind_codes = (unwind_info.add(4)) as *mut u16;
// We add the previously generated offset to start the array parsing from a random position within the fucntion's prolog.
let mut unwind_code = unwind_codes.add(start as usize) as *mut u8;
// We fix any issue that may appear due to the fact that we are starting from a random and unknow
// position inside the Unwind codes array.
let check = check_unwind_node(unwind_code);
unwind_code = check.0;
start += check.1;
// We get the Offset in prolog value contained in the first UNWIND_CODE struct that we parse.
// This offset will be added to the function's memory base address.
let offset = *unwind_code;
// From this point, we parse the remaining items in the Unwind codes array, obtaining the stack
// offset where we will have to insert our spoofed address.
let result = iterate_unwind_array(module, unwind_code,unwind_codes_count,flags,start);
(result, func_base_addr + offset as isize)
}
}
fn iterate_unwind_array(module: isize, mut unwind_code: *mut u8, unwind_codes_count: u8, flags:u8, start: u8) -> i32
{
unsafe
{
let mut unwind_code_operation_code_info = unwind_code.add(1);
// This counter stores the offset of the stack.
let mut stack_count = 0;
let mut index = start;
while index < unwind_codes_count
{
let operation_code_and_info = (*unwind_code_operation_code_info).to_be_bytes().clone();
let mut reader = BitReader::new(&operation_code_and_info);
let operation_info = reader.read_u8(4).unwrap(); // operation info
let operation_code = reader.read_u8(4).unwrap(); // operation code
// Since this is a PoC, just some of the unwind codes (the most common of them) have been implemented.
// If you try to use a function with an unwind code not contemplated below the spoof will
// likely fail.
match operation_code
{
0 =>
{
// println!("UWOP_PUSH_NONVOL");
stack_count += 1;
},
1 =>
{
// println!("UWOP_ALLOC_LARGE");
if operation_info == 0
{
let size = *(unwind_code_operation_code_info.add(1) as *mut i16);
stack_count += size as i32;
unwind_code = unwind_code.add(2);
index += 1;
}
else if operation_code == 1
{
let size = *(unwind_code_operation_code_info.add(1) as *mut u16) as i32;
let size2 = (*(unwind_code_operation_code_info.add(3) as *mut u16) as i32) << 16;
stack_count += (size + size2) / 8;
unwind_code = unwind_code.add(4);
index += 2;
}
},
2 =>
{
// println!("UWOP_ALLOC_SMALL");
stack_count += ((operation_info * 8 + 8) / 8) as i32;
},
4 =>
{
// println!("UWOP_SAVE_NONVOL ");
unwind_code = unwind_code.add(2);
index += 1;
},
5 =>
{
// println!("UWOP_SAVE_NONVOL_FAR ");
unwind_code = unwind_code.add(4);
index += 2;
},
_=> println!("[x] Unknown unwind code: {}", operation_code),
}
unwind_code = unwind_code.add(2);
unwind_code_operation_code_info = unwind_code.add(1);
index += 1;
}
// In case that the flag UNW_FLAG_CHAININFO is set, we recursively call this function.
if (flags & 0x4) != 0
{
if unwind_codes_count % 2 != 0
{
unwind_code = unwind_code.add(2);
}
let runtime_table: *mut RUNTIME_FUNCTION = transmute(unwind_code);
let unwind_info = (module + (*runtime_table).unwind_addr as isize) as *mut u8;
let version_and_flags = (*unwind_info).to_le_bytes().clone();
let mut reader = BitReader::new(&version_and_flags);
let flags = reader.read_u8(5).unwrap();
let unwind_codes_count = *(unwind_info.add(2));
let mut start = 0;
if unwind_codes_count > 1
{
let mut rng = rand::thread_rng();
start = rng.gen_range(1..unwind_codes_count);
}
let unwind_codes = (unwind_info.add(4)) as *mut u16;
let unwind_code = unwind_codes.add(start as usize) as *mut u8;
let result = iterate_unwind_array(module, unwind_code, unwind_codes_count, flags, 0);
stack_count += result;
}
// We just return the stack offset calculated in this function, which is the number of
// words that we should add to the stack in order to obtain the memory address where the
// next return address will be expected.
stack_count
}
}
fn check_unwind_node(unwind_code: *mut u8) -> (*mut u8, u8)
{
unsafe
{
let previous_code = unwind_code.sub(2);
let unwind_code_operation_code_info = previous_code.add(1);
let operation_code_and_info = (*unwind_code_operation_code_info).to_be_bytes().clone();
let mut reader = BitReader::new(&operation_code_and_info);
let operation_info = reader.read_u8(4).unwrap(); // operation info
let operation_code = reader.read_u8(4).unwrap(); // operation code
if operation_code == 4 || (operation_code == 1 && operation_info == 0)
{
return (unwind_code.add(2),1);
}
if operation_code == 5 || (operation_code == 1 && operation_info == 1)
{
return (unwind_code.add(4),2);
}
let prev_prev_code = previous_code.sub(2);
let unwind_code_operation_code_info = prev_prev_code.add(1);
let operation_code_and_info = (*unwind_code_operation_code_info).to_be_bytes().clone();
let mut reader = BitReader::new(&operation_code_and_info);
let operation_info = reader.read_u8(4).unwrap(); // operation info
let operation_code = reader.read_u8(4).unwrap(); // operation code
if operation_code == 5 || (operation_code == 1 && operation_info == 1)
{
return (unwind_code.add(2),1);
}
(unwind_code,0)
}
}
fn get_runtime_table(pe_info: &PeMetadata, image_ptr: *mut c_void) -> *mut data::RUNTIME_FUNCTION
{
unsafe
{
let mut runtime: *mut data::RUNTIME_FUNCTION = ptr::null_mut();
for section in &pe_info.sections
{
let s = std::str::from_utf8(&section.Name).unwrap();
if s.contains(".pdata")
{
let base = image_ptr as isize;
runtime = std::mem::transmute(base + section.VirtualAddress as isize);
}
}
return runtime;
}
}
fn get_pe_metadata (module_ptr: *const u8) -> Result<PeMetadata,String>
{
let mut pe_metadata= PeMetadata::default();
unsafe {
let e_lfanew = *((module_ptr as usize + 0x3C) as *const u32);
pe_metadata.pe = *((module_ptr as usize + e_lfanew as usize) as *const u32);
if pe_metadata.pe != 0x4550
{
return Err("[x] Invalid PE signature.".to_string());
}
pe_metadata.image_file_header = *((module_ptr as usize + e_lfanew as usize + 0x4) as *mut IMAGE_FILE_HEADER);
let opt_header: *const u16 = (module_ptr as usize + e_lfanew as usize + 0x18) as *const u16;
let pe_arch = *(opt_header);
if pe_arch == 0x010B
{
pe_metadata.is_32_bit = true;
let opt_header_content: *const IMAGE_OPTIONAL_HEADER32 = std::mem::transmute(opt_header);
pe_metadata.opt_header_32 = *opt_header_content;
}
else if pe_arch == 0x020B
{
pe_metadata.is_32_bit = false;
let opt_header_content: *const IMAGE_OPTIONAL_HEADER64 = std::mem::transmute(opt_header);
pe_metadata.opt_header_64 = *opt_header_content;
}
else
{
return Err("[x] Invalid magic value.".to_string());
}
let mut sections: Vec<IMAGE_SECTION_HEADER> = vec![];
for i in 0..pe_metadata.image_file_header.number_of_sections
{
let section_ptr = (opt_header as usize + pe_metadata.image_file_header.size_of_optional_header as usize + (i * 0x28) as usize) as *const u8;
let section_ptr: *const IMAGE_SECTION_HEADER = std::mem::transmute(section_ptr);
sections.push(*section_ptr);
}
pe_metadata.sections = sections;
Ok(pe_metadata)
}
}