Skip to content
spadieriPublic

About

No description, website, or topics provided.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

rhook

An educational Rust framework for function hooking and in-process memory manipulation, built as an anti-cheat research aid.

CI License: MPL 2.0

WARNING READ FIRST: DISCLAIMER.md. rhook is for research and education only: not for cheating in games, modifying software without permission, or circumventing technical protection measures.

The rhook desktop app, disconnected

The operator console at rest. Pick a process, inject, and the engine opens the pipe itself.


What is rhook?

rhook is a Cargo workspace containing a complete, self-contained game-hacking laboratory written in pure Rust (no C/C++ dependencies beyond the Windows platform itself):

  • A graphical operator console (rhook-app, Tauri 2 + React, crates/app/desktop) with process picker, one-click injection, and a typed named-pipe client for driving the engine.
  • A CLI DLL injector (rhook-injector) as a fallback, using the classic CreateRemoteThread + LoadLibraryA technique.
  • A multi-technique hook engine (rhook-engine) built on iced-x86. Five techniques on one registry:
    • Inline 14-byte absolute JMP with RIP-relative relocation via BlockEncoder.
    • Inline 5-byte relative JMP + dispatch stub for functions too short to host 14 bytes.
    • IAT hooking: atomic aligned-qword write in the Import Address Table.
    • VMT hooking: atomic aligned-qword write in a C++ virtual method table.
    • VEH / INT3 hooking: single 0xCC plus a process-wide vectored exception handler, for prologues too short to patch and patch sites where a JMP signature is undesirable.
  • A Unity runtime enumerator with two backends:
    • IL2CPP: walks GameAssembly.dll's Domain → Assembly → Image → Class → Method → Event graph via the exported il2cpp_* API, with graceful degradation on stripped runtimes.
    • Mono: walks mono-2.0-bdwgc.dll's Domain → Assembly → Image → TypeDef → Class → Method metadata tables via the mono_* embedding API, with on-demand JIT compile for hook install.
  • An Unreal Engine reflection explorer: locates GUObjectArray / GNames on shipping binaries (signature scan plus a signature-less structural walk), enumerates the live UObject / UClass / UFunction / UProperty graph, and hooks either single native thunks or the engine-wide ProcessEvent chokepoint with per-UFunction log / block / typed-override subscriptions.
  • A memory scanner (GUI panel + rhook-scan-cli): Cheat-Engine-style value scanning with freeze, IDA-style AOB pattern scanning, and multi-level pointer-path resolution.
  • An IPC layer (rhook-ipc): serde-serialised Command / Response enums over a named pipe (\\.\pipe\rhook), so the GUI and engine are decoupled processes.
  • A custom target game (rhook-target-core + rhook-target-host) with a synthetic IL2CPP-like export surface, so the entire pipeline is reproducible end-to-end without touching third-party software.

Its defensive counterpart, rhook-anticheat, lives in the companion repo spadieri/rhook-anticheat: it compares a running process against the clean on-disk image of every module that process loaded, so an inline hook surfaces as the bytes it actually changed. The same primitives, pointed in the opposite direction.

┌───────────────────────────── host machine ─────────────────────────────┐
│                                                                        │
│   ┌────────────────────────────┐    ┌─────────────────────────────┐    │
│   │ rhook desktop app          │    │ rhook-injector.exe (CLI)    │    │
│   │  Tauri 2 + React           │    │  fallback entry point       │    │
│   │  process picker + inject   │    └─────────────────────────────┘    │
│   │  named-pipe JSON client    │                                       │
│   └─────┬──────────────┬───────┘                                       │
│         │              │                                               │
│     inject DLL     Command / Response                                  │
│         │              │                                               │
└─────────┼──────────────┼───────────────────────────────────────────────┘
          │              │
          │              └────────► \\.\pipe\rhook ────┐
          │                                            │
┌─────────▼────────────── target process ──────────────▼─────────────────┐
│                                                                        │
│   ┌──────────────────────────┐                                         │
│   │ rhook_engine.dll         │  named-pipe server (background thread)  │
│   │                          │  AllocConsole fallback UI (opt-in)      │
│   │   hook registry ─────────┼──► inline 14-byte / inline 5-byte+stub  │
│   │                          │    IAT / VMT / VEH techniques           │
│   │   il2cpp catalog ────────┼──► domain → class → method → event walk │
│   │   mono catalog ──────────┼──► domain → image → typedef → method    │
│   │                          │                                         │
│   └──────────────────────────┘                                         │
│              │ hooks                                                   │
│              ▼                                                         │
│   ┌─────────────────────────────────────────────┐                      │
│   │  game code (GameAssembly.dll, target-host)  │                      │
│   └─────────────────────────────────────────────┘                      │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

          ┌───────────────────────────────────────┐
          │ rhook-anticheat.exe (separate host    │
          │ process - defender-side scanner       │
          │ watching the target for injection,    │
          │ tampered prologues, suspicious        │
          │ threads)                              │
          └───────────────────────────────────────┘

Quickstart

# Build everything in release.
cargo build --release --workspace

# Terminal A: run the synthetic target game.
cargo run --release -p rhook-target-host

# Terminal B: launch the desktop app (first run needs `npm install`).
cd crates/app/desktop
npm install
npm run tauri dev

In the app:

  1. Session view: pick rhook-target-host in the Target process picker, leave the engine DLL at the auto-resolved default (rhook_engine.dll), click Inject. Injection also opens the pipe: the console reads injected into … ; connected and the app switches to the Discover view. Connect is the retry path for when that automatic connect fails, and stays greyed out while the session is live.
  2. Back in Session, click Detect runtimes. On the synthetic target IL2CPP reports "detected", because rhook_target_core.dll exposes an IL2CPP-like stub surface.
  3. In Discover, open Modules and click rhook_target_core.dll: the view switches to its Exports. Filter for game_set_money and click the row: rhook resolves module_base + RVA and echoes the absolute address to the console. Copy it from there. (Catalog rows in the IL2CPP / Mono / Unreal tabs pre-fill the Install form on click; the Exports tab hands you the address instead.)
  4. Install view: paste the address, technique Inline, behaviour Override i32, value 99999, click Install. The new hook appears in the Hooks view.
  5. Watch the target's console: every tick that calls game_set_money now commits 99999 instead of the value the host passed in.

For a real Unity IL2CPP game the flow is the same, but step 3 uses the IL2CPP catalog tab (or an RVA read from Il2CppDumper's dump.cs). See docs/il2cpp-primer.md for the full workflow and examples/hooking-your-own-target.md for a step-by-step walkthrough.

The CLI injector is still present as a fallback, and the engine can spawn a console-inside-target UI when built with the opt-in console-ui cargo feature (off by default):

# CLI path: injector only, then drive the engine from the app or scan-cli.
cargo run --release -p rhook-injector -- rhook-target-host \
    target/release/rhook_engine.dll

The defensive scanner that detects rhook injection from the outside now lives in the companion repo spadieri/rhook-anticheat.

Project layout

Crate Kind Role
crates/core/common lib process / memory / module helpers, PE parser, injection primitive, process listing
crates/core/ipc lib serde-serialised Command / Response enums for the named pipe
crates/core/rhook-core lib host-side session / catalog / scanner client consumed by the desktop app
crates/core/injector bin CLI DLL injector (thin wrapper over rhook_common::injection)
crates/core/hook-engine cdylib multi-technique hook engine, IL2CPP / Mono / Unreal catalog walkers, memory scanner, pipe server, console fallback UI
crates/app/desktop bin Tauri 2 + React operator console (rhook-app), named-pipe client, integrated injector
crates/app/scan-cli bin headless memory-scanner client over the engine pipe
crates/fixtures/game-core cdylib synthetic IL2CPP-like target DLL
crates/fixtures/game-host bin loads the target DLL, runs a game loop
crates/fixtures/mono-core cdylib synthetic Mono-like target DLL
crates/fixtures/unreal-core cdylib synthetic Unreal-reflection target DLL
crates/fixtures/scan-core cdylib scanner ground-truth fixture (heap / static / pointer chains)

Why these choices

Decision Rationale
Pure Rust, no C/C++ FFI Full unsafe discipline under Rust 2024's deny(unsafe_op_in_unsafe_fn)
iced-x86 disassembler Industry standard; handles instruction boundaries and RIP-relative fixup
Two inline JMP variants 14-byte covers the general case, 5-byte + stub covers short setters
Trampoline within ±2 GB Keeps 32-bit RIP-relative offsets valid after relocation
Per-hook bridge thunks One dispatcher, N hooks, O(1) dispatch: identity by which bridge ran
Thread-suspend on byte patches Closes the race window where another thread executes bytes mid-patch
Atomic stores on IAT / VMT paths Aligned 8-byte writes are single-copy atomic on x86-64, no suspend needed
Named-pipe IPC Decouples engine (in-process) from GUI (host): GUI crashes don't kill the game
Self-hosted target game No legal risk; reproducible; demonstrates real IL2CPP challenges

Supported runtimes

Runtime Module candidates Catalog Address source Shipped
Windows PE every loaded *.dll / *.exe Exports tab module_base + RVA (exports) v0.3.0
Unity IL2CPP GameAssembly.dll, libil2cpp.dll IL2CPP catalog module_base + RVA (AOT) v0.5.0
Unity Mono mono-2.0-bdwgc.dll, MonoBleedingEdge.dll, mono.dll Mono catalog mono_compile_method(…) (JIT) v0.6.0
Unreal Engine *-Win64-Shipping.exe / *-Core.dll (FNamePool, FUObjectArray) Unreal catalog UFunction::Func thunk or engine-wide ProcessEvent v0.7.0

Coverage scales across pre-2019 Unity indies (Mono), modern Unity AA/AAA (IL2CPP), and a broad slice of the Unreal Engine catalog (validated on UE4.27 and UE5.0-5.4 shipping builds; other versions are detected but enumerate with reduced layout support). The hook engine itself is runtime-agnostic: every path lands at the same registry::Entry and dispatches through the same bridge, so future runtimes slot in at the discovery layer without touching the dispatcher.

Applying rhook to real targets

rhook is deliberately agnostic to specific commercial games. To apply it to a Unity title you own or have permission to analyse:

  1. (IL2CPP only, optional) Dump metadata with Il2CppDumper to get dump.cs with RVA annotations.
  2. Launch the desktop app (npm run tauri dev in crates/app/desktop, or a packaged build), pick the game process in the Session view, and click Inject: the pipe opens in the same step.
  3. Click Detect; on a positive result the matching runtime is reported, and the Discover view offers the matching catalog tab.
  4. In the IL2CPP catalog / Mono catalog tab, click Enumerate, filter for the class / method of interest. On Mono, also click the row's Compile button to JIT the method.
  5. Click Use as hook target on the method row (pre-fills the Install form with address + label). Alternatively, enter a module + RVA manually from a dump.
  6. Choose a technique (usually Inline), a behaviour (log / override / multiply / block / override struct field), and click Install.

Keep your per-target notes, dumps and offsets under targets/<name>/: that directory is gitignored by default (see targets/README.md).

rhook does not ship game-specific offsets, signatures, dumps, or DRM-bypass logic. It does ship engine-generic Unreal layout constants and signature patterns, derived from public sources (patternsleuth, UE4SS, Dumper-7) and credited in-tree under crates/core/hook-engine/src/unreal/layout/; those locate engine globals, never game code. See DISCLAIMER.md for acceptable use.

Not a vulnerability disclosure

rhook is not a disclosure of security vulnerabilities in any specific product.

  • The primitives rhook uses (DLL injection via CreateRemoteThread, PE parsing, inline hooking via x86-64 JMPs, IAT/VMT modification, VEH handlers) have been publicly documented by Microsoft and the security community for 25+ years (see Detours, MinHook, Cheat Engine).
  • Offline single-player games typically do not implement runtime integrity checks against local memory modification, by design. This is expected behaviour, not a flaw.
  • rhook does not bypass any anti-cheat, DRM, or technical protection measure. It is designed to be easy for a defender to detect: the companion rhook-anticheat scanner demonstrates exactly that.
  • No game-specific offsets, signatures, or dumps are shipped in this repository. The engine-generic Unreal constants and patterns that are shipped are credited to their public sources in-tree.

If you are a rights holder of software you believe is affected by rhook in a way that constitutes a real security vulnerability, please open an issue or contact the maintainers: we will cooperate with responsible disclosure.

Operational security notes

  • The named pipe is unauthenticated by design. The engine serves \\.\pipe\rhook with default OS security attributes: any process running as the same user (or higher) can connect and issue commands, up to arbitrary memory writes and hook control inside the injected target. Treat an injected engine as an open control channel: detach or close the target when the session is over, and expect any local tool (including a defender) to be able to drive or disable it.
  • rhook is loud on purpose. RWX trampoline pages, a fixed pipe name, first-position vectored-handler registration, mass thread suspension during patching, and textbook CreateRemoteThread injection are all trivially detectable by modern anti-cheat and EDR products. rhook is built for research on software you own, not for stealth.

Documentation

Contributing

See CONTRIBUTING.md for the full contributor guide: project scope, coding rules (Rust 2024 unsafe discipline, # Safety doc sections, no unwrap in library code, …), quality gate commands, and commit style.

Offsets and ready-to-use exploits for specific commercial games are out of scope and such PRs will be closed.

Ethics & research resources

  • OWASP Game Security
  • UnknownCheats and GuidedHacking host active research communities. Treat them as reading material, not as tutorials to run against live services.
  • Responsible disclosure first, always.

License

Mozilla Public License 2.0.

MPL-2.0 is a file-level copyleft. Modify an rhook source file and distribute it, and that file's source has to stay available under the MPL. Combine rhook with your own code in separate files and the result is a "Larger Work" you may license however you like (§3.3), which is what keeps the door open for a commercial game to embed the defensive side without opening its own source.

Every file in this repository is covered, per the Exhibit A provision allowing the notice to live where a recipient would look for it. The crates intended to be consumed elsewhere (rhook-common, rhook-ipc, rhook-engine, rhook-core) additionally carry the notice in each source file.

About

No description, website, or topics provided.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages