From 7c748977216f565a00e2f15b79faf91094504b83 Mon Sep 17 00:00:00 2001 From: pancake Date: Wed, 6 May 2026 19:41:52 +0200 Subject: [PATCH] Initial import of the RBin.Unity plugin --- .gitignore | 4 + Makefile | 28 +- src/lib/lib.c | 205 +++++++++++-- src/lib/lib.h | 44 ++- src/r2/bin_r2unity.c | 716 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 953 insertions(+), 44 deletions(-) create mode 100644 src/r2/bin_r2unity.c diff --git a/.gitignore b/.gitignore index d31e37b..bbd1eff 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,10 @@ # Build artifacts *.o /r2unity +/src/r2/*.dylib +/src/r2/*.so +/src/r2/*.dll +*.dSYM/ # Debian packaging artifacts /dist/debian/*.deb diff --git a/Makefile b/Makefile index 4b51892..e1f1ec8 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,19 @@ EXEC = r2unity -PLUGIN = src/r2/core_r2unity.$(SOEXT) +CORE_PLUGIN = src/r2/core_r2unity.$(SOEXT) +BIN_PLUGIN = src/r2/bin_r2unity.$(SOEXT) +PLUGINS = $(CORE_PLUGIN) $(BIN_PLUGIN) CC = gcc CFLAGS = -Wall -Wextra -g $(shell pkg-config --cflags r_util 2>/dev/null || echo "") LDFLAGS = $(shell pkg-config --libs r_util 2>/dev/null || echo "") # r_core plugin flags (full radare2) -PLUGIN_CFLAGS = -Wall -Wextra -g -fPIC $(shell pkg-config --cflags r_core 2>/dev/null || echo "") -PLUGIN_LDFLAGS = $(shell pkg-config --libs r_core 2>/dev/null || echo "") +CORE_PLUGIN_CFLAGS = -Wall -Wextra -g -fPIC $(shell pkg-config --cflags r_core 2>/dev/null || echo "") +CORE_PLUGIN_LDFLAGS = $(shell pkg-config --libs r_core 2>/dev/null || echo "") + +# r_bin plugin flags +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) @@ -28,16 +34,17 @@ R2PM_BINDIR=$(shell r2pm -H R2PM_BINDIR) all: $(EXEC) -plugin: $(PLUGIN) +plugin: $(PLUGINS) -install-plugin: $(PLUGIN) +install-plugin: $(PLUGINS) @[ -n "$(R2_USER_PLUGINS)" ] || (echo "r2 not found; cannot resolve R2_USER_PLUGINS"; exit 1) mkdir -p "$(R2_USER_PLUGINS)" - cp $(PLUGIN) "$(R2_USER_PLUGINS)/" + cp $(PLUGINS) "$(R2_USER_PLUGINS)/" uninstall-plugin: @[ -n "$(R2_USER_PLUGINS)" ] || (echo "r2 not found; cannot resolve R2_USER_PLUGINS"; exit 1) rm -f "$(R2_USER_PLUGINS)/core_r2unity.$(SOEXT)" + rm -f "$(R2_USER_PLUGINS)/bin_r2unity.$(SOEXT)" fmt: clang-format-radare2 src/**/*.c @@ -54,14 +61,17 @@ user-uninstall: uninstall-plugin $(EXEC): $(OBJS) $(CC) -o $@ $^ $(LDFLAGS) -$(PLUGIN): src/r2/core_r2unity.c $(LIB_SRCS) - $(CC) $(PLUGIN_CFLAGS) $(PLUGIN_SHFLAGS) -o $@ $^ $(PLUGIN_LDFLAGS) +$(CORE_PLUGIN): src/r2/core_r2unity.c $(LIB_SRCS) + $(CC) $(CORE_PLUGIN_CFLAGS) $(PLUGIN_SHFLAGS) -o $@ $^ $(CORE_PLUGIN_LDFLAGS) + +$(BIN_PLUGIN): src/r2/bin_r2unity.c $(LIB_SRCS) + $(CC) $(BIN_PLUGIN_CFLAGS) $(PLUGIN_SHFLAGS) -o $@ $^ $(BIN_PLUGIN_LDFLAGS) %.o: %.c $(CC) $(CFLAGS) -c -o $@ $< clean: - rm -f $(EXEC) $(OBJS) $(PLUGIN) + rm -f $(EXEC) $(OBJS) $(PLUGINS) .PHONY: all clean plugin install-plugin uninstall-plugin fmt diff --git a/src/lib/lib.c b/src/lib/lib.c index 1c3c502..cba325e 100644 --- a/src/lib/lib.c +++ b/src/lib/lib.c @@ -13,39 +13,41 @@ R_API bool r2unity_is_debug(void) { return g_debug; } -typedef enum { - R2U_SEC_STRING_LITERALS, - R2U_SEC_STRING_LITERAL_DATA, - R2U_SEC_STRINGS, - R2U_SEC_EVENTS, - R2U_SEC_PROPERTIES, - R2U_SEC_METHODS, - R2U_SEC_PARAMETER_DEFAULT_VALUES, - R2U_SEC_FIELD_DEFAULT_VALUES, - R2U_SEC_FIELD_AND_PARAMETER_DEFAULT_VALUE_DATA, - R2U_SEC_FIELD_MARSHALED_SIZES, - R2U_SEC_PARAMETERS, - R2U_SEC_FIELDS, - R2U_SEC_GENERIC_PARAMETERS, - R2U_SEC_GENERIC_PARAMETER_CONSTRAINTS, - R2U_SEC_GENERIC_CONTAINERS, - R2U_SEC_NESTED_TYPES, - R2U_SEC_INTERFACES, - R2U_SEC_VTABLE_METHODS, - R2U_SEC_INTERFACE_OFFSETS, - R2U_SEC_TYPE_DEFINITIONS, - R2U_SEC_IMAGES, - R2U_SEC_ASSEMBLIES, - R2U_SEC_FIELD_REFS, - R2U_SEC_REFERENCED_ASSEMBLIES, - R2U_SEC_ATTRIBUTE_DATA, - R2U_SEC_ATTRIBUTE_DATA_RANGES, - R2U_SEC_UNRESOLVED_INDIRECT_CALL_PARAMETER_TYPES, - R2U_SEC_UNRESOLVED_INDIRECT_CALL_PARAMETER_RANGES, - R2U_SEC_WINDOWS_RUNTIME_TYPE_NAMES, - R2U_SEC_WINDOWS_RUNTIME_STRINGS, - R2U_SEC_EXPORTED_TYPE_DEFINITIONS -} R2UMetadataSectionId; +static const char *metadata_section_names[R2UNITY_METADATA_SECTION_COUNT] = { + "string_literals", + "string_literal_data", + "strings", + "events", + "properties", + "methods", + "parameter_default_values", + "field_default_values", + "field_and_parameter_default_value_data", + "field_marshaled_sizes", + "parameters", + "fields", + "generic_parameters", + "generic_parameter_constraints", + "generic_containers", + "nested_types", + "interfaces", + "vtable_methods", + "interface_offsets", + "type_definitions", + "images", + "assemblies", + "field_refs", + "referenced_assemblies", + "attribute_data", + "attribute_data_ranges", + "unresolved_indirect_call_parameter_types", + "unresolved_indirect_call_parameter_ranges", + "windows_runtime_type_names", + "windows_runtime_strings", + "exported_type_definitions", + "rgctx_entries", + "rgctx_entries_data" +}; static int index_size_from_count(uint32_t count) { if (count <= 0xff) { @@ -141,6 +143,14 @@ static void init_legacy_sections(R2UnityMetadata *meta) { set_section (&s[R2U_SEC_WINDOWS_RUNTIME_TYPE_NAMES], h->windowsRuntimeTypeNamesOffset, h->windowsRuntimeTypeNamesSize); set_section (&s[R2U_SEC_WINDOWS_RUNTIME_STRINGS], h->windowsRuntimeStringsOffset, h->windowsRuntimeStringsSize); set_section (&s[R2U_SEC_EXPORTED_TYPE_DEFINITIONS], h->exportedTypeDefinitionsOffset, h->exportedTypeDefinitionsSize); + if (meta->version >= 27 && meta->version < 29) { + const Il2CppGlobalMetadataHeader_v27 *h27 = &meta->header.v27; + set_section (&s[R2U_SEC_RGCTX_ENTRIES], h27->rgctxEntriesOffset, h27->rgctxEntriesSize); + } else if (meta->version >= 29) { + const Il2CppGlobalMetadataHeader_v29 *h29 = &meta->header.v29; + set_section (&s[R2U_SEC_RGCTX_ENTRIES], h29->rgctxEntriesOffset, h29->rgctxEntriesSize); + set_section (&s[R2U_SEC_RGCTX_ENTRIES_DATA], h29->rgctxEntriesDataOffset, h29->rgctxEntriesDataSize); + } } static ut8 *read_metadata_table(R2UnityMetadata *meta, R2UMetadataSectionId id, ut64 entry, size_t *count) { @@ -168,6 +178,51 @@ static ut8 *read_metadata_table(R2UnityMetadata *meta, R2UMetadataSectionId id, return buf; } +static ut64 string_literal_entry_size(R2UnityMetadata *meta) { + return meta->version <= 31? 8: 4; +} + +static ut64 type_definition_entry_size(R2UnityMetadata *meta) { + const bool compact_indices = (meta->version >= 35); + const bool has_element_type = (meta->version <= 31); + const int type_index_size = compact_indices? meta->typeIndexSize: 4; + const int generic_container_size = compact_indices? meta->genericContainerIndexSize: 4; + return 4 + 4 + + type_index_size + type_index_size + type_index_size + + (has_element_type? 4: 0) + + generic_container_size + + 4 + 32 + 16 + 4 + 4; +} + +static ut64 method_definition_entry_size(R2UnityMetadata *meta) { + const bool has_return_parameter = (meta->version >= 31); + const bool compact_indices = (meta->version >= 35); + const int type_definition_size = compact_indices? meta->typeDefinitionIndexSize: 4; + const int type_index_size = compact_indices? meta->typeIndexSize: 4; + const int parameter_index_size = meta->version >= 39? meta->parameterIndexSize: 4; + const int generic_container_size = compact_indices? meta->genericContainerIndexSize: 4; + return 4 + + type_definition_size + + type_index_size + + (has_return_parameter? 4: 0) + + parameter_index_size + + generic_container_size + + 4 + 2 + 2 + 2 + 2; +} + +static ut64 image_definition_entry_size(R2UnityMetadata *meta) { + const bool compact_indices = (meta->version >= 35); + const int type_definition_size = compact_indices? meta->typeDefinitionIndexSize: 4; + return 4 + 4 + type_definition_size + 4 + + type_definition_size + 4 + 4 + 4 + 4 + 4; +} + +static ut64 field_definition_entry_size(R2UnityMetadata *meta) { + const bool compact_indices = (meta->version >= 35); + const int type_index_size = compact_indices? meta->typeIndexSize: 4; + return 4 + type_index_size + 4; +} + /* Detect v24.0 vs v24.1+ (both land on disk as version==24). * * At v24.1 the ImageDefinition row grew by 8 bytes (added @@ -249,7 +304,8 @@ R_API R2UnityMetadata *r2unity_parse_metadata(RBuffer *buf) { hdr_words[i] = r_read_le32 (&hdr_words[i]); } if (version >= 38) { - memcpy (meta->sections, &meta->header.v38.stringLiterals, sizeof (meta->sections)); + memcpy (meta->sections, &meta->header.v38.stringLiterals, + sizeof (Il2CppMetadataSection) * R2UNITY_METADATA_BASE_SECTION_COUNT); Il2CppMetadataSection *s = meta->sections; if (s[R2U_SEC_PARAMETERS].count) { uint32_t param_entry = s[R2U_SEC_PARAMETERS].size / s[R2U_SEC_PARAMETERS].count; @@ -312,6 +368,87 @@ R_API void r2unity_free_metadata(R2UnityMetadata *meta) { R_FREE (meta); } +R_API const char *r2unity_metadata_section_name(R2UMetadataSectionId id) { + if (id < 0 || id >= R2UNITY_METADATA_SECTION_COUNT) { + return NULL; + } + return metadata_section_names[id]; +} + +R_API bool r2unity_metadata_section(R2UnityMetadata *meta, R2UMetadataSectionId id, Il2CppMetadataSection *section) { + R_RETURN_VAL_IF_FAIL (meta && section, false); + if (id < 0 || id >= R2UNITY_METADATA_SECTION_COUNT) { + return false; + } + *section = meta->sections[id]; + return true; +} + +R_API ut64 r2unity_metadata_section_entry_size(R2UnityMetadata *meta, R2UMetadataSectionId id) { + R_RETURN_VAL_IF_FAIL (meta, 0); + if (id < 0 || id >= R2UNITY_METADATA_SECTION_COUNT) { + return 0; + } + Il2CppMetadataSection sec = meta->sections[id]; + if (sec.count) { + return sec.size / sec.count; + } + switch (id) { + case R2U_SEC_STRING_LITERALS: + return string_literal_entry_size (meta); + case R2U_SEC_METHODS: + return method_definition_entry_size (meta); + case R2U_SEC_FIELDS: + return field_definition_entry_size (meta); + case R2U_SEC_TYPE_DEFINITIONS: + return type_definition_entry_size (meta); + case R2U_SEC_IMAGES: + return image_definition_entry_size (meta); + case R2U_SEC_ASSEMBLIES: { + size_t image_count = 0; + Il2CppImageDefinition *images = r2unity_get_images (meta, &image_count); + R_FREE (images); + return image_count? sec.size / image_count: 0; + } + case R2U_SEC_REFERENCED_ASSEMBLIES: + case R2U_SEC_NESTED_TYPES: + case R2U_SEC_INTERFACES: + case R2U_SEC_VTABLE_METHODS: + case R2U_SEC_EXPORTED_TYPE_DEFINITIONS: + case R2U_SEC_RGCTX_ENTRIES: + return 4; + default: + return 0; + } +} + +R_API ut64 r2unity_metadata_section_count(R2UnityMetadata *meta, R2UMetadataSectionId id) { + R_RETURN_VAL_IF_FAIL (meta, 0); + if (id < 0 || id >= R2UNITY_METADATA_SECTION_COUNT) { + return 0; + } + Il2CppMetadataSection sec = meta->sections[id]; + if (sec.count) { + return sec.count; + } + ut64 entry = r2unity_metadata_section_entry_size (meta, id); + return entry? sec.size / entry: 0; +} + +R_API ut64 r2unity_metadata_header_size(R2UnityMetadata *meta) { + R_RETURN_VAL_IF_FAIL (meta, 0); + if (meta->version >= 38) { + return sizeof (Il2CppGlobalMetadataHeader_v38); + } + if (meta->version < 27) { + return sizeof (Il2CppGlobalMetadataHeader_v24); + } + if (meta->version < 29) { + return sizeof (Il2CppGlobalMetadataHeader_v27); + } + return sizeof (Il2CppGlobalMetadataHeader_v29); +} + R_API const char *r2unity_get_string(R2UnityMetadata *meta, uint32_t index) { R_RETURN_VAL_IF_FAIL (meta, NULL); if (!meta->strings || index >= r_buf_size (meta->strings)) { diff --git a/src/lib/lib.h b/src/lib/lib.h index 744d1b3..80cfa95 100644 --- a/src/lib/lib.h +++ b/src/lib/lib.h @@ -4,7 +4,44 @@ #include #define IL2CPP_MAGIC 0xFAB11BAF -#define R2UNITY_METADATA_SECTION_COUNT 31 +#define R2UNITY_METADATA_BASE_SECTION_COUNT 31 +#define R2UNITY_METADATA_SECTION_COUNT 33 + +typedef enum { + R2U_SEC_STRING_LITERALS, + R2U_SEC_STRING_LITERAL_DATA, + R2U_SEC_STRINGS, + R2U_SEC_EVENTS, + R2U_SEC_PROPERTIES, + R2U_SEC_METHODS, + R2U_SEC_PARAMETER_DEFAULT_VALUES, + R2U_SEC_FIELD_DEFAULT_VALUES, + R2U_SEC_FIELD_AND_PARAMETER_DEFAULT_VALUE_DATA, + R2U_SEC_FIELD_MARSHALED_SIZES, + R2U_SEC_PARAMETERS, + R2U_SEC_FIELDS, + R2U_SEC_GENERIC_PARAMETERS, + R2U_SEC_GENERIC_PARAMETER_CONSTRAINTS, + R2U_SEC_GENERIC_CONTAINERS, + R2U_SEC_NESTED_TYPES, + R2U_SEC_INTERFACES, + R2U_SEC_VTABLE_METHODS, + R2U_SEC_INTERFACE_OFFSETS, + R2U_SEC_TYPE_DEFINITIONS, + R2U_SEC_IMAGES, + R2U_SEC_ASSEMBLIES, + R2U_SEC_FIELD_REFS, + R2U_SEC_REFERENCED_ASSEMBLIES, + R2U_SEC_ATTRIBUTE_DATA, + R2U_SEC_ATTRIBUTE_DATA_RANGES, + R2U_SEC_UNRESOLVED_INDIRECT_CALL_PARAMETER_TYPES, + R2U_SEC_UNRESOLVED_INDIRECT_CALL_PARAMETER_RANGES, + R2U_SEC_WINDOWS_RUNTIME_TYPE_NAMES, + R2U_SEC_WINDOWS_RUNTIME_STRINGS, + R2U_SEC_EXPORTED_TYPE_DEFINITIONS, + R2U_SEC_RGCTX_ENTRIES, + R2U_SEC_RGCTX_ENTRIES_DATA +} R2UMetadataSectionId; typedef struct { uint32_t offset; @@ -413,6 +450,11 @@ R_API R2UnityInterop *r2unity_enumerate_pinvokes(R2UnityMetadata *meta, size_t * * Pre-v29 metadata returns NULL (attribute ctor args live in generator stubs). */ R_API R2UnityInterop *r2unity_enumerate_reverse_pinvokes(R2UnityMetadata *meta, size_t *count); R_API void r2unity_free_interop(R2UnityInterop *items, size_t count); +R_API const char *r2unity_metadata_section_name(R2UMetadataSectionId id); +R_API bool r2unity_metadata_section(R2UnityMetadata *meta, R2UMetadataSectionId id, Il2CppMetadataSection *section); +R_API ut64 r2unity_metadata_section_entry_size(R2UnityMetadata *meta, R2UMetadataSectionId id); +R_API ut64 r2unity_metadata_section_count(R2UnityMetadata *meta, R2UMetadataSectionId id); +R_API ut64 r2unity_metadata_header_size(R2UnityMetadata *meta); /* Simplified API: use format-specific finders, or manual read stub. */ R_API bool r2unity_read_method_pointers_at(R2UnityMetadata *meta, const char *exe_path, ut64 addr, size_t count, ut64 **out_ptrs); R_API bool r2unity_find_method_pointers_macho(R2UnityMetadata *meta, const char *macho_path, ut64 **out_ptrs); diff --git a/src/r2/bin_r2unity.c b/src/r2/bin_r2unity.c new file mode 100644 index 0000000..e23c2bb --- /dev/null +++ b/src/r2/bin_r2unity.c @@ -0,0 +1,716 @@ +#define R_LOG_ORIGIN "r2unity.bin" + +#include +#include +#include "../lib/lib.h" + +typedef struct { + R2UnityMetadata *meta; + RBuffer *buf; + Sdb *kv; + ut8 *strings; + ut64 strings_size; +} R2UnityBinObj; + +static const char *unity_range_from_wire(int wire) { + switch (wire) { + case 21: return "5.3.0-5.3.5"; + case 22: return "5.3.6-5.4"; + case 23: return "5.5"; + case 24: return "5.6-2020.1"; + case 27: return "2020.2-2021.3"; + case 29: return "2022.1-2022.3"; + case 31: return "2023.x-6000.x"; + case 39: return "6000.x"; + default: return "unknown"; + } +} + +static R2UnityBinObj *get_obj(RBinFile *bf) { + RBinObject *o = bf? bf->bo: NULL; + return o? (R2UnityBinObj *)o->bin_obj: NULL; +} + +static char *get_string(R2UnityBinObj *obj, uint32_t index) { + if (!obj || !obj->strings || index >= obj->strings_size) { + return NULL; + } + ut64 idx = index; + while (idx > 0 && obj->strings[idx - 1]) { + idx--; + } + ut64 len = 0; + while (idx + len < obj->strings_size && obj->strings[idx + len]) { + len++; + } + return len? r_str_ndup ((const char *)obj->strings + idx, (int)len): NULL; +} + +static bool valid_wire_version(int32_t version) { + return version >= 24 && version <= 39 && (version <= 35 || version >= 38); +} + +static bool check(RBinFile *bf, RBuffer *b) { + (void)bf; + R_RETURN_VAL_IF_FAIL (b, false); + ut8 preamble[8]; + if (r_buf_read_at (b, 0, preamble, sizeof (preamble)) != (st64)sizeof (preamble)) { + return false; + } + if (r_read_le32 (preamble) != IL2CPP_MAGIC) { + return false; + } + return valid_wire_version ((int32_t)r_read_le32 (preamble + 4)); +} + +static void fill_sdb(R2UnityBinObj *obj) { + Sdb *kv = obj->kv; + R2UnityMetadata *meta = obj->meta; + sdb_num_set (kv, "version", (ut64)meta->version, 0); + sdb_set (kv, "unity_range", unity_range_from_wire (meta->version), 0); + sdb_num_set (kv, "header.size", r2unity_metadata_header_size (meta), 0); + for (int i = 0; i < R2UNITY_METADATA_SECTION_COUNT; i++) { + Il2CppMetadataSection sec; + if (!r2unity_metadata_section (meta, (R2UMetadataSectionId)i, &sec)) { + continue; + } + const char *name = r2unity_metadata_section_name ((R2UMetadataSectionId)i); + if (!name) { + continue; + } + char key[192]; + snprintf (key, sizeof (key), "sections.%s.offset", name); + sdb_num_set (kv, key, sec.offset, 0); + snprintf (key, sizeof (key), "sections.%s.size", name); + sdb_num_set (kv, key, sec.size, 0); + snprintf (key, sizeof (key), "sections.%s.count", name); + sdb_num_set (kv, key, r2unity_metadata_section_count (meta, (R2UMetadataSectionId)i), 0); + snprintf (key, sizeof (key), "sections.%s.entry_size", name); + sdb_num_set (kv, key, r2unity_metadata_section_entry_size (meta, (R2UMetadataSectionId)i), 0); + } + sdb_num_set (kv, "counts.types", r2unity_metadata_section_count (meta, R2U_SEC_TYPE_DEFINITIONS), 0); + sdb_num_set (kv, "counts.methods", r2unity_metadata_section_count (meta, R2U_SEC_METHODS), 0); + sdb_num_set (kv, "counts.images", r2unity_metadata_section_count (meta, R2U_SEC_IMAGES), 0); + sdb_num_set (kv, "counts.assemblies", r2unity_metadata_section_count (meta, R2U_SEC_ASSEMBLIES), 0); + sdb_num_set (kv, "counts.string_literals", r2unity_metadata_section_count (meta, R2U_SEC_STRING_LITERALS), 0); +} + +static bool load(RBinFile *bf, RBuffer *buf, ut64 loadaddr) { + (void)loadaddr; + R_RETURN_VAL_IF_FAIL (bf && bf->bo && buf, false); + R2UnityBinObj *obj = R_NEW0 (R2UnityBinObj); + obj->buf = r_buf_new_slice (buf, 0, r_buf_size (buf)); + obj->meta = r2unity_parse_metadata (obj->buf); + if (!obj->meta) { + r_unref (obj->buf); + free (obj); + return false; + } + Il2CppMetadataSection strings_sec; + r2unity_metadata_section (obj->meta, R2U_SEC_STRINGS, &strings_sec); + obj->strings_size = strings_sec.size; + if (obj->strings_size) { + obj->strings = R_NEWS (ut8, obj->strings_size); + if (obj->strings && r_buf_read_at (obj->buf, strings_sec.offset, obj->strings, obj->strings_size) != (st64)obj->strings_size) { + R_FREE (obj->strings); + obj->strings_size = 0; + } + } + obj->kv = sdb_new0 (); + fill_sdb (obj); + bf->bo->bin_obj = obj; + return true; +} + +static void destroy(RBinFile *bf) { + R2UnityBinObj *obj = get_obj (bf); + if (!obj) { + return; + } + r2unity_free_metadata (obj->meta); + r_unref (obj->buf); + R_FREE (obj->strings); + free (obj); +} + +static Sdb *get_sdb(RBinFile *bf) { + R2UnityBinObj *obj = get_obj (bf); + return obj? obj->kv: NULL; +} + +static ut64 baddr(RBinFile *bf) { + (void)bf; + return 0; +} + +static ut64 size(RBinFile *bf) { + R2UnityBinObj *obj = get_obj (bf); + return obj? r_buf_size (obj->buf): 0; +} + +static RBinInfo *info(RBinFile *bf) { + R2UnityBinObj *obj = get_obj (bf); + R_RETURN_VAL_IF_FAIL (obj, NULL); + RBinInfo *ret = R_NEW0 (RBinInfo); + ret->file = bf->file? strdup (bf->file): NULL; + ret->type = r_str_newf ("Unity IL2CPP global-metadata v%d", obj->meta->version); + ret->bclass = strdup ("Unity IL2CPP metadata"); + ret->rclass = strdup ("il2cpp"); + ret->machine = strdup ("Unity IL2CPP metadata"); + ret->os = strdup ("any"); + ret->arch = strdup ("cil"); + ret->bits = 32; + ret->has_va = false; + ret->big_endian = 0; + ret->has_lit = true; + ret->lang = "csharp"; + return ret; +} + +static void section_free(void *p) { + RBinSection *sec = (RBinSection *)p; + if (sec) { + free (sec->name); + free (sec->format); + free (sec); + } +} + +static RBinSection *new_section(const char *name, ut64 off, ut64 size, bool has_strings) { + RBinSection *sec = R_NEW0 (RBinSection); + sec->name = strdup (name); + sec->paddr = off; + sec->vaddr = off; + sec->size = size; + sec->vsize = size; + sec->perm = R_PERM_R; + sec->type = "DATA"; + sec->bits = 32; + sec->has_strings = has_strings; + sec->is_data = true; + sec->add = true; + return sec; +} + +static RList *sections(RBinFile *bf) { + R2UnityBinObj *obj = get_obj (bf); + R_RETURN_VAL_IF_FAIL (obj, NULL); + RList *ret = r_list_newf (section_free); + ut64 header_size = r2unity_metadata_header_size (obj->meta); + if (header_size) { + r_list_append (ret, new_section ("il2cpp.header", 0, header_size, false)); + } + for (int i = 0; i < R2UNITY_METADATA_SECTION_COUNT; i++) { + Il2CppMetadataSection sec; + r2unity_metadata_section (obj->meta, (R2UMetadataSectionId)i, &sec); + if (!sec.size) { + continue; + } + const char *name = r2unity_metadata_section_name ((R2UMetadataSectionId)i); + if (!name) { + continue; + } + bool has_strings = i == R2U_SEC_STRINGS || i == R2U_SEC_STRING_LITERAL_DATA || i == R2U_SEC_WINDOWS_RUNTIME_STRINGS; + char *sname = r_str_newf ("il2cpp.%s", name); + r_list_append (ret, new_section (sname, sec.offset, sec.size, has_strings)); + free (sname); + } + return ret; +} + +static RBinAttribute method_attr(uint16_t flags, const char *name) { + enum { + MemberAccessMask = 0x0007, + MdPrivate = 0x0001, + MdAssembly = 0x0003, + MdFamily = 0x0004, + MdPublic = 0x0006, + MdStatic = 0x0010, + MdFinal = 0x0020, + MdVirtual = 0x0040, + MdAbstract = 0x0400 + }; + RBinAttribute attr = R_BIN_ATTR_NONE; + switch (flags & MemberAccessMask) { + case MdPublic: + attr |= R_BIN_ATTR_PUBLIC; + break; + case MdFamily: + attr |= R_BIN_ATTR_PROTECTED; + break; + case MdAssembly: + attr |= R_BIN_ATTR_INTERNAL; + break; + case MdPrivate: + attr |= R_BIN_ATTR_PRIVATE; + break; + } + if (flags & MdStatic) { + attr |= R_BIN_ATTR_STATIC; + } + if (flags & MdFinal) { + attr |= R_BIN_ATTR_FINAL; + } + if (flags & MdVirtual) { + attr |= R_BIN_ATTR_VIRTUAL; + } + if (flags & MdAbstract) { + attr |= R_BIN_ATTR_ABSTRACT; + } + if (flags & IL2CPP_METHOD_ATTRIBUTE_PINVOKE_IMPL) { + attr |= R_BIN_ATTR_EXTERN | R_BIN_ATTR_NATIVE; + } + if (name && (!strcmp (name, ".ctor") || !strcmp (name, ".cctor"))) { + attr |= R_BIN_ATTR_CONSTRUCTOR; + } + return attr; +} + +static RBinAttribute type_attr(uint32_t flags) { + enum { + TypeVisibilityMask = 0x00000007, + TypePublic = 0x00000001, + TypeNestedPublic = 0x00000002, + TypeNestedPrivate = 0x00000003, + TypeNestedFamily = 0x00000004, + TypeAbstract = 0x00000080, + TypeSealed = 0x00000100, + TypeInterface = 0x00000020 + }; + RBinAttribute attr = R_BIN_ATTR_NONE; + switch (flags & TypeVisibilityMask) { + case TypePublic: + case TypeNestedPublic: + attr |= R_BIN_ATTR_PUBLIC; + break; + case TypeNestedPrivate: + attr |= R_BIN_ATTR_PRIVATE; + break; + case TypeNestedFamily: + attr |= R_BIN_ATTR_PROTECTED; + break; + } + if (flags & TypeAbstract) { + attr |= R_BIN_ATTR_ABSTRACT; + } + if (flags & TypeSealed) { + attr |= R_BIN_ATTR_SEALED; + } + if (flags & TypeInterface) { + attr |= R_BIN_ATTR_INTERFACE; + } + return attr; +} + +static char *type_fullname(R2UnityBinObj *obj, const Il2CppTypeDefinition *td, size_t idx) { + char *ns = td? get_string (obj, td->namespaceIndex): NULL; + char *tn = td? get_string (obj, td->nameIndex): NULL; + char *out; + if (ns && *ns && tn && *tn) { + out = r_str_newf ("%s.%s", ns, tn); + } else if (tn && *tn) { + out = strdup (tn); + } else { + out = r_str_newf ("type.%zu", idx); + } + free (ns); + free (tn); + return out; +} + +static char *method_fullname(R2UnityBinObj *obj, + const Il2CppMethodDefinition *m, + const Il2CppTypeDefinition *types, + size_t type_count) { + char *mn = get_string (obj, m->nameIndex); + if (!mn) { + return NULL; + } + char *owner = NULL; + if (m->declaringType >= 0 && (size_t)m->declaringType < type_count) { + owner = type_fullname (obj, &types[m->declaringType], (size_t)m->declaringType); + } + char *out = owner && *owner + ? r_str_newf ("%s.%s(%u)", owner, mn, (unsigned)m->parameterCount) + : r_str_newf ("%s(%u)", mn, (unsigned)m->parameterCount); + free (owner); + free (mn); + return out; +} + +static RBinSymbol *new_symbol(const char *name, ut64 paddr, ut64 size, const char *type, RBinAttribute attr, const char *classname, int ordinal) { + RBinSymbol *sym = r_bin_symbol_new (name, paddr, paddr); + if (!sym) { + return NULL; + } + sym->size = (ut32)R_MIN (size, UT32_MAX); + sym->type = type; + sym->attr = attr; + sym->ordinal = ordinal; + sym->lang = R_BIN_LANG_NONE; + if (classname) { + sym->classname = strdup (classname); + } + return sym; +} + +static RList *symbols(RBinFile *bf) { + R2UnityBinObj *obj = get_obj (bf); + R_RETURN_VAL_IF_FAIL (obj, NULL); + R2UnityMetadata *meta = obj->meta; + RList *ret = r_list_newf ((RListFree)r_bin_symbol_free); + int ordinal = 0; + + for (int i = 0; i < R2UNITY_METADATA_SECTION_COUNT; i++) { + Il2CppMetadataSection sec; + r2unity_metadata_section (meta, (R2UMetadataSectionId)i, &sec); + if (!sec.size) { + continue; + } + const char *name = r2unity_metadata_section_name ((R2UMetadataSectionId)i); + if (!name) { + continue; + } + char *sname = r_str_newf ("section.il2cpp.%s", name); + RBinSymbol *sym = new_symbol (sname, sec.offset, sec.size, R_BIN_TYPE_SECTION_STR, R_BIN_ATTR_READONLY, NULL, ordinal++); + if (sym) { + r_list_append (ret, sym); + } + free (sname); + } + + size_t image_count = 0; + Il2CppImageDefinition *images = r2unity_get_images (meta, &image_count); + ut64 image_entry = r2unity_metadata_section_entry_size (meta, R2U_SEC_IMAGES); + Il2CppMetadataSection image_sec; + r2unity_metadata_section (meta, R2U_SEC_IMAGES, &image_sec); + for (size_t i = 0; images && i < image_count; i++) { + char *name = get_string (obj, images[i].nameIndex); + if (name) { + char *sname = r_str_newf ("image.%s", name); + RBinSymbol *sym = new_symbol (sname, image_sec.offset + i * image_entry, image_entry, R_BIN_TYPE_OBJECT_STR, R_BIN_ATTR_READONLY, NULL, ordinal++); + if (sym) { + r_list_append (ret, sym); + } + free (sname); + free (name); + } + } + + size_t asm_count = 0; + Il2CppAssemblyDefinition *asms = r2unity_get_assemblies (meta, &asm_count); + ut64 asm_entry = r2unity_metadata_section_entry_size (meta, R2U_SEC_ASSEMBLIES); + Il2CppMetadataSection asm_sec; + r2unity_metadata_section (meta, R2U_SEC_ASSEMBLIES, &asm_sec); + for (size_t i = 0; asms && i < asm_count; i++) { + char *name = get_string (obj, asms[i].aname.name_idx); + if (name) { + char *sname = r_str_newf ("assembly.%s", name); + RBinSymbol *sym = new_symbol (sname, asm_sec.offset + i * asm_entry, asm_entry, R_BIN_TYPE_OBJECT_STR, R_BIN_ATTR_READONLY, NULL, ordinal++); + if (sym) { + r_list_append (ret, sym); + } + free (sname); + free (name); + } + } + + size_t type_count = 0; + Il2CppTypeDefinition *types = r2unity_get_type_definitions (meta, &type_count); + ut64 type_entry = r2unity_metadata_section_entry_size (meta, R2U_SEC_TYPE_DEFINITIONS); + Il2CppMetadataSection type_sec; + r2unity_metadata_section (meta, R2U_SEC_TYPE_DEFINITIONS, &type_sec); + for (size_t i = 0; types && i < type_count; i++) { + char *name = type_fullname (obj, &types[i], i); + RBinSymbol *sym = new_symbol (name, type_sec.offset + i * type_entry, type_entry, R_BIN_TYPE_OBJECT_STR, type_attr (types[i].flags), NULL, ordinal++); + if (sym) { + r_list_append (ret, sym); + } + free (name); + } + + size_t method_count = 0; + Il2CppMethodDefinition *methods = r2unity_get_method_definitions (meta, &method_count); + ut64 method_entry = r2unity_metadata_section_entry_size (meta, R2U_SEC_METHODS); + Il2CppMetadataSection method_sec; + r2unity_metadata_section (meta, R2U_SEC_METHODS, &method_sec); + for (size_t i = 0; methods && i < method_count; i++) { + char *name = method_fullname (obj, &methods[i], types, type_count); + if (!name) { + continue; + } + char *mn = get_string (obj, methods[i].nameIndex); + char *classname = NULL; + if (methods[i].declaringType >= 0 && (size_t)methods[i].declaringType < type_count) { + classname = type_fullname (obj, &types[methods[i].declaringType], (size_t)methods[i].declaringType); + } + RBinSymbol *sym = new_symbol (name, method_sec.offset + i * method_entry, method_entry, R_BIN_TYPE_METH_STR, method_attr (methods[i].flags, mn), classname, ordinal++); + if (sym) { + r_list_append (ret, sym); + } + free (classname); + free (mn); + free (name); + } + + R_FREE (methods); + R_FREE (types); + R_FREE (asms); + R_FREE (images); + return ret; +} + +static RList *classes(RBinFile *bf) { + R2UnityBinObj *obj = get_obj (bf); + R_RETURN_VAL_IF_FAIL (obj, NULL); + R2UnityMetadata *meta = obj->meta; + RList *ret = r_list_newf ((RListFree)r_bin_class_free); + + size_t type_count = 0; + Il2CppTypeDefinition *types = r2unity_get_type_definitions (meta, &type_count); + size_t method_count = 0; + Il2CppMethodDefinition *methods = r2unity_get_method_definitions (meta, &method_count); + Il2CppMetadataSection type_sec; + Il2CppMetadataSection method_sec; + r2unity_metadata_section (meta, R2U_SEC_TYPE_DEFINITIONS, &type_sec); + r2unity_metadata_section (meta, R2U_SEC_METHODS, &method_sec); + ut64 type_entry = r2unity_metadata_section_entry_size (meta, R2U_SEC_TYPE_DEFINITIONS); + ut64 method_entry = r2unity_metadata_section_entry_size (meta, R2U_SEC_METHODS); + + for (size_t i = 0; types && i < type_count; i++) { + char *ns = get_string (obj, types[i].namespaceIndex); + char *tn = get_string (obj, types[i].nameIndex); + char *fallback = NULL; + if (!tn || !*tn) { + fallback = r_str_newf ("type.%zu", i); + } + RBinClass *klass = r_bin_class_new ((tn && *tn)? tn: fallback, NULL, type_attr (types[i].flags)); + klass->index = (int)i; + klass->addr = type_sec.offset + i * type_entry; + klass->lang = R_BIN_LANG_NONE; + if (ns && *ns) { + klass->ns = strdup (ns); + } + int start = types[i].methodStart; + int count = types[i].method_count; + for (int k = 0; methods && k < count && start >= 0 && (size_t)(start + k) < method_count; k++) { + size_t mi = (size_t)(start + k); + char *mname = method_fullname (obj, &methods[mi], types, type_count); + char *raw = get_string (obj, methods[mi].nameIndex); + RBinSymbol *sym = new_symbol (mname? mname: raw, method_sec.offset + mi * method_entry, method_entry, R_BIN_TYPE_METH_STR, method_attr (methods[mi].flags, raw), NULL, (int)mi); + if (sym) { + r_list_append (klass->methods, sym); + } + free (raw); + free (mname); + } + r_list_append (ret, klass); + free (fallback); + free (tn); + free (ns); + } + + R_FREE (methods); + R_FREE (types); + return ret; +} + +static void append_bin_string(RList *ret, char *text, ut64 paddr, ut32 size, ut32 ordinal) { + RBinString *str = R_NEW0 (RBinString); + str->string = text; + str->paddr = paddr; + str->vaddr = paddr; + str->size = size; + str->length = strlen (text); + str->ordinal = ordinal; + str->type = R_STRING_TYPE_UTF8; + r_list_append (ret, str); +} + +static void add_metadata_strings(R2UnityBinObj *obj, RList *ret, ut32 *ordinal) { + if (!obj->strings || !obj->strings_size) { + return; + } + Il2CppMetadataSection sec; + r2unity_metadata_section (obj->meta, R2U_SEC_STRINGS, &sec); + ut64 i = 0; + while (i < obj->strings_size) { + while (i < obj->strings_size && !obj->strings[i]) { + i++; + } + ut64 start = i; + while (i < obj->strings_size && obj->strings[i]) { + i++; + } + ut64 len = i - start; + if (len > 1 && len < R_BIN_SIZEOF_STRINGS) { + char *text = r_str_ndup ((const char *)obj->strings + start, (int)len); + append_bin_string (ret, text, sec.offset + start, (ut32)(len + 1), (*ordinal)++); + } + i++; + } +} + +static void add_literal_strings(R2UnityBinObj *obj, RList *ret, ut32 *ordinal) { + size_t count = 0; + Il2CppStringLiteral *lits = r2unity_get_string_literals (obj->meta, &count); + for (size_t i = 0; lits && i < count; i++) { + ut8 *bytes = NULL; + size_t len = 0; + if (!r2unity_read_string_literal (obj->meta, &lits[i], &bytes, &len)) { + continue; + } + if (len > 1 && len < R_BIN_SIZEOF_STRINGS) { + char *text = r_str_ndup ((const char *)bytes, (int)len); + append_bin_string (ret, text, obj->meta->stringLiteralDataOffset + lits[i].dataIndex, (ut32)len, (*ordinal)++); + } + R_FREE (bytes); + } + R_FREE (lits); +} + +static RList *strings(RBinFile *bf) { + R2UnityBinObj *obj = get_obj (bf); + R_RETURN_VAL_IF_FAIL (obj, NULL); + RList *ret = r_list_newf ((RListFree)r_bin_string_free); + ut32 ordinal = 0; + add_metadata_strings (obj, ret, &ordinal); + add_literal_strings (obj, ret, &ordinal); + return ret; +} + +static RList *imports(RBinFile *bf) { + R2UnityBinObj *obj = get_obj (bf); + R_RETURN_VAL_IF_FAIL (obj, NULL); + RList *ret = r_list_newf ((RListFree)r_bin_import_free); + size_t count = 0; + R2UnityInterop *items = r2unity_enumerate_pinvokes (obj->meta, &count); + for (size_t i = 0; items && i < count; i++) { + R2UnityInterop *it = &items[i]; + RBinImport *imp = R_NEW0 (RBinImport); + imp->name = r_bin_name_new (it->entry_name? it->entry_name: it->name); + imp->libname = strdup (it->dll_name? it->dll_name: ""); + imp->classname = it->name? strdup (it->name): NULL; + imp->type = R_BIN_TYPE_FUNC_STR; + imp->bind = R_BIN_BIND_GLOBAL_STR; + imp->ordinal = (ut32)i; + r_list_append (ret, imp); + } + r2unity_free_interop (items, count); + return ret; +} + +static RList *libs(RBinFile *bf) { + R2UnityBinObj *obj = get_obj (bf); + R_RETURN_VAL_IF_FAIL (obj, NULL); + RList *ret = r_list_newf (free); + size_t count = 0; + Il2CppAssemblyDefinition *asms = r2unity_get_assemblies (obj->meta, &count); + for (size_t i = 0; asms && i < count; i++) { + char *name = get_string (obj, asms[i].aname.name_idx); + if (name) { + r_list_append (ret, name); + } + } + R_FREE (asms); + return ret; +} + +static RList *fields(RBinFile *bf) { + R2UnityBinObj *obj = get_obj (bf); + R_RETURN_VAL_IF_FAIL (obj, NULL); + RList *ret = r_list_newf ((RListFree)r_bin_field_free); + r_list_append (ret, r_bin_field_new (0, 0, IL2CPP_MAGIC, 4, "metadata.sanity", NULL, "x", false)); + r_list_append (ret, r_bin_field_new (4, 4, obj->meta->version, 4, "metadata.version", NULL, "i", false)); + bool v38 = obj->meta->version >= 38; + for (int i = 0; i < R2UNITY_METADATA_SECTION_COUNT; i++) { + Il2CppMetadataSection sec; + r2unity_metadata_section (obj->meta, (R2UMetadataSectionId)i, &sec); + if (!sec.size) { + continue; + } + const char *name = r2unity_metadata_section_name ((R2UMetadataSectionId)i); + ut64 desc = 0; + if (v38) { + if (i >= R2UNITY_METADATA_BASE_SECTION_COUNT) { + continue; + } + desc = 8 + (ut64)i * 12; + } else { + desc = 8 + (ut64)i * 8; + } + char *field = r_str_newf ("metadata.%s.offset", name); + r_list_append (ret, r_bin_field_new (desc, desc, sec.offset, 4, field, NULL, "x", false)); + free (field); + field = r_str_newf ("metadata.%s.size", name); + r_list_append (ret, r_bin_field_new (desc + 4, desc + 4, sec.size, 4, field, NULL, "x", false)); + free (field); + if (v38) { + field = r_str_newf ("metadata.%s.count", name); + r_list_append (ret, r_bin_field_new (desc + 8, desc + 8, sec.count, 4, field, NULL, "i", false)); + free (field); + } + } + return ret; +} + +static char *header(RBinFile *bf, int mode) { + (void)mode; + R2UnityBinObj *obj = get_obj (bf); + if (!obj) { + return NULL; + } + RStrBuf *sb = r_strbuf_new (""); + r_strbuf_appendf (sb, "pf.r2unity_global_metadata_header @ 0x%08"PFMT64x"\n", (ut64)0); + r_strbuf_appendf (sb, "0x00000000 Sanity 0x%08x\n", IL2CPP_MAGIC); + r_strbuf_appendf (sb, "0x00000004 Version %d (%s)\n", obj->meta->version, unity_range_from_wire (obj->meta->version)); + r_strbuf_appendf (sb, "0x%08"PFMT64x" HeaderSize %"PFMT64u"\n", (ut64)0, r2unity_metadata_header_size (obj->meta)); + for (int i = 0; i < R2UNITY_METADATA_SECTION_COUNT; i++) { + Il2CppMetadataSection sec; + r2unity_metadata_section (obj->meta, (R2UMetadataSectionId)i, &sec); + if (!sec.size) { + continue; + } + const char *name = r2unity_metadata_section_name ((R2UMetadataSectionId)i); + r_strbuf_appendf (sb, "0x%08x %-40s size=0x%08x count=%"PFMT64u" entry=%"PFMT64u"\n", + sec.offset, + name? name: "unknown", + sec.size, + r2unity_metadata_section_count (obj->meta, (R2UMetadataSectionId)i), + r2unity_metadata_section_entry_size (obj->meta, (R2UMetadataSectionId)i)); + } + return r_strbuf_drain (sb); +} + +RBinPlugin r_bin_plugin_r2unity = { + .meta = { + .name = "r2unity", + .desc = "Unity IL2CPP global-metadata.dat", + .author = "pancake", + .license = "MIT", + }, + .get_sdb = &get_sdb, + .load = &load, + .destroy = &destroy, + .check = &check, + .baddr = &baddr, + .size = &size, + .info = &info, + .sections = §ions, + .symbols = &symbols, + .strings = &strings, + .imports = &imports, + .libs = &libs, + .classes = &classes, + .fields = &fields, + .header = &header, + .minstrlen = 2, +}; + +#ifndef R2_PLUGIN_INCORE +R_API RLibStruct radare_plugin = { + .type = R_LIB_TYPE_BIN, + .data = &r_bin_plugin_r2unity, + .version = R2_VERSION +}; +#endif