Skip to lesson
Game Hacking Academy
Preparing lesson text…
Exit reader mode
Voice and speed

Preparing read-aloud controls…

Lesson 15.7 Listening edition

Control Gaps in Toy Defenses

Three small defenses show how an apparently valid check can miss the path that produces an effect, and how to repair its invariant.

About 32 minutes

The earlier chapters showed how a value, hook, or object layout can change while a game still appears to run normally. The next question is whether a check guards the effect it claims to guard. A perfect module inventory cannot rescue a rule that checks the wrong path. Three deliberately weak rules in a small program make that gap visible before we examine game logic controls and further trust-boundary failures.

Section: Research the failure without targeting a real defense.

This lesson uses a tiny program with no Windows APIs, drivers, processes, networking, persistence, or security-product interaction. The “attacker” is a test function calling an intentionally bad policy. That lets us study the computer-science shape of an evasion while keeping the recipe useless against a real anti-cheat or endpoint defense. 🧪

The buildable source is toy_evasion_lab.rs. Run it with:

Terminal window

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

Each exercise follows the same research loop:

Lesson diagram

Section: Recipe 1: test a name-based rule with an equivalent command.

The toy has three commands:

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

Both WriteMemory and ApplyPatch change state. The weak policy blocks only the command whose name it recognizes:

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

Section: Reproduce the toy evasion.

Set the lab to read-only mode.

Ask the weak policy about WriteMemory; it says no.

Ask about ApplyPatch; it says yes.

Observe that the second spelling reaches the same kind of state-changing effect the policy claimed to prevent.

A read-only policy that checks names instead of effects.
Lesson diagram

The weakness is coverage by spelling. Adding more forbidden names would become an endless denylist. Classify the effect instead:

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

Look at what changed about the question being asked. The weak version asked “which command is this?”, which has as many answers as there are names. The enum asks “what does this command do to state?”, and that has exactly two answers. A question with a fixed number of answers can be answered completely; a question about names cannot.

Alternative: Group the toy commands by what they change, like sorting tools by their job rather than by the name printed on the handle.

effect_of is now the single place that judgement lives. Adding a command to the lab means classifying it there, and the match makes that unavoidable — leave the new variant out and the code does not compile. The denylist that would have needed extending forever has become a decision the compiler insists you make once.

The original policy blocks only the command named WriteMemory; every other name passes, even when writes are disabled. The final if is the call-site fragment inside the lab’s command handler: it shows the shared execution boundary where that decision is used.

Before: block only one command name

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

Classify the effect of each command once, then apply the setting to every state-changing effect. The enum and exhaustive match keep new command variants from silently falling outside the rule.

Improved code

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

Show the diff

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

Takeaway: permission checks should follow what an operation does, not just what it is called.

The regression test iterates over every state-changing enum variant. Adding a new variant then forces the programmer to classify it.

Section: Recipe 2: test a change between validation and use.

The weak function checks a target build, calls a lab-only hook that simulates a change, and then writes:

Narration: An accepted check can go stale before use. Keep the toy’s changed-between-stages case as a regression test for the repaired boundary.

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

Section: Reproduce the toy evasion.

Start with build ID 7 and byte hexadecimal 9 0.

Let the first build check succeed.

In the supplied test hook, replace the build ID with 8.

Observe that the weak function still writes hexadecimal C C because it uses an old decision.

Lesson diagram

This is a time-of-check/time-of-use gap. The repair validates the build and expected old byte immediately beside the side effect:

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

For a real game process, Windows can still change the target between two calls. You cannot make another process freeze by wishing. Keep the interval small, compare expected bytes, report mismatch as a normal refusal, and make the operation reversible.

Section: Recipe 3: test a state-changing branch the logger misses.

The third toy control logs allowed work but returns early for denied work:

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

Section: Reproduce the toy evasion.

Submit WriteMemory while writes are disabled.

Confirm that the policy denies it.

Inspect the event list; it is empty.

A reviewer looking only at the log cannot distinguish “nothing happened” from “a prohibited request was blocked.”

Lesson diagram

This is a telemetry gap, not a control bypass. It still matters because a denial that leaves no event hides repeated mistakes and makes the control difficult to test. The fix computes the decision first and then logs it, whichever way it went:

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

Do not log secrets, raw memory, authentication keys, or giant payloads. A useful event records time, operation category, verified target identity, allow/deny decision, and a bounded reason code.

Section: The anatomy of a bypass.

Published evasion write-ups are usually operational recipes. Underneath the commands, each one describes the same parts, and this lab’s bypass contains every one of them:

Lesson table

Described by its parts, a bypass is a gap between what a control promised and what it checked. That is something a defender can close, and nothing in the description is a step that could be aimed at someone else’s machine.

Section: A detector observes proxies, not intent.

No program can directly read a person’s intent. A detector measures observable signals—for example a debug flag, an unusual timing gap, a changed byte, an unexpected handle, or a forbidden state transition—and applies a rule. That rule can be wrong in two directions:

Lesson table

This is why a strong repair protects the important effect boundary instead of trusting one environmental clue. In the toy lab, “state must not change in read-only mode” is an invariant. A process name, timing threshold, or single flag is only a signal that often accompanies a violation; it is not the invariant itself.

In brief: Protect the effect you promise to prevent; a familiar name or environmental signal may only be a clue about that effect.

The four-outcome table above is called a confusion matrix. Testing a rule against a controlled fixture fills it in one condition at a time: attached and detached runs, slow and fast machines, and the diagnostic tools a legitimate user is expected to run. A rule that fires on a legitimate debugger or profiler lands in the false-positive row, and that is a compatibility bug worth fixing rather than proof that every observer is hostile.

Use signal families as a checklist for honest tests and false-positive review, not as a list of concealment tricks. The goal is to understand what the detector actually measures and then protect the real invariant at the shared boundary.

Section: What the three failures share.

A name can change, an earlier validation can go stale, and a denied decision can disappear from the record. Authorize the effect at its shared boundary, record both allowed and denied outcomes, and keep the failed case as a regression test. The toy boundary makes each failure observable without conflating it with the behavior of a real game’s defenses.