Give duplicate Dart method names unique r2 flags and function names

r2 flag names are unique, so flagging a second function with a repeated
Dart name (e.g. Native._anon_closure) moved the flag away from the first
one, and current r2's af then named it after the Mach-O sym.func.* flag.
Suffix repeats with their address, use the same names for afs, and rename
analyzed functions after the af pass.

Also skip var recovery, call-following and jump tables in the signature
af pass (AuthPass -AAA 47s -> 29s) and raise R2R_TIMEOUT to 60s.
This commit is contained in:
pancake committed 2026-11-06 13:54:07 +01:00
1 parent cc338995cf
commit 0ee5e01498
5 files changed
+98 -9

No files matched your search

+1 -1
View File
@@ -9,7 +9,7 @@ LDFLAGS += $(R2_LDFLAGS)
LDLIBS += $(R2_LIBS)
DEPFLAGS = -MMD -MP
R2R_JOBS ?= 1
R2R_TIMEOUT ?= 30
R2R_TIMEOUT ?= 60
R2_LIBR_PLUGINS=$(shell r2 -H R2_LIBR_PLUGINS 2> /dev/null)
R2_LIBEXT=$(shell r2 -H R2_LIBEXT 2> /dev/null)
-include config.mk
+15
View File
@@ -2052,3 +2052,18 @@ Regressions also mark the Android table entries as lacking Code metadata and
place matching PP loads in the last function and outside the image to check
coverage and bounds. This expands instruction coverage; operand decoding
continues to use the existing standalone scanner.
## Duplicate Dart method names need unique r2 flags and function names
Several functions can share a Dart name (e.g. many `Native._anon_closure`).
r2 flag and function names are unique, so `r_flag_set` on a repeated name
moves the flag to the later function. Older r2 left a stale per-address entry
behind, which let `af` still pick the moved `method.*` name; current r2 does
not, and `af` also prefers symbol-space flags such as Mach-O `sym.func.*`.
The first function keeps `method.<name>` and repeats get a `.0x<addr>` suffix,
computed once in app order and shared by the flags, the `afs` prototypes and
`dart_dumper_name_functions`, which renames analyzed functions after the `af`
pass. The signature `af` storm also disables `anal.vars`, `anal.calls` and
`anal.jmp.tbl` like `flutter_ensure_functions`; var recovery was about half
of its cost (AuthPass `-AAA` 47s -> 29s).
+1
View File
@@ -11,6 +11,7 @@ char *dart_dumper_dump_r2(DartApp *app);
char *dart_dumper_dump_funcs(DartApp *app, int fmt);
void dart_dumper_apply_to_core(DartApp *app, bool apply_signatures);
void dart_dumper_apply_signatures(DartApp *app);
void dart_dumper_name_functions(DartApp *app);
// Disable anal.trycatch while r2flutter runs many `af` commands, when no try.*
// flag exists for it to find. Returns the previous value to pass to the restore.
bool dart_core_trycatch_suspend(RCore *core);
+80 -8
View File
@@ -124,8 +124,61 @@ static char *make_method_flagname(const char *name) {
return flagname;
}
static void dump_func_r2(RStrBuf *sb, const DartFunction *fn, bool quiet) {
char *flagname = make_method_flagname (fn->name);
// Flag and function names are unique in r2: setting an existing flag name
// moves it, so a later function sharing a Dart name (e.g. several
// Native._anon_closure) would steal it from the first one. Suffix repeated
// names with the entry address.
static char *make_unique_method_flagname(HtPP *seen, const char *name, ut64 addr) {
char *flagname = make_method_flagname (name);
bool found = false;
ut64 owner = (ut64) (size_t)ht_pp_find (seen, flagname, &found);
if (found && owner != addr) {
char *unique = r_str_newf ("%s.0x%" PFMT64x, flagname, addr);
free (flagname);
return unique;
}
ht_pp_insert (seen, flagname, (void *) (size_t)addr);
return flagname;
}
static void method_flagname_free(HtUPKv *kv) {
free (kv->value);
}
// Map each function address to its unique method.* name, in app order.
static HtUP *method_flagnames(DartApp *app) {
HtUP *names = ht_up_new (NULL, method_flagname_free, NULL);
HtPP *seen = ht_pp_new0 ();
DartFunction *fn;
R_VEC_FOREACH (app->functions, fn) {
if (fn->name && !ht_up_find (names, fn->addr, NULL)) {
ht_up_insert (names, fn->addr, make_unique_method_flagname (seen, fn->name, fn->addr));
}
}
ht_pp_free (seen);
return names;
}
// Give each analyzed function its Dart name: r2's `af` prefers flags in the
// symbols space (e.g. Mach-O sym.func.*) over the method.* flags set here.
void dart_dumper_name_functions(DartApp *app) {
if (!app || !app->core || !app->functions) {
return;
}
HtUP *names = method_flagnames (app);
DartFunction *fn;
R_VEC_FOREACH (app->functions, fn) {
RAnalFunction *fcn = r_anal_get_function_at (app->core->anal, fn->addr);
const char *name = fcn? ht_up_find (names, fn->addr, NULL): NULL;
if (name && strcmp (fcn->name, name)) {
r_anal_function_rename (fcn, name);
}
}
ht_up_free (names);
}
static void dump_func_r2(RStrBuf *sb, HtPP *seen, const DartFunction *fn, bool quiet) {
char *flagname = make_unique_method_flagname (seen, fn->name, fn->addr);
r_strbuf_appendf (sb, "f %s = 0x%" PFMT64x "\n", flagname, (uint64_t)fn->addr);
if (!quiet) {
r_strbuf_appendf (sb, "'@0x%" PFMT64x "'CC %s\n", (uint64_t)fn->addr, fn->name);
@@ -145,13 +198,15 @@ char *dart_dumper_dump_r2(DartApp *app) {
r_strbuf_appendf (sb, "f app.heap_base = 0x%" PFMT64x "\n", (uint64_t)app->heap_base);
if (app->functions) {
HtPP *seen = ht_pp_new0 ();
DartFunction *fn;
R_VEC_FOREACH (app->functions, fn) {
if (!fn->name) {
continue;
}
dump_func_r2 (sb, fn, quiet);
dump_func_r2 (sb, seen, fn, quiet);
}
ht_pp_free (seen);
}
r_strbuf_append (sb, "dr x27=`e anal.gp`\n");
@@ -219,6 +274,7 @@ char *dart_dumper_dump_funcs(DartApp *app, int fmt) {
}
RStrBuf *sb = r_strbuf_new ("");
HtPP *seen = ht_pp_new0 ();
R_VEC_FOREACH (app->functions, fn) {
if (!fn->name) {
continue;
@@ -227,12 +283,13 @@ char *dart_dumper_dump_funcs(DartApp *app, int fmt) {
break;
}
if (fmt == 'r') {
dump_func_r2 (sb, fn, app->dctx.quiet);
dump_func_r2 (sb, seen, fn, app->dctx.quiet);
} else {
dump_func_text (sb, fn);
}
count++;
}
ht_pp_free (seen);
return drain_trimmed_strbuf (sb);
}
@@ -419,17 +476,27 @@ void dart_dumper_apply_signatures(DartApp *app) {
}
}
qsort (order, n, sizeof (DartFunction *), dart_fn_addr_desc);
HtUP *names = method_flagnames (app);
// Keep the analysis local to each function start; the surrounding pass owns
// broader discovery. String ESIL emulation is very slow and irrelevant to
// prototypes, so disable it for the duration of this pass.
// Variable recovery, call-following and jump tables are not needed for the
// prototypes either, and var recovery alone is about half of this `af` cost.
int prev_hasnext = r_config_get_i (core->config, "anal.hasnext");
int prev_emustr = r_config_get_i (core->config, "emu.str");
bool prev_calls = r_config_get_b (core->config, "anal.calls");
bool prev_vars = r_config_get_b (core->config, "anal.vars");
bool prev_jmptbl = r_config_get_b (core->config, "anal.jmp.tbl");
r_config_set_i (core->config, "anal.hasnext", 0);
r_config_set_i (core->config, "emu.str", 0);
r_config_set_b (core->config, "anal.calls", false);
r_config_set_b (core->config, "anal.vars", false);
r_config_set_b (core->config, "anal.jmp.tbl", false);
const bool prev_trycatch = dart_core_trycatch_suspend (core);
for (size_t i = 0; i < n; i++) {
fn = order[i];
char *name = strdup (fn->name? fn->name: "fcn");
const char *flagname = ht_up_find (names, fn->addr, NULL);
char *name = strdup (flagname? flagname: "fcn");
r_name_filter (name, 0);
if (R_STR_ISEMPTY (name)) {
free (name);
@@ -452,7 +519,11 @@ void dart_dumper_apply_signatures(DartApp *app) {
}
r_config_set_i (core->config, "anal.hasnext", prev_hasnext);
r_config_set_i (core->config, "emu.str", prev_emustr);
r_config_set_b (core->config, "anal.calls", prev_calls);
r_config_set_b (core->config, "anal.vars", prev_vars);
r_config_set_b (core->config, "anal.jmp.tbl", prev_jmptbl);
dart_core_trycatch_restore (core, prev_trycatch);
ht_up_free (names);
free (order);
}
@@ -468,16 +539,17 @@ void dart_dumper_apply_to_core(DartApp *app, bool apply_signatures) {
}
if (app->functions) {
HtUP *names = method_flagnames (app);
DartFunction *fn;
R_VEC_FOREACH (app->functions, fn) {
if (!fn->name) {
const char *flagname = ht_up_find (names, fn->addr, NULL);
if (!fn->name || !flagname) {
continue;
}
char *flagname = make_method_flagname (fn->name);
r_flag_set (core->flags, flagname, fn->addr, fn->size);
r_meta_set_string (core->anal, R_META_TYPE_COMMENT, fn->addr, fn->name);
free (flagname);
}
ht_up_free (names);
}
r_core_cmd0 (core, "e emu.str=true");
+1
View File
@@ -1572,6 +1572,7 @@ bool r2flutter_analysis_run(RCore *core, DartCtx *dctx, bool quiet) {
RVecFlutterEntry *entries = flutter_collect_entries (core, app, &model);
flutter_ensure_functions (core, entries);
dart_dumper_name_functions (app);
flutter_scan_functions (core, &model, &pp_strings, &pp_refs, entries, &stats);
int graph_xrefs = r2flutter_apply_pool_xrefs (core, dctx);