Process Interrogation on Windows: Owning Another Address Space
Every memory manipulation workflow begins with one prerequisite: access to a target process’s virtual address space. On Windows, this is mediated by the Win32 API and enforced by the kernel’s object security model. The typical entry point isOpenProcess, which returns a handle representing the kernel’s authorization to interact with another process. What matters is not the function itself, but the access mask requested. Tools like Cheat Engine usually ask for combinations of:
PROCESS_VM_READandPROCESS_VM_WRITEto copy memory across address spacesPROCESS_VM_OPERATIONto change page protectionsPROCESS_QUERY_INFORMATIONto enumerate threads, modules, and metadataPROCESS_CREATE_THREADfor remote execution or DLL injection
ReadProcessMemory, WriteProcessMemory, VirtualProtectEx is validated against it. From the kernel’s perspective, this is all legitimate behavior; Windows was explicitly designed to allow debuggers and profilers to do exactly this.
The key architectural point is that no code runs “inside” the target process at this stage. The kernel temporarily maps the target’s physical pages into the scanner’s context during reads and writes, providing a consistent snapshot of memory without violating isolation guarantees.
Mapping the Virtual Address Space
A modern Windows process does not expose a flat block of usable memory. Instead, it sees a sparse virtual address space composed of regions with different states, protections, and backing stores. Cheat Engine navigates this space usingVirtualQueryEx, which returns MEMORY_BASIC_INFORMATION structures describing each region. This metadata drives nearly all higher-level behavior:
- State (
MEM_COMMIT,MEM_RESERVE,MEM_FREE) determines whether data actually exists - Protection (
PAGE_READWRITE,PAGE_EXECUTE_READ, etc.) distinguishes variables from code - Type (
MEM_PRIVATE,MEM_IMAGE) separates heap allocations from modules and DLLs
MEM_PRIVATE heap regions, while engine logic and static data reside in MEM_IMAGE regions belonging to UnityPlayer.dll, GameAssembly.dll, or managed runtime libraries.
By walking the address space region by region, the scanner builds a working map of “where things can possibly be,” which dramatically reduces noise before any value scanning even begins.
Value Scanning: Reducing Gigabytes to One Address
The classic Cheat Engine workflow, scan for a value, change it, scan again, is an exercise in algorithmic filtering. A first scan for a common value (say, anint with value 100) can easily return tens of thousands of candidates. Subsequent scans compare the current contents of those same addresses against new constraints:
- Increased / Decreased when the direction of change is known
- Changed / Unchanged to eliminate static or unrelated data
- Range-based scans for obfuscated or scaled values
ASLR, Heap Allocation, and the Need for Pointers
Modern applications make heavy use of dynamic allocation, and Windows enforces Address Space Layout Randomization (ASLR) across process restarts. As a result, absolute addresses are ephemeral. To survive restarts, Cheat Engine resolves pointer paths: chains of dereferences that begin at a static base (often a module + offset) and walk through heap-allocated objects to reach the final value. Conceptually, this mirrors how the game itself accesses data:ModuleBase
→ GlobalManager*
→ Player*
→ Stats*
→ health
Each arrow represents a pointer read plus a fixed offset. Cheat Engine’s pointer scanner automates discovery of these paths by identifying values in memory that look like pointers, that is, integers which themselves point to valid committed regions.
By generating pointer maps across multiple sessions and comparing them, the tool can identify stable chains that remain valid even as individual allocations move. These chains are what ultimately get serialized into reusable cheat tables.
For developers, this is a reminder that even heavily abstracted object graphs still collapse down into predictable memory layouts once compiled.
Debugging the Target: Who Writes This Value?
Finding a variable answers what to modify. Debugging answers how it’s modified. Cheat Engine supports several breakpoint mechanisms, each with different trade-offs:Software Breakpoints (INT3)
These overwrite the first byte of an instruction with0xCC. When executed, the CPU raises an exception that the debugger intercepts. This is simple and flexible, but noisy: it modifies code pages and is trivial to detect.
Hardware Breakpoints (Debug Registers)
Modern CPUs expose debug registers (DR0–DR7) that can monitor execution or memory access without altering code. These breakpoints operate at the hardware level and can trigger on reads or writes to a specific address.
For reverse engineering gameplay logic “what writes to health?” hardware breakpoints are invaluable, because they reveal the exact instruction and register state responsible for a change.
VEH Debugging
Vectored Exception Handling (VEH) avoids attaching as a traditional debugger. Instead, injected code inside the target process registers its own exception handler, intercepting breakpoints before Windows’ standard handlers run. From the target’s point of view, no debugger is attached. From the researcher’s point of view, full breakpoint telemetry is available. This technique highlights how much flexibility Windows provides to in-process exception routing.Crossing into Kernel Space (and Beyond)
As anti-cheat systems increasingly operate in kernel mode, user-mode memory tools face structural limits. Cheat Engine addresses this with optional kernel drivers and, at the extreme end, a custom hypervisor.Kernel Drivers
A Ring-0 driver can bypass user-mode API hooks, access physical memory directly, and set breakpoints invisible to user-mode detection. It effectively re-implements parts of the memory manager from a more privileged vantage point.Hypervisor (DBVM)
DBVM pushes this idea further by running beneath Windows itself, using Intel VT-x or AMD-V. In this configuration, Windows becomes a guest OS, and Cheat Engine operates at a higher privilege level. This enables:- Breakpoints that never surface to the OS
- Page-level access control via Extended Page Tables (EPT)
- Concealment from kernel self-protection mechanisms like PatchGuard
Unity-Specific Analysis: Mono vs. IL2CPP
Unity’s scripting backend choice fundamentally defines how observable and malleable a game is from outside the process. Mono and IL2CPP do not merely differ in performance characteristics; they expose very different reverse-engineering surfaces.Mono: Rich Metadata as Both Feature and Liability
In Mono-based Unity builds, C# assemblies are loaded largely intact. Class names, field names, method signatures, inheritance hierarchies, and field offsets remain discoverable at runtime. This metadata exists to support reflection, debugging, serialization, and garbage collection, and it is exposed through the Mono embedding API. Cheat Engine’s Mono Dissector leverages this directly by injecting a helper module that queries the runtime via functions such asmono_class_get_fields() and mono_field_get_offset(). The result is effectively a live, structured view of the managed heap: developers’ conceptual object models rendered directly into a memory inspection tool.
Where Obfuscation Helps in Mono
Code obfuscation does meaningfully raise the cost of analysis in Mono builds, for example:- Symbol obfuscation (renaming classes, fields, and methods) destroys semantic clarity. A field named
PlayerHealthbecominga1b2c3removes intent and slows manual exploration. - Control-flow obfuscation complicates method-level reasoning when breakpoints hit managed code paths.
- String encryption prevents trivial discovery of logic tied to configuration keys, events, or debug output.
- Exist at fixed offsets within objects
- Are enumerable via the Mono API
- Can be observed changing over time
Player.currentHealth and a.b.c are equally writable once their offset is known. Obfuscation increases analyst effort, but it does not meaningfully restrict capability.
In short: Mono obfuscation protects meaning, not access.
IL2CPP: Native Code, Reduced Introspection Surface
IL2CPP fundamentally alters the analysis model by converting managed code into native C++ ahead of time. The managed runtime disappears, taking with it reflection, JIT compilation, and most high-level metadata. Instead, Unity emits:- A native binary (
GameAssembly.dll) - A serialized metadata file (
global-metadata.dat) describing types, methods, and fields
- Metadata dumping and reconstruction
- Static analysis of native code
- API hooking of IL2CPP runtime functions
- Traditional pointer and code analysis
Where Obfuscation Helps in IL2CPP
Obfuscation is materially more effective in IL2CPP builds because it compounds existing opacity rather than merely renaming exposed metadata. Effective IL2CPP obfuscation strategies include:-
Metadata obfuscation
Renaming types and fields which then are requested inside
global-metadata.datbreaking automated dumpers’ ability to produce meaningful headers. - Layout randomization Reordering fields within classes invalidates previously discovered offsets, forcing re-analysis after every build.
-
Control-flow flattening in native code
Makes static disassembly of
GameAssembly.dllsignificantly harder, even with full symbols reconstructed. - Dead-code and fake-type injection Pollutes metadata with unused or misleading structures, increasing noise during reverse engineering.
- Objects are still heap-allocated
- Fields still live at deterministic offsets
- Native instructions still mutate memory
Temporal Manipulation: How Speed Hacks Work
Cheat Engine’s speed hack does not accelerate the CPU or alter the system clock. Instead, it manipulates time perception inside the target process by intercepting Windows timing APIs such asQueryPerformanceCounter, GetTickCount64, and timeGetTime.
Most game engines, including Unity, advance simulation based on per-frame delta time derived from these APIs. By scaling the reported elapsed time with a multiplier, the speed hack convinces the engine that more (or less) time has passed than actually has. Physics, animation, cooldowns, and AI logic then evolve faster or slower accordingly.
The transformation is conceptually simple: real time is sampled once, and all subsequent time queries are linearly scaled before being returned to the game. Unity’s Time.deltaTime ultimately traces back to QueryPerformanceCounter, making it the primary interception point in modern titles.
Anti-Cheat Detection Strategies
Because speed hacks do not modify kernel or hardware clocks, anti-cheat systems focus on inconsistencies rather than prevention. Common detection approaches include:-
Cross-clock validation
Comparing user-mode timing results (e.g.,
QueryPerformanceCounter) against kernel-level timers or CPU counters (RDTSC). Diverging rates indicate manipulation. - Kernel-time verification Sampling time directly in Ring 0, bypassing user-mode hooks entirely. Any mismatch between kernel-observed time and client-reported deltas is grounds for suspicion.
- Behavioral validation Detecting impossible outcomes: movement speed, cooldown completion, or simulation progress exceeding what authoritative time allows, especially in multiplayer environments.
- API integrity checks Verifying that timing functions are unhooked, reside on image-backed pages, and match expected instruction signatures.
What This Means for Game Developers
Cheat Engine is not magic. It is a systematic exploitation of documented operating system features, runtime metadata, and hardware capabilities. For developers, the takeaways are practical:- Client-side state is never authoritative
- Metadata is a trade-off between productivity and observability
- Security features raise effort, not guarantees
- Debugging infrastructure is indistinguishable from attack surface
