Tutorials

How to Analyze a Unity IL2CPP Android App with Il2CppDumper

Springmusk Springmusk 7 min read 0 comments
Abstract illustration of deconstructed mobile game binary with glowing code blocks and data streams flowing from a smartphone into class diagrams.

Open a Unity game APK in JADX and you will find almost no game code. Unity’s IL2CPP scripting backend compiles all C# into native machine code, so the DEX files contain only a thin loader while the real logic lives in a native library. Recovering that logic requires a different tool: Il2CppDumper, which rebuilds the full C# API surface from the metadata the build ships with.

TL;DR: Unity IL2CPP apps hide their code in native libraries, not DEX bytecode. Extract two files from the APK — the native library (libil2cpp.so) and global-metadata.dat — feed them to Il2CppDumper, and you get back a dump.cs with every class and method name, plus DLLs you can open in dnSpy or ILSpy. This post walks through the whole workflow, with what to do when the metadata is encrypted.

A note on scope: reverse engineering is a legitimate learning and research practice. Only analyze apps you own or have permission to analyze, and respect the licenses of the tools and software involved.

Why JADX comes back empty on Unity games

Most Android apps ship their logic as DEX bytecode, which tools like JADX turn back into readable Java. Unity games using the IL2CPP scripting backend are different: the C# game code is compiled ahead-of-time into native C++, packaged as libil2cpp.so inside the APK’s lib/ directory. JADX will decompile the APK fine — it just won’t find the game logic, because there is no DEX bytecode to decompile. (This is exactly the “when it won’t work” case from the JADX guide.)

The good news is that IL2CPP builds ship a second file, global-metadata.dat, which still contains every class name, method name, and field name from the original C# code. Il2CppDumper reads that metadata and reconstructs the full managed type system — turning anonymous native functions back into named methods like PlayerController.TakeDamage.

The two files you need

Unzip the APK (an APK is just a ZIP archive) and locate:

  1. The native library: lib/arm64-v8a/libil2cpp.so — the compiled game code. On 32-bit builds it lives under lib/armeabi-v7a/ instead. This is the “executable file” Il2CppDumper asks for.
  2. The metadata: assets/bin/Data/Managed/Metadata/global-metadata.dat — the class and method names. The exact path can vary slightly between Unity versions, but it is always under assets/.

You can pull an installed APK off a device with adb shell pm path <package> followed by adb pull, or work from an APK file you already have.

Step-by-step: dumping an IL2CPP APK

  1. Get Il2CppDumper. Download a release build; it supports ELF, ELF64, Mach-O, PE, NSO, and WASM formats, and Unity versions from 5.3 through 2022.2. There are also maintained forks that extend support to newer Unity releases.
  2. Run the dump. Point it at the two files and an output directory:
    Il2CppDumper.exe libil2cpp.so global-metadata.dat ./dump-output

    The tool may ask for the exact Unity version the game was built with. If you don’t know it, check the APK for version strings — many builds include a unity_builtin_extra file or embed the version in the library itself.

  3. Choose an extraction mode. The interactive prompt offers several modes; Auto(Plus) is the recommended starting point. If the output looks wrong, try a different mode — results vary by Unity version.
  4. Inspect the output directory. A successful run produces the artifacts in the table below.

What Il2CppDumper gives you

Output What it is What to do with it
dump.cs The full recovered C# API surface: every class, method signature, and field Search it with grep or a text editor to find interesting classes and method names
DummyDll/ Restored .NET DLLs built from the metadata Open in dnSpy or ILSpy to browse the API like normal .NET assemblies
ida.py / ida_with_struct.py IDA Pro scripts that rename functions and apply structure info Run in IDA after loading libil2cpp.so to turn sub_XXXX into real method names
ghidra.py Ghidra equivalent of the IDA script Run in Ghidra with the native library loaded
il2cpp.h C header with recovered structure definitions Feed to ida_with_struct.py or use as a reference for native analysis
script.json Machine-readable dump data Drive the IDA/Ghidra scripts, or parse it with your own tooling
stringliteral.json Every string literal in the binary Grep for URLs, API endpoints, and hardcoded secrets

Reading the recovered code

Start with dump.cs. It is a large file, but it is searchable — look for class names related to what you want to understand (networking, purchases, save data). Note that Il2CppDumper restores declarations, not method bodies; the actual logic still lives in the native code. The dump tells you what exists and how it connects, which is usually enough to navigate.

For a browsable view, open the DummyDll folder in ILSpy or dnSpy. These are stub assemblies — method bodies are empty — but the type hierarchy, inheritance, and cross-references are intact, which makes exploring far easier than grepping a flat file.

To read actual logic, load libil2cpp.so into Ghidra or IDA, run the generated script, and the disassembly gets annotated with real method and class names. This is where reverse engineering proper begins: you go from staring at anonymous subroutines to reading named game code in assembly.

Don’t overlook stringliteral.json. It contains every string the binary references — endpoints, error messages, configuration keys. A quick search there often answers “where does this app talk to?” faster than any disassembly.

When the metadata is encrypted

Some builds — particularly games with anti-tamper protection — encrypt or obfuscate global-metadata.dat. Il2CppDumper will fail on these files, and no extraction mode will fix it because the input itself is unreadable.

The standard workaround is to dump the metadata from memory at runtime: launch the app (on a rooted device or emulator), wait until the loader has decrypted the metadata into memory, then dump the process memory and carve out the decrypted global-metadata.dat. Frida is the usual tool for this — attach to the process, locate the decrypted metadata region after the metadata-load completes, and write it to disk. Only then will Il2CppDumper accept it as input.

This is the point where static analysis hands off to dynamic analysis. If you are not set up for that yet, treat encrypted metadata as a hard stop rather than guessing at decryption.

Il2CppDumper vs. Cpp2IL

Il2CppDumper Cpp2IL
Primary output dump.cs declarations, stub DLLs, IDA/Ghidra scripts Decompiled DLLs you can open directly
Best for Navigating the full API surface and annotating native disassembly Quickly getting DLLs into dnSpy with a one-liner (--just-give-me-dlls-asap-dammit)
Native analysis support Yes — generates scripts and headers for IDA, Ghidra, Binary Ninja Limited — focused on the managed side
Encrypted metadata Requires a decrypted global-metadata.dat (dump from memory first) Same requirement

They are complementary, not competing: use Cpp2IL when you want DLLs fast, Il2CppDumper when you want the full picture including native-side annotation.

Key takeaways

  • JADX can’t help with IL2CPP games — the code is native, not DEX. Recognize this early by checking for libil2cpp.so in the APK’s lib/ directory.
  • You only need two files: libil2cpp.so and global-metadata.dat. Everything else is derived.
  • The dump restores names, not logic. dump.cs and the stub DLLs map the territory; reading behavior still means native disassembly with the generated scripts applied.
  • stringliteral.json is the fastest win for finding endpoints and hardcoded values without touching a disassembler.
  • Encrypted metadata means going dynamic. Dump it from memory at runtime with Frida rather than fighting the encryption statically.

References

Join the discussion

Your email address will not be published. Required fields are marked *