Full Code of joaoviictorti/uwd for AI

main 6b39d40843d9 cached
22 files
86.4 KB
23.1k tokens
38 symbols
1 requests
Download .txt
Repository: joaoviictorti/uwd
Branch: main
Commit: 6b39d40843d9
Files: 22
Total size: 86.4 KB

Directory structure:
gitextract_me8cjxhe/

├── .gitattributes
├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .vscode/
│   └── settings.json
├── Cargo.toml
├── Justfile
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── build.rs
├── clippy.toml
├── rust-toolchain.toml
├── rustfmt.toml
├── src/
│   ├── asm/
│   │   ├── gnu/
│   │   │   ├── desync.asm
│   │   │   └── synthetic.asm
│   │   └── msvc/
│   │       ├── desync.asm
│   │       └── synthetic.asm
│   ├── lib.rs
│   ├── types.rs
│   ├── util.rs
│   └── uwd.rs
└── taplo.toml

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
* text=auto eol=lf

================================================
FILE: .github/workflows/ci.yml
================================================
name: build

on: [push, pull_request]

jobs:
  clippy:
    # Runs Clippy to check for lints in the Rust code
    name: Clippy Lint Check
    runs-on: windows-latest

    steps:
      - uses: actions/checkout@v4

      - name: Set up Rust
        run: |
          rustup default stable
          rustup component add clippy

      - name: Run Clippy
        run: cargo clippy -- -D warnings

  doc:
    # Builds project documentation, including private items
    name: Docs Check
    runs-on: windows-latest

    steps:
      - uses: actions/checkout@v4

      - name: Check docs
        run: cargo doc --no-deps --document-private-items

================================================
FILE: .gitignore
================================================
# Generated by Cargo
# will have compiled files and executables
debug/
target/

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
examples/Cargo.lock
tests/Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk

# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb


# RustRover
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

================================================
FILE: .vscode/settings.json
================================================
{
    "rust-analyzer.linkedProjects": [
        "Cargo.toml"
    ],
}

================================================
FILE: Cargo.toml
================================================
[package]
name = "uwd"
version = "0.3.5"
edition = "2024"
description = "Call Stack Spoofing for Rust"
license = "MIT OR Apache-2.0"
repository = "https://github.com/joaoviictorti/uwd"
homepage = "https://github.com/joaoviictorti/uwd"
readme = "README.md"
build = "build.rs"
keywords = ["spoofing", "stack", "windows", "rust", "redteam"]
categories = ["os", "security"]
include = [
    "src/**",
    "build.rs",
    "Cargo.toml",
    "README.md",
    "LICENSE",
]

[lib]
doctest = false

[dependencies]
bitfield = "0.19.0"
obfstr = "0.4.4"
dinvk = "0.4.2"
anyhow = { version = "1.0.98", default-features = false }
memchr = { version = "2.7.4", default-features = false }

[build-dependencies]
cc = "1.2.19"
nasm-rs = "0.3.0"

[features]
default = []
desync = []

[package.metadata.docs.rs]
default-target = "x86_64-pc-windows-msvc"
targets = ["x86_64-pc-windows-gnu", "x86_64-pc-windows-msvc"]


================================================
FILE: Justfile
================================================
# Aliases
alias c := clean
alias up := update

# Use PowerShell shell on Windows
set windows-shell := ["powershell.exe", "-NoLogo", "-Command"]

# Clean target
clean:
    cargo clean

# Updates dependencies as per Cargo.toml
update:
    cargo update

# Publishes the crate to crates.io
publish:
    cargo publish --allow-dirty

# Format all .toml files using Taplo
taplo:
    taplo format

================================================
FILE: LICENSE-APACHE
================================================

                                 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.

================================================
FILE: LICENSE-MIT
================================================
MIT License

Copyright (c) 2025 Victor

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

================================================
FILE: README.md
================================================
# uwd

![Rust](https://img.shields.io/badge/made%20with-Rust-red)
![crate](https://img.shields.io/crates/v/uwd.svg)
![docs](https://docs.rs/uwd/badge.svg)
![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-brightgreen)
[![Actions status](https://github.com/joaoviictorti/uwd/actions/workflows/ci.yml/badge.svg)](https://github.com/joaoviictorti/uwd/actions)

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.

## Features

- ✅ Call stack spoofing via `Synthetic` and `Desync`.
- ✅ Compatible with both `MSVC` and `GNU` toolchains (**x64**).
- ✅ Inline macros: `spoof!` / `syscall!`.
- ✅ Supports `#[no_std]` environments (with `alloc`).

To enable Desync mode, activate the `desync` feature in your project, the macros will automatically use Desync behavior when the feature is enabled.

## Getting started

Add `uwd` to your project by updating your `Cargo.toml`:
```bash
cargo add uwd
```

## Usage

`uwd` allows you to spoof the call stack in Rust when calling either standard Windows APIs or performing indirect syscalls. The library handles the full setup of fake frames, gadget chains, and register preparation to make execution appear as if it came from a legitimate source.

You can spoof:

* Normal functions (like `VirtualAlloc`, `WinExec`, etc.)
* Native syscalls with automatic SSN and stub resolution (like `NtAllocateVirtualMemory`)

### Spoofing WinExec

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::module::{get_module_address, get_proc_address};
use uwd::spoof;

// Resolves addresses of the WinAPI functions to be used
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";
let mut result = spoof!(win_exec, cmd.as_ptr(), 1)?;
if result.is_null() {
    eprintln!("WinExec Failed");
    return Ok(());
}
```

### Spoofing an Indirect Syscall

This example performs a indirect system call to `NtAllocateVirtualMemory` with a spoofed call stack.

```rust
use std::{ffi::c_void, ptr::null_mut};
use dinvk::winapis::NT_SUCCESS;
use uwd::{syscall, AsPointer};

// Running indirect syscall with Call Stack Spoofing
let mut addr = null_mut::<c_void>();
let mut size = (1 << 12) as usize;
let mut status = syscall!("NtAllocateVirtualMemory", -1isize, addr.as_ptr_mut(), 0, size.as_ptr_mut(), 0x3000, 0x04)? as i32;
if !NT_SUCCESS(status) {
    eprintln!("[-] NtAllocateVirtualMemory Failed With Status: {status:#X}");
    return Ok(())
}

println!("[+] Address allocated: {:?}", addr);
```

## References

I want to express my gratitude to these projects that inspired me to create `uwd` and contribute with some features:

- [SilentMoonwalk](https://github.com/klezVirus/SilentMoonwalk)

Special thanks to:

- [Kudaes](https://x.com/_Kudaes_)
- [Klez](https://x.com/KlezVirus)
- [Waldo-IRC](https://x.com/waldoirc)
- [Trickster0](https://x.com/trickster012)
- [namazso](https://x.com/namazso)

## License

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.

================================================
FILE: build.rs
================================================
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 environment variable");
    let out_dir = env::var("OUT_DIR").expect("Missing OUT_DIR environment variable");

    // Supports x86_64 environments only
    if !target.contains("x86_64") {
        panic!("This build script only supports x86_64 targets.");
    }

    if target.contains("msvc") {
        // Use MASM with cc
        cc::Build::new()
            .file("src/asm/msvc/desync.asm")
            .file("src/asm/msvc/synthetic.asm")
            .compile("spoof");
    } else if target.contains("gnu") {
        // Use NASM with nasm_rs
        let sources = ["src/asm/gnu/desync.asm", "src/asm/gnu/synthetic.asm"];
        if let Err(e) = nasm_rs::compile_library("spoof", &sources) {
            panic!("Failed to compile with NASM [spoof]: {}", e);
        }

        for source in &sources {
            println!("cargo:rerun-if-changed={}", source);
        }

        println!("cargo:rustc-link-search=native={}", out_dir);
        println!("cargo:rustc-link-lib=static=spoof");
    } else {
        panic!("Unsupported target: {}", target);
    }
}


================================================
FILE: clippy.toml
================================================
msrv = "1.89"

================================================
FILE: rust-toolchain.toml
================================================
[toolchain]
channel = "stable"
targets = ["x86_64-pc-windows-msvc"]


================================================
FILE: rustfmt.toml
================================================
# Uses Rustfmt 2024 edition rules, but not all of them are enforced 100%.
edition = "2024"
style_edition = "2024"


================================================
FILE: src/asm/gnu/desync.asm
================================================
;;
;; 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

================================================
FILE: src/asm/gnu/synthetic.asm
================================================
;;
;; 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

================================================
FILE: src/asm/msvc/desync.asm
================================================
;;
;; 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

================================================
FILE: src/asm/msvc/synthetic.asm
================================================
;;
;; 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

================================================
FILE: src/lib.rs
================================================
#![no_std]
#![doc = include_str!("../README.md")]

extern crate alloc;

mod types;
mod uwd;
mod util;

pub use uwd::*;


================================================
FILE: src/types.rs
================================================
#![allow(non_snake_case, non_camel_case_types)]

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;

/// 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 }
    }

    /// 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)]
pub struct Config {
    /// Address RtlUserThreadStart
    pub rtl_user_addr: *const c_void,

    /// Stack Size RtlUserThreadStart
    pub rtl_user_thread_size: u64,

    /// Address BaseThreadInitThunk
    pub base_thread_addr: *const c_void,

    /// Stack Size BaseThreadInitThunk
    pub base_thread_size: u64,

    /// First (fake) return address frame
    pub first_frame_fp: *const c_void,

    /// Second (ROP) return address frame
    pub second_frame_fp: *const c_void,

    /// Gadget: `jmp [rbx]`
    pub jmp_rbx_gadget: *const c_void,

    /// Gadget: `add rsp, X; ret`
    pub add_rsp_gadget: *const c_void,

    /// Stack size of first spoofed frame
    pub first_frame_size: u64,

    /// Stack size of second spoofed frame
    pub second_frame_size: u64,

    /// Stack frame size where the `jmp [rbx]` gadget resides
    pub jmp_rbx_frame_size: u64,

    /// Stack frame size where the `add rsp, X` gadget resides
    pub add_rsp_frame_size: u64,

    /// Offset on the stack where `rbp` is pushed
    pub rbp_stack_offset: u64,

    /// The function to be spoofed / called
    pub spoof_function: *const c_void,

    /// Return address (used as stack-resume point after call)
    pub return_address: *const c_void,

    /// Checks if the target is a syscall
    pub is_syscall: u32,

    /// System Service Number (SSN)
    pub ssn: u32,

    /// Arguments that will be passed to the function that will be spoofed
    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() }
    }
}

/// Enumeration of x86_64 general-purpose registers.
///
/// Used in unwind parsing or register mapping logic.
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
#[allow(dead_code)]
pub enum Registers {
    Rax = 0,
    Rcx,
    Rdx,
    Rbx,
    Rsp,
    Rbp,
    Rsi,
    Rdi,
    R8,
    R9,
    R10,
    R11,
    R12,
    R13,
    R14,
    R15,
}

impl PartialEq<usize> for Registers {
    fn eq(&self, other: &usize) -> bool {
        *self as usize == *other
    }
}

/// Union representing a single unwind operation code.
#[repr(C)]
pub union UNWIND_CODE {
    /// Offset into the stack frame for the operation.
    pub FrameOffset: u16,

    /// Structured fields of the unwind code.
    pub Anonymous: UNWIND_CODE_0,
}

bitfield::bitfield! {
    /// Bitfield representation of an unwind code entry.
    #[repr(C)]
    #[derive(Debug, Clone, Copy)]
    pub struct UNWIND_CODE_0(u16);

    /// Byte offset from the start of the prologue where this operation is applied.
    pub u8, CodeOffset, SetCodeOffset: 7, 0;

    /// The unwind operation code.
    pub u8, UnwindOp, SetUnwindOp: 11, 8;

    /// Additional operation-specific information.
    pub u8, OpInfo, SetOpInfo: 15, 12;
}

/// Union representing optional exception handler or chained function entry.
#[repr(C)]
pub union UNWIND_INFO_0 {
    /// Address of the exception handler (RVA).
    pub ExceptionHandler: u32,

    /// Address of a chained function entry.
    pub FunctionEntry: u32,
}

bitfield::bitfield! {
    /// Combines the `Version` and `Flags` fields in a compact format.
    #[repr(C)]
    #[derive(Debug, Clone, Copy)]
    pub struct UNWIND_VERSION_FLAGS(u8);

    /// Unwind info format version.
    pub u8, Version, SetVersion: 2, 0;

    /// Unwind flags.
    pub u8, Flags, SetFlags: 7, 3;
}

bitfield::bitfield! {
    /// Compact representation of frame register and offset fields.
    #[repr(C)]
    #[derive(Debug, Clone, Copy)]
    pub struct UNWIND_FRAME_INFO(u8);

    /// The register used as the frame pointer.
    pub u8, FrameRegister, SetFrameRegister: 3, 0;

    /// Offset from the stack pointer to the frame pointer.
    pub u8, FrameOffset, SetFrameOffset: 7, 4;
}

/// Structure containing the unwind information of a function.
#[repr(C)]
pub struct UNWIND_INFO {
    /// Separate structure containing `Version` and `Flags`.
    pub VersionFlags: UNWIND_VERSION_FLAGS,

    /// Size of the function prologue in bytes.
    pub SizeOfProlog: u8,

    /// Number of non-array `UnwindCode` entries.
    pub CountOfCodes: u8,

    /// Separate structure containing `FrameRegister` and `FrameOffset`.
    pub FrameInfo: UNWIND_FRAME_INFO,

    /// Array of unwind codes describing specific operations.
    pub UnwindCode: UNWIND_CODE,

    /// Union containing `ExceptionHandler` or `FunctionEntry`.
    pub Anonymous: UNWIND_INFO_0,

    /// Optional exception data.
    pub ExceptionData: u32,
}

/// Unwind operation codes used by the Windows x64 exception handling model.
///
/// For full details, refer to Microsoft documentation:
/// https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64
#[repr(u8)]
#[allow(dead_code)]
pub enum UNWIND_OP_CODES {
    UWOP_PUSH_NONVOL = 0,
    UWOP_ALLOC_LARGE = 1,
    UWOP_ALLOC_SMALL = 2,
    UWOP_SET_FPREG = 3,
    UWOP_SAVE_NONVOL = 4,
    UWOP_SAVE_NONVOL_BIG = 5,
    UWOP_EPILOG = 6,
    UWOP_SPARE_CODE = 7,
    UWOP_SAVE_XMM128 = 8,
    UWOP_SAVE_XMM128BIG = 9,
    UWOP_PUSH_MACH_FRAME = 10,
}

impl TryFrom<u8> for UNWIND_OP_CODES {
    type Error = ();

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0..=10 => Ok(unsafe { core::mem::transmute::<u8, UNWIND_OP_CODES>(value) }),
            _ => Err(()),
        }
    }
}

================================================
FILE: src/util.rs
================================================
use core::{ffi::c_void, slice::from_raw_parts};
use alloc::vec::Vec;

use obfstr::obfbytes as b;
use dinvk::types::IMAGE_RUNTIME_FUNCTION;

use crate::ignoring_set_fpreg;

/// Searches for a valid instruction offset in a function.
///
/// 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 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;

    // Find a gadget `call qword ptr [rip+0]`
    let pattern = b!(&[0x48, 0xFF, 0x15]);
    unsafe {
        let bytes = from_raw_parts(start as *const u8, size as usize);
        if let Some(pos) = memchr::memmem::find(bytes, pattern) {
            // Returns valid RVA: offset of the gadget inside the function
            return Some((pos + 7) as u32);
        }
    }

    None
}

/// 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], 
    runtime_table: &[IMAGE_RUNTIME_FUNCTION]
) -> Option<(*mut u8, u32)> {
    unsafe {
        let mut gadgets = 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);

                // Read bytes from the function's code region
                let bytes = 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::<Vec<(*mut u8, u32)>>();

        if gadgets.is_empty() {
            return None;
        }

        // Shuffle to reduce pattern predictability.
        shuffle(&mut gadgets);

        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`.
#[cfg(feature = "desync")]
pub fn find_base_thread_return_address() -> Option<usize> {
    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 = get_module_address(2808682670u32, Some(murmur3));
        if kernel32.is_null() {
            return None;
        }

        // Resolves the address of the BaseThreadInitThunk function
        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 = Unwind::new(PE::parse(kernel32));
        let size = pe_kernel32.function_size(base_thread)? as usize;

        // Access the TEB and stack limits
        let teb = dinvk::winapis::NtCurrentTeb();
        let stack_base = (*teb).Reserved1[1] as usize;
        let stack_limit = (*teb).Reserved1[2] as usize;

        // Stack scanning begins
        let base_addr = base_thread as usize;
        let mut rsp = stack_base - 8;
        while rsp >= stack_limit {
            let val = (rsp as *const usize).read();

            // Checks if the return is in the BaseThreadInitThunk range
            if val >= base_addr && val < base_addr + size {
                return Some(rsp);
            }

            rsp -= 8;
        }

        None
    }
}

/// 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() {
        seed = seed.wrapping_mul(1103515245).wrapping_add(12345);
        let j = seed as usize % (i + 1);
        list.swap(i, j);
    }
}


================================================
FILE: src/uwd.rs
================================================
use alloc::{string::String, vec::Vec};
use core::ffi::c_void;

use anyhow::{Context, Result, bail};
use obfstr::obfstring as s;
use dinvk::module::{get_module_address, get_proc_address};
use dinvk::types::IMAGE_RUNTIME_FUNCTION;
use dinvk::hash::murmur3;
use dinvk::helper::PE;

#[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" {
    /// Function responsible for Call Stack Spoofing (Desync)
    fn Spoof(config: &mut Config) -> *mut c_void;
}

#[cfg(not(feature = "desync"))]
unsafe extern "C" {
    /// Function responsible for Call Stack Spoofing (Synthetic)
    fn SpoofSynthetic(config: &mut Config) -> *mut c_void;
}

/// Invokes a function using a synthetic spoofed call stack.
///
/// # Examples
///
/// ```
/// 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::__private::spoof(
                $addr,
                &[$(::core::mem::transmute($arg as usize)),*],
                $crate::SpoofKind::Function,
            )
        }
    };
}

/// Invokes a Windows native syscall using a spoofed stack.
///
/// # Examples
/// 
/// ```
/// use core::ptr;
/// use uwd::{AsPointer, syscall};
///
/// 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::__private::spoof(
                core::ptr::null_mut(),
                &[$(::core::mem::transmute($arg as usize)),*],
                $crate::SpoofKind::Syscall($name),
            )
        }
    };
}

#[doc(hidden)]
pub mod __private {
    use core::ffi::c_void;
    use super::*;

    /// Performs call stack spoofing in `synthetic` mode.
    #[cfg(not(feature = "desync"))]
    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"));
        }

        // Function pointer must be valid unless syscall spoof
        if let SpoofKind::Function = kind && addr.is_null() {
            bail!(s!("null function address"));
        }

        let mut config = Config::default();

        // Resolve kernelbase
        let kernelbase = get_module_address(2737729883u32, Some(murmur3));

        // 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"
            ))?;

        // Resolve APIs
        let ntdll = get_module_address(2788516083u32, Some(murmur3));
        if ntdll.is_null() {
            bail!(s!("ntdll.dll not found"));
        }

        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;

        // Unwind lookup
        let pe_ntdll = Unwind::new(PE::parse(ntdll));
        let rtl_user_runtime = pe_ntdll
            .function_by_offset(rlt_user_addr as u32 - ntdll as u32)
            .context(s!("RtlUserThreadStart unwind info not found"))?;

        let pe_kernel32 = Unwind::new(PE::parse(kernel32));
        let base_thread_runtime = pe_kernel32
            .function_by_offset(base_thread_addr as u32 - kernel32 as u32)
            .context(s!("BaseThreadInitThunk unwind info not found"))?;

        // Stack sizes
        let rtl_user_size = ignoring_set_fpreg(ntdll, rtl_user_runtime)
            .context(s!("RtlUserThreadStart stack size not found"))?;
        
        let base_thread_size = ignoring_set_fpreg(kernel32, base_thread_runtime)
            .context(s!("BaseThreadInitThunk stack size not found"))?;

        config.rtl_user_thread_size = rtl_user_size as u64;
        config.base_thread_size = base_thread_size as u64;

        // 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 prologue
        let second_prolog = Prolog::find_push_rbp(kernelbase, tables)
            .context(s!("second prolog not found"))?;
        
        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;

        // 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;

        // 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;

        // Prepare arguments
        let len = args.len();
        config.number_args = len as u64;
        
        for (i, &arg) in args.iter().take(len).enumerate() {
            match i {
                0 => config.arg01 = arg,
                1 => config.arg02 = arg,
                2 => config.arg03 = arg,
                3 => config.arg04 = arg,
                4 => config.arg05 = arg,
                5 => config.arg06 = arg,
                6 => config.arg07 = arg,
                7 => config.arg08 = arg,
                8 => config.arg09 = arg,
                9 => config.arg10 = arg,
                10 => config.arg11 = arg,
                _ => break,
            }
        }

        // Handle syscall spoofing
        match kind {
            SpoofKind::Function => config.spoof_function = addr,
            SpoofKind::Syscall(name) => {
                let addr = get_proc_address(ntdll, name, None);
                if addr.is_null() {
                    bail!(s!("get_proc_address returned null"));
                }

                config.is_syscall = true as u32;
                config.ssn = dinvk::ssn(name, ntdll).context(s!("ssn not found"))?.into();
                config.spoof_function = dinvk::get_syscall_address(addr)
                    .context(s!("syscall address not found"))? as *const c_void;
            }
        }

        Ok(unsafe { SpoofSynthetic(&mut config) })
    }

    /// Performs call stack spoofing in `desync` mode.
    #[cfg(feature = "desync")]
    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"));
        }

        // Function pointer must be valid unless syscall spoof
        if let SpoofKind::Function = kind && addr.is_null() {
            bail!(s!("null function address"));
        }

        let mut config = Config::default();

        // Resolve kernelbase
        let kernelbase = get_module_address(2737729883u32, Some(murmur3));

        // 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 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 prologue
        let second_prolog = Prolog::find_push_rbp(kernelbase, tables)
            .context(s!("second prolog not found"))?;
        
        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;

        // 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;

        // 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;

        // Prepare arguments
        let len = args.len();
        config.number_args = len as u64;
        
        for (i, &arg) in args.iter().take(len).enumerate() {
            match i {
                0 => config.arg01 = arg,
                1 => config.arg02 = arg,
                2 => config.arg03 = arg,
                3 => config.arg04 = arg,
                4 => config.arg05 = arg,
                5 => config.arg06 = arg,
                6 => config.arg07 = arg,
                7 => config.arg08 = arg,
                8 => config.arg09 = arg,
                9 => config.arg10 = arg,
                10 => config.arg11 = arg,
                _ => break,
            }
        }

        // Handle syscall spoofing
        match kind {
            SpoofKind::Function => config.spoof_function = addr,
            SpoofKind::Syscall(name) => {
                let ntdll = get_module_address(2788516083u32, Some(murmur3));
                if ntdll.is_null() {
                    bail!(s!("ntdll.dll not found"));
                }

                let addr = get_proc_address(ntdll, name, None);
                if addr.is_null() {
                    bail!(s!("get_proc_address returned null"));
                }

                config.is_syscall = true as u32;
                config.ssn = dinvk::ssn(name, ntdll).context(s!("ssn not found"))?.into();
                config.spoof_function = dinvk::get_syscall_address(addr)
                    .context(s!("syscall address not found"))? as *const c_void;
            }
        }

        Ok(unsafe { Spoof(&mut config) })
    }
}

/// Metadata extracted from a function prologue that is suitable for spoofing.
#[derive(Copy, Clone, Default)]
struct Prolog {
    /// Address of the selected function frame.
    frame: u64,

    /// Total stack space reserved by the function.
    stack_size: u32,

    /// Offset inside the function where a valid instruction pattern was found.
    offset: u32,

    /// Offset in the stack where `rbp` is pushed or saved.
    rbp_offset: u32,
}

impl Prolog {
    /// Finds the first prologue in the unwind table that looks safe for spoofing.
    ///
    /// 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()
            .filter_map(|runtime| {
                let (is_valid, stack_size) = 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(Self {
                    frame,
                    stack_size,
                    offset,
                    ..Default::default()
                })
            })
            .collect::<Vec<Self>>();

        if prologs.is_empty() {
            return None;
        }

        // Shuffle to reduce pattern predictability.
        shuffle(&mut prologs);

        prologs.first().copied()
    }

    /// Finds a prologue that uses `push rbp` and an RBP-based frame.
    ///
    /// 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()
            .filter_map(|runtime| {
                let (rbp_offset, stack_size) = rbp_offset(module_base, runtime)?;
                if rbp_offset == 0 || stack_size == 0 || stack_size <= rbp_offset {
                    return None;
                }

                let offset = find_valid_instruction_offset(module_base, runtime)?;
                let frame = module_base as u64 + runtime.BeginAddress as u64;
                Some(
                    Self {
                        frame,
                        stack_size,
                        offset,
                        rbp_offset,
                    }
                )
            })
            .collect::<Vec<Self>>();

        if prologs.is_empty() {
            return None;
        }

        // The first frame is often not suitable on many Windows versions.
        prologs.remove(0);

        // Shuffle to reduce pattern predictability.
        shuffle(&mut prologs);

        prologs.first().copied()
    }
}

/// Determines whether RBP is pushed or saved in a spoof-compatible manner and
/// computes the total stack size for a function.
///
/// 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;
        let unwind_code = (unwind_info as *mut u8).add(4) as *mut UNWIND_CODE;
        let flag = (*unwind_info).VersionFlags.Flags();

        let mut i = 0usize;
        let mut total_stack = 0u32;
        let mut rbp_pushed = false;
        let mut stack_offset = 0;

        while i < (*unwind_info).CountOfCodes as usize {
            // Accessing `UNWIND_CODE` based on the index
            let unwind_code = unwind_code.add(i);

            // Information used in operation codes
            let op_info = (*unwind_code).Anonymous.OpInfo() as usize;
            let unwind_op = (*unwind_code).Anonymous.UnwindOp();

            match UNWIND_OP_CODES::try_from(unwind_op) {
                // Saves a non-volatile register on the stack.
                //
                // Example: push <reg>
                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;
                }

                // Allocates large space on the stack.
                // - OpInfo == 0: The next slot contains the /8 size of the allocation (maximum 512 KB - 8).
                // - OpInfo == 1: The next two slots contain the full size of the allocation (up to 4 GB - 8).
                //
                // Example (OpInfo == 0): sub rsp, 0x100 ; Allocates 256 bytes
                // Example (OpInfo == 1): sub rsp, 0x10000 ; Allocates 65536 bytes (two slots used)
                Ok(UWOP_ALLOC_LARGE) => {
                    if (*unwind_code).Anonymous.OpInfo() == 0 {
                        // Case 1: OpInfo == 0 (Size in 1 slot, divided by 8)
                        // Multiplies by 8 to the actual value

                        let frame_offset = ((*unwind_code.add(1)).FrameOffset as i32) * 8;
                        total_stack += frame_offset as u32;

                        // Consumes 2 slots (1 for the instruction, 1 for the size divided by 8)
                        i += 2
                    } else {
                        // Case 2: OpInfo == 1 (Size in 2 slots, 32 bits)
                        let frame_offset = *(unwind_code.add(1) as *mut i32);
                        total_stack += frame_offset as u32;

                        // Consumes 3 slots (1 for the instruction, 2 for the full size)
                        i += 3
                    }
                }

                // Allocates small space in the stack.
                //
                // Example (OpInfo = 3): sub rsp, 0x20  ; Aloca 32 bytes (OpInfo + 1) * 8
                Ok(UWOP_ALLOC_SMALL) => {
                    total_stack += ((op_info + 1) * 8) as u32;
                    i += 1;
                }

                // UWOP_SAVE_NONVOL: Saves the contents of a non-volatile register in a specific position on the stack.
                // - Reg: Name of the saved register.
                // - FrameOffset: Offset indicating where the value of the register is saved.
                //
                // Example: mov [rsp + 0x40], rsi ; Saves the contents of RSI in RSP + 0x40
                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;
                }

                // Saves a non-volatile register to a stack address with a long offset.
                // - Reg: Name of the saved register.
                // - FrameOffset: Long offset indicating where the value of the register is saved.
                //
                // Example: mov [rsp + 0x1040], rsi ; Saves the contents of RSI in RSP + 0x1040.
                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;
                }

                // Return
                Ok(UWOP_SET_FPREG) => return None,

                // - Reg: Name of the saved XMM register.
                // - FrameOffset: Offset indicating where the value of the register is saved.
                Ok(UWOP_SAVE_XMM128) => i += 2,

                // UWOP_SAVE_XMM128BIG: Saves the contents of a non-volatile XMM register to a stack address with a long offset.
                // - Reg: Name of the saved XMM register.
                // - FrameOffset: Long offset indicating where the value of the register is saved.
                //
                // Example: movaps [rsp + 0x1040], xmm6 ; Saves the contents of XMM6 in RSP + 0x1040.
                Ok(UWOP_SAVE_XMM128BIG) => i += 3,

                // Reserved code, not currently used.
                Ok(UWOP_EPILOG) | Ok(UWOP_SPARE_CODE) => i += 1,

                // Push a machine frame. This unwind code is used to record the effect of a hardware interrupt or exception.
                Ok(UWOP_PUSH_MACH_FRAME) => {
                    total_stack += if op_info == 0 { 0x40 } else { 0x48 };
                    i += 1
                }

                _ => {}
            }
        }

        // If there is a chain unwind structure, it too must be processed
        // recursively and included in the stack size calculation.
        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 IMAGE_RUNTIME_FUNCTION;
            if let Some((_, child_total)) = rbp_offset(module, &*runtime) {
                total_stack += child_total;
            } else {
                return None;
            }
        }

        Some((stack_offset, total_stack))
    }
}

/// Computes stack frame metadata while rejecting `setfp` frames.
///
/// 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;
        let unwind_code = (unwind_info as *mut u8).add(4) as *mut UNWIND_CODE;
        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 {
            // Accessing `UNWIND_CODE` based on the index
            let unwind_code = unwind_code.add(i);

            // Information used in operation codes
            let op_info = (*unwind_code).Anonymous.OpInfo() as usize;
            let unwind_op = (*unwind_code).Anonymous.UnwindOp();

            match UNWIND_OP_CODES::try_from(unwind_op) {
                // Saves a non-volatile register on the stack.
                //
                // Example: push <reg>
                Ok(UWOP_PUSH_NONVOL) => {
                    if Registers::Rsp == op_info && !set_fpreg_hit {
                        return None;
                    }

                    total_stack += 8;
                    i += 1;
                }

                // Allocates small space in the stack.
                //
                // Example (OpInfo = 3): sub rsp, 0x20  ; Aloca 32 bytes (OpInfo + 1) * 8
                Ok(UWOP_ALLOC_SMALL) => {
                    total_stack += ((op_info + 1) * 8) as i32;
                    i += 1;
                }

                // Allocates large space on the stack.
                // - OpInfo == 0: The next slot contains the /8 size of the allocation (maximum 512 KB - 8).
                // - OpInfo == 1: The next two slots contain the full size of the allocation (up to 4 GB - 8).
                //
                // Example (OpInfo == 0): sub rsp, 0x100 ; Allocates 256 bytes
                // Example (OpInfo == 1): sub rsp, 0x10000 ; Allocates 65536 bytes (two slots used)
                Ok(UWOP_ALLOC_LARGE) => {
                    if (*unwind_code).Anonymous.OpInfo() == 0 {
                        // Case 1: OpInfo == 0 (Size in 1 slot, divided by 8)
                        // Multiplies by 8 to the actual value

                        let frame_offset = ((*unwind_code.add(1)).FrameOffset as i32) * 8;
                        total_stack += frame_offset;

                        // Consumes 2 slots (1 for the instruction, 1 for the size divided by 8)
                        i += 2
                    } else {
                        // Case 2: OpInfo == 1 (Size in 2 slots, 32 bits)
                        let frame_offset = *(unwind_code.add(1) as *mut i32);
                        total_stack += frame_offset;

                        // Consumes 3 slots (1 for the instruction, 2 for the full size)
                        i += 3
                    }
                }

                // UWOP_SAVE_NONVOL: Saves the contents of a non-volatile register in a specific position on the stack.
                // - Reg: Name of the saved register.
                // - FrameOffset: Offset indicating where the value of the register is saved.
                //
                // Example: mov [rsp + 0x40], rsi ; Saves the contents of RSI in RSP + 0x40
                Ok(UWOP_SAVE_NONVOL) => {
                    if Registers::Rsp == op_info || Registers::Rbp == op_info {
                        return None;
                    }

                    i += 2;
                }

                // Saves a non-volatile register to a stack address with a long offset.
                // - Reg: Name of the saved register.
                // - FrameOffset: Long offset indicating where the value of the register is saved.
                //
                // Example: mov [rsp + 0x1040], rsi ; Saves the contents of RSI in RSP + 0x1040.
                Ok(UWOP_SAVE_NONVOL_BIG) => {
                    if Registers::Rsp == op_info || Registers::Rbp == op_info {
                        return None;
                    }

                    i += 3;
                }

                // Saves the contents of a non-volatile XMM register on the stack.
                // - Reg: Name of the saved XMM register.
                // - FrameOffset: Offset indicating where the value of the register is saved.
                //
                // Example: movaps [rsp + 0x20], xmm6 ; Saves the contents of XMM6 in RSP + 0x20.
                Ok(UWOP_SAVE_XMM128) => i += 2,

                // UWOP_SAVE_XMM128BIG: Saves the contents of a non-volatile XMM register to a stack address with a long offset.
                // - Reg: Name of the saved XMM register.
                // - FrameOffset: Long offset indicating where the value of the register is saved.
                //
                // Example: movaps [rsp + 0x1040], xmm6 ; Saves the contents of XMM6 in RSP + 0x1040.
                Ok(UWOP_SAVE_XMM128BIG) => i += 3,

                // UWOP_SET_FPREG: Marks use of register as stack base (e.g. RBP).
                // Ignore if not RBP, has EH handler or chained unwind.
                // Subtract `FrameOffset << 4` from the stack total.
                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
                }

                // Reserved code, not currently used.
                Ok(UWOP_EPILOG) | Ok(UWOP_SPARE_CODE) => i += 1,

                // Push a machine frame. This unwind code is used to record the effect of a hardware interrupt or exception.
                Ok(UWOP_PUSH_MACH_FRAME) => {
                    total_stack += if op_info == 0 { 0x40 } else { 0x48 };
                    i += 1
                }
                _ => {}
            }
        }

        // If there is a chain unwind structure, it too must be processed
        // recursively and included in the stack size calculation.
        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 IMAGE_RUNTIME_FUNCTION;
            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))
    }
}

/// 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;
        let unwind_code = (unwind_info as *mut u8).add(4) as *mut UNWIND_CODE;
        let flag = (*unwind_info).VersionFlags.Flags();

        let mut i = 0usize;
        let mut total_stack = 0u32;
        while i < (*unwind_info).CountOfCodes as usize {
            // Accessing `UNWIND_CODE` based on the index
            let unwind_code = unwind_code.add(i);

            // Information used in operation codes
            let op_info = (*unwind_code).Anonymous.OpInfo() as usize;
            let unwind_op = (*unwind_code).Anonymous.UnwindOp();

            match UNWIND_OP_CODES::try_from(unwind_op) {
                // Saves a non-volatile register on the stack.
                //
                // Example: push <reg>
                Ok(UWOP_PUSH_NONVOL) => {
                    if Registers::Rsp == op_info {
                        return None;
                    }

                    total_stack += 8;
                    i += 1;
                }

                // Allocates small space in the stack.
                //
                // Example (OpInfo = 3): sub rsp, 0x20  ; Aloca 32 bytes (OpInfo + 1) * 8
                Ok(UWOP_ALLOC_SMALL) => {
                    total_stack += ((op_info + 1) * 8) as u32;
                    i += 1;
                }

                // Allocates large space on the stack.
                // - OpInfo == 0: The next slot contains the /8 size of the allocation (maximum 512 KB - 8).
                // - OpInfo == 1: The next two slots contain the full size of the allocation (up to 4 GB - 8).
                //
                // Example (OpInfo == 0): sub rsp, 0x100 ; Allocates 256 bytes
                // Example (OpInfo == 1): sub rsp, 0x10000 ; Allocates 65536 bytes (two slots used)
                Ok(UWOP_ALLOC_LARGE) => {
                    if (*unwind_code).Anonymous.OpInfo() == 0 {
                        // Case 1: OpInfo == 0 (Size in 1 slot, divided by 8)
                        // Multiplies by 8 to the actual value

                        let frame_offset = ((*unwind_code.add(1)).FrameOffset as i32) * 8;
                        total_stack += frame_offset as u32;

                        // Consumes 2 slots (1 for the instruction, 1 for the size divided by 8)
                        i += 2
                    } else {
                        // Case 2: OpInfo == 1 (Size in 2 slots, 32 bits)
                        let frame_offset = *(unwind_code.add(1) as *mut i32);
                        total_stack += frame_offset as u32;

                        // Consumes 3 slots (1 for the instruction, 2 for the full size)
                        i += 3
                    }
                }

                // UWOP_SAVE_NONVOL: Saves the contents of a non-volatile register in a specific position on the stack.
                // - Reg: Name of the saved register.
                // - FrameOffset: Offset indicating where the value of the register is saved.
                //
                // Example: mov [rsp + 0x40], rsi ; Saves the contents of RSI in RSP + 0x40
                Ok(UWOP_SAVE_NONVOL) => {
                    if Registers::Rsp == op_info {
                        return None;
                    }

                    i += 2;
                }

                // Saves a non-volatile register to a stack address with a long offset.
                // - Reg: Name of the saved register.
                // - FrameOffset: Long offset indicating where the value of the register is saved.
                //
                // Example: mov [rsp + 0x1040], rsi ; Saves the contents of RSI in RSP + 0x1040.
                Ok(UWOP_SAVE_NONVOL_BIG) => {
                    if Registers::Rsp == op_info {
                        return None;
                    }

                    i += 3;
                }

                // Saves the contents of a non-volatile XMM register on the stack.
                // - Reg: Name of the saved XMM register.
                // - FrameOffset: Offset indicating where the value of the register is saved.
                //
                // Example: movaps [rsp + 0x20], xmm6 ; Saves the contents of XMM6 in RSP + 0x20.
                Ok(UWOP_SAVE_XMM128) => i += 2,

                // UWOP_SAVE_XMM128BIG: Saves the contents of a non-volatile XMM register to a stack address with a long offset.
                // - Reg: Name of the saved XMM register.
                // - FrameOffset: Long offset indicating where the value of the register is saved.
                //
                // Example: movaps [rsp + 0x1040], xmm6 ; Saves the contents of XMM6 in RSP + 0x1040.
                Ok(UWOP_SAVE_XMM128BIG) => i += 3,

                // Ignoring.
                Ok(UWOP_SET_FPREG) => i += 1,

                // Reserved code, not currently used.
                Ok(UWOP_EPILOG) | Ok(UWOP_SPARE_CODE) => i += 1,

                // Push a machine frame. This unwind code is used to record the effect of a hardware interrupt or exception.
                Ok(UWOP_PUSH_MACH_FRAME) => {
                    total_stack += if op_info == 0 { 0x40 } else { 0x48 };
                    i += 1
                }
                _ => {}
            }
        }

        // If there is a chain unwind structure, it too must be processed
        // recursively and included in the stack size calculation.
        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 IMAGE_RUNTIME_FUNCTION;
            if let Some(chained_stack) = ignoring_set_fpreg(module, &*runtime) {
                total_stack += chained_stack;
            } else {
                return None;
            }
        }

        Some(total_stack)
    }
}

/// 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`.
    fn as_ptr_const(&self) -> *const c_void;

    /// Returns a raw mutable pointer to `self`.
    fn as_ptr_mut(&mut self) -> *mut c_void;
}

impl<T> AsPointer for T {
    #[inline(always)]
    fn as_ptr_const(&self) -> *const c_void {
        self as *const _ as *const c_void
    }

    #[inline(always)]
    fn as_ptr_mut(&mut self) -> *mut c_void {
        self as *mut _ as *mut c_void
    }
}

/// Specifies the spoofing mode used by the engine.
pub enum SpoofKind<'a> {
    /// Spoofs a direct function call.
    Function,

    /// 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(())
    }
}

================================================
FILE: taplo.toml
================================================
[formatting]
crlf = true
array_auto_expand = true
Download .txt
gitextract_me8cjxhe/

├── .gitattributes
├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .vscode/
│   └── settings.json
├── Cargo.toml
├── Justfile
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── build.rs
├── clippy.toml
├── rust-toolchain.toml
├── rustfmt.toml
├── src/
│   ├── asm/
│   │   ├── gnu/
│   │   │   ├── desync.asm
│   │   │   └── synthetic.asm
│   │   └── msvc/
│   │       ├── desync.asm
│   │       └── synthetic.asm
│   ├── lib.rs
│   ├── types.rs
│   ├── util.rs
│   └── uwd.rs
└── taplo.toml
Download .txt
SYMBOL INDEX (38 symbols across 4 files)

FILE: build.rs
  function main (line 3) | fn main() {

FILE: src/types.rs
  constant UNW_FLAG_EHANDLER (line 7) | pub const UNW_FLAG_EHANDLER: u8 = 0x1;
  constant UNW_FLAG_CHAININFO (line 10) | pub const UNW_FLAG_CHAININFO: u8 = 0x4;
  type Unwind (line 14) | pub struct Unwind {
    method new (line 21) | pub fn new(pe: PE) -> Self {
    method entries (line 26) | pub fn entries(&self) -> Option<&[IMAGE_RUNTIME_FUNCTION]> {
    method function_by_offset (line 43) | pub fn function_by_offset(&self, offset: u32) -> Option<&IMAGE_RUNTIME...
    method function_size (line 49) | pub fn function_size(&self, func: *mut c_void) -> Option<u64> {
  type Config (line 62) | pub struct Config {
  method default (line 130) | fn default() -> Self {
  type Registers (line 141) | pub enum Registers {
    method eq (line 161) | fn eq(&self, other: &usize) -> bool {
  type UNWIND_INFO (line 230) | pub struct UNWIND_INFO {
  type UNWIND_OP_CODES (line 259) | pub enum UNWIND_OP_CODES {
    type Error (line 274) | type Error = ();
    method try_from (line 276) | fn try_from(value: u8) -> Result<Self, Self::Error> {

FILE: src/util.rs
  function find_valid_instruction_offset (line 18) | pub fn find_valid_instruction_offset(
  function find_gadget (line 41) | pub fn find_gadget(
  function find_base_thread_return_address (line 82) | pub fn find_base_thread_return_address() -> Option<usize> {
  function shuffle (line 128) | pub fn shuffle<T>(list: &mut [T]) {

FILE: src/uwd.rs
  function Spoof (line 22) | fn Spoof(config: &mut Config) -> *mut c_void;
  function SpoofSynthetic (line 28) | fn SpoofSynthetic(config: &mut Config) -> *mut c_void;
  function spoof (line 109) | pub fn spoof(addr: *mut c_void, args: &[*const c_void], kind: SpoofKind)...
  function spoof (line 238) | pub fn spoof(addr: *mut c_void, args: &[*const c_void], kind: SpoofKind)...
  type Prolog (line 343) | struct Prolog {
    method find_prolog (line 363) | fn find_prolog(module_base: *mut c_void, runtime_table: &[IMAGE_RUNTIM...
    method find_push_rbp (line 397) | fn find_push_rbp(module_base: *mut c_void, runtime_table: &[IMAGE_RUNT...
  function rbp_offset (line 439) | pub fn rbp_offset(module: *mut c_void, runtime: &IMAGE_RUNTIME_FUNCTION)...
  function stack_frame (line 607) | pub fn stack_frame(module: *mut c_void, runtime: &IMAGE_RUNTIME_FUNCTION...
  function ignoring_set_fpreg (line 761) | pub fn ignoring_set_fpreg(module: *mut c_void, runtime: &IMAGE_RUNTIME_F...
  type AsPointer (line 898) | pub trait AsPointer {
    method as_ptr_const (line 900) | fn as_ptr_const(&self) -> *const c_void;
    method as_ptr_mut (line 903) | fn as_ptr_mut(&mut self) -> *mut c_void;
    method as_ptr_const (line 908) | fn as_ptr_const(&self) -> *const c_void {
    method as_ptr_mut (line 913) | fn as_ptr_mut(&mut self) -> *mut c_void {
  type SpoofKind (line 919) | pub enum SpoofKind<'a> {
  function test_spoof (line 934) | fn test_spoof() -> Result<(), Box<dyn core::error::Error>> {
  function test_syscall (line 944) | fn test_syscall() -> Result<(), Box<dyn core::error::Error>> {
Condensed preview — 22 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (93K chars).
[
  {
    "path": ".gitattributes",
    "chars": 18,
    "preview": "* text=auto eol=lf"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 636,
    "preview": "name: build\n\non: [push, pull_request]\n\njobs:\n  clippy:\n    # Runs Clippy to check for lints in the Rust code\n    name: C"
  },
  {
    "path": ".gitignore",
    "chars": 821,
    "preview": "# Generated by Cargo\n# will have compiled files and executables\ndebug/\ntarget/\n\n# Remove Cargo.lock from gitignore if cr"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 69,
    "preview": "{\n    \"rust-analyzer.linkedProjects\": [\n        \"Cargo.toml\"\n    ],\n}"
  },
  {
    "path": "Cargo.toml",
    "chars": 894,
    "preview": "[package]\nname = \"uwd\"\nversion = \"0.3.5\"\nedition = \"2024\"\ndescription = \"Call Stack Spoofing for Rust\"\nlicense = \"MIT OR"
  },
  {
    "path": "Justfile",
    "chars": 388,
    "preview": "# Aliases\nalias c := clean\nalias up := update\n\n# Use PowerShell shell on Windows\nset windows-shell := [\"powershell.exe\","
  },
  {
    "path": "LICENSE-APACHE",
    "chars": 11357,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "LICENSE-MIT",
    "chars": 1062,
    "preview": "MIT License\n\nCopyright (c) 2025 Victor\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
  },
  {
    "path": "README.md",
    "chars": 4058,
    "preview": "# uwd\n\n![Rust](https://img.shields.io/badge/made%20with-Rust-red)\n![crate](https://img.shields.io/crates/v/uwd.svg)\n![do"
  },
  {
    "path": "build.rs",
    "chars": 1275,
    "preview": "use std::env;\n\nfn main() {\n    if env::var(\"DOCS_RS\").is_ok() {\n        println!(\"cargo:warning=Skipping ASM build for d"
  },
  {
    "path": "clippy.toml",
    "chars": 13,
    "preview": "msrv = \"1.89\""
  },
  {
    "path": "rust-toolchain.toml",
    "chars": 68,
    "preview": "[toolchain]\nchannel = \"stable\"\ntargets = [\"x86_64-pc-windows-msvc\"]\n"
  },
  {
    "path": "rustfmt.toml",
    "chars": 114,
    "preview": "# Uses Rustfmt 2024 edition rules, but not all of them are enforced 100%.\nedition = \"2024\"\nstyle_edition = \"2024\"\n"
  },
  {
    "path": "src/asm/gnu/desync.asm",
    "chars": 4203,
    "preview": ";;\n;; Code responsible for Call Stack Spoofing Via Desync (NASM)\n;;\n[BITS 64]\n\n;;\n;; Export\n;;\nGLOBAL Spoof\n\n[SECTION .d"
  },
  {
    "path": "src/asm/gnu/synthetic.asm",
    "chars": 5074,
    "preview": ";;\n;; Code responsible for Call Stack Spoofing Via Synthetic (NASM)\n;;\n[BITS 64]\n\n;;\n;; Export\n;;\nGLOBAL SpoofSynthetic\n"
  },
  {
    "path": "src/asm/msvc/desync.asm",
    "chars": 4116,
    "preview": ";;\n;; Code responsible for Call Stack Spoofing Via Desync (MASM)\n;;\n\n;;\n;; Export\n;;\nSpoof proto\n\n.data\n\n;;\n;; Configura"
  },
  {
    "path": "src/asm/msvc/synthetic.asm",
    "chars": 4979,
    "preview": ";;\n;; Code responsible for Call Stack Spoofing Via Synthetic (MASM)\n;;\n\n;;\n;; Export\n;;\nSpoofSynthetic proto\n\n.data\n\n;;\n"
  },
  {
    "path": "src/lib.rs",
    "chars": 119,
    "preview": "#![no_std]\n#![doc = include_str!(\"../README.md\")]\n\nextern crate alloc;\n\nmod types;\nmod uwd;\nmod util;\n\npub use uwd::*;\n"
  },
  {
    "path": "src/types.rs",
    "chars": 7598,
    "preview": "#![allow(non_snake_case, non_camel_case_types)]\n\nuse core::{ffi::c_void, slice::from_raw_parts};\nuse dinvk::{types::*, h"
  },
  {
    "path": "src/util.rs",
    "chars": 4430,
    "preview": "use core::{ffi::c_void, slice::from_raw_parts};\nuse alloc::vec::Vec;\n\nuse obfstr::obfbytes as b;\nuse dinvk::types::IMAGE"
  },
  {
    "path": "src/uwd.rs",
    "chars": 37126,
    "preview": "use alloc::{string::String, vec::Vec};\nuse core::ffi::c_void;\n\nuse anyhow::{Context, Result, bail};\nuse obfstr::obfstrin"
  },
  {
    "path": "taplo.toml",
    "chars": 50,
    "preview": "[formatting]\ncrlf = true\narray_auto_expand = true\n"
  }
]

About this extraction

This page contains the full source code of the joaoviictorti/uwd GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 22 files (86.4 KB), approximately 23.1k tokens, and a symbol index with 38 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!