Anti-Piracy

Is IL2CPP Really Secure? — How It Gets Reverse-Engineered and What to Do About It

The Myth and Reality of "IL2CPP Means We're Safe"

In conversations about security with Unity game teams, one phrase comes up again and again: "We build with IL2CPP, so our code is reasonably protected." It's true that IL2CPP offers better code protection than Mono — but "reasonably protected" only tells half the story.

IL2CPP converts C# bytecode into native code, making C# IL harder to read directly. The problem is that attackers don't need to read IL at all. A single metadata file that is inevitably included in every IL2CPP build is enough to reconstruct most of the game's class and method structure, giving attackers a map to guide runtime hooking or binary patching.

This article examines how IL2CPP reverse engineering actually works in practice, why IL2CPP alone is not enough, and why build integrity verification is essential.

The Structure of an IL2CPP Build — What Gets Left Behind

The IL2CPP build process works like this:

[C# source code]
  └─> IL2CPP transpiler → generates C++ source
        └─> native compiler → libil2cpp.so / GameAssembly.dll (native binary)
              + global-metadata.dat (metadata file)

The native binary (libil2cpp.so / GameAssembly.dll) is compiled to machine code, so the original C# structure isn't immediately apparent from disassembly alone.

However, global-metadata.dat is always generated and packaged alongside it. This file contains class names, method names, field names, string literals, type information — the entire skeleton of the game's code. The IL2CPP runtime needs this metadata for reflection, serialization, and other features, so it must be present in every production build.

For an attacker, this file is a map through the otherwise impenetrable maze of the native binary.

How IL2CPP Reverse Engineering Actually Works

Il2CppDumper (libil2cpp.so + global-metadata.dat) ──> DummyDll (structure restored) ──> offset located ──> hooking / patching

Step 1: Metadata extraction and dummy assembly restoration

A publicly available tool like Il2CppDumper takes libil2cpp.so and global-metadata.dat as input and produces DummyDll — an assembly with no actual implementation, but with all class, method, and field names and signatures restored. Opening this in a .NET decompiler like dnSpy lets you browse the original project structure as a tree.

Step 2: Locating the target method offset

The dump process also outputs a mapping (script.json) that shows exactly where in the binary each method lives (its RVA). This makes it trivial to pinpoint the precise location of security-sensitive methods like CheckSpeedHack, ValidateInventory, or VerifyIAP.

Step 3: Runtime hooking or binary patching

With the offset known, two attack paths open up:

  • Runtime hooking (dynamic tampering): Tools like Frida intercept the method's entry point and manipulate it to always return "no issue detected."
  • Binary patching (static tampering): After unpacking the APK, branch instructions (B, BL, etc.) at the target offset in libil2cpp.so are overwritten with NOPs to skip the check. The modified APK is then re-signed and redistributed.

The most alarming aspect is that all of this is achievable with publicly available tools, requiring no advanced hacking expertise.

What IL2CPP Cannot Protect Against

IL2CPP's protection is essentially limited to "making it harder to read and modify C# code directly." It does not cover:

  • Metadata-based structure recovery: As long as global-metadata.dat is bundled unprotected, class and method structure can be restored at any time.
  • Runtime dynamic hooking: Whether the code is C# or native, runtime hooking intercepts functions in memory — the compilation method is irrelevant.
  • Binary patching and repackaging: IL2CPP does not prevent modified binaries from being re-signed and redistributed as MOD APKs.

Build Integrity Verification — Why It's Needed and How It Works

To address IL2CPP's structural limitations, you need a system that actively verifies at runtime that "the app currently running is identical to the original that the developer signed and distributed."

(1) Signature integrity verification A legitimate build carries the developer's unique certificate signature. A tampered MOD build will necessarily have a different signature. At launch, the current signature fingerprint is compared against the known original; any mismatch is flagged as a tampered build.

(2) Binary integrity verification (hash check) The hash of critical binary regions — such as libil2cpp.so — is computed at build time and stored. At runtime, the same region is hashed again and compared. Even a single-byte patch is detected immediately.

(3) Isolating verification logic in the Native layer If the integrity verification code itself sits in the C# layer, it becomes the first target — metadata dumping and hooking will neutralize it before anything else. Moving verification logic into a separately obfuscated Native C++ layer makes it substantially harder for attackers to locate and bypass the guard.

OZero Security implements signature and build integrity verification, and metadata tampering detection, entirely in the Native C++ layer. The verification logic runs inside an obfuscated binary, isolated from the C# layer. With the Plus Add-on's per-app Native Variant, the binary structure changes with every build, preventing analysis of one game from being reused against another. The Pro Add-on's telemetry allows abnormal clients to be identified and acted upon server-side in real time.

Summary

  • IL2CPP is a valuable security measure for Unity games, but global-metadata.dat exposes game structure, and runtime hooking and binary patching can bypass verification logic.
  • Most tampered builds involve re-signing, making signature and binary integrity verification the strongest first-line detection signal.
  • If this verification logic is exposed in the C# layer, it can itself be patched through reverse engineering. Isolating it in the Native layer is what makes the attack genuinely costly.

Trusting IL2CPP and relying solely on IL2CPP are two different things. Use the compilation switch as a starting point — but pair it with metadata protection, build integrity verification, and Native-layer isolation to build a defense that actually holds.

Fill the gaps in your IL2CPP build with OZero Security's Native-layer build integrity verification.

Related reading