mirror of
https://github.com/radareorg/r2flutter.git
synced 2026-11-07 06:26:00 -10:00
Fix ppstring recovery in disasm and add tests
This commit is contained in:
1 parent
08001fe45b
commit
02efb309a2
5 files changed
+240
-14
No files matched your search
@@ -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"
|
||||
```
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user