mirror of
https://github.com/radareorg/r2unity.git
synced 2026-11-07 06:36:00 -10:00
Add -P to enumerate all PInvokes
This commit is contained in:
1 parent
cdf1feec76
commit
5192b295b2
4 files changed
+333
-12
No files matched your search
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
Reference in new issue
Block a user