From b7f425ca9726acbba12f765879115f08191b3080 Mon Sep 17 00:00:00 2001 From: pancake Date: Thu, 1 Oct 2026 15:28:07 +0200 Subject: [PATCH] Apply dart pool refs to actual data strings with -AAA --- doc/learn.md | 25 ++++++++++++++ include/r2flutter/dart_pool_parse.h | 1 + src/lib/dart_pool_xrefs.c | 39 ++++++++++++++++++++++ src/r2/flutter_analysis.c | 20 +++++++++-- test/db/cmd/pp-string-dataref-xref-android | 13 ++++++++ 5 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 test/db/cmd/pp-string-dataref-xref-android diff --git a/doc/learn.md b/doc/learn.md index 3aed223..a4e36d7 100644 --- a/doc/learn.md +++ b/doc/learn.md @@ -1724,3 +1724,28 @@ This is exactly the primitive the FatalSec talk's Frida bypass keys on Verified count on AuthPass: ~6k true, ~7k false, ~31k null, ~23k null-compares. x26 (THR, the thread register with a large fixed-offset table of runtime stubs) is the natural next target but is version-specific and left as a follow-up. + +## Apply data-image string references so axt resolves pool strings + +A string loaded only through a const data structure (e.g. a `Uri` or a list of +endpoints) is never loaded by a `ldr [x27, off]` in code, so the -AAA scan, +which only walks disassembled functions, created no xref for it. `axt +0x` came back empty even though the string is clearly used. Example +(an Android app): `https://pss.aakredit.in/` at 0x76a97 is referenced from a +const object at 0x13a945 in `.rodata`; the scan never visits `.rodata`. + +The reference was already recoverable - `r2flutter -z`/`-xz -r` lists each +reliable ObjectPool string with its reference sites (`DartStringRef.source_addr`, +including data-image addresses) and emits `ax ` - but only as a +script that nothing applied, and -AAA never ran it. -AAA now calls +`dart_pool_apply_xrefs`, which walks `dart_pool_extract_pool_strings` and sets a +real STRN xref from every `source_addr` to the string, so `axt` resolves +pool-referenced strings that no instruction loads directly (4195 such xrefs on +that app). + +Gotcha: `-xz` resolves to the strings action (`z`), not xrefs (`x`) - the `ax` +lines come from `dart_pool_strings.c` (string references), not +`dart_pool_extract_xrefs` (which only yields metadata refs with no source +address). The apply runs on a shallow DartCtx copy with its own read cache for +the same reason as `r2flutter_setup_pp_gp`: the scan has already mutated +app->dctx, and extraction needs the pristine state the standalone command sees. diff --git a/include/r2flutter/dart_pool_parse.h b/include/r2flutter/dart_pool_parse.h index d316cc7..7dfb518 100644 --- a/include/r2flutter/dart_pool_parse.h +++ b/include/r2flutter/dart_pool_parse.h @@ -203,6 +203,7 @@ char *dart_pool_dump_strings(DartCtx *ctx, int fmt); char *dart_pool_dump_strings_fuzzy(DartCtx *ctx, int fmt); void dart_string_list_free(RList *list); char *dart_pool_dump_xrefs(DartCtx *ctx, int fmt); +int dart_pool_apply_xrefs(DartCtx *ctx); char *dart_pool_dump_sbom(DartCtx *ctx, const char *input_path, int fmt); // ============================================================================ diff --git a/src/lib/dart_pool_xrefs.c b/src/lib/dart_pool_xrefs.c index dd4cc4f..5b1c71a 100644 --- a/src/lib/dart_pool_xrefs.c +++ b/src/lib/dart_pool_xrefs.c @@ -1015,3 +1015,42 @@ char *dart_pool_dump_xrefs(DartCtx *ctx, int fmt) { } return out; } + +// Apply the recovered xref graph to the RCore as real r2 xrefs. Unlike the +// analysis disassembly scan, which only sees loads inside functions, this also +// surfaces references that live in the data image (e.g. a const object in +// .rodata pointing at a string), so `axt` resolves pool-referenced strings that +// no instruction loads directly. Returns the number of xrefs applied. +int dart_pool_apply_xrefs(DartCtx *ctx) { + if (!ctx || !ctx->core || !ctx->core->anal) { + return 0; + } + // The reliable ObjectPool strings carry their reference sites (source_addr), + // including references that live in the data image (e.g. a const object in + // .rodata pointing at the string). The analysis disassembly scan only sees + // loads inside functions, so without this `axt ` is empty for + // strings no instruction loads directly. + RList *strings = dart_pool_extract_pool_strings (ctx); + if (!strings) { + return 0; + } + int applied = 0; + RListIter *it; + DartStringInfo *si; + r_list_foreach (strings, it, si) { + if (!si || !si->address || !si->references) { + continue; + } + RListIter *rit; + DartStringRef *sr; + r_list_foreach (si->references, rit, sr) { + if (!sr || sr->source_addr == 0) { + continue; + } + r_anal_xrefs_set (ctx->core->anal, sr->source_addr, si->address, R_ANAL_REF_TYPE_STRN | R_ANAL_REF_TYPE_READ); + applied++; + } + } + dart_string_list_free (strings); + return applied; +} diff --git a/src/r2/flutter_analysis.c b/src/r2/flutter_analysis.c index 3c696c4..2ff725c 100644 --- a/src/r2/flutter_analysis.c +++ b/src/r2/flutter_analysis.c @@ -1425,15 +1425,31 @@ bool r2flutter_analysis_run(RCore *core, DartCtx *dctx, bool quiet) { flutter_ensure_functions (core, entries); flutter_scan_functions (core, &model, &pp_strings, &pp_refs, entries, &stats); + // Apply the recovered xref graph (including data-image references such as a + // const object pointing at a string) so `axt` resolves pool-referenced + // strings that no instruction loads directly. Run it on a clean copy of the + // original context - the scan above mutates app->dctx (read cache, layout), + // and dart_pool_extract_xrefs needs the same pristine state the standalone + // `-x` command sees. The copy shares heap-owned pointers, so we free only + // the read cache it allocates. + DartCtx xrctx = *dctx; + xrctx.core = core; + xrctx.rmem = NULL; + xrctx.layout = NULL; + xrctx.dump_string_refs = true; + int graph_xrefs = dart_pool_apply_xrefs (&xrctx); + dart_read_cache_free (xrctx.rmem); + if (!quiet) { - R_LOG_INFO ("Flutter analysis: %d functions, %d calls, %d fields, %d classes, %d types, %d strings, %d PP refs", + R_LOG_INFO ("Flutter analysis: %d functions, %d calls, %d fields, %d classes, %d types, %d strings, %d PP refs, %d graph xrefs", (int)stats.functions, (int)stats.call_xrefs, (int)stats.field_refs, (int)stats.class_refs, (int)stats.type_refs, (int)stats.string_refs, - (int)stats.pp_refs); + (int)stats.pp_refs, + graph_xrefs); } RVecFlutterEntry_free (entries); diff --git a/test/db/cmd/pp-string-dataref-xref-android b/test/db/cmd/pp-string-dataref-xref-android new file mode 100644 index 0000000..468fc1b --- /dev/null +++ b/test/db/cmd/pp-string-dataref-xref-android @@ -0,0 +1,13 @@ +NAME=r2-pp-string-dataref-xref-android +FILE=bins/android/w0rdle/libapp.so +ARGS=-NN -e scr.color=0 -e bin.relocs.apply=true +CMDS=<