Fix ppstring recovery in disasm and add tests

This commit is contained in:
pancake committed 2026-08-12 12:55:44 +02:00
1 parent 08001fe45b
commit 02efb309a2
5 files changed
+240 -14

No files matched your search

+26
View File
@@ -1268,3 +1268,29 @@ singletons are still recoverable from the nearby handshake/schedule-filter
cluster (`x22 + 0x20` is true, `x22 + 0x30` is false), so the same AArch64 entry
patch (`add x0, x22, 0x20; ret`) is valid when the goal is to force acceptance
at the Dart bad-certificate wrapper boundary.
## PP-relative string comments in r2 disassembly
`r2flutter -AAA` must account for shifted ARM64 PP-base materialization such as
`add x2, x27, 0x18, lsl 12`. radare2 may print this as `lsl 12` without a `#`.
If the shift is ignored, the analyzer records `PP slot +0x470` for a following
`ldr x2, [x2, 0x458]` instead of the real `PP slot +0x18458`, so ObjectPool
string resolution cannot attach the expected string comment.
The analysis pass now accepts both `lsl #N` and `lsl N`, builds a direct
ObjectPool string map, and annotates PP-relative string loads. On
`ssl_test2/libapp.so`, the blutter-comparable check is:
```sh
r2 -q -e scr.color=0 -e bin.relocs.apply=true \
-c 'r2flutter -AAA' \
-c 's 0x9da730' \
-c 'pd 16' \
-c q ssl_test2/libapp.so
```
The key instruction should now include:
```text
0x009da744 ... ldr x2, [x2, 0x458] ; ... "premium" ; dart: PP slot +0x18458 string "premium"
```
+71
View File
@@ -2957,6 +2957,77 @@ bool dart_modern_collect_object_pool_string_refs(const DartModernClusterRequest
return ok;
}
bool dart_modern_collect_direct_object_pool_string_refs(const DartModernClusterRequest *req, HtUP *pp_offsets, DartModernPoolStringRefCallback cb, void *user) {
DartCtx *ctx = req? req->ctx: NULL;
if (!ctx || !cb || !dart_modern_is_supported_snapshot (ctx)) {
return false;
}
ModernClusterMeta *meta = modern_parse_cluster_meta (ctx, req->cluster_start, req->cluster_end, req->num_clusters, req->num_base_objects);
if (!meta) {
return false;
}
ModernRefResolver resolver = { 0 };
bool ok = modern_ref_resolver_init (&resolver, ctx, meta, req->num_clusters, req->num_base_objects);
if (!ok) {
modern_cluster_meta_free (meta, req->num_clusters);
return false;
}
for (ut64 i = 0; i < req->num_clusters; i++) {
ModernClusterMeta *m = &meta[i];
if (m->fill_kind != MODERN_FILL_OBJECT_POOL || !m->fill_parsed || !m->fill_ok || m->fill_offset >= m->fill_end || !m->count) {
continue;
}
ClusterStream s = {
.ctx = ctx,
.cursor = m->fill_offset,
.end = m->fill_end,
};
for (ut64 pool_index = 0; pool_index < m->count; pool_index++) {
ut64 length = 0;
if (!cs_read_unsigned (&s, &length)) {
ok = false;
break;
}
for (ut64 entry_index = 0; entry_index < length; entry_index++) {
ModernPoolEntry entry;
if (!modern_read_pool_entry (&s, ctx, entry_index, &entry)) {
ok = false;
break;
}
ut64 pp_offset = modern_pool_entry_pp_offset (ctx, entry_index);
if (pp_offsets && !ht_up_find (pp_offsets, pp_offset, NULL)) {
continue;
}
if (entry.behavior != 0 || entry.type != 1 || !entry.ref || entry.ref >= resolver.refs_count || R_STR_ISEMPTY (resolver.strings_by_ref[entry.ref])) {
continue;
}
DartModernPoolStringRefInfo info = {
.pp_offset = pp_offset,
.pool_ref = m->start_ref + pool_index,
.pool_index = pool_index,
.entry_index = entry_index,
.target_ref = entry.ref,
.target_kind = "object_pool.entry",
.field_index = entry_index,
.string_ref = entry.ref,
.string_addr = resolver.string_addr_by_ref[entry.ref],
.string_value = resolver.strings_by_ref[entry.ref],
};
cb (&info, user);
}
if (!ok) {
break;
}
}
if (!ok) {
break;
}
}
modern_ref_resolver_fini (&resolver);
modern_cluster_meta_free (meta, req->num_clusters);
return ok;
}
static bool modern_object_pool_string_ref_exists(RList *refs, ut64 pool_ref, ut64 entry_index) {
if (!refs) {
return false;
+1
View File
@@ -262,6 +262,7 @@ bool dart_modern_emit_cluster_summary(const DartModernClusterSummaryRequest *req
bool dart_modern_build_synthetic_pp(const DartModernClusterRequest *req, ut64 snapshot_base, const char *snapshot_label, ut64 data_image_base, DartPpInfo *out);
bool dart_modern_resolve_pp_slot(const DartModernClusterRequest *req, ut64 pp_offset, DartModernPoolSlotInfo *out);
void dart_modern_pool_slot_info_fini(DartModernPoolSlotInfo *info);
bool dart_modern_collect_direct_object_pool_string_refs(const DartModernClusterRequest *req, HtUP *pp_offsets, DartModernPoolStringRefCallback cb, void *user);
bool dart_modern_collect_object_pool_string_refs(const DartModernClusterRequest *req, HtUP *pp_offsets, DartModernPoolStringRefCallback cb, void *user);
bool dart_modern_extract_object_pool_strings_from_clusters(const DartModernClusterRequest *req, RList *strings, HtUP *seen_refs, ut64 *ref_counter);
bool dart_modern_scan_names_from_clusters(DartCtx *ctx, ut64 cluster_start, ut64 cluster_end, ut64 num_clusters, ut64 itlen);
+133 -14
View File
@@ -54,6 +54,19 @@ typedef struct {
ut64 comments;
} FlutterAnalStats;
typedef struct {
ut64 pp_offset;
ut64 string_addr;
char *string_value;
char *target_kind;
bool direct;
} FlutterPpStringRef;
typedef struct {
HtUP *by_pp_off;
RList *refs;
} FlutterPpStringMap;
typedef struct {
HtUP *seen;
RVecFlutterEntry *entries;
@@ -63,6 +76,31 @@ static void flutter_stack_slot_free(void *p) {
free (p);
}
static void flutter_pp_string_ref_free(void *p) {
FlutterPpStringRef *ref = p;
if (!ref) {
return;
}
free (ref->string_value);
free (ref->target_kind);
free (ref);
}
static void flutter_pp_string_map_init(FlutterPpStringMap *map) {
memset (map, 0, sizeof (*map));
map->by_pp_off = ht_up_new0 ();
map->refs = r_list_newf (flutter_pp_string_ref_free);
}
static void flutter_pp_string_map_fini(FlutterPpStringMap *map) {
if (!map) {
return;
}
ht_up_free (map->by_pp_off);
r_list_free (map->refs);
memset (map, 0, sizeof (*map));
}
static void flutter_model_fini(FlutterAnalModel *model) {
dart_recovery_model_fini (model);
}
@@ -214,11 +252,17 @@ static bool flutter_parse_shifted_imm(const char *line, ut64 *imm) {
if (!lsl) {
return false;
}
const char *hash = strchr (lsl, '#');
if (!hash) {
const char *p = lsl + 3;
while (isspace ((ut8)*p)) {
p++;
}
if (*p == '#') {
p++;
}
if (!isdigit ((ut8)*p)) {
return false;
}
int shift = atoi (hash + 1);
int shift = atoi (p);
if (shift <= 0 || shift >= 63) {
return false;
}
@@ -351,6 +395,64 @@ static bool flutter_model_load(FlutterAnalModel *model, RCore *core, DartCtx *dc
return true;
}
static void flutter_pp_string_map_add(FlutterPpStringMap *map, const DartModernPoolStringRefInfo *info) {
if (!map || !map->by_pp_off || !map->refs || !info || R_STR_ISEMPTY (info->string_value) || !info->string_addr) {
return;
}
const bool direct = R_STR_ISNOTEMPTY (info->target_kind) && !strcmp (info->target_kind, "object_pool.entry");
FlutterPpStringRef *old = ht_up_find (map->by_pp_off, info->pp_offset, NULL);
if (old && (old->direct || !direct)) {
return;
}
FlutterPpStringRef *ref = R_NEW0 (FlutterPpStringRef);
ref->pp_offset = info->pp_offset;
ref->string_addr = info->string_addr;
ref->string_value = strdup (info->string_value);
ref->target_kind = R_STR_ISNOTEMPTY (info->target_kind)? strdup (info->target_kind): NULL;
ref->direct = direct;
r_list_append (map->refs, ref);
ht_up_insert (map->by_pp_off, info->pp_offset, ref);
}
static void flutter_pp_string_ref_cb(const DartModernPoolStringRefInfo *info, void *user) {
flutter_pp_string_map_add (user, info);
}
static void flutter_collect_pp_strings_from_snapshot(DartCtx *ctx, ut64 snapshot_base, HtUP *wanted, FlutterPpStringMap *map) {
if (!ctx || !snapshot_base || !map) {
return;
}
DartSnapshotHeader sh = { 0 };
if (!dart_snapshot_header_read (ctx, snapshot_base, &sh) || !sh.ok) {
return;
}
const DartModernClusterRequest req = {
.ctx = ctx,
.cluster_start = sh.cluster_start,
.cluster_end = snapshot_base + sh.total_len,
.num_clusters = sh.nc,
.num_base_objects = sh.nb,
};
(void)dart_modern_collect_direct_object_pool_string_refs (&req, wanted, flutter_pp_string_ref_cb, map);
}
static void flutter_pp_string_map_load(DartCtx *ctx, HtUP *wanted, FlutterPpStringMap *map) {
if (!ctx || !map || find_snapshots (ctx) != 0) {
return;
}
DartVerLayout layout_tmp;
DartVerLayout *layout_owned = ctx->layout? NULL: dart_ctx_init_layout (ctx, &layout_tmp);
flutter_collect_pp_strings_from_snapshot (ctx, ctx->iso_data, wanted, map);
flutter_collect_pp_strings_from_snapshot (ctx, ctx->vm_data, wanted, map);
if (layout_owned) {
dart_ctx_fini_layout (ctx, layout_owned);
}
}
static const FlutterPpStringRef *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;
}
static void flutter_apply_model_to_core(FlutterAnalModel *model, RCore *core, FlutterAnalStats *stats) {
if (!model || !core) {
return;
@@ -740,7 +842,7 @@ static bool flutter_process_stack_store(FlutterAnalState *state, const FlutterOp
return true;
}
static bool flutter_process_pp_load(RCore *core, FlutterAnalState *state, ut64 at, const FlutterOpInfo *info, FlutterAnalStats *stats) {
static bool flutter_process_pp_load(RCore *core, FlutterAnalState *state, FlutterPpStringMap *pp_strings, ut64 at, const FlutterOpInfo *info, FlutterAnalStats *stats) {
if (!state || !info || info->n_regs < 1 || !info->has_mem) {
return false;
}
@@ -761,11 +863,23 @@ static bool flutter_process_pp_load(RCore *core, FlutterAnalState *state, ut64 a
memset (&state->regs[dst], 0, sizeof (state->regs[dst]));
state->regs[dst].has_pp_off = true;
state->regs[dst].pp_off = pp_off;
char *msg = r_str_newf ("dart: "
"PP slot +0x%" PFMT64x,
pp_off);
flutter_append_comment (core, at, msg, stats);
free (msg);
const FlutterPpStringRef *string_ref = flutter_pp_string_map_get (pp_strings, pp_off);
if (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);
flutter_append_comment (core, at, msg, stats);
free (msg);
free (escaped);
} else {
char *msg = r_str_newf ("dart: "
"PP slot +0x%" PFMT64x,
pp_off);
flutter_append_comment (core, at, msg, stats);
free (msg);
}
stats->pp_refs++;
return true;
}
@@ -837,7 +951,7 @@ static void flutter_process_indirect_call(RCore *core, FlutterAnalState *state,
}
}
static void flutter_scan_function(RCore *core, FlutterAnalModel *model, RAnalFunction *fcn, FlutterAnalStats *stats) {
static void flutter_scan_function(RCore *core, FlutterAnalModel *model, FlutterPpStringMap *pp_strings, RAnalFunction *fcn, FlutterAnalStats *stats) {
if (!core || !model || !fcn || !stats) {
return;
}
@@ -898,7 +1012,7 @@ static void flutter_scan_function(RCore *core, FlutterAnalModel *model, RAnalFun
(void)flutter_process_add (&state, &info);
break;
case R_ANAL_OP_TYPE_LOAD:
if (!flutter_process_pp_load (core, &state, at, &info, stats) &&
if (!flutter_process_pp_load (core, &state, pp_strings, at, &info, stats) &&
!flutter_process_stack_load (&state, &info) &&
!flutter_process_field_load (core, model, &state, at, &info, stats) &&
has_info && info.n_regs > 0) {
@@ -938,7 +1052,7 @@ static void flutter_scan_function(RCore *core, FlutterAnalModel *model, RAnalFun
flutter_state_fini (&state);
}
static void flutter_scan_functions(RCore *core, FlutterAnalModel *model, RVecFlutterEntry *entries, FlutterAnalStats *stats) {
static void flutter_scan_functions(RCore *core, FlutterAnalModel *model, FlutterPpStringMap *pp_strings, RVecFlutterEntry *entries, FlutterAnalStats *stats) {
if (!core || !model || !entries || !stats) {
return;
}
@@ -953,7 +1067,7 @@ static void flutter_scan_functions(RCore *core, FlutterAnalModel *model, RVecFlu
continue;
}
ht_up_insert (seen_fcns, fcn->addr, fcn);
flutter_scan_function (core, model, fcn, stats);
flutter_scan_function (core, model, pp_strings, fcn, stats);
}
ht_up_free (seen_fcns);
}
@@ -979,9 +1093,13 @@ bool r2flutter_analysis_run(RCore *core, DartCtx *dctx, bool quiet) {
flutter_apply_app_methods_to_core (core, app, &stats);
flutter_apply_model_to_core (&model, core, &stats);
FlutterPpStringMap pp_strings;
flutter_pp_string_map_init (&pp_strings);
flutter_pp_string_map_load (&app->dctx, NULL, &pp_strings);
RVecFlutterEntry *entries = flutter_collect_entries (core, app, &model);
flutter_ensure_functions (core, entries);
flutter_scan_functions (core, &model, entries, &stats);
flutter_scan_functions (core, &model, &pp_strings, entries, &stats);
if (!quiet) {
R_LOG_INFO ("Flutter analysis: %d functions, %d calls, %d fields, %d classes, %d types, %d strings, %d PP refs",
@@ -995,6 +1113,7 @@ bool r2flutter_analysis_run(RCore *core, DartCtx *dctx, bool quiet) {
}
RVecFlutterEntry_free (entries);
flutter_pp_string_map_fini (&pp_strings);
flutter_model_fini (&model);
dart_app_free (app);
return true;
+9
View File
@@ -0,0 +1,9 @@
NAME=r2-analysis-string-comment
FILE=-
CMDS=<<EOF
!!sh -c 'p=../src/r2/core_flutter.so; [ -f "$p" ] || p=../src/r2/core_flutter.dylib; r2 -q -i "$p" -e scr.color=0 -e bin.relocs.apply=true -c "r2flutter -AAA" -c "s 0x8b914" -c "pd 1" -c q bins/android/first/libapp.so 2>/dev/null | awk "/_nativeGetFloat64/ && /dart: string/ { print \"string-comment-ok\" }"'
EOF
EXPECT=<<EOF
string-comment-ok
EOF
RUN