diff --git a/doc/learn.md b/doc/learn.md index ea9a9a1..0103564 100644 --- a/doc/learn.md +++ b/doc/learn.md @@ -1883,3 +1883,35 @@ placeholder (`func.*`, `method.fn_*`) names. came out as 128 + words, e.g. `_Closure._context` at `+0x428` instead of `+0x28` on 64-bit, and the `SemanticsAction` const map key `tap` decoded as 129 instead of 1. `cs_read_tagged32` was already correct. + +## ObjectPool instances can carry strings through their fields + +`t/libapp_1.so` loads `PP+0x17200` at `0xfb4ae8`. The slot references +snapshot object 165628, a `Ubb` instance derived from `_Enum`; its string field +references object 33665 (`privilegedAccess`, payload `0xbcedf`). Blutter's +`pp.txt` confirms enum index 9 and the name at instance offset `0x10`. +A direct-String-only PP lookup consequently misses this use. + +Analysis now collects direct strings and immediate string fields of pool +instances. `-AA` links the synthetic pool slot (`0x30a1b0` in this sample) to +the string, and `-AAA` links the loading instruction and adds a `contains +string` comment while retaining the `instance` label. Each slot can contain +multiple strings; they must not collapse to its first string field. This +analysis path stops at immediate fields rather than traversing metadata or +arbitrary object graphs. + +Instance field counts include an eight-byte header expressed in compressed +words: two words on compressed Android snapshots, one on uncompressed iOS. +Using a fixed two-word header skips the enum name on iOS. The collector also +loads ROData strings for uncompressed snapshots and preserves VM base-object +strings. Regression tests use `Brightness.dark` in Android w0rdle and iOS +Runner to cover both layouts, pool xrefs, and instruction comments/xrefs. + +PP resolution can initialize `ctx->layout` from a local stack buffer. Callers +that need to continue parsing after it returns must keep their own live +layout across resolution and free it afterward; otherwise the field-string +collector can silently miss all slots on either platform. + +Instance fill offsets are indexed once per cluster while collecting strings. +Rescanning a cluster prefix for each PP slot makes large const collections +quadratic, even when only immediate fields are read. diff --git a/src/lib/dart_pool_modern.c b/src/lib/dart_pool_modern.c index c5267bb..9eca4f0 100644 --- a/src/lib/dart_pool_modern.c +++ b/src/lib/dart_pool_modern.c @@ -3501,6 +3501,8 @@ typedef struct { ut64 pool_ref; ut64 pool_index; ut64 entry_index; + bool instance_fields_only; + ut64 *instance_offsets; } ModernPoolStringRefCollector; static void modern_emit_pool_string_ref(ModernPoolStringRefCollector *collector, ut64 target_ref, const char *target_kind, ut64 field_index, ut64 string_ref) { @@ -3654,36 +3656,49 @@ static bool modern_collect_instance_strings(ModernPoolStringRefCollector *collec if (!cs_read_unsigned (&s, &bitmap)) { return false; } - int header_words = 2; - int num_fields = meta->next_field_offset_words - header_words; - if (num_fields < 0) { - num_fields = 0; - } - ut64 target_index = target_ref - meta->start_ref; - for (ut64 i = 0; i < meta->count; i++) { - for (int j = 0; j < num_fields; j++) { - int field_word_idx = header_words + j; - bool is_unboxed = ((bitmap >> field_word_idx) & 1ULL) != 0; - if (is_unboxed) { + const int word_size = resolver->ctx->compressed_word_size? resolver->ctx->compressed_word_size: 4; + const int header_words = 8 / word_size; + const int num_fields = R_MAX (meta->next_field_offset_words - header_words, 0); + // Index the cluster once. Rescanning its prefix for every pool slot is + // quadratic for large collections of const instances such as icons. + if (!collector->instance_offsets[meta->start_ref]) { + for (ut64 i = 0; i < meta->count; i++) { + collector->instance_offsets[meta->start_ref + i] = s.cursor; + for (int j = 0; j < num_fields; j++) { + const int field_word = header_words + j; + const bool unboxed = field_word < 64 && ((bitmap >> field_word) & 1ULL); ut32 tmp32 = 0; - if (!cs_read_tagged32 (&s, &tmp32) || !cs_read_tagged32 (&s, &tmp32)) { - return false; - } - } else { ut64 ref = 0; - if (!cs_read_ref_id (&s, &ref)) { + if (unboxed) { + if (!cs_read_tagged32 (&s, &tmp32) || !cs_read_tagged32 (&s, &tmp32)) { + collector->instance_offsets[meta->start_ref] = 0; + return false; + } + } else if (!cs_read_ref_id (&s, &ref)) { + collector->instance_offsets[meta->start_ref] = 0; return false; } - if (i == target_index) { - (void)modern_collect_ref_strings (collector, ref, "instance", (ut64)j, depth + 1); - } } } - if (i == target_index) { - return true; + } + s.cursor = collector->instance_offsets[target_ref]; + for (int j = 0; j < num_fields; j++) { + const int field_word = header_words + j; + const bool unboxed = field_word < 64 && ((bitmap >> field_word) & 1ULL); + if (unboxed) { + ut32 tmp32 = 0; + if (!cs_read_tagged32 (&s, &tmp32) || !cs_read_tagged32 (&s, &tmp32)) { + return false; + } + } else { + ut64 ref = 0; + if (!cs_read_ref_id (&s, &ref)) { + return false; + } + (void)modern_collect_ref_strings (collector, ref, "instance", (ut64)j, depth + 1); } } - return false; + return true; } static bool modern_collect_ref_strings(ModernPoolStringRefCollector *collector, ut64 target_ref, const char *target_kind, ut64 field_index, int depth) { @@ -3698,6 +3713,11 @@ static bool modern_collect_ref_strings(ModernPoolStringRefCollector *collector, if (collector->seen) { ht_up_insert (collector->seen, seen_key, (void *)1); } + // Isolate base objects can include VM strings without a local cluster. + if (R_STR_ISNOTEMPTY (resolver->strings_by_ref[target_ref])) { + modern_emit_pool_string_ref (collector, target_ref, target_kind, field_index, target_ref); + return true; + } const ModernClusterMeta *meta = modern_resolver_cluster_for_ref (resolver, target_ref); if (!meta) { return false; @@ -3709,6 +3729,9 @@ static bool modern_collect_ref_strings(ModernPoolStringRefCollector *collector, if (!meta->fill_parsed || !meta->fill_ok || meta->fill_offset >= meta->fill_end || !meta->count) { return false; } + if (collector->instance_fields_only && (depth > 0 || meta->fill_kind != MODERN_FILL_INSTANCE)) { + return false; + } if (modern_is_array_cid (collector->cids, meta->cid)) { return modern_collect_array_strings (collector, meta, target_ref, false, depth); } @@ -3729,7 +3752,7 @@ static bool modern_collect_ref_strings(ModernPoolStringRefCollector *collector, return false; } -bool modern_collect_pool_strrefs(const ModernReq *req, HtUP *pp_offsets, ModernPoolStrRefCb cb, void *user) { +static bool modern_collect_pool_strrefs_internal(const ModernReq *req, HtUP *pp_offsets, ModernPoolStrRefCb cb, void *user, bool instance_fields_only) { DartCtx *ctx = req? req->ctx: NULL; if (!ctx || !cb || !modern_supported (ctx)) { return false; @@ -3744,6 +3767,21 @@ bool modern_collect_pool_strrefs(const ModernReq *req, HtUP *pp_offsets, ModernP modern_cluster_meta_free (meta, req->num_clusters); return false; } + ut64 *instance_offsets = (ut64 *)calloc ((size_t)resolver.refs_count, sizeof (ut64)); + if (!instance_offsets) { + modern_ref_resolver_fini (&resolver); + modern_cluster_meta_free (meta, req->num_clusters); + return false; + } + const ut64 snapshot_bases[] = { ctx->iso_data, ctx->vm_data }; + for (size_t i = 0; i < R_ARRAY_SIZE (snapshot_bases); i++) { + DartSnapshotHeader sh = { 0 }; + const ut64 base = snapshot_bases[i]; + if (base && dart_snapshot_header_read (ctx, base, &sh) && sh.ok && sh.cluster_start == req->cluster_start) { + modern_load_rodata_strings (ctx, meta, req->num_clusters, base, sh.total_len, resolver.strings_by_ref, resolver.string_addr_by_ref, resolver.refs_count); + break; + } + } const ModernCidCache cids = modern_cid_cache_init (ctx->layout); for (ut64 i = 0; i < req->num_clusters; i++) { ModernClusterMeta *m = &meta[i]; @@ -3785,6 +3823,8 @@ bool modern_collect_pool_strrefs(const ModernReq *req, HtUP *pp_offsets, ModernP .pool_ref = m->start_ref + pool_index, .pool_index = pool_index, .entry_index = entry_index, + .instance_fields_only = instance_fields_only, + .instance_offsets = instance_offsets, }; (void)modern_collect_ref_strings (&collector, entry.ref, "object_pool.entry", entry_index, 0); ht_up_free (seen); @@ -3797,11 +3837,22 @@ bool modern_collect_pool_strrefs(const ModernReq *req, HtUP *pp_offsets, ModernP break; } } + free (instance_offsets); modern_ref_resolver_fini (&resolver); modern_cluster_meta_free (meta, req->num_clusters); return ok; } +bool modern_collect_pool_strrefs(const ModernReq *req, HtUP *pp_offsets, ModernPoolStrRefCb cb, void *user) { + return modern_collect_pool_strrefs_internal (req, pp_offsets, cb, user, false); +} + +// Include immediate string fields of instances (notably _Enum._name) without +// walking unrelated metadata graphs or treating the instance as a String. +bool modern_collect_pool_value_strrefs(const ModernReq *req, HtUP *pp_offsets, ModernPoolStrRefCb cb, void *user) { + return modern_collect_pool_strrefs_internal (req, pp_offsets, cb, user, true); +} + static ut64 modern_find_sb(const ModernReq *req) { DartCtx *ctx = req? req->ctx: NULL; if (!ctx) { diff --git a/src/lib/dart_pool_parse_priv.h b/src/lib/dart_pool_parse_priv.h index 0c74caf..d629438 100644 --- a/src/lib/dart_pool_parse_priv.h +++ b/src/lib/dart_pool_parse_priv.h @@ -291,6 +291,7 @@ const char *modern_value_graph_root_kind(ModernValueGraph *graph); bool modern_collect_direct_pool_strrefs(const ModernReq *req, HtUP *pp_offs, ModernPoolStrRefCb cb, void *user); bool modern_collect_direct_pool_refs(const ModernReq *req, ModernPoolRefCb cb, void *user); bool modern_collect_pool_strrefs(const ModernReq *req, HtUP *pp_offs, ModernPoolStrRefCb cb, void *user); +bool modern_collect_pool_value_strrefs(const ModernReq *req, HtUP *pp_offs, ModernPoolStrRefCb cb, void *user); bool modern_extract_pool_strings(const ModernReq *req, RList *strings, HtUP *seen, ut64 *next_ref); bool modern_scan_names(DartCtx *ctx, ut64 start, ut64 end, ut64 nclusters, ut64 itlen); bool modern_extract_classes(const ModernReq *req, RList *classes); diff --git a/src/lib/dart_pool_xrefs.c b/src/lib/dart_pool_xrefs.c index 5b1c71a..84d09cf 100644 --- a/src/lib/dart_pool_xrefs.c +++ b/src/lib/dart_pool_xrefs.c @@ -1016,6 +1016,47 @@ char *dart_pool_dump_xrefs(DartCtx *ctx, int fmt) { return out; } +typedef struct { + RCore *core; + ut64 pp_base; + int applied; +} DartPoolValueXrefCtx; + +static void xref_pool_value_string_cb(const ModernPoolStrRef *info, void *user) { + DartPoolValueXrefCtx *ux = user; + if (!info->string_addr || !strcmp (info->target_kind, "object_pool.entry")) { + return; + } + r_anal_xrefs_set (ux->core->anal, ux->pp_base + info->pp_offset, info->string_addr, R_ANAL_REF_TYPE_STRN | R_ANAL_REF_TYPE_READ); + ux->applied++; +} + +static int apply_pool_value_xrefs(DartCtx *ctx) { + if (find_snapshots (ctx) != 0) { + return 0; + } + // Keep a live layout across PP resolution: its fallback layout can otherwise + // point at dart_resolve_pp_info's temporary stack buffer on return. + DartVerLayout layout_tmp; + bool layout_ours = !ctx->layout; + DartVerLayout *layout_owned = layout_ours? dart_ctx_init_layout (ctx, &layout_tmp): NULL; + DartPpInfo pp = { 0 }; + DartPoolValueXrefCtx ux = { .core = ctx->core }; + if (dart_resolve_pp_info (ctx, &pp)) { + ux.pp_base = pp.base; + DartSnapshotHeader sh = { 0 }; + if (dart_snapshot_header_read (ctx, pp.snapshot_base, &sh) && sh.ok) { + const ModernReq req = { ctx, sh.cluster_start, pp.snapshot_base + sh.total_len, sh.nc, sh.nb }; + modern_collect_pool_value_strrefs (&req, NULL, xref_pool_value_string_cb, &ux); + } + } + dart_pp_info_fini (&pp); + if (layout_ours) { + dart_ctx_fini_layout (ctx, layout_owned); + } + return ux.applied; +} + // 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 @@ -1052,5 +1093,5 @@ int dart_pool_apply_xrefs(DartCtx *ctx) { } } dart_string_list_free (strings); - return applied; + return applied + apply_pool_value_xrefs (ctx); } diff --git a/src/r2/flutter_analysis.c b/src/r2/flutter_analysis.c index a87fd99..cb1d0d5 100644 --- a/src/r2/flutter_analysis.c +++ b/src/r2/flutter_analysis.c @@ -60,6 +60,7 @@ typedef struct { typedef struct { ut64 string_addr; char *string_value; + bool indirect; } FlutterPpStringRef; typedef struct { @@ -93,15 +94,19 @@ static void flutter_stack_slot_free(void *p) { free (p); } -static void flutter_pp_string_ref_free(HtUPKv *kv) { - FlutterPpStringRef *ref = kv->value; +static void flutter_pp_string_ref_free(void *p) { + FlutterPpStringRef *ref = p; free (ref->string_value); free (ref); } +static void flutter_pp_string_bucket_free(HtUPKv *kv) { + r_list_free (kv->value); +} + static void flutter_pp_string_map_init(FlutterPpStringMap *map) { memset (map, 0, sizeof (*map)); - map->by_pp_off = ht_up_new (NULL, flutter_pp_string_ref_free, NULL); + map->by_pp_off = ht_up_new (NULL, flutter_pp_string_bucket_free, NULL); } static void flutter_pp_string_map_fini(FlutterPpStringMap *map) { @@ -567,13 +572,23 @@ static void flutter_pp_string_map_add(FlutterPpStringMap *map, const ModernPoolS if (!map || !map->by_pp_off || !info || R_STR_ISEMPTY (info->string_value) || !info->string_addr) { return; } - if (ht_up_find (map->by_pp_off, info->pp_offset, NULL)) { - return; + RList *refs = ht_up_find (map->by_pp_off, info->pp_offset, NULL); + if (!refs) { + refs = r_list_newf (flutter_pp_string_ref_free); + ht_up_insert (map->by_pp_off, info->pp_offset, refs); + } + RListIter *it; + FlutterPpStringRef *existing; + r_list_foreach (refs, it, existing) { + if (existing->string_addr == info->string_addr) { + return; + } } FlutterPpStringRef *ref = R_NEW0 (FlutterPpStringRef); ref->string_addr = info->string_addr; ref->string_value = strdup (info->string_value); - ht_up_insert (map->by_pp_off, info->pp_offset, ref); + ref->indirect = strcmp (info->target_kind, "object_pool.entry") != 0; + r_list_append (refs, ref); } static void flutter_pp_string_ref_cb(const ModernPoolStrRef *info, void *user) { @@ -595,7 +610,7 @@ static bool flutter_collect_pp_strings_from_snapshot(DartCtx *ctx, ut64 snapshot .num_clusters = sh.nc, .num_base_objects = sh.nb, }; - return modern_collect_direct_pool_strrefs (&req, wanted, flutter_pp_string_ref_cb, map); + return modern_collect_pool_value_strrefs (&req, wanted, flutter_pp_string_ref_cb, map); } static void flutter_pp_string_map_load(DartCtx *ctx, HtUP *wanted, FlutterPpStringMap *map) { @@ -616,7 +631,7 @@ static void flutter_pp_string_map_load(DartCtx *ctx, HtUP *wanted, FlutterPpStri } } -static const FlutterPpStringRef *flutter_pp_string_map_get(FlutterPpStringMap *map, ut64 pp_off) { +static RList *flutter_pp_string_map_get(FlutterPpStringMap *map, ut64 pp_off) { return map && map->by_pp_off? ht_up_find (map->by_pp_off, pp_off, NULL): NULL; } @@ -1215,22 +1230,26 @@ static bool flutter_process_pp_load(RCore *core, FlutterAnalState *state, Flutte if (pp_refs && pp_refs->pp_base) { flutter_add_ref (core, at, pp_refs->pp_base + slot_off, R_ANAL_REF_TYPE_DATA | R_ANAL_REF_TYPE_READ); } - const FlutterPpStringRef *string_ref = flutter_pp_string_map_get (pp_strings, slot_off); - if (string_ref) { + RList *strings = flutter_pp_string_map_get (pp_strings, slot_off); + FlutterPpStringRef *first = strings? r_list_first (strings): NULL; + if (!first || first->indirect) { + if (!flutter_annotate_pp_ref (core, pp_refs, at, slot_off, false, stats)) { + char *msg = r_str_newf ("dart: PP slot +0x%" PFMT64x, slot_off); + flutter_append_comment (core, at, msg, stats); + free (msg); + } + } + RListIter *it; + FlutterPpStringRef *string_ref; + r_list_foreach (strings, it, string_ref) { flutter_set_flag (core, "dart.str", string_ref->string_value, string_ref->string_addr, (ut32)strlen (string_ref->string_value), true); flutter_add_ref (core, at, string_ref->string_addr, R_ANAL_REF_TYPE_STRN | R_ANAL_REF_TYPE_READ); stats->string_refs++; char *escaped = r_str_escape_utf8 (string_ref->string_value, false, true); - char *msg = r_str_newf ("dart: PP slot +0x%" PFMT64x " string \"%s\"", pp_off, escaped); + char *msg = r_str_newf ("dart: PP slot +0x%" PFMT64x " %sstring \"%s\"", pp_off, string_ref->indirect? "contains ": "", escaped); flutter_append_comment (core, at, msg, stats); free (msg); free (escaped); - } else if (!flutter_annotate_pp_ref (core, pp_refs, at, slot_off, false, stats)) { - char *msg = r_str_newf ("dart: " - "PP slot +0x%" PFMT64x, - slot_off); - flutter_append_comment (core, at, msg, stats); - free (msg); } stats->pp_refs++; return true; diff --git a/test/db/cmd/pp-discarded-code-android b/test/db/cmd/pp-discarded-code-android new file mode 100644 index 0000000..b432aae --- /dev/null +++ b/test/db/cmd/pp-discarded-code-android @@ -0,0 +1,21 @@ +NAME=r2-pp-analysis-discarded-code-android +FILE=bins/android/first/libapp.so +ARGS=-NN -e scr.color=0 -e bin.relocs.apply=true -e io.cache=true +CMDS=<