From ac757ccd8ff496cb2b78fd60137178a54eabf0e5 Mon Sep 17 00:00:00 2001 From: pancake Date: Wed, 6 May 2026 21:05:45 +0200 Subject: [PATCH] Update readme and minor clang-format fixup --- Makefile | 19 +++-- README.md | 181 +++++++++++++++++------------------------- doc/future.md | 1 + doc/r2unity.md | 1 - r2plugin/config.mk | 2 +- src/r2/Makefile | 10 +++ src/r2/core_r2unity.c | 16 +++- 7 files changed, 109 insertions(+), 121 deletions(-) create mode 100644 src/r2/Makefile diff --git a/Makefile b/Makefile index e1f1ec8..7587e94 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,12 @@ EXEC = r2unity + +SOEXT = $(shell r2 -H R2_LIBEXT) +ifeq ($(SOEXT),dylib) +PLUGIN_SHFLAGS = -dynamiclib +else +PLUGIN_SHFLAGS = -shared +endif + CORE_PLUGIN = src/r2/core_r2unity.$(SOEXT) BIN_PLUGIN = src/r2/bin_r2unity.$(SOEXT) PLUGINS = $(CORE_PLUGIN) $(BIN_PLUGIN) @@ -15,13 +23,6 @@ CORE_PLUGIN_LDFLAGS = $(shell pkg-config --libs r_core 2>/dev/null || echo "") BIN_PLUGIN_CFLAGS = -Wall -Wextra -g -fPIC $(shell pkg-config --cflags r_bin 2>/dev/null || echo "") BIN_PLUGIN_LDFLAGS = $(shell pkg-config --libs r_bin 2>/dev/null || echo "") -SOEXT = $(shell r2 -H R2_LIBEXT) -ifeq ($(SOEXT),dylib) -PLUGIN_SHFLAGS = -dynamiclib -else -PLUGIN_SHFLAGS = -shared -endif - R2_USER_PLUGINS = $(shell r2 -H R2_USER_PLUGINS 2>/dev/null) LIB_SRCS = $(wildcard src/lib/*.c) @@ -39,7 +40,7 @@ plugin: $(PLUGINS) install-plugin: $(PLUGINS) @[ -n "$(R2_USER_PLUGINS)" ] || (echo "r2 not found; cannot resolve R2_USER_PLUGINS"; exit 1) mkdir -p "$(R2_USER_PLUGINS)" - cp $(PLUGINS) "$(R2_USER_PLUGINS)/" + cp -f $(PLUGINS) "$(R2_USER_PLUGINS)/" uninstall-plugin: @[ -n "$(R2_USER_PLUGINS)" ] || (echo "r2 not found; cannot resolve R2_USER_PLUGINS"; exit 1) @@ -56,8 +57,6 @@ user-install: install-plugin user-uninstall: uninstall-plugin rm -f "$(R2PM_BINDIR)/$(EXEC)" - - $(EXEC): $(OBJS) $(CC) -o $@ $^ $(LDFLAGS) diff --git a/README.md b/README.md index 1c7c485..936b692 100644 --- a/README.md +++ b/README.md @@ -1,132 +1,97 @@ # r2unity -logo +[![CI](https://github.com/radareorg/r2unity/actions/workflows/ci.yml/badge.svg)](https://github.com/radareorg/r2unity/actions/workflows/ci.yml) -`r2unity` is a commandline tool and radare2 plugin for inspecting Unity apps. +r2unity logo -Unity apps are written in **C#**, compiled into **MSIL** and then transpiled into **C++** with the il2cpp program. +`r2unity` is a command-line tool and radare2 plugin set for inspecting Unity +IL2CPP builds. It parses `global-metadata.dat`, correlates it with the native +IL2CPP binary, and exposes managed metadata for reverse engineering. -The resulting binary is an AOT executable that picks from the `global-metadata.dat` file all the strings, symbols, classes, structs and many other ecma335 metadata from DotNet. +## Highlights -With this plugin we load all that information and expose it as plaintext, json or inside radare2, ready for proper analysis. - -For idiomatic decompilation it is recommended to use **decai** with `-e decai.lang=csharp`. - ---pancake - -## Features - -* Support from il2cpp from v24 to v31, plus Unity 6 metadata v39 -* Support 32 and 64bit ELF, FatMACHO, MACHO and PE -* Heuristic method-pointer scanner -* Works on Windows, Linux, macOS, iOS and Android -* Mainly validated on arm64, but should support Intel too -* Parse headers, strings, literals, method names, types, images, assemblies -* Decodes managed `ldstr` string literals from the payload table region -* Find il2cpp companion files relative to the main app executable (`-D`) -* Interop (managed <=> native) listings (PInvoke and reverse-PInvoke calls) -* Support SBOM listings, emit CycloneDX 1.5 JSON with the assemblies (`-S`) - -radare2 plugin support: - -- `make plugin` builds `src/r2/core_r2unity.$(so|dylib)`. -- `make install-plugin` copies the plugin into the user radare2 plugin - directory. -- The plugin adds `r2unity.*` eval variables and can auto-detect metadata - and IL2CPP library paths from the currently loaded binary. -- Method-symbol mode can apply flags/comments directly to the r2 session, - print r2 commands, or return JSON. +- Parses IL2CPP metadata wire versions v24.1+ through v35, plus v38/v39 + metadata used by recent Unity 6 builds. +- Detects companion files for iOS, Android extracted APKs, macOS, Windows, + Linux, and flat fixture layouts. +- Recovers managed images, assemblies, types, methods, method flags, and `ldstr` + string literals. +- Finds method-pointer tables heuristically in ELF, Mach-O, and PE binaries. +- Lists P/Invoke and v29+ reverse-P/Invoke metadata, and emits CycloneDX 1.5 + SBOMs for managed assemblies. +- Provides both a core r2 command plugin and an `r_bin` plugin for direct + `global-metadata.dat` inspection. ## Build -The commandline tool and the r2 plugin require **pkg-config** and **radare2** libraries to work: +`r2unity` requires radare2 development files available through `pkg-config`. ```sh make make plugin -make user-install # install user-plugin and cli in the r2pm path +make user-install ``` -Most users may just run `r2pm -ci r2unity` to get the repo cloned, built and installed. +Most users can install it through r2pm: -After installing the plugin, open a Unity binary in r2 and use: +```sh +r2pm -ci r2unity +``` + +`make` builds the CLI. `make plugin` builds `core_r2unity` and `bin_r2unity`. +`make user-install` installs both the CLI and plugins for the current user. + +## CLI + +The normal inputs are the native IL2CPP binary and the matching +`global-metadata.dat`. + +```sh +# detect companion files and platform +./r2unity -D /path/to/unity-build + +# compact metadata summary +./r2unity -j /path/to/GameAssembly.dll /path/to/global-metadata.dat + +# recover method flags/comments as r2 commands +./r2unity -f /path/to/GameAssembly.dll /path/to/global-metadata.dat > methods.r2 + +# list managed strings, interop metadata, or managed-assembly SBOM data +./r2unity -z /path/to/global-metadata.dat +./r2unity -P -j /path/to/GameAssembly.dll /path/to/global-metadata.dat +./r2unity -R -j /path/to/GameAssembly.dll /path/to/global-metadata.dat +./r2unity -S /path/to/GameAssembly.dll /path/to/global-metadata.dat > sbom.json +``` + +## radare2 + +After installing the plugins, open a Unity binary in r2 and use: ```text r2unity? show help +r2unity-D detect and cache companion file paths +r2unity-i[j] show metadata summary +r2unity-s apply managed method flags/comments +r2unity-s* print the r2 commands instead of applying them +r2unity-z[j] list managed string literals +r2unity-P[*j] list P/Invoke entries +r2unity-R[*j] list reverse-P/Invoke entries +r2unity-S emit managed-assembly CycloneDX JSON ``` -And you may also have it available via `r2pm -r r2unity` unless you setup the *PATH* environ. +Set `r2unity.metadata` and `r2unity.library` manually when auto-detection is not +enough. The `bin_r2unity` plugin also lets radare2/rabin2 treat +`global-metadata.dat` as a binary format, exposing sections, strings, symbols, +classes, imports, libraries, and header fields. +## Current Limits -## CLI Usage +- v24.0 metadata, v36/v37 metadata, and WebAssembly are not supported. +- Method-pointer recovery is heuristic; manual `-a` / `-c` pointer reads are + not implemented yet. +- P/Invoke and reverse-P/Invoke output is metadata-first and does not fully + recover native wrapper addresses or every `DllImportAttribute` detail. +- SBOM output covers managed assemblies only, not native dependencies or file + hashes. -```text -Usage: ./r2unity [options] - -Options: - -a 0xADDR Read the method pointer table starting at virtual address 0xADDR - -c N Read N pointer entries (pair with -a) - -D Detect companion files from the given executable path and exit - -f Fast path: auto-detect ELF/Mach-O/PE and scan method pointers - -h Show help and exit - -j One-line JSON status, or JSON output with -P - -l N Limit emitted entries to N - -P Enumerate P/Invoke (managed -> native) methods - -q Quiet mode: omit banner and informational comments - -r Emit r2 script commands (flags + comments); pairs with -P/-R - -R Enumerate reverse-P/Invoke (native -> managed) methods (v29+) - -S Emit a CycloneDX SBOM (JSON) of the managed assemblies - -v Verbose debug tracing on stderr - -z Enumerate managed string literals (`ldstr`) from metadata -``` - -Normal symbol recovery needs two inputs but the `-D` will automatically resolve it for you. - -1. The native IL2CPP binary: - - iOS: `UnityFramework` - - Android: `libil2cpp.so` - - macOS: `GameAssembly.dylib` - - Windows: `GameAssembly.dll` - - Linux: `GameAssembly.so` -2. The matching `global-metadata.dat` - -## radare2 Plugin - -After installing the plugin, open a Unity binary in r2 and use: - -```console -r2unity-i metadata summary -r2unity-ij metadata summary as JSON -r2unity-s apply managed method flags/comments to the current r2 session -``` - -The commands suffixed with `*` will print r2 commands, so you can run them by prefixing it with a dot `.`: - -```text -.r2unity-R* -``` - -Plugin configuration variables: - -```text -r2unity.metadata path to global-metadata.dat -r2unity.library path to IL2CPP native library -``` - -If either variable is empty, the plugin tries to detect it from the loaded binary path using the `-D` approach. - - -## Known Limitations - -- `-a` / `-c` are wired into the CLI, but `r2unity_read_method_pointers_at()` is still a stub. -- Method-pointer discovery is heuristic and can lock onto a wrong count-prefixed table on unfamiliar binaries. -- No full `Il2CppCodeRegistration` or `Il2CppMetadataRegistration` structural recovery exists yet. -- P/Invoke currently identifies managed extern methods, but does not decode `DllImportAttribute` DLL names, entry-point names, charset, calling convention, or `SetLastError`. -- Reverse-P/Invoke currently identifies annotated v29+ managed methods, but does not recover native wrapper VAs from `reversePInvokeWrappers`. -- Pre-v29 reverse-P/Invoke attribute argument recovery is not implemented; those values live in native custom-attribute generator thunks. -- Interop wrapper recovery from `Il2CppInteropData` is not implemented. -- SBOM output is managed-metadata-only. It does not enumerate native dependencies, imports, build IDs, UUIDs, PDB GUIDs, code signatures, toolchain data, or file hashes. -- Field, parameter, property, event, generic, interface, vtable, RGCTX, default-value, marshaling, and field-offset tables are declared in the header but not exposed by the CLI/plugin yet. -- Type signatures and parameter names are not printed yet, so methods are named with an argument count rather than a full C# signature. -- WebAssembly is unsupported. -- The plugin and CLI SBOM paths are similar but not identical; the CLI currently emits more assembly properties. +Deep technical notes live in `doc/`. diff --git a/doc/future.md b/doc/future.md index 538f610..462b20e 100644 --- a/doc/future.md +++ b/doc/future.md @@ -814,6 +814,7 @@ Ordered by RE value vs. implementation effort: - **ELF**: packed Android relocations (`DT_ANDROID_RELA = 0x60000010`, `DT_ANDROID_RELR = 0x6fffe000`) not handled — Play-Store builds compressed with packed relocs read as "no + relocations". `DT_VERSYM`/`DT_VERNEED` not consulted (would help identify Unity version from the `.so` directly). `ET_EXEC` vs `ET_DYN` branching (we assume PIC shared objects). diff --git a/doc/r2unity.md b/doc/r2unity.md index 5d692ca..eb33fb4 100644 --- a/doc/r2unity.md +++ b/doc/r2unity.md @@ -1126,7 +1126,6 @@ Data present on disk / in the binary but **not yet consumed**: - `reverse-pinvoke-count`, `reverse-pinvoke-list` — `-R` - `strings-list` — `-z` - ## 9. Further reading Primary references used by r2unity's decoders: diff --git a/r2plugin/config.mk b/r2plugin/config.mk index d91d0a3..611291a 100644 --- a/r2plugin/config.mk +++ b/r2plugin/config.mk @@ -6,4 +6,4 @@ EXTERNAL_PLUGINS+=r2unity r2unity: p/r2unity p/r2unity: - cd p && git clone https://github.com/trufae/r2unity + cd p && git clone https://github.com/radareorg/r2unity diff --git a/src/r2/Makefile b/src/r2/Makefile new file mode 100644 index 0000000..de87cde --- /dev/null +++ b/src/r2/Makefile @@ -0,0 +1,10 @@ +all: + $(MAKE) plugin + +plugin user-install user-uninstall: + $(MAKE) -C ../.. $@ + +clean: + rm -f *.dylib *.so *.dSYM + +.PHONY: all clean diff --git a/src/r2/core_r2unity.c b/src/r2/core_r2unity.c index 2852f2d..ab8c981 100644 --- a/src/r2/core_r2unity.c +++ b/src/r2/core_r2unity.c @@ -4,9 +4,23 @@ #include #include "../lib/lib.h" +// clang-format off static const char *g_help_msg[] = { - "Usage:", "r2unity[-subcmd]", " Unity IL2CPP analyzer", "r2unity", "", "show this help", "r2unity?", "", "show this help", "r2unity-D", "", "auto-detect companion files from current binary path", "r2unity-i", "[j]", "summary (metadata version, type/method counts)", "r2unity-s", "[*j]", "apply/list managed method symbols as flags + comments", "r2unity-z", "[j]", "list managed string literals", "r2unity-P", "[*j]", "list P/Invoke (managed -> native)", "r2unity-R", "[*j]", "list reverse-P/Invoke (native -> managed)", "r2unity-S", "", "emit CycloneDX SBOM (JSON)", "Variables:", "", "", "r2unity.metadata", "", "path to global-metadata.dat", "r2unity.library", "", "path to IL2CPP native library", NULL + "Usage:", "r2unity[-subcmd]", " Unity IL2CPP analyzer", + "r2unity", "", "show this help", + "r2unity?", "", "show this help", + "r2unity-D", "", "auto-detect companion files from current binary path", + "r2unity-i", "[j]", "summary (metadata version, type/method counts)", + "r2unity-s", "[*j]", "apply/list managed method symbols as flags + comments", + "r2unity-z", "[j]", "list managed string literals", + "r2unity-P", "[*j]", "list P/Invoke (managed -> native)", + "r2unity-R", "[*j]", "list reverse-P/Invoke (native -> managed)", + "r2unity-S", "", "emit CycloneDX SBOM (JSON)", "Variables:", "", "", + "r2unity.metadata", "", "path to global-metadata.dat", + "r2unity.library", "", "path to IL2CPP native library", + NULL }; +// clang-format on static char *method_attrs_str(unsigned flags) { enum {