This commit is contained in:
Whitecat18
2026-06-06 17:08:16 +05:30
parent b012e6dcaf
commit ff0d2cbb21
47 changed files with 5325 additions and 352 deletions
+4 -21
View File
@@ -1,21 +1,4 @@
# Build output
target/
**/target/
# Editor scratch
.vscode/
.idea/
*.swp
*.swo
*~
# OS scratch
Thumbs.db
.DS_Store
# Sub-crate lockfiles (only the root Cargo.lock should be tracked)
data/Cargo.lock
dmanager/Cargo.lock
dyncvoke_core/Cargo.lock
manualmap/Cargo.lock
overload/Cargo.lock
/target
**/target
Cargo.lock.bak
*.pdb
Generated
+72 -2
View File
@@ -41,6 +41,26 @@ dependencies = [
"windows-link",
]
[[package]]
name = "bitfield"
version = "0.19.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21ba6517c6b0f2bf08be60e187ab64b038438f22dd755614d8fe4d4098c46419"
dependencies = [
"bitfield-macros",
]
[[package]]
name = "bitfield-macros"
version = "0.19.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f48d6ace212fdf1b45fd6b566bb40808415344642b76c3224c07c8df9da81e97"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -62,6 +82,16 @@ dependencies = [
"objc2",
]
[[package]]
name = "cc"
version = "1.2.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
@@ -78,6 +108,7 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
name = "data"
version = "0.1.0"
dependencies = [
"obfstr",
"windows-sys 0.59.0",
]
@@ -106,13 +137,14 @@ dependencies = [
[[package]]
name = "dyncvoke"
version = "0.1.1"
version = "0.1.0"
dependencies = [
"data",
"dmanager",
"dyncvoke_core",
"manualmap",
"overload",
"spoof",
]
[[package]]
@@ -121,7 +153,6 @@ version = "0.1.0"
dependencies = [
"data",
"libc",
"litcrypt2",
"nanorand",
"windows-sys 0.59.0",
]
@@ -148,6 +179,12 @@ dependencies = [
"synstructure",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "getrandom"
version = "0.2.17"
@@ -227,6 +264,15 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e3d189da485332e96ba8a5ef646a311871abd7915bf06ac848a9117f19cf6e4"
[[package]]
name = "nasm-rs"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "706bf8a5e8c8ddb99128c3291d31bd21f4bcde17f0f4c20ec678d85c74faa149"
dependencies = [
"log",
]
[[package]]
name = "nix"
version = "0.30.1"
@@ -239,6 +285,12 @@ dependencies = [
"libc",
]
[[package]]
name = "obfstr"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0d354e9a302760d07e025701d40534f17dd1fe4c4db955b4e3bd2907c63bdee"
[[package]]
name = "objc2"
version = "0.6.4"
@@ -498,6 +550,24 @@ version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "spoof"
version = "0.1.0"
dependencies = [
"bitfield",
"cc",
"data",
"dyncvoke_core",
"memchr",
"nasm-rs",
]
[[package]]
name = "syn"
version = "1.0.109"
+17 -6
View File
@@ -1,6 +1,6 @@
[package]
name = "dyncvoke"
version = "0.1.1"
version = "0.1.0"
edition = "2021"
description = "Dynamically invoke arbitrary unmanaged code"
license = "MIT OR Apache-2.0"
@@ -10,7 +10,7 @@ documentation = "README.md"
readme = "README.md"
keywords = ["windows_sys", "dynamic", "redteam"]
categories = ["command-line-utilities"]
authors = ["5mukx", "staffs@5mukx.site"]
authors = ["5mukx"]
[lib]
name = "dyncvoke"
@@ -25,8 +25,19 @@ strip = true
lto = false
[dependencies]
dyncvoke_core = { path = "dyncvoke_core" }
manualmap = { path = "manualmap" }
data = { path = "data" }
overload = { path = "overload" }
dmanager = { path = "dmanager" }
dyncvoke_core = { path = "dyncvoke_core", optional = true }
manualmap = { path = "manualmap", optional = true }
overload = { path = "overload", optional = true }
dmanager = { path = "dmanager", optional = true }
spoof = { path = "spoof", optional = true }
[features]
default = ["syscall"]
syscall = ["dep:dyncvoke_core"]
manualmap = ["dep:manualmap", "syscall"]
overload = ["dep:overload", "manualmap"]
dmanager = ["dep:dmanager", "overload"]
spoof = ["dep:spoof", "syscall"]
spoof-desync = ["spoof", "spoof/desync"]
full = ["syscall", "manualmap", "overload", "dmanager", "spoof", "spoof-desync"]
+76 -10
View File
@@ -1,4 +1,4 @@
# dyncvoke_rs
# Dyncvoke_rs
A Rust crate for indirect syscalls and dynamic invocation on Windows. The point is calling NT functions without leaving import-table traces and without tripping inline hooks on ntdll exports.
@@ -18,6 +18,7 @@ dyncvoke_core::sys Tartarus Gate plus the variadic do_syscall gateway
manualmap PE mapping with relocations and IAT rewriting
overload section overloading, module stomping, template stomping
dmanager the fluctuation manager
spoof call stack spoofing, synthetic and desync modes
data shared types and FFI signatures
```
@@ -27,24 +28,41 @@ This is still moving. Test before shipping anything you care about.
```toml
[dependencies]
dyncvoke = { git = "https://github.com/Whitecat18/Dyncvoke" }
dyncvoke = { git = "https://codeberg.org/smukx/Dyncvoke.git" }
```
Or from a local checkout:
```toml
[dependencies]
dyncvoke = { path = "path/to/dyncvoke_rs" }
dyncvoke = { path = "path/to/Dyncvoke" }
```
Just the core:
Core only:
```toml
[dependencies]
dyncvoke_core = { path = "path/to/dyncvoke_rs/dyncvoke_core" }
dyncvoke_core = { path = "path/to/Dyncvoke/dyncvoke_core" }
```
No feature flags. The syscall path is always on for x86_64.
Features:
- `syscall` (default): indirect syscall path. Pulls in `dyncvoke_core`.
- `manualmap`: PE manual mapping. Implies `syscall`.
- `overload`: section overloading and module stomping. Implies `manualmap`.
- `dmanager`: module fluctuation manager. Implies `overload`.
- `spoof`: call stack spoofing, synthetic mode.
- `spoof-desync`: spoof with the desync mode toggled on.
- `full`: everything above.
Picking a non-default set looks like this:
```toml
[dependencies]
dyncvoke = { git = "https://codeberg.org/smukx/Dyncvoke.git", default-features = false, features = ["syscall", "spoof", "spoof-desync"] }
```
x86_64 only.
## Using it
@@ -203,13 +221,61 @@ fn main() {
x64 only.
### Call stack spoofing (spoof crate)
Enable the `spoof` feature for synthetic mode, or `spoof-desync` for desync mode. Synthetic mode builds a fake stack rooted at `RtlUserThreadStart` and runs from any thread, including pool threads. Desync mode finds a real `BaseThreadInitThunk` return address on the current thread's stack and splices spoofed frames on top. Use desync only on threads started through the normal user-thread chain.
The `spoof_syscall!` macro spoofs the call stack and dispatches an indirect syscall in one step. SSN resolution runs through the same Tartarus Gate path as `dyncvoke_core::syscall!`.
```rust
use spoof::spoof_syscall;
use core::ffi::c_void;
fn main() {
let mut addr: *mut c_void = core::ptr::null_mut();
let mut size: usize = 0x1000;
let status = spoof_syscall!(
"NtAllocateVirtualMemory",
-1isize, // NtCurrentProcess
&mut addr as *mut _,
0usize,
&mut size as *mut _,
0x3000u32, // MEM_COMMIT | MEM_RESERVE
0x04u32 // PAGE_READWRITE
).map(|p| p as i32).unwrap_or(-1);
println!("NTSTATUS = 0x{:08X}", status as u32);
}
```
For non-ntdll calls, resolve the address with `dyncvoke_core` and dispatch through `spoof::spoof!` instead.
```rust
use dyncvoke::dyncvoke_core;
use spoof::spoof;
fn main() {
let kernel32 = dyncvoke_core::get_module_base_address("kernel32.dll");
let virtual_alloc = dyncvoke_core::get_function_address(kernel32, "VirtualAlloc");
let p = unsafe {
spoof!(
virtual_alloc,
core::ptr::null_mut::<core::ffi::c_void>(),
1 << 12,
0x3000u32,
0x04u32
)
};
}
```
## License
[MIT LICENSE](./LICENSE)
## Credits
My inspirations:-
- [DInvoke_rs](https://github.com/Kudaes/DInvoke_rs/tree/main) by Kudaes.
- [dinvk](https://github.com/joaoviictorti/dinvk) by João Victor.
- [DInvoke_rs](https://github.com/Kudaes/DInvoke_rs/tree/main)
- [dinvk](https://github.com/joaoviictorti/dinvk)
+90
View File
@@ -0,0 +1,90 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "data"
version = "0.1.0"
dependencies = [
"obfstr",
"windows-sys",
]
[[package]]
name = "obfstr"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0d354e9a302760d07e025701d40534f17dd1fe4c4db955b4e3bd2907c63bdee"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+1
View File
@@ -7,6 +7,7 @@ edition = "2021"
strip = true
[dependencies]
obfstr = "0.4"
[dependencies.windows-sys]
version = "0.59"
+32 -9
View File
@@ -1,7 +1,26 @@
// dyncvoke - Data Types
// @5mukx
use std::{collections::BTreeMap, ffi::c_void};
#![no_std]
extern crate alloc;
use alloc::{collections::BTreeMap, string::String, vec::Vec};
use core::ffi::c_void;
// obfstr is the no_std-compatible replacement for litcrypt2. Re-exported so
// the workspace-wide lc!() macro below can resolve obfstr::obfstring via
// $crate::obfstr::obfstring from any consumer crate.
pub use obfstr;
/// Compile-time obfuscated string. Replaces litcrypt2's lc!() with an
/// obfstr-backed expansion that returns alloc::string::String and works in
/// no_std. Call sites are unchanged.
#[macro_export]
macro_rules! lc {
($s:literal) => {
$crate::obfstr::obfstring!($s)
};
}
use windows_sys::Win32::Foundation::BOOL;
use windows_sys::Win32::Graphics::Printing::{DOC_INFO_1A, PRINTER_DEFAULTSA};
use windows_sys::Win32::Security::SECURITY_ATTRIBUTES;
@@ -42,7 +61,7 @@ pub struct OBJECT_ATTRIBUTES {
impl Default for OBJECT_ATTRIBUTES {
fn default() -> Self {
unsafe { std::mem::zeroed() }
unsafe { core::mem::zeroed() }
}
}
@@ -127,7 +146,7 @@ pub type CryptSignHashW = unsafe extern "system" fn (usize, u32, *mut u16, u32,
pub type CreateEventW = unsafe extern "system" fn (*const SECURITY_ATTRIBUTES, i32, i32, *const u16) -> HANDLE;
/// Counted ANSI string as the kernel expects. LdrGetProcedureAddress reads
/// 16 bytes here (USHORT Length, USHORT MaximumLength, PCHAR Buffer + 4
/// bytes of natural alignment padding on x64).
/// bytes of natural alignment padding on x64). Bug 10 in AUDIT.md.
#[repr(C)]
#[allow(non_camel_case_types, non_snake_case)]
pub struct ANSI_STRING {
@@ -263,8 +282,8 @@ impl Default for PeMetadata {
pe: u32::default(),
is_32_bit: false,
image_file_header: ImageFileHeader::default(),
opt_header_32: std::mem::zeroed(),
opt_header_64: std::mem::zeroed(),
opt_header_32: core::mem::zeroed(),
opt_header_64: core::mem::zeroed(),
sections: Vec::default(),
}
}
@@ -456,7 +475,7 @@ pub struct ClientId {
impl Default for ClientId {
fn default() -> Self {
unsafe { std::mem::zeroed() }
unsafe { core::mem::zeroed() }
}
}
@@ -715,9 +734,13 @@ pub struct ThreadBasicInformation {
// Tests
// ============================================================================
#[cfg(test)]
extern crate std;
#[cfg(test)]
mod tests {
use super::*;
use core::ptr;
// Test UNICODE_STRING structure
#[test]
@@ -733,7 +756,7 @@ mod tests {
let us = UNICODE_STRING {
Length: 10,
MaximumLength: 12,
Buffer: std::ptr::null_mut(),
Buffer: ptr::null_mut(),
};
assert_eq!(us.Length, 10);
assert_eq!(us.MaximumLength, 12);
@@ -948,14 +971,14 @@ mod tests {
// Test HANDLE type
#[test]
fn test_handle_type() {
let null_handle: HANDLE = std::ptr::null_mut();
let null_handle: HANDLE = ptr::null_mut();
assert!(null_handle.is_null());
}
// Test HINSTANCE type
#[test]
fn test_hinstance_type() {
let null_hinstance: HINSTANCE = std::ptr::null_mut();
let null_hinstance: HINSTANCE = ptr::null_mut();
assert!(null_hinstance.is_null());
}
}
+688
View File
@@ -0,0 +1,688 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "addr2line"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "backtrace"
version = "0.3.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-link",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "block2"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
dependencies = [
"objc2",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "data"
version = "0.1.0"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "dispatch2"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
dependencies = [
"bitflags 2.11.0",
"objc2",
]
[[package]]
name = "dmanager"
version = "0.1.0"
dependencies = [
"data",
"dyncvoke_core",
"litcrypt2",
"manualmap",
"nanorand",
"overload",
"windows-sys 0.59.0",
]
[[package]]
name = "dyncvoke_core"
version = "0.1.0"
dependencies = [
"data",
"libc",
"litcrypt2",
"nanorand",
"windows-sys 0.59.0",
]
[[package]]
name = "failure"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
dependencies = [
"backtrace",
"failure_derive",
]
[[package]]
name = "failure_derive"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"synstructure",
]
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gimli"
version = "0.32.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "litcrypt2"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4126aa57ac1b3dd20a5bc827a2972cdf74c619a4d6ae5660656408289e5bc60d"
dependencies = [
"lazy_static",
"proc-macro2",
"quote",
"rand",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "manualmap"
version = "0.1.0"
dependencies = [
"data",
"dyncvoke_core",
"litcrypt2",
"os_info",
"windows-sys 0.59.0",
]
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
]
[[package]]
name = "nanorand"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e3d189da485332e96ba8a5ef646a311871abd7915bf06ac848a9117f19cf6e4"
[[package]]
name = "nix"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags 2.11.0",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "objc2"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05"
dependencies = [
"objc2-encode",
]
[[package]]
name = "objc2-cloud-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
dependencies = [
"bitflags 2.11.0",
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-data"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags 2.11.0",
"dispatch2",
"objc2",
]
[[package]]
name = "objc2-core-graphics"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
dependencies = [
"bitflags 2.11.0",
"dispatch2",
"objc2",
"objc2-core-foundation",
"objc2-io-surface",
]
[[package]]
name = "objc2-core-image"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-location"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-text"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
dependencies = [
"bitflags 2.11.0",
"objc2",
"objc2-core-foundation",
"objc2-core-graphics",
]
[[package]]
name = "objc2-encode"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
[[package]]
name = "objc2-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags 2.11.0",
"block2",
"libc",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "objc2-io-surface"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
dependencies = [
"bitflags 2.11.0",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "objc2-quartz-core"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
dependencies = [
"bitflags 2.11.0",
"objc2",
"objc2-core-foundation",
"objc2-foundation",
]
[[package]]
name = "objc2-ui-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
dependencies = [
"bitflags 2.11.0",
"block2",
"objc2",
"objc2-cloud-kit",
"objc2-core-data",
"objc2-core-foundation",
"objc2-core-graphics",
"objc2-core-image",
"objc2-core-location",
"objc2-core-text",
"objc2-foundation",
"objc2-quartz-core",
"objc2-user-notifications",
]
[[package]]
name = "objc2-user-notifications"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "object"
version = "0.37.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
dependencies = [
"memchr",
]
[[package]]
name = "os_info"
version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224"
dependencies = [
"android_system_properties",
"log",
"nix",
"objc2",
"objc2-foundation",
"objc2-ui-kit",
"windows-sys 0.61.2",
]
[[package]]
name = "overload"
version = "0.1.0"
dependencies = [
"data",
"dyncvoke_core",
"litcrypt2",
"manualmap",
"nanorand",
"windows-sys 0.59.0",
"winproc",
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "rustc-demangle"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.115"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"unicode-xid",
]
[[package]]
name = "unicode-ident"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "widestring"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c"
[[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"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winproc"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "979e07b33c6af27e5c454e42d1b946b403ec222e7bad52ef020820708e087b25"
dependencies = [
"bitflags 1.3.2",
"failure",
"widestring",
"winapi",
]
[[package]]
name = "zerocopy"
version = "0.8.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.115",
]
+112
View File
@@ -0,0 +1,112 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "data"
version = "0.1.0"
dependencies = [
"obfstr",
"windows-sys",
]
[[package]]
name = "dyncvoke_core"
version = "0.1.0"
dependencies = [
"data",
"libc",
"nanorand",
"windows-sys",
]
[[package]]
name = "libc"
version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "nanorand"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e3d189da485332e96ba8a5ef646a311871abd7915bf06ac848a9117f19cf6e4"
[[package]]
name = "obfstr"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0d354e9a302760d07e025701d40534f17dd1fe4c4db955b4e3bd2907c63bdee"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+2 -3
View File
@@ -8,11 +8,10 @@ strip = true
[dependencies]
data = { path = "../data" }
libc = "0.2.182"
litcrypt2 = "=0.1.2"
libc = { version = "0.2.182", default-features = false }
[target.'cfg(target_arch = "x86_64")'.dependencies]
nanorand = { version = "0.8.0" }
nanorand = { version = "0.8.0", default-features = false, features = ["wyrand"] }
[dependencies.windows-sys]
version = "0.59"
+149 -202
View File
@@ -4,28 +4,33 @@
// This library provides dynamic Windows API invocation, PE manipulation,
// indirect syscalls, and various red team techniques.
#[macro_use]
extern crate litcrypt2;
use_litcrypt!();
#![no_std]
extern crate alloc;
use alloc::{
collections::BTreeMap,
ffi::CString,
format,
string::{String, ToString},
vec,
vec::Vec,
};
use core::{mem::size_of, ptr};
use data::lc;
use data::LARGE_INTEGER;
use libc::c_void;
use litcrypt2::lc;
use std::ffi::{CString, OsString};
use std::mem::size_of;
use std::os::windows::ffi::OsStringExt;
use std::panic;
use std::{collections::HashMap, ptr};
use windows_sys::Win32::Foundation::BOOL;
use windows_sys::Win32::Security::SECURITY_ATTRIBUTES;
#[cfg(target_arch = "x86_64")]
use windows_sys::Win32::System::Diagnostics::Debug::{GetThreadContext, SetThreadContext};
use windows_sys::Win32::System::Memory::MEMORY_BASIC_INFORMATION;
use windows_sys::Win32::System::ProcessStatus::{
EnumProcessModules, GetModuleBaseNameW, GetModuleFileNameExW,
};
use windows_sys::Win32::System::SystemInformation::SYSTEM_INFO;
use windows_sys::Win32::System::IO::IO_STATUS_BLOCK;
#[cfg(target_arch = "x86_64")]
use windows_sys::Win32::System::Threading::GetCurrentProcessId;
pub mod peb;
pub use data::HANDLE;
pub use data::HINSTANCE;
@@ -41,8 +46,14 @@ pub use sys::{extract_ssn, get_syscall_address, resolve_syscall, SyscallError};
#[cfg(target_arch = "x86_64")]
pub use sys::asm::do_syscall;
// Constants and shared types. Importing them once unconditionally keeps the
// unit-test module (which references them) in scope on every build cfg.
// Constants and shared types — same on every cfg. Importing them once
// unconditionally keeps the unit-test module (which references them) in
// scope on every build configuration. Bug 9 in AUDIT.md.
//
// A subset of the constants is only used by the non-syscall prepare_syscall
// path (the syscall variant doesn't allocate shellcode). #[allow] keeps the
// build clean under --features syscall until Phase 3 removes the feature
// flag entirely.
#[allow(unused_imports)]
use data::{
ApiSetNamespace, ApiSetNamespaceEntry, ApiSetValueEntry, ClientId, EntryPoint,
@@ -53,7 +64,7 @@ use data::{
PROCESS_QUERY_LIMITED_INFORMATION, PVOID, TLS_OUT_OF_INDEXES,
};
// Types that are only used on x86_64. Keep the cfg-gate on these so x86
// Types that are only used on x86_64 — keep the cfg-gate on these so x86
// builds don't drag them in.
#[cfg(target_arch = "x86_64")]
use data::{
@@ -130,7 +141,7 @@ pub fn set_hardware_breakpoint(address: usize) {
use windows_sys::Win32::System::Diagnostics::Debug::CONTEXT;
unsafe {
let mut context: CONTEXT = std::mem::zeroed();
let mut context: CONTEXT = core::mem::zeroed();
context.ContextFlags = 0x100000 | 0x10; // CONTEXT_DEBUG_REGISTERS
let _ = GetThreadContext(GetCurrentThread(), &mut context);
@@ -163,44 +174,44 @@ pub unsafe extern "system" fn breakpoint_handler(exceptioninfo: *mut ExceptionPo
(*(*exceptioninfo).context_record).R10 =
NT_ALLOCATE_VIRTUAL_MEMORY_ARGS.handle as u64 as u64;
(*(*exceptioninfo).context_record).Rdx =
std::mem::transmute(NT_ALLOCATE_VIRTUAL_MEMORY_ARGS.base_address);
core::mem::transmute(NT_ALLOCATE_VIRTUAL_MEMORY_ARGS.base_address);
}
ExceptionHandleFunction::NtProtectVirtualMemory => {
(*(*exceptioninfo).context_record).R10 =
NT_PROTECT_VIRTUAL_MEMORY_ARGS.handle as u64 as u64;
(*(*exceptioninfo).context_record).Rdx =
std::mem::transmute(NT_PROTECT_VIRTUAL_MEMORY_ARGS.base_address);
core::mem::transmute(NT_PROTECT_VIRTUAL_MEMORY_ARGS.base_address);
(*(*exceptioninfo).context_record).R8 =
std::mem::transmute(NT_PROTECT_VIRTUAL_MEMORY_ARGS.size);
core::mem::transmute(NT_PROTECT_VIRTUAL_MEMORY_ARGS.size);
(*(*exceptioninfo).context_record).R9 =
NT_PROTECT_VIRTUAL_MEMORY_ARGS.protection as u64;
}
ExceptionHandleFunction::NtOpenProcess => {
(*(*exceptioninfo).context_record).R10 =
std::mem::transmute(NT_OPEN_PROCESS_ARGS.handle);
core::mem::transmute(NT_OPEN_PROCESS_ARGS.handle);
(*(*exceptioninfo).context_record).Rdx = NT_OPEN_PROCESS_ARGS.access as u64;
(*(*exceptioninfo).context_record).R8 =
std::mem::transmute(NT_OPEN_PROCESS_ARGS.attributes);
core::mem::transmute(NT_OPEN_PROCESS_ARGS.attributes);
(*(*exceptioninfo).context_record).R9 =
std::mem::transmute(NT_OPEN_PROCESS_ARGS.client_id);
core::mem::transmute(NT_OPEN_PROCESS_ARGS.client_id);
}
ExceptionHandleFunction::NtWriteVirtualMemory => {
(*(*exceptioninfo).context_record).R10 =
NT_WRITE_VIRTUAL_MEMORY_ARGS.handle as u64 as u64;
(*(*exceptioninfo).context_record).Rdx =
std::mem::transmute(NT_WRITE_VIRTUAL_MEMORY_ARGS.base_address);
core::mem::transmute(NT_WRITE_VIRTUAL_MEMORY_ARGS.base_address);
(*(*exceptioninfo).context_record).R8 =
std::mem::transmute(NT_WRITE_VIRTUAL_MEMORY_ARGS.buffer);
core::mem::transmute(NT_WRITE_VIRTUAL_MEMORY_ARGS.buffer);
(*(*exceptioninfo).context_record).R9 =
NT_WRITE_VIRTUAL_MEMORY_ARGS.size as u64;
}
ExceptionHandleFunction::NtCreateThreadEx => {
(*(*exceptioninfo).context_record).R10 =
std::mem::transmute(NT_CREATE_THREAD_EX_ARGS.thread);
core::mem::transmute(NT_CREATE_THREAD_EX_ARGS.thread);
(*(*exceptioninfo).context_record).Rdx =
NT_CREATE_THREAD_EX_ARGS.access as u64;
(*(*exceptioninfo).context_record).R8 =
std::mem::transmute(NT_CREATE_THREAD_EX_ARGS.attributes);
core::mem::transmute(NT_CREATE_THREAD_EX_ARGS.attributes);
(*(*exceptioninfo).context_record).R9 =
NT_CREATE_THREAD_EX_ARGS.process as u64;
}
@@ -258,11 +269,11 @@ pub fn hook_function(src_address: usize, dst_address: usize) -> bool {
unsafe {
let original_address = src_address;
let handle = INVALID_HANDLE_VALUE;
let base_address: *mut PVOID = std::mem::transmute(&original_address);
let base_address: *mut PVOID = core::mem::transmute(&original_address);
let size = 4096 as usize;
let size: *mut usize = std::mem::transmute(&size);
let size: *mut usize = core::mem::transmute(&size);
let o = u32::default();
let old_protection: *mut u32 = std::mem::transmute(&o);
let old_protection: *mut u32 = core::mem::transmute(&o);
let z = nt_protect_virtual_memory(
handle,
@@ -304,7 +315,7 @@ pub fn hook_function(src_address: usize, dst_address: usize) -> bool {
HOOKED_FUNCTIONS_INFO.push((src_address, original_bytes));
let u = 0u32;
let unused: *mut u32 = std::mem::transmute(&u);
let unused: *mut u32 = core::mem::transmute(&u);
let z = nt_protect_virtual_memory(handle, base_address, size, *old_protection, unused);
@@ -361,11 +372,11 @@ pub fn unhook_function(address: usize) -> bool {
let original_address = unhook_info.0;
let handle = INVALID_HANDLE_VALUE;
let base_address: *mut PVOID = std::mem::transmute(&original_address);
let base_address: *mut PVOID = core::mem::transmute(&original_address);
let size = 4096 as usize;
let size: *mut usize = std::mem::transmute(&size);
let size: *mut usize = core::mem::transmute(&size);
let o = u32::default();
let old_protection: *mut u32 = std::mem::transmute(&o);
let old_protection: *mut u32 = core::mem::transmute(&o);
let z = nt_protect_virtual_memory(
handle,
@@ -386,7 +397,7 @@ pub fn unhook_function(address: usize) -> bool {
}
let u = 0u32;
let unused: *mut u32 = std::mem::transmute(&u);
let unused: *mut u32 = core::mem::transmute(&u);
let z = nt_protect_virtual_memory(handle, base_address, size, *old_protection, unused);
@@ -416,99 +427,13 @@ pub fn unhook_function(address: usize) -> bool {
/// }
/// ```
pub fn get_module_base_address(module_name: &str) -> usize {
let modules = get_modules_list().unwrap();
for m in modules {
let name = get_module_name(m);
let path = get_module_path(m);
if name.is_err() || path.is_err() {
continue;
}
if name.unwrap().to_ascii_lowercase() == module_name.to_ascii_lowercase()
|| path.unwrap().to_ascii_lowercase() == module_name.to_ascii_lowercase()
{
return m;
}
}
0
peb::get_module_by_name(module_name)
}
fn get_modules_list() -> Result<Vec<usize>, u32> {
unsafe {
let mut mod_handles: Vec<usize> = Vec::new();
let mut reserved = 0;
let mut needed = 0;
let current_process = GetCurrentProcess();
// Code extracted from winproc
let enum_mods = |mod_handles: &mut Vec<usize>, needed: *mut u32| {
let cb = mod_handles.len() as u32;
let res = EnumProcessModules(
current_process,
mod_handles.as_mut_ptr() as *mut _,
cb,
needed,
);
if res == 0 {
Err(get_last_error())
} else {
Ok(())
}
};
// Code extracted from winproc
loop {
enum_mods(&mut mod_handles, &mut needed)?;
if needed <= reserved {
break;
}
reserved = needed;
mod_handles.resize(needed as usize, 0);
}
Ok(mod_handles)
}
}
fn get_module_name(hmodule: usize) -> Result<String, u32> {
unsafe {
let current_process = GetCurrentProcess();
let mut buffer: [u16; MAX_PATH as _] = std::mem::zeroed();
let res = GetModuleBaseNameW(current_process, hmodule as _, buffer.as_mut_ptr(), MAX_PATH);
if res == 0 {
Err(-1i32 as u32)
} else {
// Code extracted from winproc
Ok(OsString::from_wide(&buffer[0..res as usize])
.to_string_lossy()
.into_owned())
}
}
}
fn get_module_path(hmodule: usize) -> Result<String, u32> {
unsafe {
let current_process = GetCurrentProcess();
let mut buffer: [u16; MAX_PATH as _] = std::mem::zeroed();
let res =
GetModuleFileNameExW(current_process, hmodule as _, buffer.as_mut_ptr(), MAX_PATH);
if res == 0 {
Err(-1i32 as u32)
} else {
// Code extracted from winproc
Ok(OsString::from_wide(&buffer[0..res as usize])
.to_string_lossy()
.into_owned())
}
}
/// Hash-based module lookup. Pass the djb2 hash of the lowercased ascii name
/// (compute via [`peb::hash_name`]). The plaintext never lands in `.rdata`.
pub fn get_module_base_address_h(name_hash: u32) -> usize {
peb::get_module_by_hash(name_hash)
}
/// Retrieves the address of an exported function from the specified module.
@@ -627,18 +552,14 @@ fn get_forward_address(function_ptr: *mut u8) -> usize {
let api_set = get_api_mapping();
let prev_hook = panic::take_hook();
panic::set_hook(Box::new(|_| {}));
let result = panic::catch_unwind(|| {
let _ = format!(
"{}{}",
&forwarded_module_name[..forwarded_module_name.len() - 2],
".dll"
);
});
panic::set_hook(prev_hook);
if result.is_err() {
// PE forwarder strings end in "_<ordinal>" or ".<ordinal>" so we drop
// the last 2 bytes before tacking ".dll" on. Slicing at len-2 on
// arbitrary UTF-8 could panic mid-codepoint; the old code wrapped
// this in panic::catch_unwind. PE export names are always ASCII so
// a is_char_boundary check is sufficient and works in no_std.
if forwarded_module_name.len() < 2
|| !forwarded_module_name.is_char_boundary(forwarded_module_name.len() - 2)
{
return function_ptr as usize;
}
@@ -672,11 +593,11 @@ fn get_forward_address(function_ptr: *mut u8) -> usize {
}
}
pub fn get_api_mapping() -> HashMap<String, String> {
pub fn get_api_mapping() -> BTreeMap<String, String> {
unsafe {
let handle = GetCurrentProcess();
let p: PROCESS_BASIC_INFORMATION = std::mem::zeroed();
let process_information: *mut c_void = std::mem::transmute(&p);
let p: PROCESS_BASIC_INFORMATION = core::mem::zeroed();
let process_information: *mut c_void = core::mem::transmute(&p);
let _ret = nt_query_information_process(
handle,
0,
@@ -686,7 +607,7 @@ pub fn get_api_mapping() -> HashMap<String, String> {
);
let process_information_ptr: *mut PROCESS_BASIC_INFORMATION =
std::mem::transmute(process_information);
core::mem::transmute(process_information);
let api_set_map_offset: usize;
@@ -696,12 +617,12 @@ pub fn get_api_mapping() -> HashMap<String, String> {
api_set_map_offset = 0x68;
}
let mut api_set_dict: HashMap<String, String> = HashMap::new();
let mut api_set_dict: BTreeMap<String, String> = BTreeMap::new();
let api_set_namespace_ptr = *(((*process_information_ptr).PebBaseAddress as usize
+ api_set_map_offset) as *mut usize);
let api_set_namespace_ptr: *mut ApiSetNamespace =
std::ptr::with_exposed_provenance_mut::<ApiSetNamespace>(api_set_namespace_ptr);
core::ptr::with_exposed_provenance_mut::<ApiSetNamespace>(api_set_namespace_ptr);
let namespace = *api_set_namespace_ptr;
for i in 0..namespace.count {
@@ -735,7 +656,7 @@ pub fn get_api_mapping() -> HashMap<String, String> {
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);
set_value_ptr = core::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
@@ -819,7 +740,7 @@ pub fn call_module_entry_point(
}
unsafe {
let main: EntryPoint = std::mem::transmute(entry_point);
let main: EntryPoint = core::mem::transmute(entry_point);
let module = entry_point as HINSTANCE;
let ret = main(module, DLL_PROCESS_ATTACH, ptr::null_mut());
@@ -867,11 +788,11 @@ pub fn fork() -> i32 {
unsafe {
let process_handle = 0;
let thread_handle = 0;
let process_handle: *mut HANDLE = std::mem::transmute(&process_handle);
let thread_handle: *mut HANDLE = std::mem::transmute(&thread_handle);
let mut create_info: PsCreateInfo = std::mem::zeroed();
let process_handle: *mut HANDLE = core::mem::transmute(&process_handle);
let thread_handle: *mut HANDLE = core::mem::transmute(&thread_handle);
let mut create_info: PsCreateInfo = core::mem::zeroed();
create_info.size = size_of::<PsCreateInfo>();
let ps_create_info: *mut PsCreateInfo = std::mem::transmute(&create_info);
let ps_create_info: *mut PsCreateInfo = core::mem::transmute(&create_info);
let ret = nt_create_user_process(
process_handle, // NULL
@@ -917,15 +838,15 @@ pub fn ldr_get_procedure_address(module_handle: usize, function_name: &str, ordi
unsafe {
let ret: Option<i32>;
let func_ptr: data::LdrGetProcedureAddress;
let hmodule: PVOID = std::ptr::with_exposed_provenance_mut::<libc::c_void>(module_handle);
let hmodule: PVOID = core::ptr::with_exposed_provenance_mut::<libc::c_void>(module_handle);
let r = usize::default();
let return_address: *mut c_void = std::mem::transmute(&r);
let return_address: *mut PVOID = std::mem::transmute(return_address);
let return_address: *mut c_void = core::mem::transmute(&r);
let return_address: *mut PVOID = core::mem::transmute(return_address);
// LdrGetProcedureAddress wants a PANSI_STRING (16 bytes on x64),
// not a Rust String (24 bytes, different layout). We hold the
// CString alive for the duration of the call to keep .as_ptr()
// valid; the ANSI_STRING borrows from it.
// Bug 10 in AUDIT.md: LdrGetProcedureAddress wants a PANSI_STRING
// (16 bytes on x64), not a Rust String (24 bytes, different layout).
// We hold the CString alive for the duration of the call to keep the
// .as_ptr() valid; the ANSI_STRING borrows from it.
let c_name = if function_name.is_empty() {
None
} else {
@@ -1019,7 +940,7 @@ pub fn load_library_a_tp(module: &str) -> usize {
let ret: Option<i32>;
let func_ptr: data::RtlQueueWorkItem;
let name = CString::new(module.to_string()).expect("");
let module_name: PVOID = std::mem::transmute(name.as_ptr());
let module_name: PVOID = core::mem::transmute(name.as_ptr());
let k32 = get_module_base_address(&lc!("kernel32.dll"));
let ntdll = get_module_base_address(&lc!("ntdll.dll"));
let load_library = get_function_address(k32, &lc!("LoadLibraryA"));
@@ -1038,10 +959,7 @@ pub fn load_library_a_tp(module: &str) -> usize {
if x != 0 {
return 0;
} else {
use std::{thread, time};
let ten_millis = time::Duration::from_millis(500);
thread::sleep(ten_millis);
nt_sleep_millis(500);
return get_module_base_address(module);
}
}
@@ -1052,6 +970,30 @@ pub fn load_library_a_tp(module: &str) -> usize {
}
}
/// Block the current thread for `ms` milliseconds via NtDelayExecution.
/// no_std-friendly replacement for std::thread::sleep that also avoids the
/// kernel32!Sleep hook surface; the call lands directly in ntdll.
pub fn nt_sleep_millis(ms: u64) {
type NtDelayExecution = unsafe extern "system" fn(u8, *mut LARGE_INTEGER) -> i32;
unsafe {
let mut interval = LARGE_INTEGER {
QuadPart: -(ms as i64 * 10_000),
};
let ret: Option<i32>;
let func_ptr: NtDelayExecution;
let ntdll = get_module_base_address(&lc!("ntdll.dll"));
dynamic_invoke!(
ntdll,
&lc!("NtDelayExecution"),
func_ptr,
ret,
0u8,
&mut interval as *mut _
);
let _ = ret;
}
}
/// Dynamically calls LoadLibraryA.
///
/// It will return either the module's base address or 0.
@@ -1068,7 +1010,7 @@ pub fn load_library_a(module: &str) -> usize {
let ret: Option<usize>;
let func_ptr: data::LoadLibraryA;
let name = CString::new(module.to_string()).expect("");
let module_name: *mut u8 = std::mem::transmute(name.as_ptr());
let module_name: *mut u8 = core::mem::transmute(name.as_ptr());
let k32 = get_module_base_address(&lc!("kernel32.dll"));
dynamic_invoke!(k32, &lc!("LoadLibraryA"), func_ptr, ret, module_name);
@@ -1146,7 +1088,7 @@ pub fn create_file_a(
match ret {
Some(x) => return x,
None => return std::ptr::null_mut(),
None => return core::ptr::null_mut(),
}
}
}
@@ -1196,7 +1138,7 @@ pub fn create_file_mapping_w(
match ret {
Some(x) => return x,
None => return std::ptr::null_mut(),
None => return core::ptr::null_mut(),
}
}
}
@@ -1309,7 +1251,7 @@ pub fn open_process(desired_access: u32, inherit_handle: i32, process_id: u32) -
match ret {
Some(x) => return x,
None => return std::ptr::null_mut(),
None => return core::ptr::null_mut(),
}
}
}
@@ -1346,7 +1288,7 @@ pub fn open_thread(desired_access: u32, inherit_handle: i32, thread_id: u32) ->
match ret {
Some(x) => return x,
None => return std::ptr::null_mut(),
None => return core::ptr::null_mut(),
}
}
}
@@ -1712,8 +1654,8 @@ pub fn nt_write_virtual_memory(
let ret;
let func_ptr: data::NtWriteVirtualMemory;
let ntdll = get_module_base_address(&lc!("ntdll.dll"));
let _decoy_buf: Vec<u8>;
let _decoy_buf: Vec<u8>;
if HARDWARE_BREAKPOINTS {
let addr = get_function_address(ntdll, &lc!("NtWriteVirtualMemory")) as usize;
HARDWARE_EXCEPTION_FUNCTION = ExceptionHandleFunction::NtWriteVirtualMemory;
@@ -1894,7 +1836,7 @@ pub fn nt_protect_virtual_memory(
handle = INVALID_HANDLE_VALUE;
base_address = ptr::null_mut();
let s = 10usize;
size = std::mem::transmute(&s);
size = core::mem::transmute(&s);
new_protection = PAGE_READONLY;
}
@@ -1976,15 +1918,15 @@ pub fn nt_open_process(
set_hardware_breakpoint(sys::get_syscall_address(addr as *mut _).unwrap_or(0));
let h = INVALID_HANDLE_VALUE;
handle = std::mem::transmute(&h);
handle = core::mem::transmute(&h);
access = PROCESS_QUERY_LIMITED_INFORMATION;
let a: OBJECT_ATTRIBUTES = std::mem::zeroed();
attributes = std::mem::transmute(&a);
let a: OBJECT_ATTRIBUTES = core::mem::zeroed();
attributes = core::mem::transmute(&a);
let c = ClientId {
unique_process: std::process::id() as *mut std::ffi::c_void,
unique_thread: std::ptr::null_mut(),
unique_process: GetCurrentProcessId() as *mut core::ffi::c_void,
unique_thread: core::ptr::null_mut(),
};
client_id = std::mem::transmute(&c);
client_id = core::mem::transmute(&c);
}
dynamic_invoke!(
@@ -2355,10 +2297,10 @@ pub fn nt_create_thread_ex(
set_hardware_breakpoint(sys::get_syscall_address(addr as *mut _).unwrap_or(0));
let h = INVALID_HANDLE_VALUE;
thread = std::mem::transmute(&h);
thread = core::mem::transmute(&h);
access = PROCESS_QUERY_LIMITED_INFORMATION;
let a: OBJECT_ATTRIBUTES = std::mem::zeroed();
attributes = std::mem::transmute(&a);
let a: OBJECT_ATTRIBUTES = core::mem::zeroed();
attributes = core::mem::transmute(&a);
process = INVALID_HANDLE_VALUE;
}
@@ -2507,11 +2449,11 @@ pub fn nt_read_virtual_memory(
/// let ptr = dyncvoke_core::get_module_base_address("ntdll.dll");
/// let function_ptr: LdrGetProcedureAddress;
/// let ret: Option<i32>;
/// let hmodule: PVOID = std::mem::transmute(ptr);
/// let hmodule: PVOID = core::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);
/// let return_address: *mut c_void = core::mem::transmute(&usize::default());
/// let return_address: *mut PVOID = core::mem::transmute(return_address);
/// //dyncvoke_core::dynamic_invoke(usize,&str,<function_type>,Option<return_type>,[arguments])
/// dyncvoke_core::dynamic_invoke!(ptr,"LdrGetProcedureAddress",function_ptr,ret,hmodule,fun_name,ordinal,return_address);
///
@@ -2539,7 +2481,7 @@ macro_rules! dynamic_invoke {
let function_ptr = $crate::get_function_address($a, $b);
if function_ptr != 0
{
$c = std::mem::transmute(function_ptr);
$c = core::mem::transmute(function_ptr);
$d = Some($c($($e)*));
}
else
@@ -2557,16 +2499,17 @@ macro_rules! dynamic_invoke {
/// failed (the syscall was never executed); `Ok(status)` returns the raw
/// NTSTATUS the kernel gave us.
///
/// Supports arbitrary arity, zero-arg syscalls (e.g. NtYieldExecution),
/// and trailing commas.
/// Replaces the deleted `execute_syscall!` / `indirect_syscall!` macros.
/// Supports arbitrary arity (kills AUDIT.md Bug 8), zero-arg syscalls
/// (e.g. NtYieldExecution), and trailing commas.
///
/// # Examples
///
/// ```ignore
/// use dyncvoke_core::syscall;
/// use std::ffi::c_void;
/// use core::ffi::c_void;
///
/// let mut handle: *mut c_void = std::ptr::null_mut();
/// let mut handle: *mut c_void = core::ptr::null_mut();
/// // ... fill in object attributes / client id ...
/// let status = syscall!("NtOpenProcess",
/// &mut handle as *mut *mut c_void,
@@ -2590,7 +2533,7 @@ macro_rules! syscall {
///
/// Skips resolution entirely. Useful for caching SSNs at init or for
/// resolving via a non-default mechanism (e.g. a hand-rolled SSDT walker
/// or a remote-process resolver). Always returns the raw NTSTATUS. no
/// or a remote-process resolver). Always returns the raw NTSTATUS no
/// `Result` wrapping.
///
/// # Examples
@@ -2616,6 +2559,9 @@ macro_rules! do_syscall {
// Tests
// ============================================================================
#[cfg(test)]
extern crate std;
#[cfg(test)]
mod tests {
use super::*;
@@ -2623,9 +2569,10 @@ mod tests {
#[test]
fn test_invalid_handle_value() {
// Windows defines INVALID_HANDLE_VALUE as (HANDLE)(LONG_PTR)-1,
// the NtCurrentProcess pseudo-handle. NULL is rejected by NT memory
// syscalls with STATUS_INVALID_HANDLE.
// Windows defines INVALID_HANDLE_VALUE as (HANDLE)(LONG_PTR)-1
// (the NtCurrentProcess pseudo-handle). Bug 5 in AUDIT.md changed
// it from core::ptr::null_mut() which the kernel rejected with
// STATUS_INVALID_HANDLE.
assert_eq!(INVALID_HANDLE_VALUE as isize, -1);
}
@@ -2690,7 +2637,7 @@ mod tests {
let mut us = UNICODE_STRING {
Length: 0,
MaximumLength: 0,
Buffer: std::ptr::null_mut(),
Buffer: core::ptr::null_mut(),
};
// Call rtl_init_unicode_string to initialize it
@@ -2744,7 +2691,7 @@ mod tests {
fn test_nt_allocate_virtual_memory_args() {
let args = NtAllocateVirtualMemoryArgs {
handle: INVALID_HANDLE_VALUE,
base_address: std::ptr::null_mut(),
base_address: core::ptr::null_mut(),
};
assert_eq!(args.handle as isize, -1);
assert!(args.base_address.is_null());
@@ -2754,10 +2701,10 @@ mod tests {
#[test]
fn test_nt_open_process_args() {
let args = NtOpenProcessArgs {
handle: std::ptr::null_mut(),
handle: core::ptr::null_mut(),
access: 0x1000,
attributes: std::ptr::null_mut(),
client_id: std::ptr::null_mut(),
attributes: core::ptr::null_mut(),
client_id: core::ptr::null_mut(),
};
assert!(args.handle.is_null());
assert_eq!(args.access, 0x1000);
@@ -2768,8 +2715,8 @@ mod tests {
fn test_nt_protect_virtual_memory_args() {
let args = NtProtectVirtualMemoryArgs {
handle: INVALID_HANDLE_VALUE,
base_address: std::ptr::null_mut(),
size: std::ptr::null_mut(),
base_address: core::ptr::null_mut(),
size: core::ptr::null_mut(),
protection: PAGE_EXECUTE_READWRITE,
};
assert_eq!(args.handle as isize, -1);
@@ -2781,8 +2728,8 @@ mod tests {
fn test_nt_write_virtual_memory_args() {
let args = NtWriteVirtualMemoryArgs {
handle: INVALID_HANDLE_VALUE,
base_address: std::ptr::null_mut(),
buffer: std::ptr::null_mut(),
base_address: core::ptr::null_mut(),
buffer: core::ptr::null_mut(),
size: 0,
};
assert_eq!(args.handle as isize, -1);
@@ -2792,9 +2739,9 @@ mod tests {
#[test]
fn test_nt_create_thread_ex_args() {
let args = NtCreateThreadExArgs {
thread: std::ptr::null_mut(),
thread: core::ptr::null_mut(),
access: 0x1F03FF,
attributes: std::ptr::null_mut(),
attributes: core::ptr::null_mut(),
process: INVALID_HANDLE_VALUE,
};
assert!(args.thread.is_null());
@@ -2820,7 +2767,7 @@ mod tests {
// Test type sizes match expected Windows sizes
#[test]
fn test_type_sizes() {
assert_eq!(std::mem::size_of::<HANDLE>(), std::mem::size_of::<*mut std::ffi::c_void>());
assert_eq!(std::mem::size_of::<PVOID>(), std::mem::size_of::<*mut std::ffi::c_void>());
assert_eq!(core::mem::size_of::<HANDLE>(), core::mem::size_of::<*mut core::ffi::c_void>());
assert_eq!(core::mem::size_of::<PVOID>(), core::mem::size_of::<*mut core::ffi::c_void>());
}
}
+220
View File
@@ -0,0 +1,220 @@
//! Direct PEB walker. Replaces the EnumProcessModules / GetModuleBaseNameW /
//! OsString::from_wide chain with reads from the in-process LDR data.
//!
//! Two reasons this exists:
//!
//! 1. It removes the only no_std blocker in module enumeration — the old path
//! needed OsString::from_wide which is std-only.
//! 2. It removes any user-mode-hookable kernel32/psapi call from the module
//! lookup path. Walking the PEB is invisible to inline hooks placed on
//! EnumProcessModules / GetModuleBaseNameW.
//!
//! There are two lookup modes:
//!
//! - [`get_module_by_name`] takes a `&str` and matches case-insensitively
//! against each LDR entry's BaseDllName. Used by call sites that already
//! have a runtime-discovered name (PE imports, ApiSet mapping, etc.).
//! - [`get_module_by_hash`] takes a precomputed djb2 hash of the lowercased
//! ascii name. Used by hot OPSEC paths so the plaintext name never
//! materializes in `.rdata` at all. Compute the hash at compile time via
//! the [`hash_name`] const fn.
use core::ffi::c_void;
/// LIST_ENTRY — Windows doubly-linked list head.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct ListEntry {
pub flink: *mut ListEntry,
pub blink: *mut ListEntry,
}
/// UNICODE_STRING — Windows counted UTF-16 string. Mirrors the layout of the
/// kernel struct exactly.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct UnicodeString {
pub length: u16,
pub maximum_length: u16,
pub buffer: *mut u16,
}
/// LDR_DATA_TABLE_ENTRY — one loaded module's record. The PEB's Ldr field
/// points at a PEB_LDR_DATA whose InLoadOrderModuleList head is the start of
/// a circular linked list of these.
#[repr(C)]
pub struct LdrDataTableEntry {
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: u32,
pub full_dll_name: UnicodeString,
pub base_dll_name: UnicodeString,
}
/// PEB_LDR_DATA — the loader's in-memory bookkeeping for the current process.
///
/// Internal layout (WRK / ReactOS, not winternl.h's public abbreviation):
/// 0x00 ULONG Length
/// 0x04 BOOLEAN Initialized + 3 bytes padding to align next pointer
/// 0x08 PVOID SsHandle
/// 0x10 LIST_ENTRY InLoadOrderModuleList <- we want this one
/// 0x20 LIST_ENTRY InMemoryOrderModuleList
/// 0x30 LIST_ENTRY InInitializationOrderModuleList
#[repr(C)]
pub struct PebLdrData {
pub _length_and_init: [u8; 8],
pub _ss_handle: *mut c_void,
pub in_load_order_module_list: ListEntry,
}
/// PEB — only the Ldr pointer is exposed.
///
/// Public winternl.h layout:
/// 0x00 BYTE Reserved1[2]
/// 0x02 BYTE BeingDebugged
/// 0x03 BYTE Reserved2[1]
/// 0x08 PVOID Reserved3[2] (on x64, aligned)
/// 0x18 PPEB_LDR_DATA Ldr <- we want this
#[repr(C)]
pub struct Peb {
pub _reserved1: [u8; 2],
pub being_debugged: u8,
pub _reserved2: [u8; 1],
pub _padding: [u8; 4],
pub _reserved3: [*mut c_void; 2],
pub ldr: *mut PebLdrData,
}
/// Return the current process's PEB pointer.
#[inline(always)]
pub fn current_peb() -> *mut Peb {
let peb: *mut Peb;
#[cfg(target_arch = "x86_64")]
unsafe {
// PEB pointer lives at gs:[0x60] on x64.
core::arch::asm!(
"mov {peb}, gs:[0x60]",
peb = out(reg) peb,
options(nostack, preserves_flags, readonly)
);
}
#[cfg(target_arch = "x86")]
unsafe {
// PEB pointer lives at fs:[0x30] on x86.
core::arch::asm!(
"mov {peb}, fs:[0x30]",
peb = out(reg) peb,
options(nostack, preserves_flags, readonly)
);
}
peb
}
/// djb2 over the ascii-lowercased input. Stable, branch-light, const-evaluable
/// so call sites can write `hash_name(b"ntdll.dll")` to embed a u32 instead
/// of the literal.
pub const fn hash_name(bytes: &[u8]) -> u32 {
let mut hash: u32 = 5381;
let mut i = 0;
while i < bytes.len() {
let mut c = bytes[i];
if c >= b'A' && c <= b'Z' {
c += 32;
}
hash = hash.wrapping_mul(33).wrapping_add(c as u32);
i += 1;
}
hash
}
/// Compare a UNICODE_STRING against ascii bytes case-insensitively. Walks the
/// UTF-16 buffer one code unit at a time, lowercases ascii ranges, and bails
/// on any non-ascii unit (no module we care about has non-ascii in its name).
unsafe fn unicode_eq_ascii_ci(us: &UnicodeString, target: &[u8]) -> bool {
let len = (us.length / 2) as usize;
if len != target.len() {
return false;
}
for i in 0..len {
let mut wide = *us.buffer.add(i);
if wide >= 0x80 {
return false;
}
let mut byte = wide as u8;
if byte >= b'A' && byte <= b'Z' {
byte += 32;
}
let mut tgt = target[i];
if tgt >= b'A' && tgt <= b'Z' {
tgt += 32;
}
wide = byte as u16;
let _ = wide;
if byte != tgt {
return false;
}
}
true
}
/// Compute djb2 over a UNICODE_STRING's buffer, lowercasing ascii. Mirrors
/// [`hash_name`] so a precomputed const hash matches what we derive at
/// runtime from the loader's records.
unsafe fn hash_unicode_string(us: &UnicodeString) -> u32 {
let len = (us.length / 2) as usize;
let mut hash: u32 = 5381;
for i in 0..len {
let wide = *us.buffer.add(i);
if wide >= 0x80 {
return 0;
}
let mut byte = wide as u8;
if byte >= b'A' && byte <= b'Z' {
byte += 32;
}
hash = hash.wrapping_mul(33).wrapping_add(byte as u32);
}
hash
}
/// Iterate the InLoadOrderModuleList and call `visit` for each entry. Returns
/// the first non-null DllBase the visitor accepts.
unsafe fn walk_modules<F>(mut visit: F) -> usize
where
F: FnMut(&LdrDataTableEntry) -> bool,
{
let peb = current_peb();
if peb.is_null() {
return 0;
}
let ldr = (*peb).ldr;
if ldr.is_null() {
return 0;
}
let head = &(*ldr).in_load_order_module_list as *const ListEntry as *mut ListEntry;
let mut cursor = (*head).flink;
while !cursor.is_null() && cursor != head {
let entry = cursor as *mut LdrDataTableEntry;
if visit(&*entry) {
return (*entry).dll_base as usize;
}
cursor = (*cursor).flink;
}
0
}
/// Look up a loaded module by its base name (e.g. `"ntdll.dll"`). ASCII
/// case-insensitive. Returns 0 if no entry matches.
pub fn get_module_by_name(name: &str) -> usize {
unsafe { walk_modules(|entry| unicode_eq_ascii_ci(&entry.base_dll_name, name.as_bytes())) }
}
/// Look up a loaded module by the djb2 hash of its lowercased ascii base name.
/// The plaintext name never appears in the calling binary's image. Returns
/// 0 if no entry matches.
pub fn get_module_by_hash(hash: u32) -> usize {
unsafe { walk_modules(|entry| hash_unicode_string(&entry.base_dll_name) == hash) }
}
+20 -13
View File
@@ -1,18 +1,25 @@
//! Variadic syscall gateway.
//! Hell's Hall variadic gateway.
//!
//! `do_syscall(ssn, syscall_addr, n_args, ...)` dispatches an indirect
//! syscall by jumping to the `syscall` instruction inside ntdll itself.
//! Single C-callable variadic function that receives `(ssn, syscall_addr,
//! n_args, ...)` and dispatches an indirect syscall by jumping to the real
//! `syscall` instruction inside ntdll. Replaces the old fixed-arity
//! gateway with cmp-ladder + 11 per-arity handlers + Configuration struct +
//! restore proc — see AUDIT.md Bugs 1/6/7/8 and PLAN.md Phase 3 for the
//! design notes.
//!
//! Stack discipline at the moment of `jmp rcx` (which lands on ntdll's
//! `syscall` instruction):
//!
//! - rsp is unchanged from function entry. The x64 red zone holds the
//! non-volatile saves (rsi, rdi, r12), so no `sub rsp` is needed.
//! `[rsp]` is the return address back to the Rust caller.
//! - After the kernel returns to the next instruction in ntdll's stub
//! (`ret`), the `ret` pops `[rsp]` and lands back in the Rust caller.
//! - rsp is unchanged from function entry (we used the x64 red zone for
//! non-volatile saves, not `sub rsp`). So `[rsp]` is the return address
//! back to the Rust caller.
//! - After the kernel syscall returns to the next instruction in ntdll's
//! stub (`ret`), the `ret` pops `[rsp]` and lands back in the Rust
//! caller — no bespoke `restore` proc needed.
//! - Args 5..N have been slid down from `[rsp + 0x40]` to `[rsp + 0x28]`
//! via `rep movsq` so the kernel reads them where it expects.
//!
//! Mirrors `dinvk/src/sys/asm.rs:1-37`.
#[cfg(target_arch = "x86_64")]
core::arch::global_asm!("
@@ -54,15 +61,15 @@ do_syscall:
#[cfg(target_arch = "x86_64")]
unsafe extern "C" {
/// Variadic syscall dispatcher.
/// Hell's Hall variadic syscall dispatcher.
///
/// # Safety
///
/// `syscall_addr` must point at a valid `syscall` instruction inside
/// ntdll (use [`crate::resolve_syscall`]). `n_args` must match the
/// number of variadic arguments that follow. Passing wrong-sized
/// integer types in the varargs (e.g. a u32 where the syscall expects
/// u64) will land garbage in the upper bits and the syscall will fail
/// in non-obvious ways.
/// number of variadic args that follow. Passing wrong-sized integer
/// types in the varargs (e.g. a u32 where the syscall expects u64)
/// will land garbage in the upper bits and the syscall will fail in
/// non-obvious ways.
pub fn do_syscall(ssn: u16, syscall_addr: usize, n_args: u32, ...) -> i32;
}
+15 -9
View File
@@ -1,19 +1,24 @@
//! Tartarus Gate SSN resolution and strict syscall-instruction location.
//! Tartarus Gate SSN resolution + strict syscall-instruction location.
//!
//! Replaces the old BTreeMap-position approach (AUDIT.md Bug 1). Mirrors the
//! layout of `dinvk/src/sys/mod.rs` so future improvements there can be ported
//! cleanly here.
//!
//! Three modes for the SSN derivation, in order of preference:
//!
//! 1. Hell's Gate: stub has the clean `MOV R10, RCX; MOV EAX, <ssn>`
//! 1. **Hell's Gate** — stub has the clean `MOV R10, RCX; MOV EAX, <ssn>`
//! prologue, read the SSN straight out of `stub[4..6]`.
//! 2. Halo's Gate: stub starts with `E9 ...` (hook at entry); walk
//! 2. **Halo's Gate** — stub starts with `E9 ` (hook at entry); walk
//! ±32-byte neighbors until we find a clean stub, then adjust the SSN by
//! the offset distance (each stub is 32 bytes apart on x64 ntdll).
//! 3. Tartarus Gate: stub has `E9` at offset 3 (the hook landed after
//! `MOV R10, RCX`, which preserves r10 but redirects flow). Same
//! neighbor walk as Halo's Gate.
//! 3. **Tartarus Gate** — stub has `E9` at offset 3 (the hook landed *after*
//! `MOV R10, RCX`, which preserves r10 but redirects flow). Same neighbor
//! walk as Halo's Gate.
use alloc::string::String;
use core::ptr::read;
use litcrypt2::lc;
use data::lc;
#[doc(hidden)]
pub mod asm;
@@ -137,11 +142,12 @@ impl core::fmt::Display for SyscallError {
}
}
impl std::error::Error for SyscallError {}
impl core::error::Error for SyscallError {}
/// Resolve (SSN, syscall_instr_addr) for a Zw/Nt export of ntdll.
///
/// Returns a typed error on every failure mode. Never hangs.
/// Replaces the old `prepare_syscall` + `get_ntdll_eat` + `get_syscall_id`
/// pipeline. Returns a typed error on every failure mode — never hangs.
pub fn resolve_syscall(name: &str) -> Result<(u16, usize), SyscallError> {
let ntdll = crate::get_module_base_address(&lc!("ntdll.dll"));
if ntdll == 0 {
@@ -1,10 +1,13 @@
// dmanager's xor_key derivation folds a 15-byte random buffer with
// `rand_bytes.iter().fold(0u8, |acc, b| acc ^ b)`.
// Regression for AUDIT.md Bug 2.
//
// dmanager's xor_key derivation used to be a C-style null-terminated walk
// over a 15-byte random buffer. ~94% of runs read past the array. After the
// fix it's `rand_bytes.iter().fold(0u8, |acc, b| acc ^ b)`.
//
// This test mirrors the production derivation against a controlled buffer
// with a sentinel one byte past the intended end. The sentinel must not
// participate in the key, proving exactly 15 bytes are consumed and no
// out-of-bounds read happens regardless of buffer content.
// with a sentinel one byte past the intended end. The sentinel must NOT
// participate in the key proving exactly 15 bytes are consumed and no
// OOB read happens regardless of buffer content.
#[test]
fn xor_key_fold_consumes_exactly_15_bytes_no_oob() {
@@ -24,7 +27,7 @@ fn xor_key_fold_consumes_exactly_15_bytes_no_oob() {
assert_eq!(from_array, 0xFF, "fold of 15 × 0xFF should yield 0xFF (odd count)");
assert_eq!(
from_backing_slice, from_array,
"fold over backing[..15] must equal fold over the array, sentinel must not leak in"
"fold over backing[..15] must equal fold over the array sentinel must not leak in"
);
// For total certainty: change only the sentinel and recompute the fold
@@ -1,22 +1,26 @@
// Broad coverage sweep across every Zw* export in ntdll.
// Hell's Gate sweep — broad coverage across the entire Zw* export space of
// ntdll. Replaces the two old audit_hellsgate* tests that depended on the
// deleted BTreeMap-position API.
//
// For every Zw* export:
// 1. Read the canonical SSN directly from the stub bytes (4C 8B D1 B8
// <lo> <hi> 00 00), without touching the neighbor walker.
// 2. Run resolve_syscall(name).
// 3. The two must agree for any stub that has the canonical prologue.
// 3. The two MUST agree for any stub that has the canonical prologue.
//
// Stubs that don't have the canonical prologue (e.g. NtQuerySystemTime,
// which reads from KUSER_SHARED_DATA without a syscall) are tracked
// separately. They exercise the Tartarus neighbor walk and we just log
// them.
// separately and OK — they exercise the Tartarus neighbor walk and we just
// log them.
//
// Catches OS-wide drift on a new Windows build: if a future build
// rearranges the SSDT, this fails loudly with the offending name and SSNs.
use dyncvoke_core::{get_function_address, get_module_base_address, resolve_syscall};
/// Walk the ntdll EAT and collect every `Zw*` export's name.
/// Walk the ntdll EAT and collect every `Zw*` export's name. Mirrors what
/// the deleted `get_ntdll_eat` did but discards the BTreeMap layer that
/// gave Bug 1 its second-order behavior.
fn collect_zw_exports(ntdll: usize) -> Vec<String> {
let mut out = Vec::new();
unsafe {
@@ -97,7 +101,7 @@ fn resolve_syscall_matches_canonical_byte_derived_ssn_for_every_zw_export() {
}
None => {
// Atypical stub (e.g. NtQuerySystemTime). resolve_syscall
// recovered the SSN via the neighbor walk, that's fine.
// recovered the SSN via the neighbor walk that's fine.
atypical.push((name.clone(), resolver_ssn));
}
}
@@ -1,10 +1,14 @@
// The Windows INVALID_HANDLE_VALUE constant is (HANDLE)(LONG_PTR)-1,
// the NtCurrentProcess pseudo-handle that NT memory syscalls accept.
// NULL is rejected with STATUS_INVALID_HANDLE (0xC0000008).
// Regression for AUDIT.md Bug 5.
//
// This test asserts both directions: (a) the OS-level invariant that
// NULL is rejected and -1 succeeds, and (b) the lib-level invariant
// that dyncvoke_core's constant points at -1.
// Before the fix, `dyncvoke_core::INVALID_HANDLE_VALUE` was NULL, which the
// kernel rejects with STATUS_INVALID_HANDLE (0xC0000008). The Windows
// constant is (HANDLE)(LONG_PTR)-1 — that's the NtCurrentProcess pseudo-
// handle and is what the NT memory syscalls actually expect.
//
// This test now asserts both directions: (a) the OS-level invariant that
// NULL is rejected and -1 succeeds, and (b) the lib-level invariant that
// dyncvoke_core's constant points at -1 so any caller threading it through
// is using a valid pseudo-handle.
use dyncvoke_core::{nt_protect_virtual_memory, INVALID_HANDLE_VALUE};
use std::ffi::c_void;
@@ -1,9 +1,13 @@
// LdrGetProcedureAddress wants a PANSI_STRING (16 bytes on x64) as its
// second parameter. The wrapper sends a real `ANSI_STRING` value backed
// by a CString.
// Regression for AUDIT.md Bug 10.
//
// This test resolves a few exports by name through ldr_get_procedure_address
// and asserts the result matches get_function_address, proving the kernel
// LdrGetProcedureAddress wants a PANSI_STRING (16 bytes on x64) as its 2nd
// parameter. Dyncvoke used to pass `*mut String` — a Rust 24-byte struct
// with a different field order. Calls by name were silently broken; only
// ordinal lookups (where the name pointer is NULL) worked. The fix sends a
// real `ANSI_STRING` value backed by a CString.
//
// This test resolves NtClose by name through ldr_get_procedure_address and
// asserts the result matches get_function_address — proving the kernel
// actually understood the name we sent.
use dyncvoke_core::{get_function_address, get_module_base_address, ldr_get_procedure_address};
@@ -20,7 +24,7 @@ fn ldr_get_procedure_address_by_name_matches_get_function_address() {
let by_ldr = ldr_get_procedure_address(ntdll, name, 0);
assert!(
by_ldr != 0,
"LdrGetProcedureAddress returned 0 for {}, name was not understood",
"LdrGetProcedureAddress returned 0 for {} name was not understood",
name
);
@@ -34,7 +38,7 @@ fn ldr_get_procedure_address_by_name_matches_get_function_address() {
#[test]
fn ldr_get_procedure_address_by_ordinal_still_works() {
// Empty function_name path (NULL ANSI_STRING) must continue to work.
// Empty function_name path (NULL ANSI_STRING) must continue to work.
let ntdll = get_module_base_address("ntdll.dll");
assert!(ntdll != 0);
// Ordinal 8 is RtlDispatchAPC on most Win10/Win11 builds. We don't pin
@@ -1,16 +1,16 @@
// Two malformed-PE cases the relocation walker must handle:
// Regression for AUDIT.md Bugs 3 + 4.
//
// (a) A relocation block with SizeOfBlock = 0 must not spin the loop
// (the pointer would never advance).
// Bug 3: a relocation block with SizeOfBlock = 0 used to spin the loop
// forever because the pointer never advanced.
//
// (b) A PE with no relocation directory (VirtualAddress = 0) must not
// fall into reading the DOS header as IMAGE_BASE_RELOCATION, which
// would produce a VirtualAddress of 0x00905A4D and corrupt memory.
// Bug 4: a PE with no relocation directory (VirtualAddress = 0) used to fall
// straight into reading the DOS header as IMAGE_BASE_RELOCATION, producing
// a VirtualAddress of 0x00905A4D and corrupting memory.
//
// manualmap::relocate_module and overload::relocate_text_section guard
// both: early-return on `dir.VirtualAddress == 0 || dir.Size == 0`, treat
// `SizeOfBlock < sizeof(IMAGE_BASE_RELOCATION)` as end-of-table, and
// bound the walk by `dir.Size`.
// The fix in manualmap::relocate_module and overload::relocate_text_section
// is: early-return on `dir.VirtualAddress == 0 || dir.Size == 0`, treat
// `SizeOfBlock < sizeof(IMAGE_BASE_RELOCATION)` as end-of-table, and bound
// the walk by `dir.Size`.
//
// We emulate exactly the fixed loop pattern over a synthetic block and
// assert termination.
@@ -25,7 +25,7 @@ struct ImageBaseRelocation {
#[test]
fn fixed_loop_breaks_on_size_zero_block() {
// A block with non-zero VirtualAddress but zero SizeOfBlock is the
// The walker must break out instead of spinning.
// malformed case from Bug 3. The fix breaks on it.
let mut storage: Vec<u8> = vec![0u8; 32];
unsafe {
let p = storage.as_mut_ptr() as *mut ImageBaseRelocation;
@@ -60,7 +60,7 @@ fn fixed_loop_breaks_on_size_zero_block() {
#[test]
fn fixed_loop_no_reloc_directory_returns_early() {
// dir.VirtualAddress == 0 (PE has no relocation directory). The
// Bug 4: dir.VirtualAddress == 0 (PE has no relocation directory). The
// fix is a `return` before any read. We mirror just the predicate here
// and assert we never enter the loop.
let virtual_address: u32 = 0;
@@ -52,7 +52,7 @@ fn tartarus_recovers_ssn_on_halos_gate_hook() {
let target = unsafe { copy_base.add(offset) };
// Plant `E9 XX XX XX XX` (jmp rel32) at offset 0. The actual rel32
// value doesn't matter. we only execute up to extract_ssn's bytewise
// value doesn't matter we only execute up to extract_ssn's bytewise
// inspection of the prologue.
unsafe {
*target = 0xE9;
@@ -111,7 +111,7 @@ fn tartarus_recovers_ssn_on_post_mov_r10_rcx_hook() {
fn tartarus_returns_none_on_unrecoverable_stub() {
// Allocate a window full of 0xCC (INT3, definitely not a syscall stub).
// Neither Hell's Gate prologue nor any neighbor stub exists, so
// extract_ssn must return None. no hang, no panic.
// extract_ssn must return None no hang, no panic.
let size = WINDOW_HALF * 2;
let buf = alloc_rwx(size);
unsafe { std::ptr::write_bytes(buf, 0xCC, size) };
@@ -1,7 +1,7 @@
// Exercises 1, 4, 6, and 11 argument calls through `syscall!`, confirming
// the variadic gateway correctly shuffles registers, performs the
// `rep movsq` slide for args 5+, and returns the real NTSTATUS to the
// caller.
// Variadic Hell's Hall dispatch regression — exercises 1, 4, 6, and 11
// argument calls through `syscall!`, confirming the gateway correctly
// shuffles registers, performs the `rep movsq` slide for args 5+, and
// returns the real NTSTATUS to the caller.
use dyncvoke_core::syscall;
use std::ffi::c_void;
@@ -9,14 +9,15 @@ use std::ptr::null_mut;
#[test]
fn one_arg_nt_close() {
// Pure register dispatch, no stack copy. Verifies the r10 = arg1 setup.
// Pure register dispatch no stack copy. Verifies the r10 = arg1
// setup.
let status = syscall!("NtClose", 0xDEADBEEFusize as *mut c_void).expect("resolve");
assert_eq!(status as u32, 0xC0000008, "expected STATUS_INVALID_HANDLE");
}
#[test]
fn four_arg_nt_query_information_process() {
// All four args in registers (r10, rdx, r8, r9), no stack copy still.
// All four args in registers (r10, rdx, r8, r9) no stack copy still.
// ProcessBasicInformation = 0. Use the current process pseudo-handle.
let mut pbi = [0u8; 48]; // PROCESS_BASIC_INFORMATION is 48 bytes on x64.
let mut return_length: u32 = 0;
@@ -52,7 +53,7 @@ fn six_arg_alloc_then_free() {
assert_eq!(alloc_status, 0, "alloc NTSTATUS=0x{:08X}", alloc_status as u32);
assert!(!base.is_null());
// Quick liveness probe, write and read back.
// Quick liveness probe write and read back.
unsafe { *(base as *mut u32) = 0xCAFEBABE };
let got = unsafe { *(base as *mut u32) };
assert_eq!(got, 0xCAFEBABE);
@@ -1,11 +1,12 @@
// End-to-end smoke check covering the full syscall path:
// End-to-end smoke test for the entire post-rewrite stack:
//
// syscall! macro
// -> resolve_syscall (extract_ssn + get_syscall_address)
// -> variadic do_syscall
// -> jump into ntdll's `syscall` instruction
// -> Tartarus Gate resolve_syscall (Phase 2)
// -> extract_ssn(stub) and get_syscall_address(stub)
// -> Hell's Hall variadic do_syscall (Phase 3)
// -> jump into ntdll's `syscall` instruction
//
// Three syscalls in sequence:
// Three real syscalls in sequence:
//
// 1. NtAllocateVirtualMemory -> get a writable page
// 2. NtProtectVirtualMemory -> flip it to PAGE_EXECUTE_READ
@@ -50,7 +51,7 @@ fn smoke_alloc_protect_free_round_trip() {
}
// 2. reprotect to executable-read. The protect syscall takes a *mut PVOID
// for the base and a *mut SIZE_T for the size, both inout.
// for the base and a *mut SIZE_T for the size both inout.
let mut old_prot: u32 = 0;
let prot_status = syscall!(
"NtProtectVirtualMemory",
@@ -91,9 +92,9 @@ fn smoke_alloc_protect_free_round_trip() {
#[test]
fn smoke_do_syscall_macro_with_cached_resolution() {
// Same dance, but resolve once and use the do_syscall! macro for
// the hot path. Confirms the bypass macro lines up bit-for-bit with
// the top-level syscall! macro.
// Same dance, but resolve once and use the do_syscall! macro for the
// hot path — confirms the bypass macro lines up bit-for-bit with the
// top-level syscall! macro.
use dyncvoke_core::{do_syscall, resolve_syscall};
let (alloc_ssn, alloc_addr) = resolve_syscall("NtAllocateVirtualMemory").unwrap();
+1 -1
View File
@@ -1,4 +1,4 @@
// End-to-end check that the variadic do_syscall dispatches correctly.
// Phase 3 acceptance — Hell's Hall variadic do_syscall works end-to-end.
use dyncvoke_core::{do_syscall, resolve_syscall};
use std::ffi::c_void;
@@ -1,4 +1,6 @@
// Resolves a handful of known syscalls via the byte-derived API and
// Sanity check for the new sys module — Phase 2 acceptance.
//
// Resolves a handful of known syscalls via the new byte-derived API and
// asserts (a) the SSNs match expected values for Win11 24H2, and (b) the
// syscall instruction address found is exactly 18 bytes into the stub
// (standard Hell's Gate stub layout) for clean stubs.
@@ -9,9 +11,9 @@ use dyncvoke_core::{
#[test]
fn resolve_known_syscalls() {
// Expected SSNs on Win11 24H2 build 26200. If this test starts failing
// on a different build, that's the kernel SSDT shifting, not a regression
// in our resolver, but a useful signal.
// Expected SSNs from audit_hellsgate.rs on Win11 24H2 build 26200.
// If this test starts failing on a different build, that's the kernel
// SSDT shifting — not a regression in our resolver, but a useful signal.
for (name, expected_ssn) in &[
("NtClose", 15u16),
("NtAllocateVirtualMemory", 24),
+499
View File
@@ -0,0 +1,499 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "bitflags"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "block2"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
dependencies = [
"objc2",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "data"
version = "0.1.0"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "dispatch2"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
dependencies = [
"bitflags",
"objc2",
]
[[package]]
name = "dyncvoke_core"
version = "0.1.0"
dependencies = [
"data",
"libc",
"litcrypt2",
"nanorand",
"windows-sys 0.59.0",
]
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "litcrypt2"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4126aa57ac1b3dd20a5bc827a2972cdf74c619a4d6ae5660656408289e5bc60d"
dependencies = [
"lazy_static",
"proc-macro2",
"quote",
"rand",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "manualmap"
version = "0.1.0"
dependencies = [
"data",
"dyncvoke_core",
"litcrypt2",
"os_info",
"windows-sys 0.59.0",
]
[[package]]
name = "nanorand"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e3d189da485332e96ba8a5ef646a311871abd7915bf06ac848a9117f19cf6e4"
[[package]]
name = "nix"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "objc2"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05"
dependencies = [
"objc2-encode",
]
[[package]]
name = "objc2-cloud-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
dependencies = [
"bitflags",
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-data"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags",
"dispatch2",
"objc2",
]
[[package]]
name = "objc2-core-graphics"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
dependencies = [
"bitflags",
"dispatch2",
"objc2",
"objc2-core-foundation",
"objc2-io-surface",
]
[[package]]
name = "objc2-core-image"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-location"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-text"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
dependencies = [
"bitflags",
"objc2",
"objc2-core-foundation",
"objc2-core-graphics",
]
[[package]]
name = "objc2-encode"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
[[package]]
name = "objc2-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags",
"block2",
"libc",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "objc2-io-surface"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
dependencies = [
"bitflags",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "objc2-quartz-core"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
dependencies = [
"bitflags",
"objc2",
"objc2-core-foundation",
"objc2-foundation",
]
[[package]]
name = "objc2-ui-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
dependencies = [
"bitflags",
"block2",
"objc2",
"objc2-cloud-kit",
"objc2-core-data",
"objc2-core-foundation",
"objc2-core-graphics",
"objc2-core-image",
"objc2-core-location",
"objc2-core-text",
"objc2-foundation",
"objc2-quartz-core",
"objc2-user-notifications",
]
[[package]]
name = "objc2-user-notifications"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "os_info"
version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224"
dependencies = [
"android_system_properties",
"log",
"nix",
"objc2",
"objc2-foundation",
"objc2-ui-kit",
"windows-sys 0.61.2",
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "zerocopy"
version = "0.8.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
+9 -8
View File
@@ -9,7 +9,7 @@ use data::LARGE_INTEGER;
use std::cell::UnsafeCell;
use std::ffi::c_void;
use std::mem::size_of;
use std::{collections::HashMap, path::Path};
use std::path::Path;
use std::{fs, ptr};
use data::{RuntimeFunction, OBJECT_ATTRIBUTES, UNICODE_STRING};
@@ -333,10 +333,10 @@ pub fn relocate_module(pe_info: &PeMetadata, image_ptr: *mut c_void) {
image_delta = module_memory_base as isize - pe_info.opt_header_64.image_base as isize;
}
// Guard: PE has its relocation directory stripped (fixed-base EXE,
// or DLL built with /FIXED). Without this check the loop reads the
// DOS header as an IMAGE_BASE_RELOCATION block and starts patching
// memory at module_base + 0x00905A4D + reloc_patch.
// Bug 4 guard: PE has its relocation directory stripped (fixed-base
// EXE, or DLL built with /FIXED). Without this check the loop reads
// the DOS header as an IMAGE_BASE_RELOCATION block and proceeds to
// patch memory at module_base + 0x00905A4D + reloc_patch.
if image_data_directory.VirtualAddress == 0 || image_data_directory.Size == 0 {
return;
}
@@ -353,9 +353,9 @@ pub fn relocate_module(pe_info: &PeMetadata, image_ptr: *mut c_void) {
let ibr: *mut IMAGE_BASE_RELOCATION = std::mem::transmute(reloc_table_ptr);
let image_base_relocation = *ibr;
// Guard: a block claiming SizeOfBlock < header size has no
// Bug 3 guard: a block claiming SizeOfBlock < header size has no
// entries and advancing by it would loop forever (SizeOfBlock = 0)
// or walk backwards. Treat as end-of-table.
// or backwards. Treat as end-of-table.
if image_base_relocation.VirtualAddress == 0
|| (image_base_relocation.SizeOfBlock as usize)
< size_of::<IMAGE_BASE_RELOCATION>()
@@ -433,7 +433,8 @@ pub fn rewrite_module_iat(pe_info: &PeMetadata, image_ptr: *mut c_void) -> Resul
let info = os_info::get();
let version = info.version().to_string();
let mut api_set_dict: HashMap<String, String> = HashMap::new();
let mut api_set_dict: std::collections::BTreeMap<String, String> =
std::collections::BTreeMap::new();
if version >= "10".to_string() {
api_set_dict = dyncvoke_core::get_api_mapping();
}
+675
View File
@@ -0,0 +1,675 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "addr2line"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "backtrace"
version = "0.3.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-link",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "block2"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
dependencies = [
"objc2",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "data"
version = "0.1.0"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "dispatch2"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
dependencies = [
"bitflags 2.11.0",
"objc2",
]
[[package]]
name = "dyncvoke_core"
version = "0.1.0"
dependencies = [
"data",
"libc",
"litcrypt2",
"nanorand",
"windows-sys 0.59.0",
]
[[package]]
name = "failure"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
dependencies = [
"backtrace",
"failure_derive",
]
[[package]]
name = "failure_derive"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"synstructure",
]
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gimli"
version = "0.32.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "litcrypt2"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4126aa57ac1b3dd20a5bc827a2972cdf74c619a4d6ae5660656408289e5bc60d"
dependencies = [
"lazy_static",
"proc-macro2",
"quote",
"rand",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "manualmap"
version = "0.1.0"
dependencies = [
"data",
"dyncvoke_core",
"litcrypt2",
"os_info",
"windows-sys 0.59.0",
]
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
]
[[package]]
name = "nanorand"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e3d189da485332e96ba8a5ef646a311871abd7915bf06ac848a9117f19cf6e4"
[[package]]
name = "nix"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags 2.11.0",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "objc2"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05"
dependencies = [
"objc2-encode",
]
[[package]]
name = "objc2-cloud-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
dependencies = [
"bitflags 2.11.0",
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-data"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags 2.11.0",
"dispatch2",
"objc2",
]
[[package]]
name = "objc2-core-graphics"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
dependencies = [
"bitflags 2.11.0",
"dispatch2",
"objc2",
"objc2-core-foundation",
"objc2-io-surface",
]
[[package]]
name = "objc2-core-image"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-location"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-text"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
dependencies = [
"bitflags 2.11.0",
"objc2",
"objc2-core-foundation",
"objc2-core-graphics",
]
[[package]]
name = "objc2-encode"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
[[package]]
name = "objc2-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags 2.11.0",
"block2",
"libc",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "objc2-io-surface"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
dependencies = [
"bitflags 2.11.0",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "objc2-quartz-core"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
dependencies = [
"bitflags 2.11.0",
"objc2",
"objc2-core-foundation",
"objc2-foundation",
]
[[package]]
name = "objc2-ui-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
dependencies = [
"bitflags 2.11.0",
"block2",
"objc2",
"objc2-cloud-kit",
"objc2-core-data",
"objc2-core-foundation",
"objc2-core-graphics",
"objc2-core-image",
"objc2-core-location",
"objc2-core-text",
"objc2-foundation",
"objc2-quartz-core",
"objc2-user-notifications",
]
[[package]]
name = "objc2-user-notifications"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "object"
version = "0.37.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
dependencies = [
"memchr",
]
[[package]]
name = "os_info"
version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224"
dependencies = [
"android_system_properties",
"log",
"nix",
"objc2",
"objc2-foundation",
"objc2-ui-kit",
"windows-sys 0.61.2",
]
[[package]]
name = "overload"
version = "0.1.0"
dependencies = [
"data",
"dyncvoke_core",
"litcrypt2",
"manualmap",
"nanorand",
"windows-sys 0.59.0",
"winproc",
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "rustc-demangle"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"unicode-xid",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "widestring"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c"
[[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"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winproc"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "979e07b33c6af27e5c454e42d1b946b403ec222e7bad52ef020820708e087b25"
dependencies = [
"bitflags 1.3.2",
"failure",
"widestring",
"winapi",
]
[[package]]
name = "zerocopy"
version = "0.8.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
+4 -8
View File
@@ -837,9 +837,7 @@ fn relocate_text_section(
image_delta = module_memory_base as isize - pe_info.opt_header_64.image_base as isize;
}
// Guard: PE has no relocation directory (stripped, fixed-base).
// Without this, the loop reads the DOS header as IMAGE_BASE_RELOCATION
// and starts patching memory at module_base + 0x00905A4D + offset.
// Bug 4 guard: no relocation directory at all.
if image_data_directory.VirtualAddress == 0 || image_data_directory.Size == 0 {
return;
}
@@ -856,9 +854,7 @@ fn relocate_text_section(
let ibr: *mut IMAGE_BASE_RELOCATION = std::mem::transmute(reloc_table_ptr);
let image_base_relocation = *ibr;
// Guard: malformed block with SizeOfBlock < header size has
// no entries; advancing by it would spin (SizeOfBlock = 0) or
// walk backwards. Treat as end-of-table.
// Bug 3 guard.
if image_base_relocation.VirtualAddress == 0
|| (image_base_relocation.SizeOfBlock as usize)
< size_of::<IMAGE_BASE_RELOCATION>()
@@ -1044,8 +1040,8 @@ mod tests {
fn test_module_handle_types() {
use dyncvoke_core::INVALID_HANDLE_VALUE;
// Must be (HANDLE)(-1), the NtCurrentProcess pseudo-handle.
// NULL is rejected by NT memory syscalls with STATUS_INVALID_HANDLE.
// Bug 5 in AUDIT.md: must be (HANDLE)(-1), the NtCurrentProcess
// pseudo-handle, not NULL.
assert_eq!(INVALID_HANDLE_VALUE as isize, -1);
// Test valid handle would be non-zero
+222
View File
@@ -0,0 +1,222 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "bitfield"
version = "0.19.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21ba6517c6b0f2bf08be60e187ab64b038438f22dd755614d8fe4d4098c46419"
dependencies = [
"bitfield-macros",
]
[[package]]
name = "bitfield-macros"
version = "0.19.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f48d6ace212fdf1b45fd6b566bb40808415344642b76c3224c07c8df9da81e97"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "cc"
version = "1.2.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "data"
version = "0.1.0"
dependencies = [
"obfstr",
"windows-sys",
]
[[package]]
name = "dyncvoke_core"
version = "0.1.0"
dependencies = [
"data",
"libc",
"nanorand",
"windows-sys",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "log"
version = "0.4.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f"
[[package]]
name = "memchr"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
[[package]]
name = "nanorand"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e3d189da485332e96ba8a5ef646a311871abd7915bf06ac848a9117f19cf6e4"
[[package]]
name = "nasm-rs"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "706bf8a5e8c8ddb99128c3291d31bd21f4bcde17f0f4c20ec678d85c74faa149"
dependencies = [
"log",
]
[[package]]
name = "obfstr"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0d354e9a302760d07e025701d40534f17dd1fe4c4db955b4e3bd2907c63bdee"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "spoof"
version = "0.1.0"
dependencies = [
"bitfield",
"cc",
"data",
"dyncvoke_core",
"memchr",
"nasm-rs",
]
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+27
View File
@@ -0,0 +1,27 @@
[package]
name = "spoof"
version = "0.1.0"
edition = "2021"
description = "Call stack spoofing primitives for Dyncvoke (synthetic + desync)."
license = "MIT OR Apache-2.0"
build = "build.rs"
[lib]
doctest = false
[dependencies]
data = { path = "../data" }
dyncvoke_core = { path = "../dyncvoke_core" }
bitfield = "0.19"
memchr = { version = "2.7", default-features = false }
[build-dependencies]
cc = "1.2"
nasm-rs = "0.3"
[features]
default = []
desync = []
[profile.release]
strip = true
+33
View File
@@ -0,0 +1,33 @@
use std::env;
fn main() {
if env::var("DOCS_RS").is_ok() {
println!("cargo:warning=Skipping ASM build for docs.rs");
return;
}
let target = env::var("TARGET").expect("missing TARGET");
let out_dir = env::var("OUT_DIR").expect("missing OUT_DIR");
if !target.contains("x86_64") {
panic!("spoof crate only supports x86_64 targets (got {})", target);
}
if target.contains("msvc") {
cc::Build::new()
.file("src/asm/msvc/synthetic.asm")
.file("src/asm/msvc/desync.asm")
.compile("spoof_asm");
} else if target.contains("gnu") {
let sources = ["src/asm/gnu/synthetic.asm", "src/asm/gnu/desync.asm"];
nasm_rs::compile_library("spoof_asm", &sources)
.unwrap_or_else(|e| panic!("nasm compile failed: {}", e));
for s in &sources {
println!("cargo:rerun-if-changed={}", s);
}
println!("cargo:rustc-link-search=native={}", out_dir);
println!("cargo:rustc-link-lib=static=spoof_asm");
} else {
panic!("unsupported target: {}", target);
}
}
+7
View File
@@ -0,0 +1,7 @@
use data::lc;
fn main() {
let canary = lc!("DYNCVOKE_CANARY_STRING_2026");
let len = canary.len();
std::process::exit(len as i32);
}
+29
View File
@@ -0,0 +1,29 @@
use core::ffi::c_void;
use core::ptr;
use data::lc;
use spoof::{spoof_syscall, AsPointer};
fn main() {
let mut addr: *mut c_void = ptr::null_mut();
let mut size: usize = 0x1000;
let alloc = lc!("NtAllocateVirtualMemory");
let status = unsafe {
spoof_syscall!(
alloc.as_str(),
-1isize,
addr.as_ptr_mut(),
0usize,
size.as_ptr_mut(),
0x3000u32,
0x04u32
)
};
let code = match status {
Ok(_) => 0,
Err(_) => 1,
};
std::process::exit(code);
}
+219
View File
@@ -0,0 +1,219 @@
;;
;; Code responsible for Call Stack Spoofing Via Desync (NASM)
;;
[BITS 64]
;;
;; Export
;;
GLOBAL Spoof
[SECTION .data]
;;
;; Configuration structure passed to the spoof ASM routine
;;
STRUC Config
.RtlUserThreadStartAddr RESQ 1
.RtlUserThreadStartFrameSize RESQ 1
.BaseThreadInitThunkAddr RESQ 1
.BaseThreadInitThunkFrameSize RESQ 1
.FirstFrame RESQ 1
.SecondFrame RESQ 1
.JmpRbxGadget RESQ 1
.AddRspXGadget RESQ 1
.FirstFrameSize RESQ 1
.SecondFrameSize RESQ 1
.JmpRbxGadgetFrameSize RESQ 1
.AddRspXGadgetFrameSize RESQ 1
.RbpOffset RESQ 1
.SpooFunction RESQ 1
.ReturnAddress RESQ 1
.IsSyscall RESD 1
.Ssn RESD 1
.NArgs RESQ 1
.Arg01 RESQ 1
.Arg02 RESQ 1
.Arg03 RESQ 1
.Arg04 RESQ 1
.Arg05 RESQ 1
.Arg06 RESQ 1
.Arg07 RESQ 1
.Arg08 RESQ 1
.Arg09 RESQ 1
.Arg10 RESQ 1
.Arg11 RESQ 1
ENDSTRUC
[SECTION .text]
;;
;; Function responsible for Call Stack Spoofing
;;
Spoof:
;;
;; Saving non-vol registers
;;
push rbp
push rbx
;;
;; Return main
;;
mov rbp, rsp
;;
;; Creating stack pointer to Restore PROC
;;
lea rax, [rel Restore]
push rax
lea rbx, [rsp]
;;
;; First Frame (Fake origin)
;;
push QWORD [rcx + Config.FirstFrame]
mov rax, [rcx + Config.ReturnAddress]
sub rax, [rcx + Config.FirstFrameSize]
sub rsp, [rcx + Config.SecondFrameSize]
mov r10, [rcx + Config.RbpOffset]
mov [rsp + r10], rax
;;
;; ROP Frames
;;
push QWORD [rcx + Config.SecondFrame]
;;
;; JMP [RBX] Gadget / Stack Pivot (To restore original Control Flow Stack)
;;
sub rsp, [rcx + Config.JmpRbxGadgetFrameSize]
push QWORD [rcx + Config.JmpRbxGadget]
sub rsp, [rcx + Config.AddRspXGadgetFrameSize]
push QWORD [rcx + Config.AddRspXGadget]
;;
;; Set the pointer to the function to call in R11
;;
mov r11, [rcx + Config.SpooFunction]
jmp Parameters
;;
;; Set the parameters to pass to the target function
;;
Parameters:
mov r12, rcx
mov rax, [r12 + Config.NArgs]
; Arg01 (rcx)
cmp rax, 1
jb skip_1
mov rcx, [r12 + Config.Arg01]
skip_1:
; Arg02 (rdx)
cmp rax, 2
jb skip_2
mov rdx, [r12 + Config.Arg02]
skip_2:
; Arg03 (r8)
cmp rax, 3
jb skip_3
mov r8, [r12 + Config.Arg03]
skip_3:
; Arg04 (r9)
cmp rax, 4
jb skip_4
mov r9, [r12 + Config.Arg04]
skip_4:
; Stack-based args
lea r13, [rsp]
cmp rax, 5
jb skip_5
mov r10, [r12 + Config.Arg05]
mov [r13 + 28h], r10
skip_5:
; Arg06
cmp rax, 6
jb skip_6
mov r10, [r12 + Config.Arg06]
mov [r13 + 30h], r10
skip_6:
; Arg07
cmp rax, 7
jb skip_7
mov r10, [r12 + Config.Arg07]
mov [r13 + 38h], r10
skip_7:
; Arg08
cmp rax, 8
jb skip_8
mov r10, [r12 + Config.Arg08]
mov [r13 + 40h], r10
skip_8:
; Arg09
cmp rax, 9
jb skip_9
mov r10, [r12 + Config.Arg09]
mov [r13 + 48h], r10
skip_9:
; Arg10
cmp rax, 10
jb skip_10
mov r10, [r12 + Config.Arg10]
mov [r13 + 50h], r10
skip_10:
; Arg11
cmp rax, 11
jb skip_11
mov r10, [r12 + Config.Arg11]
mov [r13 + 58h], r10
skip_11:
cmp BYTE [r12 + Config.IsSyscall], 1
je ExecuteSyscall
jmp Execute
;;
;; Restores the original stack frame
;;
Restore:
mov rsp, rbp
pop rbx
pop rbp
ret
;;
;; Executes the target function
;;
Execute:
jmp r11
;;
;; Executes a native Windows system call using the spoofed context
;;
ExecuteSyscall:
mov r10, rcx
mov eax, DWORD [r12 + Config.Ssn]
jmp r11
+247
View File
@@ -0,0 +1,247 @@
;;
;; Code responsible for Call Stack Spoofing Via Synthetic (NASM)
;;
[BITS 64]
;;
;; Export
;;
GLOBAL SpoofSynthetic
[SECTION .data]
;;
;; Configuration structure passed to the spoof ASM routine
;;
STRUC Config
.RtlUserThreadStartAddr RESQ 1
.RtlUserThreadStartFrameSize RESQ 1
.BaseThreadInitThunkAddr RESQ 1
.BaseThreadInitThunkFrameSize RESQ 1
.FirstFrame RESQ 1
.SecondFrame RESQ 1
.JmpRbxGadget RESQ 1
.AddRspXGadget RESQ 1
.FirstFrameSize RESQ 1
.SecondFrameSize RESQ 1
.JmpRbxGadgetFrameSize RESQ 1
.AddRspXGadgetFrameSize RESQ 1
.RbpOffset RESQ 1
.SpooFunction RESQ 1
.ReturnAddress RESQ 1
.IsSyscall RESD 1
.Ssn RESD 1
.NArgs RESQ 1
.Arg01 RESQ 1
.Arg02 RESQ 1
.Arg03 RESQ 1
.Arg04 RESQ 1
.Arg05 RESQ 1
.Arg06 RESQ 1
.Arg07 RESQ 1
.Arg08 RESQ 1
.Arg09 RESQ 1
.Arg10 RESQ 1
.Arg11 RESQ 1
ENDSTRUC
[SECTION .text]
;;
;; Function responsible for Call Stack Spoofing
;;
SpoofSynthetic:
;;
;; Saving non-vol registers
;;
push QWORD rbp
push QWORD rbx
push QWORD r15
;;
;; Everything between RSP and RBP is our new stack frame for unwinding
;;
sub rsp, 210h
mov rbp, rsp
;;
;; Creating stack pointer to Restore PROC
;;
lea rax, [rel RestoreSynthetic]
push rax
lea rbx, [rsp]
;;
;; Cutting the call stack. The 0 pushed in this position will be the return address
;; of the next frame "RtlUserThreadStart", making it effectively the originating function
;;
xor rax, rax
push rax
;;
;; RtlUserThreadStart
;;
sub rsp, [rcx + Config.RtlUserThreadStartFrameSize]
push QWORD [rcx + Config.RtlUserThreadStartAddr]
add QWORD [rsp], 21h
;;
;; BaseThreadInitThunk
;;
sub rsp, [rcx + Config.BaseThreadInitThunkFrameSize]
push QWORD [rcx + Config.BaseThreadInitThunkAddr]
add QWORD [rsp], 14h
;;
;; Return Adress
;;
mov rax, rsp
;;
;; First Frame (Fake origin)
;;
push QWORD [rcx + Config.FirstFrame]
sub rax, [rcx + Config.FirstFrameSize]
sub rsp, [rcx + Config.SecondFrameSize]
mov r10, [rcx + Config.RbpOffset]
mov [rsp + r10], rax
;;
;; ROP Frames
;;
push QWORD [rcx + Config.SecondFrame]
;;
;; JMP [RBX] Gadget / Stack Pivot (To restore original Control Flow Stack)
;;
sub rsp, [rcx + Config.JmpRbxGadgetFrameSize]
push QWORD [rcx + Config.JmpRbxGadget]
sub rsp, [rcx + Config.AddRspXGadgetFrameSize]
push QWORD [rcx + Config.AddRspXGadget]
;;
;; Set the pointer to the function to call in R11
;;
mov r11, [rcx + Config.SpooFunction]
jmp ParametersSynthetic
;;
;; Set the parameters to pass to the target function
;;
ParametersSynthetic:
mov r12, rcx
mov rax, [r12 + Config.NArgs]
; Arg01 (rcx)
cmp rax, 1
jb skip_1
mov rcx, [r12 + Config.Arg01]
skip_1:
; Arg02 (rdx)
cmp rax, 2
jb skip_2
mov rdx, [r12 + Config.Arg02]
skip_2:
; Arg03 (r8)
cmp rax, 3
jb skip_3
mov r8, [r12 + Config.Arg03]
skip_3:
; Arg04 (r9)
cmp rax, 4
jb skip_4
mov r9, [r12 + Config.Arg04]
skip_4:
; Stack-based args
lea r13, [rsp]
cmp rax, 5
jb skip_5
mov r10, [r12 + Config.Arg05]
mov [r13 + 28h], r10
skip_5:
; Arg06
cmp rax, 6
jb skip_6
mov r10, [r12 + Config.Arg06]
mov [r13 + 30h], r10
skip_6:
; Arg07
cmp rax, 7
jb skip_7
mov r10, [r12 + Config.Arg07]
mov [r13 + 38h], r10
skip_7:
; Arg08
cmp rax, 8
jb skip_8
mov r10, [r12 + Config.Arg08]
mov [r13 + 40h], r10
skip_8:
; Arg09
cmp rax, 9
jb skip_9
mov r10, [r12 + Config.Arg09]
mov [r13 + 48h], r10
skip_9:
; Arg10
cmp rax, 10
jb skip_10
mov r10, [r12 + Config.Arg10]
mov [r13 + 50h], r10
skip_10:
; Arg11
cmp rax, 11
jb skip_11
mov r10, [r12 + Config.Arg11]
mov [r13 + 58h], r10
skip_11:
cmp BYTE [r12 + Config.IsSyscall], 1
je ExecuteSyscallSynthetic
jmp ExecuteSynthetic
;;
;; Restores the original stack frame
;;
RestoreSynthetic:
mov rsp, rbp
add QWORD rsp, 210h
pop QWORD r15
pop QWORD rbx
pop QWORD rbp
ret
;;
;; Executes the target function
;;
ExecuteSynthetic:
jmp r11
;;
;; Executes a native Windows system call using the spoofed context
;;
ExecuteSyscallSynthetic:
mov r10, rcx
mov eax, DWORD [r12 + Config.Ssn]
jmp r11
+225
View File
@@ -0,0 +1,225 @@
;;
;; Code responsible for Call Stack Spoofing Via Desync (MASM)
;;
;;
;; Export
;;
Spoof proto
.data
;;
;; Configuration structure passed to the spoof ASM routine
;;
Config STRUCT
RtlUserThreadStartAddr DQ 1
RtlUserThreadStartFrameSize DQ 1
BaseThreadInitThunkAddr DQ 1
BaseThreadInitThunkFrameSize DQ 1
FirstFrame DQ 1
SecondFrame DQ 1
JmpRbxGadget DQ 1
AddRspXGadget DQ 1
FirstFrameSize DQ 1
SecondFrameSize DQ 1
JmpRbxGadgetFrameSize DQ 1
AddRspXGadgetFrameSize DQ 1
RbpOffset DQ 1
SpooFunction DQ 1
ReturnAddress DQ 1
IsSyscall DD 0
Ssn DD 0
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
Config ENDS
.code
;;
;; Function responsible for Call Stack Spoofing
;;
Spoof PROC
;;
;; Saving non-vol registers
;;
push rbp
push rbx
;;
;; Return main
;;
mov rbp, rsp
;;
;; Creating stack pointer to Restore PROC
;;
lea rax, Restore
push rax
lea rbx, [rsp]
;;
;; First Frame (Fake origin)
;;
push [rcx].Config.FirstFrame
mov rax, [rcx].Config.ReturnAddresS
sub rax, [rcx].Config.FirstFrameSize
sub rsp, [rcx].Config.SecondFrameSize
mov r10, [rcx].Config.RbpOffset
mov [rsp + r10], rax
;;
;; ROP Frames
;;
push [rcx].Config.SecondFrame
;;
;; JMP [RBX] Gadget / Stack Pivot (To restore original Control Flow Stack)
;;
sub rsp, [rcx].Config.JmpRbxGadgetFrameSize
push [rcx].Config.JmpRbxGadget
sub rsp, [rcx].Config.AddRspXGadgetFrameSize
push [rcx].Config.AddRspXGadget
;;
;; Set the pointer to the function to call in R11
;;
mov r11, [rcx].Config.SpooFunction
jmp Parameters
Spoof ENDP
;;
;; Set the parameters to pass to the target function
;;
Parameters PROC
mov r12, rcx
mov rax, [r12].Config.NArgs
; Arg01 (rcx)
cmp rax, 1
jb skip_1
mov rcx, [r12].Config.Arg01
skip_1:
; Arg02 (rdx)
cmp rax, 2
jb skip_2
mov rdx, [r12].Config.Arg02
skip_2:
; Arg03 (r8)
cmp rax, 3
jb skip_3
mov r8, [r12].Config.Arg03
skip_3:
; Arg04 (r9)
cmp rax, 4
jb skip_4
mov r9, [r12].Config.Arg04
skip_4:
; Stack-based args
lea r13, [rsp]
cmp rax, 5
jb skip_5
mov r10, [r12].Config.Arg05
mov [r13 + 28h], r10
skip_5:
; Arg06
cmp rax, 6
jb skip_6
mov r10, [r12].Config.Arg06
mov [r13 + 30h], r10
skip_6:
; Arg07
cmp rax, 7
jb skip_7
mov r10, [r12].Config.Arg07
mov [r13 + 38h], r10
skip_7:
; Arg08
cmp rax, 8
jb skip_8
mov r10, [r12].Config.Arg08
mov [r13 + 40h], r10
skip_8:
; Arg09
cmp rax, 9
jb skip_9
mov r10, [r12].Config.Arg09
mov [r13 + 48h], r10
skip_9:
; Arg10
cmp rax, 10
jb skip_10
mov r10, [r12].Config.Arg10
mov [r13 + 50h], r10
skip_10:
; Arg11
cmp rax, 11
jb skip_11
mov r10, [r12].Config.Arg11
mov [r13 + 58h], r10
skip_11:
cmp [r12].Config.IsSyscall, 1
je ExecuteSyscall
jmp Execute
Parameters ENDP
;;
;; Restores the original stack frame
;;
Restore PROC
mov rsp, rbp
pop rbx
pop rbp
ret
Restore ENDP
;;
;; Executes the target function
;;
Execute PROC
jmp QWORD PTR r11
Execute ENDP
;;
;; Executes a native Windows system call using the spoofed context
;;
ExecuteSyscall PROC
mov r10, rcx
mov eax, [r12].Config.Ssn
jmp QWORD PTR r11
ExecuteSyscall ENDP
END
+253
View File
@@ -0,0 +1,253 @@
;;
;; Code responsible for Call Stack Spoofing Via Synthetic (MASM)
;;
;;
;; Export
;;
SpoofSynthetic proto
.data
;;
;; Configuration structure passed to the spoof ASM routine
;;
Config STRUCT
RtlUserThreadStartAddr DQ 1
RtlUserThreadStartFrameSize DQ 1
BaseThreadInitThunkAddr DQ 1
BaseThreadInitThunkFrameSize DQ 1
FirstFrame DQ 1
SecondFrame DQ 1
JmpRbxGadget DQ 1
AddRspXGadget DQ 1
FirstFrameSize DQ 1
SecondFrameSize DQ 1
JmpRbxGadgetFrameSize DQ 1
AddRspXGadgetFrameSize DQ 1
RbpOffset DQ 1
SpooFunction DQ 1
ReturnAddress DQ 1
IsSyscall DD 0
Ssn DD 0
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
Config ENDS
.code
;;
;; Function responsible for Call Stack Spoofing
;;
SpoofSynthetic PROC
;;
;; Saving non-vol registers
;;
push rbp
push rbx
push r15
;;
;; Everything between RSP and RBP is our new stack frame for unwinding
;;
sub rsp, 210h
mov rbp, rsp
;;
;; Creating stack pointer to Restore PROC
;;
lea rax, RestoreSynthetic
push rax
lea rbx, [rsp]
;;
;; Cutting the call stack. The 0 pushed in this position will be the return address
;; of the next frame "RtlUserThreadStart", making it effectively the originating function
;;
xor rax, rax
push rax
;;
;; RtlUserThreadStart
;;
sub rsp, [rcx].Config.RtlUserThreadStartFrameSize
push [rcx].Config.RtlUserThreadStartAddr
add QWORD PTR [rsp], 21h
;;
;; BaseThreadInitThunk
;;
sub rsp, [rcx].Config.BaseThreadInitThunkFrameSize
push [rcx].Config.BaseThreadInitThunkAddr
add QWORD PTR [rsp], 14h
;;
;; Return address
;;
mov rax, rsp
;;
;; First Frame (Fake origin)
;;
push [rcx].Config.FirstFrame
sub rax, [rcx].Config.FirstFrameSize
sub rsp, [rcx].Config.SecondFrameSize
mov r10, [rcx].Config.RbpOffset
mov [rsp + r10], rax
;;
;; ROP Frames
;;
push [rcx].Config.SecondFrame
;;
;; JMP [RBX] Gadget / Stack Pivot (To restore original Control Flow Stack)
;;
sub rsp, [rcx].Config.JmpRbxGadgetFrameSize
push [rcx].Config.JmpRbxGadget
sub rsp, [rcx].Config.AddRspXGadgetFrameSize
push [rcx].Config.AddRspXGadget
;;
;; Set the pointer to the function to call in R11
;;
mov r11, [rcx].Config.SpooFunction
jmp ParametersSynthetic
SpoofSynthetic ENDP
;;
;; Set the parameters to pass to the target function
;;
ParametersSynthetic PROC
mov r12, rcx
mov rax, [r12].Config.NArgs
; Arg01 (rcx)
cmp rax, 1
jb skip_1
mov rcx, [r12].Config.Arg01
skip_1:
; Arg02 (rdx)
cmp rax, 2
jb skip_2
mov rdx, [r12].Config.Arg02
skip_2:
; Arg03 (r8)
cmp rax, 3
jb skip_3
mov r8, [r12].Config.Arg03
skip_3:
; Arg04 (r9)
cmp rax, 4
jb skip_4
mov r9, [r12].Config.Arg04
skip_4:
; Stack-based args
lea r13, [rsp]
cmp rax, 5
jb skip_5
mov r10, [r12].Config.Arg05
mov [r13 + 28h], r10
skip_5:
; Arg06
cmp rax, 6
jb skip_6
mov r10, [r12].Config.Arg06
mov [r13 + 30h], r10
skip_6:
; Arg07
cmp rax, 7
jb skip_7
mov r10, [r12].Config.Arg07
mov [r13 + 38h], r10
skip_7:
; Arg08
cmp rax, 8
jb skip_8
mov r10, [r12].Config.Arg08
mov [r13 + 40h], r10
skip_8:
; Arg09
cmp rax, 9
jb skip_9
mov r10, [r12].Config.Arg09
mov [r13 + 48h], r10
skip_9:
; Arg10
cmp rax, 10
jb skip_10
mov r10, [r12].Config.Arg10
mov [r13 + 50h], r10
skip_10:
; Arg11
cmp rax, 11
jb skip_11
mov r10, [r12].Config.Arg11
mov [r13 + 58h], r10
skip_11:
cmp [r12].Config.IsSyscall, 1
je ExecuteSyscallSynthetic
jmp ExecuteSynthetic
ParametersSynthetic ENDP
;;
;; Restores the original stack frame
;;
RestoreSynthetic PROC
mov rsp, rbp
add rsp, 210h
pop r15
pop rbx
pop rbp
ret
RestoreSynthetic ENDP
;;
;; Executes the target function
;;
ExecuteSynthetic PROC
jmp QWORD PTR r11
ExecuteSynthetic ENDP
;;
;; Executes a native Windows system call using the spoofed context
;;
ExecuteSyscallSynthetic PROC
mov r10, rcx
mov eax, [r12].Config.Ssn
jmp QWORD PTR r11
ExecuteSyscallSynthetic ENDP
END
+515
View File
@@ -0,0 +1,515 @@
//! Call stack spoofing for Dyncvoke.
//!
//! Two modes, picked at compile time:
//!
//! - **Synthetic** (default): build a fake call stack rooted at
//! `RtlUserThreadStart -> BaseThreadInitThunk -> ...`. Works from any
//! thread including pool threads. Selected when the `desync` feature is
//! off.
//!
//! - **Desync** (`desync` feature): locate an existing
//! `BaseThreadInitThunk` return address on the current thread's real
//! stack and splice spoofed frames on top. Most realistic-looking stack,
//! but only works on threads that started through the normal user-thread
//! chain.
//!
//! The trampoline implementations live in `src/asm/{msvc,gnu}/*.asm`. They
//! read the `Config` struct laid out in [`types::Config`] — do not reorder
//! its fields.
//!
//! SSN resolution for `spoof_syscall!` uses dyncvoke_core's existing
//! Tartarus Gate (`dyncvoke_core::resolve_syscall`). No second resolver,
//! no second PE parser for module/function lookup — those come from
//! dyncvoke_core too.
#![no_std]
extern crate alloc;
use alloc::string::String;
use alloc::vec::Vec;
use core::ffi::c_void;
use data::lc;
pub mod pe;
pub mod types;
pub mod unwind;
pub mod util;
use crate::pe::{function_by_rva, runtime_function_table};
use crate::types::{Config, ImageRuntimeFunction};
use crate::unwind::{rbp_offset, stack_frame};
#[cfg(not(feature = "desync"))]
use crate::unwind::ignoring_set_fpreg;
use crate::util::{find_gadget, find_valid_instruction_offset, shuffle};
#[cfg(feature = "desync")]
use crate::util::find_base_thread_return_address;
// Compile-time-selected asm trampoline. Synthetic mode calls SpoofSynthetic,
// desync calls Spoof. The asm files compile both — the linker drops the
// unused symbol.
#[cfg(feature = "desync")]
unsafe extern "C" {
fn Spoof(config: &mut Config) -> *mut c_void;
}
#[cfg(not(feature = "desync"))]
unsafe extern "C" {
fn SpoofSynthetic(config: &mut Config) -> *mut c_void;
}
/// Spoof a direct function call. Resolves the spoofing scaffolding once
/// and then jumps to `addr` with the supplied args under a fake stack.
///
/// # Examples
///
/// ```ignore
/// let kernel32 = dyncvoke_core::get_module_base_address("kernel32.dll");
/// let virtual_alloc = dyncvoke_core::get_function_address(kernel32, "VirtualAlloc");
/// let addr = spoof::spoof!(
/// virtual_alloc,
/// core::ptr::null_mut::<core::ffi::c_void>(),
/// 1 << 12,
/// 0x3000u32,
/// 0x04u32
/// )?;
/// ```
#[macro_export]
macro_rules! spoof {
($addr:expr, $($arg:expr),+ $(,)?) => {
unsafe {
$crate::__private::spoof(
$addr as *mut ::core::ffi::c_void,
&[$(::core::mem::transmute($arg as usize)),*],
$crate::SpoofKind::Function,
)
}
};
}
/// Spoof an indirect syscall. SSN is resolved via dyncvoke_core's Tartarus
/// Gate, then the syscall instruction inside ntdll is dispatched under a
/// fake stack.
///
/// # Examples
///
/// ```ignore
/// use spoof::{spoof_syscall, AsPointer};
/// let mut addr = core::ptr::null_mut::<core::ffi::c_void>();
/// let mut size = (1 << 12) as usize;
/// let status = spoof_syscall!(
/// "NtAllocateVirtualMemory",
/// -1isize,
/// addr.as_ptr_mut(),
/// 0usize,
/// size.as_ptr_mut(),
/// 0x3000u32,
/// 0x04u32
/// )? as i32;
/// ```
#[macro_export]
macro_rules! spoof_syscall {
($name:expr, $($arg:expr),* $(,)?) => {
unsafe {
$crate::__private::spoof(
::core::ptr::null_mut(),
&[$(::core::mem::transmute($arg as usize)),*],
$crate::SpoofKind::Syscall($name),
)
}
};
}
/// Selects which mode the public entry uses.
pub enum SpoofKind<'a> {
/// Spoof a direct function call.
Function,
/// Spoof an indirect syscall by ntdll export name.
Syscall(&'a str),
}
/// Where a failure happened. Carried by SpoofError variants as a non-string
/// tag so the error path doesn't leak plaintext module/function names into
/// `.rdata`. Display impl decrypts via obfstr at format time only.
#[derive(Debug, Clone, Copy)]
pub enum SpoofTag {
Kernelbase,
Kernel32,
Ntdll,
RtlUserThreadStart,
BaseThreadInitThunk,
AddRspGadget,
JmpRbxGadget,
}
/// Spoofing errors. Variants carry [`SpoofTag`] rather than literal strings.
#[derive(Debug)]
pub enum SpoofError {
TooManyArguments,
NullFunctionAddress,
ModuleNotLoaded(SpoofTag),
FunctionNotFound(SpoofTag),
PdataMissing(SpoofTag),
UnwindInfoMissing(SpoofTag),
PrologueNotFound,
PushRbpPrologueNotFound,
GadgetNotFound(SpoofTag),
BaseThreadReturnNotFound,
SsnResolutionFailed,
}
impl core::fmt::Display for SpoofError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let tag_str = |t: SpoofTag| match t {
SpoofTag::Kernelbase => lc!("kernelbase.dll"),
SpoofTag::Kernel32 => lc!("kernel32.dll"),
SpoofTag::Ntdll => lc!("ntdll.dll"),
SpoofTag::RtlUserThreadStart => lc!("RtlUserThreadStart"),
SpoofTag::BaseThreadInitThunk => lc!("BaseThreadInitThunk"),
SpoofTag::AddRspGadget => lc!("add rsp,0x58; ret"),
SpoofTag::JmpRbxGadget => lc!("jmp [rbx]"),
};
match self {
SpoofError::TooManyArguments => write!(f, "{}", lc!("too many arguments")),
SpoofError::NullFunctionAddress => write!(f, "{}", lc!("null function address")),
SpoofError::ModuleNotLoaded(t) => write!(f, "{}: {}", lc!("module"), tag_str(*t)),
SpoofError::FunctionNotFound(t) => write!(f, "{}: {}", lc!("export"), tag_str(*t)),
SpoofError::PdataMissing(t) => write!(f, "{}: {}", lc!("pdata"), tag_str(*t)),
SpoofError::UnwindInfoMissing(t) => write!(f, "{}: {}", lc!("unwind"), tag_str(*t)),
SpoofError::PrologueNotFound => write!(f, "{}", lc!("no prologue")),
SpoofError::PushRbpPrologueNotFound => write!(f, "{}", lc!("no rbp prologue")),
SpoofError::GadgetNotFound(t) => write!(f, "{}: {}", lc!("gadget"), tag_str(*t)),
SpoofError::BaseThreadReturnNotFound => write!(f, "{}", lc!("no thread return")),
SpoofError::SsnResolutionFailed => write!(f, "{}", lc!("ssn fail")),
}
}
}
impl core::error::Error for SpoofError {}
/// AsPointer — uwd's ergonomic trait. Lets call sites write `addr.as_ptr_mut()`
/// in macro args instead of `&mut addr as *mut _ as *mut c_void`.
pub trait AsPointer {
fn as_ptr_const(&self) -> *const c_void;
fn as_ptr_mut(&mut self) -> *mut c_void;
}
impl<T> AsPointer for T {
#[inline(always)]
fn as_ptr_const(&self) -> *const c_void {
self as *const _ as *const c_void
}
#[inline(always)]
fn as_ptr_mut(&mut self) -> *mut c_void {
self as *mut _ as *mut c_void
}
}
#[doc(hidden)]
pub mod __private {
use super::*;
#[allow(unused_imports)]
use alloc::string::String;
/// Synthetic-mode entry. Builds the full scaffolding from scratch and
/// hands it to the asm trampoline. Works on any thread.
#[cfg(not(feature = "desync"))]
pub unsafe fn spoof(
addr: *mut c_void,
args: &[*const c_void],
kind: SpoofKind,
) -> Result<*mut c_void, SpoofError> {
if args.len() > 11 {
return Err(SpoofError::TooManyArguments);
}
if matches!(kind, SpoofKind::Function) && addr.is_null() {
return Err(SpoofError::NullFunctionAddress);
}
let mut config = Config::default();
let kernelbase = dyncvoke_core::get_module_base_address(&lc!("kernelbase.dll"));
if kernelbase == 0 {
return Err(SpoofError::ModuleNotLoaded(SpoofTag::Kernelbase));
}
let ntdll = dyncvoke_core::get_module_base_address(&lc!("ntdll.dll"));
if ntdll == 0 {
return Err(SpoofError::ModuleNotLoaded(SpoofTag::Ntdll));
}
let kernel32 = dyncvoke_core::get_module_base_address(&lc!("kernel32.dll"));
if kernel32 == 0 {
return Err(SpoofError::ModuleNotLoaded(SpoofTag::Kernel32));
}
let rtl_user_addr =
dyncvoke_core::get_function_address(ntdll, &lc!("RtlUserThreadStart"));
if rtl_user_addr == 0 {
return Err(SpoofError::FunctionNotFound(SpoofTag::RtlUserThreadStart));
}
let base_thread_addr =
dyncvoke_core::get_function_address(kernel32, &lc!("BaseThreadInitThunk"));
if base_thread_addr == 0 {
return Err(SpoofError::FunctionNotFound(SpoofTag::BaseThreadInitThunk));
}
config.rtl_user_addr = rtl_user_addr as *const c_void;
config.base_thread_addr = base_thread_addr as *const c_void;
let kb_table = runtime_function_table(kernelbase as *mut c_void)
.ok_or(SpoofError::PdataMissing(SpoofTag::Kernelbase))?;
let ntdll_table = runtime_function_table(ntdll as *mut c_void)
.ok_or(SpoofError::PdataMissing(SpoofTag::Ntdll))?;
let k32_table = runtime_function_table(kernel32 as *mut c_void)
.ok_or(SpoofError::PdataMissing(SpoofTag::Kernel32))?;
let rtl_user_runtime =
function_by_rva(ntdll_table, (rtl_user_addr - ntdll) as u32)
.ok_or(SpoofError::UnwindInfoMissing(SpoofTag::RtlUserThreadStart))?;
let base_thread_runtime =
function_by_rva(k32_table, (base_thread_addr - kernel32) as u32)
.ok_or(SpoofError::UnwindInfoMissing(SpoofTag::BaseThreadInitThunk))?;
config.rtl_user_thread_size =
ignoring_set_fpreg(ntdll as *mut c_void, rtl_user_runtime)
.ok_or(SpoofError::UnwindInfoMissing(SpoofTag::RtlUserThreadStart))? as u64;
config.base_thread_size =
ignoring_set_fpreg(kernel32 as *mut c_void, base_thread_runtime)
.ok_or(SpoofError::UnwindInfoMissing(SpoofTag::BaseThreadInitThunk))? as u64;
let first_prolog = find_prolog(kernelbase as *mut c_void, kb_table)
.ok_or(SpoofError::PrologueNotFound)?;
config.first_frame_fp =
(first_prolog.frame + first_prolog.offset as u64) as *const c_void;
config.first_frame_size = first_prolog.stack_size as u64;
let second_prolog = find_push_rbp(kernelbase as *mut c_void, kb_table)
.ok_or(SpoofError::PushRbpPrologueNotFound)?;
config.second_frame_fp =
(second_prolog.frame + second_prolog.offset as u64) as *const c_void;
config.second_frame_size = second_prolog.stack_size as u64;
config.rbp_stack_offset = second_prolog.rbp_offset as u64;
let (add_rsp_addr, size) = find_gadget(
kernelbase as *mut c_void,
&[0x48, 0x83, 0xC4, 0x58, 0xC3],
kb_table,
)
.ok_or(SpoofError::GadgetNotFound(SpoofTag::AddRspGadget))?;
config.add_rsp_gadget = add_rsp_addr as *const c_void;
config.add_rsp_frame_size = size as u64;
let (jmp_rbx_addr, size) =
find_gadget(kernelbase as *mut c_void, &[0xFF, 0x23], kb_table)
.ok_or(SpoofError::GadgetNotFound(SpoofTag::JmpRbxGadget))?;
config.jmp_rbx_gadget = jmp_rbx_addr as *const c_void;
config.jmp_rbx_frame_size = size as u64;
config.number_args = args.len() as u64;
write_args(&mut config, args);
match kind {
SpoofKind::Function => config.spoof_function = addr as *const c_void,
SpoofKind::Syscall(name) => {
let (ssn, syscall_addr) = dyncvoke_core::resolve_syscall(name)
.map_err(|_| SpoofError::SsnResolutionFailed)?;
config.is_syscall = 1;
config.ssn = ssn as u32;
config.spoof_function = syscall_addr as *const c_void;
}
}
Ok(SpoofSynthetic(&mut config))
}
/// Desync-mode entry. Reuses the current thread's real
/// BaseThreadInitThunk return record. Won't work on pool threads.
#[cfg(feature = "desync")]
pub unsafe fn spoof(
addr: *mut c_void,
args: &[*const c_void],
kind: SpoofKind,
) -> Result<*mut c_void, SpoofError> {
if args.len() > 11 {
return Err(SpoofError::TooManyArguments);
}
if matches!(kind, SpoofKind::Function) && addr.is_null() {
return Err(SpoofError::NullFunctionAddress);
}
let mut config = Config::default();
let kernelbase = dyncvoke_core::get_module_base_address(&lc!("kernelbase.dll"));
if kernelbase == 0 {
return Err(SpoofError::ModuleNotLoaded(SpoofTag::Kernelbase));
}
let kernel32 = dyncvoke_core::get_module_base_address(&lc!("kernel32.dll"));
if kernel32 == 0 {
return Err(SpoofError::ModuleNotLoaded(SpoofTag::Kernel32));
}
let base_thread_addr =
dyncvoke_core::get_function_address(kernel32, &lc!("BaseThreadInitThunk"));
if base_thread_addr == 0 {
return Err(SpoofError::FunctionNotFound(SpoofTag::BaseThreadInitThunk));
}
let k32_table = runtime_function_table(kernel32 as *mut c_void)
.ok_or(SpoofError::PdataMissing(SpoofTag::Kernel32))?;
let base_thread_runtime =
function_by_rva(k32_table, (base_thread_addr - kernel32) as u32)
.ok_or(SpoofError::UnwindInfoMissing(SpoofTag::BaseThreadInitThunk))?;
let base_thread_size =
(base_thread_runtime.EndAddress - base_thread_runtime.BeginAddress) as usize;
let stack_slot = find_base_thread_return_address(
kernel32 as *mut c_void,
base_thread_addr as *mut c_void,
base_thread_size,
)
.ok_or(SpoofError::BaseThreadReturnNotFound)?;
config.return_address = stack_slot as *const c_void;
let kb_table = runtime_function_table(kernelbase as *mut c_void)
.ok_or(SpoofError::PdataMissing(SpoofTag::Kernelbase))?;
let first_prolog = find_prolog(kernelbase as *mut c_void, kb_table)
.ok_or(SpoofError::PrologueNotFound)?;
config.first_frame_fp =
(first_prolog.frame + first_prolog.offset as u64) as *const c_void;
config.first_frame_size = first_prolog.stack_size as u64;
let second_prolog = find_push_rbp(kernelbase as *mut c_void, kb_table)
.ok_or(SpoofError::PushRbpPrologueNotFound)?;
config.second_frame_fp =
(second_prolog.frame + second_prolog.offset as u64) as *const c_void;
config.second_frame_size = second_prolog.stack_size as u64;
config.rbp_stack_offset = second_prolog.rbp_offset as u64;
let (add_rsp_addr, size) = find_gadget(
kernelbase as *mut c_void,
&[0x48, 0x83, 0xC4, 0x58, 0xC3],
kb_table,
)
.ok_or(SpoofError::GadgetNotFound(SpoofTag::AddRspGadget))?;
config.add_rsp_gadget = add_rsp_addr as *const c_void;
config.add_rsp_frame_size = size as u64;
let (jmp_rbx_addr, size) =
find_gadget(kernelbase as *mut c_void, &[0xFF, 0x23], kb_table)
.ok_or(SpoofError::GadgetNotFound(SpoofTag::JmpRbxGadget))?;
config.jmp_rbx_gadget = jmp_rbx_addr as *const c_void;
config.jmp_rbx_frame_size = size as u64;
config.number_args = args.len() as u64;
write_args(&mut config, args);
match kind {
SpoofKind::Function => config.spoof_function = addr as *const c_void,
SpoofKind::Syscall(name) => {
let (ssn, syscall_addr) = dyncvoke_core::resolve_syscall(name)
.map_err(|_| SpoofError::SsnResolutionFailed)?;
config.is_syscall = 1;
config.ssn = ssn as u32;
config.spoof_function = syscall_addr as *const c_void;
}
}
Ok(Spoof(&mut config))
}
#[inline]
fn write_args(cfg: &mut Config, args: &[*const c_void]) {
for (i, &a) in args.iter().enumerate() {
match i {
0 => cfg.arg01 = a,
1 => cfg.arg02 = a,
2 => cfg.arg03 = a,
3 => cfg.arg04 = a,
4 => cfg.arg05 = a,
5 => cfg.arg06 = a,
6 => cfg.arg07 = a,
7 => cfg.arg08 = a,
8 => cfg.arg09 = a,
9 => cfg.arg10 = a,
10 => cfg.arg11 = a,
_ => break,
}
}
}
}
/// Selected decoy frame metadata.
#[derive(Copy, Clone, Default)]
struct Prolog {
frame: u64,
stack_size: u32,
offset: u32,
rbp_offset: u32,
}
/// Scan kernelbase's runtime function table for a function whose unwind
/// info describes a spoof-compatible RSP-based frame. Shuffles the survivors
/// and returns the first one — picks a different decoy each run.
fn find_prolog(module_base: *mut c_void, runtime_table: &[ImageRuntimeFunction]) -> Option<Prolog> {
let mut prologs: Vec<Prolog> = runtime_table
.iter()
.filter_map(|runtime| {
let (is_valid, stack_size) = unsafe { stack_frame(module_base, runtime) }?;
if !is_valid {
return None;
}
let offset = find_valid_instruction_offset(module_base, runtime)?;
let frame = module_base as u64 + runtime.BeginAddress as u64;
Some(Prolog {
frame,
stack_size,
offset,
..Default::default()
})
})
.collect();
if prologs.is_empty() {
return None;
}
shuffle(&mut prologs);
prologs.first().copied()
}
/// Same idea but for an RBP-pushing prologue — these are needed for the
/// inner spoofed frame where the unwinder will reconstruct rbp from the
/// stored slot.
fn find_push_rbp(
module_base: *mut c_void,
runtime_table: &[ImageRuntimeFunction],
) -> Option<Prolog> {
let mut prologs: Vec<Prolog> = runtime_table
.iter()
.filter_map(|runtime| {
let (rbp_off, stack_size) = unsafe { rbp_offset(module_base, runtime) }?;
if rbp_off == 0 || stack_size == 0 || stack_size <= rbp_off {
return None;
}
let offset = find_valid_instruction_offset(module_base, runtime)?;
let frame = module_base as u64 + runtime.BeginAddress as u64;
Some(Prolog {
frame,
stack_size,
offset,
rbp_offset: rbp_off,
})
})
.collect();
if prologs.is_empty() {
return None;
}
// First match is consistently unsuitable on most Windows builds; drop it.
prologs.remove(0);
if prologs.is_empty() {
return None;
}
shuffle(&mut prologs);
prologs.first().copied()
}
+64
View File
@@ -0,0 +1,64 @@
//! Minimal PE walker — just enough to locate the .pdata RUNTIME_FUNCTION
//! table for a loaded module. We deliberately don't reuse dyncvoke_core's PE
//! parser because that one was built for manual mapping (full optional-header
//! deserialization, etc.). For spoofing we only need the data-directory at
//! IMAGE_DIRECTORY_ENTRY_EXCEPTION.
use core::ffi::c_void;
use core::slice;
use crate::types::{ImageDataDirectory, ImageRuntimeFunction, IMAGE_DIRECTORY_ENTRY_EXCEPTION};
/// Read the exception data directory's runtime function table for a loaded
/// module. Returns None if the headers are malformed or the directory is
/// empty.
pub unsafe fn runtime_function_table<'a>(
module_base: *mut c_void,
) -> Option<&'a [ImageRuntimeFunction]> {
let base = module_base as *const u8;
// PE signature offset lives at DOS header offset 0x3C.
let nt_offset = *(base.add(0x3C) as *const i32);
if nt_offset <= 0 {
return None;
}
let nt_header = base.add(nt_offset as usize);
// "PE\0\0" signature check.
if *(nt_header as *const u32) != 0x0000_4550 {
return None;
}
// Magic byte at NT+0x18 distinguishes PE32 (0x10b) from PE32+ (0x20b).
// We only support x64 / PE32+ here.
let optional_header = nt_header.add(0x18);
let magic = *(optional_header as *const u16);
if magic != 0x020b {
return None;
}
// DataDirectory[] sits at optional_header+0x70 on PE32+. Each entry is 8
// bytes (VA + Size).
let data_dirs_base = optional_header.add(0x70);
let dir_ptr = data_dirs_base
.add(IMAGE_DIRECTORY_ENTRY_EXCEPTION * size_of::<ImageDataDirectory>())
as *const ImageDataDirectory;
let dir = *dir_ptr;
if dir.VirtualAddress == 0 || dir.Size == 0 {
return None;
}
let table_addr = (module_base as usize + dir.VirtualAddress as usize) as *const ImageRuntimeFunction;
let count = dir.Size as usize / size_of::<ImageRuntimeFunction>();
Some(slice::from_raw_parts(table_addr, count))
}
/// Find the [`ImageRuntimeFunction`] whose BeginAddress matches `rva`.
pub fn function_by_rva<'a>(
table: &'a [ImageRuntimeFunction],
rva: u32,
) -> Option<&'a ImageRuntimeFunction> {
table.iter().find(|f| f.BeginAddress == rva)
}
+183
View File
@@ -0,0 +1,183 @@
#![allow(non_snake_case, non_camel_case_types)]
use core::ffi::c_void;
/// Indicates the presence of an exception handler in the function.
pub const UNW_FLAG_EHANDLER: u8 = 0x1;
/// Indicates chained unwind information is present.
pub const UNW_FLAG_CHAININFO: u8 = 0x4;
/// IMAGE_DIRECTORY_ENTRY_EXCEPTION — index into the optional header's
/// DataDirectory[] where the .pdata RUNTIME_FUNCTION table lives.
pub const IMAGE_DIRECTORY_ENTRY_EXCEPTION: usize = 3;
/// IMAGE_RUNTIME_FUNCTION_ENTRY — one entry of the .pdata exception table.
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub struct ImageRuntimeFunction {
pub BeginAddress: u32,
pub EndAddress: u32,
pub UnwindData: u32,
}
/// IMAGE_DATA_DIRECTORY — VA + size pair used by the optional header.
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub struct ImageDataDirectory {
pub VirtualAddress: u32,
pub Size: u32,
}
/// Config struct read by the asm trampoline. Layout must match
/// `src/asm/{msvc,gnu}/synthetic.asm` and `desync.asm` byte-for-byte.
/// Field reordering will corrupt argument passing — do not touch.
#[repr(C)]
#[derive(Debug)]
pub struct Config {
pub rtl_user_addr: *const c_void,
pub rtl_user_thread_size: u64,
pub base_thread_addr: *const c_void,
pub base_thread_size: u64,
pub first_frame_fp: *const c_void,
pub second_frame_fp: *const c_void,
pub jmp_rbx_gadget: *const c_void,
pub add_rsp_gadget: *const c_void,
pub first_frame_size: u64,
pub second_frame_size: u64,
pub jmp_rbx_frame_size: u64,
pub add_rsp_frame_size: u64,
pub rbp_stack_offset: u64,
pub spoof_function: *const c_void,
pub return_address: *const c_void,
pub is_syscall: u32,
pub ssn: u32,
pub number_args: u64,
pub arg01: *const c_void,
pub arg02: *const c_void,
pub arg03: *const c_void,
pub arg04: *const c_void,
pub arg05: *const c_void,
pub arg06: *const c_void,
pub arg07: *const c_void,
pub arg08: *const c_void,
pub arg09: *const c_void,
pub arg10: *const c_void,
pub arg11: *const c_void,
}
impl Default for Config {
fn default() -> Self {
unsafe { core::mem::zeroed() }
}
}
/// x86_64 general-purpose register encoding used by unwind codes.
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
#[allow(dead_code)]
pub enum Registers {
Rax = 0,
Rcx,
Rdx,
Rbx,
Rsp,
Rbp,
Rsi,
Rdi,
R8,
R9,
R10,
R11,
R12,
R13,
R14,
R15,
}
impl PartialEq<usize> for Registers {
fn eq(&self, other: &usize) -> bool {
*self as usize == *other
}
}
/// One unwind code slot. The full union: either a packed 16-bit slot
/// (CodeOffset/UnwindOp/OpInfo) or a raw FrameOffset for following slots.
#[repr(C)]
pub union UnwindCode {
pub FrameOffset: u16,
pub Anonymous: UnwindCode0,
}
bitfield::bitfield! {
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct UnwindCode0(u16);
pub u8, CodeOffset, SetCodeOffset: 7, 0;
pub u8, UnwindOp, SetUnwindOp: 11, 8;
pub u8, OpInfo, SetOpInfo: 15, 12;
}
/// Optional Exception/ChainInfo slot in UNWIND_INFO.
#[repr(C)]
pub union UnwindInfo0 {
pub ExceptionHandler: u32,
pub FunctionEntry: u32,
}
bitfield::bitfield! {
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct UnwindVersionFlags(u8);
pub u8, Version, SetVersion: 2, 0;
pub u8, Flags, SetFlags: 7, 3;
}
bitfield::bitfield! {
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct UnwindFrameInfo(u8);
pub u8, FrameRegister, SetFrameRegister: 3, 0;
pub u8, FrameOffset, SetFrameOffset: 7, 4;
}
/// UNWIND_INFO header at the front of every function's unwind record.
#[repr(C)]
pub struct UnwindInfo {
pub VersionFlags: UnwindVersionFlags,
pub SizeOfProlog: u8,
pub CountOfCodes: u8,
pub FrameInfo: UnwindFrameInfo,
pub UnwindCode: UnwindCode,
pub Anonymous: UnwindInfo0,
pub ExceptionData: u32,
}
/// x64 unwind operation codes (subset we care about for stack-size
/// calculation). MS docs: learn.microsoft.com/cpp/build/exception-handling-x64
#[repr(u8)]
#[allow(dead_code)]
pub enum UnwindOpCode {
UWOP_PUSH_NONVOL = 0,
UWOP_ALLOC_LARGE = 1,
UWOP_ALLOC_SMALL = 2,
UWOP_SET_FPREG = 3,
UWOP_SAVE_NONVOL = 4,
UWOP_SAVE_NONVOL_BIG = 5,
UWOP_EPILOG = 6,
UWOP_SPARE_CODE = 7,
UWOP_SAVE_XMM128 = 8,
UWOP_SAVE_XMM128BIG = 9,
UWOP_PUSH_MACH_FRAME = 10,
}
impl TryFrom<u8> for UnwindOpCode {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0..=10 => Ok(unsafe { core::mem::transmute::<u8, UnwindOpCode>(value) }),
_ => Err(()),
}
}
}
+288
View File
@@ -0,0 +1,288 @@
//! Walk UNWIND_INFO records to derive stack sizes and RBP offsets. Direct
//! port of uwd's three walkers (rbp_offset, stack_frame, ignoring_set_fpreg),
//! kept verbatim because their correctness was verified against live ntdll /
//! kernelbase / kernel32 prologues.
use core::ffi::c_void;
use crate::types::{
ImageRuntimeFunction, Registers, UnwindCode, UnwindInfo, UnwindOpCode::*,
UnwindOpCode, UNW_FLAG_CHAININFO, UNW_FLAG_EHANDLER,
};
/// Determine RBP push location and total stack size. Returns None if the
/// frame layout isn't spoof-compatible (RSP saved as nonvol, RBP pushed
/// twice, etc.).
pub unsafe fn rbp_offset(
module: *mut c_void,
runtime: &ImageRuntimeFunction,
) -> Option<(u32, u32)> {
let unwind_info = (module as usize + runtime.UnwindData as usize) as *mut UnwindInfo;
let unwind_code = (unwind_info as *mut u8).add(4) as *mut UnwindCode;
let flag = (*unwind_info).VersionFlags.Flags();
let mut i = 0usize;
let mut total_stack = 0u32;
let mut rbp_pushed = false;
let mut stack_offset = 0u32;
while i < (*unwind_info).CountOfCodes as usize {
let unwind_code = unwind_code.add(i);
let op_info = (*unwind_code).Anonymous.OpInfo() as usize;
let unwind_op = (*unwind_code).Anonymous.UnwindOp();
match UnwindOpCode::try_from(unwind_op) {
Ok(UWOP_PUSH_NONVOL) => {
if Registers::Rsp == op_info {
return None;
}
if Registers::Rbp == op_info {
if rbp_pushed {
return None;
}
rbp_pushed = true;
stack_offset = total_stack;
}
total_stack += 8;
i += 1;
}
Ok(UWOP_ALLOC_LARGE) => {
if (*unwind_code).Anonymous.OpInfo() == 0 {
let frame_offset = ((*unwind_code.add(1)).FrameOffset as i32) * 8;
total_stack += frame_offset as u32;
i += 2;
} else {
let frame_offset = *(unwind_code.add(1) as *mut i32);
total_stack += frame_offset as u32;
i += 3;
}
}
Ok(UWOP_ALLOC_SMALL) => {
total_stack += ((op_info + 1) * 8) as u32;
i += 1;
}
Ok(UWOP_SAVE_NONVOL) => {
if Registers::Rsp == op_info {
return None;
}
if Registers::Rbp == op_info {
if rbp_pushed {
return None;
}
let offset = (*unwind_code.add(1)).FrameOffset * 8;
stack_offset = total_stack + offset as u32;
rbp_pushed = true;
}
i += 2;
}
Ok(UWOP_SAVE_NONVOL_BIG) => {
if Registers::Rsp == op_info {
return None;
}
if Registers::Rbp == op_info {
if rbp_pushed {
return None;
}
let offset = *(unwind_code.add(1) as *mut u32);
stack_offset = total_stack + offset;
rbp_pushed = true;
}
i += 3;
}
Ok(UWOP_SET_FPREG) => return None,
Ok(UWOP_SAVE_XMM128) => i += 2,
Ok(UWOP_SAVE_XMM128BIG) => i += 3,
Ok(UWOP_EPILOG) | Ok(UWOP_SPARE_CODE) => i += 1,
Ok(UWOP_PUSH_MACH_FRAME) => {
total_stack += if op_info == 0 { 0x40 } else { 0x48 };
i += 1;
}
_ => {}
}
}
if (flag & UNW_FLAG_CHAININFO) != 0 {
let count = (*unwind_info).CountOfCodes as usize;
let index = if count & 1 == 1 { count + 1 } else { count };
let runtime = unwind_code.add(index) as *const ImageRuntimeFunction;
if let Some((_, child_total)) = rbp_offset(module, &*runtime) {
total_stack += child_total;
} else {
return None;
}
}
Some((stack_offset, total_stack))
}
/// Compute total stack size, rejecting any frame that uses RBP/RSP as a
/// non-volatile save target. Used when picking the first decoy prologue.
pub unsafe fn stack_frame(
module: *mut c_void,
runtime: &ImageRuntimeFunction,
) -> Option<(bool, u32)> {
let unwind_info = (module as usize + runtime.UnwindData as usize) as *mut UnwindInfo;
let unwind_code = (unwind_info as *mut u8).add(4) as *mut UnwindCode;
let flag = (*unwind_info).VersionFlags.Flags();
let mut i = 0usize;
let mut set_fpreg_hit = false;
let mut total_stack = 0i32;
while i < (*unwind_info).CountOfCodes as usize {
let unwind_code = unwind_code.add(i);
let op_info = (*unwind_code).Anonymous.OpInfo() as usize;
let unwind_op = (*unwind_code).Anonymous.UnwindOp();
match UnwindOpCode::try_from(unwind_op) {
Ok(UWOP_PUSH_NONVOL) => {
if Registers::Rsp == op_info && !set_fpreg_hit {
return None;
}
total_stack += 8;
i += 1;
}
Ok(UWOP_ALLOC_SMALL) => {
total_stack += ((op_info + 1) * 8) as i32;
i += 1;
}
Ok(UWOP_ALLOC_LARGE) => {
if (*unwind_code).Anonymous.OpInfo() == 0 {
let frame_offset = ((*unwind_code.add(1)).FrameOffset as i32) * 8;
total_stack += frame_offset;
i += 2;
} else {
let frame_offset = *(unwind_code.add(1) as *mut i32);
total_stack += frame_offset;
i += 3;
}
}
Ok(UWOP_SAVE_NONVOL) => {
if Registers::Rsp == op_info || Registers::Rbp == op_info {
return None;
}
i += 2;
}
Ok(UWOP_SAVE_NONVOL_BIG) => {
if Registers::Rsp == op_info || Registers::Rbp == op_info {
return None;
}
i += 3;
}
Ok(UWOP_SAVE_XMM128) => i += 2,
Ok(UWOP_SAVE_XMM128BIG) => i += 3,
Ok(UWOP_SET_FPREG) => {
if (flag & UNW_FLAG_EHANDLER) != 0 && (flag & UNW_FLAG_CHAININFO) != 0 {
return None;
}
if (*unwind_info).FrameInfo.FrameRegister() != Registers::Rbp as u8 {
return None;
}
set_fpreg_hit = true;
let offset = ((*unwind_info).FrameInfo.FrameOffset() as i32) << 4;
total_stack -= offset;
i += 1;
}
Ok(UWOP_EPILOG) | Ok(UWOP_SPARE_CODE) => i += 1,
Ok(UWOP_PUSH_MACH_FRAME) => {
total_stack += if op_info == 0 { 0x40 } else { 0x48 };
i += 1;
}
_ => {}
}
}
if (flag & UNW_FLAG_CHAININFO) != 0 {
let count = (*unwind_info).CountOfCodes as usize;
let index = if count & 1 == 1 { count + 1 } else { count };
let runtime = unwind_code.add(index) as *const ImageRuntimeFunction;
if let Some((chained_fpreg_hit, chained_stack)) = stack_frame(module, &*runtime) {
total_stack += chained_stack as i32;
set_fpreg_hit |= chained_fpreg_hit;
} else {
return None;
}
}
Some((set_fpreg_hit, total_stack as u32))
}
/// Total stack size, treating set_fpreg as a no-op. Used for sizing the
/// frames we splice in (RtlUserThreadStart, BaseThreadInitThunk, gadget
/// frames).
pub unsafe fn ignoring_set_fpreg(
module: *mut c_void,
runtime: &ImageRuntimeFunction,
) -> Option<u32> {
let unwind_info = (module as usize + runtime.UnwindData as usize) as *mut UnwindInfo;
let unwind_code = (unwind_info as *mut u8).add(4) as *mut UnwindCode;
let flag = (*unwind_info).VersionFlags.Flags();
let mut i = 0usize;
let mut total_stack = 0u32;
while i < (*unwind_info).CountOfCodes as usize {
let unwind_code = unwind_code.add(i);
let op_info = (*unwind_code).Anonymous.OpInfo() as usize;
let unwind_op = (*unwind_code).Anonymous.UnwindOp();
match UnwindOpCode::try_from(unwind_op) {
Ok(UWOP_PUSH_NONVOL) => {
if Registers::Rsp == op_info {
return None;
}
total_stack += 8;
i += 1;
}
Ok(UWOP_ALLOC_SMALL) => {
total_stack += ((op_info + 1) * 8) as u32;
i += 1;
}
Ok(UWOP_ALLOC_LARGE) => {
if (*unwind_code).Anonymous.OpInfo() == 0 {
let frame_offset = ((*unwind_code.add(1)).FrameOffset as i32) * 8;
total_stack += frame_offset as u32;
i += 2;
} else {
let frame_offset = *(unwind_code.add(1) as *mut i32);
total_stack += frame_offset as u32;
i += 3;
}
}
Ok(UWOP_SAVE_NONVOL) => {
if Registers::Rsp == op_info {
return None;
}
i += 2;
}
Ok(UWOP_SAVE_NONVOL_BIG) => {
if Registers::Rsp == op_info {
return None;
}
i += 3;
}
Ok(UWOP_SAVE_XMM128) => i += 2,
Ok(UWOP_SAVE_XMM128BIG) => i += 3,
Ok(UWOP_SET_FPREG) => i += 1,
Ok(UWOP_EPILOG) | Ok(UWOP_SPARE_CODE) => i += 1,
Ok(UWOP_PUSH_MACH_FRAME) => {
total_stack += if op_info == 0 { 0x40 } else { 0x48 };
i += 1;
}
_ => {}
}
}
if (flag & UNW_FLAG_CHAININFO) != 0 {
let count = (*unwind_info).CountOfCodes as usize;
let index = if count & 1 == 1 { count + 1 } else { count };
let runtime = unwind_code.add(index) as *const ImageRuntimeFunction;
if let Some(chained_stack) = ignoring_set_fpreg(module, &*runtime) {
total_stack += chained_stack;
} else {
return None;
}
}
Some(total_stack)
}
+116
View File
@@ -0,0 +1,116 @@
use alloc::vec::Vec;
use core::{ffi::c_void, slice};
use crate::types::ImageRuntimeFunction;
use crate::unwind::ignoring_set_fpreg;
/// Scan a function body for `call qword ptr [rip+0]` (48 FF 15 00 00 00 00).
/// Returns the offset *past* the gadget — that's the address the unwinder
/// will treat as the post-call site when the spoofed frame is on the stack.
pub fn find_valid_instruction_offset(
module: *mut c_void,
runtime: &ImageRuntimeFunction,
) -> Option<u32> {
let start = module as u64 + runtime.BeginAddress as u64;
let end = module as u64 + runtime.EndAddress as u64;
let size = end - start;
let pattern = &[0x48u8, 0xFFu8, 0x15u8];
unsafe {
let bytes = slice::from_raw_parts(start as *const u8, size as usize);
memchr::memmem::find(bytes, pattern).map(|pos| (pos + 7) as u32)
}
}
/// Find the first occurrence of `pattern` in any function body of `module`
/// (restricted to ranges from the RUNTIME_FUNCTION table — anything else
/// might be data or alignment padding). Returns the address and the size of
/// the function that contains it.
pub fn find_gadget(
module: *mut c_void,
pattern: &[u8],
runtime_table: &[ImageRuntimeFunction],
) -> Option<(*mut u8, u32)> {
unsafe {
let mut gadgets: Vec<(*mut u8, u32)> = runtime_table
.iter()
.filter_map(|runtime| {
let start = module as u64 + runtime.BeginAddress as u64;
let end = module as u64 + runtime.EndAddress as u64;
let size = end.saturating_sub(start);
let bytes = slice::from_raw_parts(start as *const u8, size as usize);
let pos = memchr::memmem::find(bytes, pattern)?;
let addr = (start as *mut u8).wrapping_add(pos);
let frame_size = ignoring_set_fpreg(module, runtime)?;
if frame_size == 0 {
return None;
}
Some((addr, frame_size))
})
.collect();
if gadgets.is_empty() {
return None;
}
shuffle(&mut gadgets);
gadgets.first().copied()
}
}
/// Fisher-Yates shuffle using rdtsc as a cheap entropy source. Same approach
/// uwd uses — the goal isn't cryptographic randomness, it's reducing the
/// chance that an EDR signature pinned to a specific gadget address survives
/// across runs.
pub fn shuffle<T>(list: &mut [T]) {
let mut seed = unsafe { core::arch::x86_64::_rdtsc() };
for i in (1..list.len()).rev() {
seed = seed.wrapping_mul(1103515245).wrapping_add(12345);
let j = seed as usize % (i + 1);
list.swap(i, j);
}
}
/// Walk the current thread's stack looking for a return address that lands
/// inside the BaseThreadInitThunk function body. Used by desync mode to
/// splice our spoofed frames on top of a legitimately-resident kernel32
/// return record.
///
/// Returns the stack address holding the return, not the return value
/// itself — the asm trampoline rewrites the slot in place.
#[cfg(feature = "desync")]
pub unsafe fn find_base_thread_return_address(
kernel32: *mut c_void,
base_thread_addr: *mut c_void,
base_thread_size: usize,
) -> Option<usize> {
let _ = kernel32;
let base_addr = base_thread_addr as usize;
// Read TEB.StackBase (gs:[0x08]) and TEB.StackLimit (gs:[0x10]) directly
// — same OPSEC reason we use the PEB walker in dyncvoke_core: no
// kernel32/psapi calls, nothing to hook.
let stack_base: usize;
let stack_limit: usize;
core::arch::asm!(
"mov {sb}, gs:[0x08]",
"mov {sl}, gs:[0x10]",
sb = out(reg) stack_base,
sl = out(reg) stack_limit,
options(nostack, preserves_flags, readonly),
);
let mut rsp = stack_base.saturating_sub(8);
while rsp >= stack_limit {
let val = *(rsp as *const usize);
if val >= base_addr && val < base_addr + base_thread_size {
return Some(rsp);
}
rsp = rsp.saturating_sub(8);
if rsp == 0 {
break;
}
}
None
}
+38
View File
@@ -0,0 +1,38 @@
//! Desync-mode spoofed syscall. Only enabled under the desync feature.
//! Verifies the BaseThreadInitThunk return-address splicing path.
#![cfg(feature = "desync")]
use core::ffi::c_void;
use core::ptr;
use spoof::{spoof_syscall, AsPointer};
#[test]
fn alloc_via_desync_syscall() {
let mut addr: *mut c_void = ptr::null_mut();
let mut size: usize = 0x1000;
let status = spoof_syscall!(
"NtAllocateVirtualMemory",
-1isize,
addr.as_ptr_mut(),
0usize,
size.as_ptr_mut(),
0x3000u32,
0x04u32
)
.expect("desync spoof_syscall returned Err") as i32;
assert_eq!(status, 0, "NTSTATUS = 0x{:08X}", status);
assert!(!addr.is_null());
let mut zero_size: usize = 0;
let _ = spoof_syscall!(
"NtFreeVirtualMemory",
-1isize,
addr.as_ptr_mut(),
zero_size.as_ptr_mut(),
0x8000u32
);
}
+9
View File
@@ -0,0 +1,9 @@
//! Verify that obfstr actually obfuscates literals in the final binary.
use data::lc;
#[test]
fn canary_is_obfuscated() {
let decoded = lc!("DYNCVOKE_CANARY_STRING_2026");
assert_eq!(decoded, "DYNCVOKE_CANARY_STRING_2026");
}
+29
View File
@@ -0,0 +1,29 @@
//! Spoofed direct function call. Resolves VirtualAlloc by name, then calls
//! it under the synthetic stack. The returned address is the allocation, not
//! the spoof scaffolding — confirms the asm trampoline isn't corrupting the
//! return value.
use core::ffi::c_void;
use core::ptr;
use spoof::spoof;
#[test]
fn virtual_alloc_via_spoof() {
let kernel32 = dyncvoke_core::get_module_base_address("kernel32.dll");
assert!(kernel32 != 0, "kernel32 not loaded");
let virtual_alloc = dyncvoke_core::get_function_address(kernel32, "VirtualAlloc");
assert!(virtual_alloc != 0, "VirtualAlloc not found");
let addr = spoof!(
virtual_alloc,
ptr::null_mut::<c_void>(),
1usize << 12,
0x3000u32,
0x04u32
)
.expect("spoof returned Err");
assert!(!addr.is_null());
unsafe { *(addr as *mut u8) = 0x42; }
}
+44
View File
@@ -0,0 +1,44 @@
//! Live test of the synthetic-stack spoof path. Allocates 4KB via
//! NtAllocateVirtualMemory through the spoofed trampoline, then frees it.
//! If the asm setup, prologue/gadget discovery, or SSN resolution is wrong
//! this either crashes or returns a non-zero NTSTATUS.
use core::ffi::c_void;
use core::ptr;
use spoof::{spoof_syscall, AsPointer};
#[test]
fn alloc_via_spoofed_syscall() {
let mut addr: *mut c_void = ptr::null_mut();
let mut size: usize = 0x1000;
let status = spoof_syscall!(
"NtAllocateVirtualMemory",
-1isize,
addr.as_ptr_mut(),
0usize,
size.as_ptr_mut(),
0x3000u32,
0x04u32
)
.expect("spoof_syscall returned Err") as i32;
assert_eq!(status, 0, "NtAllocateVirtualMemory NTSTATUS = 0x{:08X}", status);
assert!(!addr.is_null());
// Touch the page so we know it's actually committed.
unsafe { *(addr as *mut u8) = 0x42; }
let mut zero_size: usize = 0;
let status = spoof_syscall!(
"NtFreeVirtualMemory",
-1isize,
addr.as_ptr_mut(),
zero_size.as_ptr_mut(),
0x8000u32
)
.expect("spoof_syscall free returned Err") as i32;
assert_eq!(status, 0, "NtFreeVirtualMemory NTSTATUS = 0x{:08X}", status);
}
+13 -1
View File
@@ -1,6 +1,18 @@
#![no_std]
pub use data;
#[cfg(feature = "syscall")]
pub use dyncvoke_core;
pub use dmanager;
#[cfg(feature = "manualmap")]
pub use manualmap;
#[cfg(feature = "overload")]
pub use overload;
#[cfg(feature = "dmanager")]
pub use dmanager;
#[cfg(feature = "spoof")]
pub use spoof;