Object pool pointer parser

This commit is contained in:
pancake committed 2026-06-08 16:59:37 +02:00
1 parent f16826e33d
commit ae901bb0ee
14 files changed
+1015 -8

No files matched your search

+1 -1
View File
@@ -13,7 +13,7 @@ BUILD_DIR = build
BIN_DIR = bin
# Source files
LIB_SRC = $(SRC_DIR)/dart_app.c $(SRC_DIR)/dart_dumper.c $(SRC_DIR)/dart_pool_snapshot.c $(SRC_DIR)/dart_pool_discovery.c $(SRC_DIR)/dart_pool_names.c $(SRC_DIR)/dart_pool_strings.c $(SRC_DIR)/dart_pool_data_image.c $(SRC_DIR)/dart_pool_it.c $(SRC_DIR)/dart_pool_xrefs.c $(SRC_DIR)/dart_sbom.c $(SRC_DIR)/dart_pool_clusters.c $(SRC_DIR)/dart_pool_classes.c $(SRC_DIR)/dart_pool_model.c $(SRC_DIR)/dart_pool_parse.c $(SRC_DIR)/dart_pool_modern.c $(SRC_DIR)/dart_version.c $(SRC_DIR)/dart_cid.c $(SRC_DIR)/dart_obf.c
LIB_SRC = $(SRC_DIR)/dart_app.c $(SRC_DIR)/dart_dumper.c $(SRC_DIR)/dart_pool_snapshot.c $(SRC_DIR)/dart_pool_discovery.c $(SRC_DIR)/dart_pool_names.c $(SRC_DIR)/dart_pool_strings.c $(SRC_DIR)/dart_pool_data_image.c $(SRC_DIR)/dart_pool_it.c $(SRC_DIR)/dart_pool_xrefs.c $(SRC_DIR)/dart_object.c $(SRC_DIR)/dart_sbom.c $(SRC_DIR)/dart_pool_clusters.c $(SRC_DIR)/dart_pool_classes.c $(SRC_DIR)/dart_pool_model.c $(SRC_DIR)/dart_pool_parse.c $(SRC_DIR)/dart_pool_modern.c $(SRC_DIR)/dart_version.c $(SRC_DIR)/dart_cid.c $(SRC_DIR)/dart_obf.c
MAIN_SRC = src/tool/main.c
ANALYSIS_SRC = src/r2/flutter_analysis.c
+16
View File
@@ -963,3 +963,19 @@ Dart package versions are not normally serialized in AOT snapshots. Treat
is explicit packaged metadata. The report carries `source`, `confidence`, and
`evidence` so downstream tooling can distinguish hard metadata from URI/string
inference. The top-level JSON intentionally says `complete: false`.
## Object And PP Slot Inspector
Runtime Dart values and static ObjectPool serialization need separate decode
paths. `-O <addr>` treats the input as a Dart tagged value first: low-bit-clear
values are Smis, low-bit-set values are untagged before reading an object
header, and plain aligned addresses are also tried as object starts. When the
CID resolves to a string class, the length Smi is the exact code-unit length;
NUL padding is only display padding.
`-O pp+<off>` resolves against the same reconstructed static PP image as `-p`.
The synthetic PP stores immediate entries directly, but tagged-object entries
are zero placeholders because the serialized cluster stores reference IDs, not
live object pointers. The inspector therefore also walks the first decoded
ObjectPool cluster entry at that PP offset and reports entry bits, ref ID,
resolved kind/name/CID, and code index when available.
+30
View File
@@ -67,6 +67,23 @@ value, which may be a string object pointer, a Smi, a class, a type, a code
object, or something else. The length comes only after resolving and decoding
the target object.
### Decision Rule
Exact string length is available only when the parser has structured Dart
metadata:
1. a snapshot string record boundary, where `encoded >> 1` gives the code-unit
length; or
2. a resolved Dart string object, where the object `length_` Smi gives the
code-unit length.
If the parser only has a raw address into bytes, and it cannot prove that the
address is the start of a serialized string record or a valid Dart string
object, then the exact Dart string length is not knowable. Reading until
`0x00`, until a non-printable byte, until padding, or until the next plausible
record is carving. That can be useful for `-zz` triage, but it must not be
treated as a non-heuristic string length.
## Tagged Pointers
Dart heap object pointers are tagged. Smis and heap objects are distinguished
@@ -90,6 +107,19 @@ The CID table is version-dependent. Do not hardcode string CIDs globally. Use
the resolved `DartVerLayout` values for `cid_string`, `cid_one_byte_string`, and
`cid_two_byte_string`.
`r2flutter -O <addr>` is the direct inspector for this path. It accepts a Smi,
a tagged heap pointer such as `0x1651`, or an untagged object address such as
`0x1650`. For string objects it reports the object header, CID, length Smi,
payload address, code-unit length, encoding, and a bounded value preview. With
`-j`, the same fields are emitted as JSON.
`r2flutter -O pp+<off>` resolves a reconstructed ObjectPool PP slot. For
immediate entries it decodes the raw tagged value. For tagged-object entries it
reports the serialized ObjectPool entry bits, target reference ID, and any
cluster-resolved kind/name/CID. The synthetic PP image contains zero
placeholders for those object refs, so the command must not treat that zero as
the runtime object value.
## NUL Termination
NUL bytes are not the Dart string delimiter.
+2
View File
@@ -356,6 +356,7 @@ name, then applies it to:
| `-HH` | Dump the snapshot header plus VM/isolate cluster allocation/fill layout. |
| `-HHH` | Add selected cluster payload diagnostics, currently ObjectPool entry decoding/status plus conservative ref resolution. |
| `-i` | Dump instruction-table entries. |
| `-O <addr\|pp+off>` | Decode a Dart Smi/tagged heap pointer/object address, or decode a reconstructed ObjectPool PP slot such as `pp+0x68`. Honors `-j`, `-q`, and `-r`. |
| `-p` | Print the reconstructed static ObjectPool PP address pair; with `-r`, map the synthetic pool image and set `anal.gp`/`x27` from the synthetic vaddr. |
| `-R` | Dump a radare2 script for applying method flags/comments and PP helpers. |
| `-S` | Dump best-effort recovered components/SBOM with source, confidence, evidence, and nullable versions. |
@@ -404,6 +405,7 @@ Plugin actions:
| `r2flutter -HH` | Print snapshot header info plus VM/isolate cluster allocation/fill layout. |
| `r2flutter -HHH` | Print cluster layout plus selected payload diagnostics, currently ObjectPool entries/status and ref resolution. |
| `r2flutter -i` | Print instruction-table entries. |
| `r2flutter -O addr` | Decode a Dart Smi/tagged heap pointer/object address, or decode a reconstructed ObjectPool PP slot such as `pp+0x68`. Honors `-j`, `-q`, and `-r`. |
| `r2flutter -p` | Print the reconstructed static ObjectPool PP address pair. |
| `r2flutter -R` | Print full radare2 script output. |
| `r2flutter -S` | Dump best-effort recovered components/SBOM from the current binary context. |
+1
View File
@@ -165,6 +165,7 @@ char *dart_pool_dump_header(DartCtx *ctx, int fmt);
char *dart_pool_dump_header_ext(DartCtx *ctx, int fmt);
char *dart_pool_dump_header_deep(DartCtx *ctx, int fmt);
char *dart_pool_dump_pp(DartCtx *ctx, int fmt);
char *dart_pool_dump_object(DartCtx *ctx, const char *spec, int fmt);
// ============================================================================
// Class Extraction API
+723
View File
@@ -0,0 +1,723 @@
/* r2flutter - MIT - Copyright 2026 - pancake */
#include "dart_pool_parse_priv.h"
#define DART_OBJECT_STRING_PREVIEW_BYTES 4096ULL
#define DART_OBJECT_MAX_STRING_BYTES (1024ULL * 1024ULL)
#define DART_OBJECT_MAX_CANDIDATES 8
#define DART_OBJECT_SYNTHETIC_PP_BASE 0x100000000ULL
typedef struct {
bool valid;
bool recognized_cid;
bool is_string;
bool is_two_byte;
bool value_truncated;
ut64 addr;
ut64 header;
ut64 length_raw;
ut64 length;
ut64 payload_addr;
ut64 payload_size;
int cid;
const char *cid_name;
char *value;
} DartObjectInfo;
typedef struct {
ut64 addr;
const char *kind;
bool ok;
DartObjectInfo object;
} DartObjectCandidate;
typedef struct {
bool is_smi;
st64 smi;
ut64 raw;
int ncandidates;
int selected;
DartObjectCandidate candidates[DART_OBJECT_MAX_CANDIDATES];
} DartValueInfo;
typedef struct {
bool present;
bool read_ok;
bool bits_ok;
bool slot_ok;
ut64 offset;
ut64 addr;
ut64 raw;
ut64 index;
ut8 bits;
DartPpInfo pp;
DartModernPoolSlotInfo slot;
} DartPpSlotInfo;
static void dart_object_info_fini(DartObjectInfo *info) {
if (!info) {
return;
}
free (info->value);
memset (info, 0, sizeof (*info));
}
static void dart_value_info_fini(DartValueInfo *info) {
if (!info) {
return;
}
for (int i = 0; i < info->ncandidates; i++) {
dart_object_info_fini (&info->candidates[i].object);
}
memset (info, 0, sizeof (*info));
}
static void dart_pp_slot_info_fini(DartPpSlotInfo *info) {
if (!info) {
return;
}
dart_pp_info_fini (&info->pp);
dart_modern_pool_slot_info_fini (&info->slot);
memset (info, 0, sizeof (*info));
}
static int dart_object_heap_tag(DartCtx *ctx) {
const int tag = ctx && ctx->layout && ctx->layout->heap_object_tag > 0? ctx->layout->heap_object_tag: 1;
return tag;
}
static int dart_object_cid_from_header(DartCtx *ctx, ut64 header) {
DartTagStyle style = ctx && ctx->layout? ctx->layout->tag_style: DART_TAG_STYLE_OBJECT_HEADER;
switch (style) {
case DART_TAG_STYLE_CID_INT32:
return (int) (header & 0xffffffffU);
case DART_TAG_STYLE_CID_SHIFT1:
return (int) ((header >> 1) & 0xffffffffU);
case DART_TAG_STYLE_OBJECT_HEADER:
default:
return (int) ((header >> 12) & 0xfffffU);
}
}
static const char *dart_object_cid_name(DartCtx *ctx, int cid) {
const DartVerLayout *layout = ctx? ctx->layout: NULL;
for (int i = 0; i < DART_CID_KIND_COUNT; i++) {
if (dart_cid_get (layout, (DartCidKind)i) == cid) {
return dart_cid_kind_name ((DartCidKind)i);
}
}
return NULL;
}
static bool dart_object_parse_u64(const char *s, ut64 *out) {
if (R_STR_ISEMPTY (s) || !out) {
return false;
}
while (isspace ((ut8)*s)) {
s++;
}
if (!*s) {
return false;
}
char *end = NULL;
ut64 value = (ut64)strtoull (s, &end, 0);
if (end == s) {
return false;
}
while (isspace ((ut8)*end)) {
end++;
}
if (*end) {
return false;
}
*out = value;
return true;
}
static bool dart_object_parse_pp_spec(const char *spec, ut64 *out) {
if (R_STR_ISEMPTY (spec) || !out) {
return false;
}
const char *p = r_str_trim_head_ro (spec);
if (*p == '@') {
p++;
}
if (tolower ((ut8)p[0]) != 'p' || tolower ((ut8)p[1]) != 'p' || (p[2] != '+' && p[2] != ':')) {
return false;
}
return dart_object_parse_u64 (p + 3, out);
}
static char *dart_object_bytes_preview(const ut8 *buf, ut64 len) {
RStrBuf *sb = r_strbuf_new ("");
for (ut64 i = 0; i < len; i++) {
ut8 b = buf[i];
if (b == '\\') {
r_strbuf_append (sb, "\\\\");
} else if (b == '"') {
r_strbuf_append (sb, "\\\"");
} else if (b >= 0x20 && b < 0x7f) {
r_strbuf_appendf (sb, "%c", b);
} else {
r_strbuf_appendf (sb, "\\x%02x", b);
}
}
return r_strbuf_drain (sb);
}
static bool dart_object_read_one_byte_string(DartCtx *ctx, DartObjectInfo *info) {
const ut64 nread = R_MIN (info->payload_size, DART_OBJECT_STRING_PREVIEW_BYTES);
if (nread == 0) {
info->value = strdup ("");
return true;
}
ut8 *buf = (ut8 *)malloc ((size_t)nread);
if (!buf) {
return false;
}
bool ok = read_mem (ctx, info->payload_addr, buf, (int)nread);
if (ok) {
info->value = dart_object_bytes_preview (buf, nread);
info->value_truncated = info->payload_size > nread;
}
free (buf);
return ok;
}
static bool dart_object_read_two_byte_string(DartCtx *ctx, DartObjectInfo *info) {
ut64 nbytes = R_MIN (info->payload_size, DART_OBJECT_STRING_PREVIEW_BYTES * 2ULL);
nbytes &= ~1ULL;
if (nbytes == 0) {
info->value = strdup ("");
return true;
}
ut8 *buf = (ut8 *)malloc ((size_t)nbytes);
if (!buf) {
return false;
}
bool ok = read_mem (ctx, info->payload_addr, buf, (int)nbytes);
if (ok) {
info->value = dart_utf16le_to_utf8 (buf, nbytes);
if (!info->value) {
info->value = dart_object_bytes_preview (buf, nbytes);
}
info->value_truncated = info->payload_size > nbytes;
}
free (buf);
return ok;
}
static bool dart_object_read_string_payload(DartCtx *ctx, DartObjectInfo *info, bool two_byte, ut64 payload_addr) {
info->is_string = true;
info->is_two_byte = two_byte;
info->payload_addr = payload_addr;
info->payload_size = two_byte? info->length * 2ULL: info->length;
if (info->payload_size > DART_OBJECT_MAX_STRING_BYTES) {
info->value_truncated = true;
return true;
}
return two_byte? dart_object_read_two_byte_string (ctx, info): dart_object_read_one_byte_string (ctx, info);
}
static bool dart_object_decode_string(DartCtx *ctx, DartObjectInfo *info, const ut8 *hdr) {
const bool two_byte_cid = dart_cid_is (ctx->layout, info->cid, DART_CID_TWO_BYTE_STRING);
const bool string_cid = dart_cid_is (ctx->layout, info->cid, DART_CID_STRING);
const bool one_byte_cid = dart_cid_is (ctx->layout, info->cid, DART_CID_ONE_BYTE_STRING);
if (!string_cid && !one_byte_cid && !two_byte_cid) {
return true;
}
if (ctx->compressed_word_size == 4) {
info->length_raw = r_read_le32 (hdr + 8);
} else {
info->length_raw = r_read_le64 (hdr + 8);
}
if (info->length_raw & 1ULL) {
return true;
}
info->length = info->length_raw >> 1;
ut64 payload_candidates[2];
int npayloads = 0;
if (ctx->compressed_word_size == 4) {
payload_candidates[npayloads++] = info->addr + 12;
payload_candidates[npayloads++] = info->addr + 16;
} else {
payload_candidates[npayloads++] = info->addr + 16;
}
for (int i = 0; i < npayloads; i++) {
if (two_byte_cid) {
if (dart_object_read_string_payload (ctx, info, true, payload_candidates[i])) {
return true;
}
} else if (dart_object_read_string_payload (ctx, info, false, payload_candidates[i])) {
return true;
}
}
return true;
}
static bool dart_object_decode_at(DartCtx *ctx, ut64 addr, DartObjectInfo *out) {
if (!ctx || !out) {
return false;
}
memset (out, 0, sizeof (*out));
ut8 hdr[32];
if (!read_mem (ctx, addr, hdr, sizeof (hdr))) {
return false;
}
out->addr = addr;
out->header = r_read_le64 (hdr);
out->cid = dart_object_cid_from_header (ctx, out->header);
if (out->cid <= 0 || out->cid >= 0xfffff) {
return false;
}
out->cid_name = dart_object_cid_name (ctx, out->cid);
out->recognized_cid = R_STR_ISNOTEMPTY (out->cid_name);
if (!out->recognized_cid) {
return false;
}
out->valid = true;
dart_object_decode_string (ctx, out, hdr);
return true;
}
static void dart_value_add_candidate(DartValueInfo *value, ut64 addr, const char *kind) {
if (!value || value->ncandidates >= DART_OBJECT_MAX_CANDIDATES) {
return;
}
for (int i = 0; i < value->ncandidates; i++) {
if (value->candidates[i].addr == addr) {
return;
}
}
DartObjectCandidate *candidate = &value->candidates[value->ncandidates++];
candidate->addr = addr;
candidate->kind = kind;
}
static void dart_value_add_compressed_candidates(DartCtx *ctx, DartValueInfo *value, ut64 raw) {
if (!ctx || ctx->compressed_word_size != 4 || raw > UT32_MAX) {
return;
}
const int tag = dart_object_heap_tag (ctx);
ut64 data_base = 0;
DartSnapshotHeader sh = { 0 };
if (ctx->iso_data && dart_snapshot_header_read (ctx, ctx->iso_data, &sh) && sh.ok) {
ut64 align = ctx->layout && ctx->layout->max_alignment? (ut64)ctx->layout->max_alignment: 16;
data_base = ctx->iso_data + ((sh.total_len + (align - 1)) & ~ (align - 1));
}
ut64 full = (data_base & ~0xffffffffULL) | (raw & 0xffffffffULL);
if ((full & (ut64)tag) == (ut64)tag) {
dart_value_add_candidate (value, full - (ut64)tag, "compressed_tagged_heap");
}
dart_value_add_candidate (value, data_base + (raw & ~3ULL), "compressed_data_image");
}
static void dart_value_decode(DartCtx *ctx, ut64 raw, DartValueInfo *value) {
memset (value, 0, sizeof (*value));
value->raw = raw;
value->selected = -1;
value->is_smi = (raw & 1ULL) == 0;
if (value->is_smi) {
value->smi = ((st64)raw) >> 1;
}
const int tag = dart_object_heap_tag (ctx);
if ((raw & (ut64)tag) == (ut64)tag && raw >= (ut64)tag) {
dart_value_add_candidate (value, raw - (ut64)tag, "tagged_heap");
}
dart_value_add_candidate (value, raw, "raw_address");
if ((raw & 3ULL) != 0) {
dart_value_add_candidate (value, raw & ~3ULL, "low_bits_stripped");
}
dart_value_add_compressed_candidates (ctx, value, raw);
for (int i = 0; i < value->ncandidates; i++) {
DartObjectCandidate *candidate = &value->candidates[i];
candidate->ok = dart_object_decode_at (ctx, candidate->addr, &candidate->object);
if (candidate->ok && value->selected < 0) {
value->selected = i;
}
}
}
static bool dart_pp_slot_decode(DartCtx *ctx, ut64 offset, DartPpSlotInfo *slot) {
memset (slot, 0, sizeof (*slot));
slot->present = true;
slot->offset = offset;
if (!dart_resolve_pp_info (ctx, &slot->pp)) {
return false;
}
if (offset + (ut64)slot->pp.word_size > slot->pp.size) {
return false;
}
slot->addr = slot->pp.base + offset;
slot->raw = slot->pp.word_size == 4? (ut64)r_read_le32 (slot->pp.image + offset): r_read_le64 (slot->pp.image + offset);
slot->read_ok = true;
if (offset >= slot->pp.entries_offset && offset < slot->pp.entry_bits_offset) {
ut64 entry_off = offset - slot->pp.entries_offset;
if (slot->pp.word_size > 0 && entry_off % (ut64)slot->pp.word_size == 0) {
slot->index = entry_off / (ut64)slot->pp.word_size;
if (slot->index < slot->pp.length) {
slot->bits = slot->pp.image[slot->pp.entry_bits_offset + slot->index];
slot->bits_ok = true;
}
}
}
if (slot->bits_ok) {
DartSnapshotHeader sh = { 0 };
if (dart_snapshot_header_read (ctx, slot->pp.snapshot_base, &sh) && sh.ok) {
const DartModernClusterRequest req = {
ctx,
sh.cluster_start,
slot->pp.snapshot_base + sh.total_len,
sh.nc,
sh.nb
};
slot->slot_ok = dart_modern_resolve_pp_slot (&req, offset, &slot->slot);
}
}
return true;
}
static void dart_object_json_object_fields(PJ *pj, const DartObjectInfo *obj) {
pj_kn (pj, "addr", obj->addr);
pj_kn (pj, "header", obj->header);
pj_ki (pj, "cid", obj->cid);
pj_ks (pj, "cid_name", obj->cid_name? obj->cid_name: "unknown");
pj_kb (pj, "recognized_cid", obj->recognized_cid);
if (obj->is_string) {
pj_ko (pj, "string");
pj_kn (pj, "length_raw", obj->length_raw);
pj_kn (pj, "length", obj->length);
pj_kn (pj, "payload_addr", obj->payload_addr);
pj_kn (pj, "payload_size", obj->payload_size);
pj_kb (pj, "two_byte", obj->is_two_byte);
pj_kb (pj, "value_truncated", obj->value_truncated);
if (obj->value) {
pj_ks (pj, "value", obj->value);
}
pj_end (pj);
}
}
static void dart_object_json_value(PJ *pj, const DartValueInfo *value, bool decode_raw) {
pj_ko (pj, "value");
pj_kn (pj, "raw", value->raw);
pj_kb (pj, "decoded", decode_raw);
if (!decode_raw) {
pj_ks (pj, "kind", "object_pool_ref");
} else if (value->selected >= 0) {
pj_ks (pj, "kind", "object");
pj_ko (pj, "object");
dart_object_json_object_fields (pj, &value->candidates[value->selected].object);
pj_end (pj);
} else if (value->is_smi) {
pj_kb (pj, "smi", true);
pj_kN (pj, "smi_value", value->smi);
pj_ks (pj, "kind", "smi");
} else {
pj_kb (pj, "smi", false);
pj_ks (pj, "kind", "unknown");
}
pj_ka (pj, "candidates");
for (int i = 0; i < value->ncandidates; i++) {
const DartObjectCandidate *candidate = &value->candidates[i];
pj_o (pj);
pj_ks (pj, "kind", candidate->kind);
pj_kn (pj, "addr", candidate->addr);
pj_kb (pj, "ok", candidate->ok);
if (candidate->ok) {
pj_ko (pj, "object");
pj_kn (pj, "header", candidate->object.header);
pj_ki (pj, "cid", candidate->object.cid);
pj_ks (pj, "cid_name", candidate->object.cid_name? candidate->object.cid_name: "unknown");
pj_end (pj);
}
pj_end (pj);
}
pj_end (pj);
pj_end (pj);
}
static void dart_object_json_pool_slot(PJ *pj, const DartPpSlotInfo *slot) {
pj_ko (pj, "pp_slot");
pj_kn (pj, "pp", slot->pp.base);
pj_kn (pj, "offset", slot->offset);
pj_kn (pj, "addr", slot->addr);
pj_kn (pj, "raw", slot->raw);
pj_kn (pj, "index", slot->index);
pj_kb (pj, "bits_ok", slot->bits_ok);
if (slot->bits_ok) {
pj_ki (pj, "bits", slot->bits);
}
pj_kb (pj, "resolved", slot->slot_ok);
if (slot->slot_ok) {
const DartModernPoolSlotInfo *si = &slot->slot;
pj_kn (pj, "cluster_index", si->cluster_index);
pj_kn (pj, "pool_index", si->pool_index);
pj_kn (pj, "pool_ref", si->pool_ref);
pj_kn (pj, "length", si->length);
pj_kn (pj, "entry_index", si->index);
pj_kn (pj, "pool_offset", si->pool_offset);
pj_kn (pj, "pp_offset", si->pp_offset);
pj_kn (pj, "stream_offset", si->stream_offset);
pj_kn (pj, "value_offset", si->value_offset);
pj_ki (pj, "entry_bits", si->bits);
pj_ks (pj, "type", si->type_name? si->type_name: "unknown");
pj_ks (pj, "patch", si->patch_name? si->patch_name: "unknown");
pj_ks (pj, "behavior", si->behavior_name? si->behavior_name: "unknown");
if (si->behavior == 0 && si->type == 1) {
pj_kn (pj, "ref", si->ref);
} else if (si->behavior == 0 && si->type == 0) {
pj_kn (pj, "raw_value", si->raw);
}
if (R_STR_ISNOTEMPTY (si->resolved_kind)) {
pj_ks (pj, "resolved_kind", si->resolved_kind);
}
if (R_STR_ISNOTEMPTY (si->resolved_name)) {
pj_ks (pj, "resolved_name", si->resolved_name);
}
if (si->resolved_cid >= 0) {
pj_ki (pj, "resolved_cid", si->resolved_cid);
}
if (si->resolved_code_index != UT64_MAX) {
pj_kn (pj, "resolved_code_index", si->resolved_code_index);
}
}
pj_end (pj);
}
static char *dart_object_dump_json(const char *spec, const DartValueInfo *value, const DartPpSlotInfo *slot, bool decode_raw) {
PJ *pj = pj_new ();
if (!pj) {
return strdup ("{\"error\":\"Failed to create JSON\"}");
}
pj_o (pj);
pj_ks (pj, "spec", spec? spec: "");
if (slot && slot->present) {
if (!slot->read_ok) {
pj_ks (pj, "kind", "error");
pj_ks (pj, "error", "PP slot not resolved");
} else if (slot->slot_ok && slot->slot.behavior == 0 && slot->slot.type == 1) {
pj_ks (pj, "kind", "object_pool_ref");
} else {
pj_ks (pj, "kind", "pp_slot");
}
dart_object_json_pool_slot (pj, slot);
} else if (decode_raw && value->selected >= 0) {
pj_ks (pj, "kind", "object");
} else if (value->is_smi) {
pj_ks (pj, "kind", "smi");
} else {
pj_ks (pj, "kind", "unknown");
}
dart_object_json_value (pj, value, decode_raw);
pj_end (pj);
return pj_drain (pj);
}
static void dart_object_text_object(RStrBuf *sb, const DartObjectInfo *obj, const char *prefix) {
r_strbuf_appendf (sb, "%saddr: 0x%" PFMT64x "\n", prefix, obj->addr);
r_strbuf_appendf (sb, "%sheader: 0x%" PFMT64x "\n", prefix, obj->header);
r_strbuf_appendf (sb, "%scid: %d %s\n", prefix, obj->cid, obj->cid_name? obj->cid_name: "unknown");
if (obj->is_string) {
r_strbuf_appendf (sb, "%sstring.len: %" PRIu64 "\n", prefix, (uint64_t)obj->length);
r_strbuf_appendf (sb, "%sstring.raw: 0x%" PFMT64x "\n", prefix, obj->length_raw);
r_strbuf_appendf (sb, "%spayload: 0x%" PFMT64x " size=0x%" PFMT64x "\n", prefix, obj->payload_addr, obj->payload_size);
r_strbuf_appendf (sb, "%sencoding: %s\n", prefix, obj->is_two_byte? "utf16le": "one-byte");
if (obj->value) {
r_strbuf_appendf (sb, "%svalue: \"%s%s\"\n", prefix, obj->value, obj->value_truncated? "...": "");
}
}
}
static void dart_object_text_value(RStrBuf *sb, const DartValueInfo *value, bool decode_raw) {
r_strbuf_appendf (sb, "raw: 0x%" PFMT64x "\n", value->raw);
if (!decode_raw) {
r_strbuf_append (sb, "value: object_pool_ref\n");
return;
}
if (value->is_smi) {
r_strbuf_appendf (sb, "smi: %" PFMT64d "\n", value->smi);
}
if (decode_raw && value->selected >= 0) {
const DartObjectCandidate *candidate = &value->candidates[value->selected];
r_strbuf_appendf (sb, "object: %s\n", candidate->kind);
dart_object_text_object (sb, &candidate->object, "");
} else if (!value->is_smi) {
r_strbuf_append (sb, "object: unresolved\n");
}
if (value->ncandidates > 0) {
r_strbuf_append (sb, "candidates:\n");
for (int i = 0; i < value->ncandidates; i++) {
const DartObjectCandidate *candidate = &value->candidates[i];
r_strbuf_appendf (sb, " - %-22s 0x%" PFMT64x " %s", candidate->kind, candidate->addr, candidate->ok? "ok": "no");
if (candidate->ok) {
r_strbuf_appendf (sb, " cid=%d %s", candidate->object.cid, candidate->object.cid_name? candidate->object.cid_name: "unknown");
}
r_strbuf_append (sb, "\n");
}
}
}
static void dart_object_text_pool_slot(RStrBuf *sb, const DartPpSlotInfo *slot) {
if (!slot->read_ok) {
r_strbuf_append (sb, "pp_slot: unresolved\n");
return;
}
r_strbuf_appendf (sb, "pp: 0x%" PFMT64x "\n", slot->pp.base);
r_strbuf_appendf (sb, "pp.offset: 0x%" PFMT64x "\n", slot->offset);
r_strbuf_appendf (sb, "pp.addr: 0x%" PFMT64x "\n", slot->addr);
r_strbuf_appendf (sb, "pp.raw: 0x%" PFMT64x "\n", slot->raw);
if (slot->bits_ok) {
r_strbuf_appendf (sb, "pp.index: %" PRIu64 "\n", (uint64_t)slot->index);
r_strbuf_appendf (sb, "pp.bits: 0x%02x\n", (unsigned int)slot->bits);
}
if (slot->slot_ok) {
const DartModernPoolSlotInfo *si = &slot->slot;
r_strbuf_appendf (sb, "pool.ref: %" PRIu64 "\n", (uint64_t)si->pool_ref);
r_strbuf_appendf (sb, "pool.index: %" PRIu64 "\n", (uint64_t)si->pool_index);
r_strbuf_appendf (sb, "entry.index: %" PRIu64 "\n", (uint64_t)si->index);
r_strbuf_appendf (sb, "entry.stream: 0x%" PFMT64x "\n", si->stream_offset);
r_strbuf_appendf (sb, "entry.value: 0x%" PFMT64x "\n", si->value_offset);
r_strbuf_appendf (sb, "entry.type: %s\n", si->type_name? si->type_name: "unknown");
r_strbuf_appendf (sb, "entry.patch: %s\n", si->patch_name? si->patch_name: "unknown");
r_strbuf_appendf (sb, "entry.behavior: %s\n", si->behavior_name? si->behavior_name: "unknown");
if (si->behavior == 0 && si->type == 1) {
r_strbuf_appendf (sb, "target.ref: %" PRIu64 "\n", (uint64_t)si->ref);
} else if (si->behavior == 0 && si->type == 0) {
r_strbuf_appendf (sb, "target.raw: 0x%" PFMT64x "\n", si->raw);
}
if (R_STR_ISNOTEMPTY (si->resolved_kind)) {
r_strbuf_appendf (sb, "target.kind: %s\n", si->resolved_kind);
}
if (si->resolved_cid >= 0) {
r_strbuf_appendf (sb, "target.cid: %d\n", si->resolved_cid);
}
if (si->resolved_code_index != UT64_MAX) {
r_strbuf_appendf (sb, "target.code: %" PRIu64 "\n", (uint64_t)si->resolved_code_index);
}
if (R_STR_ISNOTEMPTY (si->resolved_name)) {
r_strbuf_appendf (sb, "target.name: \"%s\"\n", si->resolved_name);
}
}
}
static char *dart_object_dump_text(const char *spec, const DartValueInfo *value, const DartPpSlotInfo *slot, bool decode_raw, bool quiet) {
RStrBuf *sb = r_strbuf_new ("");
if (quiet) {
if (slot && slot->present && slot->slot_ok && R_STR_ISNOTEMPTY (slot->slot.resolved_name)) {
r_strbuf_appendf (sb, "%s", slot->slot.resolved_name);
return r_strbuf_drain (sb);
}
if (decode_raw && value->selected >= 0) {
const DartObjectInfo *obj = &value->candidates[value->selected].object;
if (obj->is_string && obj->value) {
r_strbuf_appendf (sb, "%s", obj->value);
return r_strbuf_drain (sb);
}
}
r_strbuf_appendf (sb, "raw=0x%" PFMT64x, value->raw);
return r_strbuf_drain (sb);
}
r_strbuf_appendf (sb, "spec: %s\n", spec? spec: "");
if (slot && slot->present) {
dart_object_text_pool_slot (sb, slot);
}
dart_object_text_value (sb, value, decode_raw);
return r_strbuf_drain (sb);
}
static char *dart_object_dump_r2(const DartValueInfo *value, const DartPpSlotInfo *slot, bool decode_raw) {
RStrBuf *sb = r_strbuf_new ("");
if (slot && slot->present && slot->read_ok) {
r_strbuf_appendf (sb, "'@0x%" PFMT64x "'CC Dart PP slot offset=0x%" PFMT64x " raw=0x%" PFMT64x, slot->addr, slot->offset, slot->raw);
if (slot->slot_ok) {
const DartModernPoolSlotInfo *si = &slot->slot;
r_strbuf_appendf (sb, " entry=%" PRIu64 " type=%s behavior=%s", (uint64_t)si->index, si->type_name? si->type_name: "unknown", si->behavior_name? si->behavior_name: "unknown");
if (R_STR_ISNOTEMPTY (si->resolved_kind)) {
r_strbuf_appendf (sb, " resolved=%s", si->resolved_kind);
}
if (R_STR_ISNOTEMPTY (si->resolved_name)) {
char *escaped = r_str_escape_utf8 (si->resolved_name, false, true);
r_strbuf_appendf (sb, " name=\"%s\"", escaped);
free (escaped);
}
}
r_strbuf_append (sb, "\n");
}
if (decode_raw && value->selected >= 0) {
const DartObjectInfo *obj = &value->candidates[value->selected].object;
r_strbuf_appendf (sb, "f dart.object 1 @ 0x%" PFMT64x "\n", obj->addr);
r_strbuf_appendf (sb, "'@0x%" PFMT64x "'CC Dart object cid=%d %s", obj->addr, obj->cid, obj->cid_name? obj->cid_name: "unknown");
if (obj->is_string && obj->value) {
char *escaped = r_str_escape_utf8 (obj->value, false, true);
r_strbuf_appendf (sb, " len=%" PRIu64 " value=\"%s%s\"", (uint64_t)obj->length, escaped, obj->value_truncated? "...": "");
free (escaped);
}
r_strbuf_append (sb, "\n");
} else if (decode_raw && value->is_smi) {
r_strbuf_appendf (sb, "'CC Dart Smi raw=0x%" PFMT64x " value=%" PFMT64d "\n", value->raw, value->smi);
}
return r_strbuf_drain (sb);
}
char *dart_pool_dump_object(DartCtx *ctx, const char *spec, int fmt) {
if (!ctx || R_STR_ISEMPTY (spec)) {
if (fmt == 'j') {
return strdup ("{\"error\":\"missing object spec\"}");
}
return fmt == 'r'? strdup ("# Error: missing object spec\n"): strdup ("Error: missing object spec\n");
}
if (find_snapshots (ctx) != 0) {
if (fmt == 'j') {
return strdup ("{\"error\":\"Dart snapshots not found\"}");
}
return fmt == 'r'? strdup ("# Error: Dart snapshots not found\n"): strdup ("Error: Dart snapshots not found\n");
}
DartVerLayout layout_tmp;
DartVerLayout *layout_owned = ctx->layout? NULL: dart_ctx_init_layout (ctx, &layout_tmp);
ut64 raw = 0;
ut64 pp_offset = 0;
bool is_pp = dart_object_parse_pp_spec (spec, &pp_offset);
if (!is_pp) {
if (!dart_object_parse_u64 (spec, &raw)) {
if (layout_owned) {
dart_ctx_fini_layout (ctx, layout_owned);
}
if (fmt == 'j') {
return strdup ("{\"error\":\"invalid object spec\"}");
}
return fmt == 'r'? strdup ("# Error: invalid object spec\n"): strdup ("Error: invalid object spec\n");
}
if (raw >= DART_OBJECT_SYNTHETIC_PP_BASE) {
pp_offset = raw - DART_OBJECT_SYNTHETIC_PP_BASE;
is_pp = true;
}
}
DartValueInfo value;
memset (&value, 0, sizeof (value));
value.selected = -1;
DartPpSlotInfo slot;
memset (&slot, 0, sizeof (slot));
bool decode_raw = true;
if (is_pp) {
if (dart_pp_slot_decode (ctx, pp_offset, &slot)) {
raw = slot.slot_ok && slot.slot.behavior == 0 && slot.slot.type == 0? slot.slot.raw: slot.raw;
decode_raw = ! (slot.slot_ok && slot.slot.behavior == 0 && slot.slot.type == 1);
}
}
dart_value_decode (ctx, raw, &value);
char *out = NULL;
if (fmt == 'j') {
out = dart_object_dump_json (spec, &value, is_pp? &slot: NULL, decode_raw);
} else if (fmt == 'r') {
out = dart_object_dump_r2 (&value, is_pp? &slot: NULL, decode_raw);
} else {
out = dart_object_dump_text (spec, &value, is_pp? &slot: NULL, decode_raw, ctx->quiet);
}
dart_value_info_fini (&value);
dart_pp_slot_info_fini (&slot);
if (layout_owned) {
dart_ctx_fini_layout (ctx, layout_owned);
}
return out;
}
+106
View File
@@ -2364,6 +2364,112 @@ fail:
return NULL;
}
void dart_modern_pool_slot_info_fini(DartModernPoolSlotInfo *info) {
if (!info) {
return;
}
free (info->resolved_name);
memset (info, 0, sizeof (*info));
info->resolved_cid = -1;
info->resolved_code_index = UT64_MAX;
}
static void modern_pool_slot_info_set_resolved(DartModernPoolSlotInfo *out, const ModernResolvedRef *resolved) {
if (!out || !resolved) {
return;
}
out->resolved_kind = resolved->kind;
out->resolved_cid = resolved->cid;
out->resolved_code_index = resolved->code_index;
if (R_STR_ISNOTEMPTY (resolved->name)) {
out->resolved_name = strdup (resolved->name);
}
}
bool dart_modern_resolve_pp_slot(const DartModernClusterRequest *req, ut64 pp_offset, DartModernPoolSlotInfo *out) {
DartCtx *ctx = req? req->ctx: NULL;
if (!ctx || !out || !dart_modern_is_supported_snapshot (ctx)) {
return false;
}
memset (out, 0, sizeof (*out));
out->resolved_cid = -1;
out->resolved_code_index = UT64_MAX;
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 have_resolver = modern_ref_resolver_init (&resolver, ctx, meta, req->num_clusters, req->num_base_objects);
const ModernCidCache cids = modern_cid_cache_init (ctx->layout);
bool done = false;
bool ok = false;
for (ut64 i = 0; i < req->num_clusters && !done; 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 && !done; pool_index++) {
ut64 length = 0;
if (!cs_read_unsigned (&s, &length)) {
break;
}
done = true;
for (ut64 entry_index = 0; entry_index < length; entry_index++) {
ModernPoolEntry entry = {
.index = entry_index,
.stream_offset = s.cursor,
};
if (!cs_read_u8 (&s, &entry.bits)) {
break;
}
entry.type = entry.bits & 0x0f;
entry.patch = (entry.bits >> 4) & 1;
entry.behavior = entry.bits >> 5;
entry.value_offset = s.cursor;
if (!modern_decode_pool_entry (&s, ctx, entry.type, entry.behavior, &entry.ref, &entry.raw)) {
break;
}
ut64 entry_pp = modern_pool_entry_pp_offset (ctx, entry_index);
if (entry_pp != pp_offset) {
continue;
}
modern_resolve_pool_entry (have_resolver? &resolver: NULL, &cids, entry.type, entry.behavior, entry.ref, &entry.resolved);
out->valid = true;
out->cluster_index = i;
out->pool_index = pool_index;
out->pool_ref = m->start_ref + pool_index;
out->length = length;
out->index = entry_index;
out->pool_offset = modern_pool_entry_pool_offset (ctx, entry_index);
out->pp_offset = entry_pp;
out->stream_offset = entry.stream_offset;
out->value_offset = entry.value_offset;
out->ref = entry.ref;
out->raw = entry.raw;
out->bits = entry.bits;
out->type = entry.type;
out->patch = entry.patch;
out->behavior = entry.behavior;
out->type_name = modern_pool_entry_type_name (entry.type);
out->patch_name = modern_pool_entry_patch_name (entry.patch);
out->behavior_name = modern_pool_entry_behavior_name (entry.behavior);
modern_pool_slot_info_set_resolved (out, &entry.resolved);
modern_resolved_ref_fini (&entry.resolved);
ok = true;
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;
+7 -5
View File
@@ -934,7 +934,7 @@ char *dart_pool_dump_header_deep(DartCtx *ctx, int fmt) {
return dart_pool_dump_header_ext_level (ctx, fmt, 3);
}
static void dart_pp_info_fini(DartPpInfo *info) {
void dart_pp_info_fini(DartPpInfo *info) {
if (!info) {
return;
}
@@ -956,7 +956,7 @@ static bool resolve_pp_from_snapshot(DartCtx *ctx, ut64 snapshot_base, const cha
return dart_modern_build_synthetic_pp (&req, snapshot_base, label, data_image_base, info);
}
static bool resolve_pp_info(DartCtx *ctx, DartPpInfo *info) {
bool dart_resolve_pp_info(DartCtx *ctx, DartPpInfo *info) {
if (!ctx || !ctx->core || !info) {
return false;
}
@@ -965,12 +965,14 @@ static bool resolve_pp_info(DartCtx *ctx, DartPpInfo *info) {
return false;
}
DartVerLayout layout_tmp;
DartVerLayout *layout_owned = dart_ctx_init_layout (ctx, &layout_tmp);
DartVerLayout *layout_owned = ctx->layout? NULL: dart_ctx_init_layout (ctx, &layout_tmp);
bool ok = resolve_pp_from_snapshot (ctx, ctx->iso_data, "Isolate", info);
if (!ok) {
ok = resolve_pp_from_snapshot (ctx, ctx->vm_data, "VM", info);
}
dart_ctx_fini_layout (ctx, layout_owned);
if (layout_owned) {
dart_ctx_fini_layout (ctx, layout_owned);
}
return ok;
}
@@ -1061,7 +1063,7 @@ static char *dump_pp_text(const DartPpInfo *info, bool quiet) {
char *dart_pool_dump_pp(DartCtx *ctx, int fmt) {
DartPpInfo info = { 0 };
if (!resolve_pp_info (ctx, &info)) {
if (!dart_resolve_pp_info (ctx, &info)) {
if (fmt == 'j') {
return strdup ("{\"error\":\"PP not resolved\",\"reason\":\"ObjectPool fill payload was not decoded\"}");
}
+31
View File
@@ -150,6 +150,32 @@ typedef struct {
ut8 *image;
} DartPpInfo;
typedef struct {
bool valid;
ut64 cluster_index;
ut64 pool_index;
ut64 pool_ref;
ut64 length;
ut64 index;
ut64 pool_offset;
ut64 pp_offset;
ut64 stream_offset;
ut64 value_offset;
ut64 ref;
ut64 raw;
ut8 bits;
ut8 type;
ut8 patch;
ut8 behavior;
const char *type_name;
const char *patch_name;
const char *behavior_name;
const char *resolved_kind;
char *resolved_name;
int resolved_cid;
ut64 resolved_code_index;
} DartModernPoolSlotInfo;
typedef void(*DartInstructionTableEntryCallback)(const DartInstructionTableEntry *entry, void *user);
typedef struct {
@@ -233,6 +259,8 @@ bool modern_skip_n_bytes(ClusterStream *s, ut64 len);
bool dart_modern_is_supported_snapshot(DartCtx *ctx);
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_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);
bool dart_modern_extract_classes_from_clusters(const DartModernClusterRequest *req, RList *class_list);
@@ -249,4 +277,7 @@ int dart_it_emit_linear(const DartItEmitRequest *req);
int dart_it_emit_fixed(const DartItEmitRequest *req);
int dart_it_emit_varint(const DartItEmitRequest *req);
bool dart_resolve_pp_info(DartCtx *ctx, DartPpInfo *info);
void dart_pp_info_fini(DartPpInfo *info);
#endif
+21 -1
View File
@@ -20,6 +20,7 @@ typedef struct {
bool string_refs;
bool help;
char *obf_map_path;
char *object_spec;
} R2FlutterCmd;
static bool r2flutter_core_init(RCorePluginSession *cps) {
@@ -69,6 +70,7 @@ static void r2flutter_help(RCore *core) {
"| r2flutter -HHH decode selected cluster payloads for diagnostics\n"
"| r2flutter -h show this help\n"
"| r2flutter -i dump instruction table entries\n"
"| r2flutter -O addr decode Dart tagged/object pointer or ObjectPool PP slot\n"
"| r2flutter -p print reconstructed ObjectPool PP value\n"
"| r2flutter -R dump full radare2 script (like standalone -R)\n"
"| r2flutter -S dump best-effort recovered SBOM/components\n"
@@ -161,6 +163,7 @@ static bool r2flutter_parse_cmd(const char *args, DartCtx *dctx, R2FlutterCmd *c
cmd->string_refs = false;
cmd->help = false;
cmd->obf_map_path = NULL;
cmd->object_spec = NULL;
char *dup = strdup (args);
int nwords = r_str_word_set0 (dup);
char **words = R_NEWS0 (char *, nwords);
@@ -211,6 +214,19 @@ static bool r2flutter_parse_cmd(const char *args, DartCtx *dctx, R2FlutterCmd *c
dctx->dump_it = true;
cmd->action = flag;
break;
case 'O':
{
const char *arg = r2flutter_opt_arg (tail, words, nwords, &i);
if (!arg) {
cmd->help = true;
break;
}
free (cmd->object_spec);
cmd->object_spec = strdup (arg);
cmd->action = flag;
j += strlen (tail);
break;
}
case 'p':
cmd->action = flag;
break;
@@ -284,7 +300,7 @@ static bool r2flutter_parse_cmd(const char *args, DartCtx *dctx, R2FlutterCmd *c
cmd->help = true;
break;
}
if (cmd->help || (R_STR_ISNOTEMPTY (tail) && (flag == 'l' || flag == 'm'))) {
if (cmd->help || (R_STR_ISNOTEMPTY (tail) && (flag == 'l' || flag == 'm' || flag == 'O'))) {
break;
}
}
@@ -335,6 +351,9 @@ static bool r2flutter_run_cmd(RCore *core, DartCtx *dctx, const R2FlutterCmd *cm
case 'i':
out = dart_pool_dump_it (dctx, cmd->fmt);
break;
case 'O':
out = dart_pool_dump_object (dctx, cmd->object_spec, cmd->fmt);
break;
case 'p':
out = dart_pool_dump_pp (dctx, cmd->fmt);
break;
@@ -401,6 +420,7 @@ static bool r_cmd_r2flutter_call(RCorePluginSession *cps, const char *input) {
ret = r2flutter_run_cmd (core, &dctx, &cmd);
}
free (cmd.obf_map_path);
free (cmd.object_spec);
return ret;
}
r2flutter_help (core);
+10 -1
View File
@@ -30,6 +30,7 @@ static const char usage_text[] =
" -HH Print extended snapshot header and cluster layout\n"
" -HHH Decode selected cluster payloads for diagnostics\n"
" -i Print instruction table entries to stdout\n"
" -O <addr|pp+off> Decode Dart tagged/object pointer or ObjectPool PP slot\n"
" -p Print reconstructed ObjectPool PP value\n"
" -R Print radare2 script for snapshot analysis\n"
" -S Print best-effort recovered SBOM/components\n"
@@ -111,11 +112,12 @@ int main(int argc, char **argv) {
int analysis_depth = 0;
int header_depth = 0;
int string_depth = 0;
const char *object_spec = NULL;
DartCtx dctx = {
.no_stubs = true
};
RGetopt opt;
r_getopt_init (&opt, argc, (const char **)argv, "AcfhHijnm:pqrRSzTvVxl:");
r_getopt_init (&opt, argc, (const char **)argv, "AcfhHijnm:O:pqrRSzTvVxl:");
int c;
while ((c = r_getopt_next (&opt)) != -1) {
switch (c) {
@@ -140,6 +142,10 @@ int main(int argc, char **argv) {
action = c;
dctx.dump_it = true;
break;
case 'O':
action = c;
object_spec = opt.arg;
break;
case 'p':
action = c;
break;
@@ -305,6 +311,9 @@ int main(int argc, char **argv) {
case 'i':
output = dart_pool_dump_it (&app->dctx, fmt);
break;
case 'O':
output = dart_pool_dump_object (&app->dctx, object_spec, fmt);
break;
case 'p':
output = dart_pool_dump_pp (&app->dctx, fmt);
break;
+12
View File
@@ -0,0 +1,12 @@
{
"name": "sbom-synthetic",
"cwd": "test",
"cmd": ["../bin/r2flutter", "-q", "-S", "bins/synthetic/field_snapshot.bin"],
"timeout": 10,
"expect": {
"contains": [
"dart-package\ttest\t-\tlibrary_cluster\t90",
"runtime\tdart-sdk\t-\tsnapshot_header\t70"
]
}
}
+29
View File
@@ -0,0 +1,29 @@
NAME=object-inspect-tagged-android
FILE=-
CMDS=<<EOF
!!../bin/r2flutter -q -j -O 0x1651 bins/android/first 2>/dev/null | python3 -c 'import json, sys; j = json.load (sys.stdin); o = j["value"]["object"]; s = o["string"]; print ("%s addr=0x%x cid=%d len=%d value=%s" % (j["kind"], o["addr"], o["cid"], s["length"], s["value"]))'
EOF
EXPECT=<<EOF
object addr=0x1650 cid=93 len=11 value=_Uint16List
EOF
RUN
NAME=object-inspect-pp-mafia
FILE=-
CMDS=<<EOF
!!../bin/r2flutter -q -j -O pp+0x68 bins/android/mafia 2>/dev/null | python3 -c 'import json, sys; j = json.load (sys.stdin); p = j["pp_slot"]; v = j["value"]; print ("%s index=%d type=%s kind=%s cid=%d name=%s decoded=%s" % (j["kind"], p["entry_index"], p["type"], p["resolved_kind"], p["resolved_cid"], p["resolved_name"], v["decoded"]))'
EOF
EXPECT=<<EOF
object_pool_ref index=11 type=tagged_object kind=string cid=93 name=getCallbackHandle decoded=False
EOF
RUN
NAME=object-inspect-plugin-android
FILE=-
CMDS=<<EOF
!!sh -c "p=../src/r2/core_flutter.dylib; [ -f \"\$p\" ] || p=../src/r2/core_flutter.so; r2 -NN -q -i \"\$p\" -qc \"r2flutter -q -O 0x1651\" bins/android/first/libapp.so 2>/dev/null"
EOF
EXPECT=<<EOF
_Uint16List
EOF
RUN
+26
View File
@@ -0,0 +1,26 @@
NAME=sbom-synthetic
FILE=-
CMDS=<<EOF
!!../bin/r2flutter -S bins/synthetic/field_snapshot.bin 2>/dev/null | sed -n '1,8p'
EOF
EXPECT=<<EOF
# Dart/Flutter Recovered Components
# complete: false
# versions: explicit metadata only; AOT snapshots usually do not serialize pub package versions
snapshot_hash: 00000000000000000000000000000000
dart_version: unknown
components: 2
dart-package test - confidence=90 source=library_cluster occurrences=3 evidence=package:test
runtime dart-sdk - confidence=70 source=snapshot_header evidence=snapshot_hash=00000000000000000000000000000000
EOF
RUN
NAME=json-sbom-synthetic
FILE=-
CMDS=<<EOF
!!../bin/r2flutter -jS bins/synthetic/field_snapshot.bin 2>/dev/null
EOF
EXPECT=<<EOF
{"format":"r2flutter-recovered-sbom","complete":false,"note":"Best-effort component report. Dart package versions are null unless explicit packaged metadata is present.","input":"bins/synthetic/field_snapshot.bin","snapshot":{"hash":"00000000000000000000000000000000","dart_version":"unknown","vm_data":4,"iso_data":4},"count":2,"components":[{"type":"dart-package","name":"test","version":null,"confidence":90,"occurrences":3,"source":"library_cluster","evidence":"package:test"},{"type":"runtime","name":"dart-sdk","version":null,"confidence":70,"occurrences":1,"source":"snapshot_header","evidence":"snapshot_hash=00000000000000000000000000000000"}]}
EOF
RUN