Read the metadata before the machine code
An IL2CPP build converts the C# to C++ and compiles the result into a native library, so no assembly holds the application code. What survives is a metadata file that describes the managed type system together with a string table. Type names, method names, field names and string literals are all there, and each method carries a recorded offset inside the native library. Reading that pair gives you the API surface without touching a single instruction.
Start with the string table. Base URLs, path segments, header names and JSON field names appear as literals, so a search for a scheme or a leading slash produces most of the backend inventory in one pass. That inventory answers the question this article asks, and it usually takes far less time than a disassembly session.
Extract three things from the package before reading any of them: the native library for the ABI you will analyze, the metadata file, and the assets that ship beside them. Pick one ABI and stay on it, because method offsets differ between architectures, so a parser output built for one library does not describe another.
Which types carry the backend calls
- Unity ships its own client. UnityWebRequest is the engine client and appears in metadata as a type with methods for setting a URL and attaching upload and download handlers. A method that builds one is a named call site.
- The managed base library supplies a second client. Projects that prefer it use HttpClient from the .NET base class library, with the same shape as any other C# client.
- A commercial asset adds its own request types. Packages such as BestHTTP or RestSharp bring their own method names, which become easy to search once you know the package is present.
- A serialiser defines the payload fields. JsonUtility is Unity's built-in option, and Newtonsoft.Json appears in larger projects. The fields on the request class are the fields on the wire.
The same list tells you how much of the API the app can reach. A project that funnels every call through one wrapper type has a single method to read, while a project that calls the transport directly from many screens spreads its endpoints across the metadata. Count the references to each transport type first, then read the busiest wrapper, because that is where the shared headers and the credentials are attached.
Pair a name with an address
A metadata parser produces dummy assemblies and a header that maps each managed method to an offset in the native library. That mapping is the bridge between what you can read and what the device executes. Load the native library in a disassembler, resolve the module base, and add the offset to reach the compiled method. From there, follow the references to the URL literal you found in the string table. The instructions around that literal show the header set, the request method and how the body is attached, even though no type names are attached to them.
Recover the payload shape
The request and response classes sit in the metadata with their fields. A field name and its serialisation attribute determine the JSON key, and a class that nests another class describes the object graph. Reading those classes answers what the backend expects before any live request is observed, which matters when a test account is not available yet. Where the app builds a payload by hand into a dictionary or a formatted string, the string table usually keeps the format string and the field names it inserts.
Use run time when reading is not enough
Some values are produced at run time and never appear in metadata. A signing key derived from device values, a token fetched at startup, or a header computed from the body all need observation. Instrumentation that knows the module base address can hook the compiled method at its offset and print the arguments. Hook the transport method instead when the signing routine is inlined or stripped, because the final URL, the headers and the body are all visible at that boundary. A protected build can encrypt the metadata or the string table, and a parser that rejects the file usually indicates encryption rather than corruption.
From inventory to client
The backend calls are the input to a client, and the client also has to reproduce what metadata does not describe: the order of calls, the state between them, token renewal and any request signing. Unity IL2CPP Android reverse engineering covers that work, and Reconstructing an API from a Unity IL2CPP app describes the wider method.
Related work
Reviewed 28 September 2026 · SReverse research desk