8 Commits
Author SHA1 Message Date
Abhi c2f219c95a meson: guard the class-import test on the file's existence
test/test_import.py is referenced but not committed on all branches,
which makes meson configure abort on Windows with
ERROR: File test/test_import.py does not exist. Wire the test only
when the file is present.
2026-10-18 11:45:15 +05:30
Abhi 4a35664331 Fix bindist triplet naming on runners with older r2pm
CI's bindist job pins radare2 6.2.2, whose r2pm does not provide
R2PM_OS/ARCH/BITS yet, so the unix builds produced
r2unity-6.2.2---.zip. Fall back to uname (darwin/linux, x86/arm)
like the radare2-pm bindist recipes expect.
2026-10-18 11:18:47 +05:30
pancake 31cb6bce07 Improved companion paths detection 2026-10-08 00:54:55 +02:00
pancake ae094c666a Apply interop with -AAA 2026-10-08 00:47:31 +02:00
pancake ccaa476cac Add more -A levels for analysis info 2026-10-08 00:44:32 +02:00
pancake 0bcdb6b8aa Add r2unity-A to import class names 2026-10-08 00:37:42 +02:00
pancake 212085e7fb Fix method offsets, map native code and silence class imports 2026-10-08 00:34:02 +02:00
pancake aa3c6c6254 Register pkgname 2026-10-06 16:55:45 +02:00
7 changed files with 319 additions and 47 deletions

No files matched your search

+38 -5
View File
@@ -46,10 +46,18 @@ all: $(EXEC)
r2 plugin: $(PLUGINS)
test: $(EXEC) $(PLUGINS)
python3 test/test_import.py
install-plugin: $(PLUGINS)
@[ -n "$(R2_USER_PLUGINS)" ] || (echo "r2 not found; cannot resolve R2_USER_PLUGINS"; exit 1)
mkdir -p "$(R2_USER_PLUGINS)"
cp -f $(PLUGINS) "$(R2_USER_PLUGINS)/"
# Replace the inode so macOS does not reuse a stale code-signature cache.
@for plugin in $(PLUGINS); do \
name=$${plugin##*/}; \
cp -f "$$plugin" "$(R2_USER_PLUGINS)/.$$name.tmp" && \
mv -f "$(R2_USER_PLUGINS)/.$$name.tmp" "$(R2_USER_PLUGINS)/$$name" || exit 1; \
done
uninstall-plugin:
@[ -n "$(R2_USER_PLUGINS)" ] || (echo "r2 not found; cannot resolve R2_USER_PLUGINS"; exit 1)
@@ -59,15 +67,40 @@ uninstall-plugin:
fmt:
clang-format-radare2 src/**/*.c
user-install: install-plugin
user-install: $(EXEC) install-plugin
mkdir -p "$(R2PM_BINDIR)"
cp -f $(EXEC) "$(R2PM_BINDIR)"
cp -f $(EXEC) "$(R2PM_BINDIR)/.$(EXEC).tmp"
mv -f "$(R2PM_BINDIR)/.$(EXEC).tmp" "$(R2PM_BINDIR)/$(EXEC)"
user-uninstall: uninstall-plugin
rm -f "$(R2PM_BINDIR)/$(EXEC)"
# Binary package for r2pm -bi: plugins/ and bin/ for this r2 version and platform
BINDIST_ZIP = r2unity-$(shell r2 -qv)-$(shell r2pm -H R2PM_OS)-$(shell r2pm -H R2PM_ARCH)-$(shell r2pm -H R2PM_BITS).zip
# Binary package for r2pm -bi: plugins/ and bin/ for this r2 version and platform
# R2PM_OS/ARCH/BITS require a recent r2pm; fall back to uname when missing
R2PM_OS_SH = $(shell r2pm -H R2PM_OS 2>/dev/null)
R2PM_ARCH_SH = $(shell r2pm -H R2PM_ARCH 2>/dev/null)
R2PM_BITS_SH = $(shell r2pm -H R2PM_BITS 2>/dev/null)
ifeq ($(R2PM_OS_SH),)
ifeq ($(shell uname -s),Darwin)
R2PM_OS_SH = darwin
else
R2PM_OS_SH = linux
endif
endif
ifeq ($(R2PM_ARCH_SH),)
ifeq ($(shell uname -m),x86_64)
R2PM_ARCH_SH = x86
else ifneq (,$(filter $(shell uname -m),aarch64 arm64))
R2PM_ARCH_SH = arm
else
R2PM_ARCH_SH = $(shell uname -m)
endif
endif
ifeq ($(R2PM_BITS_SH),)
R2PM_BITS_SH = 64
endif
BINDIST_ZIP = r2unity-$(shell r2 -qv)-$(R2PM_OS_SH)-$(R2PM_ARCH_SH)-$(R2PM_BITS_SH).zip
bindist: $(EXEC) $(PLUGINS)
rm -rf build/bindist $(BINDIST_ZIP)
@@ -96,4 +129,4 @@ clean:
rm -f $(EXEC) $(OBJS) $(LEGACY_OBJS) $(PLUGINS) $(CONFIG_H)
.PHONY: all clean plugin install-plugin uninstall-plugin fmt bindist
.PHONY: all clean plugin install-plugin uninstall-plugin fmt bindist test
+31 -4
View File
@@ -93,19 +93,46 @@ After installing the plugins, open a Unity binary in r2 and use:
```text
r2unity? show help
r2unity-A import classes and method flags (same as .r2unity-c*)
r2unity-AA also import method comments, strings, native tables and interop
r2unity-AAA also analyze native references (aar)
r2unity-AAAA also perform native analysis (aaa)
r2unity-D detect and cache companion file paths
r2unity-L open/select and map the IL2CPP native library
r2unity-c[*j] list classes, or emit an import script / JSON
r2unity-i[j] show metadata summary
r2unity-s apply managed method flags/comments
r2unity-s* print the r2 commands instead of applying them
r2unity-z[j] list managed string literals
r2unity-P[*j] list P/Invoke entries
r2unity-R[*j] list reverse-P/Invoke entries
r2unity-z[+j] list managed string literals (+ imports them)
r2unity-P[+*j] list P/Invoke entries (+ imports flags and comments)
r2unity-R[+*j] list reverse-P/Invoke entries (+ imports known wrappers)
r2unity-S emit managed-assembly SBOM text summary
r2unity-Sj emit managed-assembly CycloneDX JSON
```
Use `r2unity-A` (equivalent to `.r2unity-c*`) to import classes and method
flags. The script loads the native companion library automatically, so
`s sym.unity.<class>.<method>` followed by `pd` shows the method's native code.
Methods without a native implementation retain address zero and do not get
a seekable flag.
The analysis levels are cumulative. `r2unity-AA` adds method signatures and
comments, native registration tables, managed strings, P/Invoke method flags
and comments, and known reverse-P/Invoke wrappers. It maps `global-metadata.dat`
read-only at an unused address range named `r2unity.metadata`. Seek to
`str.unity.<index>` to read a literal's actual bytes; these addresses refer to
the mapped metadata, rather than runtime managed string objects. You can also
import strings or interop separately with `r2unity-z+`, `r2unity-P+`, and
`r2unity-R+`.
`r2unity-AAA` performs all of those imports and then runs `aar` to find native
references. `r2unity-AAAA` additionally runs `aaa` for deeper native analysis.
These two levels take longer on large binaries.
Set `r2unity.metadata` and `r2unity.library` manually when auto-detection is not
enough. The `bin_r2unity` plugin also lets radare2/rabin2 treat
enough. iOS detection checks both the app's `Data` directory and
`Frameworks/UnityFramework.framework/Data` for managed metadata.
The `bin_r2unity` plugin also lets radare2/rabin2 treat
`global-metadata.dat` as a binary format, exposing sections, strings, symbols,
classes, imports, libraries, and header fields. It also recognizes loose Unity
SerializedFile v22 inputs such as `sharedassets*.assets`:
+12 -2
View File
@@ -1,7 +1,7 @@
project(
'r2unity',
'c',
version: '1.2.8',
version: '1.2.9',
default_options: [
'c_std=c99',
'warning_level=2',
@@ -91,7 +91,7 @@ if build_plugins
warning('r2 was not found; installing plugins under ' + r2_plugindir)
endif
shared_library(
core_plugin = shared_library(
'core_r2unity',
files('src/r2/core_r2unity.c') + lib_sources,
include_directories: lib_inc,
@@ -115,4 +115,14 @@ if build_plugins
install_dir: r2_plugindir,
)
# test_import.py is not always present (it is not committed yet on every
# branch); only wire the test when the file exists
fs = import('fs')
python = find_program('python3', required: false)
if python.found() and fs.exists('test' / 'test_import.py')
test('r2unity-class-import', python,
args: [files('test/test_import.py'), core_plugin],
)
endif
endif
+13 -7
View File
@@ -156,6 +156,7 @@ typedef struct {
R2UnityMetadata *meta;
Il2CppImageDefinition *images;
Il2CppTypeDefinition *types;
Il2CppMethodDefinition *methods;
size_t image_count;
size_t type_count;
size_t method_count;
@@ -166,13 +167,14 @@ static bool method_tables_init(R2UnityMethodTables *tables, R2UnityMetadata *met
tables->meta = meta;
tables->images = r2unity_get_images (meta, &tables->image_count);
tables->types = r2unity_get_type_definitions (meta, &tables->type_count);
tables->method_count = (size_t)r2unity_metadata_section_count (meta, R2U_SEC_METHODS);
return tables->images && tables->image_count && tables->types && tables->type_count && tables->method_count;
tables->methods = r2unity_get_method_definitions (meta, &tables->method_count);
return tables->images && tables->image_count && tables->types && tables->type_count && tables->methods && tables->method_count;
}
static void method_tables_fini(R2UnityMethodTables *tables) {
R_FREE (tables->images);
R_FREE (tables->types);
R_FREE (tables->methods);
}
static int image_index_by_name(R2UnityMethodTables *tables, const char *module_name) {
@@ -215,23 +217,27 @@ static size_t copy_image_method_table(R2UnityNativeView *view, R2UnityMethodTabl
if (image->typeStart < 0) {
return 0;
}
size_t local = 0;
size_t copied = 0;
size_t start = (size_t)image->typeStart;
size_t end = R_MIN (tables->type_count, start + image->typeCount);
for (size_t ti = start; ti < end && local < table_count; ti++) {
for (size_t ti = start; ti < end; ti++) {
const Il2CppTypeDefinition *td = &tables->types[ti];
if (td->methodStart < 0) {
local += td->method_count;
continue;
}
for (size_t k = 0; k < td->method_count && local < table_count; k++, local++) {
for (size_t k = 0; k < td->method_count; k++) {
size_t mi = (size_t)td->methodStart + k;
if (mi >= tables->method_count) {
continue;
}
ut32 token = tables->methods[mi].token;
ut32 rid = token & 0x00ffffff;
if ((token >> 24) != 0x06 || !rid || rid > table_count) {
continue;
}
ut64 raw = 0;
if (!read_ptr_at (view, table_va + (ut64)local * view->ptr_size, &raw)) {
// CodeGenModule method pointers are indexed by the MethodDef token RID.
if (!read_ptr_at (view, table_va + (ut64)(rid - 1) * view->ptr_size, &raw)) {
continue;
}
ut64 addr = code_va_from_raw (view, raw);
+17 -4
View File
@@ -148,25 +148,33 @@ static bool try_ios(R2UnityPaths *p, const char *abs, const char *dir, const cha
* Flat iOS layout: Game.app/UnityFramework (older builds)
* Extracted app root: Game/Data plus the main Mach-O beside Data
* Metadata: Game.app/Data/Managed/Metadata/global-metadata.dat
* Game.app/Frameworks/UnityFramework.framework/Data/Managed/Metadata/global-metadata.dat
* Game.app/Data/Raw/Managed/Metadata/global-metadata.dat (newer) */
char *app_dir = NULL;
if (r_str_endswith (dir, ".framework") && !r_str_casecmp (base, "UnityFramework")) {
char *frameworks = r_file_dirname (dir);
app_dir = r_file_dirname (frameworks);
free (frameworks);
} else if (r_str_endswith (dir, ".app")) {
app_dir = strdup (dir);
} else {
app_dir = strdup (dir);
}
char *metadata = find_ios_metadata (app_dir);
char *framework_dir = r_file_new (app_dir, "Frameworks", "UnityFramework.framework", NULL);
char *metadata = find_ios_metadata (framework_dir);
char *data_dir = metadata? r_file_new (framework_dir, "Data", NULL): NULL;
free (framework_dir);
if (!metadata) {
metadata = find_ios_metadata (app_dir);
if (metadata) {
data_dir = r_file_new (app_dir, "Data", NULL);
}
}
if (!metadata) {
free (app_dir);
return false;
}
p->platform = strdup ("ios");
p->metadata = metadata;
p->data_dir = r_file_new (app_dir, "Data", NULL);
p->data_dir = data_dir;
if (!r_str_casecmp (base, "UnityFramework")) {
p->il2cpp_binary = strdup (abs);
} else if (!take_if_exists (&p->il2cpp_binary,
@@ -314,6 +322,11 @@ static char *expand_app_bundle(const char *dir) {
* Mach-O, whose basename is project-specific. */
static char *expand_ios_root_dir(const char *dir) {
char *metadata = find_ios_metadata (dir);
if (!metadata) {
char *framework = r_file_new (dir, "Frameworks", "UnityFramework.framework", NULL);
metadata = find_ios_metadata (framework);
free (framework);
}
if (!metadata) {
return NULL;
}
+2 -2
View File
@@ -822,7 +822,7 @@ static void emit_class_r2(R2UnityMetadata *meta,
}
free (base);
}
printf ("ic+%s @ 0\n", r2klass);
printf ("'@0x0'ic+ %s\n", r2klass);
int mstart = td->methodStart;
for (int k = 0; methods && k < td->method_count && mstart >= 0 && (size_t)(mstart + k) < method_count; k++) {
@@ -833,7 +833,7 @@ static void emit_class_r2(R2UnityMetadata *meta,
}
char *mname = method_name_or_fallback (meta, &methods[mi], mi);
char *r2meth = r2_ic_name ("method", mname, mi);
printf ("ic+%s.%s @ 0x%" PFMT64x "\n", r2klass, r2meth, addr);
printf ("'@0x%" PFMT64x "'ic+ %s.%s\n", addr, r2klass, r2meth);
free (r2meth);
free (mname);
}
+206 -23
View File
@@ -13,13 +13,18 @@ static const char *g_help_msg[] = {
"Usage:", "r2unity[-subcmd]", " Unity IL2CPP analyzer",
"r2unity", "", "show this help",
"r2unity?", "", "show this help",
"r2unity-A", "", "import classes and method flags (same as .r2unity-c*)",
"r2unity-AA", "", "also import method comments, strings, native tables and interop",
"r2unity-AAA", "", "also analyze native references (aar)",
"r2unity-AAAA", "", "also perform native analysis (aaa)",
"r2unity-c", "[*j]", "enumerate classes, inheritance, methods, and fields",
"r2unity-D", "", "auto-detect companion files from current binary path",
"r2unity-L", "", "open/select and map the IL2CPP native library",
"r2unity-i", "[j]", "summary (metadata version, type/method counts)",
"r2unity-s", "[*j]", "apply/list managed/native symbols as flags + comments",
"r2unity-z", "[j]", "list managed string literals",
"r2unity-P", "[*j]", "list P/Invoke (managed -> native)",
"r2unity-R", "[*j]", "list reverse-P/Invoke (native -> managed)",
"r2unity-z", "[+j]", "list managed string literals (+ imports flags and string metadata)",
"r2unity-P", "[+*j]", "list P/Invoke (managed -> native, + imports flags and comments)",
"r2unity-R", "[+*j]", "list reverse-P/Invoke (native -> managed, + imports wrappers)",
"r2unity-S", "[j]", "emit managed-assembly SBOM (text or JSON)", "Variables:", "", "",
"r2unity.metadata", "", "path to global-metadata.dat",
"r2unity.library", "", "path to IL2CPP native library",
@@ -287,6 +292,28 @@ static const char *resolve_library_path(RCore *core) {
return R_STR_ISNOTEMPTY (v)? v: NULL;
}
static int cmd_load_library(RCore *core) {
const char *path = resolve_library_path (core);
if (!path) {
R_LOG_ERROR ("r2unity.library is not set. Run r2unity-D or set it manually");
return 1;
}
RBinFile *bf = r_bin_file_find_by_name (core->bin, path);
if (bf) {
if (!r_core_bin_set_by_fd (core, bf->fd) || !r_io_use_fd (core->io, bf->fd)) {
return 1;
}
} else if (!current_binary_matches_path (core, path)) {
RIODesc *desc = r_core_file_open (core, path, R_PERM_R, UT64_MAX);
if (!desc || !r_core_bin_load (core, path, UT64_MAX)) {
R_LOG_ERROR ("cannot load IL2CPP native library: %s", path);
return 1;
}
}
r_core_block_read (core);
return 0;
}
static R2UnityMetadata *open_metadata(RCore *core, RBuffer **out_buf) {
const char *path = resolve_metadata_path (core);
if (!path) {
@@ -334,6 +361,7 @@ static int cmd_detect(RCore *core, bool as_json) {
}
R2UnityPaths *p = r2unity_detect_paths (bin);
if (!p) {
R_LOG_WARN ("could not find global-metadata.dat companion for %s", bin);
if (as_json) {
PJ *pj = pj_new ();
pj_o (pj);
@@ -342,8 +370,6 @@ static int cmd_detect(RCore *core, bool as_json) {
pj_end (pj);
r_cons_println (core->cons, pj_string (pj));
pj_free (pj);
} else {
R_LOG_ERROR ("could not detect Unity IL2CPP layout from %s", bin);
}
return 1;
}
@@ -352,6 +378,8 @@ static int cmd_detect(RCore *core, bool as_json) {
}
if (p->il2cpp_binary) {
r_config_set (core->config, "r2unity.library", p->il2cpp_binary);
} else {
R_LOG_WARN ("could not find IL2CPP native library companion for %s", bin);
}
if (as_json) {
PJ *pj = pj_new ();
@@ -667,10 +695,10 @@ static void emit_class_r2(RCore *core,
} else {
r_cons_printf (core->cons, "# class %s\n", name? name: r2klass);
}
r_cons_printf (core->cons, "ic+%s @ 0\n", r2klass);
r_cons_printf (core->cons, "'@0x0'ic+ %s\n", r2klass);
if (base) {
char *r2base = r2_ic_name ("type", base, 0, false);
r_cons_printf (core->cons, "ic+%s:%s @ 0\n", r2klass, r2base);
r_cons_printf (core->cons, "'@0x0'ic+ %s:%s\n", r2klass, r2base);
free (r2base);
}
free (base);
@@ -679,7 +707,7 @@ static void emit_class_r2(RCore *core,
char *iname = type_name_from_index (meta, types, type_count, interfaces[td->interfacesStart + k], false);
if (iname) {
char *r2iface = r2_ic_name ("type", iname, 0, false);
r_cons_printf (core->cons, "ic+%s:%s @ 0\n", r2klass, r2iface);
r_cons_printf (core->cons, "'@0x0'ic+ %s:%s\n", r2klass, r2iface);
free (r2iface);
}
free (iname);
@@ -693,7 +721,10 @@ static void emit_class_r2(RCore *core,
addr = addr > 0x1000? addr: 0;
char *mname = method_name_or_fallback (meta, &methods[mi], mi);
char *r2meth = r2_ic_name ("method", mname, mi, true);
r_cons_printf (core->cons, "ic+%s.%s @ 0x%" PFMT64x "\n", r2klass, r2meth, addr);
r_cons_printf (core->cons, "'@0x%" PFMT64x "'ic+ %s.%s\n", addr, r2klass, r2meth);
if (addr) {
r_cons_printf (core->cons, "'@0x%" PFMT64x "'f sym.unity.%s.%s 1\n", addr, r2klass, r2meth);
}
free (r2meth);
free (mname);
}
@@ -704,7 +735,7 @@ static void emit_class_r2(RCore *core,
char *fname = field_name_or_fallback (meta, &fields[fi], fi);
char *r2field = r2_ic_name ("field", fname, fi, false);
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",
r_cons_printf (core->cons, "'@0x0'ic+ %s..%s %s\n",
r2klass,
r2field,
ftype? ftype: "unknown");
@@ -769,6 +800,9 @@ static int cmd_classes(RCore *core, char 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: "-");
print_native_result (core, &native_result);
if (has_ptrs) {
r_cons_println (core->cons, "r2unity-L");
}
if (!has_ptrs) {
r_cons_println (core->cons, "# Method addresses default to 0; run r2unity-D or set r2unity.library to recover native addresses.");
}
@@ -805,6 +839,9 @@ static int cmd_symbols(RCore *core, char mode) {
R_LOG_ERROR ("r2unity.library is not set. Run r2unity-D or set it manually");
return 1;
}
if (!mode && cmd_load_library (core)) {
return 1;
}
RBuffer *buf = NULL;
R2UnityMetadata *meta = open_metadata (core, &buf);
if (!meta) {
@@ -836,6 +873,9 @@ static int cmd_symbols(RCore *core, char mode) {
pj_ka (pj, "methods");
} else if (mode == '*') {
print_native_result (core, &native_result);
if (has_ptrs) {
r_cons_println (core->cons, "r2unity-L");
}
}
if (mode != 'j') {
@@ -981,6 +1021,46 @@ static int cmd_symbols(RCore *core, char mode) {
}
/* ---------- r2unity-z (string literals) ---------- */
static bool map_metadata(RCore *core, ut64 size, ut64 *base) {
const char *path = resolve_metadata_path (core);
RIODesc *desc = r_io_desc_get_byuri (core->io, path);
if (desc) {
RList *maps = r_io_map_get_by_fd (core->io, desc->fd);
RListIter *iter;
RIOMap *map;
r_list_foreach (maps, iter, map) {
if (!map->delta && r_itv_size (map->itv) >= size
&& r_io_map_get_at (core->io, r_io_map_begin (map)) == map) {
*base = r_io_map_begin (map);
r_list_free (maps);
return true;
}
}
r_list_free (maps);
}
*base = 0;
if (!r_io_map_locate (core->io, base, size, 0x1000)) {
R_LOG_ERROR ("cannot find space to map metadata");
return false;
}
int fd = r_io_fd_get_current (core->io);
bool opened = !desc;
if (opened) {
desc = r_io_open_nomap (core->io, path, R_PERM_R, 0);
r_io_use_fd (core->io, fd);
}
RIOMap *map = desc? r_io_map_add (core->io, desc->fd, R_PERM_R, 0, *base, size): NULL;
if (!map) {
if (opened && desc) {
r_io_desc_close (desc);
}
R_LOG_ERROR ("cannot map metadata: %s", path);
return false;
}
r_io_map_set_name (map, "r2unity.metadata");
return true;
}
static int cmd_strings(RCore *core, char mode) {
RBuffer *buf = NULL;
R2UnityMetadata *meta = open_metadata (core, &buf);
@@ -990,9 +1070,20 @@ static int cmd_strings(RCore *core, char mode) {
size_t count = 0;
Il2CppStringLiteral *lits = r2unity_get_string_literals (meta, &count);
if (!lits) {
R_LOG_ERROR ("no string literals found");
if (mode != '+') {
R_LOG_ERROR ("no string literals found");
}
close_metadata (meta, buf);
return 1;
return mode == '+'? 0: 1;
}
ut64 base = 0;
if (mode == '+') {
if (!map_metadata (core, r_buf_size (buf), &base)) {
R_FREE (lits);
close_metadata (meta, buf);
return 1;
}
r_flag_set (core->flags, "r2unity.metadata", base, r_buf_size (buf));
}
PJ *pj = NULL;
if (mode == 'j') {
@@ -1001,21 +1092,35 @@ static int cmd_strings(RCore *core, char mode) {
pj_kb (pj, "ok", true);
pj_kn (pj, "count", (ut64)count);
pj_ka (pj, "strings");
} else {
} else if (mode != '+') {
r_cons_printf (core->cons, "# managed string literals (count=%zu)\n", count);
r_cons_printf (core->cons, "# idx\tdata_off\tlen\ttext\n");
}
for (size_t i = 0; i < count; i++) {
ut8 *bytes = NULL;
size_t len = 0;
ut32 data_off = meta->stringLiteralDataOffset + lits[i].dataIndex;
if (!r2unity_read_string_literal (meta, &lits[i], &bytes, &len)) {
if (mode != 'j') {
r_cons_printf (core->cons, "%zu\t0x%x\t%u\t<invalid>\n", i, data_off, lits[i].length);
ut64 data_off = (ut64)meta->stringLiteralDataOffset + lits[i].dataIndex;
if (lits[i].dataIndex > (ut64)meta->stringLiteralDataSize
|| !r2unity_read_string_literal (meta, &lits[i], &bytes, &len)) {
if (mode != 'j' && mode != '+') {
r_cons_printf (core->cons, "%zu\t0x%" PFMT64x "\t%u\t<invalid>\n", i, data_off, lits[i].length);
}
continue;
}
if (mode == 'j') {
if (mode == '+') {
char flag[64];
snprintf (flag, sizeof (flag), "str.unity.%zu", i);
ut64 addr = base + data_off;
r_flag_set (core->flags, flag, addr, len);
if (len && len <= INT_MAX) {
char *text = r_str_ndup ((const char *)bytes, (int)len);
if (text) {
r_meta_set_with_subtype (core->anal, R_META_TYPE_STRING,
R_STRING_ENC_UTF8, addr, len, text);
free (text);
}
}
} else if (mode == 'j') {
pj_o (pj);
pj_kn (pj, "idx", (ut64)i);
pj_kn (pj, "data_off", (ut64)data_off);
@@ -1031,7 +1136,7 @@ static int cmd_strings(RCore *core, char mode) {
} else {
char *text = r_str_ndup ((const char *)bytes, (int)len);
char *escaped = text? r_str_escape (text): NULL;
r_cons_printf (core->cons, "%zu\t0x%x\t%u\t\"%s\"\n", i, data_off, lits[i].length, escaped? escaped: "");
r_cons_printf (core->cons, "%zu\t0x%" PFMT64x "\t%u\t\"%s\"\n", i, data_off, lits[i].length, escaped? escaped: "");
free (escaped);
free (text);
}
@@ -1100,7 +1205,48 @@ static void print_interop_wrapper_flag(RCore *core, const char *name, ut64 wrapp
}
}
static void apply_interop(RCore *core, const R2UnityInterop *it, bool reverse, ut64 addr) {
if (!addr || !it->name) {
return;
}
char *name = interop_flag_name (reverse? "sym.unity.reverse": "sym.unity.pinvoke", it);
if (!name) {
return;
}
if (!reverse) {
char *indexed = r_str_newf ("%s.%d", name, it->method_index);
free (name);
name = indexed;
if (!name) {
return;
}
}
r_flag_set (core->flags, name, addr, 1);
if (reverse && !r_anal_get_function_at (core->anal, addr)) {
r_anal_create_function (core->anal, name, addr, R_ANAL_FCN_TYPE_FCN, NULL);
}
char *comment = reverse
? r_str_newf ("ReversePInvoke %s [%s]", it->name, interop_kind_label (it->kind))
: it->dll_name
? r_str_newf ("PInvoke %s -> %s!%s", it->name, it->dll_name,
it->entry_name? it->entry_name: it->name)
: r_str_newf ("PInvoke %s -> <unresolved>", it->name);
const char *previous = r_meta_get_string (core->anal, R_META_TYPE_COMMENT, addr);
if (comment && (!previous || !strstr (previous, comment))) {
char *combined = previous? r_str_newf ("%s\n%s", previous, comment): strdup (comment);
if (combined) {
r_meta_set_string (core->anal, R_META_TYPE_COMMENT, addr, combined);
free (combined);
}
}
free (comment);
free (name);
}
static int cmd_interop(RCore *core, bool reverse, char mode) {
if (mode == '+' && cmd_load_library (core)) {
return 1;
}
RBuffer *buf = NULL;
R2UnityMetadata *meta = open_metadata (core, &buf);
if (!meta) {
@@ -1110,6 +1256,11 @@ static int cmd_interop(RCore *core, bool reverse, char mode) {
R2UnityInterop *items = reverse
? r2unity_enumerate_reverse_pinvokes (meta, &n)
: r2unity_enumerate_pinvokes (meta, &n);
R2UnityNativeResult native_result = { 0 };
size_t method_count = r2unity_metadata_section_count (meta, R2U_SEC_METHODS);
if (!reverse && mode == '+' && n) {
find_method_pointers (core, meta, resolve_library_path (core), &native_result);
}
if (reverse) {
const char *lib = resolve_library_path (core);
if (lib) {
@@ -1162,6 +1313,13 @@ static int cmd_interop(RCore *core, bool reverse, char mode) {
}
}
pj_end (pj);
} else if (mode == '+') {
ut64 addr = reverse? it->wrapper_va: 0;
if (!reverse && native_result.method_ptrs && it->method_index >= 0
&& (size_t)it->method_index < method_count) {
addr = native_result.method_ptrs[it->method_index];
}
apply_interop (core, it, reverse, addr);
} else if (mode == '*') {
if (it->name) {
const char *prefix = reverse? "sym.unity.reverse": "sym.unity";
@@ -1208,10 +1366,25 @@ static int cmd_interop(RCore *core, bool reverse, char mode) {
r_cons_printf (core->cons, "# summary: %s=%zu\n", reverse? "reverse_pinvokes": "pinvokes", n);
}
r2unity_free_interop (items, n);
r2unity_native_result_fini (&native_result);
close_metadata (meta, buf);
return 0;
}
static bool cmd_analyze(RCore *core, unsigned level) {
if (r_core_cmd0 (core, ".r2unity-c*") != 0) {
return false;
}
if (level >= 2 && (cmd_symbols (core, 0) || cmd_strings (core, '+')
|| cmd_interop (core, false, '+') || cmd_interop (core, true, '+'))) {
return false;
}
if (level >= 3 && r_core_cmd0 (core, "aar") != 0) {
return false;
}
return level < 4 || r_core_cmd0 (core, "aaa") == 0;
}
/* ---------- r2unity-S (SBOM) ---------- */
static int cmd_sbom(RCore *core, char mode) {
const char *metadata_path = resolve_metadata_path (core);
@@ -1251,16 +1424,21 @@ static bool r2unity_call(RCorePluginSession *cps, const char *input) {
}
rest++;
/* "r2unity-" followed by the subcommand letter, optionally a modifier
*('j' for JSON, '*' for r2 commands), optionally whitespace + args. */
*('j' for JSON, '*' for r2 commands, '+' to import), or repeated 'A's. */
char sub = *rest;
char mode = 0;
unsigned level = 1;
if (sub) {
rest++;
if (*rest == 'j' || *rest == '*') {
while (sub == 'A' && *rest == 'A') {
level++;
rest++;
}
if (sub != 'A' && (*rest == 'j' || *rest == '*' || *rest == '+')) {
mode = *rest;
rest++;
}
if (*rest && *rest != ' ' && *rest != '\t') {
if (level > 4 || (*rest && *rest != ' ' && *rest != '\t')) {
r_cons_cmd_help (core->cons, g_help_msg);
return true;
}
@@ -1270,10 +1448,14 @@ static bool r2unity_call(RCorePluginSession *cps, const char *input) {
}
switch (sub) {
case 'A':
return cmd_analyze (core, level);
case 'c':
return cmd_classes (core, mode) == 0;
case 'D':
return cmd_detect (core, mode == 'j') == 0;
case 'L':
return cmd_load_library (core) == 0;
case 'i':
return cmd_info (core, mode == 'j') == 0;
case 's':
@@ -1340,6 +1522,7 @@ RCorePlugin r_core_plugin_r2unity = {
R_API RLibStruct radare_plugin = {
.type = R_LIB_TYPE_CORE,
.data = &r_core_plugin_r2unity,
.version = R2_VERSION
.version = R2_VERSION,
.pkgname = "r2unity"
};
#endif