An educational Rust framework for function hooking and in-process memory manipulation, built as an anti-cheat research aid.
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 operator console at rest. Pick a process, inject, and the engine opens the pipe itself.
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 classicCreateRemoteThread+LoadLibraryAtechnique. - A multi-technique hook engine (
rhook-engine) built oniced-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
0xCCplus a process-wide vectored exception handler, for prologues too short to patch and patch sites where a JMP signature is undesirable.
- Inline 14-byte absolute JMP with RIP-relative relocation via
- A Unity runtime enumerator with two backends:
- IL2CPP: walks
GameAssembly.dll's Domain → Assembly → Image → Class → Method → Event graph via the exportedil2cpp_*API, with graceful degradation on stripped runtimes. - Mono: walks
mono-2.0-bdwgc.dll's Domain → Assembly → Image → TypeDef → Class → Method metadata tables via themono_*embedding API, with on-demand JIT compile for hook install.
- IL2CPP: walks
- An Unreal Engine reflection explorer: locates
GUObjectArray/GNameson 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-wideProcessEventchokepoint 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-serialisedCommand/Responseenums 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) │
└───────────────────────────────────────┘
# 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 devIn the app:
- Session view: pick
rhook-target-hostin 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 readsinjected into … ; connectedand 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. - Back in Session, click Detect runtimes. On the synthetic target IL2CPP reports "detected", because
rhook_target_core.dllexposes an IL2CPP-like stub surface. - In Discover, open Modules and click
rhook_target_core.dll: the view switches to its Exports. Filter forgame_set_moneyand click the row: rhook resolvesmodule_base + RVAand 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.) - Install view: paste the address, technique
Inline, behaviourOverride i32, value99999, click Install. The new hook appears in the Hooks view. - Watch the target's console: every tick that calls
game_set_moneynow commits99999instead 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.dllThe defensive scanner that detects rhook injection from the outside now lives in the companion repo spadieri/rhook-anticheat.
| 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) |
| 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 |
| 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.
rhook is deliberately agnostic to specific commercial games. To apply it to a Unity title you own or have permission to analyse:
- (IL2CPP only, optional) Dump metadata with Il2CppDumper to get
dump.cswith RVA annotations. - Launch the desktop app (
npm run tauri devincrates/app/desktop, or a packaged build), pick the game process in the Session view, and click Inject: the pipe opens in the same step. - Click Detect; on a positive result the matching runtime is reported, and the Discover view offers the matching catalog tab.
- 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.
- 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.
- 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.
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-anticheatscanner 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.
- The named pipe is unauthenticated by design. The engine serves
\\.\pipe\rhookwith 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
CreateRemoteThreadinjection are all trivially detectable by modern anti-cheat and EDR products. rhook is built for research on software you own, not for stealth.
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.
- 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.
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.
