Add -P to enumerate all PInvokes

This commit is contained in:
pancake committed 2026-04-17 13:31:47 +02:00
1 parent cdf1feec76
commit 5192b295b2
4 files changed
+333 -12

No files matched your search

+9
View File
@@ -58,6 +58,15 @@ full radare2 core.
---
## 1.1. Files
| OS / target | Native file required | Metadata file required | Typical metadata location relative to binary | Current status in docs |
|---|---|---|---|---|
| Android | libil2cpp.so | global-metadata.dat | ../../assets/bin/Data/Managed/Metadata/global-metadata.dat | Main supported target; arm64 works best (doc/r2unity.md:419, doc/r2unity.md:469) |
| iOS | UnityFramework | global-metadata.dat | ../../Data/Managed/Metadata/global-metadata.dat | Main supported target for ARM64 Mach-O (doc/r2unity.md:389, doc/r2unity.md:470) |
| macOS standalone | GameAssembly.dylib | global-metadata.dat | ../Resources/Data/il2cpp_data/Metadata/global- metadata.dat | Mach-O path technically works, but CLI auto-discovery is not wired yet (doc/r2unity.md:447,
| Windows standalone | GameAssembly.dll | global-metadata.dat | *_Data/il2cpp_data/Metadata/global- metadata.dat | Good |
## 2. `global-metadata.dat` parser (`src/lib/lib.c`, `src/lib/lib.h`)
### 2.1 Header versions
+166 -10
View File
@@ -203,9 +203,22 @@ R_API Il2CppMethodDefinition *r2unity_get_method_definitions (R2UnityMetadata *m
if (!meta || !count) {
return NULL;
}
// On-disk Il2CppMethodDefinition is 32 bytes (little-endian)
const ut64 entry = 32;
if ((ut64) (ut64) meta->methodsSize < entry) {
/* On-disk Il2CppMethodDefinition:
* v24.1 - v30: 32 bytes
* v31+: 36 bytes (returnParameterToken inserted at +12,
* pushing parameterStart.. by +4)
* See doc/future.md §4.1 and third_party/Il2CppDumper/.../MetadataClass.cs. */
const bool v31_layout = (meta->version >= 31);
const ut64 entry = v31_layout ? 36 : 32;
const int off_parameterStart = v31_layout ? 16 : 12;
const int off_genericContainer = v31_layout ? 20 : 16;
const int off_token = v31_layout ? 24 : 20;
const int off_flags = v31_layout ? 28 : 24;
const int off_iflags = v31_layout ? 30 : 26;
const int off_slot = v31_layout ? 32 : 28;
const int off_parameterCount = v31_layout ? 34 : 30;
if ((ut64) meta->methodsSize < entry) {
*count = 0;
return NULL;
}
@@ -229,13 +242,13 @@ R_API Il2CppMethodDefinition *r2unity_get_method_definitions (R2UnityMetadata *m
methods[i].nameIndex = r_read_le32 (p + 0);
methods[i].declaringType = (int32_t) r_read_le32 (p + 4);
methods[i].returnType = (int32_t) r_read_le32 (p + 8);
methods[i].parameterStart = (int32_t) r_read_le32 (p + 12);
methods[i].genericContainerIndex = (int32_t) r_read_le32 (p + 16);
methods[i].token = r_read_le32 (p + 20);
methods[i].flags = r_read_le16 (p + 24);
methods[i].iflags = r_read_le16 (p + 26);
methods[i].slot = r_read_le16 (p + 28);
methods[i].parameterCount = r_read_le16 (p + 30);
methods[i].parameterStart = (int32_t) r_read_le32 (p + off_parameterStart);
methods[i].genericContainerIndex = (int32_t) r_read_le32 (p + off_genericContainer);
methods[i].token = r_read_le32 (p + off_token);
methods[i].flags = r_read_le16 (p + off_flags);
methods[i].iflags = r_read_le16 (p + off_iflags);
methods[i].slot = r_read_le16 (p + off_slot);
methods[i].parameterCount = r_read_le16 (p + off_parameterCount);
}
R_FREE (buf);
return methods;
@@ -461,3 +474,146 @@ R_API bool r2unity_read_method_pointers_at (R2UnityMetadata *meta, const char *e
(void) meta; (void) exe_path; (void) addr; (void) count; (void) out_ptrs;
return false;
}
static char *build_qualified_name (R2UnityMetadata *meta,
const Il2CppTypeDefinition *td, uint32_t method_name_idx) {
char *ns = td ? (char *) r2unity_get_string (meta, td->namespaceIndex) : NULL;
char *tn = td ? (char *) r2unity_get_string (meta, td->nameIndex) : NULL;
char *mn = (char *) r2unity_get_string (meta, method_name_idx);
if (!mn) {
free (ns);
free (tn);
return NULL;
}
char buf[1024];
if (ns && *ns && tn && *tn) {
snprintf (buf, sizeof (buf), "%s.%s.%s", ns, tn, mn);
} else if (tn && *tn) {
snprintf (buf, sizeof (buf), "%s.%s", tn, mn);
} else {
snprintf (buf, sizeof (buf), "%s", mn);
}
char *out = strdup (buf);
free (ns);
free (tn);
free (mn);
return out;
}
R_API R2UnityInterop *r2unity_enumerate_pinvokes (R2UnityMetadata *meta, size_t *count) {
if (!meta || !count) {
return NULL;
}
*count = 0;
size_t method_count = 0;
Il2CppMethodDefinition *methods = r2unity_get_method_definitions (meta, &method_count);
if (!methods || !method_count) {
R_FREE (methods);
return NULL;
}
size_t type_count = 0;
Il2CppTypeDefinition *types = r2unity_get_type_definitions (meta, &type_count);
size_t image_count = 0;
Il2CppImageDefinition *images = r2unity_get_images (meta, &image_count);
/* Build typeIndex -> imageIndex map once. */
int *type2img = NULL;
if (images && type_count) {
type2img = R_NEWS (int, type_count);
if (type2img) {
for (size_t ti = 0; ti < type_count; ti++) {
type2img[ti] = -1;
}
for (size_t ii = 0; ii < image_count; ii++) {
int start = images[ii].typeStart;
int tcount = (int) images[ii].typeCount;
if (start < 0 || tcount <= 0) {
continue;
}
for (int k = 0; k < tcount && (size_t) (start + k) < type_count; k++) {
type2img[start + k] = (int) ii;
}
}
}
}
/* First pass: count P/Invoke methods. */
size_t n = 0;
for (size_t j = 0; j < method_count; j++) {
if (methods[j].flags & IL2CPP_METHOD_ATTRIBUTE_PINVOKE_IMPL) {
n++;
}
}
if (!n) {
R_FREE (methods);
R_FREE (types);
R_FREE (images);
R_FREE (type2img);
return NULL;
}
R2UnityInterop *out = R_NEWS0 (R2UnityInterop, n);
if (!out) {
R_FREE (methods);
R_FREE (types);
R_FREE (images);
R_FREE (type2img);
return NULL;
}
size_t k = 0;
for (size_t j = 0; j < method_count && k < n; j++) {
Il2CppMethodDefinition *m = &methods[j];
if (!(m->flags & IL2CPP_METHOD_ATTRIBUTE_PINVOKE_IMPL)) {
continue;
}
R2UnityInterop *it = &out[k++];
it->kind = R2U_INTEROP_PINVOKE;
it->method_index = (int32_t) j;
it->token = m->token;
it->flags = m->flags;
it->iflags = m->iflags;
it->wrapper_va = 0;
it->wrapper_index = UINT32_MAX;
it->confidence = 100;
const Il2CppTypeDefinition *td = NULL;
if (types && m->declaringType >= 0 && (size_t) m->declaringType < type_count) {
td = &types[m->declaringType];
}
it->name = build_qualified_name (meta, td, m->nameIndex);
int img_idx = -1;
if (type2img && m->declaringType >= 0 && (size_t) m->declaringType < type_count) {
img_idx = type2img[m->declaringType];
}
it->image_index = img_idx;
if (img_idx >= 0 && images && (size_t) img_idx < image_count) {
it->image_name = (char *) r2unity_get_string (meta, images[img_idx].nameIndex);
}
/* DLL name / entry-point recovery requires attribute-BLOB decoding
* (v29+) or generator disassembly (v<=27.2). Not implemented yet. */
}
R_FREE (methods);
R_FREE (types);
R_FREE (images);
R_FREE (type2img);
*count = n;
return out;
}
R_API void r2unity_free_interop (R2UnityInterop *items, size_t count) {
if (!items) {
return;
}
for (size_t i = 0; i < count; i++) {
free (items[i].name);
free (items[i].image_name);
free (items[i].dll_name);
free (items[i].entry_name);
}
free (items);
}
+30
View File
@@ -313,6 +313,34 @@ typedef struct {
Il2CppAssemblyNameDefinition aname;
} Il2CppAssemblyDefinition;
/* ECMA-335 MethodAttributes.PinvokeImpl — bit 13 on methodDef.flags */
#define IL2CPP_METHOD_ATTRIBUTE_PINVOKE_IMPL 0x2000
enum {
R2U_INTEROP_PINVOKE = 1, /* managed -> native, [DllImport] */
R2U_INTEROP_REVERSE_PINVOKE = 2, /* native -> managed, [MonoPInvokeCallback] */
R2U_INTEROP_UNMANAGED_ONLY = 3, /* [UnmanagedCallersOnly] (.NET 5+) */
R2U_INTEROP_INTEROP_DATA = 4 /* Il2CppInteropData entry (opaque) */
};
/* A single P/Invoke or reverse-P/Invoke entry.
* String fields are owned by the struct and freed by r2unity_free_interop. */
typedef struct {
int32_t method_index; /* -1 for anonymous native wrapper */
uint32_t token; /* 0 if not applicable */
int32_t image_index; /* -1 if unresolved */
uint16_t flags; /* copy of methodDef.flags */
uint16_t iflags; /* copy of methodDef.iflags */
char *name; /* "Ns.Class.Method" or NULL */
char *image_name; /* "Assembly-CSharp" or NULL */
char *dll_name; /* NULL if unrecovered */
char *entry_name; /* NULL if unrecovered (=> method name) */
ut64 wrapper_va; /* 0 if no native wrapper */
uint32_t wrapper_index; /* global reversePInvokeWrappers index, or UINT32_MAX */
uint8_t kind; /* R2U_INTEROP_* */
uint8_t confidence; /* 0..100 */
} R2UnityInterop;
R_API R2UnityMetadata *r2unity_parse_metadata (RBuffer *buf);
R_API void r2unity_free_metadata (R2UnityMetadata *meta);
R_API const char *r2unity_get_string (R2UnityMetadata *meta, uint32_t index);
@@ -321,6 +349,8 @@ R_API Il2CppMethodDefinition *r2unity_get_method_definitions (R2UnityMetadata *m
R_API Il2CppImageDefinition *r2unity_get_images (R2UnityMetadata *meta, size_t *count);
R_API Il2CppAssemblyDefinition *r2unity_get_assemblies (R2UnityMetadata *meta, size_t *count);
R_API int32_t *r2unity_get_referenced_assemblies (R2UnityMetadata *meta, size_t *count);
R_API R2UnityInterop *r2unity_enumerate_pinvokes (R2UnityMetadata *meta, size_t *count);
R_API void r2unity_free_interop (R2UnityInterop *items, size_t count);
/* 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);
+128 -2
View File
@@ -17,6 +17,7 @@ static void build_method_attrs_string (char *o, size_t osz, unsigned int flags)
const unsigned int MdFinal = 0x0020;
const unsigned int MdVirtual = 0x0040;
const unsigned int MdAbstract = 0x0400;
const unsigned int MdPinvokeImpl = IL2CPP_METHOD_ATTRIBUTE_PINVOKE_IMPL;
const char *vis = "";
switch (flags & MemberAccessMask) {
case MdPublic: vis = "public"; break;
@@ -47,6 +48,10 @@ static void build_method_attrs_string (char *o, size_t osz, unsigned int flags)
if (*tmp) strncat (tmp, " ", sizeof (tmp) - 1);
strncat (tmp, "final", sizeof (tmp) - 1);
}
if (flags & MdPinvokeImpl) {
if (*tmp) strncat (tmp, " ", sizeof (tmp) - 1);
strncat (tmp, "extern", sizeof (tmp) - 1);
}
if (!*tmp) {
strncpy (o, "", osz);
} else {
@@ -56,7 +61,7 @@ static void build_method_attrs_string (char *o, size_t osz, unsigned int flags)
}
static void print_usage (const char *prog_name) {
fprintf (stderr, "Usage: %s [-j] [-q] [-l N] [-f] [-a 0xADDR] [-c N] [-S] [-v] <executable> <path/to/global-metadata.dat>\n", prog_name);
fprintf (stderr, "Usage: %s [-j] [-q] [-l N] [-f] [-a 0xADDR] [-c N] [-S] [-P] [-F text|json|r2] [-v] <executable> <path/to/global-metadata.dat>\n", prog_name);
}
static const char *unity_range_from_wire (int wire) {
@@ -241,6 +246,110 @@ static int emit_sbom (R2UnityMetadata *meta, const char *exe_path, const char *m
return 0;
}
/* Cross-entry sort for stable output: sort by image_name, then name. */
static int interop_cmp (const void *a, const void *b) {
const R2UnityInterop *x = (const R2UnityInterop *) a;
const R2UnityInterop *y = (const R2UnityInterop *) b;
const char *xi = x->image_name ? x->image_name : "";
const char *yi = y->image_name ? y->image_name : "";
int c = strcmp (xi, yi);
if (c) {
return c;
}
const char *xn = x->name ? x->name : "";
const char *yn = y->name ? y->name : "";
return strcmp (xn, yn);
}
static int emit_pinvokes (R2UnityMetadata *meta, const char *exe_path,
const char *format, bool quiet) {
(void) exe_path;
size_t n = 0;
R2UnityInterop *items = r2unity_enumerate_pinvokes (meta, &n);
if (items && n > 1) {
qsort (items, n, sizeof (R2UnityInterop), interop_cmp);
}
const bool is_json = format && !strcmp (format, "json");
const bool is_r2 = format && !strcmp (format, "r2");
if (is_json) {
FILE *f = stdout;
fprintf (f, "{\"ok\":true,\"version\":%d,\"pinvokes\":[", meta->version);
for (size_t i = 0; i < n; i++) {
R2UnityInterop *it = &items[i];
if (i) fputc (',', f);
fprintf (f, "{\"image\":\"");
json_escape (f, it->image_name ? it->image_name : "");
fprintf (f, "\",\"method\":\"");
json_escape (f, it->name ? it->name : "");
fprintf (f, "\",\"token\":\"0x%08x\",\"flags\":\"0x%04x\",\"iflags\":\"0x%04x\",\"confidence\":%u",
it->token, it->flags, it->iflags, (unsigned) it->confidence);
if (it->dll_name) {
fprintf (f, ",\"dll\":\"");
json_escape (f, it->dll_name);
fprintf (f, "\"");
} else {
fprintf (f, ",\"dll\":null");
}
if (it->entry_name) {
fprintf (f, ",\"entry\":\"");
json_escape (f, it->entry_name);
fprintf (f, "\"");
} else {
fprintf (f, ",\"entry\":null");
}
fputc ('}', f);
}
fprintf (f, "],\"reverse_pinvokes\":[],\"anonymous_reverse_pinvokes\":[],\"interop_data\":null}\n");
} else if (is_r2) {
for (size_t i = 0; i < n; i++) {
R2UnityInterop *it = &items[i];
if (!it->name) {
continue;
}
char buf[1024];
if (it->image_name && *it->image_name) {
snprintf (buf, sizeof (buf), "sym.unity.%s.%s", it->image_name, it->name);
} else {
snprintf (buf, sizeof (buf), "sym.unity.%s", it->name);
}
r_name_filter (buf, -1);
if (it->dll_name) {
printf ("# PInvoke %s -> %s!%s\n", buf,
it->dll_name,
it->entry_name ? it->entry_name : it->name);
} else {
printf ("# PInvoke %s -> <unresolved>\n", buf);
}
}
} else {
if (!quiet) {
printf ("# P/Invoke methods (managed -> native), metadata wire version %d\n", meta->version);
printf ("# IMAGE\tMETHOD\tDLL\tENTRY\tFLAGS\tCONFIDENCE\n");
}
for (size_t i = 0; i < n; i++) {
R2UnityInterop *it = &items[i];
char attrs[128];
attrs[0] = 0;
build_method_attrs_string (attrs, sizeof (attrs), it->flags);
printf ("%s\t%s\t%s\t%s\t%s\t%u\n",
it->image_name ? it->image_name : "",
it->name ? it->name : "",
it->dll_name ? it->dll_name : "<unresolved>",
it->entry_name ? it->entry_name : "<default>",
*attrs ? attrs : "-",
(unsigned) it->confidence);
}
if (!quiet) {
printf ("# summary: pinvokes=%zu reverse_pinvokes=0 (wrapper resolution not yet implemented)\n", n);
}
}
r2unity_free_interop (items, n);
return 0;
}
int main (int argc, char *argv[]) {
bool json_one_line = false;
bool quiet = false;
@@ -250,14 +359,18 @@ int main (int argc, char *argv[]) {
size_t gmp_count = 0;
bool fast = false;
bool sbom = false;
bool pinvokes = false;
const char *format = "text";
int opt;
while ((opt = getopt (argc, argv, "jqfvSl:a:c:")) != -1) {
while ((opt = getopt (argc, argv, "jqfvSPl:a:c:F:")) != -1) {
switch (opt) {
case 'j': json_one_line = true; break;
case 'q': quiet = true; break;
case 'f': fast = true; break;
case 'v': debug = true; break;
case 'S': sbom = true; break;
case 'P': pinvokes = true; break;
case 'F': format = optarg; break;
case 'l': limit = strtol (optarg, NULL, 0); break;
case 'a': gmp_addr = (ut64) strtoull (optarg, NULL, 0); break;
case 'c': gmp_count = (size_t) strtoull (optarg, NULL, 0); break;
@@ -296,6 +409,19 @@ int main (int argc, char *argv[]) {
return rc;
}
if (pinvokes) {
if (strcmp (format, "text") && strcmp (format, "json") && strcmp (format, "r2")) {
fprintf (stderr, "r2unity: unknown -F format %s (expected text|json|r2)\n", format);
r2unity_free_metadata (meta);
r_unref (buf);
return 1;
}
int rc = emit_pinvokes (meta, exe_path, format, quiet);
r2unity_free_metadata (meta);
r_unref (buf);
return rc;
}
size_t type_count = 0;
Il2CppTypeDefinition *types = r2unity_get_type_definitions (meta, &type_count);