About reversing-unity-il2cpp
A reverse-engineering skill for mobile security assessors analyzing Unity IL2CPP apps. It covers build identification, metadata recovery, static and runtime inspection, and recurring security findings.
Key features
- 01Identify Mono and IL2CPP builds
- 02Combine metadata with native binaries
- 03Use Il2CppDumper and Il2CppInspector
- 04Inspect runtime behavior with Frida
- 05Check credentials, networking, and local storage
Use cases
- 01Analyze app logic in Unity APKs or IPAs
- 02Locate API, authentication, or purchase flows
- 03Recover in-memory metadata from protected builds
- 04Assess client-authoritative logic and certificate pinning
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ trilwu/ secskills --skill reversing-unity-il2cppAfter installing, type/reversing-unity-il2cppin Claude Code to confirm it loaded.
Details
From the project READMEReversing Unity IL2CPP
Unity ships in two very different shapes, and identifying which one you have is the entire first decision. A Mono build hands you decompilable .NET assemblies. An IL2CPP build compiles C# to C++ to native code, and the C# metadata lives in a separate file that you must recombine with the binary before anything is readable.
When to Use
- The APK contains
assets/bin/Data/Managed/Metadata/global-metadata.datandlib/arm64-v8a/libil2cpp.so - The APK contains
assets/bin/Data/Managed/Assembly-CSharp.dll(Mono build) - The IPA contains
Frameworks/UnityFramework.framework jadxshows onlyUnityPlayerActivityand Unity plumbing- You need to recover game or app logic, anti-cheat behaviour, or the API layer
When NOT to Use
- Flutter or React Native — use
reversing-flutter-appsorreversing-react-native-apps - Ordinary native apps — use
testing-mobile-applications - Desktop game binaries with no Unity markers — use
analyzing-binaries - Building cheats or bypassing anti-cheat in live multiplayer services — out of scope; that is service abuse, not assessment
Identify the Build
unzip -l target.apk | rg 'global-metadata|libil2cpp|Assembly-CSharp|libmono'
| Present | Build | Difficulty |
|---|---|---|
Assembly-CSharp.dll, libmono*.so | Mono | Easy — decompile the DLL directly |
global-metadata.dat + libil2cpp.so | IL2CPP | The main path below |
Neither, but UnityFramework | iOS IL2CPP | Same as IL2CPP; extract from the decrypted IPA |
Mono builds are a short job. Pull the assembly and open it:
unzip -j target.apk 'assets/bin/Data/Managed/Assembly-CSharp.dll' -d ./out
# dnSpy / dnSpyEx / ILSpy / dotPeek — full C# source recovery, and dnSpy can edit
ilspycmd ./out/Assembly-CSharp.dll -o ./decompiled
Everything below is for IL2CPP.
Recombining Metadata with the Binary
IL2CPP splits the information: libil2cpp.so holds the compiled code,
global-metadata.dat holds the C# type system — class names, method names,
field names, string literals. Neither is useful alone. The tools' job is to
match them and produce symbols.
unzip -j target.apk 'lib/arm64-v8a/libil2cpp.so' \
'assets/bin/Data/Managed/Metadata/global-metadata.dat' -d ./in
# Il2CppDumper: the standard first attempt
Il2CppDumper ./in/libil2cpp.so ./in/global-metadata.dat ./out
Typical output and what it is for:
| Artifact | Use |
|---|---|
dump.cs | Every class, method, and field with addresses — read this first |
DummyDll/ | Stub .NET assemblies; open in dnSpy/ILSpy to browse the type system comfortably |
script.json | Symbol import for IDA/Ghidra — applies names to libil2cpp.so |
stringliteral.json | String constants: URLs, keys, messages |
Il2CppInspector is the alternative when Il2CppDumper fails; it supports a
different range of Unity versions and emits Ghidra and IDA scripts plus a
richer C++ header. Try both before concluding a build is unsupported.
Version mismatch is the usual failure. The metadata format changes across Unity releases; the tool asks for or infers the version. Read the Unity version first and feed it explicitly:
strings ./in/libil2cpp.so | rg -m3 '20[0-9]{2}\.[0-9]+\.[0-9]+'
strings ./in/global-metadata.dat | head -5
Protected builds encrypt or scramble global-metadata.dat — the header
magic will be wrong and the dumpers will refuse. Signs: the file does not
start with the expected metadata magic, or entropy is uniformly high. The
practical route is dynamic: let the app decrypt the metadata itself, then dump
it from memory once loaded.
# Dump the decrypted metadata from a running process
frida -U -f com.target.app -l dump-metadata.js
# Then feed the dumped file to Il2CppDumper as normal
That pattern — let it unpack itself, dump from memory — is the same one used
for packed malware, and it is almost always cheaper than defeating the
protection statically. See analyzing-binaries.
Working the Recovered Code
- Read
dump.csfor the type map. Search for the classes that matter: anything named*Manager,*Service,*Api,*Network,*Auth,*Purchase,*Config. - Load
script.jsoninto IDA or Ghidra solibil2cpp.soshows C# method names instead ofsub_1A2B3C. Without this step the disassembly is unreadable; with it, it reads like decompiled C#. - Check
stringliteral.jsonfor endpoints and keys. Same reasoning as every other framework — string constants survive everything. - Hook at runtime with Frida using the addresses from
dump.cs. Method addresses are relative to thelibil2cpp.sobase, so resolve the module base and add the offset.
// Pattern: resolve base, add the RVA from dump.cs, intercept
const base = Module.findBaseAddress('libil2cpp.so');
Interceptor.attach(base.add(0x1A2B3C), {
onEnter(args) { console.log('called with', args[1]); },
onLeave(ret) { console.log('->', ret); }
});
Security-Relevant Findings
For an assessment rather than a curiosity exercise, the recurring issues:
- Hardcoded credentials and endpoints in
stringliteral.json— backend keys, analytics tokens, cloud storage credentials. - Client-authoritative logic. Currency, entitlements, and validation done in C# and trusted by the server. Verify by calling the API directly.
- Weak or absent certificate pinning. Unity's networking layer is often
configured permissively; check
UnityWebRequestusage. - Local save and config tampering.
PlayerPrefsand local save files storing values the server trusts. - Receipt validation done client-side for in-app purchases.
Rationalizations to Reject
- "jadx shows nothing, so it's protected." Unity apps have almost nothing in the dex by design.
- "The dumper failed, so the build can't be analyzed." Try the other dumper, supply the Unity version explicitly, and if metadata is encrypted, dump it from memory.
- "IL2CPP is compiled, so the logic is safe." The metadata ships alongside it. That is the whole point of the format.
- "The client validates the purchase." Then the purchase is not validated.
- "I'll read the raw arm64." Apply
script.jsonfirst. The same function is unreadable before and routine after.
References
testing-mobile-applications— platform storage, permissions, and native layertesting-apis— the backend, where client-authoritative logic becomes a findinganalyzing-binaries— memory dumping and anti-analysis for protected builds- Il2CppDumper, Il2CppInspector, dnSpy/ILSpy, Frida, IDA/Ghidra