6 Commits
9 changed files with 991 additions and 32 deletions

No files matched your search

+5 -2
View File
@@ -106,9 +106,9 @@ jobs:
- name: Upload Debian package artifact
uses: actions/upload-artifact@v7
with:
name: debian-package
path: dist/debian/*.deb
if-no-files-found: error
archive: false
release:
name: Publish release
@@ -129,7 +129,9 @@ jobs:
- name: Download artifacts
uses: actions/download-artifact@v8
with:
pattern: '*.deb'
path: dist/artifacts
merge-multiple: true
- name: Generate release notes
run: ./release.sh notes > release-notes.md
@@ -142,5 +144,6 @@ jobs:
draft: false
prerelease: false
body_path: release-notes.md
fail_on_unmatched_files: true
files: |
dist/artifacts/debian-package/*.deb
dist/artifacts/*.deb
+1 -1
View File
@@ -40,7 +40,7 @@ R2PM_BINDIR=$(shell r2pm -H R2PM_BINDIR)
all: $(EXEC)
plugin: $(PLUGINS)
r2 plugin: $(PLUGINS)
install-plugin: $(PLUGINS)
@[ -n "$(R2_USER_PLUGINS)" ] || (echo "r2 not found; cannot resolve R2_USER_PLUGINS"; exit 1)
+1 -1
View File
@@ -1,7 +1,7 @@
project(
'r2unity',
'c',
version: '1.0.0',
version: '1.1.0',
default_options: [
'c_std=c99',
'warning_level=2',
+20 -3
View File
@@ -5,6 +5,14 @@
# $ sys/release-notes.sh 4.5.0 4.5.1 # from 4.5.0 to 4.5.1
#
release_log() {
if [ -n "$PREV" ]; then
git log ${PREV}..${VERS} > .l
else
git log ${VERS} > .l
fi
}
release_notes() {
if [ -n "`git log -n 1 | grep Release`" ]; then
VERS=`git tag --sort=committerdate | grep -v conti | tail -n 1`
@@ -16,15 +24,24 @@ fi
[ -n "$1" ] && PREV="$1"
[ -n "$2" ] && VERS="$2"
[ -z "$PREV" ] && PREV="$(git rev-list --max-parents=0 HEAD)"
FIRST_RELEASE=0
if [ "$PREV" = "$VERS" ]; then
if [ "`git tag | wc -l | awk '{print $1}'`" = 1 ]; then
PREV=
FIRST_RELEASE=1
else
PREV=`git tag --sort=committerdate | grep -v conti | tail -n 2 | head -n1`
fi
fi
[ -z "$PREV" ] && [ "$FIRST_RELEASE" = 0 ] && PREV="$(git rev-list --max-parents=0 HEAD)"
git log ${PREV}..${VERS} > .l
release_log
# When HEAD contains a tag do this magic
if [ ! -s .l ]; then
VERS=$PREV
PREV=`git tag --sort=committerdate | grep -v conti | tail -n 2 | head -n1`
git log ${PREV}..${VERS} > .l
release_log
fi
grep ^Author .l | cut -d : -f 2- | sed -e 's,radare,pancake,' | sort -u > .A
CODENAME="`git log | grep -i 'Release' | head -n 1 | cut -d - -f 2- | cut -d ' ' -f 3-`"
+45
View File
@@ -796,6 +796,51 @@ R_API Il2CppMethodDefinition *r2unity_get_method_definitions(R2UnityMetadata *me
return methods;
}
R_API Il2CppFieldDefinition *r2unity_get_field_definitions(R2UnityMetadata *meta, size_t *count) {
R_RETURN_VAL_IF_FAIL (meta && count, NULL);
const bool compact_indices = (meta->version >= 35);
const int type_index_size = compact_indices? meta->typeIndexSize: 4;
const ut64 entry = field_definition_entry_size (meta);
ut8 *buf = read_metadata_table (meta, R2U_SEC_FIELDS, entry, count);
if (!buf) {
return NULL;
}
Il2CppFieldDefinition *fields = R_NEWS (Il2CppFieldDefinition, *count);
if (!fields) {
R_FREE (buf);
return NULL;
}
for (size_t i = 0; i < *count; i++) {
const ut8 *p = buf + i * entry;
fields[i].nameIndex = read_u32p (&p);
fields[i].typeIndex = read_indexp (&p, type_index_size);
fields[i].token = read_u32p (&p);
}
R_FREE (buf);
return fields;
}
R_API int32_t *r2unity_get_type_index_table(R2UnityMetadata *meta, R2UMetadataSectionId id, size_t *count) {
R_RETURN_VAL_IF_FAIL (meta && count, NULL);
const bool compact_indices = (meta->version >= 35);
const int type_index_size = compact_indices? meta->typeIndexSize: 4;
ut8 *buf = read_metadata_table (meta, id, type_index_size, count);
if (!buf) {
return NULL;
}
int32_t *out = R_NEWS (int32_t, *count);
if (!out) {
R_FREE (buf);
return NULL;
}
for (size_t i = 0; i < *count; i++) {
const ut8 *p = buf + i * type_index_size;
out[i] = read_indexp (&p, type_index_size);
}
R_FREE (buf);
return out;
}
R_API Il2CppImageDefinition *r2unity_get_images(R2UnityMetadata *meta, size_t *count) {
R_RETURN_VAL_IF_FAIL (meta && count, NULL);
*count = 0;
+8
View File
@@ -119,6 +119,12 @@ typedef struct {
uint16_t parameterCount;
} Il2CppMethodDefinition;
typedef struct {
uint32_t nameIndex;
int32_t typeIndex;
uint32_t token;
} Il2CppFieldDefinition;
typedef struct {
uint32_t nameIndex;
int32_t assemblyIndex;
@@ -203,6 +209,8 @@ R_API Il2CppStringLiteral *r2unity_get_string_literals(R2UnityMetadata *meta, si
R_API bool r2unity_read_string_literal(R2UnityMetadata *meta, const Il2CppStringLiteral *lit, ut8 **out_bytes, size_t *out_len);
R_API Il2CppTypeDefinition *r2unity_get_type_definitions(R2UnityMetadata *meta, size_t *count);
R_API Il2CppMethodDefinition *r2unity_get_method_definitions(R2UnityMetadata *meta, size_t *count);
R_API Il2CppFieldDefinition *r2unity_get_field_definitions(R2UnityMetadata *meta, size_t *count);
R_API int32_t *r2unity_get_type_index_table(R2UnityMetadata *meta, R2UMetadataSectionId id, size_t *count);
R_API Il2CppImageDefinition *r2unity_get_images(R2UnityMetadata *meta, size_t *count);
R_API int *r2unity_build_type_image_map(const Il2CppImageDefinition *images, size_t image_count, size_t type_count);
R_API int r2unity_image_index_for_method(const int *type2img, size_t type_count, const Il2CppMethodDefinition *m);
+445 -11
View File
@@ -60,15 +60,15 @@ static void print_usage(FILE *out, const char *prog_name) {
"\n"
"Options:\n"
" -a 0xADDR Read the method pointer table starting at virtual address 0xADDR\n"
" -c N Read N pointer entries (pair with -a)\n"
" -c Enumerate classes, inheritance, methods, and fields\n"
" -D Detect companion files from the given executable path and exit\n"
" -f Fast path: auto-detect ELF/Mach-O/PE and scan method pointers\n"
" -h Show this help and exit\n"
" -j One-line JSON status, or JSON output with -P/-R/-S\n"
" -j One-line JSON status, or JSON output with -c/-P/-R/-S\n"
" -l N Limit emitted entries to N\n"
" -P Enumerate P/Invoke (managed -> native) methods\n"
" -q Quiet mode: omit banner and informational comments\n"
" -r Emit r2 script commands (flags + comments); pairs with -P\n"
" -r Emit r2 script commands; pairs with -c/-P/-R\n"
" -R Enumerate reverse-P/Invoke (native -> managed) methods (v29+)\n"
" -S Emit a text SBOM of the managed assemblies\n"
" -V Verbose debug tracing on stderr\n"
@@ -78,6 +78,7 @@ static void print_usage(FILE *out, const char *prog_name) {
"Arguments:\n"
" <executable> Native IL2CPP binary for the target platform\n"
" <global-metadata.dat> Unity IL2CPP metadata file\n"
" -c and -z also accept metadata-only input\n"
"\n"
"Expected files per target platform:\n"
" iOS build: UnityFramework + global-metadata.dat\n"
@@ -486,6 +487,420 @@ static bool emit_r2_method(R2UnityMetadata *meta,
return true;
}
static int type_definition_for_type_index(const Il2CppTypeDefinition *types, size_t type_count, int32_t type_index) {
if (type_index < 0) {
return -1;
}
for (size_t i = 0; i < type_count; i++) {
if (types[i].byvalTypeIndex == type_index) {
return (int)i;
}
}
return -1;
}
static char *type_name_from_index(R2UnityMetadata *meta, const Il2CppTypeDefinition *types, size_t type_count, int32_t type_index, bool fallback) {
int idx = type_definition_for_type_index (types, type_count, type_index);
if (idx >= 0) {
return r2unity_type_fullname (meta, &types[idx], (size_t)idx, R2U_NAME_FALLBACK_TYPE);
}
if (fallback && type_index >= 0) {
return r_str_newf ("type_index.%d", type_index);
}
return NULL;
}
static char *field_name_or_fallback(R2UnityMetadata *meta, const Il2CppFieldDefinition *field, size_t index) {
char *name = r2unity_get_string (meta, field->nameIndex);
if (name && *name) {
return name;
}
free (name);
return r_str_newf ("field.%zu", index);
}
static char *method_name_or_fallback(R2UnityMetadata *meta, const Il2CppMethodDefinition *method, size_t index) {
char *name = r2unity_get_string (meta, method->nameIndex);
if (name && *name) {
return name;
}
free (name);
return r_str_newf ("method.%zu", index);
}
static void sanitize_ic_name(char *s) {
if (!s) {
return;
}
r_name_filter (s, -1);
for (char *p = s; *p; p++) {
if (*p == '.' || *p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') {
*p = '_';
}
}
}
static char *r2_class_name(const char *name, size_t index) {
char *out = (name && *name)? strdup (name): r_str_newf ("type_%zu", index);
sanitize_ic_name (out);
if (!out || !*out) {
free (out);
out = r_str_newf ("type_%zu", index);
}
return out;
}
static char *r2_method_name(const char *name, size_t index) {
char *out = (name && *name)? strdup (name): r_str_newf ("method_%zu", index);
sanitize_ic_name (out);
if (!out || !*out) {
free (out);
out = r_str_newf ("method_%zu", index);
}
return out;
}
static void pj_hex(PJ *pj, const char *key, ut64 value, int width) {
RStrBuf *sb = r_strbuf_new ("");
if (!sb) {
pj_knull (pj, key);
return;
}
r_strbuf_appendf (sb, "0x%0*" PFMT64x, width, value);
char *s = r_strbuf_drain (sb);
pj_ks (pj, key, s? s: "");
free (s);
}
static void pj_string_or_null(PJ *pj, const char *key, const char *value) {
if (value) {
pj_ks (pj, key, value);
} else {
pj_knull (pj, key);
}
}
static void emit_class_text(R2UnityMetadata *meta,
const Il2CppTypeDefinition *types,
size_t type_count,
const Il2CppMethodDefinition *methods,
size_t method_count,
const Il2CppFieldDefinition *fields,
size_t field_count,
const int32_t *interfaces,
size_t interface_count,
size_t type_index) {
const Il2CppTypeDefinition *td = &types[type_index];
char *name = r2unity_type_fullname (meta, td, type_index, R2U_NAME_FALLBACK_TYPE);
char *base = type_name_from_index (meta, types, type_count, td->parentIndex, true);
printf ("class %zu %s", type_index, name? name: "-");
if (base) {
printf (" : %s", base);
}
if (td->interfaces_count > 0 && td->interfacesStart >= 0) {
bool first = true;
for (size_t k = 0; k < td->interfaces_count && (size_t)(td->interfacesStart + k) < interface_count; k++) {
char *iname = type_name_from_index (meta, types, type_count, interfaces[td->interfacesStart + k], true);
if (iname) {
printf ("%s%s", first? " implements ": ", ", iname);
first = false;
}
free (iname);
}
}
printf (" token=0x%08x fields=%u methods=%u\n", td->token, (unsigned)td->field_count, (unsigned)td->method_count);
int fstart = td->fieldStart;
for (int k = 0; fields && k < td->field_count && fstart >= 0 && (size_t)(fstart + k) < field_count; k++) {
size_t fi = (size_t)(fstart + k);
char *fname = field_name_or_fallback (meta, &fields[fi], fi);
char *ftype = type_name_from_index (meta, types, type_count, fields[fi].typeIndex, true);
printf (" field %zu %s %s token=0x%08x type_index=%d\n",
fi,
ftype? ftype: "-",
fname? fname: "-",
fields[fi].token,
fields[fi].typeIndex);
free (ftype);
free (fname);
}
int mstart = td->methodStart;
for (int k = 0; methods && k < td->method_count && mstart >= 0 && (size_t)(mstart + k) < method_count; k++) {
size_t mi = (size_t)(mstart + k);
char *mname = method_name_or_fallback (meta, &methods[mi], mi);
char *attrs = method_attrs (methods[mi].flags);
printf (" method %zu %s%s%s(%u) token=0x%08x flags=0x%04x\n",
mi,
attrs && *attrs? attrs: "",
attrs && *attrs? " ": "",
mname? mname: "-",
(unsigned)methods[mi].parameterCount,
methods[mi].token,
methods[mi].flags);
free (attrs);
free (mname);
}
free (base);
free (name);
}
static void emit_class_json(PJ *pj,
R2UnityMetadata *meta,
const Il2CppTypeDefinition *types,
size_t type_count,
const Il2CppMethodDefinition *methods,
size_t method_count,
const Il2CppFieldDefinition *fields,
size_t field_count,
const int32_t *interfaces,
size_t interface_count,
const ut64 *method_ptrs,
bool has_ptrs,
size_t type_index) {
const Il2CppTypeDefinition *td = &types[type_index];
char *name = r2unity_type_fullname (meta, td, type_index, R2U_NAME_FALLBACK_TYPE);
char *ns = r2unity_get_string (meta, td->namespaceIndex);
char *short_name = r2unity_get_string (meta, td->nameIndex);
char *base = type_name_from_index (meta, types, type_count, td->parentIndex, true);
pj_o (pj);
pj_kn (pj, "index", (ut64)type_index);
pj_string_or_null (pj, "name", name);
pj_string_or_null (pj, "namespace", ns);
pj_string_or_null (pj, "short_name", short_name);
pj_string_or_null (pj, "base", base);
pj_ki (pj, "base_type_index", td->parentIndex);
pj_hex (pj, "flags", td->flags, 8);
pj_hex (pj, "token", td->token, 8);
pj_ka (pj, "interfaces");
if (td->interfaces_count > 0 && td->interfacesStart >= 0) {
for (size_t k = 0; k < td->interfaces_count && (size_t)(td->interfacesStart + k) < interface_count; k++) {
int32_t idx = interfaces[td->interfacesStart + k];
char *iname = type_name_from_index (meta, types, type_count, idx, true);
pj_o (pj);
pj_ki (pj, "type_index", idx);
pj_string_or_null (pj, "name", iname);
pj_end (pj);
free (iname);
}
}
pj_end (pj);
pj_ka (pj, "fields");
int fstart = td->fieldStart;
for (int k = 0; fields && k < td->field_count && fstart >= 0 && (size_t)(fstart + k) < field_count; k++) {
size_t fi = (size_t)(fstart + k);
char *fname = field_name_or_fallback (meta, &fields[fi], fi);
char *ftype = type_name_from_index (meta, types, type_count, fields[fi].typeIndex, true);
pj_o (pj);
pj_kn (pj, "index", (ut64)fi);
pj_string_or_null (pj, "name", fname);
pj_ki (pj, "type_index", fields[fi].typeIndex);
pj_string_or_null (pj, "type", ftype);
pj_hex (pj, "token", fields[fi].token, 8);
pj_end (pj);
free (ftype);
free (fname);
}
pj_end (pj);
pj_ka (pj, "methods");
int mstart = td->methodStart;
for (int k = 0; methods && k < td->method_count && mstart >= 0 && (size_t)(mstart + k) < method_count; k++) {
size_t mi = (size_t)(mstart + k);
char *mname = method_name_or_fallback (meta, &methods[mi], mi);
char *fullname = r2unity_method_fullname (meta, &methods[mi], td, type_index, R2U_NAME_WITH_PARAMS | R2U_NAME_FALLBACK_TYPE);
char *attrs = method_attrs (methods[mi].flags);
ut64 addr = (has_ptrs && method_ptrs)? method_ptrs[mi]: 0;
pj_o (pj);
pj_kn (pj, "index", (ut64)mi);
pj_string_or_null (pj, "name", mname);
pj_string_or_null (pj, "full_name", fullname);
pj_kn (pj, "parameter_count", (ut64)methods[mi].parameterCount);
pj_kn (pj, "addr", addr);
pj_hex (pj, "token", methods[mi].token, 8);
pj_hex (pj, "flags", methods[mi].flags, 4);
pj_hex (pj, "iflags", methods[mi].iflags, 4);
pj_string_or_null (pj, "attrs", (attrs && *attrs)? attrs: NULL);
pj_end (pj);
free (attrs);
free (fullname);
free (mname);
}
pj_end (pj);
pj_end (pj);
free (base);
free (short_name);
free (ns);
free (name);
}
static void emit_class_r2(R2UnityMetadata *meta,
const Il2CppTypeDefinition *types,
size_t type_count,
const Il2CppMethodDefinition *methods,
size_t method_count,
const Il2CppFieldDefinition *fields,
size_t field_count,
const ut64 *method_ptrs,
bool has_ptrs,
size_t type_index,
bool quiet) {
const Il2CppTypeDefinition *td = &types[type_index];
char *name = r2unity_type_fullname (meta, td, type_index, R2U_NAME_FALLBACK_TYPE);
char *r2klass = r2_class_name (name, type_index);
if (!quiet) {
char *base = type_name_from_index (meta, types, type_count, td->parentIndex, false);
if (base) {
printf ("# class %s : %s\n", name? name: r2klass, base);
} else {
printf ("# class %s\n", name? name: r2klass);
}
free (base);
}
printf ("ic+%s @ 0\n", r2klass);
int mstart = td->methodStart;
for (int k = 0; methods && k < td->method_count && mstart >= 0 && (size_t)(mstart + k) < method_count; k++) {
size_t mi = (size_t)(mstart + k);
ut64 addr = (has_ptrs && method_ptrs)? method_ptrs[mi]: 0;
if (addr <= 0x1000) {
continue;
}
char *mname = method_name_or_fallback (meta, &methods[mi], mi);
char *r2meth = r2_method_name (mname, mi);
printf ("ic+%s.%s @ 0x%" PFMT64x "\n", r2klass, r2meth, addr);
free (r2meth);
free (mname);
}
if (!quiet) {
int fstart = td->fieldStart;
for (int k = 0; fields && k < td->field_count && fstart >= 0 && (size_t)(fstart + k) < field_count; k++) {
size_t fi = (size_t)(fstart + k);
char *fname = field_name_or_fallback (meta, &fields[fi], fi);
char *ftype = type_name_from_index (meta, types, type_count, fields[fi].typeIndex, true);
printf ("# field %s.%s %s type_index=%d token=0x%08x\n",
r2klass,
fname? fname: "-",
ftype? ftype: "-",
fields[fi].typeIndex,
fields[fi].token);
free (ftype);
free (fname);
}
}
free (r2klass);
free (name);
}
static int emit_classes(R2UnityMetadata *meta, const char *exe_path, const char *metadata_path, bool as_json, bool as_r2, bool fast, bool quiet, long limit) {
if (as_json && as_r2) {
R_LOG_ERROR ("-j and -r are mutually exclusive with -c");
return 1;
}
if (fast && (!exe_path || !*exe_path)) {
R_LOG_ERROR ("-c -f requires both executable and metadata paths");
return 1;
}
size_t type_count = 0;
Il2CppTypeDefinition *types = r2unity_get_type_definitions (meta, &type_count);
if (!types) {
R_LOG_ERROR ("unable to decode type definitions");
return 1;
}
size_t method_count = 0;
Il2CppMethodDefinition *methods = r2unity_get_method_definitions (meta, &method_count);
size_t field_count = 0;
Il2CppFieldDefinition *fields = r2unity_get_field_definitions (meta, &field_count);
size_t interface_count = 0;
int32_t *interfaces = r2unity_get_type_index_table (meta, R2U_SEC_INTERFACES, &interface_count);
ut64 *method_ptrs = NULL;
bool has_ptrs = false;
if (fast) {
has_ptrs = find_method_pointers_fast (meta, exe_path, &method_ptrs);
}
if (method_ptrs && !has_ptrs) {
for (size_t k = 0; k < method_count; k++) {
if (method_ptrs[k]) {
has_ptrs = true;
break;
}
}
}
size_t max = type_count;
if (limit >= 0 && (size_t)limit < max) {
max = (size_t)limit;
}
if (as_json) {
PJ *pj = pj_new ();
if (!pj) {
R_LOG_ERROR ("unable to allocate JSON builder");
R_FREE (method_ptrs);
R_FREE (interfaces);
R_FREE (fields);
R_FREE (methods);
R_FREE (types);
return 1;
}
pj_o (pj);
pj_kb (pj, "ok", true);
pj_ki (pj, "version", meta->version);
pj_ks (pj, "unity_range", r2unity_unity_range_from_wire (meta->version));
pj_kb (pj, "has_ptrs", has_ptrs);
pj_kn (pj, "types", (ut64)type_count);
pj_kn (pj, "methods", (ut64)method_count);
pj_kn (pj, "fields", (ut64)field_count);
pj_ka (pj, "classes");
for (size_t i = 0; i < max; i++) {
emit_class_json (pj, meta, types, type_count, methods, method_count, fields, field_count, interfaces, interface_count, method_ptrs, has_ptrs, i);
}
pj_end (pj);
pj_end (pj);
char *out = pj_drain (pj);
if (out) {
puts (out);
free (out);
}
} else if (as_r2) {
if (!quiet) {
printf ("# r2 script generated by r2unity -c\n");
printf ("# Input file: %s\n", metadata_path && *metadata_path? metadata_path: "-");
if (!has_ptrs) {
printf ("# Method ic+ entries need native addresses; pass -f with an executable to recover them.\n");
}
}
for (size_t i = 0; i < max; i++) {
emit_class_r2 (meta, types, type_count, methods, method_count, fields, field_count, method_ptrs, has_ptrs, i, quiet);
}
} else {
if (!quiet) {
printf ("# classes from %s\n", metadata_path && *metadata_path? metadata_path: "-");
printf ("# wire_version=%d (%s) types=%zu methods=%zu fields=%zu\n",
meta->version,
r2unity_unity_range_from_wire (meta->version),
type_count,
method_count,
field_count);
}
for (size_t i = 0; i < max; i++) {
emit_class_text (meta, types, type_count, methods, method_count, fields, field_count, interfaces, interface_count, i);
}
}
R_FREE (method_ptrs);
R_FREE (interfaces);
R_FREE (fields);
R_FREE (methods);
R_FREE (types);
return 0;
}
int main(int argc, char *argv[]) {
bool json_one_line = false;
bool r2_script = false;
@@ -493,16 +908,17 @@ int main(int argc, char *argv[]) {
bool debug = false;
long limit = -1;
ut64 gmp_addr = 0;
size_t gmp_count = 0;
bool fast = false;
bool sbom = false;
bool pinvokes = false;
bool reverse_pinvokes = false;
bool string_literals = false;
bool detect_paths = false;
bool classes = false;
int opt;
while ((opt = getopt (argc, argv, "hjrqfVvSPRDzl:a:c:")) != -1) {
while ((opt = getopt (argc, argv, "chjrqfVvSPRDzl:a:")) != -1) {
switch (opt) {
case 'c': classes = true; break;
case 'j': json_one_line = true; break;
case 'r': r2_script = true; break;
case 'q': quiet = true; break;
@@ -518,7 +934,6 @@ int main(int argc, char *argv[]) {
case 'z': string_literals = true; 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;
case 'h':
print_usage (stdout, argv[0]);
return 0;
@@ -538,9 +953,22 @@ int main(int argc, char *argv[]) {
}
return emit_detected_paths (argv[optind], json_one_line);
}
if (string_literals) {
if (classes) {
if (sbom || pinvokes || reverse_pinvokes || string_literals) {
R_LOG_ERROR ("-c cannot be combined with -S, -P, -R or -z");
return 1;
}
if (argc - optind != 1 && argc - optind != 2) {
print_usage (stderr, argv[0]);
return 1;
}
if (fast && argc - optind != 2) {
R_LOG_ERROR ("-c -f requires both executable and metadata paths");
return 1;
}
} else if (string_literals) {
if (json_one_line || fast || gmp_addr || sbom || pinvokes || reverse_pinvokes) {
R_LOG_ERROR ("-z cannot be combined with -j, -f, -a/-c, -S, -P or -R");
R_LOG_ERROR ("-z cannot be combined with -j, -f, -a, -S, -P or -R");
return 1;
}
if (argc - optind != 1 && argc - optind != 2) {
@@ -554,7 +982,7 @@ int main(int argc, char *argv[]) {
const char *exe_path = NULL;
const char *metadata_path = NULL;
if (string_literals && argc - optind == 1) {
if ((string_literals || classes) && argc - optind == 1) {
exe_path = "";
metadata_path = argv[optind];
} else {
@@ -607,6 +1035,13 @@ int main(int argc, char *argv[]) {
return rc;
}
if (classes) {
int rc = emit_classes (meta, exe_path, metadata_path, json_one_line, r2_script, fast, quiet, limit);
r2unity_free_metadata (meta);
r_unref (buf);
return rc;
}
if (string_literals) {
int rc = emit_string_literals (meta, metadata_path, quiet, limit);
r2unity_free_metadata (meta);
@@ -623,8 +1058,7 @@ int main(int argc, char *argv[]) {
ut64 *method_ptrs = NULL;
bool has_ptrs = false;
if (gmp_addr) {
R_LOG_WARN ("Manual method-pointer table reading (-a/-c) is not implemented");
(void)gmp_count;
R_LOG_WARN ("Manual method-pointer table reading (-a) is not implemented");
} else if (fast) {
has_ptrs = find_method_pointers_fast (meta, exe_path, &method_ptrs);
}
+54 -14
View File
@@ -16,6 +16,12 @@
#define R2UNITY_BIN_VEC_ABI 0
#endif
#if R2_ABIVERSION >= 104
#define R2UNITY_BIN_STRINGS_VEC_ABI 1
#else
#define R2UNITY_BIN_STRINGS_VEC_ABI 0
#endif
typedef struct {
R2UnityMetadata *meta;
RBuffer *buf;
@@ -225,9 +231,6 @@ static void section_free(void *p) {
static RBinSection *new_section(const char *name, ut64 off, ut64 size, bool has_strings) {
RBinSection *sec = R_NEW0 (RBinSection);
if (!sec) {
return NULL;
}
init_section (sec, name, off, size, has_strings);
return sec;
}
@@ -505,7 +508,7 @@ static bool symbols_vec(RBinFile *bf) {
#else
static RList *symbols(RBinFile *bf) {
RList *ret = r_list_newf ((RListFree)r_bin_symbol_free);
if (!ret || !symbols_fill (bf, ret)) {
if (!symbols_fill (bf, ret)) {
r_list_free (ret);
return NULL;
}
@@ -572,7 +575,33 @@ static RList *classes(RBinFile *bf) {
return ret;
}
static void append_bin_string(RList *ret, char *text, ut64 paddr, ut32 size, ut32 ordinal) {
#if R2UNITY_BIN_STRINGS_VEC_ABI
typedef RVecRBinString R2UnityStringSink;
static void append_bin_string(R2UnityStringSink *ret, char *text, ut64 paddr, ut32 size, ut32 ordinal) {
if (!text) {
return;
}
RBinString *str = RVecRBinString_emplace_back (ret);
if (!str) {
free (text);
return;
}
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;
}
#else
typedef RList R2UnityStringSink;
static void append_bin_string(R2UnityStringSink *ret, char *text, ut64 paddr, ut32 size, ut32 ordinal) {
if (!text) {
return;
}
RBinString *str = R_NEW0 (RBinString);
str->string = text;
str->paddr = paddr;
@@ -583,8 +612,9 @@ static void append_bin_string(RList *ret, char *text, ut64 paddr, ut32 size, ut3
str->type = R_STRING_TYPE_UTF8;
r_list_append (ret, str);
}
#endif
static void add_metadata_strings(R2UnityBinObj *obj, RList *ret, ut32 *ordinal) {
static void add_metadata_strings(R2UnityBinObj *obj, R2UnityStringSink *ret, ut32 *ordinal) {
if (!obj->strings || !obj->strings_size) {
return;
}
@@ -608,7 +638,7 @@ static void add_metadata_strings(R2UnityBinObj *obj, RList *ret, ut32 *ordinal)
}
}
static void add_literal_strings(R2UnityBinObj *obj, RList *ret, ut32 *ordinal) {
static void add_literal_strings(R2UnityBinObj *obj, R2UnityStringSink *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++) {
@@ -626,6 +656,20 @@ static void add_literal_strings(R2UnityBinObj *obj, RList *ret, ut32 *ordinal) {
R_FREE (lits);
}
#if R2UNITY_BIN_STRINGS_VEC_ABI
static RVecRBinString *strings(RBinFile *bf) {
R2UnityBinObj *obj = get_obj (bf);
R_RETURN_VAL_IF_FAIL (obj, NULL);
RVecRBinString *ret = RVecRBinString_new ();
if (!ret) {
return NULL;
}
ut32 ordinal = 0;
add_metadata_strings (obj, ret, &ordinal);
add_literal_strings (obj, ret, &ordinal);
return ret;
}
#else
static RList *strings(RBinFile *bf) {
R2UnityBinObj *obj = get_obj (bf);
R_RETURN_VAL_IF_FAIL (obj, NULL);
@@ -635,14 +679,12 @@ static RList *strings(RBinFile *bf) {
add_literal_strings (obj, ret, &ordinal);
return ret;
}
#endif
#if R2UNITY_BIN_VEC_ABI
typedef RVecRBinImport R2UnityImportSink;
static void append_import(R2UnityImportSink *ret, RBinImport *imp) {
if (!imp) {
return;
}
RBinImport *dst = RVecRBinImport_emplace_back (ret);
if (!dst) {
r_bin_import_free (imp);
@@ -655,9 +697,7 @@ static void append_import(R2UnityImportSink *ret, RBinImport *imp) {
typedef RList R2UnityImportSink;
static void append_import(R2UnityImportSink *ret, RBinImport *imp) {
if (imp) {
r_list_append (ret, imp);
}
r_list_append (ret, imp);
}
#endif
@@ -690,7 +730,7 @@ static bool imports_vec(RBinFile *bf) {
#else
static RList *imports(RBinFile *bf) {
RList *ret = r_list_newf ((RListFree)r_bin_import_free);
if (!ret || !imports_fill (bf, ret)) {
if (!imports_fill (bf, ret)) {
r_list_free (ret);
return NULL;
}
+412
View File
@@ -11,6 +11,7 @@ static const char *g_help_msg[] = {
"Usage:", "r2unity[-subcmd]", " Unity IL2CPP analyzer",
"r2unity", "", "show this help",
"r2unity?", "", "show this help",
"r2unity-c", "[*j]", "enumerate classes, inheritance, methods, and fields",
"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",
@@ -284,6 +285,415 @@ static bool find_method_pointers(R2UnityMetadata *meta, const char *path, ut64 *
return false;
}
static int type_definition_for_type_index(const Il2CppTypeDefinition *types, size_t type_count, int32_t type_index) {
if (type_index >= 0) {
for (size_t i = 0; i < type_count; i++) {
if (types[i].byvalTypeIndex == type_index) {
return (int)i;
}
}
}
return -1;
}
static char *type_name_from_index(R2UnityMetadata *meta, const Il2CppTypeDefinition *types, size_t type_count, int32_t type_index, bool fallback) {
int idx = type_definition_for_type_index (types, type_count, type_index);
if (idx >= 0) {
return r2unity_type_fullname (meta, &types[idx], (size_t)idx, R2U_NAME_FALLBACK_TYPE);
}
if (fallback && type_index >= 0) {
return r_str_newf ("type_index.%d", type_index);
}
return NULL;
}
static char *field_name_or_fallback(R2UnityMetadata *meta, const Il2CppFieldDefinition *field, size_t index) {
char *name = r2unity_get_string (meta, field->nameIndex);
if (R_STR_ISNOTEMPTY (name)) {
return name;
}
free (name);
return r_str_newf ("field.%zu", index);
}
static char *method_name_or_fallback(R2UnityMetadata *meta, const Il2CppMethodDefinition *method, size_t index) {
char *name = r2unity_get_string (meta, method->nameIndex);
if (R_STR_ISNOTEMPTY (name)) {
return name;
}
free (name);
return r_str_newf ("method.%zu", index);
}
static void sanitize_ic_name(char *s) {
if (!s) {
return;
}
r_name_filter (s, -1);
for (char *p = s; *p; p++) {
if (*p == '.' || *p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') {
*p = '_';
}
}
}
static char *r2_class_name(const char *name, size_t index) {
char *out = (name && *name)? strdup (name): r_str_newf ("type_%zu", index);
sanitize_ic_name (out);
if (!out || !*out) {
free (out);
out = r_str_newf ("type_%zu", index);
}
return out;
}
static char *r2_method_name(const char *name, size_t index) {
char *out = (name && *name)? r_str_newf ("%s_%zu", name, index): r_str_newf ("method_%zu", index);
sanitize_ic_name (out);
if (!out || !*out) {
free (out);
out = r_str_newf ("method_%zu", index);
}
return out;
}
static char *r2_field_name(const char *name, size_t index) {
char *out = (name && *name)? strdup (name): r_str_newf ("field_%zu", index);
sanitize_ic_name (out);
if (!out || !*out) {
free (out);
out = r_str_newf ("field_%zu", index);
}
return out;
}
static void pj_hex(PJ *pj, const char *key, ut64 value, int width) {
char hex[64];
snprintf (hex, sizeof (hex), "0x%0*" PFMT64x, width, value);
pj_ks (pj, key, hex);
}
static void pj_string_or_null(PJ *pj, const char *key, const char *value) {
if (value) {
pj_ks (pj, key, value);
} else {
pj_knull (pj, key);
}
}
static void emit_class_text(RCore *core,
R2UnityMetadata *meta,
const Il2CppTypeDefinition *types,
size_t type_count,
const Il2CppMethodDefinition *methods,
size_t method_count,
const Il2CppFieldDefinition *fields,
size_t field_count,
const int32_t *interfaces,
size_t interface_count,
size_t type_index) {
const Il2CppTypeDefinition *td = &types[type_index];
char *name = r2unity_type_fullname (meta, td, type_index, R2U_NAME_FALLBACK_TYPE);
char *base = type_name_from_index (meta, types, type_count, td->parentIndex, true);
r_cons_printf (core->cons, "class %zu %s", type_index, name? name: "-");
if (base) {
r_cons_printf (core->cons, " : %s", base);
}
if (interfaces && td->interfaces_count > 0 && td->interfacesStart >= 0) {
bool first = true;
for (size_t k = 0; k < td->interfaces_count && (size_t)(td->interfacesStart + k) < interface_count; k++) {
char *iname = type_name_from_index (meta, types, type_count, interfaces[td->interfacesStart + k], true);
if (iname) {
r_cons_printf (core->cons, "%s%s", first? " implements ": ", ", iname);
first = false;
}
free (iname);
}
}
r_cons_printf (core->cons, " token=0x%08x fields=%u methods=%u\n", td->token, (unsigned)td->field_count, (unsigned)td->method_count);
int fstart = td->fieldStart;
for (int k = 0; fields && k < td->field_count && fstart >= 0 && (size_t)(fstart + k) < field_count; k++) {
size_t fi = (size_t)(fstart + k);
char *fname = field_name_or_fallback (meta, &fields[fi], fi);
char *ftype = type_name_from_index (meta, types, type_count, fields[fi].typeIndex, true);
r_cons_printf (core->cons, " field %zu %s %s token=0x%08x type_index=%d\n",
fi,
ftype? ftype: "-",
fname? fname: "-",
fields[fi].token,
fields[fi].typeIndex);
free (ftype);
free (fname);
}
int mstart = td->methodStart;
for (int k = 0; methods && k < td->method_count && mstart >= 0 && (size_t)(mstart + k) < method_count; k++) {
size_t mi = (size_t)(mstart + k);
char *mname = method_name_or_fallback (meta, &methods[mi], mi);
char *attrs = method_attrs_str (methods[mi].flags);
r_cons_printf (core->cons, " method %zu %s%s%s(%u) token=0x%08x flags=0x%04x\n",
mi,
attrs && *attrs? attrs: "",
attrs && *attrs? " ": "",
mname? mname: "-",
(unsigned)methods[mi].parameterCount,
methods[mi].token,
methods[mi].flags);
free (attrs);
free (mname);
}
free (base);
free (name);
}
static void emit_class_json(PJ *pj,
R2UnityMetadata *meta,
const Il2CppTypeDefinition *types,
size_t type_count,
const Il2CppMethodDefinition *methods,
size_t method_count,
const Il2CppFieldDefinition *fields,
size_t field_count,
const int32_t *interfaces,
size_t interface_count,
const ut64 *method_ptrs,
bool has_ptrs,
size_t type_index) {
const Il2CppTypeDefinition *td = &types[type_index];
char *name = r2unity_type_fullname (meta, td, type_index, R2U_NAME_FALLBACK_TYPE);
char *ns = r2unity_get_string (meta, td->namespaceIndex);
char *short_name = r2unity_get_string (meta, td->nameIndex);
char *base = type_name_from_index (meta, types, type_count, td->parentIndex, true);
pj_o (pj);
pj_kn (pj, "index", (ut64)type_index);
pj_string_or_null (pj, "name", name);
pj_string_or_null (pj, "namespace", ns);
pj_string_or_null (pj, "short_name", short_name);
pj_string_or_null (pj, "base", base);
pj_ki (pj, "base_type_index", td->parentIndex);
pj_hex (pj, "flags", td->flags, 8);
pj_hex (pj, "token", td->token, 8);
pj_ka (pj, "interfaces");
if (interfaces && td->interfaces_count > 0 && td->interfacesStart >= 0) {
for (size_t k = 0; k < td->interfaces_count && (size_t)(td->interfacesStart + k) < interface_count; k++) {
int32_t idx = interfaces[td->interfacesStart + k];
char *iname = type_name_from_index (meta, types, type_count, idx, true);
pj_o (pj);
pj_ki (pj, "type_index", idx);
pj_string_or_null (pj, "name", iname);
pj_end (pj);
free (iname);
}
}
pj_end (pj);
pj_ka (pj, "fields");
int fstart = td->fieldStart;
for (int k = 0; fields && k < td->field_count && fstart >= 0 && (size_t)(fstart + k) < field_count; k++) {
size_t fi = (size_t)(fstart + k);
char *fname = field_name_or_fallback (meta, &fields[fi], fi);
char *ftype = type_name_from_index (meta, types, type_count, fields[fi].typeIndex, true);
pj_o (pj);
pj_kn (pj, "index", (ut64)fi);
pj_string_or_null (pj, "name", fname);
pj_ki (pj, "type_index", fields[fi].typeIndex);
pj_string_or_null (pj, "type", ftype);
pj_hex (pj, "token", fields[fi].token, 8);
pj_end (pj);
free (ftype);
free (fname);
}
pj_end (pj);
pj_ka (pj, "methods");
int mstart = td->methodStart;
for (int k = 0; methods && k < td->method_count && mstart >= 0 && (size_t)(mstart + k) < method_count; k++) {
size_t mi = (size_t)(mstart + k);
char *mname = method_name_or_fallback (meta, &methods[mi], mi);
char *fullname = r2unity_method_fullname (meta, &methods[mi], td, type_index, R2U_NAME_WITH_PARAMS | R2U_NAME_FALLBACK_TYPE);
char *attrs = method_attrs_str (methods[mi].flags);
ut64 addr = (has_ptrs && method_ptrs)? method_ptrs[mi]: 0;
pj_o (pj);
pj_kn (pj, "index", (ut64)mi);
pj_string_or_null (pj, "name", mname);
pj_string_or_null (pj, "full_name", fullname);
pj_kn (pj, "parameter_count", (ut64)methods[mi].parameterCount);
pj_kn (pj, "addr", addr);
pj_hex (pj, "token", methods[mi].token, 8);
pj_hex (pj, "flags", methods[mi].flags, 4);
pj_hex (pj, "iflags", methods[mi].iflags, 4);
pj_string_or_null (pj, "attrs", (attrs && *attrs)? attrs: NULL);
pj_end (pj);
free (attrs);
free (fullname);
free (mname);
}
pj_end (pj);
pj_end (pj);
free (base);
free (short_name);
free (ns);
free (name);
}
static void emit_class_r2(RCore *core,
R2UnityMetadata *meta,
const Il2CppTypeDefinition *types,
size_t type_count,
const Il2CppMethodDefinition *methods,
size_t method_count,
const Il2CppFieldDefinition *fields,
size_t field_count,
const int32_t *interfaces,
size_t interface_count,
const ut64 *method_ptrs,
bool has_ptrs,
size_t type_index) {
const Il2CppTypeDefinition *td = &types[type_index];
char *name = r2unity_type_fullname (meta, td, type_index, R2U_NAME_FALLBACK_TYPE);
char *r2klass = r2_class_name (name, type_index);
char *base = type_name_from_index (meta, types, type_count, td->parentIndex, false);
if (base) {
r_cons_printf (core->cons, "# class %s : %s\n", name? name: r2klass, base);
} else {
r_cons_printf (core->cons, "# class %s\n", name? name: r2klass);
}
r_cons_printf (core->cons, "ic+%s @ 0\n", r2klass);
if (base) {
char *r2base = r2_class_name (base, 0);
r_cons_printf (core->cons, "ic+%s:%s @ 0\n", r2klass, r2base);
free (r2base);
}
free (base);
if (interfaces && td->interfaces_count > 0 && td->interfacesStart >= 0) {
for (size_t k = 0; k < td->interfaces_count && (size_t)(td->interfacesStart + k) < interface_count; k++) {
char *iname = type_name_from_index (meta, types, type_count, interfaces[td->interfacesStart + k], false);
if (iname) {
char *r2iface = r2_class_name (iname, 0);
r_cons_printf (core->cons, "ic+%s:%s @ 0\n", r2klass, r2iface);
free (r2iface);
}
free (iname);
}
}
int mstart = td->methodStart;
for (int k = 0; methods && k < td->method_count && mstart >= 0 && (size_t)(mstart + k) < method_count; k++) {
size_t mi = (size_t)(mstart + k);
ut64 addr = (has_ptrs && method_ptrs)? method_ptrs[mi]: 0;
addr = addr > 0x1000? addr: 0;
char *mname = method_name_or_fallback (meta, &methods[mi], mi);
char *r2meth = r2_method_name (mname, mi);
r_cons_printf (core->cons, "ic+%s.%s @ 0x%" PFMT64x "\n", r2klass, r2meth, addr);
free (r2meth);
free (mname);
}
int fstart = td->fieldStart;
for (int k = 0; fields && k < td->field_count && fstart >= 0 && (size_t)(fstart + k) < field_count; k++) {
size_t fi = (size_t)(fstart + k);
char *fname = field_name_or_fallback (meta, &fields[fi], fi);
char *r2field = r2_field_name (fname, fi);
char *ftype = type_name_from_index (meta, types, type_count, fields[fi].typeIndex, true);
r_cons_printf (core->cons, "ic+%s..%s %s @ 0\n",
r2klass,
r2field,
ftype? ftype: "unknown");
free (ftype);
free (r2field);
free (fname);
}
free (r2klass);
free (name);
}
static int cmd_classes(RCore *core, char mode) {
const char *metadata_path = resolve_metadata_path (core);
RBuffer *buf = NULL;
R2UnityMetadata *meta = open_metadata (core, &buf);
if (!meta) {
return 1;
}
size_t type_count = 0;
Il2CppTypeDefinition *types = r2unity_get_type_definitions (meta, &type_count);
if (!types) {
R_LOG_ERROR ("unable to decode type definitions");
close_metadata (meta, buf);
return 1;
}
size_t method_count = 0;
Il2CppMethodDefinition *methods = r2unity_get_method_definitions (meta, &method_count);
size_t field_count = 0;
Il2CppFieldDefinition *fields = r2unity_get_field_definitions (meta, &field_count);
size_t interface_count = 0;
int32_t *interfaces = r2unity_get_type_index_table (meta, R2U_SEC_INTERFACES, &interface_count);
ut64 *method_ptrs = NULL;
bool has_ptrs = false;
const char *lib = resolve_library_path (core);
if (lib && *lib) {
has_ptrs = find_method_pointers (meta, lib, &method_ptrs);
}
if (method_ptrs && !has_ptrs) {
for (size_t k = 0; k < method_count; k++) {
if (method_ptrs[k]) {
has_ptrs = true;
break;
}
}
}
if (mode == 'j') {
PJ *pj = pj_new ();
pj_o (pj);
pj_kb (pj, "ok", true);
pj_ki (pj, "version", meta->version);
pj_ks (pj, "unity_range", r2unity_unity_range_from_wire (meta->version));
pj_kb (pj, "has_ptrs", has_ptrs);
pj_kn (pj, "types", (ut64)type_count);
pj_kn (pj, "methods", (ut64)method_count);
pj_kn (pj, "fields", (ut64)field_count);
pj_ka (pj, "classes");
for (size_t i = 0; i < type_count; i++) {
emit_class_json (pj, meta, types, type_count, methods, method_count, fields, field_count, interfaces, interface_count, method_ptrs, has_ptrs, i);
}
pj_end (pj);
pj_end (pj);
r_cons_println (core->cons, pj_string (pj));
pj_free (pj);
} else if (mode == '*') {
r_cons_println (core->cons, "# r2 script generated by r2unity-c");
r_cons_printf (core->cons, "# Input file: %s\n", metadata_path && *metadata_path? metadata_path: "-");
if (!has_ptrs) {
r_cons_println (core->cons, "# Method addresses default to 0; run r2unity-D or set r2unity.library to recover native addresses.");
}
for (size_t i = 0; i < type_count; i++) {
emit_class_r2 (core, meta, types, type_count, methods, method_count, fields, field_count, interfaces, interface_count, method_ptrs, has_ptrs, i);
}
} else {
r_cons_printf (core->cons, "# classes from %s\n", metadata_path && *metadata_path? metadata_path: "-");
r_cons_printf (core->cons, "# wire_version=%d (%s) types=%zu methods=%zu fields=%zu\n",
meta->version,
r2unity_unity_range_from_wire (meta->version),
type_count,
method_count,
field_count);
for (size_t i = 0; i < type_count; i++) {
emit_class_text (core, meta, types, type_count, methods, method_count, fields, field_count, interfaces, interface_count, i);
}
}
R_FREE (method_ptrs);
R_FREE (interfaces);
R_FREE (fields);
R_FREE (methods);
R_FREE (types);
close_metadata (meta, buf);
return 0;
}
/* ---------- r2unity-s (methods) ---------- */
static int cmd_symbols(RCore *core, char mode) {
/* mode: 0 = apply to r2 session, '*' = print r2 commands, 'j' = JSON */
@@ -678,6 +1088,8 @@ static bool r2unity_call(RCorePluginSession *cps, const char *input) {
}
switch (sub) {
case 'c':
return cmd_classes (core, mode) == 0;
case 'D':
return cmd_detect (core, mode == 'j') == 0;
case 'i':