From ff0d2cbb21e645bf7ce4e3c82d07a06af94a7c16 Mon Sep 17 00:00:00 2001 From: Whitecat18 Date: Sat, 6 Jun 2026 17:08:16 +0530 Subject: [PATCH] Dyncvoke --- .gitignore | 25 +- Cargo.lock | 74 +- Cargo.toml | 23 +- README.md | 86 ++- data/Cargo.lock | 90 +++ data/Cargo.toml | 1 + data/src/lib.rs | 41 +- dmanager/Cargo.lock | 688 ++++++++++++++++++ dyncvoke_core/Cargo.lock | 112 +++ dyncvoke_core/Cargo.toml | 5 +- dyncvoke_core/src/lib.rs | 351 ++++----- dyncvoke_core/src/peb.rs | 220 ++++++ dyncvoke_core/src/sys/asm.rs | 33 +- dyncvoke_core/src/sys/mod.rs | 24 +- ...ey_derivation.rs => audit_dmanager_xor.rs} | 15 +- ...gate_sweep.rs => audit_hellsgate_sweep.rs} | 16 +- ...andle_value.rs => audit_invalid_handle.rs} | 16 +- ...edure_address.rs => audit_ldr_get_proc.rs} | 18 +- ...e_guards.rs => audit_relocate_no_reloc.rs} | 24 +- ...recovery.rs => audit_tartarus_recovery.rs} | 4 +- ...iadic_arity.rs => audit_variadic_arity.rs} | 15 +- .../tests/smoke_alloc_protect_free.rs | 19 +- dyncvoke_core/tests/sys_do_syscall_basic.rs | 2 +- .../tests/sys_resolve_syscall_basic.rs | 10 +- manualmap/Cargo.lock | 499 +++++++++++++ manualmap/src/lib.rs | 17 +- overload/Cargo.lock | 675 +++++++++++++++++ overload/src/lib.rs | 12 +- spoof/Cargo.lock | 222 ++++++ spoof/Cargo.toml | 27 + spoof/build.rs | 33 + spoof/examples/canary.rs | 7 + spoof/examples/spoofed_alloc.rs | 29 + spoof/src/asm/gnu/desync.asm | 219 ++++++ spoof/src/asm/gnu/synthetic.asm | 247 +++++++ spoof/src/asm/msvc/desync.asm | 225 ++++++ spoof/src/asm/msvc/synthetic.asm | 253 +++++++ spoof/src/lib.rs | 515 +++++++++++++ spoof/src/pe.rs | 64 ++ spoof/src/types.rs | 183 +++++ spoof/src/unwind.rs | 288 ++++++++ spoof/src/util.rs | 116 +++ spoof/tests/desync_syscall.rs | 38 + spoof/tests/obfstr_canary.rs | 9 + spoof/tests/synthetic_function.rs | 29 + spoof/tests/synthetic_syscall.rs | 44 ++ src/lib.rs | 14 +- 47 files changed, 5325 insertions(+), 352 deletions(-) create mode 100644 data/Cargo.lock create mode 100644 dmanager/Cargo.lock create mode 100644 dyncvoke_core/Cargo.lock create mode 100644 dyncvoke_core/src/peb.rs rename dyncvoke_core/tests/{xor_key_derivation.rs => audit_dmanager_xor.rs} (78%) rename dyncvoke_core/tests/{hellsgate_sweep.rs => audit_hellsgate_sweep.rs} (86%) rename dyncvoke_core/tests/{invalid_handle_value.rs => audit_invalid_handle.rs} (73%) rename dyncvoke_core/tests/{ldr_get_procedure_address.rs => audit_ldr_get_proc.rs} (68%) rename dyncvoke_core/tests/{relocate_module_guards.rs => audit_relocate_no_reloc.rs} (83%) rename dyncvoke_core/tests/{tartarus_neighbor_recovery.rs => audit_tartarus_recovery.rs} (96%) rename dyncvoke_core/tests/{variadic_arity.rs => audit_variadic_arity.rs} (89%) create mode 100644 manualmap/Cargo.lock create mode 100644 overload/Cargo.lock create mode 100644 spoof/Cargo.lock create mode 100644 spoof/Cargo.toml create mode 100644 spoof/build.rs create mode 100644 spoof/examples/canary.rs create mode 100644 spoof/examples/spoofed_alloc.rs create mode 100644 spoof/src/asm/gnu/desync.asm create mode 100644 spoof/src/asm/gnu/synthetic.asm create mode 100644 spoof/src/asm/msvc/desync.asm create mode 100644 spoof/src/asm/msvc/synthetic.asm create mode 100644 spoof/src/lib.rs create mode 100644 spoof/src/pe.rs create mode 100644 spoof/src/types.rs create mode 100644 spoof/src/unwind.rs create mode 100644 spoof/src/util.rs create mode 100644 spoof/tests/desync_syscall.rs create mode 100644 spoof/tests/obfstr_canary.rs create mode 100644 spoof/tests/synthetic_function.rs create mode 100644 spoof/tests/synthetic_syscall.rs diff --git a/.gitignore b/.gitignore index 787d1b9..9ae0866 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/Cargo.lock b/Cargo.lock index 533f3f6..9b423cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 63f5fb7..7dd1f01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] diff --git a/README.md b/README.md index a3dd11a..3336382 100644 --- a/README.md +++ b/README.md @@ -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::(), + 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) \ No newline at end of file diff --git a/data/Cargo.lock b/data/Cargo.lock new file mode 100644 index 0000000..6066b7d --- /dev/null +++ b/data/Cargo.lock @@ -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" diff --git a/data/Cargo.toml b/data/Cargo.toml index f3f92fa..6f57d06 100644 --- a/data/Cargo.toml +++ b/data/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" strip = true [dependencies] +obfstr = "0.4" [dependencies.windows-sys] version = "0.59" diff --git a/data/src/lib.rs b/data/src/lib.rs index d057d83..e1b81f7 100644 --- a/data/src/lib.rs +++ b/data/src/lib.rs @@ -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()); } } diff --git a/dmanager/Cargo.lock b/dmanager/Cargo.lock new file mode 100644 index 0000000..cf5fc38 --- /dev/null +++ b/dmanager/Cargo.lock @@ -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", +] diff --git a/dyncvoke_core/Cargo.lock b/dyncvoke_core/Cargo.lock new file mode 100644 index 0000000..cb87a18 --- /dev/null +++ b/dyncvoke_core/Cargo.lock @@ -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" diff --git a/dyncvoke_core/Cargo.toml b/dyncvoke_core/Cargo.toml index f538adf..cf1da66 100644 --- a/dyncvoke_core/Cargo.toml +++ b/dyncvoke_core/Cargo.toml @@ -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" diff --git a/dyncvoke_core/src/lib.rs b/dyncvoke_core/src/lib.rs index 558e072..f59a87f 100644 --- a/dyncvoke_core/src/lib.rs +++ b/dyncvoke_core/src/lib.rs @@ -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, u32> { - unsafe { - let mut mod_handles: Vec = 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, 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 { - 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 { - 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 "_" or "." 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 { +pub fn get_api_mapping() -> BTreeMap { 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 { ); 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 { api_set_map_offset = 0x68; } - let mut api_set_dict: HashMap = HashMap::new(); + let mut api_set_dict: BTreeMap = 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::(api_set_namespace_ptr); + core::ptr::with_exposed_provenance_mut::(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 { 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::(); - 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; let func_ptr: data::LdrGetProcedureAddress; - let hmodule: PVOID = std::ptr::with_exposed_provenance_mut::(module_handle); + let hmodule: PVOID = core::ptr::with_exposed_provenance_mut::(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; 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; + 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; 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; + let _decoy_buf: Vec; 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; -/// 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,,Option,[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::(), std::mem::size_of::<*mut std::ffi::c_void>()); - assert_eq!(std::mem::size_of::(), std::mem::size_of::<*mut std::ffi::c_void>()); + assert_eq!(core::mem::size_of::(), core::mem::size_of::<*mut core::ffi::c_void>()); + assert_eq!(core::mem::size_of::(), core::mem::size_of::<*mut core::ffi::c_void>()); } } diff --git a/dyncvoke_core/src/peb.rs b/dyncvoke_core/src/peb.rs new file mode 100644 index 0000000..b6fad54 --- /dev/null +++ b/dyncvoke_core/src/peb.rs @@ -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(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) } +} diff --git a/dyncvoke_core/src/sys/asm.rs b/dyncvoke_core/src/sys/asm.rs index 909f4e8..ae00871 100644 --- a/dyncvoke_core/src/sys/asm.rs +++ b/dyncvoke_core/src/sys/asm.rs @@ -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; } diff --git a/dyncvoke_core/src/sys/mod.rs b/dyncvoke_core/src/sys/mod.rs index d4bdea2..8348ba8 100644 --- a/dyncvoke_core/src/sys/mod.rs +++ b/dyncvoke_core/src/sys/mod.rs @@ -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, ` +//! 1. **Hell's Gate** — stub has the clean `MOV R10, RCX; MOV EAX, ` //! 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 { diff --git a/dyncvoke_core/tests/xor_key_derivation.rs b/dyncvoke_core/tests/audit_dmanager_xor.rs similarity index 78% rename from dyncvoke_core/tests/xor_key_derivation.rs rename to dyncvoke_core/tests/audit_dmanager_xor.rs index 89d67bc..71b1ab0 100644 --- a/dyncvoke_core/tests/xor_key_derivation.rs +++ b/dyncvoke_core/tests/audit_dmanager_xor.rs @@ -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 diff --git a/dyncvoke_core/tests/hellsgate_sweep.rs b/dyncvoke_core/tests/audit_hellsgate_sweep.rs similarity index 86% rename from dyncvoke_core/tests/hellsgate_sweep.rs rename to dyncvoke_core/tests/audit_hellsgate_sweep.rs index 362ba47..213fe33 100644 --- a/dyncvoke_core/tests/hellsgate_sweep.rs +++ b/dyncvoke_core/tests/audit_hellsgate_sweep.rs @@ -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 // 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 { 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)); } } diff --git a/dyncvoke_core/tests/invalid_handle_value.rs b/dyncvoke_core/tests/audit_invalid_handle.rs similarity index 73% rename from dyncvoke_core/tests/invalid_handle_value.rs rename to dyncvoke_core/tests/audit_invalid_handle.rs index 3bed431..a16b83f 100644 --- a/dyncvoke_core/tests/invalid_handle_value.rs +++ b/dyncvoke_core/tests/audit_invalid_handle.rs @@ -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; diff --git a/dyncvoke_core/tests/ldr_get_procedure_address.rs b/dyncvoke_core/tests/audit_ldr_get_proc.rs similarity index 68% rename from dyncvoke_core/tests/ldr_get_procedure_address.rs rename to dyncvoke_core/tests/audit_ldr_get_proc.rs index fdf751a..486955b 100644 --- a/dyncvoke_core/tests/ldr_get_procedure_address.rs +++ b/dyncvoke_core/tests/audit_ldr_get_proc.rs @@ -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 diff --git a/dyncvoke_core/tests/relocate_module_guards.rs b/dyncvoke_core/tests/audit_relocate_no_reloc.rs similarity index 83% rename from dyncvoke_core/tests/relocate_module_guards.rs rename to dyncvoke_core/tests/audit_relocate_no_reloc.rs index d21b95a..483d8ce 100644 --- a/dyncvoke_core/tests/relocate_module_guards.rs +++ b/dyncvoke_core/tests/audit_relocate_no_reloc.rs @@ -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 = 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; diff --git a/dyncvoke_core/tests/tartarus_neighbor_recovery.rs b/dyncvoke_core/tests/audit_tartarus_recovery.rs similarity index 96% rename from dyncvoke_core/tests/tartarus_neighbor_recovery.rs rename to dyncvoke_core/tests/audit_tartarus_recovery.rs index 23f78a9..6b35b6e 100644 --- a/dyncvoke_core/tests/tartarus_neighbor_recovery.rs +++ b/dyncvoke_core/tests/audit_tartarus_recovery.rs @@ -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) }; diff --git a/dyncvoke_core/tests/variadic_arity.rs b/dyncvoke_core/tests/audit_variadic_arity.rs similarity index 89% rename from dyncvoke_core/tests/variadic_arity.rs rename to dyncvoke_core/tests/audit_variadic_arity.rs index f22d39c..084ae2b 100644 --- a/dyncvoke_core/tests/variadic_arity.rs +++ b/dyncvoke_core/tests/audit_variadic_arity.rs @@ -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); diff --git a/dyncvoke_core/tests/smoke_alloc_protect_free.rs b/dyncvoke_core/tests/smoke_alloc_protect_free.rs index 8842a10..3a724f0 100644 --- a/dyncvoke_core/tests/smoke_alloc_protect_free.rs +++ b/dyncvoke_core/tests/smoke_alloc_protect_free.rs @@ -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(); diff --git a/dyncvoke_core/tests/sys_do_syscall_basic.rs b/dyncvoke_core/tests/sys_do_syscall_basic.rs index 2781dd7..2793f06 100644 --- a/dyncvoke_core/tests/sys_do_syscall_basic.rs +++ b/dyncvoke_core/tests/sys_do_syscall_basic.rs @@ -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; diff --git a/dyncvoke_core/tests/sys_resolve_syscall_basic.rs b/dyncvoke_core/tests/sys_resolve_syscall_basic.rs index fc47de6..5cddb8c 100644 --- a/dyncvoke_core/tests/sys_resolve_syscall_basic.rs +++ b/dyncvoke_core/tests/sys_resolve_syscall_basic.rs @@ -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), diff --git a/manualmap/Cargo.lock b/manualmap/Cargo.lock new file mode 100644 index 0000000..a314f17 --- /dev/null +++ b/manualmap/Cargo.lock @@ -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", +] diff --git a/manualmap/src/lib.rs b/manualmap/src/lib.rs index 79b976c..350b98d 100644 --- a/manualmap/src/lib.rs +++ b/manualmap/src/lib.rs @@ -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::() @@ -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 = HashMap::new(); + let mut api_set_dict: std::collections::BTreeMap = + std::collections::BTreeMap::new(); if version >= "10".to_string() { api_set_dict = dyncvoke_core::get_api_mapping(); } diff --git a/overload/Cargo.lock b/overload/Cargo.lock new file mode 100644 index 0000000..df68d1e --- /dev/null +++ b/overload/Cargo.lock @@ -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", +] diff --git a/overload/src/lib.rs b/overload/src/lib.rs index 6907edf..c7835f7 100644 --- a/overload/src/lib.rs +++ b/overload/src/lib.rs @@ -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::() @@ -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 diff --git a/spoof/Cargo.lock b/spoof/Cargo.lock new file mode 100644 index 0000000..f53d2f8 --- /dev/null +++ b/spoof/Cargo.lock @@ -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" diff --git a/spoof/Cargo.toml b/spoof/Cargo.toml new file mode 100644 index 0000000..1545b8b --- /dev/null +++ b/spoof/Cargo.toml @@ -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 diff --git a/spoof/build.rs b/spoof/build.rs new file mode 100644 index 0000000..492e180 --- /dev/null +++ b/spoof/build.rs @@ -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); + } +} diff --git a/spoof/examples/canary.rs b/spoof/examples/canary.rs new file mode 100644 index 0000000..5d3f4ff --- /dev/null +++ b/spoof/examples/canary.rs @@ -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); +} diff --git a/spoof/examples/spoofed_alloc.rs b/spoof/examples/spoofed_alloc.rs new file mode 100644 index 0000000..a723584 --- /dev/null +++ b/spoof/examples/spoofed_alloc.rs @@ -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); +} diff --git a/spoof/src/asm/gnu/desync.asm b/spoof/src/asm/gnu/desync.asm new file mode 100644 index 0000000..9a65400 --- /dev/null +++ b/spoof/src/asm/gnu/desync.asm @@ -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 \ No newline at end of file diff --git a/spoof/src/asm/gnu/synthetic.asm b/spoof/src/asm/gnu/synthetic.asm new file mode 100644 index 0000000..58b7b35 --- /dev/null +++ b/spoof/src/asm/gnu/synthetic.asm @@ -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 \ No newline at end of file diff --git a/spoof/src/asm/msvc/desync.asm b/spoof/src/asm/msvc/desync.asm new file mode 100644 index 0000000..45efcc1 --- /dev/null +++ b/spoof/src/asm/msvc/desync.asm @@ -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 \ No newline at end of file diff --git a/spoof/src/asm/msvc/synthetic.asm b/spoof/src/asm/msvc/synthetic.asm new file mode 100644 index 0000000..136e10c --- /dev/null +++ b/spoof/src/asm/msvc/synthetic.asm @@ -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 \ No newline at end of file diff --git a/spoof/src/lib.rs b/spoof/src/lib.rs new file mode 100644 index 0000000..34faad6 --- /dev/null +++ b/spoof/src/lib.rs @@ -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::(), +/// 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::(); +/// 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 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 { + let mut prologs: Vec = 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 { + let mut prologs: Vec = 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() +} diff --git a/spoof/src/pe.rs b/spoof/src/pe.rs new file mode 100644 index 0000000..df80385 --- /dev/null +++ b/spoof/src/pe.rs @@ -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::()) + 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::(); + + 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) +} diff --git a/spoof/src/types.rs b/spoof/src/types.rs new file mode 100644 index 0000000..f35e484 --- /dev/null +++ b/spoof/src/types.rs @@ -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 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 for UnwindOpCode { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 0..=10 => Ok(unsafe { core::mem::transmute::(value) }), + _ => Err(()), + } + } +} diff --git a/spoof/src/unwind.rs b/spoof/src/unwind.rs new file mode 100644 index 0000000..5362bee --- /dev/null +++ b/spoof/src/unwind.rs @@ -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 { + 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) +} diff --git a/spoof/src/util.rs b/spoof/src/util.rs new file mode 100644 index 0000000..dba30bd --- /dev/null +++ b/spoof/src/util.rs @@ -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 { + 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(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 { + 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 +} diff --git a/spoof/tests/desync_syscall.rs b/spoof/tests/desync_syscall.rs new file mode 100644 index 0000000..f9b2177 --- /dev/null +++ b/spoof/tests/desync_syscall.rs @@ -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 + ); +} diff --git a/spoof/tests/obfstr_canary.rs b/spoof/tests/obfstr_canary.rs new file mode 100644 index 0000000..aeb6f60 --- /dev/null +++ b/spoof/tests/obfstr_canary.rs @@ -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"); +} diff --git a/spoof/tests/synthetic_function.rs b/spoof/tests/synthetic_function.rs new file mode 100644 index 0000000..d1af713 --- /dev/null +++ b/spoof/tests/synthetic_function.rs @@ -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::(), + 1usize << 12, + 0x3000u32, + 0x04u32 + ) + .expect("spoof returned Err"); + + assert!(!addr.is_null()); + unsafe { *(addr as *mut u8) = 0x42; } +} diff --git a/spoof/tests/synthetic_syscall.rs b/spoof/tests/synthetic_syscall.rs new file mode 100644 index 0000000..719846f --- /dev/null +++ b/spoof/tests/synthetic_syscall.rs @@ -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); +} diff --git a/src/lib.rs b/src/lib.rs index e95b786..c0870bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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;