125 Commits
Author SHA1 Message Date
pancake 02b0b84fa6 Release 0.6.0 2026-11-06 14:03:14 +01:00
pancake 0ee5e01498 Give duplicate Dart method names unique r2 flags and function names
r2 flag names are unique, so flagging a second function with a repeated
Dart name (e.g. Native._anon_closure) moved the flag away from the first
one, and current r2's af then named it after the Mach-O sym.func.* flag.
Suffix repeats with their address, use the same names for afs, and rename
analyzed functions after the af pass.

Also skip var recovery, call-following and jump tables in the signature
af pass (AuthPass -AAA 47s -> 29s) and raise R2R_TIMEOUT to 60s.
2026-11-06 13:54:07 +01:00
pancake cc338995cf Update mafia -z goldens for VM base-object pool strings 2026-11-06 13:37:57 +01:00
pancake 8a9e54be29 Support RVec fcn->bbs on R2_ABIVERSION >= 168 2026-11-05 12:57:12 +01:00
pancake 441d614937 Scan complete instruction ranges for standalone xrefs 2026-10-21 12:47:42 +02:00
pancake 25a5b3f026 Decode Record fields in snapshot value graphs 2026-10-21 12:46:56 +02:00
pancake 3bfdd5601a Resolve direct ObjectPool Code targets 2026-10-21 12:20:58 +02:00
pancake 84a25ea38c Resolve ObjectPool Field names and owners 2026-10-21 12:12:31 +02:00
pancake 2bf613618f Propagate ObjectPool collection strings into analysis 2026-10-21 10:52:18 +02:00
pancake a2fd82106e Include ObjectPool instance strings in -z output 2026-10-21 10:39:11 +02:00
pancake bd6f596279 Fix ObjectPool string xrefs through instance fields 2026-10-21 09:32:21 +02:00
pancake 7be0c178fb Release 0.5.1 2026-10-16 03:04:52 +02:00
pancake 3552d7485c Update class metadata golden for quoted pf output 2026-10-16 03:04:40 +02:00
pancake 878a588adf Release 0.5.0 2026-10-16 00:26:17 +02:00
pancake d0215dbfc2 Use r2-6.2.4 2026-10-16 00:26:09 +02:00
pancake 3cef0ce07a Fix signed 64-bit varint decoding
cs_read_tagged64 biased the final byte by -64 instead of -192, adding
128 << shift to every Mint/Smi value, which inflated field offsets and
decoded constants.
2026-10-09 16:05:25 +02:00
pancake 7d019ea748 Resolve class method entry points
Map each Function's code index (a raw InstructionTable slot, including
discarded-Code slots) to its entry address. Class-derived names only
replace placeholder names when merged into the function list, so
InstructionTable names stay authoritative for deduplicated code.
2026-10-09 16:05:23 +02:00
pancake f7c6cc5b3d Extract class metadata from Dart 2.14-3.3 snapshots
modern_can_extract_classes only accepted OBJECT_HEADER (3.4+) snapshots,
so CID_SHIFT1 apps such as Dart 2.19 fell back to the string heuristic
after the legacy parser failed. Gate on modern_supported instead.
2026-10-09 16:05:20 +02:00
pancake 24e3502737 Complete code xrefs for Dart 2.19 2026-10-06 17:50:40 +02:00
pancake 78719df43e Fix Dart 2.19 ObjectPool xrefs after Type fill parsing 2026-10-06 17:30:45 +02:00
pancake 3fe3c11bac Register the r2flutter pkg 2026-10-06 16:55:12 +02:00
pancake fa854fb648 Release 0.4.0 2026-10-06 00:43:14 +02:00
pancake a9efd1d262 doc: record x86-64 Dart AOT scan support 2026-10-02 12:33:45 +02:00
pancake 01db9e6b54 Seed anal.gp with the right PP register per arch
r2flutter_setup_pp_gp now sets anal.gpseed/anal.roregs to r15 on x86-64 and
x27 on arm64, so plain-r2 ESIL resolves [PP, off] pool loads on both.
2026-10-02 12:33:43 +02:00
pancake abcdbb2b11 Resolve x86-64 pool slots by adding back the heap tag
On x86-64 the pool pointer r15 carries the heap tag, so a [r15, off]
instruction offset is one less than the collector/arm64 slot offset. Detect
x64 from asm.arch/bits and add the tag (+1) back to get the canonical slot
offset used for the string/ref map lookups and the pool-slot address.

This makes the whole chain line up on x64: the code->pool-slot xref now lands
on the same slot the data-image string ref targets, pool strings resolve
inline, and a real code->string xref is emitted. On the Android x86_64 app
axt 0x76a97 now shows 'method.DioManager.initParams ... mov rax, [r15 +
str.https:__pss.aakredit.in_]' with the value inlined; resolved strings went
1455 -> 7411. arm64 is unchanged (slot_off == pp_off).
2026-10-02 12:33:40 +02:00
pancake 05ebbd2c2d Detect x86-64 pool loads and xref the pool slot from code
- flutter_reg_is_pp now recognises r15 (x64 PP) alongside x27 (arm64).
- Reclassify x86-64 'mov reg,[mem]' / 'mov [mem],reg' (typed MOV by r2) as
  LOAD/STORE via an effective op type, so the pool-load logic runs on x64.
- Emit a code->pool-slot xref (pp_base + off) on every pool load, for both
  arches, so axt on a slot shows the loading code - the 'link code to the PP
  table' that was missing.
- Drop the (pp_off & 7) alignment guard: it assumed arm64's even PP base and
  rejected x64's tagged-base odd offsets, and the has_pp_slot fix already
  prevents the field-deref it guarded against (arm64 still shows 0 misaligned).

x64 (an Android x86_64 app) went from 0 to 36467 PP refs; arm64 is unchanged
(w0rdle still 27388 PP refs, 0 misaligned).
2026-10-02 12:33:37 +02:00
pancake cd90ffe555 Recognize x86-64 registers and operand syntax in the scan
Add an x86-64 general-purpose register name->slot mapping (rax..r15, 32-bit
aliases) to flutter_reg_index and accept the x64 '[reg + disp]' memory operand
form alongside arm64's '[reg, disp]'. Foundation for scanning x64 Dart AOT
snapshots; arm64 behaviour is unchanged (names never collide).
2026-10-02 12:33:35 +02:00
pancake 05c0616b37 Add r2flutter.str options to configure string parsing limits 2026-10-01 17:41:24 +02:00
pancake 49f174f3cd Fix extras tests for tiered -A/-AA/-AAA and the analysis log line
- r2flutter-help: the help now lists -A/-AA/-AAA separately instead of
  -A[AA], so update both expected copies.
- analysis: run -qAAA and drop the EXPECT_ERR count line. The summary now
  carries the graph-xref count and the x22 fix changed the PP-ref total, and
  those counts also drift with the r2 build; the version-independent
  dart.class flag list remains the real assertion.
2026-10-01 17:01:12 +02:00
pancake cfb6448172 Dartpool refs moved into -AA and speedup AAA 2026-10-01 16:46:58 +02:00
pancake b7f425ca97 Apply dart pool refs to actual data strings with -AAA 2026-10-01 16:34:11 +02:00
pancake db30ebcb55 Annotate NULL_REG (x22) null/true/false idioms in -AAA
x22 caches Object::null() program-wide; the VM places null/true/false as
adjacent heap objects, so the compiler materialises booleans with add off
NULL_REG (true = null+0x20, false = null+0x30 on arm64, constant across Dart
versions and pointer compression). The scan now comments add x22,#0x20/#0x30
as true/false, mov from x22 as null, and compares against x22 as null checks -
the primitive the certificate-pinning bypass keys on. Verified on iOS
(cws=8) and Android (cws=4).
2026-10-01 14:30:49 +02:00
pancake 5651efc492 Expose the ObjectPool as anal.gp in r2flutter-A
x27/PP is the only Dart reserved register with a static address (the pool is
in the snapshot). r2flutter -A now seeds anal.gp with the pool base,
anal.gpseed=x27, anal.roregs=x27,gp,zero and an 'f PP' flag, so plain-r2
analysis/ESIL resolves [x27, off] pool loads - matching what -p -r already
set up. Resolution runs on a shallow DartCtx copy so the per-command read
cache (ctx->rmem) is not shared into the analysis DartApp and double-freed.
2026-10-01 14:30:46 +02:00
pancake a6fc7e4829 Fix PP mis-identification: a loaded pool value is not a PP pointer
The tracker marked the destination of a pool load as a PP pointer, so a later
[dst, field] deref was read as another slot: ldr x30,[x27,0x18] (a Code
object) then ldur x30,[x30,7] (entry-point) produced a bogus PP slot +0x1f,
and the blr reported 'indirect call via PP+0x1f'. Now:
- pp_load tags the dest with has_pp_slot/pp_slot (the slot it came from),
  never has_pp_off;
- flutter_process_pp_slot_deref carries that taint across the entry-point
  deref so the blr names the real slot and can xref it;
- a hard guard rejects any pp_off & 7, since valid slots are always
  word-aligned ((index+2)*8), killing every misaligned +0x..7 bogus comment.
2026-10-01 12:23:36 +02:00
pancake d0467e4e41 Resolve PP-slot comments to kind/name and add xrefs
After -AAA, non-string PP slots were left as bare 'dart: PP slot +0x..'
comments even though -O resolves them by hand. Resolve every slot up front
with modern_collect_direct_pool_refs and, in the analysis:
- inline the resolved name for function/class slots,
- add a navigable xref to the entrypoint for code/function slots (CALL for
  blr, DATA otherwise), mapping code_index through the instruction table,
- label every remaining slot by its kind, so none stays a bare offset.
2026-10-01 12:23:31 +02:00
pancake f35d5b569d Add modern_collect_direct_pool_refs batch pool resolver
Single-pass counterpart of the per-slot -O decode: walk every direct
(tagged, snapshotable) ObjectPool entry once and report its resolved
kind/name/cid/code_index via modern_resolve_pool_entry, the same resolver -O
uses. Non-object entries (native stubs, reset/bootstrap behaviors) are
labeled by kind; only plain immediates are skipped. Lets a caller resolve
tens of thousands of PP slots without building a value graph per slot.
2026-10-01 12:23:28 +02:00
pancake 2396599251 Clear dangling ctx->layout in the PP-string loader
dart_ctx_init_layout returns NULL (not an owned layout) when it loads the
layout into the caller's stack tmp buffer, leaving ctx->layout pointing at
that stack. The loader only cleared it when an owned layout came back, so on
return ctx->layout dangled and the next consumer of the shared DartCtx read
through freed stack. Clear it whenever we initialised it, regardless of the
return value.
2026-10-01 12:23:22 +02:00
pancake 7d283902f3 Support pre r2-6.2.4 triplets 2026-09-30 11:32:54 +02:00
pancake 30b8fe76d9 Improve the ci builds to make binary r2pm possible 2026-09-30 11:10:21 +02:00
fatalSecandClaude Opus 5 67935055e5 Speed up -AAA on large snapshots without changing its results
On a 37,863-function Dart 3.13 iOS app `r2flutter -AAA` took 19 minutes
(1,136s). Three costs grew with snapshot size:

- read_mem cached a single 1 MB window. modern_load_rodata_strings
  alternates between a cluster alloc stream and the RO data image
  megabytes away, so each string refilled the window twice (a 1 MB read
  plus the 0xff fill r_io_bank_read_at does first). Keep four windows
  with LRU eviction. Move the cache behind a pointer so DartCtx copies
  (flutter_model_load, dart_recovery_model_load) share buffers together
  with their addresses instead of leaving the original with stale ones,
  and drop a window whose refill failed since the failed read already
  overwrote it. `-f` 12.4s -> 0.5s; -c/-i/-T/-S ~6s -> <0.25s.

- afs stores prototypes through r_anal_save_parsed_type, which calls
  r_anal_remove_parsed_type for each declared type. For an existing name
  that sorts and scans the whole type database although r_type_del has
  already removed every key of a function type. Names repeat often:
  anonymous closures, and names the C parser cuts at ':' (dyn:call is
  stored as `call`). Parse the prototype with r_anal_cparse and
  r_type_del those names first so afs takes its early return. The
  signature pass went from 1,127s to 27s.

- With anal.trycatch (the radare2 6.2.2 default) every af walks all flags
  twice for try.<addr>.catch|filter. After -AAA creates its flags that is
  O(flags) per function. Disable it around r2flutter's af loops when no
  try.* flag exists, which cannot change the result.

-AAA now takes 36s on that app (35s -> 6s on a smaller 3.13 build). A
dump of afl, afs @@F, f, CC and ax after -AAA is byte-identical to the
previous build on both apps (796,194 and 138,032 lines), and so is stderr.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01KmxPHTVwSkr2HTHJHQsord
2026-09-19 00:17:15 +08:00
FatalSec 5c4a51d5fb Fix Dart 3.13 ObjectPool decode: parse early base-object clusters
- Support Dart 3.13 combined VM snapshots
- Add CID kinds and layouts for LocalVarDescriptors, ApiError, and UnwindError
- Fix stream desync causing "PP not resolved" in `r2flutter -p`
- Skip zero-length ObjectPools when resolving PP slots
2026-09-16 07:48:42 +08:00
FatalSec e8bf641f91 Fix CID_SHIFT1 ObjectPool decode: PatchClass has 2 refs on Dart 3.0-3.3 2026-09-13 23:23:07 +08:00
pancake 3e12922ba0 Release 0.3.6 2026-09-08 13:30:40 +02:00
pancake 42b606ae7f Build the debian package in the ci 2026-09-08 13:30:06 +02:00
pancake 507994deda Add dist/debian 2026-09-08 13:26:00 +02:00
pancakeandClaude Opus 5 ce97e96eda Ship the r2 plugin in the windows zip and strip the binaries before packaging
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EUoqrdf1quwjg3MsThyq1B
2026-09-08 11:50:25 +02:00
pancake 2f8343b97b Release 0.3.4 2026-09-08 11:14:04 +02:00
pancake 5394a6f6e6 Use r_str_tok_r in the sbom parser to link on windows 2026-09-08 09:52:56 +02:00
pancake b2d8a4917a Fix the windows build by avoiding posix-only apis and translating the r2 flags for msvc 2026-09-08 09:52:56 +02:00
mikhail-ekzi edd43d6042 Fix PatchClass parsing for CID_SHIFT1 snapshots 2026-09-08 09:13:29 +02:00
pancake 2f53ba301d Release 0.3.2 2026-09-07 21:49:17 +02:00
pancake 116aa82551 Release artifacts for windows linux and mac 2026-09-07 14:04:34 +02:00
pancake d80fa5bd2e Autorelease when pushing numeric tags 2026-09-07 13:58:56 +02:00
pancake ae66842877 Release 0.3.0 2026-09-07 13:01:47 +02:00
pancake e6b9eefae9 Use r2-6.2.2 2026-09-07 01:07:16 +02:00
pancake 5d86dff9fa Update all tests to use commands instead of !! oneliners 2026-09-03 18:09:11 +02:00
pancake 14c1847177 Probe Snapshot Layouts for Unknown Hashes 2026-09-01 16:18:24 +02:00
pancake edd90ba5c7 Retain Snapshot Object Value Graph 2026-09-01 15:36:07 +02:00
pancake a7317c7705 Retain Serialized Class Layout Metadata 2026-09-01 15:20:54 +02:00
pancake 3fe9dff0cc Support Dart 3.13 Single Snapshots 2026-09-01 15:16:12 +02:00
pancake 2e85b70bda Parse Versioned Code Cluster Layouts 2026-09-01 14:58:59 +02:00
pancake b45d487fd3 Handle Versioned ObjectPool Entry Bits 2026-09-01 14:54:37 +02:00
pancake b1994cd98e Add Versioned Snapshot Reference Decoding 2026-09-01 14:51:16 +02:00
pancake 3b9d12efea Parse Version-Specific Snapshot Headers 2026-09-01 14:46:26 +02:00
pancake f5d7bb15f1 List dae in the readme 2026-09-01 14:25:59 +02:00
pancake 66b294ab7c Use r2-6.2.0 in the ci 2026-08-22 12:20:03 +02:00
pancake 5a3da4b149 Add w0rdle test asserting recovered function signatures 2026-08-21 19:47:53 +02:00
pancake 545a2161ca Recover Dart function signatures with receiver and return types 2026-08-21 19:47:53 +02:00
pancake 6bad309eb6 Add w0rdle tests for ObjectPool decode, string/method recovery and header JSON 2026-08-21 18:47:48 +02:00
pancake 283242b724 Assert recovered w0rdle method names and pool strings in tests 2026-08-21 17:28:03 +02:00
pancake 81acc1c5df Decode ObjectPool and recover names/strings for Dart 2.x cws=4 snapshots 2026-08-21 17:28:03 +02:00
pancake 5d745cdd6b Add w0rdle Flutter crackme sample and regression tests 2026-08-21 16:43:11 +02:00
pancake 54c7aed1ba Performance improvement 1.6x for -x 2026-08-14 20:39:18 +02:00
pancakeandClaude Fable 5 75ad39a7cb Add macOS cws=8 class-extraction regression test
The cws=8 class/field extraction (f85bd3b) enabled the macOS OBJECT_HEADER
path but only iOS Runner goldens covered it. dump-classes-macos asserts
real class/field recovery on hello.aot (_Closure size=64, _Uri size=96,
field offsets) and, negatively, that the old size=0 fallback garbage is
gone. Verified as a real anchor: reverting the cws==8 gate fails all six
of its assertions.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 18:25:01 +02:00
pancake 1f36c65729 Use bin.lang=dart instead of repeating that attribute for all 2026-08-14 18:15:06 +02:00
pancake 2e9bb55763 Export class/method/field information for latest r2 2026-08-14 18:06:44 +02:00
pancake f85bd3beae Extract classes and fields from uncompressed (cws=8) snapshots 2026-08-14 10:24:14 +02:00
pancake 791547c855 Updated readme 2026-08-12 17:42:19 +02:00
pancake ef2fd323d7 Fix test with improved output 2026-08-12 16:09:49 +02:00
pancake bea45d8348 Better api names 2026-08-12 15:19:50 +02:00
pancake 43671cfcb3 Fix tests to use only r2 commands 2026-08-12 14:14:17 +02:00
pancake 2db2a74bc7 Fix pp string references on iOS, better perf and more testing 2026-08-12 13:41:45 +02:00
pancake 02efb309a2 Fix ppstring recovery in disasm and add tests 2026-08-12 12:55:44 +02:00
pancake 08001fe45b Tutorial to bypass ssl pinning on iOS/flutter 2026-08-12 12:30:46 +02:00
dependabot[bot] 5d3ffce739 Bump r2 github actions from 2 to 3 2026-08-12 00:14:54 +02:00
pancake b6ea0b8923 Synthetic legacy tags 2026-08-12 00:09:05 +02:00
pancake 540efcf5e1 Fix remaining iOS symbol recovery blockers 2026-08-11 21:07:03 +02:00
pancake 25dc49ef8f Fix -x reading JSON keys that pdj does not emit
* Side effect is 2x speedup
* 3 bugs fixed here affected to 96 instruction window when 28 ops is enough
2026-08-11 19:47:18 +02:00
pancake 6a7e01b0c9 Fix tags reads on iOS 2026-08-11 19:30:45 +02:00
pancake 334c97b88d Resolve the CID-table fallback via the newest profile 2026-08-11 17:13:51 +02:00
pancake f10d30dd6f Replace the fake CID enum with layout-resolved class ids
* Memoize cid table for 100x speedup in -x,-f,-c
* Fix Builtin.TypeName
2026-08-11 16:38:03 +02:00
pancakeandClaude Fable 5 f138b952c9 Fix discovery-stripped-ios test on r2 release builds
The r2 6.1.6 release parses the bare '>' redirect inside the quoted
!!sh -c one-liner and truncates the command, so the test produced no
output on CI while passing against r2 git. Move the pipeline into a
helper script so the r2 command line carries no quotes or redirects.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-11 15:54:30 +02:00
pancake 833f89a5d9 Remove legacy cluster walker 2026-08-11 15:49:26 +02:00
pancake 897a98d24d Unify the tag from cid getter for the 3 possible formats 2026-08-11 14:17:36 +02:00
pancake e9fa905562 Handle read-only clusters in uncompressed Dart snapshots 2026-08-11 14:08:07 +02:00
pancake 33bb19bbe0 Refactor modern snapshot parsing around a shared cluster walker 2026-08-11 13:31:11 +02:00
pancake d11ebc35b9 Fix modern fill rules for shifted Dart CIDs 2026-08-11 12:55:41 +02:00
pancakeandClaude Opus 4.8 5592349d99 Update TODO roadmap: rank by priority, record solved items
Rewrites the roadmap now that four items have landed. Adds a status table
(d387c0b fingerprinting/provenance, 2391781 explicit 3.7-3.12 profiles, cd6b655
discovery hardening, 0716a95 structural instruction location) and records the two
corrected assumptions worth remembering: the CID tables were already correct by
floor across 2.10-3.12, and the kDart* symbols survive strip.

Re-ranks everything P0..P4 by concrete failure instead of the old section order,
driven by a new finding: only 1 of 5 sample binaries (mafia, 3.9.2) exercises the
modern cluster decoder, because dart_modern_is_supported_snapshot() also gates on
compressed_word_size == 4. That makes two items P0 -- the modern_get_fill_spec
rules[] table is keyed on absolute 3.6/3.9-era CIDs and desyncs the fill stream on
Dart 3.4/3.5 compressed snapshots (cid 46 TYPE_ARGUMENTS read as LIBRARY_PREFIX,
89 ARRAY as CONST_SET), invisible to CI for exactly that reason; and widening the
modern decoder to uncompressed snapshots, which currently skips most iOS/macOS
samples. Also notes the new latent P2.3: the CID fallback still uses a literal
3.9.2 while unknown-hash fingerprinting now resolves to 3.12.0.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-11 12:32:38 +02:00
pancakeandClaude Opus 4.8 986e2cb42a Locate Dart instruction images without symbols on stripped Mach-O
The four kDart*Snapshot* symbols are external/global and load-bearing (the engine
resolves them at runtime), so they survive strip; but obfuscators/repackagers can
remove them, leaving only the two data snapshots findable by magic scan
(instruction images carry no magic) and iso_instr=0, which disables IT name
recovery.

Add locate_instr_images_structural(): vm_instr = start of the first executable
section; read the Dart Image header (word0=ImageSize) and chain
iso_instr = align_up(vm_instr + ImageSize, 64). Every step is cross-checked
(plausible headers; the isolate image must end where the first data image
begins), so an unknown/gapped layout degrades to 0 (no IT names) rather than junk
addresses. No-op when symbols already resolved the instructions.

Verified against symbol-derived ground truth: Runner (0x4000/0xe700, full 7671
IT entries recovered) and AuthPass (0x5a80/0xfdc0); the gapped macOS hello.aot
safely falls back to 0. Symbol-present bins unchanged; suite green (95 db/cmd,
3 extras, 35 custom).

Adds scripts/strip_macho_symbols.py (zeroes LC_SYMTAB + dyld export trie, since
strip cannot) and test/db/cmd/discovery-stripped-ios which builds the fixture
from the committed Runner App and asserts structural recovery. Remaining
follow-ups tracked in COMMIT2.md.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-11 12:32:01 +02:00
pancake cd6b655c0c Harden Dart snapshot discovery and drop bogus instruction-blob classification
* Only data snapshots carry the 0xf5f5dcdc magic
* A stray f5f5dcdc byte run could poison vm_instr/iso_instr on symbol-stripped Mach-O
* Preserve symbol-derived addresses instead of letting scan guesses overwrite them
2026-08-11 12:08:50 +02:00
pancake 239178152b Fill in the Dart 3.7-3.12 CID/layout tables 2026-08-11 11:29:36 +02:00
pancake d387c0b15b Harden unknown-hash handling and report version provenance
- Replaces the hardcoded 'v3.9.2 defaults' fallback
- Fix load_layout_from_json to use that profile on the -f/-A decode path for
  unknown hashes, instead of a degenerate zeroed layout (cws=0/alignment=0);
- Adds a version_source field to -H, guessed layout is never exact match now
2026-08-11 11:29:32 +02:00
pancake 19d8b96fc5 Fix build for latest r2 2026-07-23 14:12:33 +02:00
pancake 7e1803998e Fix build with latest r2 from git 2026-07-09 15:07:42 +02:00
pancake 33588f29a7 Make -p (and cluster-stream walkers) fast: cache reads, skip RBin strings
* Make -p (and cluster-stream walkers) fast: cache reads, skip RBin strings

r2flutter -p burned ~100% CPU for several seconds on large libapp.so/App
images. Two bottlenecks were responsible:

1. read_mem() went through the full radare2 IO stack (banks, caches,
   interval trees, mmap seek/lseek) once per byte, because the cluster
   stream decoders read one byte at a time while walking the whole
   modern_parse_cluster_meta stream twice. read_mem() now serves reads from
   a forward-sliding 1 MiB window cached in DartCtx, collapsing millions of
   IO calls into a handful. The cache is scoped to a single command and
   freed in dart_obf_fini. A request is only served when fully contained in
   the window; a failed windowed refill falls back to an exact read, so
   semantics are unchanged.

2. The standalone tool let r_core_bin_load run RBin's whole-file string
   scan (the single largest profile entry) even though r2flutter never uses
   RBin's string list. The CLI now sets bin.strings=false before loading.
   The core plugin path is untouched.

On test/bins/android/mafia/libapp.so (14 MB), -p drops from ~2.6s to
~0.05s (~50x) with byte-identical output across all actions; the shared
read cache also speeds up the heavier -x/-z walkers. Full custom
testsuite (35/35) still passes.
2026-07-07 19:27:31 +02:00
pancake 4eede7cb33 Fix configure behaviour 2026-07-07 18:08:11 +02:00
pancake 48736a2a89 Properly set anal.roregs for x27 2026-07-07 16:15:27 +02:00
pancake f680d5551a Properly compute the pp.vaddr with baddr in mind 2026-07-07 16:09:27 +02:00
pancake 07b0ca1e4e Resolve serialized ObjectPool string xrefs and entrypoint CLI alias 2026-07-07 16:09:23 +02:00
pancake 7f6449b82a Resolve ObjectPool string xrefs from serialized PP data 2026-07-07 15:39:25 +02:00
pancake 3b8516b706 Improve r2flutter plugin help message again 2026-07-07 13:35:41 +02:00
pancake 6b3730c535 Rename r2flutter-E to r2flutter-ie 2026-07-07 13:11:47 +02:00
pancake fd857f5918 Add tests for r2flutter-E (entrypoint) 2026-07-07 12:59:39 +02:00
pancake 2a8d3151e8 Improve r2flutter plugin command parsing 2026-07-07 12:59:22 +02:00
pancake d7ff3c5b78 Fix PP parsing on modern Dart pools 2026-07-06 21:22:30 +02:00
pancake 4886597928 Add -E for entrypoint and instruction-image codemeta 2026-07-06 20:00:31 +02:00
Abhi 8257b88040 Use r2-H instead of pkg-config 2026-07-06 18:49:04 +02:00
pancake bfaf13a013 Use the script to sync with the missing 21 versions of Dart 2026-07-05 20:48:49 +02:00
pancake 5cf313c681 Refactor version metadata: single offsets.json, remove CID fields from DartVerLayout
- Consolidate all version metadata into offsets.json as the single source
  of truth (hashes, version profiles, offset arrays)
- Remove CID fields (cid_class, cid_function, cid_code, cid_string, ...)
  from DartVerLayout struct — all CID lookups now go through dart_cid.c's
  cid_tables[] via dart_cid_get()
- Remove CID fast-path switch from dart_cid_get(); always use cid_tables[]
- Replace l->cid_* references in dart_pool_parse.c and dart_pool_names.c
  with dart_cid_get(l, DART_CID_*) calls
- Remove CID fallback assignments from dart_pick_layout_by_hash()
- Delete obsolete scripts/derive_offsets.py and scripts/gen_offsets.sh
- Add `make gen` target to regenerate C files from offsets.json
- Update doc/learn.md, doc/support.md, INFO.md, and test expectations
2026-07-03 20:33:44 +02:00
pancake 553d583298 Dont remove rconfig vars on fini when building the plugin incore 2026-07-03 18:24:33 +02:00
Abhi 78013b15e1 Add support for Dart 3.11.x and 3.12.x snapshot profiles
- Update snapshot hash mapping for Dart 3.11.5, 3.12.0, and 3.12.1.
- Add CID and layout profiles for the new versions.
- Document that snapshot hashes are shared across patch-release groups.
- Update documentation and test expectations.
2026-06-29 19:13:54 +05:30
pancake 20d18a1694 Add hash for Dart 3.1.3 2026-06-27 19:05:06 +02:00
pancake 06adc8d617 Use R_NEW instead of calloc 2026-06-27 19:03:57 +02:00
pancake a86cf02636 Alphabetically sort the commandline flags 2026-06-25 02:07:43 +02:00
pancake 91c3e94630 Allow to override the DartVersion from cli (-D)
* Useful for hacked binaries and fuzzy analysis for unknown/unsupported builds
2026-06-25 02:04:25 +02:00
167 changed files with 12331 additions and 4933 deletions

No files matched your search

+189 -6
View File
@@ -1,12 +1,14 @@
name: ci
env:
R2V: 6.1.6
R2V: 6.2.4
on:
push:
pull_request:
permissions:
contents: write
jobs:
build-linux24:
@@ -14,7 +16,7 @@ jobs:
steps:
- uses: actions/checkout@v7
- name: Installing radare2 ${{ env.R2V }}
uses: radareorg/github-actions@v2
uses: radareorg/github-actions@v3
with:
version: ${{ env.R2V }}
from-git: false
@@ -26,6 +28,48 @@ jobs:
run: r2r test/db/cmd
- name: Radare Extras Testing
run: r2r test/db/extras
- name: Package r2pm binary
run: make bindist
- name: Verify r2pm binary
run: |
unzip -l r2flutter-${{ env.R2V }}-linux-x86-64.zip
unzip -l r2flutter-${{ env.R2V }}-linux-x86-64.zip | grep -q 'bin/r2flutter$'
unzip -l r2flutter-${{ env.R2V }}-linux-x86-64.zip | grep -q 'plugins/core_flutter.so$'
- name: Upload r2pm binary
uses: actions/upload-artifact@v7
with:
name: bindist-linux-x86-64
path: r2flutter-*.zip
if-no-files-found: error
- name: Package Debian build
run: make -C dist/debian
- name: Verify Debian package
shell: bash
run: |
shopt -s nullglob
packages=(dist/debian/r2flutter_*_amd64.deb)
test "${#packages[@]}" -eq 1
package="${packages[0]}"
version="$(./configure -qV)"
test "$(dpkg-deb --field "${package}" Package)" = r2flutter
test "$(dpkg-deb --field "${package}" Version)" = "${version}"
test "$(dpkg-deb --field "${package}" Architecture)" = amd64
test "$(dpkg-deb --field "${package}" Depends)" = radare2
contents="$(dpkg-deb --contents "${package}")"
printf '%s\n' "${contents}"
grep -E '^-.+ root/root .+ \./usr/local/bin/r2flutter$' <<< "${contents}"
plugin=".$(r2 -H R2_LIBR_PLUGINS)/core_flutter.so"
grep -Fq "${plugin}" <<< "${contents}"
make user-uninstall
sudo dpkg --force-depends --install "${package}"
test "$(r2flutter -V)" = "r2flutter ${version}"
test "$(r2 -q -c 'r2flutter -V' --)" = "r2flutter ${version}"
- name: Upload Debian package
uses: actions/upload-artifact@v7
with:
name: r2flutter-debian-amd64
path: dist/debian/r2flutter_*_amd64.deb
if-no-files-found: error
meson-linux24:
name: Meson build on Linux
@@ -33,9 +77,9 @@ jobs:
steps:
- uses: actions/checkout@v7
- name: Install build dependencies
run: sudo apt-get update -y && sudo apt-get install -y build-essential meson ninja-build pkg-config
run: sudo apt-get update -y && sudo apt-get install -y build-essential meson ninja-build
- name: Installing radare2 ${{ env.R2V }}
uses: radareorg/github-actions@v2
uses: radareorg/github-actions@v3
with:
version: ${{ env.R2V }}
from-git: false
@@ -51,7 +95,7 @@ jobs:
steps:
- uses: actions/checkout@v7
- name: Installing radare2 ${{ env.R2V }}
uses: radareorg/github-actions@v2
uses: radareorg/github-actions@v3
with:
version: ${{ env.R2V }}
from-git: false
@@ -63,6 +107,78 @@ jobs:
run: r2r test/db/cmd
- name: Radare Extras Testing
run: r2r test/db/extras
- name: Package r2pm binary
run: make bindist
- name: Verify r2pm binary
run: |
unzip -l r2flutter-${{ env.R2V }}-darwin-arm-64.zip
unzip -l r2flutter-${{ env.R2V }}-darwin-arm-64.zip | grep -q 'bin/r2flutter$'
unzip -l r2flutter-${{ env.R2V }}-darwin-arm-64.zip | grep -q 'plugins/core_flutter.dylib$'
- name: Upload r2pm binary
uses: actions/upload-artifact@v7
with:
name: bindist-darwin-arm-64
path: r2flutter-*.zip
if-no-files-found: error
build-linux-arm:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v7
- name: Installing radare2 ${{ env.R2V }}
uses: radareorg/github-actions@v3
with:
version: ${{ env.R2V }}
from-git: false
- name: Building r2flutter
run: make && make user-install
- name: Basic Testing
run: make test
- name: Package r2pm binary
run: make bindist
- name: Upload r2pm binary
uses: actions/upload-artifact@v7
with:
name: bindist-linux-arm-64
path: r2flutter-*.zip
if-no-files-found: error
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: 3.14.x
- name: Prepare MSVC toolchain
uses: ilammy/msvc-dev-cmd@v1
with:
arch: x64
- name: Install build tools
shell: pwsh
run: python -m pip install meson ninja
- name: Install radare2 ${{ env.R2V }}
uses: radareorg/github-actions@v3
with:
version: ${{ env.R2V }}
from-git: false
- name: Build and package r2flutter
shell: pwsh
run: ./dist/windows/build.ps1
- name: Verify Windows package
shell: pwsh
run: |
Expand-Archive -Path r2flutter-$env:R2V-windows-x86-64.zip -DestinationPath check-zip -Force
Get-ChildItem -Recurse check-zip | Select-Object -ExpandProperty FullName
$root = 'check-zip'
if (-not (Test-Path "$root/bin/r2flutter.exe")) { throw 'missing r2flutter.exe' }
if (-not (Test-Path "$root/plugins/core_flutter.dll")) { throw 'missing core_flutter.dll' }
- name: Upload Windows package
uses: actions/upload-artifact@v7
with:
name: bindist-windows-x86-64
path: r2flutter-*.zip
if-no-files-found: error
r2-xps:
name: Build radare2 with r2flutter XPS
@@ -72,7 +188,7 @@ jobs:
with:
fetch-depth: 0
- name: Install build dependencies
run: sudo apt-get update -y && sudo apt-get install -y build-essential pkg-config
run: sudo apt-get update -y && sudo apt-get install -y build-essential
- name: Build radare2 from source with r2flutter
run: |
git clone --depth=1 https://github.com/radareorg/radare2.git
@@ -87,3 +203,70 @@ jobs:
run: |
r2 -q -c 'Lc~r2flutter' -- | grep r2flutter
r2 -q -c 'r2flutter -V' -- | grep 'r2flutter '
check-release:
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }}
needs:
- build-linux24
- meson-linux24
- build-macos
- build-linux-arm
- build-windows
- r2-xps
runs-on: ubuntu-24.04
outputs:
is_release: ${{ steps.tag.outputs.is_release }}
steps:
- name: Check release tag
id: tag
shell: bash
run: |
if [[ "${GITHUB_REF_NAME}" =~ ^[0-9] ]]; then
echo "is_release=yes" >> "$GITHUB_OUTPUT"
else
echo "is_release=no" >> "$GITHUB_OUTPUT"
fi
release:
if: ${{ needs.check-release.outputs.is_release == 'yes' }}
needs: check-release
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Prepare release notes
shell: bash
run: |
tag="${GITHUB_REF_NAME}"
previous_tag="$(git tag --merged "${tag}^" --sort=-version:refname | awk '/^[0-9]/{print; exit}')"
{
echo "# ${tag}"
echo
if [ -n "${previous_tag}" ]; then
echo "## Changes since ${previous_tag}"
echo
git log --pretty=format:'- %s (%h)' "${previous_tag}..${tag}"
else
echo "## Changes"
echo
git log --pretty=format:'- %s (%h)' "${tag}"
fi
echo
} > RELEASE_NOTES.md
- name: Download release artifacts
uses: actions/download-artifact@v8
with:
path: release-artifacts
- name: Create GitHub release
uses: softprops/action-gh-release@v3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
body_path: RELEASE_NOTES.md
generate_release_notes: false
files: |
release-artifacts/r2flutter-debian-amd64/r2flutter_*_amd64.deb
release-artifacts/bindist-*/r2flutter-*.zip
+1
View File
@@ -6,3 +6,4 @@ bin
*.so
*.dylib
*.swp
/r2flutter-*.zip
+3 -2
View File
@@ -4,8 +4,9 @@ Scope
- This document is for agents working in this repo to implement and iterate on the Dart AOT snapshot/ObjectPool parser and CLI.
Build
- Requires radare2 dev headers (`r_core`) and `pkg-config`.
- Build with `make` (uses pkg-config to resolve r2 includes/libs).
- Requires radare2 (`r2` binary in PATH) to resolve include/link flags via `r2 -H`.
- Do NOT use `pkg-config` — the build system uses `r2 -H R2_CFLAGS` and `r2 -H R2_LDFLAGS` instead.
- Build with `make` (uses `r2 -H` to resolve r2 includes/libs).
- Do not build with gcc oneliners, always use `make`
- Run `make fmt` to format/indent the source code
- To update the version change it in configure.acr and then run autogen.sh
+14 -13
View File
@@ -4,7 +4,7 @@ Overview
- Goal: Extract real Dart function names and entrypoints from Flutter/Dart AOT snapshots (libapp.so) without relying on afl/aflj or the Dart VM.
- Tools:
- r2flutter: Small C utility that loads `libapp.so` with radare2, locates AOT snapshots, and decodes the ObjectPool to emit names + addresses.
- scripts/gen_offsets.sh: Generates a per-snapshot-hash offsets file (`r2flutter/offsets.json`) to keep the decoder stable across Dart versions.
- scripts/update-dart-version: Unified script that pulls the Dart SDK, computes the snapshot hash, extracts CID values, and updates all tables.
- Key property: r2flutter builds standalone (no Dart VM headers/libs) and does not parse afl output.
Repo Layout (relevant parts)
@@ -13,18 +13,18 @@ Repo Layout (relevant parts)
- dart_app.[ch]: Minimal app model; wires callback for extracted functions; emits addNames.r2/r2_dart_struct.h.
- dart_pool_parse.c: Snapshot discovery (via r2), offsets loading (offsets.json), and clustered snapshot/ObjectPool decoder (constant pool → names).
- dart_dumper.[ch]: Emits radare2 flags/comments, PP (x27) helper flags, and a basic struct header.
- Makefile: Pure-C build; links against libr2 via pkg-config; no Dart VM linkage.
- Makefile: Pure-C build; links against libr2 via `r2 -H`; no Dart VM linkage.
- third_party/dartvm/: Vendored VM sources (headers only) to document snapshot/cluster formats used by the decoder (no linking).
- arm64-v8a/: Example directory with libapp.so/libflutter.so for testing.
- scripts/
- gen_offsets.sh: Generates `r2flutter/offsets.json` keyed by snapshot hash using only local binaries + curl (when needed for future enhancement).
- update-dart-version: Unified script to add new Dart versions (pulls SDK, computes hash, extracts CIDs, updates all tables).
- blutter/: Standalone C++ reference implementation (with VM linkage) used as conceptual reference. r2flutter must not depend on it.
Quick Start
- Requirements: radare2 development headers (pkg-config), make, a libapp.so and libflutter.so pair.
- Requirements: radare2 (`r2` in PATH for `r2 -H` flag resolution), make, a libapp.so and libflutter.so pair.
- Generate offsets for your libapp:
- `./scripts/gen_offsets.sh r2flutter/arm64-v8a`
- This produces `r2flutter/offsets.json` keyed by snapshot hash (read from VM snapshot data in libapp.so).
- `./scripts/update-dart-version --hash <snapshot-hash> <dart-version>`
- This produces `r2flutter/offsets.json` keyed by snapshot hash.
- Build and run r2flutter:
- `make -C r2flutter`
- `./bin/r2flutter r2flutter/arm64-v8a r2flutter/out`
@@ -59,13 +59,14 @@ Usage in radare2 sessions (Android ELF and iOS Mach-O)
- You’ll see `method.*` flags and comments across the binary.
Maintaining for New Dart Versions
1) Generate/Update offsets.json:
- Run: `./scripts/gen_offsets.sh <dir-with-libapp+libflutter>`
- This produces a new `hash → { compressed_word_size, heap_object_tag }` entry in `r2flutter/offsets.json`.
- For deeper stability (field offsets), enhance `gen_offsets.sh` to:
- `curl` the VM sources for the detected Dart version/commit (e.g., class_id.h, object.h, app_snapshot.cc).
- Parse constants and offsets to compute a richer layout JSON.
- Commit the updated offsets.json.
1) Add a new Dart version:
- Run: `./scripts/update-dart-version <dart-version>`
- This pulls the Dart SDK from GitHub, computes the snapshot hash (MD5 of VM source files),
extracts CID values from class_id.h, and updates both `offsets.json` and `src/lib/dart_version.c`.
- For a local SDK checkout: `./scripts/update-dart-version --sdk-dir /path/to/dart-sdk <version>`
- To add a hash manually (no SDK pull): `./scripts/update-dart-version --hash <md5> <version>`
- To regenerate all tables from known data: `./scripts/update-dart-version --regenerate`
- Commit the updated offsets.json and dart_version.c.
2) Decoder tuning:
- If the clustered snapshot changes (e.g., new EntryType or field order), adjust the decoder logic in `dart_pool_parse.c` referencing `third_party/dartvm/` sources.
- Avoid adding a runtime dependency: keep vendored snapshots sources only as references (no linking).
+34 -4
View File
@@ -1,13 +1,15 @@
CC ?= gcc
R2_CFLAGS ?= $(shell r2 -H R2_CFLAGS)
R2_LDFLAGS ?= $(shell r2 -H R2_LDFLAGS)
R2_LIBS ?= $(shell r2 -H R2_LIBS)
CFLAGS ?= -Wall -Wextra -O2 -fPIC
CFLAGS += -Iinclude -Iinclude/r2flutter
CFLAGS += $(R2_CFLAGS)
LDFLAGS += $(R2_LDFLAGS)
LDLIBS += $(R2_LIBS)
DEPFLAGS = -MMD -MP
R2R_JOBS ?= 1
R2R_TIMEOUT ?= 30
R2R_TIMEOUT ?= 60
R2_LIBR_PLUGINS=$(shell r2 -H R2_LIBR_PLUGINS 2> /dev/null)
R2_LIBEXT=$(shell r2 -H R2_LIBEXT 2> /dev/null)
-include config.mk
@@ -32,10 +34,14 @@ DEP_FILES = $(LIB_OBJ:.o=.d) $(MAIN_OBJ:.o=.d) $(ANALYSIS_OBJ:.o=.d)
# Artifacts
BIN_FILE = $(BIN_DIR)/r2flutter
STATIC_LIB = $(BUILD_DIR)/libr2flutter.a
INC_VER = $(SRC_DIR)/../include/r2flutter/r2flutter_version.h
INC_VER = include/r2flutter/r2flutter_version.h
all: $(BIN_FILE) $(INC_VER)
# Regenerate C files from offsets.json (the single source of truth)
gen:
python3 scripts/update-dart-version --generate
$(INC_VER):
./configure
@@ -43,7 +49,7 @@ $(STATIC_LIB): $(LIB_OBJ)
ar rcs $@ $^
$(BIN_FILE): $(STATIC_LIB) $(MAIN_OBJ) $(ANALYSIS_OBJ) | $(shell mkdir -p $(BIN_DIR))
$(CC) $(CFLAGS) -o $@ $(MAIN_OBJ) $(ANALYSIS_OBJ) -L$(BUILD_DIR) -lr2flutter $(LDFLAGS) -Wl,-rpath,$(PREFIX)/lib -g
$(CC) $(CFLAGS) -o $@ $(MAIN_OBJ) $(ANALYSIS_OBJ) -L$(BUILD_DIR) -lr2flutter $(LDFLAGS) $(LDLIBS) -Wl,-rpath,$(PREFIX)/lib -g
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
@mkdir -p $(dir $@)
@@ -67,6 +73,30 @@ user-install user-uninstall:
$(MAKE) -C src/r2
$(MAKE) -C src/r2 $@
# Binary package for r2pm -bi: bin/ and plugins/ for this r2 version and platform
# r2 <= 6.2.2 lacks r2pm -H R2PM_TRIPLET, fallback to the host asm.* defaults
PKG_TRIPLET := $(or $(shell r2pm -H R2PM_TRIPLET 2> /dev/null),$(shell r2 -N -qc '?e `e asm.os`-`e asm.arch`-`e asm.bits`' -- 2> /dev/null))
BINDIST_ZIP = r2flutter-$(shell r2 -qv)-$(PKG_TRIPLET).zip
BINDIST_DIR = $(BUILD_DIR)/bindist
BINDIST_FILES = $(BINDIST_DIR)/bin/r2flutter $(BINDIST_DIR)/plugins/core_flutter.$(R2_LIBEXT)
STRIP ?= strip
bindist: $(BIN_FILE)
$(MAKE) $(STATIC_LIB)
$(MAKE) -C src/r2
rm -rf $(BINDIST_DIR) $(BINDIST_ZIP)
mkdir -p $(BINDIST_DIR)/bin $(BINDIST_DIR)/plugins
cp -f $(BIN_FILE) $(BINDIST_DIR)/bin/
cp -f src/r2/core_flutter.$(R2_LIBEXT) $(BINDIST_DIR)/plugins/
cp -f dist/README.md $(BINDIST_DIR)/
ifeq ($(R2_LIBEXT),dylib)
$(STRIP) -x $(BINDIST_FILES)
codesign -f -s - $(BINDIST_FILES)
else
$(STRIP) --strip-unneeded $(BINDIST_FILES)
endif
cd $(BINDIST_DIR) && zip -r $(CURDIR)/$(BINDIST_ZIP) README.md bin plugins
install: uninstall
mkdir -p "$(DESTDIR)$(PREFIX)/bin"
cp -f bin/r2flutter "$(DESTDIR)$(PREFIX)/bin/r2flutter"
@@ -99,6 +129,6 @@ test: $(BIN_FILE)
@echo "Running custom testsuite"
@python3 scripts/run_tests.py
.PHONY: all clean test test-r2r r2 plugin user-install user-uninstall fmt indent format
.PHONY: all clean test test-r2r r2 plugin user-install user-uninstall fmt indent format bindist
-include $(DEP_FILES)
+89 -110
View File
@@ -1,144 +1,123 @@
# r2flutter - Dart/Flutter support for Radare2
# r2flutter
<p>
<img border="0" align="left" width="125px" height="125px" src="doc/images/r2flutter500.png" />
<p align="center">
<img width="160" src="doc/images/r2flutter500.png" alt="r2flutter logo" />
</p>
[![ci](https://github.com/trufae/r2flutter/actions/workflows/ci.yml/badge.svg)](https://github.com/trufae/r2flutter/actions/workflows/ci.yml)
<br />
The default parser uses radare2 for binary loading but does not rely on existing
r2 analysis (`afl`/`aflj`); Dart-aware code analysis is opt-in with `-AAA`. The
Dart parser is self-contained C code (no Dart VM headers or libs) and reads AOT
snapshots, data images, clusters, ObjectPool records, and instruction tables
directly.
</p>
**r2flutter brings Dart and Flutter AOT awareness to [radare2](https://rada.re/).**
It reads the snapshots embedded in release builds and turns their metadata into
useful names, addresses, strings, classes, and references for reverse
engineering. It is available both as the `bin/r2flutter` command-line tool and
as an `r2flutter` command inside radare2.
## Features
Give it an extracted Android `libapp.so`, an Android directory containing one,
an iOS `.app` bundle, or a direct AOT binary. AArch64 is the primary analysis
target. The parser has in-tree layouts for Dart 2.10 through 3.12; see the
[support matrix](doc/support.md) for platform and version details.
- Support iOS and Android Flutter apps
- Accepts direct binaries, Android directories with `libapp.so`, and iOS `.app`
bundles with `Frameworks/App.framework/App`
- Derives names/addresses from Dart snapshots, data images, clusters,
ObjectPool records, and instruction tables
- Recovers snapshot headers, functions, classes, fields, string refs, xrefs,
reconstructed ObjectPool PP data, and best-effort SBOM/component reports
- Uses radare2 for binary loading while keeping the Dart parser self-contained
- Prints text, JSON, or radare2 command output for supported dump actions
## What it can do
## Supported Dart/Flutter Versions
- Find Dart AOT snapshots in Flutter apps and supported standalone Dart
containers.
- Recover snapshot headers, functions, classes, fields, type names, strings,
ObjectPool values, instruction-table entries, and metadata references.
- Apply recovered names, flags, comments, classes, and code references to a
radare2 session.
- Emit readable text, JSON for automation, or radare2 commands for importing
into another session.
- Use Flutter's `--save-obfuscation-map` output to restore recovered names.
r2flutter supports Dart AOT snapshots from **Dart 2.10.0 to 3.10.7** (Flutter 1.22.x to 3.38.x).
r2flutter is a metadata and analysis aid, not a Dart source-code decompiler.
Recovery depends on what survived in the AOT snapshot, so some names and
references are necessarily best effort.
The detailed support matrix, including tag-style changes and Android/iOS-specific notes, lives in [doc/support.md](doc/support.md).
## Build and install
Unknown snapshot hashes default to the v3.9.2 profile with ObjectHeader tag encoding.
You need `radare2` 6.2.2 or newer (or a git build of 6.2.1), including its
development headers, and `r2` must be on `PATH`. The build obtains the
required flags from `r2 -H`. Older radare2 versions lack the `ic+` attribute
support used to register class metadata and are rejected at compile time.
## Building
This project can be used from the system shell or directly integrated inside radare2:
```bash
```sh
git clone https://github.com/trufae/r2flutter.git
cd r2flutter
make
```
To get the plugin inside your r2 shell do that:
The command-line tool is now ready at `bin/r2flutter`.
```bash
To install the radare2 plugin for your user account:
```sh
make user-install
```
## Usage
For a system-wide command-line tool and plugin, build the plugin first and
then use the usual install prefix:
```bash
Usage: bin/r2flutter [options] <libapp_path_or_dir>
Modifiers:
-h Show help
-j Output in JSON format
-q Compact output; suppress non-essential detail
-r Output r2 commands for the selected action
-n Heuristic fallback for unknown functions; may assign wrong names
-v Verbose (stderr debug info)
-vv More verbose (dump headers)
-V Show version
Actions:
-A Analyze Dart snapshot and apply flags/comments
-AA Analyze with field extraction enabled
-AAA Run Dart-aware code analysis and recover code refs
-c Print extracted class information
-f Print all extracted functions (addr name)
-H Print Dart AOT snapshot header info
-HH Print extended snapshot header and cluster layout
-HHH Decode selected cluster payloads for diagnostics
-i Print instruction table entries to stdout
-O <addr|pp+off> Decode Dart tagged/object pointer or ObjectPool PP slot
-p Print reconstructed ObjectPool PP value
-R Print radare2 script for snapshot analysis
-S Print best-effort recovered SBOM/components
-T Print string-based type names
-x Print metadata/data-image xrefs; with -z, include string refs/ax in -r
-z Print reliable ObjectPool-referenced strings (-q prints values only)
-zz Print all fuzzy/carved extracted strings (-xzz includes refs/ax in -r)
Options:
-l <N> Limit output to N items
-m <file> Load Flutter obfuscation map JSON
```sh
make r2
sudo make install
```
`bin/r2flutter` runs one action per invocation. Directory inputs resolve Android
first (`libapp.so`), then iOS (`Frameworks/App.framework/App`).
## First look at an app
`-f` and the `-A` analysis flow skip loader-provided ELF/Mach-O stub symbols; radare2 already gets those from `RBin`, so `r2flutter` stays focused on Dart-derived metadata.
Start with the snapshot header. It confirms that r2flutter found a Dart AOT
image and shows the detected Dart profile:
`-m` consumes the JSON array emitted by Flutter/Dart `--save-obfuscation-map`. r2flutter inverts that mapping and applies it to recovered functions, instruction-table names, classes, fields, and method owners.
`-n` is intentionally opt-in. It consumes `package:` and `dart:` strings from the data image as a sequential fallback for otherwise unnamed functions, so it can produce plausible but incorrect names when the string order does not match the instruction table.
`-S` emits a best-effort recovered component report, not a complete dependency
inventory. JSON output uses `format: "r2flutter-recovered-sbom"` and
`complete: false`; package versions are usually `null` unless explicit packaged
metadata such as `pubspec.lock`, `.dart_tool/package_config.json`, or framework
`Info.plist` survived in the input bundle.
Dump actions honor the global format modifiers:
```bash
bin/r2flutter -i test/bins/ios/Runner.app
bin/r2flutter -j -l 16 -i test/bins/ios/Runner.app
bin/r2flutter -r -l 16 -i test/bins/ios/Runner.app
bin/r2flutter -jS test/bins/ios/Runner.app
bin/r2flutter -HHH -l 36 test/bins/android/mafia/libapp.so
bin/r2flutter -O pp+0x68 test/bins/android/mafia/libapp.so
bin/r2flutter -qrz test/bins/ios/Runner.app
bin/r2flutter -rz test/bins/ios/Runner.app
bin/r2flutter -rzz test/bins/ios/Runner.app
```sh
bin/r2flutter -H path/to/libapp.so
```
## radare2 Plugin
Then try a small, practical extraction:
`make user-install` installs the core plugin command as `r2flutter`. Inside r2,
the same modifiers apply to dump actions:
```sh
# Recovered Dart function entrypoints and names
bin/r2flutter -f path/to/libapp.so
```text
r2flutter -A analyze and apply flags/comments
r2flutter -AA analyze with field extraction enabled
r2flutter -AAA run Dart-aware code analysis and recover refs/comments
r2flutter -C apply Dart classes, fields, methods and types
r2flutter -jS print recovered components/SBOM as JSON
r2flutter -r -p map a synthetic ObjectPool and set anal.gp/x27
# A compact JSON header, useful for scripts
bin/r2flutter -jH path/to/libapp.so
# The first 20 instruction-table entries
bin/r2flutter -l 20 -i path/to/libapp.so
```
Plugin config keys:
Directory and bundle inputs work too. For example, pass an Android directory
containing `libapp.so`, or an iOS `Runner.app` bundle directly.
- `r2flutter.mapfile`: Flutter obfuscation map JSON path
- `r2flutter.namepool`: enable heuristic name-pool fallback
After `make user-install`, open the same binary in radare2 and apply the Dart
metadata to the analysis session:
## Dependencies
```sh
r2 -q path/to/libapp.so
> r2flutter -AAA
> afl~method
```
- radare2 (with development headers)
- pkg-config
`-AAA` is the Dart-aware analysis pass. For a concise list of every action and
output modifier, run `bin/r2flutter -h` or `r2flutter -h` inside radare2.
## Other Projects
## Learn more
- https://github.com/dart-lang/sdk
- https://github.com/worawit/blutter
- https://github.com/zboralski/unflutter
- https://github.com/Impact-I/reFlutter
- [Support matrix](doc/support.md) — accepted containers, platform scope, and
Dart layout coverage.
- [AuthPass walkthrough](doc/tuto-authpass.md) — a complete iOS AOT analysis
example using recovered method names in radare2.
- [ObjectPool internals](doc/objpool.md) and [instruction-table entries](doc/its.md)
— the two central structures behind function and constant recovery.
- [Strings](doc/strings.md) and [cross references](doc/xrefs.md) — what can be
recovered and how reliable each result is.
- [Flutter obfuscation maps](doc/obfuscate.md) — restore names from a map
produced during a Flutter build.
## Related projects
- [Dart SDK](https://github.com/dart-lang/sdk)
- [blutter](https://github.com/worawit/blutter)
- [unflutter](https://github.com/zboralski/unflutter)
- [reFlutter](https://github.com/Impact-I/reFlutter)
- [dae](https://github.com/ejfkdev/dae)
Released under the [MIT license](LICENSE.txt).
+307
View File
@@ -0,0 +1,307 @@
# r2flutter — Analysis & Correction Roadmap
Review of the library (`src/lib`), the r2 core plugin (`src/r2`), and the CLI
(`src/tool`) for inconsistent, confusing, duplicated, or wrong assumptions when
analyzing Flutter/Dart AOT snapshots on iOS and Android. Findings are verified
against the source, the sample binaries in `test/bins`, real Dart SDK sources in
`third_party/dart-sdk`, and a locally built Dart 3.12.2 AOT snapshot.
Maintainer priorities: **1) robust Dart-VM version identification** ·
**2) performance** · **3) iOS/Android correctness parity**.
Items are ranked **P0** (do next) → **P3** (nice to have). Severity is implied by
rank; each item states the concrete failure it causes.
---
## Status — what has been fixed (do not redo)
| Commit | What landed |
|--------|-------------|
| `d387c0b` | Unknown-hash **fingerprinting** replaces the blind hardcoded "v3.9.2 defaults" (`dart_newest_profile()` auto-tracks the profile table). `-H` reports **`version_source`** (`exact-hash`/`override`/`fingerprint`/`unknown`) and labels guessed layouts `~<ver>+ (fingerprint)`. Report and decode paths now share the same layout (previously decode used a degenerate zeroed struct). Test: `header-fingerprint`. |
| `2391781` | Explicit **3.7/3.8/3.10/3.11/3.12 CID + layout profiles** (parsed from each release's real `class_id.h`). Also **verified**: the floored tables were already byte-for-byte correct for *every* release 2.10.0–3.12.1, so no CID value changed — the rows are explicit coverage + a regression anchor. |
| `cd6b655` | Discovery hardening: removed the **bogus instruction-blob classification** (only data snapshots carry magic `0xf5f5dcdc`; a stray hit could poison `iso_instr`); symbol-derived addresses are no longer clobbered by scan guesses; `vaddr==0` duplicates can't erase a good address; magic hits deduped; Mach-O segments skipped. |
| `0716a95` | **Structural instruction-image location** for symbol-less Mach-O: `vm_instr` = first executable section, `iso_instr = align_up(vm_instr + ImageSize, 64)` from the Dart Image header, with cross-checks that fall back to `0` rather than emit junk. Verified on stripped Runner (`0x4000`/`0xe700`, full 7671 IT entries) and AuthPass (`0x5a80`/`0xfdc0`). The `discovery-stripped-ios` test clones the mapped thin slice with `o:`, clears symbol/export metadata with `wv4`, and reloads it with `ooi`. |
| `d11ebc3` | **P0.1**: `modern_get_fill_spec`'s fallback `rules[]` is keyed by `DartCidKind` and resolved through a per-layout `by_kind[]` table built once in `modern_cid_cache_init`, instead of literal 3.6-era CIDs. Fixes a whole-block off-by-one on 3.2–3.5 layouts (36 CIDs from `SENTINEL` upward got the neighbouring class's ref/scalar layout) and `MonomorphicSmiableCall`/`UnlinkedCall` on 3.9+, where the two swapped places. Abstract classes that never form a cluster (`CallSiteData`, `AbstractType`, `TypedDataBase`) no longer claim a spec. |
| `33bb19b` | **P2.5**: one shared cluster walker (`ModernWalk`) replaces the five hand-rolled tag→cid→alloc→fill loops. Consumers register readers in a `DartCidKind`-keyed table and the walker skips every class they don't claim, so tag decoding, alloc/fill skipping, `start_ref` bookkeeping and the inline-string decision each exist **once** (were 4–5× each). `modern_walk_fill_recorded` serves the resolver, which reads each cluster from its own recorded offset. Verified behaviour-preserving: output byte-identical on 5 samples × 21 actions. |
| `e9fa905` | **Part of P0.2**: the read-only-data rule (`modern_walk_is_rodata`) — the six classes Dart moves into the data image when pointer compression is off (exactly `Serializer::ReadOnlyObjectType`) now correctly emit no fill records. Inert until the gate opens; see P0.2 for what still blocks it. |
| `897a98d` | **P1.1**: one `dart_cid_from_tags` helper now decodes `CID_INT32`, `CID_SHIFT1`, and `OBJECT_HEADER` tags for object inspection, modern and legacy cluster walks, data-image scanners, and InstructionTable discovery. This removes the duplicate decoders and fixes `CID_INT32` being treated as `OBJECT_HEADER` in two paths. |
| `833f89a` | **P2.1**: deleted the legacy cluster walker — `deserialize_clusters`, `decode_class_cluster`, `decode_function_cluster`, `resolve_names`, the `DartClass`/`DartPoolFunction` types and the `DartCtx.classes`/`.functions` fields that only it wrote (244 lines). It had been running on every `-f`/`-A` alongside the modern scanner while contributing nothing: verified before removal by stubbing it out (50/50 outputs identical), and after by diffing 95 sample×action outputs against the previous commit. `decode_string_cluster`/`skip_generic_cluster`/`free_dart_string` stay — the legacy **class** parser in `dart_pool_classes.c` still uses them for CID_SHIFT1 samples. |
| `f10d30d` | **P2.2**: the fake `kXxxCid` enum is gone. Only 3 of its 28 values (`Class`, `PatchClass`, `Function`) matched any real Dart release, so the legacy class parser decoded Class/Function clusters and silently skipped String/Array/Type/Mint/Double — the ones it needed for names. Its `switch` is now an `if` chain over `LegacyClusterCids`, resolved per layout via `dart_cid_get`. The two sites that fell back to a *fabricated* id when resolution failed (`dart_pool_data_image.c`, `dart_pool_it.c`) now fail closed, and a third raw `kFieldCid` comparison was resolved the same way. `builtin_type_name` keeps working on `Type.type_class_id` — a class id packed in the type flags, not a ref despite the field name — but against real ids. The `ti->kind` discriminator, which was never a snapshot id, became `DartTypeKind`. |
| `f10d30d` | **Perf, found via P2.2**: `cid_table_for_layout` resolved the CID table by comparing version *strings* against every row on **every** `dart_cid_get` call, and that call sits per-object on some paths. A one-entry memo keyed on the layout pointer makes it a pointer compare. Synthetic `-x` 4862 → **37 ms** (131×), mafia `-f` 1008 → 550 ms, mafia `-c` 756 → 312 ms. Output unchanged on all 95 comparisons. This was already the dominant cost *before* P2.2 — more evidence for the P3.1 note that the double cluster walk was the wrong suspect. |
| `251bec7` | **Initial `--dump-xrefs`**: real-binary `-x` tests (`xrefs-android`, `xrefs-ios`) land, so the ObjectPool-use scanner is no longer covered only by the synthetic blob. |
| `25dc49e` | **P3.5 + two `-x` correctness bugs**: `collect_pool_uses_from_entry` read JSON keys `pdj` does not emit. `offset` (real key: `addr`) always returned 0, so *every* pool use fell back to `entry->address` — the reported address was the function start, never the referencing instruction. `opstr` (real keys: `disasm`/`opcode`) was always NULL, making two of the three parser calls duplicates. The fixed 96-op window also ran past short functions (median Dart function is 28 instructions) into their neighbours; it is now bounded by the next entry's address. After the fix 100% of pool uses lie inside the function they are attributed to (before, 100% sat at a function start, which is why the attribution check could not fail). mafia `-x` 7783 → 4738 ms. Also hardens the `dart_cid_get` memo to key on a copy of the version text, not the (freeable) layout pointer. |
| `334c97b` | **P2.3**: `cid_table_for_layout`'s fallback for a layout with no usable version now goes through `dart_newest_profile()` instead of the literal `"3.9.2"`, so it agrees with the baseline the version fingerprinter picks for an unknown hash. Verified a no-op today (the two tables have zero differing CIDs); it stops being one the moment a release shifts the numbering. |
| `540efcf` | **P0.2 largely landed — the modern gate is now open.** `dart_modern_is_supported_snapshot` accepts `cws == 8` **and** `CID_SHIFT1`, not just `cws == 4` / OBJECT_HEADER. The fill desync that blocked this (the roadmap's cluster-12 failure on `hello.aot`) is resolved by `modern_skip_fill_instance` (version-dependent instance fill shape) and `modern_load_rodata_strings`, which reads the six RO-data string classes from the data image where uncompressed builds put them. **Result:** iOS Runner (3.4.3) recovers **6389 method names** where it emitted `func.<addr>` placeholders before; `hello.aot` (3.11.5) recovers 1130/1325. Tests `dump-funcs-ios-runner`/`-authpass`/`dump-it-ios-runner` updated to assert real names. **Still gated:** class/field extraction (`modern_can_extract_classes` keeps `cws == 4`), so `-c` on cws=8 still shows the weak `size=0` fallback — see the trimmed P0.2. |
| `b6ea0b8` | **Synthetic fixture reconciled** with the varint-tag switch. `6a7e01b` correctly made the legacy class parser read cluster tags as varints (Dart writes them with `Read<uint32_t>`, a varint), which broke `bins/synthetic/field_snapshot.bin` — it stores raw LE u32 tags. Rather than re-encode the blob, the parser branches on `synthetic_legacy_parse`: raw u32 for the fixture, varint for real snapshots. All tests green again. The fixture still has no generator (see P4). |
| `02efb30` | **String comments in `-AAA` disasm**: `PP+N` ObjectPool loads that resolve to a string now emit a `; dart: string "…"` comment at the referencing instruction. New test `analysis-string-comments`. |
| `2db2a74` | **`-AAA` PP-string comments work on iOS.** A new direct resolver (`dart_modern_collect_direct_object_pool_string_refs`) resolves ObjectPool slots to string literals straight from the cluster graph, so `flutter_analysis.c` can annotate loads without a disasm round-trip. New test `analysis-string-comments-ios` asserts an `https` string comment on Runner. (Does **not** touch `-x`, which still round-trips through `pdj` — see the P3.1 note.) |
| `bea45d8` | **Public API rename sweep**: `dart_modern_is_supported_snapshot`→`modern_supported`, `dart_ctx_effective_version/_hash`→`dart_ctx_version/_hash`, `dart_pool_enumerate`→`dart_pool_scan`, `dart_pool_dump_pp`→`dump_pool`, `dart_pool_extract_object_pool_strings`→`extract_pool_strings`, `dart_pool_dump_entrypoint`→`dump_entry`. No behaviour change. |
| `43671cf` / `ef2fd32` / `791547c` | Test-only (authpass tutorial uses pure r2 commands; extras/analysis output) and README. No source behaviour change. |
| *(uncommitted)* | **P0.2 second half — class/field extraction on `cws == 8`.** Two changes: `modern_extract_classes` now calls `modern_load_rodata_strings` after the cluster walk (the string classes live in the RO-data image on uncompressed builds, so their names were unresolved — every class came out `class_NNNN size=…`), and `modern_can_extract_classes` drops the `cws == 4` gate. The fill readers already decoded varints and scaled by `compressed_word_size`, so no reader change was needed. **Result** (OBJECT_HEADER cws=8): `hello.aot` 480/501 classes with real names+sizes+fields+methods (was 0), Runner 2233/2254 (was 0). cws=4 byte-identical (mafia unchanged). Graceful fallback preserved where the modern walk can't run: 2.18.2 (`android/first`) still desyncs in fill and AuthPass is `CID_SHIFT1` (class gate still requires OBJECT_HEADER) — both byte-identical to before. Goldens `classes-ios`/`types-ios`/`xrefs-ios` regenerated to the real output. |
Corrections to earlier assumptions, worth remembering:
- **The CID tables are not the problem.** Flooring is exactly correct 2.10→3.12,
verified against SDK sources. Only a *future* CID shift needs action, and the
generator adds a profile automatically when one appears.
- **The `kDart*` snapshot symbols survive `strip`** — they are external/global and
load-bearing (the engine resolves them at runtime). Symbol-less iOS is an edge
case (obfuscators/repackagers), not the norm.
- **The VM snapshot's canonical string cluster carries no canonical-set table**,
unlike the isolate's. Measured on `android/mafia`: skipping one there consumes
the next cluster's bytes and desynchronises the whole walk. This contradicts
the SDK, where `StringDeserializationCluster::ReadAlloc` always calls
`BuildCanonicalSetFromLayout` and both snapshots are read with
`is_non_root_unit=false`. Unexplained; `modern_vm_strings_alloc` encodes the
measured behaviour. Worth revisiting — it may mean the VM cluster range is
being located slightly wrong and the strings we recover are a happy accident.
---
## The finding that reshaped priorities — now largely acted on
Historically **only 1 of 5 sample binaries** reached the "modern" (good) cluster
decoder, because `dart_modern_is_supported_snapshot()` gated on
`tag_style == OBJECT_HEADER && cws == 4`. `540efcf` opened that gate:
| Sample | Dart | tag_style | cws | modern **name** path | modern **class/field** path |
|--------|------|-----------|-----|-----------------|-----------------|
| `android/mafia` | 3.9.2 | OBJECT_HEADER | 4 | **yes** | **yes** |
| `android/first` | 2.18.2 | OBJECT_HEADER | 8 | **yes** | no — fill desync (see below) |
| `ios/Runner.app` | 3.4.3 | OBJECT_HEADER | 8 | **yes** | **yes** (2233/2254) |
| `ios/AuthPass.app` | 3.2.5 | CID_SHIFT1 | 8 | **yes** | no — class gate is OBJECT_HEADER-only |
| `macos/hello.aot` | 3.11.5 | OBJECT_HEADER | 8 | **yes** | **yes** (480/501) |
Name recovery runs on all five; class/field extraction now runs on every
**OBJECT_HEADER** sample regardless of pointer width. Two hold-outs remain, both
falling back gracefully (byte-identical to the old string-scan output):
- **`android/first` (2.18.2)**: the modern fill walk desyncs at cluster 0
(`cid=92`) — a genuinely version-dependent fill shape in a pre-3.0 release, the
same class of problem P0.1 addressed for CIDs but for fill *layout*. This is the
only remaining fill-desync case in the corpus.
- **`ios/AuthPass.app` (3.2.5, CID_SHIFT1)**: `modern_can_extract_classes` still
requires OBJECT_HEADER. The name path handles CID_SHIFT1; extending class
extraction to it is untried.
---
## P0 — correctness bugs with no test coverage
### P0.2 (done for OBJECT_HEADER; two edge cases left)
Class/field extraction on `cws == 8` **landed** (see status table): the gate
dropped and `modern_load_rodata_strings` now runs on the class path. `hello.aot`
and Runner recover real classes, fields and methods. Two follow-ups remain, both
lower-value than the main win and both currently falling back cleanly:
1. **2.18.2 fill desync** (`android/first`). The modern walk fails at cluster 0
(`cid=92`, an `Array`/string cluster in that era). Some pre-3.0 fill shape
differs from `rules[]`. To fix: bisect the first divergent cluster by diffing
per-class `ReadFill` between an old SDK and current (`third_party` has both),
using byte-per-object as the oracle, then make the affected rule version-aware
— same method the roadmap sketched for the (now-resolved) cluster-12 case.
Guard rails: `-c` must stay byte-identical on the working samples.
2. **CID_SHIFT1 class extraction** (`ios/AuthPass.app`, 3.2.5).
`modern_can_extract_classes` requires OBJECT_HEADER; the name path already
handles CID_SHIFT1, so the cluster walk works — the untested part is whether
the class *fill* reader is correct under the shifted tag encoding. Try opening
it and check AuthPass `-c` against a known class list.
### P0.3 Test corpus does not cover the decoders we ship
Progress: iOS **name** recovery on cws=8 is now asserted
(`dump-funcs-ios-runner`/`-authpass`, `dump-it-ios-runner`), and `-x` has
real-binary coverage (`xrefs-android`/`-ios`). Remaining gaps:
- a **Dart 3.4/3.5-era compressed-pointer** app (P0.1 is fixed but still has no
end-to-end regression test; it was verified only by resolving `rules[]` against
each layout in isolation);
- cws=8 class/field extraction is now covered on both paths: iOS (`classes-ios`,
`types-ios` on Runner) and macOS/standalone (`dump-classes-macos` on
`hello.aot`, added alongside this note). The macOS golden is a real regression
anchor — reverting the `cws==8` gate fails all six of its assertions;
- more **iOS** binaries generally, ideally real obfuscated/stripped release
`App`s (also broadens the `0716a95` structural path, per `COMMIT2.md`);
- a **32-bit ARM** app if one can be found (validates P1.2).
The remaining gaps (3.4/3.5-era compressed, obfuscated iOS, 32-bit) all need
apps **collected externally** — the installed Dart SDK is 3.12.2, so a locally
built AOT would exercise the modern decoder but *not* the old CID layout P0.1
corrected, which is the one that still lacks an end-to-end test.
---
## P1 — correctness, lower prevalence or already-guarded
### P1.2 Hardcoded 8-byte target word / arm64 assumptions
- `modern_target_word_size()` (`dart_pool_modern.c:1196-1199`) **ignores `ctx` and
returns 8** — it takes `ctx` only to `(void)` it. All ObjectPool `pp_offset`
math (`:1201-1208`) and the synthetic-PP builder (`:2243-2262`) are wrong on
32-bit ARM, so `PP+N` lookups resolve to the wrong entry.
- Entry-point stride hardcodes `*4` (`dart_pool_clusters.c:164`,
`dart_pool_classes.c:170`, `dart_pool_it.c:349,399`).
- `-AAA` (`src/r2/flutter_analysis.c:104-129`) is entirely arm64 (PP=`x27`,
`x15/x29`, AAPCS64 clobber) with **no arch guard**; on arm32/x64 it emits wrong
PP annotations instead of bailing. Neither frontend checks `asm.arch`/`bits`.
**Fix:** gate on arch, derive the PP register, or refuse with a clear message.
### P1.3 Plugin/CLI frontend drift (AGENTS.md violations)
Two hand-maintained parsers: CLI getopt (`main.c:118`) vs plugin
(`core_flutter.c:192+`).
- **`-r` is missing in the plugin** (it only has `-*`), directly violating
AGENTS.md ("-q, -j and -r flags are modifiers"). `r2flutter -f -r` errors.
- Plugin **silently no-ops under `r2 -n`** (`dart_app_new_from_core` returns NULL
via `R_UNWRAP4(core,bin,cur,file)`), so `-f/-A/-R` do nothing while `-H/-z/-i`
work — and `r2 -n` is the workflow AGENTS.md recommends.
- CLI help advertises `[jr*]`/`-c*` but `*` is not in the getopt string.
- Plugin help omits `-q` and `-f <N>`; `-C` is plugin-only; `-E` is CLI-only;
`-f <N>` grammar differs (CLI treats the number as the path).
**Fix:** one shared parser/dispatch in the library so the modifier contract holds
once. Cheap, visible, and unblocks consistent `-q/-j/-r` everywhere.
### P1.4 iOS `LC_NOTE` (`__dart_app_snap`) container is CLI-only
`main.c:84-97` extracts the embedded inner Mach-O; the plugin has no equivalent,
so `r2flutter -H` inside r2 analyses the outer VM shell and fails, and
`dctx.container_*` stays empty. **Fix:** move detection/extraction into the
library so both frontends share it.
### P1.5 Single-data-snapshot aliasing
`pick_vm_iso_by_size` (`dart_pool_discovery.c:25-49`) with one snapshot assigns
the same address to both `vm_data` and `iso_data` (visible on the synthetic bin:
both `0x4`), so one is parsed as the other. The `sbom-synthetic` golden currently
*encodes* this, so fixing it means deciding the right semantics (probably: lone
snapshot is the isolate, leave `vm_data = 0`) and updating that test.
---
## P2 — duplication, dead code, structural debt
### P2.4 CID/layout data still lives in three places
`offsets.json` (source of truth, generated from SDK), `dart_cid.c` `cid_tables[]`
(generated ✔), and `dart_offsets.h` `dart_hash_entries[]` — which only carries
`hash → compressed_word_size`, a value already derivable from the profile and then
overridden by the feature flags anyway (cws is decided three times, flags win).
**Fix:** drop the hash→cws table or derive it.
### P2.6 `data_image_base` recomputed in ≥3 places with a magic-16 fallback
`dart_object.c:304-305`, `dart_pool_classes.c:1646-1655`, plus IT callers each do
`snapshot + align_up(total_len, layout->max_alignment ?: 16)`. Extract one helper
and confirm alignment is genuinely derived (it can be raised to 64) rather than
silently defaulted.
### P2.7 `-x`/`-z` special-case in both frontends (partly overtaken)
`core_flutter.c` (`case 'x'`) and `main.c` set `string_refs = true` for *any*
`-x`. The dispatch is now live (`case 'x' → dart_pool_dump_xrefs`), so the
"dead branch" framing no longer holds — but the unconditional `string_refs`
plus the `if (cmd->action != 'z')` guard still reads as accidental coupling
between `-x` and `-z`. Re-audit against current line numbers before acting;
lower priority than when first written.
---
## P3 — performance
Read caching (`read_mem`, `dart_pool_snapshot.c:10-44`) already turns per-byte
varint reads into a 1 MB sliding window. Remaining hotspots, largest first:
### P3.1 Redundant full passes over the same regions
A single `-f`/`-A` walks the cluster stream twice and the data image 2–3 times.
**This was claimed to be the biggest perf win; measured, it is not.** Removing
the whole legacy cluster walk changes runtime by ~1%, inside noise (mafia `-f`
993 → 1005 ms; AuthPass `-f` 616 → 618 ms) — it bails out early rather than
re-walking the stream.
The real cost was elsewhere, and profiling found it: `dart_cid_get` re-resolved
the CID table by version-string comparison on every call (see the status table).
Memoising it took mafia `-f` from 1008 → 550 ms and the synthetic `-x` from 4862
→ 37 ms — far more than P2.1 could ever have given. **Profile before spending
time here.**
`-x` was the next slowest; `25dc49e` fixed its correctness bugs and the window
bound cut mafia `-x` 7783 → 4738 ms. The `pdj` JSON round-trip is now gone too
(*uncommitted*): `collect_pool_uses_from_entry` decodes each instruction with
`r_core_anal_op` and feeds `op->mnemonic` to the existing operand parsers — the
same disassembly text, minus the JSON build/parse, matching the `-AAA` path in
`flutter_analysis.c`. Output byte-identical on all 5 samples; mafia `-x`
6052 → 3842 ms (~1.6×). AuthPass gains less (12776 → 11398 ms) — it spends most
of its `-x` time in name resolution, not disasm. A full structured-operand
rewrite (reading `RAnalValue` instead of `op->mnemonic` text) could skip capstone
text rendering too, but arm64 operand modelling is unreliable enough that it
would need per-encoding output diffing — not worth the risk for the remainder.
**The 4096-entry scan cap is justified, not a bug — measured.** Removing it
(scanning all 37258 IT entries) takes mafia `-x` from ~3.8 s to **55.8 s**; the
per-instruction disasm dominates and there is no cheap way around it. The real
wart is that `-l` conflates two limits: the output-line cap (`append_xref_info`)
*and* the pool-use entry-scan cap (`collect_pool_uses`, default 4096 when no
`-l`). Passing `-l 100` to trim output also silently narrows the scan to 100
entries. Untangling them (a separate scan-depth control) belongs with the
frontend-parser work (P1.3), not here.
### P3.2 Brute-force InstructionTable header search
`dart_pool_it.c:233-330` probes `{16,0,8,12} × delta -64..64 step 4`, then scans
the whole data image every 16 bytes (`:209-231`), then a third linear scan
`+0x40..+0x40000 step 8` (`:289-320`); `decode_pool_and_emit` additionally retries
`dart_it_emit_varint` across `delta -64..64`. O(image) per lookup because the
header location isn't derived. **Fix:** compute it from the layout — the Image
header format is now understood (see `COMMIT2.md`: word0 `ImageSize`, word1
`InstructionsSectionOffset`, 64-byte header), which should make the IT header
directly addressable.
### P3.3 Per-value snapshot-header re-parse
`dart_object.c:296-312` calls `dart_snapshot_header_read(ctx, ctx->iso_data, …)`
for *every* scalar value decode just to recompute `data_base`. Cache on the ctx.
### P3.4 Read-cache tail cliff + forward-only thrash
The 1 MB refill (`:37`) needs the whole range readable, so the last <1 MB of each
section degrades to one syscall per byte; the window is forward-only, so name
resolution alternating between the cluster stream and the string heap refills
repeatedly. Consider a small multi-window/LRU cache and a short-read-tolerant refill.
---
## P4 — test quality (beyond the corpus gaps in P0.3)
- **The synthetic fixture has no generator and is fragile.** `bins/synthetic/field_snapshot.bin`
is a hand-authored blob. It has already forced two special cases as the parser
was corrected: P2.2 had to renumber its cluster tags to real CIDs, and `b6ea0b8`
had to branch the tag reader on `synthetic_legacy_parse` because it stores raw
LE u32 tags while real snapshots use varints. Every future layout/encoding fix
risks a third. **Write a generator** so the fixture tracks the parser instead of
pinning obsolete conventions; the `synthetic_legacy_parse` branch could then go.
- **Obfuscation-map tests are tautological**: `test/db/cmd/obf-map`,
`test/custom/obf-funcs-ios.json`, `obf-it-android.json` load a map whose keys
match nothing in the sample, so output is identical to the no-`-m` run. They
prove loading doesn't crash, never that renaming works. Add a fixture whose
keys actually occur.
- `analyze-aaa-android.json` only greps `"Flutter analysis:"` — passes even if
nothing is recovered. Assert a recovered count/name.
- Header tests pin entire JSON blobs incl. full CID tables: deterministic but
brittle (one new field broke ~10 tests during `d387c0b`). Consider
field-subset assertions like the `test/custom` specs already use.
- Missing behaviours: plugin `-r`, plugin under `r2 -n`, gapped-layout structural
discovery (macOS, see `COMMIT2.md`), stripped **ELF**.
- Directory-form input only recognises Android `libapp.so` / iOS
`App.framework/App` (`main.c:62-74`); `-H <dir-with-hello.aot>` errors "does
not exist". Broaden the resolver or document it.
---
## Suggested execution order
1. **P1.3** — shared frontend parser (fixes the `-r`/`r2 -n` AGENTS.md
violations). While there, untangle `-l` for `-x`: give the pool-use scan its
own depth control instead of reusing the output-line limit (see P3.1).
2. **P0.2 edge cases** — the 2.18.2 fill desync and CID_SHIFT1 class extraction
(both above). Smaller than the main win; do only if those eras matter.
3. **P0.3** — a 3.4/3.5-era compressed sample for the still-untested P0.1 fix
(needs an externally-collected app; the local SDK is 3.12.2).
4. **P1.2** — arch gating.
5. **Write a synthetic-fixture generator** (see P4) so parser fixes stop needing
blob special-cases.
6. **P3.2** — now a *lower* suspect than it looked; it never showed up in the
`-x` profile. Remaining P2 cleanups and test hardening.
Done since last revision: P0.2 name+class paths (`540efcf`, `f85bd3b`), the
macOS class regression test (`75ad39a`), and the `-x` `r_anal_op` rewrite
(uncommitted).
`COMMIT2.md` tracks the narrower follow-ups left over from the discovery work
(gapped layouts, more iOS fixtures, stripped ELF, P1.5 semantics).
Vendored
+32 -52
View File
@@ -127,12 +127,12 @@ done
: ${INSTALL_PROGRAM_STRIP:=${INSTALL} -m 755 -s}
: ${INSTALL_MAN:=${INSTALL} -m 444}
: ${INSTALL_LIB:=${INSTALL} -m 755 -c}
PKGNAME='r2flutter' ; VERSION='0.2.0' ; VERSION_MAJOR=0; VERSION_MINOR=2; VERSION_PATCH=0; VERSION_NUMBER=00200; CONTACT_MAIL="[email protected]" ; CONTACT_NAME="pancake" ; CONTACT="pancake <[email protected]>" ;
PKGNAME='r2flutter' ; VERSION='0.6.0' ; VERSION_MAJOR=0; VERSION_MINOR=6; VERSION_PATCH=0; VERSION_NUMBER=00600; CONTACT_MAIL="[email protected]" ; CONTACT_NAME="pancake" ; CONTACT="pancake <[email protected]>" ;
}
show_usage() {
cat <<EOF2
'configure' configures r2flutter-0.2.0 to adapt to many kinds of systems.
'configure' configures r2flutter-0.6.0 to adapt to many kinds of systems.
Usage: ./configure [OPTION]... [VAR=VALUE]...
@@ -214,10 +214,10 @@ ocho() {
show_version() {
if [ "$QUIET" = 1 ]; then
echo "0.2.0"
echo "0.6.0"
exit 0
fi
echo "r2flutter-0.2.0 configuration script done with acr v2.2.8.
echo "r2flutter-0.6.0 configuration script done with acr v2.2.8.
The 'Free Software Foundation' message is only for autodetection.
Originally written by pancake <nopcode.org>."
exit 0
@@ -246,11 +246,10 @@ case $flag in
show_version ; ;;
-r|--r|--report)
echo "PKGNAME: r2flutter"
echo "VERSION: 0.2.0"
echo "VERSION: 0.6.0"
echo "AUTHOR: pancake"
echo "EMAIL: [email protected]"
echo "LANGS: c"
echo "PKGCONFIG: r_core"
echo "FLAGS: --with-precompiled-agent"
exit 0
;;
@@ -321,7 +320,7 @@ parse_options "$1"
shift
done
ENVWORDS="MANDIR DESCRIPTION INFODIR LIBDIR INCLUDEDIR LOCALSTATEDIR ETCDIR SYSCONFDIR DATADIR DOCDIR LIBEXECDIR SBINDIR BINDIR EPREFIX PREFIX SPREFIX TARGET HOST BUILD INSTALL INSTALL_LIB INSTALL_MAN INSTALL_PROGRAM INSTALL_PROGRAM_STRIP INSTALL_DIR INSTALL_SCRIPT INSTALL_DATA HOST_OS HOST_CPU BUILD_OS BUILD_CPU TARGET_OS TARGET_CPU VERSION VERSION_MAJOR VERSION_MINOR VERSION_PATCH VERSION_NUMBER PKGCFG_LIBDIR PKGCFG_INCDIR PKGNAME VPATH CONTACT CONTACT_NAME CONTACT_MAIL PKGCONFIG CC CFLAGS CPPFLAGS LDFLAGS HAVE_LANG_C R2_VERSION PRECOMPILED_AGENT R2_CFLAGS R2_LDFLAGS HAVE_PKGCFG_R_CORE"
ENVWORDS="MANDIR DESCRIPTION INFODIR LIBDIR INCLUDEDIR LOCALSTATEDIR ETCDIR SYSCONFDIR DATADIR DOCDIR LIBEXECDIR SBINDIR BINDIR EPREFIX PREFIX SPREFIX TARGET HOST BUILD INSTALL INSTALL_LIB INSTALL_MAN INSTALL_PROGRAM INSTALL_PROGRAM_STRIP INSTALL_DIR INSTALL_SCRIPT INSTALL_DATA HOST_OS HOST_CPU BUILD_OS BUILD_CPU TARGET_OS TARGET_CPU VERSION VERSION_MAJOR VERSION_MINOR VERSION_PATCH VERSION_NUMBER PKGCFG_LIBDIR PKGCFG_INCDIR PKGNAME VPATH CONTACT CONTACT_NAME CONTACT_MAIL CC CFLAGS CPPFLAGS LDFLAGS HAVE_LANG_C R2_VERSION R2_CFLAGS R2_LDFLAGS R2_LIBS PRECOMPILED_AGENT"
create_environ
@@ -336,37 +335,6 @@ ocho "checking for working directories... ${WODIS}"
ocho "using prefix '${PREFIX}'"
ACR_RMFILES=" test.c a.out a.exe"
ochof "checking for pkg-config... "
ENVWORDS="${ENVWORDS} HAVE_PKGCONFIG"
if [ -n "$PKGCONFIG" ]; then
if [ -x "$PKGCONFIG" ]; then
_HAVE_PKGCONFIG="${PKGCONFIG}"
else
ocho "PKGCONFIG env is not executable"
PKGCONFIG=""
fi
else
IFS=:
for A in ${PATH} ; do
if [ -x "${A}/pkg-config" ]; then
PKGCONFIG="${A}/pkg-config"
elif [ -x "${A}/pkgconf" ]; then
PKGCONFIG="${A}/pkgconf"
else
continue
fi
break
done
unset IFS
fi
if [ -n "${PKGCONFIG}" ]; then
ocho ${PKGCONFIG}
HAVE_PKGCONFIG=1
else
HAVE_PKGCONFIG=0
echo "Cannot find pkg-config or pkgconf in PATH" >&2
exit 1
ocho no; fi
COMPILER=CC
ochof "checking for c compiler... "
HAVE_LANG_C=1
@@ -390,21 +358,33 @@ else
echo "not found"
echo 'required'; exit 1
fi
if [ -z "${PKGCONFIG}" ]; then pkg-config --version >/dev/null 2>&1 ; if [ 0 = 0 ]; then PKGCONFIG=pkg-config ; else PKGCONFIG=pkgconf ; fi; fi
type ${PKGCONFIG} > /dev/null 2>&1 || echo "ERROR: Cannot find valid PKGCONFIG, pkg-config or pkgconf in PATH"
echo 'Using PKGCONFIG: '${PKGCONFIG}
printf 'checking pkg-config flags for r_core... '
tmp=`${PKGCONFIG} --cflags r_core 2>/dev/null`
if [ $? != 0 ]; then echo no ; HAVE_PKGCFG_R_CORE=0;
R2_CFLAGS='';R2_LDFLAGS='';
echo 'This package is required'
exit 1
else
R2_CFLAGS=$tmp;
tmp=`${PKGCONFIG} --libs r_core 2>/dev/null`
printf "checking exec r2 -H R2_CFLAGS... "
R2_CFLAGS="`r2 -H R2_CFLAGS 2>/dev/null`"
if [ $? = 0 ]; then
echo yes; HAVE_PKGCFG_R_CORE=1;
R2_LDFLAGS=$tmp; fi; fi
echo "ok"
else
R2_CFLAGS=""
echo "not found"
echo 'required'; exit 1
fi
printf "checking exec r2 -H R2_LDFLAGS... "
R2_LDFLAGS="`r2 -H R2_LDFLAGS 2>/dev/null`"
if [ $? = 0 ]; then
echo "ok"
else
R2_LDFLAGS=""
echo "not found"
echo 'required'; exit 1
fi
printf "checking exec r2 -H R2_LIBS... "
R2_LIBS="`r2 -H R2_LIBS 2>/dev/null`"
if [ $? = 0 ]; then
echo "ok"
else
R2_LIBS=""
echo "not found"
echo 'required'; exit 1
fi
SEDFLAGS=" -e '"
COUNT=0
for A in ${ENVWORDS} ; do
+4 -5
View File
@@ -1,17 +1,16 @@
PKGNAME r2flutter
VERSION 0.2.0
VERSION 0.6.0
CONTACT pancake ; [email protected]
USE_PKGCONFIG!
LANG_C!
EXEC! R2_VERSION r2 -v ;
EXEC! R2_CFLAGS r2 -H R2_CFLAGS ;
EXEC! R2_LDFLAGS r2 -H R2_LDFLAGS ;
EXEC! R2_LIBS r2 -H R2_LIBS ;
ARG_WITH PRECOMPILED_AGENT precompiled-agent do not require node/npm ;
PKGCFG! R2_CFLAGS R2_LDFLAGS r_core
REPORT R2_VERSION ;
SUBDIRS ./config.mk ;
+36
View File
@@ -0,0 +1,36 @@
# r2flutter binary release
This package contains:
* `bin/r2flutter` — the standalone command line tool
* `plugins/core_flutter.{so,dylib,dll}` — the radare2 core plugin
## Installing
Copy the tool anywhere in your `$PATH` and drop the plugin into the radare2
plugin directory reported by r2:
```sh
r2 -H R2_USER_PLUGINS # per user, no root needed
r2 -H R2_LIBR_PLUGINS # system wide
```
For example:
```sh
mkdir -p "$(r2 -H R2_USER_PLUGINS)"
cp plugins/core_flutter.* "$(r2 -H R2_USER_PLUGINS)"
cp bin/r2flutter /usr/local/bin
```
On Windows the plugin directory is usually `%HOMEPATH%\.local\share\radare2\plugins`.
## Checking the installation
```sh
r2 -qc 'Lc~r2flutter' --
r2flutter -V
```
The plugin must match the radare2 version it was built against, otherwise r2
refuses to load it.
+13
View File
@@ -0,0 +1,13 @@
PACKAGE=r2flutter
VERSION?=$(shell ../../configure -qV)
DEPENDS=radare2
SECTION=user/shell
PRIORITY=optional
MAINTAINER=pancake <[email protected]>
# arch
UNAMEM=$(shell uname -m)
ifeq ($(UNAMEM),x86_64)
ARCH?=amd64
else
ARCH?=arm64
endif
+30
View File
@@ -0,0 +1,30 @@
include ./CONFIG
PWD=$(shell pwd)
all: deb
deb:
$(MAKE) root
$(MAKE) build
root:
rm -rf root
$(MAKE) -C ../.. install DESTDIR=$(PWD)/root
build:
mkdir -p root/DEBIAN
echo "Package: $(PACKAGE)" > root/DEBIAN/control
echo "Version: $(VERSION)" >> root/DEBIAN/control
echo "Section: $(SECTION)" >> root/DEBIAN/control
echo "Priority: $(PRIORITY)" >> root/DEBIAN/control
echo "Architecture: $(ARCH)" >> root/DEBIAN/control
echo "Depends: $(DEPENDS)" >> root/DEBIAN/control
echo "Maintainer: $(MAINTAINER)" >> root/DEBIAN/control
echo "Description: r2flutter plugin for radare2" >> root/DEBIAN/control
dpkg-deb --root-owner-group --build root $(PACKAGE)_$(VERSION)_$(ARCH).deb
clean:
rm -rf root *.deb
.PHONY: all deb root build clean
+15
View File
@@ -0,0 +1,15 @@
$ErrorActionPreference = 'Stop'
meson setup build-windows --buildtype=release
meson compile -C build-windows
$r2v = if ($env:R2V) { $env:R2V } else { (r2 -qv).Trim() }
$root = 'build-windows/bindist'
$archive = "r2flutter-$r2v-windows-x86-64.zip"
Remove-Item -Recurse -Force $root -ErrorAction SilentlyContinue
Remove-Item -Force $archive -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force $root/bin,$root/plugins | Out-Null
Copy-Item build-windows/r2flutter.exe $root/bin/
Copy-Item build-windows/core_flutter.dll $root/plugins/
Copy-Item dist/README.md $root/
Compress-Archive -Path $root/* -DestinationPath $archive -Force
+14 -3
View File
@@ -125,7 +125,7 @@ If you need to extract version-specific logic from the Dart SDK source :
## Summary
**You do NOT need to download anything at runtime.** The version hash in the snapshot header (32-byte ASCII at offset 0x04) maps to a finite set of constraint profiles that can be statically defined. unflutter proves this works for all Dart versions from 2.10.0 to 3.10.7 without any runtime SDK compilation or network downloads .
**You do NOT need to download anything at runtime.** The version hash in the snapshot header (32-byte ASCII at offset 0x04) maps to a finite set of constraint profiles that can be statically defined. unflutter proves this works for all Dart versions from 2.10.0 to 3.12.1 without any runtime SDK compilation or network downloads .
The only time you'd need dynamic updates is when Flutter releases a new version with breaking snapshot format changes - which happens infrequently and follows a predictable release cycle (stable, beta, dev channels) .
@@ -136,7 +136,7 @@ The only time you'd need dynamic updates is when Flutter releases a new version
r2flutter implements Option 1 (Static Constraint Tables) from this document. The implementation in `src/lib/dart_pool_parse.c` includes:
### Hash-to-Version Mapping
- 40+ known snapshot hashes from Flutter 1.22.x (Dart 2.10.0) to Flutter 3.38.x (Dart 3.10.7)
- 40+ known snapshot hashes from Flutter 1.22.x (Dart 2.10.0) to Flutter 3.41.x (Dart 3.12.1)
- Sources: unflutter version.go, reFlutter enginehash.csv, blutter precompiled SDKs
### Version Profiles
@@ -154,7 +154,18 @@ Each supported Dart version has a profile with:
| 2.13.0 | Int32 CID | Split canonical/non-canonical clusters |
| 2.14.0-2.17.6 | CID Shift1 | `(cid << 1) \| canonical` |
| 2.18.0-3.3.0 | CID Shift1 | Various CID changes |
| 3.4.3-3.10.7 | ObjectHeader | CID at bits 12-31 |
| 3.4.3-3.12.1 | ObjectHeader | CID at bits 12-31 |
Unknown hashes fall back to v3.9.2 profile with ObjectHeader encoding.
## Snapshot Hashes Are Shared Within Patch-Groups
A snapshot hash typically covers a group of patch releases within a minor line,
but not necessarily the entire minor version. For example, Dart 3.0.0-3.0.2 share
one hash while 3.0.3-3.0.7 use a different one; Dart 3.11.0-3.11.1 share a hash
separate from 3.11.3-3.11.5; Dart 3.12.0 and 3.12.1 each have distinct hashes.
The version string in the mapping table uses the latest known patch in the group
(e.g., `3.1.3` for the 3.1.x group, `3.11.5` for the 3.11.3-3.11.5 group).
The snapshot format (CID table, tag encoding, layout) is identical across all
patch versions sharing the same hash.
+39
View File
@@ -0,0 +1,39 @@
# DAE Gap Implementation TODO
This roadmap tracks the high-confidence Dart AOT format and restoration gaps
identified by comparing `third_party/dae` with r2flutter. IDs are stable so an
item can be selected by name in follow-up work.
Items are ordered by dependency: stream framing and codecs first, versioned
cluster layouts next, then metadata/value restoration and profile detection.
- [x] **R2F-DAE-001 — Parse version-specific snapshot headers**
- Read the profile-defined header field count and semantics.
- Preserve canonical-cluster and field-table fields where present.
- Commit: `Parse Version-Specific Snapshot Headers`
- [x] **R2F-DAE-002 — Add versioned snapshot reference decoding**
- Use unsigned LEB128 references through Dart 2.17.
- Use the `ReadRefId` +128 encoding from Dart 2.18 onward.
- Commit: `Add Versioned Snapshot Reference Decoding`
- [x] **R2F-DAE-003 — Handle versioned ObjectPool entry bits**
- Model low-seven-bit, swapped low-seven-bit, and modern bitfield eras.
- Commit: `Handle Versioned ObjectPool Entry Bits`
- [x] **R2F-DAE-004 — Parse versioned Code cluster layouts**
- Profile Code reference counts, text-offset deltas, and leading refs.
- Commit: `Parse Versioned Code Cluster Layouts`
- [x] **R2F-DAE-005 — Support Dart 3.13 single snapshots**
- Add hashes/CIDs and merged VM/isolate snapshot handling.
- Add the alternate symbols and 3.13 Class, Code, and Closure layouts.
- Test complete and truncated combined snapshots, including partial-fill failure reporting.
- Commit: `Support Dart 3.13 Single Snapshots`
- [x] **R2F-DAE-006 — Retain serialized class layout metadata**
- Keep class ID, library/super references, next-field offset, and bitmap.
- Test ordinary, top-level, and truncated Class fills.
- Commit: `Retain Serialized Class Layout Metadata`
- [x] **R2F-DAE-007 — Retain snapshot object value graph**
- Preserve Instance, Array, Map, and Set values by object reference.
- Expose bounded recursive values through the object inspector.
- Commit: `Retain Snapshot Object Value Graph`
- [x] **R2F-DAE-008 — Probe snapshot layouts for unknown hashes**
- Validate candidate layouts structurally instead of selecting the newest.
- Commit: `Probe Snapshot Layouts for Unknown Hashes`
+11
View File
@@ -49,6 +49,15 @@ bin/r2flutter -r -l 16 -i test/bins/ios/Runner.app
`-l` is useful because real apps can have thousands of entries.
Entrypoint and instruction-image marking:
```bash
bin/r2flutter -E test/bins/ios/Runner.app
bin/r2flutter -rE test/bins/ios/Runner.app
```
`-E` reports the first decoded code entry from the InstructionTable. This is different from the loader symbol for `_kDartVmSnapshotInstructions`, which points at the VM instruction image data, not a normal program entrypoint. With `-r`, `-E` also emits `Cd 4 ...` metadata commands for the VM and isolate instruction images so radare2 renders them as dword arrays instead of treating the snapshot symbol as code.
## Fields Exposed By r2flutter
Plain output shows:
@@ -65,9 +74,11 @@ JSON also includes:
- `canonical_stack_map_entries_offset`
- per-entry `pc_offset`
- per-entry `stack_map_offset`
- `-E` JSON includes `entrypoint`, `index`, `code_index`, and instruction-image sizes when known
## Notes
- Output is written to `stdout`, not `stderr`.
- `-j` and `-r` now affect `-i` the same way they affect the other dump commands.
- `-j`, `-q`, and `-r` also affect `-E`; use `-qE` when only the address is needed.
- The current parser finds the table by scanning the mapped data image for a valid `InstructionsTable::Data` header when the snapshot's `it_off` field does not point exactly at the payload start.
+1049 -18
View File
File diff suppressed because it is too large. Load diff
+29 -14
View File
@@ -17,8 +17,8 @@ instruction tables, and strings directly.
| Mach-O details | Thin Mach-O64 and fat Mach-O/fat64; arm64 slice is preferred. LC_NOTE owner `__dart_app_snap` is detected and the embedded Dart Mach-O payload is extracted to a temporary file. |
| Architecture | ARM64/AArch64 is the real target. Instruction-table entrypoints are based on ARM64 code offsets, PP tracking uses `x27`, and the radare2 analysis pass understands AArch64 registers/op metadata. |
| Snapshot kind | Clustered Dart AOT snapshots with magic `0xdcdcf5f5`, 32-byte snapshot hashes, feature strings, and unsigned varint header fields. |
| Dart layouts | In-tree profiles cover Dart `2.10.0` through `3.10.7`, roughly Flutter `1.22.x` through `3.38.x`. |
| Unknown hashes | First tries `r2flutter/offsets.json` or `offsets.json`; otherwise uses v3.9.2-shaped ObjectHeader defaults while reporting the Dart version as `unknown`. |
| Dart layouts | In-tree profiles cover Dart `2.10.0` through `3.14.0`. Profiles are keyed at layout-change boundaries and resolved by floor: e.g. `3.7`/`3.8` share the `3.6.0` CID layout and `3.10`–`3.12` share the `3.9.0` layout. CID tables through 3.12.1 were verified byte-for-byte against each release's `runtime/vm/class_id.h`; 3.13–3.14 add the combined-snapshot layout. |
| Unknown hashes | Git/dev builds carry an unlisted snapshot hash. r2flutter first probes compatible split- or single-snapshot profiles by parsing the complete isolate cluster stream, checking reference/object counts and fill boundaries, and scoring Function/Class/Library records. A match is reported as `~<range-start>+ (structural-probe)` with `version_source=structural-probe`. If no profile validates, the existing newest-compatible split profile remains as a conservative `~<version>+ (fingerprint)` fallback. Exact hashes and explicit `-D` overrides bypass probing. |
| Obfuscation maps | Flutter/Dart `--save-obfuscation-map` JSON arrays are supported with `-m <file>` or `e r2flutter.mapfile=<file>` in the plugin. |
## Layout Profiles
@@ -44,7 +44,7 @@ The CID column is compacted as:
| 2.18.0 | 3.3.x | `CID_SHIFT1` | 4 | 5 | 159 | `5/7/17/90/91/92/87/59/21` | SuspendState added. |
| 2.19.0 | 3.7.x | `CID_SHIFT1` | 4 | 5 | 176 | `5/7/17/92/93/94/89/60/21` | RecordType/Record added. |
| 3.0.5 | 3.10.x - 3.12.x | `CID_SHIFT1` | 4 | 5 | 177 | `5/7/18/93/94/95/90/61/22` | WeakArray added. |
| 3.1.0 | 3.13.x | `CID_SHIFT1` | 4 | 5 | 176 | `5/7/18/92/93/94/89/60/22` | TypeRef removed. |
| 3.1.3 | 3.13.x | `CID_SHIFT1` | 4 | 5 | 176 | `5/7/18/92/93/94/89/60/22` | TypeRef removed. |
| 3.2.5 | 3.16.x | `CID_SHIFT1` | 4 | 5 | 176 | `5/7/18/92/93/94/89/60/22` | PoolType swapped. |
| 3.3.0 | 3.19.x | `CID_SHIFT1` | 4 | 5 | 176 | `5/7/18/92/93/94/89/60/22` | Last Shift1 profile in-tree. |
| 3.4.3 | 3.22.x | `OBJECT_HEADER` | 4 | 5 | 174 | `5/7/18/92/93/94/89/60/22` | ObjectHeader tag encoding begins. |
@@ -52,8 +52,13 @@ The CID column is compacted as:
| 3.6.2 | 3.27.x | `OBJECT_HEADER` | 4 | 5 | 175 | `5/7/18/93/94/95/90/61/23` | CID table update. |
| 3.7.0 | 3.29.x | `OBJECT_HEADER` | 4 | 5 | 175 | `5/7/18/93/94/95/90/61/23` | Same modern profile family. |
| 3.8.1 | 3.32.x | `OBJECT_HEADER` | 4 | 5 | 175 | `5/7/18/93/94/95/90/61/23` | Same modern profile family. |
| 3.9.2 | 3.35.x | `OBJECT_HEADER` | 4 | 5 | 175 | `5/7/18/93/94/95/90/61/23` | Unknown-hash fallback baseline. |
| 3.10.7 | 3.38.x | `OBJECT_HEADER` | 4 | 5 | 175 | `5/7/18/93/94/95/90/61/23` | Newest in-tree profile. |
| 3.9.2 | 3.35.x | `OBJECT_HEADER` | 4 | 5 | 175 | `5/7/18/93/94/95/90/61/23` | Modern split profile family. |
| 3.10.7 | 3.38.x | `OBJECT_HEADER` | 4 | 5 | 175 | `5/7/18/93/94/95/90/61/23` | Same modern profile family. |
| 3.11.5 | 3.39.x | `OBJECT_HEADER` | 4 | 5 | 175 | `5/7/18/93/94/95/90/61/23` | Same modern profile family. |
| 3.12.0 | 3.41.x | `OBJECT_HEADER` | 4 | 5 | 175 | `5/7/18/93/94/95/90/61/23` | Same modern profile family. |
| 3.12.1 | 3.41.x | `OBJECT_HEADER` | 4 | 5 | 175 | `5/7/18/93/94/95/90/61/23` | Newest split compatibility profile. |
| 3.13.0 | — | `OBJECT_HEADER` | 4 | 5 | 176 | `5/7/18/93/94/95/90/61/23` | Combined snapshot; alternate Class/Code/Closure framing. |
| 3.14.0 | — | `OBJECT_HEADER` | 4 | 5 | 176 | `5/7/18/93/94/95/90/61/23` | Same combined-snapshot profile family. |
All profiles use heap object tag `1`, default alignment `16`, and instruction
table capacity `20000` before feature-flag or `offsets.json` adjustments.
@@ -73,7 +78,7 @@ These hashes are mapped directly by `src/lib/dart_version.c`.
| 2.18.0 | `b0e899ec5a90e4661501f0b69e9dd70f`, `b6d0a1f034d158b0d37b51d559379697`, `8e50e448b241be23b9e990094f4dca39`, `6a9b5a03a7e784a4558b10c769f188d9` |
| 2.19.0 | `adb4292f3ec25074ca70abcd2d5c7251`, `501ef5cbd64ca70b6b42672346af6a8a` |
| 3.0.5 | `90b56a561f70cd55e972cb49b79b3d8b`, `aa64af18e7d086041ac127cc4bc50c5e`, `36b0375d284ee2af0d0fffc6e6e48fde`, `16ad76edd19b537bf6ea64fdd31977a7` |
| 3.1.0 | `7dbbeeb8ef7b91338640dca3927636de` |
| 3.1.3 | `7dbbeeb8ef7b91338640dca3927636de` |
| 3.2.5 | `f71c76320d35b65f1164dbaa6d95fe09` |
| 3.3.0 | `ee1eb666c76a5cb7746faf39d0b97547` |
| 3.4.3 | `d20a1be77c3d3c41b2a5accaee1ce549` |
@@ -83,11 +88,16 @@ These hashes are mapped directly by `src/lib/dart_version.c`.
| 3.8.1 | `830f4f59e7969c70b595182826435c19` |
| 3.9.2 | `97ff04a728735e6b6b098bdf983faaba` |
| 3.10.7 | `1ce86630892e2dca9a8543fdb8ed8e22` |
| 3.11.5 | `78da37fed6bf1489361a312568249f3f` |
| 3.12.0 | `41be3daaabd524b8aa7423bc24584957` |
| 3.12.1 | `ace654289f5abc240509fc941453ebc5` |
| 3.13.0 | `0451907c2eaa8467e848c0067bfe8ed4` |
| 3.14.0 | `7a1ea3f6f5cf1089a7f6e55d7f20dbfd` |
`offsets.json` can enrich or override a hash with `compressed_word_size`,
`heap_object_tag`, `max_alignment`, `it_cap`, `code_entry_point_offsets`,
`code_owner_offsets`, and `function_name_offsets`. The code looks for it first
under `r2flutter/offsets.json`, then in the current directory as `offsets.json`.
`include/r2flutter/dart_offsets.h` contains the hash→compressed_word_size lookup
table and the default offset arrays, compiled statically into r2flutter. No
external JSON file is needed at runtime. The header is auto-generated by
`scripts/update-dart-version`.
## Snapshot Discovery
@@ -356,7 +366,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`. |
| `-O <addr\|pp+off\|ref:N>` | Decode a Dart Smi/tagged heap pointer/object address, a reconstructed ObjectPool PP slot such as `pp+0x68`, or a serialized snapshot reference. Snapshot values are recursively bounded and preserve arrays, maps, sets, and instance fields. 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. |
@@ -380,6 +390,11 @@ Config keys:
- `r2flutter.mapfile`: Flutter obfuscation map JSON path
- `r2flutter.namepool`: enable heuristic name-pool fallback
- `r2flutter.profile`: override Dart snapshot profile by 32-byte hash or Dart version
- `r2flutter.str.limit`: max number of strings printed by `-z`/`-zz` (default `0`, unlimited); without filters it also stops the `-zz` carving early
- `r2flutter.str.minlen`: skip printing strings shorter than N (default `0`)
- `r2flutter.str.maxlen`: skip printing strings longer than N (default `0`, unlimited)
- `r2flutter.str.filter`: only print strings matching this extended regex (default empty)
Plugin modifiers:
@@ -405,7 +420,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 -O spec` | Decode a Dart Smi/tagged heap pointer/object address, a reconstructed ObjectPool PP slot such as `pp+0x68`, or a serialized snapshot reference such as `ref:209553`. 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. |
@@ -446,8 +461,8 @@ The `-R` path emits:
- `PP=x27`; per-function PP offset helper scanning is intentionally skipped by
default in script output because it is too slow on large apps
- `-r -p` is the focused PP setup path: it opens a malloc map at the synthetic
PP base, writes the reconstructed ObjectPool image, and sets `e anal.gp` plus
`dr x27`
PP base, writes the reconstructed ObjectPool image, sets `e anal.gp`, clears
`anal.roregs` before assigning `x27`, then restores `anal.roregs=x27,gp,zero`
## Practical Limits
+304
View File
@@ -0,0 +1,304 @@
# AuthPass.app Dart AOT TLS tutorial
This tutorial uses the iOS fixture:
```text
test/bins/ios/AuthPass.app
```
Run commands from the repository root. The Dart AOT image is not the top-level
`Runner` executable; it is:
```sh
IOS_BUNDLE=test/bins/ios/AuthPass.app
IOS_APP="$IOS_BUNDLE/Frameworks/App.framework/App"
```
Rebuild the tools and plugin before following the r2 steps:
```sh
make
make r2
make user-install
```
## Challenge 1: recover Dart AOT method names and map the TLS handshake path
Start by verifying that r2flutter recognizes the snapshot:
```sh
bin/r2flutter -j -H "$IOS_BUNDLE"
```
Expected important values:
```text
"dart_version":"3.2.5"
"hash":"f71c76320d35b65f1164dbaa6d95fe09"
"it_len":37092
"tag_style":"CID_SHIFT1"
```
Dump recovered functions and resolve the TLS methods by semantic name. Do not
hardcode the private suffix; AuthPass uses `14069316`, but that value changes
between snapshots.
```sh
FUNCS=$(mktemp)
bin/r2flutter -q -f "$IOS_BUNDLE" > "$FUNCS"
BAD_CERT_ADDR=$(awk '/method\._RawSecureSocket_.*\._onBadCertificateWrapper_/ { print $1; exit }' "$FUNCS")
REGISTER_ADDR=$(awk '/method\._SecureFilterImpl_.*\.registerBadCertificateCallback$/ { print $1; exit }' "$FUNCS")
REGISTER_IMPL_ADDR=$(awk '/method\._SecureFilterImpl_.*\._registerBadCertificateCallback_/ { print $1; exit }' "$FUNCS")
HANDSHAKE_ADDR=$(awk '/method\._RawSecureSocket_.*\._secureHandshake_/ { print $1; exit }' "$FUNCS")
MARK_TRUSTED_ADDR=$(awk '/method\._SecureFilterImpl_.*\._markAsTrusted_/ { print $1; exit }' "$FUNCS")
printf 'wrapper=%s register=%s register_impl=%s handshake=%s mark=%s\n' \
"$BAD_CERT_ADDR" "$REGISTER_ADDR" "$REGISTER_IMPL_ADDR" "$HANDSHAKE_ADDR" "$MARK_TRUSTED_ADDR"
rm -f "$FUNCS"
```
Expected output:
```text
wrapper=0x3199d4 register=0x3194ec register_impl=0x31954c handshake=0x31749c mark=0x31925c
```
Apply r2flutter flags in r2 and confirm that the same method flags exist:
```sh
r2 -q -e scr.color=0 -e bin.relocs.apply=true \
-c 'r2flutter -A' \
-c 'f~onBadCertificateWrapper' \
-c 'f~registerBadCertificateCallback' \
-c 'f~secureHandshake' \
-c q "$IOS_APP"
```
Expected method flags:
```text
0x003199d4 0 method._RawSecureSocket_14069316._onBadCertificateWrapper_14069316
0x003194ec 0 method._SecureFilterImpl_14069316.registerBadCertificateCallback
0x0031954c 0 method._SecureFilterImpl_14069316._registerBadCertificateCallback_14069316
0x0031749c 0 method._RawSecureSocket_14069316._secureHandshake_14069316
```
Now disassemble the handshake:
```sh
r2 -q -e scr.color=0 -e bin.relocs.apply=true \
-c 'r2flutter -A' \
-c "s ${HANDSHAKE_ADDR:-0x31749c}" \
-c 'pd 104' \
-c q "$IOS_APP"
```
Useful lines:
```asm
0x003174e4 bl method._SecureFilterImpl_14069316.handshake
0x00317514 bl method._RawSecureSocket_14069316._secureHandshake_14069316
0x0031752c add x1, x22, 0x30
0x0031753c bl method._RawSecureSocket_14069316._readSocket_14069316
0x00317548 bl method._RawSecureSocket_14069316._writeSocket_14069316
0x00317554 bl method._RawSecureSocket_14069316._scheduleFilter_14069316
0x00317588 bl method._RawSecureSocket_14069316._reportError_14069316
0x003175ac add x0, x22, 0x20
```
The boolean constants are visible in this cluster:
- `x22 + 0x20` is Dart `true`.
- `x22 + 0x30` is Dart `false`.
## Challenge 2: patch `_RawSecureSocket._onBadCertificateWrapper`
Disassemble the wrapper:
```sh
r2 -q -e scr.color=0 -e bin.relocs.apply=true \
-c 'r2flutter -A' \
-c "s ${BAD_CERT_ADDR:-0x3199d4}" \
-c 'pd 24' \
-c q "$IOS_APP"
```
Important instructions:
```asm
0x003199d4 stp x29, x30, [x15, -0x10]!
0x003199ec ldr x0, [x29, 0x18]
0x003199f0 ldur x1, [x0, 0x5f]
0x003199f4 cmp x1, x22
0x003199f8 b.eq 0x319a28
0x00319a00 stp x16, x1, [x15]
0x00319a0c ldur x2, [x0, 0x37]
0x00319a10 blr x2
0x00319a1c ret
```
AuthPass differs from the Android samples: on the normal path the wrapper
returns the app-provided bad-certificate callback result unchanged. The bypass
target is still the wrapper entry. Returning Dart `true` before the callback
logic makes the certificate decision succeed.
For this fixture, `rabin2 -S "$IOS_APP"` reports equal `__text` physical and
virtual addresses:
```sh
rabin2 -S "$IOS_APP" | rg '__TEXT.__text'
```
Expected:
```text
0 0x00005a80 0x6f8bd0 0x00005a80 0x6f8bd0 -r-x 0x0 REGULAR 0.__TEXT.__text
```
That means the recovered method virtual address is also the raw file offset for
this fixture. If this is not true for another Mach-O, translate with:
```text
section_paddr + (method_vaddr - section_vaddr)
```
Patch a copy first:
```sh
cp "$IOS_APP" "$IOS_APP.bak"
r2 -q -w -n -e scr.color=0 \
-c "s ${BAD_CERT_ADDR:-0x3199d4}" \
-c 'wx c0820091c0035fd6' \
-c q "$IOS_APP"
```
Patch bytes:
```text
c0820091 c0035fd6
```
Decoded AArch64:
```asm
add x0, x22, 0x20
ret
```
Verify:
```sh
r2 -q -n -a arm -b 64 -e scr.color=0 \
-c "s ${BAD_CERT_ADDR:-0x3199d4}" \
-c 'p8 8' \
-c 'pd 2' \
-c q "$IOS_APP"
```
Expected:
```text
c0820091c0035fd6
add x0, x22, 0x20
ret
```
Restore the fixture after testing:
```sh
cp "$IOS_APP.bak" "$IOS_APP"
```
You can also use the generic qjs helper on a copy:
```sh
TMP_APP=$(mktemp /tmp/r2flutter-authpass-app.XXXXXX)
cp "$IOS_APP" "$TMP_APP"
r2 -q -w -e bin.relocs.apply=true \
-i scripts/dart_cert_bypass.qjs \
"$TMP_APP"
r2 -q -n -a arm -b 64 -e scr.color=0 \
-c 's 0x3199d4' \
-c 'p8 8' \
-c 'pd 2' \
-c q "$TMP_APP"
rm -f "$TMP_APP"
```
The script resolves `method._RawSecureSocket_*._onBadCertificateWrapper_*`
through r2flutter flags, translates the method address to a file offset using
section metadata, and writes the same two AArch64 instructions. It expects the
actual Dart image to be opened in r2: Android `libapp.so` or iOS
`Frameworks/App.framework/App`.
## Challenge 3: trace callback registration and direct xrefs
Disassemble callback registration:
```sh
r2 -q -e scr.color=0 -e bin.relocs.apply=true \
-c 'r2flutter -A' \
-c "s ${REGISTER_ADDR:-0x3194ec}" \
-c 'pd 44' \
-c q "$IOS_APP"
```
Useful lines:
```asm
0x0031950c stur x0, [x1, 0xf]
0x00319530 bl method._SecureFilterImpl_14069316._registerBadCertificateCallback_14069316
0x00319534 mov x0, x22
0x00319540 ret
```
Run lightweight reference analysis and query the resolved TLS addresses:
```sh
r2 -q -e scr.color=0 -e bin.relocs.apply=true \
-c 'r2flutter -A' \
-c 'aar' \
-c "axt @ ${BAD_CERT_ADDR:-0x3199d4}" \
-c "axt @ ${REGISTER_ADDR:-0x3194ec}" \
-c "axt @ ${REGISTER_IMPL_ADDR:-0x31954c}" \
-c "axt @ ${HANDSHAKE_ADDR:-0x31749c}" \
-c q "$IOS_APP"
```
Observed direct xrefs:
```text
0x3199bc -> 0x3199d4 _RawSecureSocket._anon_closure -> _onBadCertificateWrapper
0x316cd8 -> 0x3194ec _RawSecureSocket constructor -> registerBadCertificateCallback
0x319530 -> 0x31954c registerBadCertificateCallback -> _registerBadCertificateCallback
0x317028 -> 0x31749c _RawSecureSocket constructor -> _secureHandshake
0x317514 -> 0x31749c _secureHandshake -> _secureHandshake
0x3178b4 -> 0x31749c _tryFilter -> _secureHandshake
0x317e04 -> 0x31749c _closeHandler -> _secureHandshake
```
The call into the app-provided certificate callback inside the wrapper is
indirect (`blr x2`), so it is not expected to produce a stable direct `axt`
target. The stable evidence is the callback registration, the wrapper body, the
TLS method cluster, and the direct references to the wrapper.
## Regression tests
The tutorial workflow is covered by:
```sh
r2r -u test/db/cmd/authpass-r2-tutorial
```
The tests validate:
- r2 plugin flag recovery for the AuthPass TLS methods.
- wrapper and handshake disassembly landmarks.
- direct xref/callsite sources without running full analysis in the test.
- the generic qjs bypass script on a temporary copy.
Patching the embedded iOS framework invalidates the bundle code signature. A
real deployment requires re-signing with the appropriate identity/provisioning
workflow.
+3 -2
View File
@@ -18,6 +18,7 @@ typedef struct DartFunction {
ut64 addr;
ut64 size;
int quality;
char *signature; // recovered Dart signature "RET(T,T,...)", or NULL
} DartFunction;
void dart_function_fini(DartFunction *fn);
@@ -43,8 +44,8 @@ DartApp *dart_app_new(const char *path);
DartApp *dart_app_new_from_core(RCore *core, DartCtx *dctx);
void dart_app_free(DartApp *app);
void dart_app_load_info(DartApp *app);
bool dart_app_find_macho_embedded_dart(const char *path, DartAppEmbeddedPayload *out);
char *dart_app_extract_embedded_payload(const char *path, const DartAppEmbeddedPayload *payload);
bool dart_app_find_macho_dart(const char *path, DartAppEmbeddedPayload *out);
char *dart_app_extract_payload(const char *path, const DartAppEmbeddedPayload *payload);
#ifdef __cplusplus
}
+5 -2
View File
@@ -31,6 +31,7 @@ typedef enum {
DART_CID_PC_DESCRIPTORS,
DART_CID_CODE_SOURCE_MAP,
DART_CID_COMPRESSED_STACK_MAPS,
DART_CID_LOCAL_VAR_DESCRIPTORS,
DART_CID_EXCEPTION_HANDLERS,
DART_CID_CONTEXT,
DART_CID_CONTEXT_SCOPE,
@@ -43,8 +44,10 @@ typedef enum {
DART_CID_MEGAMORPHIC_CACHE,
DART_CID_SUBTYPE_TEST_CACHE,
DART_CID_LOADING_UNIT,
DART_CID_API_ERROR,
DART_CID_LANGUAGE_ERROR,
DART_CID_UNHANDLED_EXCEPTION,
DART_CID_UNWIND_ERROR,
DART_CID_INSTANCE,
DART_CID_LIBRARY_PREFIX,
DART_CID_TYPE_ARGUMENTS,
@@ -91,8 +94,8 @@ typedef enum {
const char *dart_cid_kind_name(DartCidKind kind);
int dart_cid_get(const DartVerLayout *layout, DartCidKind kind);
bool dart_cid_is(const DartVerLayout *layout, int cid, DartCidKind kind);
int dart_cid_typed_data_internal_base(const DartVerLayout *layout);
int dart_cid_typed_data_internal_limit(const DartVerLayout *layout);
int dart_cid_typed_data_base(const DartVerLayout *layout);
int dart_cid_typed_data_limit(const DartVerLayout *layout);
int dart_cid_typed_data_stride(const DartVerLayout *layout);
#ifdef __cplusplus
+8 -2
View File
@@ -7,9 +7,15 @@
extern "C" {
#endif
char *dart_dumper_dump4radare2(DartApp *app);
char *dart_dumper_dump_r2(DartApp *app);
char *dart_dumper_dump_funcs(DartApp *app, int fmt);
void dart_dumper_apply_to_core(DartApp *app);
void dart_dumper_apply_to_core(DartApp *app, bool apply_signatures);
void dart_dumper_apply_signatures(DartApp *app);
void dart_dumper_name_functions(DartApp *app);
// Disable anal.trycatch while r2flutter runs many `af` commands, when no try.*
// flag exists for it to find. Returns the previous value to pass to the restore.
bool dart_core_trycatch_suspend(RCore *core);
void dart_core_trycatch_restore(RCore *core, bool prev);
#ifdef __cplusplus
}
+94
View File
@@ -0,0 +1,94 @@
// Auto-generated from offsets.json by scripts/update-dart-version
// Hash to compressed_word_size lookup table
#ifndef R2C_DART_OFFSETS_H
#define R2C_DART_OFFSETS_H
#include <stdint.h>
// Hash entry: snapshot MD5 hash (32 hex chars) + compressed_word_size
typedef struct {
const char hash[33];
int compressed_word_size;
} DartHashEntry;
static const DartHashEntry dart_hash_entries[] = {
{ "0451907c2eaa8467e848c0067bfe8ed4", 4 }, // Dart 3.13.0
{ "0c7f1aa25fc597a7b3b9a19ed390afa1", 8 }, // Dart 2.13.0
{ "0eb277b50d6ce4069261c69150ce52c9", 4 }, // Dart 2.17.0
{ "1441d6b13b8623fa7fbf61433abebd31", 4 }, // Dart 2.17.6
{ "16ad76edd19b537bf6ea64fdd31977a7", 4 }, // Dart 3.0.5
{ "1ce86630892e2dca9a8543fdb8ed8e22", 4 }, // Dart 3.10.7
{ "24d9d411c2f90c8fbe8907f99e89d4b0", 8 }, // Dart 2.15.0
{ "2858c2c0920495f00b9bce9edf6a8cd9", 4 }, // Dart 3.6.2
{ "287683014dfbd311f6400c655c97a26d", 4 }, // Dart 2.18.6
{ "30c051cb9192f6d6812be3f0b87a6356", 4 }, // Dart 3.6.0
{ "3318fe66091c0ffbb64faec39976cb7d", 8 }, // Dart 2.16.0
{ "34f6eec64e9371856eaaa278ccf56538", 8 }, // Dart 2.13.0
{ "36b0375d284ee2af0d0fffc6e6e48fde", 4 }, // Dart 3.0.5
{ "41be3daaabd524b8aa7423bc24584957", 4 }, // Dart 3.12.0
{ "501ef5cbd64ca70b6b42672346af6a8a", 4 }, // Dart 2.19.0
{ "5191d02bb1d36a9f8609aa7495894e76", 4 }, // Dart 3.1.0
{ "5b97292b25f0a715613b7a28e0734f77", 8 }, // Dart 2.12.0
{ "6093345773c65683f60c20c8e62cbe4a", 8 }, // Dart 2.16.0
{ "617d044d9faa87a98c29f8aad5d2f220", 4 }, // Dart 3.0.3
{ "659a72e41e3276e882709901c27de33d", 8 }, // Dart 2.14.0
{ "6a9b5a03a7e784a4558b10c769f188d9", 4 }, // Dart 2.18.0
{ "71dcc635f2e5dca494525aa13ca9006b", 4 }, // Dart 3.5.0
{ "77b867742ae1ace67fbbb9a9a0d0334e", 4 }, // Dart 3.3.0
{ "77bd0a83c49bfc555a44a0740f15003a", 4 }, // Dart 2.18.0
{ "78da37fed6bf1489361a312568249f3f", 4 }, // Dart 3.11.5
{ "7a1ea3f6f5cf1089a7f6e55d7f20dbfd", 4 }, // Dart 3.14.0
{ "7a5b240780941844bae88eca5dbaa7b8", 8 }, // Dart 2.13.0
{ "7b96ef6dda75bdd1d70be8c2f7cb814e", 8 }, // Dart 2.15.0
{ "7dbbeeb8ef7b91338640dca3927636de", 4 }, // Dart 3.1.3
{ "80a49c7111088100a233b2ae788e1f48", 4 }, // Dart 3.5.0
{ "830f4f59e7969c70b595182826435c19", 4 }, // Dart 3.8.1
{ "8e50e448b241be23b9e990094f4dca39", 4 }, // Dart 2.18.0
{ "8ee4ef7a67df9845fba331734198a953", 8 }, // Dart 2.10.0
{ "90b56a561f70cd55e972cb49b79b3d8b", 4 }, // Dart 3.0.5
{ "97ff04a728735e6b6b098bdf983faaba", 4 }, // Dart 3.9.2
{ "9cf77f4405212c45daf608e1cd646852", 8 }, // Dart 2.14.0
{ "9d6362266b78e4198f784da1a28fd82b", 8 }, // Dart 3.2.0
{ "a0cb0c928b23bc17a26e062b351dc44d", 4 }, // Dart 2.17.6
{ "aa64af18e7d086041ac127cc4bc50c5e", 4 }, // Dart 3.0.5
{ "ace654289f5abc240509fc941453ebc5", 4 }, // Dart 3.12.1
{ "adb4292f3ec25074ca70abcd2d5c7251", 4 }, // Dart 2.19.0
{ "adf563436d12ba0d50ea5beb7f3be1bb", 8 }, // Dart 2.16.0
{ "afcf461a16dfa5e88f88a2aa68764c99", 4 }, // Dart 2.19.0
{ "b0e899ec5a90e4661501f0b69e9dd70f", 4 }, // Dart 2.18.0
{ "b6d0a1f034d158b0d37b51d559379697", 4 }, // Dart 2.18.0
{ "bae16a6858d8c0fe2c437b275eb807cd", 4 }, // Dart 3.7.0
{ "c03fb294bfdaca2b44c552ad35e52d45", 8 }, // Dart 2.14.0
{ "cda356e9bae476c70de33809fd92e009", 4 }, // Dart 3.5.0
{ "ce5d37cf26ea4faac543cd216f3b96d1", 4 }, // Dart 3.0.0
{ "d20a1be77c3d3c41b2a5accaee1ce549", 8 }, // Dart 3.4.3
{ "d56742caf7b3b3f4bd2df93a9bbb5503", 8 }, // Dart 2.16.0
{ "d91c0e6f35f0eb2e44124e8f42aa44a7", 4 }, // Dart 3.7.0
{ "d979fa911b038a8682e98cc8c8569261", 8 }, // Dart 2.10.0
{ "ded6ef11c73fdc638d6ff6d3ad22a67b", 4 }, // Dart 2.17.6
{ "e036be9d5df68283e1a043e348fa7ff4", 8 }, // Dart 2.12.0
{ "e4a09dbf2bb120fe4674e0576617a0dc", 8 }, // Dart 2.13.0
{ "e727fae79fb45aac639252a9453b785e", 4 }, // Dart 2.19.1
{ "ee1eb666c76a5cb7746faf39d0b97547", 4 }, // Dart 3.3.0
{ "f10776149bf76be288def3c2ca73bdc1", 8 }, // Dart 2.15.0
{ "f71c76320d35b65f1164dbaa6d95fe09", 8 }, // Dart 3.2.5
{ "f91b8b03bf7f30a5e983fd19b23d978d", 4 }, // Dart 2.18.2
{ "f956f595844a2f845a55707faaaa51e4", 4 }, // Dart 3.6.2
{ "fa07b8a2d866963d1117ecdcea9287c8", 4 }, // Dart 3.8.0
{ "fe53c4fdba846005b70514aec3840296", 8 }, // Dart 3.4.0
};
#define DART_HASH_ENTRIES_COUNT 64
// Default offset arrays (identical across all known Dart versions)
#define DART_DEFAULT_EP_OFFSETS \
{ 8, 24, 16, 32 }
#define DART_DEFAULT_EP_OFFSETS_COUNT 4
#define DART_DEFAULT_OWNER_OFFSETS \
{ 56 }
#define DART_DEFAULT_OWNER_OFFSETS_COUNT 1
#define DART_DEFAULT_NAME_OFFSETS \
{ 24, 8 }
#define DART_DEFAULT_NAME_OFFSETS_COUNT 2
#endif // R2C_DART_OFFSETS_H
+14 -3
View File
@@ -82,12 +82,17 @@ typedef struct DartClassInfo {
char *library_name;
ut64 library_ref;
ut64 super_class_ref;
ut64 super_type_ref;
ut64 interfaces_ref;
char *super_class_name;
ut32 class_id;
ut32 instance_size;
ut32 next_field_offset;
ut32 type_argument_offset;
ut32 num_type_parameters;
ut32 flags;
ut64 field_bitmap;
bool field_bitmap_present;
RList *enums;
RList *fields;
RList *interfaces;
@@ -149,6 +154,7 @@ typedef struct DartInstructionTableEntry {
ut32 stack_map_offset;
bool has_code;
char *name;
char *signature;
} DartInstructionTableEntry;
void dart_instruction_table_entry_fini(DartInstructionTableEntry *ie);
@@ -160,11 +166,14 @@ typedef void(*DartPoolFunctionCallback)(const char *name, ut64 addr, ut64 size,
// Main API - all functions take DartCtx*
// ============================================================================
int dart_pool_enumerate(DartCtx *ctx, const char *libapp_path, DartPoolFunctionCallback on_fn, void *user, ut64 *out_base, ut64 *out_heap_base);
bool dart_ctx_set_profile_override(DartCtx *ctx, const char *spec);
const char *dart_ctx_version(DartCtx *ctx);
const char *dart_ctx_hash(DartCtx *ctx);
int dart_pool_scan(DartCtx *ctx, DartPoolFunctionCallback on_fn, void *user);
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_pool(DartCtx *ctx, int fmt);
char *dart_pool_dump_object(DartCtx *ctx, const char *spec, int fmt);
// ============================================================================
@@ -189,11 +198,12 @@ void dart_string_ref_free(DartStringRef *sr);
// ============================================================================
RList *dart_pool_extract_strings(DartCtx *ctx);
RList *dart_pool_extract_object_pool_strings(DartCtx *ctx);
RList *dart_pool_extract_pool_strings(DartCtx *ctx);
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);
// ============================================================================
@@ -201,6 +211,7 @@ char *dart_pool_dump_sbom(DartCtx *ctx, const char *input_path, int fmt);
// ============================================================================
RVecDartInstructionTableEntry *dart_pool_extract_instruction_table(DartCtx *ctx);
char *dart_pool_dump_entry(DartCtx *ctx, int fmt);
char *dart_pool_dump_it(DartCtx *ctx, int fmt);
void dart_instruction_table_entry_free(DartInstructionTableEntry *ie);
void dart_instruction_table_list_free(RVecDartInstructionTableEntry *list);
+24 -2
View File
@@ -41,19 +41,27 @@ typedef struct {
ut64 container_payload_offset;
ut64 container_payload_size;
ut64 container_macho_offset;
char snapshot_hash_actual[33];
char snapshot_hash[33];
char snapshot_hash_override[33];
char dart_version_override[32];
const DartVerLayout *layout;
int compressed_word_size;
int version_source; // DartVersionSource: how the layout/version was chosen
bool layout_probed;
HtUP *name_by_ep;
char **name_by_code_index;
ut64 name_by_code_index_count;
// Recovered Dart signature per code index, format "RET(T,T,...)" where the
// first param of an instance method is the receiver typed as the owner
// class. Parallel to name_by_code_index; NULL where unresolved.
char **signature_by_code_index;
ut64 signature_by_code_index_count;
ut8 *owner_kind_by_code_index;
ut64 owner_kind_by_code_index_count;
RList *name_pool;
int name_pool_idx;
RList *strings;
RList *classes;
RList *functions;
void **refs;
ut64 refs_count;
ut64 num_base_objects;
@@ -73,9 +81,23 @@ typedef struct {
int dump_fields;
int dump_strings;
bool dump_string_refs;
ut64 str_limit; // max strings to print (0 = unlimited)
ut64 str_scan_limit; // max strings to carve in the fuzzy scan (0 = unlimited)
int str_minlen; // skip printing strings shorter than this
int str_maxlen; // skip printing strings longer than this (0 = unlimited)
const char *str_filter; // only print strings matching this regex
const char *obf_map_path;
HtPP *obf_by_obfuscated;
bool obf_map_tried;
// Windowed read cache for read_mem, to avoid one r_io_read_at syscall per
// byte while decoding cluster streams (which read one byte at a time).
// Scoped to a single command invocation and released by dart_obf_fini.
// Heap-allocated so that DartCtx copies share the windows together with
// their addresses.
struct dart_read_cache_t *rmem;
} DartCtx;
// Release a read_mem window cache (NULL is accepted).
void dart_read_cache_free(struct dart_read_cache_t *cache);
#endif
+79 -18
View File
@@ -16,10 +16,52 @@ extern "C" {
typedef enum {
DART_TAG_STYLE_CID_INT32 = 0, // v2.10-2.13: raw int32 CID
DART_TAG_STYLE_CID_SHIFT1 = 1, // v2.14-3.3: (cid << 1) | canonical
DART_TAG_STYLE_OBJECT_HEADER = 2 // v3.4.3+: ObjectHeader with ClassIdTag at bits 12-31
DART_TAG_STYLE_OBJECT_HEADER = 2 // v3.4.3+ and known 2.18.2 exception
} DartTagStyle;
// Ordered fields following the snapshot feature string. The field count alone
// is insufficient because the meaning of the five-field layouts changed.
typedef enum {
DART_HEADER_STYLE_MODERN = 0, // nb, no, nc, itlen, itdata (2.18+)
DART_HEADER_STYLE_210, // nb, no, nc, field_table_len
DART_HEADER_STYLE_212, // nb, no, canonical_nc, nc, field_table_len
DART_HEADER_STYLE_214, // nb, no, nc, field_table_len, itlen
DART_HEADER_STYLE_216 // nb, no, nc, field_table_len, itlen, itdata
} DartHeaderStyle;
// Object references use the ordinary unsigned stream integer through Dart
// 2.17. Dart 2.18 introduced a distinct big-endian RefId encoding whose
// signed terminal byte is biased by 128.
typedef enum {
DART_REF_ENCODING_REF_ID128 = 0,
DART_REF_ENCODING_UNSIGNED
} DartRefEncoding;
// ObjectPool entry type/patch/behavior bit packing changed independently of
// the surrounding cluster layout.
typedef enum {
DART_POOL_ENCODING_MODERN = 0, // type[0..3], patch[4], behavior[5..7]
DART_POOL_ENCODING_LOW7, // type[0..6], patch[7], tagged=0, immediate=1
DART_POOL_ENCODING_LOW7_SWAPPED // type[0..6], patch[7], immediate=0, tagged=1
} DartPoolEncoding;
typedef enum {
DART_CODE_ALLOC_STATE_DEFERRED = 0, // count, state words, deferred count/state words
DART_CODE_ALLOC_DEFERRED_ONLY // count, deferred count
} DartCodeAlloc;
// How the Dart version/layout for a snapshot was determined. Reported so users
// can distinguish exact, structurally validated, and fallback profiles.
typedef enum {
DART_VERSION_SOURCE_EXACT = 0, // snapshot hash matched a known SDK release
DART_VERSION_SOURCE_OVERRIDE = 1, // user forced a profile via -D
DART_VERSION_SOURCE_PROBE = 2, // unknown hash, layout selected by structural validation
DART_VERSION_SOURCE_FINGERPRINT = 3, // unknown hash, conservative fallback profile
DART_VERSION_SOURCE_UNKNOWN = 4 // no snapshot available
} DartVersionSource;
// Version layout information for Dart snapshots
// CID values are stored in dart_cid.c's cid_tables[] and accessed via dart_cid_get()
typedef struct {
char hash[33];
const char *dart_version;
@@ -29,36 +71,55 @@ typedef struct {
ut64 it_cap;
DartTagStyle tag_style;
int header_fields;
int cid_class;
int cid_function;
int cid_code;
int cid_string;
int cid_one_byte_string;
int cid_two_byte_string;
int cid_array;
int cid_mint;
int cid_object_pool;
int num_predefined_cids;
DartHeaderStyle header_style;
DartRefEncoding ref_encoding;
DartPoolEncoding pool_encoding;
DartCodeAlloc code_alloc;
int code_refs;
int code_leading_refs;
bool code_has_text_offset;
bool single_snapshot;
bool class_alloc_fixed;
bool closure_variable;
bool class_top_level_cid20;
} DartVerLayout;
// Lookup Dart version from a snapshot hash (MD5)
// Returns NULL if hash is not recognized
const char *dart_version_from_hash(const char *hash);
// Lookup version profile from a Dart version string
// Returns NULL if version is not recognized
// Lookup version profile from a Dart version string using floor semantics.
// Returns NULL if the version string is not a dotted numeric version.
const DartVerLayout *dart_profile_from_version(const char *version);
// Pick a layout structure based on hash, with fallback to defaults
// Returns a newly allocated DartVerLayout (caller must free)
// If hash is unknown, uses v3.9.2 defaults
DartVerLayout *dart_pick_layout_by_hash(const char *hash);
// Enumerate the built-in range-start profiles for structural probing.
int dart_profile_count(void);
const DartVerLayout *dart_profile_at(int index);
// Free a DartVerLayout allocated by dart_pick_layout_by_hash
// Pick a layout structure based on hash, with fallback to a compatible baseline
// Returns a newly allocated DartVerLayout (caller must free)
// If hash is unknown, fingerprints against the newest split-snapshot profile.
DartVerLayout *dart_layout_from_hash(const char *hash);
// Newest known layout profile.
const DartVerLayout *dart_newest_profile(void);
// Conservative profile used until an unknown hash can be structurally probed.
// Single-snapshot mechanics require positive evidence and are not inherited.
const DartVerLayout *dart_fingerprint_profile(void);
// Human-readable name for a DartVersionSource value.
const char *dart_version_source_str(int source);
// Free a DartVerLayout allocated by dart_layout_from_hash
void dart_ver_layout_free(DartVerLayout *layout);
void dart_version_set_verbose(int level);
// Compare dotted Dart SDK versions numerically.
// Returns -1, 0, 1, or -2 when either input is not a dotted numeric version.
int dart_version_compare(const char *a, const char *b);
#ifdef __cplusplus
}
#endif
+2 -2
View File
@@ -1,9 +1,9 @@
#ifndef R2FLUTTER_VERSION_H
#define R2FLUTTER_VERSION_H
#define R2FLUTTER_VERSION "0.2.0"
#define R2FLUTTER_VERSION "0.6.0"
#define R2FLUTTER_VERSION_MAJOR 0
#define R2FLUTTER_VERSION_MINOR 2
#define R2FLUTTER_VERSION_MINOR 6
#define R2FLUTTER_VERSION_PATCH 0
#endif
+52 -3
View File
@@ -1,12 +1,48 @@
project(
'r2flutter',
'c',
version : '0.2.0',
meson_version : '>=0.53.0',
version : '0.6.0',
meson_version : '>=0.56.0',
default_options : ['warning_level=2', 'werror=false']
)
r_core_dep = dependency('r_core', method: 'pkg-config')
cc = meson.get_compiler('c')
r2 = find_program('r2', required: true)
r2_cflags_res = run_command(r2, '-H', 'R2_CFLAGS', check: true)
r2_ldflags_res = run_command(r2, '-H', 'R2_LDFLAGS', check: true)
r2_libs_res = run_command(r2, '-H', 'R2_LIBS', check: true)
r2_plugdir_res = run_command(r2, '-H', 'R2_LIBR_PLUGINS', check: false)
r2_compile_args = r2_cflags_res.stdout().strip().split()
r2_link_args = r2_ldflags_res.stdout().strip().split() + r2_libs_res.stdout().strip().split()
r2_plugdir = r2_plugdir_res.stdout().strip()
if r2_plugdir == ''
r2_plugdir = join_paths(get_option('prefix'), get_option('libdir'), 'radare2', 'last')
endif
# r2 -H always reports gcc style flags, msvc needs its own spelling for them
if cc.get_argument_syntax() == 'msvc'
msvc_link_args = []
foreach flag : r2_link_args
if flag.startswith('-L')
msvc_link_args += '/LIBPATH:' + flag.substring(2)
elif flag.startswith('-l')
msvc_link_args += flag.substring(2) + '.lib'
else
msvc_link_args += flag
endif
endforeach
r2_link_args = msvc_link_args
# posix names like strdup are available, just noisy without this
r2_compile_args += ['-D_CRT_SECURE_NO_WARNINGS', '-D_CRT_NONSTDC_NO_DEPRECATE']
endif
r_core_dep = declare_dependency(
compile_args: r2_compile_args,
link_args: r2_link_args,
)
r2flutter_inc = include_directories('include')
r2flutter_sources = files(
@@ -54,4 +90,17 @@ r2flutter_cli = executable(
install: true,
)
# The radare2 core plugin, loaded by r2 as core_flutter.{so,dylib,dll}
core_flutter = shared_module(
'core_flutter',
'src/r2/core_flutter.c',
'src/r2/flutter_analysis.c',
name_prefix: '',
include_directories: r2flutter_inc,
link_with: r2flutter_static,
dependencies: r_core_dep,
install: true,
install_dir: r2_plugdir,
)
test('r2flutter-version', r2flutter_cli, args: ['-V'])
+97 -742
View File
@@ -1,746 +1,101 @@
{
"arch": "arm64",
"defaults": {
"code_entry_point_offsets": [8, 24, 16, 32],
"code_owner_offsets": [56],
"function_name_offsets": [24, 8]
},
"profiles": {
"2.10.0": {"cws": 8, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_INT32", "header_fields": 4, "typed_data_internal_base": 108, "typed_data_internal_limit": 150, "typed_data_stride": 3, "cids": {"ILLEGAL": 0, "CLASS": 4, "PATCH_CLASS": 5, "FUNCTION": 6, "CLOSURE_DATA": 7, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 77, "CODE": 16, "OBJECT_POOL": 20, "PC_DESCRIPTORS": 21, "CODE_SOURCE_MAP": 22, "COMPRESSED_STACK_MAPS": 23, "LOCAL_VAR_DESCRIPTORS": 24, "EXCEPTION_HANDLERS": 25, "CONTEXT": 26, "CONTEXT_SCOPE": 27, "SINGLE_TARGET_CACHE": 29, "UNLINKED_CALL": 30, "MONOMORPHIC_SMIABLE_CALL": 31, "CALL_SITE_DATA": 32, "ICDATA": 33, "MEGAMORPHIC_CACHE": 34, "SUBTYPE_TEST_CACHE": 35, "LOADING_UNIT": 36, "API_ERROR": 38, "LANGUAGE_ERROR": 39, "UNHANDLED_EXCEPTION": 40, "UNWIND_ERROR": 41, "INSTANCE": 42, "LIBRARY_PREFIX": 43, "TYPE_ARGUMENTS": 44, "TYPE": 46, "TYPE_REF": 47, "TYPE_PARAMETER": 48, "CLOSURE": 49, "MINT": 53, "DOUBLE": 54, "FLOAT32X4": 57, "INT32X4": 58, "FLOAT64X2": 59, "TYPED_DATA": 61, "EXTERNAL_TYPED_DATA": 62, "TYPED_DATA_VIEW": 63, "CAPABILITY": 66, "RECEIVE_PORT": 67, "SEND_PORT": 68, "STACK_TRACE": 69, "REGEXP": 70, "WEAK_PROPERTY": 71, "FUTURE_OR": 74, "USER_TAG": 75, "TRANSFERABLE_TYPED_DATA": 76, "ARRAY": 78, "IMMUTABLE_ARRAY": 79, "GROWABLE_OBJECT_ARRAY": 56, "STRING": 80, "ONE_BYTE_STRING": 81, "TWO_BYTE_STRING": 82, "NUM_PREDEFINED_CIDS": 156}},
"2.12.0": {"cws": 8, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_INT32", "header_fields": 5, "typed_data_internal_base": 100, "typed_data_internal_limit": 142, "typed_data_stride": 3, "cids": {"ILLEGAL": 0, "CLASS": 4, "PATCH_CLASS": 5, "FUNCTION": 6, "CLOSURE_DATA": 7, "FFI_TRAMPOLINE_DATA": 8, "FIELD": 9, "SCRIPT": 10, "LIBRARY": 11, "NAMESPACE": 12, "KERNEL_PROGRAM_INFO": 13, "WEAK_SERIALIZATION_REFERENCE": 74, "CODE": 14, "OBJECT_POOL": 17, "PC_DESCRIPTORS": 18, "CODE_SOURCE_MAP": 19, "COMPRESSED_STACK_MAPS": 20, "LOCAL_VAR_DESCRIPTORS": 21, "EXCEPTION_HANDLERS": 22, "CONTEXT": 23, "CONTEXT_SCOPE": 24, "SINGLE_TARGET_CACHE": 25, "UNLINKED_CALL": 26, "MONOMORPHIC_SMIABLE_CALL": 27, "CALL_SITE_DATA": 28, "ICDATA": 29, "MEGAMORPHIC_CACHE": 30, "SUBTYPE_TEST_CACHE": 31, "LOADING_UNIT": 32, "API_ERROR": 34, "LANGUAGE_ERROR": 35, "UNHANDLED_EXCEPTION": 36, "UNWIND_ERROR": 37, "INSTANCE": 38, "LIBRARY_PREFIX": 39, "TYPE_ARGUMENTS": 40, "TYPE": 42, "FUNCTION_TYPE": 43, "TYPE_REF": 44, "TYPE_PARAMETER": 45, "CLOSURE": 46, "MINT": 50, "DOUBLE": 51, "FLOAT32X4": 54, "INT32X4": 55, "FLOAT64X2": 56, "TYPED_DATA": 58, "EXTERNAL_TYPED_DATA": 59, "TYPED_DATA_VIEW": 60, "CAPABILITY": 63, "RECEIVE_PORT": 64, "SEND_PORT": 65, "STACK_TRACE": 66, "REGEXP": 67, "WEAK_PROPERTY": 68, "FUTURE_OR": 71, "USER_TAG": 72, "TRANSFERABLE_TYPED_DATA": 73, "ARRAY": 75, "IMMUTABLE_ARRAY": 76, "GROWABLE_OBJECT_ARRAY": 53, "STRING": 77, "ONE_BYTE_STRING": 78, "TWO_BYTE_STRING": 79, "NUM_PREDEFINED_CIDS": 148}},
"2.13.0": {"cws": 8, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_INT32", "header_fields": 5, "typed_data_internal_base": 100, "typed_data_internal_limit": 142, "typed_data_stride": 3, "cids": {"ILLEGAL": 0, "CLASS": 4, "PATCH_CLASS": 5, "FUNCTION": 6, "CLOSURE_DATA": 7, "FFI_TRAMPOLINE_DATA": 8, "FIELD": 9, "SCRIPT": 10, "LIBRARY": 11, "NAMESPACE": 12, "KERNEL_PROGRAM_INFO": 13, "WEAK_SERIALIZATION_REFERENCE": 14, "CODE": 15, "OBJECT_POOL": 18, "PC_DESCRIPTORS": 19, "CODE_SOURCE_MAP": 20, "COMPRESSED_STACK_MAPS": 21, "LOCAL_VAR_DESCRIPTORS": 22, "EXCEPTION_HANDLERS": 23, "CONTEXT": 24, "CONTEXT_SCOPE": 25, "SINGLE_TARGET_CACHE": 26, "UNLINKED_CALL": 27, "MONOMORPHIC_SMIABLE_CALL": 28, "CALL_SITE_DATA": 29, "ICDATA": 30, "MEGAMORPHIC_CACHE": 31, "SUBTYPE_TEST_CACHE": 32, "LOADING_UNIT": 33, "API_ERROR": 35, "LANGUAGE_ERROR": 36, "UNHANDLED_EXCEPTION": 37, "UNWIND_ERROR": 38, "INSTANCE": 39, "LIBRARY_PREFIX": 40, "TYPE_ARGUMENTS": 41, "TYPE": 43, "FUNCTION_TYPE": 44, "TYPE_REF": 45, "TYPE_PARAMETER": 46, "CLOSURE": 47, "MINT": 51, "DOUBLE": 52, "FLOAT32X4": 55, "INT32X4": 56, "FLOAT64X2": 57, "TYPED_DATA": 59, "EXTERNAL_TYPED_DATA": 60, "TYPED_DATA_VIEW": 61, "CAPABILITY": 64, "RECEIVE_PORT": 65, "SEND_PORT": 66, "STACK_TRACE": 67, "REGEXP": 68, "WEAK_PROPERTY": 69, "FUTURE_OR": 72, "USER_TAG": 73, "TRANSFERABLE_TYPED_DATA": 74, "ARRAY": 75, "IMMUTABLE_ARRAY": 76, "GROWABLE_OBJECT_ARRAY": 54, "STRING": 77, "ONE_BYTE_STRING": 78, "TWO_BYTE_STRING": 79, "NUM_PREDEFINED_CIDS": 148}},
"2.14.0": {"cws": 8, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_SHIFT1", "header_fields": 5, "typed_data_internal_base": 104, "typed_data_internal_limit": 146, "typed_data_stride": 3, "cids": {"ILLEGAL": 0, "CLASS": 4, "PATCH_CLASS": 5, "FUNCTION": 6, "TYPE_PARAMETERS": 7, "CLOSURE_DATA": 8, "FFI_TRAMPOLINE_DATA": 9, "FIELD": 10, "SCRIPT": 11, "LIBRARY": 12, "NAMESPACE": 13, "KERNEL_PROGRAM_INFO": 14, "WEAK_SERIALIZATION_REFERENCE": 15, "CODE": 16, "OBJECT_POOL": 20, "PC_DESCRIPTORS": 21, "CODE_SOURCE_MAP": 22, "COMPRESSED_STACK_MAPS": 23, "LOCAL_VAR_DESCRIPTORS": 24, "EXCEPTION_HANDLERS": 25, "CONTEXT": 26, "CONTEXT_SCOPE": 27, "SENTINEL": 28, "SINGLE_TARGET_CACHE": 29, "UNLINKED_CALL": 30, "MONOMORPHIC_SMIABLE_CALL": 31, "CALL_SITE_DATA": 32, "ICDATA": 33, "MEGAMORPHIC_CACHE": 34, "SUBTYPE_TEST_CACHE": 35, "LOADING_UNIT": 36, "API_ERROR": 38, "LANGUAGE_ERROR": 39, "UNHANDLED_EXCEPTION": 40, "UNWIND_ERROR": 41, "INSTANCE": 42, "LIBRARY_PREFIX": 43, "TYPE_ARGUMENTS": 44, "TYPE": 46, "FUNCTION_TYPE": 47, "TYPE_REF": 48, "TYPE_PARAMETER": 49, "CLOSURE": 50, "MINT": 54, "DOUBLE": 55, "FLOAT32X4": 58, "INT32X4": 59, "FLOAT64X2": 60, "TYPED_DATA": 62, "EXTERNAL_TYPED_DATA": 63, "TYPED_DATA_VIEW": 64, "CAPABILITY": 67, "RECEIVE_PORT": 68, "SEND_PORT": 69, "STACK_TRACE": 70, "REGEXP": 71, "WEAK_PROPERTY": 72, "FUTURE_OR": 74, "USER_TAG": 75, "TRANSFERABLE_TYPED_DATA": 76, "ARRAY": 79, "IMMUTABLE_ARRAY": 80, "GROWABLE_OBJECT_ARRAY": 57, "STRING": 81, "ONE_BYTE_STRING": 82, "TWO_BYTE_STRING": 83, "NUM_PREDEFINED_CIDS": 152}},
"2.15.0": {"cws": 8, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_SHIFT1", "header_fields": 5, "typed_data_internal_base": 106, "typed_data_internal_limit": 148, "typed_data_stride": 3, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "CODE": 17, "OBJECT_POOL": 21, "PC_DESCRIPTORS": 22, "CODE_SOURCE_MAP": 23, "COMPRESSED_STACK_MAPS": 24, "LOCAL_VAR_DESCRIPTORS": 25, "EXCEPTION_HANDLERS": 26, "CONTEXT": 27, "CONTEXT_SCOPE": 28, "SENTINEL": 29, "SINGLE_TARGET_CACHE": 30, "UNLINKED_CALL": 31, "MONOMORPHIC_SMIABLE_CALL": 32, "CALL_SITE_DATA": 33, "ICDATA": 34, "MEGAMORPHIC_CACHE": 35, "SUBTYPE_TEST_CACHE": 36, "LOADING_UNIT": 37, "API_ERROR": 39, "LANGUAGE_ERROR": 40, "UNHANDLED_EXCEPTION": 41, "UNWIND_ERROR": 42, "INSTANCE": 43, "LIBRARY_PREFIX": 44, "TYPE_ARGUMENTS": 45, "TYPE": 47, "FUNCTION_TYPE": 48, "TYPE_REF": 49, "TYPE_PARAMETER": 50, "CLOSURE": 51, "MINT": 55, "DOUBLE": 56, "FLOAT32X4": 58, "INT32X4": 59, "FLOAT64X2": 60, "TYPED_DATA": 62, "EXTERNAL_TYPED_DATA": 63, "TYPED_DATA_VIEW": 64, "CAPABILITY": 67, "RECEIVE_PORT": 68, "SEND_PORT": 69, "STACK_TRACE": 70, "REGEXP": 71, "WEAK_PROPERTY": 72, "FUTURE_OR": 74, "USER_TAG": 75, "TRANSFERABLE_TYPED_DATA": 76, "ARRAY": 81, "IMMUTABLE_ARRAY": 82, "GROWABLE_OBJECT_ARRAY": 83, "STRING": 84, "ONE_BYTE_STRING": 85, "TWO_BYTE_STRING": 86, "NUM_PREDEFINED_CIDS": 154}},
"2.16.0": {"cws": 8, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_SHIFT1", "header_fields": 6, "typed_data_internal_base": 106, "typed_data_internal_limit": 148, "typed_data_stride": 3, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "CODE": 17, "OBJECT_POOL": 21, "PC_DESCRIPTORS": 22, "CODE_SOURCE_MAP": 23, "COMPRESSED_STACK_MAPS": 24, "LOCAL_VAR_DESCRIPTORS": 25, "EXCEPTION_HANDLERS": 26, "CONTEXT": 27, "CONTEXT_SCOPE": 28, "SENTINEL": 29, "SINGLE_TARGET_CACHE": 30, "UNLINKED_CALL": 31, "MONOMORPHIC_SMIABLE_CALL": 32, "CALL_SITE_DATA": 33, "ICDATA": 34, "MEGAMORPHIC_CACHE": 35, "SUBTYPE_TEST_CACHE": 36, "LOADING_UNIT": 37, "API_ERROR": 39, "LANGUAGE_ERROR": 40, "UNHANDLED_EXCEPTION": 41, "UNWIND_ERROR": 42, "INSTANCE": 43, "LIBRARY_PREFIX": 44, "TYPE_ARGUMENTS": 45, "TYPE": 47, "FUNCTION_TYPE": 48, "TYPE_REF": 49, "TYPE_PARAMETER": 50, "CLOSURE": 51, "MINT": 55, "DOUBLE": 56, "FLOAT32X4": 58, "INT32X4": 59, "FLOAT64X2": 60, "TYPED_DATA": 62, "EXTERNAL_TYPED_DATA": 63, "TYPED_DATA_VIEW": 64, "CAPABILITY": 67, "RECEIVE_PORT": 68, "SEND_PORT": 69, "STACK_TRACE": 70, "REGEXP": 71, "WEAK_PROPERTY": 72, "FUTURE_OR": 74, "USER_TAG": 75, "TRANSFERABLE_TYPED_DATA": 76, "ARRAY": 81, "IMMUTABLE_ARRAY": 82, "GROWABLE_OBJECT_ARRAY": 83, "STRING": 84, "ONE_BYTE_STRING": 85, "TWO_BYTE_STRING": 86, "NUM_PREDEFINED_CIDS": 154}},
"2.17.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_SHIFT1", "header_fields": 6, "typed_data_internal_base": 110, "typed_data_internal_limit": 152, "typed_data_stride": 3, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "CODE": 17, "OBJECT_POOL": 21, "PC_DESCRIPTORS": 22, "CODE_SOURCE_MAP": 23, "COMPRESSED_STACK_MAPS": 24, "LOCAL_VAR_DESCRIPTORS": 25, "EXCEPTION_HANDLERS": 26, "CONTEXT": 27, "CONTEXT_SCOPE": 28, "SENTINEL": 29, "SINGLE_TARGET_CACHE": 30, "UNLINKED_CALL": 31, "MONOMORPHIC_SMIABLE_CALL": 32, "CALL_SITE_DATA": 33, "ICDATA": 34, "MEGAMORPHIC_CACHE": 35, "SUBTYPE_TEST_CACHE": 36, "LOADING_UNIT": 37, "API_ERROR": 39, "LANGUAGE_ERROR": 40, "UNHANDLED_EXCEPTION": 41, "UNWIND_ERROR": 42, "INSTANCE": 43, "LIBRARY_PREFIX": 44, "TYPE_ARGUMENTS": 45, "TYPE": 47, "FUNCTION_TYPE": 52, "TYPE_REF": 53, "TYPE_PARAMETER": 54, "CLOSURE": 55, "MINT": 59, "DOUBLE": 60, "FLOAT32X4": 62, "INT32X4": 63, "FLOAT64X2": 64, "TYPED_DATA": 66, "EXTERNAL_TYPED_DATA": 67, "TYPED_DATA_VIEW": 68, "CAPABILITY": 71, "RECEIVE_PORT": 72, "SEND_PORT": 73, "STACK_TRACE": 74, "REGEXP": 75, "WEAK_PROPERTY": 76, "WEAK_REFERENCE": 77, "FUTURE_OR": 79, "USER_TAG": 80, "TRANSFERABLE_TYPED_DATA": 81, "ARRAY": 86, "IMMUTABLE_ARRAY": 87, "GROWABLE_OBJECT_ARRAY": 88, "STRING": 89, "ONE_BYTE_STRING": 90, "TWO_BYTE_STRING": 91, "NUM_PREDEFINED_CIDS": 158}},
"2.18.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_SHIFT1", "header_fields": 5, "typed_data_internal_base": 111, "typed_data_internal_limit": 153, "typed_data_stride": 3, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "CODE": 17, "OBJECT_POOL": 21, "PC_DESCRIPTORS": 22, "CODE_SOURCE_MAP": 23, "COMPRESSED_STACK_MAPS": 24, "LOCAL_VAR_DESCRIPTORS": 25, "EXCEPTION_HANDLERS": 26, "CONTEXT": 27, "CONTEXT_SCOPE": 28, "SENTINEL": 29, "SINGLE_TARGET_CACHE": 30, "UNLINKED_CALL": 31, "MONOMORPHIC_SMIABLE_CALL": 32, "CALL_SITE_DATA": 33, "ICDATA": 34, "MEGAMORPHIC_CACHE": 35, "SUBTYPE_TEST_CACHE": 36, "LOADING_UNIT": 37, "API_ERROR": 39, "LANGUAGE_ERROR": 40, "UNHANDLED_EXCEPTION": 41, "UNWIND_ERROR": 42, "INSTANCE": 43, "LIBRARY_PREFIX": 44, "TYPE_ARGUMENTS": 45, "TYPE": 47, "FUNCTION_TYPE": 52, "TYPE_REF": 53, "TYPE_PARAMETER": 54, "CLOSURE": 55, "MINT": 59, "DOUBLE": 60, "FLOAT32X4": 62, "INT32X4": 63, "FLOAT64X2": 64, "TYPED_DATA": 66, "EXTERNAL_TYPED_DATA": 67, "TYPED_DATA_VIEW": 68, "CAPABILITY": 71, "RECEIVE_PORT": 72, "SEND_PORT": 73, "STACK_TRACE": 74, "SUSPEND_STATE": 75, "REGEXP": 76, "WEAK_PROPERTY": 77, "WEAK_REFERENCE": 78, "FUTURE_OR": 80, "USER_TAG": 81, "TRANSFERABLE_TYPED_DATA": 82, "MAP": 83, "CONST_MAP": 84, "SET": 85, "CONST_SET": 86, "ARRAY": 87, "IMMUTABLE_ARRAY": 88, "GROWABLE_OBJECT_ARRAY": 89, "STRING": 90, "ONE_BYTE_STRING": 91, "TWO_BYTE_STRING": 92, "NUM_PREDEFINED_CIDS": 159}},
"2.18.2": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 112, "typed_data_internal_limit": 168, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 23, "PC_DESCRIPTORS": 24, "CODE_SOURCE_MAP": 25, "COMPRESSED_STACK_MAPS": 26, "LOCAL_VAR_DESCRIPTORS": 27, "EXCEPTION_HANDLERS": 28, "CONTEXT": 29, "CONTEXT_SCOPE": 30, "SENTINEL": 31, "SINGLE_TARGET_CACHE": 32, "UNLINKED_CALL": 35, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 36, "MEGAMORPHIC_CACHE": 37, "SUBTYPE_TEST_CACHE": 38, "LOADING_UNIT": 39, "API_ERROR": 41, "LANGUAGE_ERROR": 42, "UNHANDLED_EXCEPTION": 43, "UNWIND_ERROR": 44, "INSTANCE": 45, "LIBRARY_PREFIX": 46, "TYPE_ARGUMENTS": 47, "TYPE": 49, "FUNCTION_TYPE": 50, "RECORD_TYPE": 51, "TYPE_PARAMETER": 52, "CLOSURE": 57, "MINT": 61, "DOUBLE": 62, "FLOAT32X4": 64, "INT32X4": 65, "FLOAT64X2": 66, "RECORD": 67, "TYPED_DATA": 69, "EXTERNAL_TYPED_DATA": 70, "TYPED_DATA_VIEW": 71, "CAPABILITY": 74, "RECEIVE_PORT": 75, "SEND_PORT": 76, "STACK_TRACE": 77, "SUSPEND_STATE": 78, "REGEXP": 79, "WEAK_PROPERTY": 80, "WEAK_REFERENCE": 81, "FUTURE_OR": 83, "USER_TAG": 84, "TRANSFERABLE_TYPED_DATA": 85, "MAP": 86, "CONST_MAP": 87, "SET": 88, "CONST_SET": 89, "ARRAY": 90, "IMMUTABLE_ARRAY": 91, "GROWABLE_OBJECT_ARRAY": 92, "STRING": 93, "ONE_BYTE_STRING": 94, "TWO_BYTE_STRING": 95, "NUM_PREDEFINED_CIDS": 175}, "note": "Uses fallback CIDs from 3.9.2 for the in-tree Flutter engine snapshot; not standalone Dart SDK 2.18.2 CIDs."},
"2.18.3": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_SHIFT1", "header_fields": 5, "typed_data_internal_base": 111, "typed_data_internal_limit": 153, "typed_data_stride": 3, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "CODE": 17, "OBJECT_POOL": 21, "PC_DESCRIPTORS": 22, "CODE_SOURCE_MAP": 23, "COMPRESSED_STACK_MAPS": 24, "LOCAL_VAR_DESCRIPTORS": 25, "EXCEPTION_HANDLERS": 26, "CONTEXT": 27, "CONTEXT_SCOPE": 28, "SENTINEL": 29, "SINGLE_TARGET_CACHE": 30, "UNLINKED_CALL": 31, "MONOMORPHIC_SMIABLE_CALL": 32, "CALL_SITE_DATA": 33, "ICDATA": 34, "MEGAMORPHIC_CACHE": 35, "SUBTYPE_TEST_CACHE": 36, "LOADING_UNIT": 37, "API_ERROR": 39, "LANGUAGE_ERROR": 40, "UNHANDLED_EXCEPTION": 41, "UNWIND_ERROR": 42, "INSTANCE": 43, "LIBRARY_PREFIX": 44, "TYPE_ARGUMENTS": 45, "TYPE": 47, "FUNCTION_TYPE": 52, "TYPE_REF": 53, "TYPE_PARAMETER": 54, "CLOSURE": 55, "MINT": 59, "DOUBLE": 60, "FLOAT32X4": 62, "INT32X4": 63, "FLOAT64X2": 64, "TYPED_DATA": 66, "EXTERNAL_TYPED_DATA": 67, "TYPED_DATA_VIEW": 68, "CAPABILITY": 71, "RECEIVE_PORT": 72, "SEND_PORT": 73, "STACK_TRACE": 74, "SUSPEND_STATE": 75, "REGEXP": 76, "WEAK_PROPERTY": 77, "WEAK_REFERENCE": 78, "FUTURE_OR": 80, "USER_TAG": 81, "TRANSFERABLE_TYPED_DATA": 82, "MAP": 83, "CONST_MAP": 84, "SET": 85, "CONST_SET": 86, "ARRAY": 87, "IMMUTABLE_ARRAY": 88, "GROWABLE_OBJECT_ARRAY": 89, "STRING": 90, "ONE_BYTE_STRING": 91, "TWO_BYTE_STRING": 92, "NUM_PREDEFINED_CIDS": 159}},
"2.19.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_SHIFT1", "header_fields": 5, "typed_data_internal_base": 113, "typed_data_internal_limit": 169, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "CODE": 17, "OBJECT_POOL": 21, "PC_DESCRIPTORS": 22, "CODE_SOURCE_MAP": 23, "COMPRESSED_STACK_MAPS": 24, "LOCAL_VAR_DESCRIPTORS": 25, "EXCEPTION_HANDLERS": 26, "CONTEXT": 27, "CONTEXT_SCOPE": 28, "SENTINEL": 29, "SINGLE_TARGET_CACHE": 30, "UNLINKED_CALL": 31, "MONOMORPHIC_SMIABLE_CALL": 32, "CALL_SITE_DATA": 33, "ICDATA": 34, "MEGAMORPHIC_CACHE": 35, "SUBTYPE_TEST_CACHE": 36, "LOADING_UNIT": 37, "API_ERROR": 39, "LANGUAGE_ERROR": 40, "UNHANDLED_EXCEPTION": 41, "UNWIND_ERROR": 42, "INSTANCE": 43, "LIBRARY_PREFIX": 44, "TYPE_ARGUMENTS": 45, "TYPE": 47, "FUNCTION_TYPE": 48, "RECORD_TYPE": 49, "TYPE_REF": 50, "TYPE_PARAMETER": 51, "CLOSURE": 56, "MINT": 60, "DOUBLE": 61, "FLOAT32X4": 63, "INT32X4": 64, "FLOAT64X2": 65, "RECORD": 66, "TYPED_DATA": 68, "EXTERNAL_TYPED_DATA": 69, "TYPED_DATA_VIEW": 70, "CAPABILITY": 73, "RECEIVE_PORT": 74, "SEND_PORT": 75, "STACK_TRACE": 76, "SUSPEND_STATE": 77, "REGEXP": 78, "WEAK_PROPERTY": 79, "WEAK_REFERENCE": 80, "FUTURE_OR": 82, "USER_TAG": 83, "TRANSFERABLE_TYPED_DATA": 84, "MAP": 85, "CONST_MAP": 86, "SET": 87, "CONST_SET": 88, "ARRAY": 89, "IMMUTABLE_ARRAY": 90, "GROWABLE_OBJECT_ARRAY": 91, "STRING": 92, "ONE_BYTE_STRING": 93, "TWO_BYTE_STRING": 94, "NUM_PREDEFINED_CIDS": 176}},
"3.0.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_SHIFT1", "header_fields": 5, "typed_data_internal_base": 114, "typed_data_internal_limit": 170, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 22, "PC_DESCRIPTORS": 23, "CODE_SOURCE_MAP": 24, "COMPRESSED_STACK_MAPS": 25, "LOCAL_VAR_DESCRIPTORS": 26, "EXCEPTION_HANDLERS": 27, "CONTEXT": 28, "CONTEXT_SCOPE": 29, "SENTINEL": 30, "SINGLE_TARGET_CACHE": 31, "UNLINKED_CALL": 32, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 35, "MEGAMORPHIC_CACHE": 36, "SUBTYPE_TEST_CACHE": 37, "LOADING_UNIT": 38, "API_ERROR": 40, "LANGUAGE_ERROR": 41, "UNHANDLED_EXCEPTION": 42, "UNWIND_ERROR": 43, "INSTANCE": 44, "LIBRARY_PREFIX": 45, "TYPE_ARGUMENTS": 46, "TYPE": 48, "FUNCTION_TYPE": 49, "RECORD_TYPE": 50, "TYPE_REF": 51, "TYPE_PARAMETER": 52, "CLOSURE": 57, "MINT": 61, "DOUBLE": 62, "FLOAT32X4": 64, "INT32X4": 65, "FLOAT64X2": 66, "RECORD": 67, "TYPED_DATA": 69, "EXTERNAL_TYPED_DATA": 70, "TYPED_DATA_VIEW": 71, "CAPABILITY": 74, "RECEIVE_PORT": 75, "SEND_PORT": 76, "STACK_TRACE": 77, "SUSPEND_STATE": 78, "REGEXP": 79, "WEAK_PROPERTY": 80, "WEAK_REFERENCE": 81, "FUTURE_OR": 83, "USER_TAG": 84, "TRANSFERABLE_TYPED_DATA": 85, "MAP": 86, "CONST_MAP": 87, "SET": 88, "CONST_SET": 89, "ARRAY": 90, "IMMUTABLE_ARRAY": 91, "GROWABLE_OBJECT_ARRAY": 92, "STRING": 93, "ONE_BYTE_STRING": 94, "TWO_BYTE_STRING": 95, "NUM_PREDEFINED_CIDS": 177}},
"3.1.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_SHIFT1", "header_fields": 5, "typed_data_internal_base": 113, "typed_data_internal_limit": 169, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 22, "PC_DESCRIPTORS": 23, "CODE_SOURCE_MAP": 24, "COMPRESSED_STACK_MAPS": 25, "LOCAL_VAR_DESCRIPTORS": 26, "EXCEPTION_HANDLERS": 27, "CONTEXT": 28, "CONTEXT_SCOPE": 29, "SENTINEL": 30, "SINGLE_TARGET_CACHE": 31, "UNLINKED_CALL": 32, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 35, "MEGAMORPHIC_CACHE": 36, "SUBTYPE_TEST_CACHE": 37, "LOADING_UNIT": 38, "API_ERROR": 40, "LANGUAGE_ERROR": 41, "UNHANDLED_EXCEPTION": 42, "UNWIND_ERROR": 43, "INSTANCE": 44, "LIBRARY_PREFIX": 45, "TYPE_ARGUMENTS": 46, "TYPE": 48, "FUNCTION_TYPE": 49, "RECORD_TYPE": 50, "TYPE_PARAMETER": 51, "CLOSURE": 56, "MINT": 60, "DOUBLE": 61, "FLOAT32X4": 63, "INT32X4": 64, "FLOAT64X2": 65, "RECORD": 66, "TYPED_DATA": 68, "EXTERNAL_TYPED_DATA": 69, "TYPED_DATA_VIEW": 70, "CAPABILITY": 73, "RECEIVE_PORT": 74, "SEND_PORT": 75, "STACK_TRACE": 76, "SUSPEND_STATE": 77, "REGEXP": 78, "WEAK_PROPERTY": 79, "WEAK_REFERENCE": 80, "FUTURE_OR": 82, "USER_TAG": 83, "TRANSFERABLE_TYPED_DATA": 84, "MAP": 85, "CONST_MAP": 86, "SET": 87, "CONST_SET": 88, "ARRAY": 89, "IMMUTABLE_ARRAY": 90, "GROWABLE_OBJECT_ARRAY": 91, "STRING": 92, "ONE_BYTE_STRING": 93, "TWO_BYTE_STRING": 94, "NUM_PREDEFINED_CIDS": 176}},
"3.2.0": {"cws": 8, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_SHIFT1", "header_fields": 5, "typed_data_internal_base": 113, "typed_data_internal_limit": 169, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 22, "PC_DESCRIPTORS": 23, "CODE_SOURCE_MAP": 24, "COMPRESSED_STACK_MAPS": 25, "LOCAL_VAR_DESCRIPTORS": 26, "EXCEPTION_HANDLERS": 27, "CONTEXT": 28, "CONTEXT_SCOPE": 29, "SENTINEL": 30, "SINGLE_TARGET_CACHE": 31, "UNLINKED_CALL": 32, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 35, "MEGAMORPHIC_CACHE": 36, "SUBTYPE_TEST_CACHE": 37, "LOADING_UNIT": 38, "API_ERROR": 40, "LANGUAGE_ERROR": 41, "UNHANDLED_EXCEPTION": 42, "UNWIND_ERROR": 43, "INSTANCE": 44, "LIBRARY_PREFIX": 45, "TYPE_ARGUMENTS": 46, "TYPE": 48, "FUNCTION_TYPE": 49, "RECORD_TYPE": 50, "TYPE_PARAMETER": 51, "CLOSURE": 56, "MINT": 60, "DOUBLE": 61, "FLOAT32X4": 63, "INT32X4": 64, "FLOAT64X2": 65, "RECORD": 66, "TYPED_DATA": 68, "EXTERNAL_TYPED_DATA": 69, "TYPED_DATA_VIEW": 70, "CAPABILITY": 73, "RECEIVE_PORT": 74, "SEND_PORT": 75, "STACK_TRACE": 76, "SUSPEND_STATE": 77, "REGEXP": 78, "WEAK_PROPERTY": 79, "WEAK_REFERENCE": 80, "FUTURE_OR": 82, "USER_TAG": 83, "TRANSFERABLE_TYPED_DATA": 84, "MAP": 85, "CONST_MAP": 86, "SET": 87, "CONST_SET": 88, "ARRAY": 89, "IMMUTABLE_ARRAY": 90, "GROWABLE_OBJECT_ARRAY": 91, "STRING": 92, "ONE_BYTE_STRING": 93, "TWO_BYTE_STRING": 94, "NUM_PREDEFINED_CIDS": 176}},
"3.3.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_SHIFT1", "header_fields": 5, "typed_data_internal_base": 113, "typed_data_internal_limit": 169, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 22, "PC_DESCRIPTORS": 23, "CODE_SOURCE_MAP": 24, "COMPRESSED_STACK_MAPS": 25, "LOCAL_VAR_DESCRIPTORS": 26, "EXCEPTION_HANDLERS": 27, "CONTEXT": 28, "CONTEXT_SCOPE": 29, "SENTINEL": 30, "SINGLE_TARGET_CACHE": 31, "UNLINKED_CALL": 32, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 35, "MEGAMORPHIC_CACHE": 36, "SUBTYPE_TEST_CACHE": 37, "LOADING_UNIT": 38, "API_ERROR": 40, "LANGUAGE_ERROR": 41, "UNHANDLED_EXCEPTION": 42, "UNWIND_ERROR": 43, "INSTANCE": 44, "LIBRARY_PREFIX": 45, "TYPE_ARGUMENTS": 46, "TYPE": 48, "FUNCTION_TYPE": 49, "RECORD_TYPE": 50, "TYPE_PARAMETER": 51, "CLOSURE": 56, "MINT": 60, "DOUBLE": 61, "FLOAT32X4": 63, "INT32X4": 64, "FLOAT64X2": 65, "RECORD": 66, "TYPED_DATA": 68, "EXTERNAL_TYPED_DATA": 69, "TYPED_DATA_VIEW": 70, "CAPABILITY": 73, "RECEIVE_PORT": 74, "SEND_PORT": 75, "STACK_TRACE": 76, "SUSPEND_STATE": 77, "REGEXP": 78, "WEAK_PROPERTY": 79, "WEAK_REFERENCE": 80, "FUTURE_OR": 82, "USER_TAG": 83, "TRANSFERABLE_TYPED_DATA": 84, "MAP": 85, "CONST_MAP": 86, "SET": 87, "CONST_SET": 88, "ARRAY": 89, "IMMUTABLE_ARRAY": 90, "GROWABLE_OBJECT_ARRAY": 91, "STRING": 92, "ONE_BYTE_STRING": 93, "TWO_BYTE_STRING": 94, "NUM_PREDEFINED_CIDS": 176}},
"3.4.0": {"cws": 8, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 111, "typed_data_internal_limit": 167, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 22, "PC_DESCRIPTORS": 23, "CODE_SOURCE_MAP": 24, "COMPRESSED_STACK_MAPS": 25, "LOCAL_VAR_DESCRIPTORS": 26, "EXCEPTION_HANDLERS": 27, "CONTEXT": 28, "CONTEXT_SCOPE": 29, "SENTINEL": 30, "SINGLE_TARGET_CACHE": 31, "UNLINKED_CALL": 32, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 35, "MEGAMORPHIC_CACHE": 36, "SUBTYPE_TEST_CACHE": 37, "LOADING_UNIT": 38, "API_ERROR": 40, "LANGUAGE_ERROR": 41, "UNHANDLED_EXCEPTION": 42, "UNWIND_ERROR": 43, "INSTANCE": 44, "LIBRARY_PREFIX": 45, "TYPE_ARGUMENTS": 46, "TYPE": 48, "FUNCTION_TYPE": 49, "RECORD_TYPE": 50, "TYPE_PARAMETER": 51, "CLOSURE": 56, "MINT": 60, "DOUBLE": 61, "FLOAT32X4": 63, "INT32X4": 64, "FLOAT64X2": 65, "RECORD": 66, "TYPED_DATA": 68, "EXTERNAL_TYPED_DATA": 69, "TYPED_DATA_VIEW": 70, "CAPABILITY": 73, "RECEIVE_PORT": 74, "SEND_PORT": 75, "STACK_TRACE": 76, "SUSPEND_STATE": 77, "REGEXP": 78, "WEAK_PROPERTY": 79, "WEAK_REFERENCE": 80, "FUTURE_OR": 82, "USER_TAG": 83, "TRANSFERABLE_TYPED_DATA": 84, "MAP": 85, "CONST_MAP": 86, "SET": 87, "CONST_SET": 88, "ARRAY": 89, "IMMUTABLE_ARRAY": 90, "GROWABLE_OBJECT_ARRAY": 91, "STRING": 92, "ONE_BYTE_STRING": 93, "TWO_BYTE_STRING": 94, "NUM_PREDEFINED_CIDS": 174}},
"3.5.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 111, "typed_data_internal_limit": 167, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 22, "PC_DESCRIPTORS": 23, "CODE_SOURCE_MAP": 24, "COMPRESSED_STACK_MAPS": 25, "LOCAL_VAR_DESCRIPTORS": 26, "EXCEPTION_HANDLERS": 27, "CONTEXT": 28, "CONTEXT_SCOPE": 29, "SENTINEL": 30, "SINGLE_TARGET_CACHE": 31, "UNLINKED_CALL": 32, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 35, "MEGAMORPHIC_CACHE": 36, "SUBTYPE_TEST_CACHE": 37, "LOADING_UNIT": 38, "API_ERROR": 40, "LANGUAGE_ERROR": 41, "UNHANDLED_EXCEPTION": 42, "UNWIND_ERROR": 43, "INSTANCE": 44, "LIBRARY_PREFIX": 45, "TYPE_ARGUMENTS": 46, "TYPE": 48, "FUNCTION_TYPE": 49, "RECORD_TYPE": 50, "TYPE_PARAMETER": 51, "CLOSURE": 56, "MINT": 60, "DOUBLE": 61, "FLOAT32X4": 63, "INT32X4": 64, "FLOAT64X2": 65, "RECORD": 66, "TYPED_DATA": 68, "EXTERNAL_TYPED_DATA": 69, "TYPED_DATA_VIEW": 70, "CAPABILITY": 73, "RECEIVE_PORT": 74, "SEND_PORT": 75, "STACK_TRACE": 76, "SUSPEND_STATE": 77, "REGEXP": 78, "WEAK_PROPERTY": 79, "WEAK_REFERENCE": 80, "FUTURE_OR": 82, "USER_TAG": 83, "TRANSFERABLE_TYPED_DATA": 84, "MAP": 85, "CONST_MAP": 86, "SET": 87, "CONST_SET": 88, "ARRAY": 89, "IMMUTABLE_ARRAY": 90, "GROWABLE_OBJECT_ARRAY": 91, "STRING": 92, "ONE_BYTE_STRING": 93, "TWO_BYTE_STRING": 94, "NUM_PREDEFINED_CIDS": 174}},
"3.6.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 112, "typed_data_internal_limit": 168, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 23, "PC_DESCRIPTORS": 24, "CODE_SOURCE_MAP": 25, "COMPRESSED_STACK_MAPS": 26, "LOCAL_VAR_DESCRIPTORS": 27, "EXCEPTION_HANDLERS": 28, "CONTEXT": 29, "CONTEXT_SCOPE": 30, "SENTINEL": 31, "SINGLE_TARGET_CACHE": 32, "UNLINKED_CALL": 33, "MONOMORPHIC_SMIABLE_CALL": 34, "CALL_SITE_DATA": 35, "ICDATA": 36, "MEGAMORPHIC_CACHE": 37, "SUBTYPE_TEST_CACHE": 38, "LOADING_UNIT": 39, "API_ERROR": 41, "LANGUAGE_ERROR": 42, "UNHANDLED_EXCEPTION": 43, "UNWIND_ERROR": 44, "INSTANCE": 45, "LIBRARY_PREFIX": 46, "TYPE_ARGUMENTS": 47, "TYPE": 49, "FUNCTION_TYPE": 50, "RECORD_TYPE": 51, "TYPE_PARAMETER": 52, "CLOSURE": 57, "MINT": 61, "DOUBLE": 62, "FLOAT32X4": 64, "INT32X4": 65, "FLOAT64X2": 66, "RECORD": 67, "TYPED_DATA": 69, "EXTERNAL_TYPED_DATA": 70, "TYPED_DATA_VIEW": 71, "CAPABILITY": 74, "RECEIVE_PORT": 75, "SEND_PORT": 76, "STACK_TRACE": 77, "SUSPEND_STATE": 78, "REGEXP": 79, "WEAK_PROPERTY": 80, "WEAK_REFERENCE": 81, "FUTURE_OR": 83, "USER_TAG": 84, "TRANSFERABLE_TYPED_DATA": 85, "MAP": 86, "CONST_MAP": 87, "SET": 88, "CONST_SET": 89, "ARRAY": 90, "IMMUTABLE_ARRAY": 91, "GROWABLE_OBJECT_ARRAY": 92, "STRING": 93, "ONE_BYTE_STRING": 94, "TWO_BYTE_STRING": 95, "NUM_PREDEFINED_CIDS": 175}},
"3.7.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 112, "typed_data_internal_limit": 168, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 23, "PC_DESCRIPTORS": 24, "CODE_SOURCE_MAP": 25, "COMPRESSED_STACK_MAPS": 26, "LOCAL_VAR_DESCRIPTORS": 27, "EXCEPTION_HANDLERS": 28, "CONTEXT": 29, "CONTEXT_SCOPE": 30, "SENTINEL": 31, "SINGLE_TARGET_CACHE": 32, "UNLINKED_CALL": 33, "MONOMORPHIC_SMIABLE_CALL": 34, "CALL_SITE_DATA": 35, "ICDATA": 36, "MEGAMORPHIC_CACHE": 37, "SUBTYPE_TEST_CACHE": 38, "LOADING_UNIT": 39, "API_ERROR": 41, "LANGUAGE_ERROR": 42, "UNHANDLED_EXCEPTION": 43, "UNWIND_ERROR": 44, "INSTANCE": 45, "LIBRARY_PREFIX": 46, "TYPE_ARGUMENTS": 47, "TYPE": 49, "FUNCTION_TYPE": 50, "RECORD_TYPE": 51, "TYPE_PARAMETER": 52, "CLOSURE": 57, "MINT": 61, "DOUBLE": 62, "FLOAT32X4": 64, "INT32X4": 65, "FLOAT64X2": 66, "RECORD": 67, "TYPED_DATA": 69, "EXTERNAL_TYPED_DATA": 70, "TYPED_DATA_VIEW": 71, "CAPABILITY": 74, "RECEIVE_PORT": 75, "SEND_PORT": 76, "STACK_TRACE": 77, "SUSPEND_STATE": 78, "REGEXP": 79, "WEAK_PROPERTY": 80, "WEAK_REFERENCE": 81, "FUTURE_OR": 83, "USER_TAG": 84, "TRANSFERABLE_TYPED_DATA": 85, "MAP": 86, "CONST_MAP": 87, "SET": 88, "CONST_SET": 89, "ARRAY": 90, "IMMUTABLE_ARRAY": 91, "GROWABLE_OBJECT_ARRAY": 92, "STRING": 93, "ONE_BYTE_STRING": 94, "TWO_BYTE_STRING": 95, "NUM_PREDEFINED_CIDS": 175}},
"3.8.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 112, "typed_data_internal_limit": 168, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 23, "PC_DESCRIPTORS": 24, "CODE_SOURCE_MAP": 25, "COMPRESSED_STACK_MAPS": 26, "LOCAL_VAR_DESCRIPTORS": 27, "EXCEPTION_HANDLERS": 28, "CONTEXT": 29, "CONTEXT_SCOPE": 30, "SENTINEL": 31, "SINGLE_TARGET_CACHE": 32, "UNLINKED_CALL": 33, "MONOMORPHIC_SMIABLE_CALL": 34, "CALL_SITE_DATA": 35, "ICDATA": 36, "MEGAMORPHIC_CACHE": 37, "SUBTYPE_TEST_CACHE": 38, "LOADING_UNIT": 39, "API_ERROR": 41, "LANGUAGE_ERROR": 42, "UNHANDLED_EXCEPTION": 43, "UNWIND_ERROR": 44, "INSTANCE": 45, "LIBRARY_PREFIX": 46, "TYPE_ARGUMENTS": 47, "TYPE": 49, "FUNCTION_TYPE": 50, "RECORD_TYPE": 51, "TYPE_PARAMETER": 52, "CLOSURE": 57, "MINT": 61, "DOUBLE": 62, "FLOAT32X4": 64, "INT32X4": 65, "FLOAT64X2": 66, "RECORD": 67, "TYPED_DATA": 69, "EXTERNAL_TYPED_DATA": 70, "TYPED_DATA_VIEW": 71, "CAPABILITY": 74, "RECEIVE_PORT": 75, "SEND_PORT": 76, "STACK_TRACE": 77, "SUSPEND_STATE": 78, "REGEXP": 79, "WEAK_PROPERTY": 80, "WEAK_REFERENCE": 81, "FUTURE_OR": 83, "USER_TAG": 84, "TRANSFERABLE_TYPED_DATA": 85, "MAP": 86, "CONST_MAP": 87, "SET": 88, "CONST_SET": 89, "ARRAY": 90, "IMMUTABLE_ARRAY": 91, "GROWABLE_OBJECT_ARRAY": 92, "STRING": 93, "ONE_BYTE_STRING": 94, "TWO_BYTE_STRING": 95, "NUM_PREDEFINED_CIDS": 175}},
"3.9.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 112, "typed_data_internal_limit": 168, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 23, "PC_DESCRIPTORS": 24, "CODE_SOURCE_MAP": 25, "COMPRESSED_STACK_MAPS": 26, "LOCAL_VAR_DESCRIPTORS": 27, "EXCEPTION_HANDLERS": 28, "CONTEXT": 29, "CONTEXT_SCOPE": 30, "SENTINEL": 31, "SINGLE_TARGET_CACHE": 32, "UNLINKED_CALL": 35, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 36, "MEGAMORPHIC_CACHE": 37, "SUBTYPE_TEST_CACHE": 38, "LOADING_UNIT": 39, "API_ERROR": 41, "LANGUAGE_ERROR": 42, "UNHANDLED_EXCEPTION": 43, "UNWIND_ERROR": 44, "INSTANCE": 45, "LIBRARY_PREFIX": 46, "TYPE_ARGUMENTS": 47, "TYPE": 49, "FUNCTION_TYPE": 50, "RECORD_TYPE": 51, "TYPE_PARAMETER": 52, "CLOSURE": 57, "MINT": 61, "DOUBLE": 62, "FLOAT32X4": 64, "INT32X4": 65, "FLOAT64X2": 66, "RECORD": 67, "TYPED_DATA": 69, "EXTERNAL_TYPED_DATA": 70, "TYPED_DATA_VIEW": 71, "CAPABILITY": 74, "RECEIVE_PORT": 75, "SEND_PORT": 76, "STACK_TRACE": 77, "SUSPEND_STATE": 78, "REGEXP": 79, "WEAK_PROPERTY": 80, "WEAK_REFERENCE": 81, "FUTURE_OR": 83, "USER_TAG": 84, "TRANSFERABLE_TYPED_DATA": 85, "MAP": 86, "CONST_MAP": 87, "SET": 88, "CONST_SET": 89, "ARRAY": 90, "IMMUTABLE_ARRAY": 91, "GROWABLE_OBJECT_ARRAY": 92, "STRING": 93, "ONE_BYTE_STRING": 94, "TWO_BYTE_STRING": 95, "NUM_PREDEFINED_CIDS": 175}},
"3.10.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 112, "typed_data_internal_limit": 168, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 23, "PC_DESCRIPTORS": 24, "CODE_SOURCE_MAP": 25, "COMPRESSED_STACK_MAPS": 26, "LOCAL_VAR_DESCRIPTORS": 27, "EXCEPTION_HANDLERS": 28, "CONTEXT": 29, "CONTEXT_SCOPE": 30, "SENTINEL": 31, "SINGLE_TARGET_CACHE": 32, "UNLINKED_CALL": 35, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 36, "MEGAMORPHIC_CACHE": 37, "SUBTYPE_TEST_CACHE": 38, "LOADING_UNIT": 39, "API_ERROR": 41, "LANGUAGE_ERROR": 42, "UNHANDLED_EXCEPTION": 43, "UNWIND_ERROR": 44, "INSTANCE": 45, "LIBRARY_PREFIX": 46, "TYPE_ARGUMENTS": 47, "TYPE": 49, "FUNCTION_TYPE": 50, "RECORD_TYPE": 51, "TYPE_PARAMETER": 52, "CLOSURE": 57, "MINT": 61, "DOUBLE": 62, "FLOAT32X4": 64, "INT32X4": 65, "FLOAT64X2": 66, "RECORD": 67, "TYPED_DATA": 69, "EXTERNAL_TYPED_DATA": 70, "TYPED_DATA_VIEW": 71, "CAPABILITY": 74, "RECEIVE_PORT": 75, "SEND_PORT": 76, "STACK_TRACE": 77, "SUSPEND_STATE": 78, "REGEXP": 79, "WEAK_PROPERTY": 80, "WEAK_REFERENCE": 81, "FUTURE_OR": 83, "USER_TAG": 84, "TRANSFERABLE_TYPED_DATA": 85, "MAP": 86, "CONST_MAP": 87, "SET": 88, "CONST_SET": 89, "ARRAY": 90, "IMMUTABLE_ARRAY": 91, "GROWABLE_OBJECT_ARRAY": 92, "STRING": 93, "ONE_BYTE_STRING": 94, "TWO_BYTE_STRING": 95, "NUM_PREDEFINED_CIDS": 175}},
"3.11.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 112, "typed_data_internal_limit": 168, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 23, "PC_DESCRIPTORS": 24, "CODE_SOURCE_MAP": 25, "COMPRESSED_STACK_MAPS": 26, "LOCAL_VAR_DESCRIPTORS": 27, "EXCEPTION_HANDLERS": 28, "CONTEXT": 29, "CONTEXT_SCOPE": 30, "SENTINEL": 31, "SINGLE_TARGET_CACHE": 32, "UNLINKED_CALL": 35, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 36, "MEGAMORPHIC_CACHE": 37, "SUBTYPE_TEST_CACHE": 38, "LOADING_UNIT": 39, "API_ERROR": 41, "LANGUAGE_ERROR": 42, "UNHANDLED_EXCEPTION": 43, "UNWIND_ERROR": 44, "INSTANCE": 45, "LIBRARY_PREFIX": 46, "TYPE_ARGUMENTS": 47, "TYPE": 49, "FUNCTION_TYPE": 50, "RECORD_TYPE": 51, "TYPE_PARAMETER": 52, "CLOSURE": 57, "MINT": 61, "DOUBLE": 62, "FLOAT32X4": 64, "INT32X4": 65, "FLOAT64X2": 66, "RECORD": 67, "TYPED_DATA": 69, "EXTERNAL_TYPED_DATA": 70, "TYPED_DATA_VIEW": 71, "CAPABILITY": 74, "RECEIVE_PORT": 75, "SEND_PORT": 76, "STACK_TRACE": 77, "SUSPEND_STATE": 78, "REGEXP": 79, "WEAK_PROPERTY": 80, "WEAK_REFERENCE": 81, "FUTURE_OR": 83, "USER_TAG": 84, "TRANSFERABLE_TYPED_DATA": 85, "MAP": 86, "CONST_MAP": 87, "SET": 88, "CONST_SET": 89, "ARRAY": 90, "IMMUTABLE_ARRAY": 91, "GROWABLE_OBJECT_ARRAY": 92, "STRING": 93, "ONE_BYTE_STRING": 94, "TWO_BYTE_STRING": 95, "NUM_PREDEFINED_CIDS": 175}},
"3.12.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 112, "typed_data_internal_limit": 168, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 23, "PC_DESCRIPTORS": 24, "CODE_SOURCE_MAP": 25, "COMPRESSED_STACK_MAPS": 26, "LOCAL_VAR_DESCRIPTORS": 27, "EXCEPTION_HANDLERS": 28, "CONTEXT": 29, "CONTEXT_SCOPE": 30, "SENTINEL": 31, "SINGLE_TARGET_CACHE": 32, "UNLINKED_CALL": 35, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 36, "MEGAMORPHIC_CACHE": 37, "SUBTYPE_TEST_CACHE": 38, "LOADING_UNIT": 39, "API_ERROR": 41, "LANGUAGE_ERROR": 42, "UNHANDLED_EXCEPTION": 43, "UNWIND_ERROR": 44, "INSTANCE": 45, "LIBRARY_PREFIX": 46, "TYPE_ARGUMENTS": 47, "TYPE": 49, "FUNCTION_TYPE": 50, "RECORD_TYPE": 51, "TYPE_PARAMETER": 52, "CLOSURE": 57, "MINT": 61, "DOUBLE": 62, "FLOAT32X4": 64, "INT32X4": 65, "FLOAT64X2": 66, "RECORD": 67, "TYPED_DATA": 69, "EXTERNAL_TYPED_DATA": 70, "TYPED_DATA_VIEW": 71, "CAPABILITY": 74, "RECEIVE_PORT": 75, "SEND_PORT": 76, "STACK_TRACE": 77, "SUSPEND_STATE": 78, "REGEXP": 79, "WEAK_PROPERTY": 80, "WEAK_REFERENCE": 81, "FUTURE_OR": 83, "USER_TAG": 84, "TRANSFERABLE_TYPED_DATA": 85, "MAP": 86, "CONST_MAP": 87, "SET": 88, "CONST_SET": 89, "ARRAY": 90, "IMMUTABLE_ARRAY": 91, "GROWABLE_OBJECT_ARRAY": 92, "STRING": 93, "ONE_BYTE_STRING": 94, "TWO_BYTE_STRING": 95, "NUM_PREDEFINED_CIDS": 175}},
"3.13.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 113, "typed_data_internal_limit": 169, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 23, "PC_DESCRIPTORS": 24, "CODE_SOURCE_MAP": 25, "COMPRESSED_STACK_MAPS": 26, "LOCAL_VAR_DESCRIPTORS": 27, "EXCEPTION_HANDLERS": 28, "CONTEXT": 29, "CONTEXT_SCOPE": 30, "SENTINEL": 31, "SINGLE_TARGET_CACHE": 32, "UNLINKED_CALL": 35, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 36, "MEGAMORPHIC_CACHE": 37, "SUBTYPE_TEST_CACHE": 38, "LOADING_UNIT": 39, "API_ERROR": 41, "LANGUAGE_ERROR": 42, "UNHANDLED_EXCEPTION": 43, "UNWIND_ERROR": 44, "INSTANCE": 45, "LIBRARY_PREFIX": 46, "TYPE_ARGUMENTS": 47, "TYPE": 49, "FUNCTION_TYPE": 50, "RECORD_TYPE": 51, "TYPE_PARAMETER": 52, "CLOSURE": 57, "MINT": 61, "DOUBLE": 62, "FLOAT32X4": 64, "INT32X4": 65, "FLOAT64X2": 66, "RECORD": 67, "TYPED_DATA": 69, "EXTERNAL_TYPED_DATA": 70, "TYPED_DATA_VIEW": 71, "CAPABILITY": 74, "RECEIVE_PORT": 75, "SEND_PORT": 76, "STACK_TRACE": 77, "SUSPEND_STATE": 78, "REGEXP": 79, "WEAK_PROPERTY": 80, "WEAK_REFERENCE": 81, "FUTURE_OR": 83, "USER_TAG": 84, "TRANSFERABLE_TYPED_DATA": 85, "MAP": 86, "CONST_MAP": 87, "SET": 88, "CONST_SET": 89, "ARRAY": 90, "IMMUTABLE_ARRAY": 91, "GROWABLE_OBJECT_ARRAY": 92, "STRING": 93, "ONE_BYTE_STRING": 94, "TWO_BYTE_STRING": 95, "NUM_PREDEFINED_CIDS": 176}},
"3.14.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 113, "typed_data_internal_limit": 169, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 23, "PC_DESCRIPTORS": 24, "CODE_SOURCE_MAP": 25, "COMPRESSED_STACK_MAPS": 26, "LOCAL_VAR_DESCRIPTORS": 27, "EXCEPTION_HANDLERS": 28, "CONTEXT": 29, "CONTEXT_SCOPE": 30, "SENTINEL": 31, "SINGLE_TARGET_CACHE": 32, "UNLINKED_CALL": 35, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 36, "MEGAMORPHIC_CACHE": 37, "SUBTYPE_TEST_CACHE": 38, "LOADING_UNIT": 39, "API_ERROR": 41, "LANGUAGE_ERROR": 42, "UNHANDLED_EXCEPTION": 43, "UNWIND_ERROR": 44, "INSTANCE": 45, "LIBRARY_PREFIX": 46, "TYPE_ARGUMENTS": 47, "TYPE": 49, "FUNCTION_TYPE": 50, "RECORD_TYPE": 51, "TYPE_PARAMETER": 52, "CLOSURE": 57, "MINT": 61, "DOUBLE": 62, "FLOAT32X4": 64, "INT32X4": 65, "FLOAT64X2": 66, "RECORD": 67, "TYPED_DATA": 69, "EXTERNAL_TYPED_DATA": 70, "TYPED_DATA_VIEW": 71, "CAPABILITY": 74, "RECEIVE_PORT": 75, "SEND_PORT": 76, "STACK_TRACE": 77, "SUSPEND_STATE": 78, "REGEXP": 79, "WEAK_PROPERTY": 80, "WEAK_REFERENCE": 81, "FUTURE_OR": 83, "USER_TAG": 84, "TRANSFERABLE_TYPED_DATA": 85, "MAP": 86, "CONST_MAP": 87, "SET": 88, "CONST_SET": 89, "ARRAY": 90, "IMMUTABLE_ARRAY": 91, "GROWABLE_OBJECT_ARRAY": 92, "STRING": 93, "ONE_BYTE_STRING": 94, "TWO_BYTE_STRING": 95, "NUM_PREDEFINED_CIDS": 176}}
},
"hashes": {
"f91b8b03bf7f30a5e983fd19b23d978d": {
"hash": "f91b8b03bf7f30a5e983fd19b23d978d",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"notes": "Generic arm64 AOT fallback with source-backed Code/Function offsets; version still unknown",
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"8ee4ef7a67df9845fba331734198a953": {
"hash": "8ee4ef7a67df9845fba331734198a953",
"compressed_word_size": 8,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"e4a09dbf2bb120fe4674e0576617a0dc": {
"hash": "e4a09dbf2bb120fe4674e0576617a0dc",
"compressed_word_size": 8,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"34f6eec64e9371856eaaa278ccf56538": {
"hash": "34f6eec64e9371856eaaa278ccf56538",
"compressed_word_size": 8,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"7a5b240780941844bae88eca5dbaa7b8": {
"hash": "7a5b240780941844bae88eca5dbaa7b8",
"compressed_word_size": 8,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"9cf77f4405212c45daf608e1cd646852": {
"hash": "9cf77f4405212c45daf608e1cd646852",
"compressed_word_size": 8,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"659a72e41e3276e882709901c27de33d": {
"hash": "659a72e41e3276e882709901c27de33d",
"compressed_word_size": 8,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"f10776149bf76be288def3c2ca73bdc1": {
"hash": "f10776149bf76be288def3c2ca73bdc1",
"compressed_word_size": 8,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"24d9d411c2f90c8fbe8907f99e89d4b0": {
"hash": "24d9d411c2f90c8fbe8907f99e89d4b0",
"compressed_word_size": 8,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"d56742caf7b3b3f4bd2df93a9bbb5503": {
"hash": "d56742caf7b3b3f4bd2df93a9bbb5503",
"compressed_word_size": 8,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"3318fe66091c0ffbb64faec39976cb7d": {
"hash": "3318fe66091c0ffbb64faec39976cb7d",
"compressed_word_size": 8,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"adf563436d12ba0d50ea5beb7f3be1bb": {
"hash": "adf563436d12ba0d50ea5beb7f3be1bb",
"compressed_word_size": 8,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"1441d6b13b8623fa7fbf61433abebd31": {
"hash": "1441d6b13b8623fa7fbf61433abebd31",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"a0cb0c928b23bc17a26e062b351dc44d": {
"hash": "a0cb0c928b23bc17a26e062b351dc44d",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"ded6ef11c73fdc638d6ff6d3ad22a67b": {
"hash": "ded6ef11c73fdc638d6ff6d3ad22a67b",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"b0e899ec5a90e4661501f0b69e9dd70f": {
"hash": "b0e899ec5a90e4661501f0b69e9dd70f",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"b6d0a1f034d158b0d37b51d559379697": {
"hash": "b6d0a1f034d158b0d37b51d559379697",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"8e50e448b241be23b9e990094f4dca39": {
"hash": "8e50e448b241be23b9e990094f4dca39",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"6a9b5a03a7e784a4558b10c769f188d9": {
"hash": "6a9b5a03a7e784a4558b10c769f188d9",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"adb4292f3ec25074ca70abcd2d5c7251": {
"hash": "adb4292f3ec25074ca70abcd2d5c7251",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"501ef5cbd64ca70b6b42672346af6a8a": {
"hash": "501ef5cbd64ca70b6b42672346af6a8a",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"90b56a561f70cd55e972cb49b79b3d8b": {
"hash": "90b56a561f70cd55e972cb49b79b3d8b",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"aa64af18e7d086041ac127cc4bc50c5e": {
"hash": "aa64af18e7d086041ac127cc4bc50c5e",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"36b0375d284ee2af0d0fffc6e6e48fde": {
"hash": "36b0375d284ee2af0d0fffc6e6e48fde",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"16ad76edd19b537bf6ea64fdd31977a7": {
"hash": "16ad76edd19b537bf6ea64fdd31977a7",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"7dbbeeb8ef7b91338640dca3927636de": {
"hash": "7dbbeeb8ef7b91338640dca3927636de",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"f71c76320d35b65f1164dbaa6d95fe09": {
"hash": "f71c76320d35b65f1164dbaa6d95fe09",
"compressed_word_size": 8,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"ee1eb666c76a5cb7746faf39d0b97547": {
"hash": "ee1eb666c76a5cb7746faf39d0b97547",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"d20a1be77c3d3c41b2a5accaee1ce549": {
"hash": "d20a1be77c3d3c41b2a5accaee1ce549",
"compressed_word_size": 8,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"80a49c7111088100a233b2ae788e1f48": {
"hash": "80a49c7111088100a233b2ae788e1f48",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"cda356e9bae476c70de33809fd92e009": {
"hash": "cda356e9bae476c70de33809fd92e009",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"2858c2c0920495f00b9bce9edf6a8cd9": {
"hash": "2858c2c0920495f00b9bce9edf6a8cd9",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"f956f595844a2f845a55707faaaa51e4": {
"hash": "f956f595844a2f845a55707faaaa51e4",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"d91c0e6f35f0eb2e44124e8f42aa44a7": {
"hash": "d91c0e6f35f0eb2e44124e8f42aa44a7",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"830f4f59e7969c70b595182826435c19": {
"hash": "830f4f59e7969c70b595182826435c19",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"97ff04a728735e6b6b098bdf983faaba": {
"hash": "97ff04a728735e6b6b098bdf983faaba",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
},
"1ce86630892e2dca9a8543fdb8ed8e22": {
"hash": "1ce86630892e2dca9a8543fdb8ed8e22",
"compressed_word_size": 4,
"heap_object_tag": 1,
"max_alignment": 16,
"it_cap": 20000,
"code_entry_point_offsets": [
8,
24,
16,
32
],
"code_owner_offsets": [
56
],
"function_name_offsets": [
24,
8
]
}
"8ee4ef7a67df9845fba331734198a953": "2.10.0",
"d979fa911b038a8682e98cc8c8569261": "2.10.0",
"5b97292b25f0a715613b7a28e0734f77": "2.12.0",
"e036be9d5df68283e1a043e348fa7ff4": "2.12.0",
"0c7f1aa25fc597a7b3b9a19ed390afa1": "2.13.0",
"34f6eec64e9371856eaaa278ccf56538": "2.13.0",
"7a5b240780941844bae88eca5dbaa7b8": "2.13.0",
"e4a09dbf2bb120fe4674e0576617a0dc": "2.13.0",
"659a72e41e3276e882709901c27de33d": "2.14.0",
"9cf77f4405212c45daf608e1cd646852": "2.14.0",
"c03fb294bfdaca2b44c552ad35e52d45": "2.14.0",
"24d9d411c2f90c8fbe8907f99e89d4b0": "2.15.0",
"7b96ef6dda75bdd1d70be8c2f7cb814e": "2.15.0",
"f10776149bf76be288def3c2ca73bdc1": "2.15.0",
"3318fe66091c0ffbb64faec39976cb7d": "2.16.0",
"6093345773c65683f60c20c8e62cbe4a": "2.16.0",
"adf563436d12ba0d50ea5beb7f3be1bb": "2.16.0",
"d56742caf7b3b3f4bd2df93a9bbb5503": "2.16.0",
"0eb277b50d6ce4069261c69150ce52c9": "2.17.0",
"1441d6b13b8623fa7fbf61433abebd31": "2.17.6",
"a0cb0c928b23bc17a26e062b351dc44d": "2.17.6",
"ded6ef11c73fdc638d6ff6d3ad22a67b": "2.17.6",
"6a9b5a03a7e784a4558b10c769f188d9": "2.18.0",
"77bd0a83c49bfc555a44a0740f15003a": "2.18.0",
"8e50e448b241be23b9e990094f4dca39": "2.18.0",
"b0e899ec5a90e4661501f0b69e9dd70f": "2.18.0",
"b6d0a1f034d158b0d37b51d559379697": "2.18.0",
"f91b8b03bf7f30a5e983fd19b23d978d": "2.18.2",
"287683014dfbd311f6400c655c97a26d": "2.18.6",
"501ef5cbd64ca70b6b42672346af6a8a": "2.19.0",
"adb4292f3ec25074ca70abcd2d5c7251": "2.19.0",
"afcf461a16dfa5e88f88a2aa68764c99": "2.19.0",
"e727fae79fb45aac639252a9453b785e": "2.19.1",
"ce5d37cf26ea4faac543cd216f3b96d1": "3.0.0",
"617d044d9faa87a98c29f8aad5d2f220": "3.0.3",
"16ad76edd19b537bf6ea64fdd31977a7": "3.0.5",
"36b0375d284ee2af0d0fffc6e6e48fde": "3.0.5",
"90b56a561f70cd55e972cb49b79b3d8b": "3.0.5",
"aa64af18e7d086041ac127cc4bc50c5e": "3.0.5",
"5191d02bb1d36a9f8609aa7495894e76": "3.1.0",
"7dbbeeb8ef7b91338640dca3927636de": "3.1.3",
"9d6362266b78e4198f784da1a28fd82b": "3.2.0",
"f71c76320d35b65f1164dbaa6d95fe09": "3.2.5",
"77b867742ae1ace67fbbb9a9a0d0334e": "3.3.0",
"ee1eb666c76a5cb7746faf39d0b97547": "3.3.0",
"fe53c4fdba846005b70514aec3840296": "3.4.0",
"d20a1be77c3d3c41b2a5accaee1ce549": "3.4.3",
"71dcc635f2e5dca494525aa13ca9006b": "3.5.0",
"80a49c7111088100a233b2ae788e1f48": "3.5.0",
"cda356e9bae476c70de33809fd92e009": "3.5.0",
"30c051cb9192f6d6812be3f0b87a6356": "3.6.0",
"2858c2c0920495f00b9bce9edf6a8cd9": "3.6.2",
"f956f595844a2f845a55707faaaa51e4": "3.6.2",
"bae16a6858d8c0fe2c437b275eb807cd": "3.7.0",
"d91c0e6f35f0eb2e44124e8f42aa44a7": "3.7.0",
"fa07b8a2d866963d1117ecdcea9287c8": "3.8.0",
"830f4f59e7969c70b595182826435c19": "3.8.1",
"97ff04a728735e6b6b098bdf983faaba": "3.9.2",
"1ce86630892e2dca9a8543fdb8ed8e22": "3.10.7",
"78da37fed6bf1489361a312568249f3f": "3.11.5",
"41be3daaabd524b8aa7423bc24584957": "3.12.0",
"ace654289f5abc240509fc941453ebc5": "3.12.1",
"0451907c2eaa8467e848c0067bfe8ed4": "3.13.0",
"7a1ea3f6f5cf1089a7f6e55d7f20dbfd": "3.14.0"
}
}
+136
View File
@@ -0,0 +1,136 @@
/*
* Patch Dart AOT RawSecureSocket bad-certificate wrappers in an opened r2 file.
*
* Usage:
* r2 -w -e bin.relocs.apply=true -i scripts/dart_cert_bypass.qjs libapp.so
* r2 -w -e bin.relocs.apply=true -i scripts/dart_cert_bypass.qjs Runner.app/Frameworks/App.framework/App
*
* The opened file must be the Dart AOT image itself, not an APK/IPA/bundle
* directory. Android usually uses libapp.so. iOS Flutter apps usually use
* Frameworks/App.framework/App.
*
* This script is intentionally suffix-agnostic: it resolves the
* method._RawSecureSocket_*._onBadCertificateWrapper_* flag produced by
* r2flutter, translates the method vaddr to a raw file offset, and writes:
*
* add x0, x22, 0x20
* ret
*
* Those bytes are AArch64-specific and return Dart true in the observed
* Android/iOS AOT snapshots.
*/
(function () {
"use strict";
function trim (s) {
return String (s || "").replace (/^\s+|\s+$/g, "");
}
function cmd (s) {
return r2.cmd (s);
}
function cmdj (s) {
try {
return r2.cmdj (s);
} catch (e) {
return null;
}
}
function fail (msg) {
console.log ("error=" + msg);
throw new Error (msg);
}
function hex (n) {
var s = Number (n).toString (16);
if (s.length & 1) {
s = "0" + s;
}
return "0x" + s;
}
function findWrapperFlag () {
var out = cmd ("f~onBadCertificateWrapper");
var lines = out.split (/\r?\n/);
for (var i = 0; i < lines.length; i++) {
var line = trim (lines[i]);
var m = line.match (/^(0x[0-9a-fA-F]+)\s+\d+\s+(method\._RawSecureSocket_.*\._onBadCertificateWrapper_.*)$/);
if (m) {
return {
addr: parseInt (m[1], 16),
name: m[2],
};
}
}
return null;
}
function fileOffsetFor (addr) {
var sections = cmdj ("iSj") || [];
for (var i = 0; i < sections.length; i++) {
var s = sections[i];
var vaddr = Number (s.vaddr);
var vsize = Number (s.vsize || s.size);
var paddr = Number (s.paddr);
if (!isFinite (vaddr) || !isFinite (vsize) || !isFinite (paddr)) {
continue;
}
if (addr >= vaddr && addr < vaddr + vsize) {
return {
offset: paddr + (addr - vaddr),
section: s.name || "",
};
}
}
return {
offset: addr,
section: "unmapped-fallback",
};
}
cmd ("e scr.color=0");
cmd ("e bin.relocs.apply=true");
var arch = trim (cmd ("e asm.arch"));
var bits = trim (cmd ("e asm.bits"));
if (arch.indexOf ("arm") === -1 || bits !== "64") {
fail ("unsupported-arch-" + arch + "-" + bits + "-expected-arm64");
}
var flag = findWrapperFlag ();
if (!flag) {
cmd ("r2flutter -A");
flag = findWrapperFlag ();
}
if (!flag) {
fail ("missing-RawSecureSocket-onBadCertificateWrapper-flag");
}
var loc = fileOffsetFor (flag.addr);
var oldIoVa = trim (cmd ("e io.va"));
var oldSeek = trim (cmd ("s"));
var patch = "c0820091c0035fd6";
cmd ("e io.va=false");
var before = trim (cmd ("p8 8 @ " + hex (loc.offset)));
cmd ("wx " + patch + " @ " + hex (loc.offset));
var after = trim (cmd ("p8 8 @ " + hex (loc.offset)));
cmd ("e io.va=" + oldIoVa);
if (oldSeek) {
cmd ("s " + oldSeek);
}
if (after !== patch) {
fail ("write-failed-before-" + before + "-after-" + after);
}
console.log ("target=" + flag.name);
console.log ("vaddr=" + hex (flag.addr));
console.log ("file_offset=" + hex (loc.offset));
console.log ("section=" + loc.section);
console.log ("before=" + before);
console.log ("after=" + after);
console.log ("status=" + (before === patch ? "already-patched" : "patched"));
})();
-173
View File
@@ -1,173 +0,0 @@
#!/usr/bin/env python3
import json, subprocess, sys, os, struct, re
def run(cmd, cwd=None):
p = subprocess.run(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
return p.returncode, p.stdout, p.stderr
def r2flutter_json(target):
rc, out, err = run([os.path.join(os.path.dirname(__file__), '..', 'bin', 'r2flutter'), '-q', '-j', '-H', target])
if rc != 0 or not out.strip():
raise RuntimeError('r2flutter failed: %s' % err)
return json.loads(out.strip().splitlines()[-1])
def round_up(x,a):
return (x + (a-1)) & ~(a-1)
HEX_RE = re.compile(r"0x([0-9a-fA-F]+)")
def search_hex(target, start, end, hexpat):
rc, out, err = run(['r2', '-nnq', '-c', f'e search.in=range; e search.from=0x{start:x}; e search.to=0x{end:x}; /x {hexpat}', target])
addrs = []
for line in out.splitlines():
m = HEX_RE.search(line)
if m:
addrs.append(int(m.group(1), 16))
return addrs
def read_qword(target, addr):
rc, out, err = run(['r2', '-nnq', '-c', f'pxq 1 @ 0x{addr:x}', target])
for line in out.splitlines():
m = HEX_RE.search(line)
if m:
return int(m.group(1), 16)
return 0
def le64(buf, off):
if off < 0 or off+8 > len(buf):
return 0
return struct.unpack_from('<Q', bytes(buf), off)[0]
def read_cstr(buf, off, maxlen=128):
if off < 0 or off >= len(buf):
return ''
end = off
n = 0
out = []
while end < len(buf) and n < maxlen:
b = buf[end]
if b == 0:
break
if 32 <= b < 127 or b == 9:
out.append(chr(b))
else:
return ''
end += 1
n += 1
return ''.join(out)
def derive_offsets(target):
j = r2flutter_json(target)
iso_data = j['iso_data']
iso_instr = j['iso_instr']
total = j['cluster']['total']
cws = j.get('cws', 4)
align = 16
base = iso_data + round_up(total, align)
end = iso_instr if iso_instr > base else base + (1<<22)
# Candidate offsets within Code and Function objects (64-bit typical)
ep_cands = [0x10,0x18,0x20,0x28,0x30]
owner_cands = [0x10,0x18,0x20,0x28,0x30]
name_cands = [0x10,0x18,0x20,0x28,0x30]
good_ep, good_owner, good_name = set(), set(), set()
# Fast path: search for common prefixes in strings to get candidate name addresses
names = []
for pat in (b'package:', b'dart:'):
hexpat = pat.hex()
addrs = search_hex(target, base, end, hexpat)
names.extend(addrs)
# For each candidate String address, locate owner(Function) via pointers.
# Consider both 64-bit and 32-bit compressed pointers.
max_hits = 800
for sa in names[:200]: # cap initial string candidates
le_name = sa.to_bytes(8, 'little').hex()
owner_ptrs = search_hex(target, base, end, le_name)
# also try 32-bit compressed encodings for (sa-base)
rel = sa - base
for sh in (0,1,2,3):
enc = (rel >> sh) & 0xffffffff
for tag in (0,1,3,7):
val = (enc | tag) & 0xffffffff
le4 = val.to_bytes(4, 'little').hex()
owner_ptrs += search_hex(target, base, end, le4)
# Each owner_ptr is at (owner + name_off)
for owner_ptr in owner_ptrs[:12]: # cap per-name refs
for name_off in name_cands:
owner = owner_ptr - name_off
if owner < base or owner >= end:
continue
# Find Code.owner refs: pointers to 'owner' within range (64-bit and 32-bit compressed)
le_owner = owner.to_bytes(8, 'little').hex()
code_owner_ptrs = search_hex(target, base, end, le_owner)
rel_owner = owner - base
for sh2 in (0,1,2,3):
enc2 = (rel_owner >> sh2) & 0xffffffff
for tag2 in (0,1,3,7):
val2 = (enc2 | tag2) & 0xffffffff
code_owner_ptrs += search_hex(target, base, end, val2.to_bytes(4, 'little').hex())
for cop in code_owner_ptrs[:16]:
# cop == (code_base + owner_off)
for owner_off in owner_cands:
code_base = cop - owner_off
if code_base < base or code_base >= end:
continue
for ep_off in ep_cands:
ep = read_qword(target, code_base + ep_off)
if iso_instr <= ep < iso_instr + (1<<26) and (ep & 3) == 0:
good_owner.add(owner_off)
good_name.add(name_off)
good_ep.add(ep_off)
max_hits -= 1
break
if max_hits <= 0:
break
if max_hits <= 0:
break
if max_hits <= 0:
break
if max_hits <= 0:
break
if max_hits <= 0:
break
return {
'hash': j['hash'],
'compressed_word_size': cws,
'heap_object_tag': 1,
'max_alignment': align,
'it_cap': 20000,
'code_entry_point_offsets': sorted(good_ep),
'code_owner_offsets': sorted(good_owner),
'function_name_offsets': sorted(good_name),
}
def merge_offsets(path, entry):
if os.path.exists(path):
with open(path, 'r') as f:
obj = json.load(f)
else:
obj = {'arch': 'arm64', 'hashes': {}}
h = entry['hash']
obj.setdefault('hashes', {})
obj['hashes'][h] = entry
with open(path, 'w') as f:
json.dump(obj, f, indent=2)
def main():
if len(sys.argv) < 2:
print('Usage: derive_offsets.py <path-to-libapp.so-or-.app>', file=sys.stderr)
sys.exit(1)
target = sys.argv[1]
# resolve .app path to App binary
if target.endswith('.app'):
appbin = os.path.join(target, 'Frameworks', 'App.framework', 'App')
if os.path.exists(appbin):
target = appbin
entry = derive_offsets(target)
out = os.path.join(os.path.dirname(__file__), '..', 'offsets.json')
merge_offsets(out, entry)
print(json.dumps(entry, indent=2))
if __name__ == '__main__':
main()
-60
View File
@@ -1,60 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Generate r2flutter/offsets.json for the detected snapshot hash and Dart version.
# Usage: scripts/gen_offsets.sh <libdir>
if [ $# -lt 1 ]; then
echo "Usage: $0 <libdir-with-libapp.so-and-libflutter.so>" >&2
exit 1
fi
LIBDIR="$1"
LIBAPP="$LIBDIR/libapp.so"
LIBFLUTTER="$LIBDIR/libflutter.so"
if [ ! -f "$LIBAPP" ] || [ ! -f "$LIBFLUTTER" ]; then
echo "Missing libapp.so or libflutter.so in $LIBDIR" >&2
exit 1
fi
SNAPSHOT_DATA_VADDR=$(readelf -Ws "$LIBAPP" | grep '_kDartVmSnapshotData$' | awk '{print "0x"$2}' | head -n 1)
if [ -z "$SNAPSHOT_DATA_VADDR" ]; then
echo "Failed to find _kDartVmSnapshotData symbol in $LIBAPP" >&2
exit 1
fi
HASH_OFFSET=$((SNAPSHOT_DATA_VADDR+20))
HASH=$(dd if="$LIBAPP" bs=1 skip=$((HASH_OFFSET)) count=32 2>/dev/null | tr -dc '0-9a-f' | head -c 32)
if [ -z "$HASH" ]; then
echo "Failed to extract snapshot hash from $LIBAPP" >&2
exit 1
fi
VERLINE="$(strings -a "$LIBFLUTTER" | sed -n 's/^\([0-9]\+\.[0-9]\+\.[0-9]\+\) (stable).*/\1/p' | head -n1)"
if [ -z "$VERLINE" ]; then
echo "Warning: Cannot determine Dart version from $LIBFLUTTER; continuing with hash only" >&2
fi
echo "Detected snapshot hash: $HASH, dart version: ${VERLINE:-unknown}"
# Prepare minimal offsets JSON with reasonable defaults; future versions of this
# generator can curl dart-lang/sdk sources and compute more fields.
OUTJSON="offsets.json"
mkdir -p "$(dirname "$OUTJSON")"
cat > "$OUTJSON" <<JSON
{
"arch": "arm64",
"hashes": {
"$HASH": {
"compressed_word_size": 4,
"heap_object_tag": 1,
"notes": "Autogenerated skeleton for $VERLINE; refine via SDK source parsing"
}
}
}
JSON
echo "Wrote $OUTJSON"
+1236
View File
File diff suppressed because it is too large. Load diff
+41 -21
View File
@@ -141,7 +141,7 @@ static bool find_dart_note_in_fat_macho(RBuffer *b, ut64 fsize, bool fat64, Dart
return false;
}
bool dart_app_find_macho_embedded_dart(const char *path, DartAppEmbeddedPayload *out) {
bool dart_app_find_macho_dart(const char *path, DartAppEmbeddedPayload *out) {
if (R_STR_ISEMPTY (path) || !out) {
return false;
}
@@ -179,14 +179,25 @@ static FILE *open_payload_temp(char **out_path) {
FILE *out = try_open_temp_path (r_file_temp ("r2flutter-dart-app-snap"), out_path);
if (!out) {
char *name = r_str_newf ("r2flutter-dart-app-snap.%" PFMT64x, (ut64)r_time_now ());
char *path = name? r_file_new (P_tmpdir, name, NULL): NULL;
char *tmpdir = r_file_tmpdir ();
char *path = (name && tmpdir)? r_file_new (tmpdir, name, NULL): NULL;
free (tmpdir);
free (name);
out = try_open_temp_path (path, out_path);
}
return out;
}
char *dart_app_extract_embedded_payload(const char *path, const DartAppEmbeddedPayload *payload) {
// msvc has no fseeko, but it ships an equivalent 64bit seek
static bool payload_seek(FILE *f, ut64 off) {
#if defined(_MSC_VER)
return _fseeki64 (f, (__int64)off, SEEK_SET) == 0;
#else
return fseeko (f, (off_t)off, SEEK_SET) == 0;
#endif
}
char *dart_app_extract_payload(const char *path, const DartAppEmbeddedPayload *payload) {
if (R_STR_ISEMPTY (path) || !payload || !payload->payload_size) {
return NULL;
}
@@ -202,7 +213,7 @@ char *dart_app_extract_embedded_payload(const char *path, const DartAppEmbeddedP
fclose (in);
return NULL;
}
bool ok = fseeko (in, (off_t)payload->payload_offset, SEEK_SET) == 0;
bool ok = payload_seek (in, payload->payload_offset);
ut64 remaining = payload->payload_size;
ut8 buf[65536];
while (ok && remaining > 0) {
@@ -231,6 +242,7 @@ char *dart_app_extract_embedded_payload(const char *path, const DartAppEmbeddedP
void dart_function_fini(DartFunction *fn) {
free (fn->name);
free (fn->signature);
}
static ut64 dart_normalize_code_addr(ut64 addr) {
@@ -253,6 +265,9 @@ static int dart_function_cmp(const DartFunction *fa, const DartFunction *fb) {
return 0;
}
// Above placeholder names (func.*, method.fn_*) but below any resolved name.
#define DART_CLASS_METHOD_NAME_QUALITY 20
static int dart_function_name_quality(const char *name) {
if (R_STR_ISEMPTY (name)) {
return 0;
@@ -284,7 +299,7 @@ static char *dart_strdup_filtered(const char *name) {
return dup;
}
static void dart_app_add_or_update_fn(DartApp *app, const char *name, ut64 addr, ut64 size, int quality) {
static void dart_app_add_or_update_fn(DartApp *app, const char *name, ut64 addr, ut64 size, int quality, const char *signature) {
if (!app || !app->functions || !addr) {
return;
}
@@ -300,6 +315,9 @@ static void dart_app_add_or_update_fn(DartApp *app, const char *name, ut64 addr,
if (size > fn->size) {
fn->size = size;
}
if (R_STR_ISNOTEMPTY (signature) && !fn->signature) {
fn->signature = strdup (signature);
}
if (quality > fn->quality || (!fn->name && filtered)) {
free (fn->name);
fn->name = filtered;
@@ -321,6 +339,7 @@ static void dart_app_add_or_update_fn(DartApp *app, const char *name, ut64 addr,
newfn->addr = addr;
newfn->size = size;
newfn->quality = quality;
newfn->signature = R_STR_ISNOTEMPTY (signature)? strdup (signature): NULL;
if (filtered) {
newfn->name = filtered;
} else {
@@ -460,14 +479,17 @@ static char *dart_format_method_full_name(const DartClassInfo *ci, const DartMet
if (!mi || R_STR_ISEMPTY (mi->name)) {
return NULL;
}
char *lib = dart_sanitize_component (ci && ci->library_name? ci->library_name: "app", true);
// Without a library name use the InstructionTable form (method.Owner.leaf)
// so both sources agree on the name of the same entry.
char *lib = ci && ci->library_name? dart_sanitize_component (ci->library_name, true): NULL;
char *owner = dart_sanitize_component (ci && ci->name? ci->name: mi->owner_name, false);
char *leaf = dart_format_method_leaf_name (ci? ci->name: mi->owner_name, mi);
const char *name = leaf? leaf: mi->name;
char *out = NULL;
if (R_STR_ISNOTEMPTY (owner)) {
out = r_str_newf ("method.%s.%s.%s", lib, owner, leaf? leaf: mi->name);
out = lib? r_str_newf ("method.%s.%s.%s", lib, owner, name): r_str_newf ("method.%s.%s", owner, name);
} else {
out = r_str_newf ("method.%s..%s", lib, leaf? leaf: mi->name);
out = lib? r_str_newf ("method.%s..%s", lib, name): r_str_newf ("method.%s", name);
}
r_name_filter (out, 0);
free (lib);
@@ -490,7 +512,8 @@ static void dart_app_load_it_functions(DartApp *app) {
entry->name? entry->name: "method.unknown",
dart_normalize_code_addr (entry->address),
0,
dart_function_name_quality (entry->name));
dart_function_name_quality (entry->name),
entry->signature);
}
dart_instruction_table_list_free (entries);
}
@@ -516,11 +539,15 @@ static void dart_app_merge_class_methods(DartApp *app) {
continue;
}
char *fullname = dart_format_method_full_name (ci, mi);
// InstructionTable names stay authoritative: deduplicated code is
// shared by several methods, so class metadata only fills the
// placeholder names the table could not resolve.
dart_app_add_or_update_fn (app,
fullname? fullname: mi->name,
dart_normalize_code_addr (mi->entry_point),
0,
dart_function_name_quality (fullname? fullname: mi->name));
R_MIN (dart_function_name_quality (fullname? fullname: mi->name), DART_CLASS_METHOD_NAME_QUALITY),
mi->signature);
free (fullname);
}
}
@@ -528,10 +555,7 @@ static void dart_app_merge_class_methods(DartApp *app) {
}
DartApp *dart_app_new(const char *path) {
DartApp *app = (DartApp *)calloc (1, sizeof (DartApp));
if (!app) {
return NULL;
}
DartApp *app = R_NEW0 (DartApp);
app->file_path = path? strdup (path): NULL;
app->functions = RVecDartFunction_new ();
return app;
@@ -573,7 +597,8 @@ static void add_fn_cb(const char *name, ut64 addr, ut64 size, void *user) {
name,
dart_normalize_code_addr (addr),
size,
dart_function_name_quality (name));
dart_function_name_quality (name),
NULL);
}
void dart_app_load_info(DartApp *app) {
@@ -589,12 +614,7 @@ void dart_app_load_info(DartApp *app) {
dart_app_load_it_functions (app);
dart_app_merge_class_methods (app);
if (RVecDartFunction_length (app->functions) == 0) {
ut64 base = 0, heap_base = 0;
int rc = dart_pool_enumerate (&app->dctx, app->file_path, add_fn_cb, app, &base, &heap_base);
if (rc == 0) {
app->base_addr = base;
app->heap_base = heap_base;
}
(void)dart_pool_scan (&app->dctx, add_fn_cb, app);
}
RVecDartFunction_sort (app->functions, dart_function_cmp);
if (app->dctx.verbose) {
+68 -1538
View File
File diff suppressed because it is too large. Load diff
+311 -9
View File
@@ -124,8 +124,61 @@ static char *make_method_flagname(const char *name) {
return flagname;
}
static void dump_func_r2(RStrBuf *sb, const DartFunction *fn, bool quiet) {
char *flagname = make_method_flagname (fn->name);
// Flag and function names are unique in r2: setting an existing flag name
// moves it, so a later function sharing a Dart name (e.g. several
// Native._anon_closure) would steal it from the first one. Suffix repeated
// names with the entry address.
static char *make_unique_method_flagname(HtPP *seen, const char *name, ut64 addr) {
char *flagname = make_method_flagname (name);
bool found = false;
ut64 owner = (ut64) (size_t)ht_pp_find (seen, flagname, &found);
if (found && owner != addr) {
char *unique = r_str_newf ("%s.0x%" PFMT64x, flagname, addr);
free (flagname);
return unique;
}
ht_pp_insert (seen, flagname, (void *) (size_t)addr);
return flagname;
}
static void method_flagname_free(HtUPKv *kv) {
free (kv->value);
}
// Map each function address to its unique method.* name, in app order.
static HtUP *method_flagnames(DartApp *app) {
HtUP *names = ht_up_new (NULL, method_flagname_free, NULL);
HtPP *seen = ht_pp_new0 ();
DartFunction *fn;
R_VEC_FOREACH (app->functions, fn) {
if (fn->name && !ht_up_find (names, fn->addr, NULL)) {
ht_up_insert (names, fn->addr, make_unique_method_flagname (seen, fn->name, fn->addr));
}
}
ht_pp_free (seen);
return names;
}
// Give each analyzed function its Dart name: r2's `af` prefers flags in the
// symbols space (e.g. Mach-O sym.func.*) over the method.* flags set here.
void dart_dumper_name_functions(DartApp *app) {
if (!app || !app->core || !app->functions) {
return;
}
HtUP *names = method_flagnames (app);
DartFunction *fn;
R_VEC_FOREACH (app->functions, fn) {
RAnalFunction *fcn = r_anal_get_function_at (app->core->anal, fn->addr);
const char *name = fcn? ht_up_find (names, fn->addr, NULL): NULL;
if (name && strcmp (fcn->name, name)) {
r_anal_function_rename (fcn, name);
}
}
ht_up_free (names);
}
static void dump_func_r2(RStrBuf *sb, HtPP *seen, const DartFunction *fn, bool quiet) {
char *flagname = make_unique_method_flagname (seen, fn->name, fn->addr);
r_strbuf_appendf (sb, "f %s = 0x%" PFMT64x "\n", flagname, (uint64_t)fn->addr);
if (!quiet) {
r_strbuf_appendf (sb, "'@0x%" PFMT64x "'CC %s\n", (uint64_t)fn->addr, fn->name);
@@ -133,7 +186,7 @@ static void dump_func_r2(RStrBuf *sb, const DartFunction *fn, bool quiet) {
free (flagname);
}
char *dart_dumper_dump4radare2(DartApp *app) {
char *dart_dumper_dump_r2(DartApp *app) {
RStrBuf *sb = r_strbuf_new ("");
const bool quiet = app && app->dctx.quiet;
@@ -145,13 +198,15 @@ char *dart_dumper_dump4radare2(DartApp *app) {
r_strbuf_appendf (sb, "f app.heap_base = 0x%" PFMT64x "\n", (uint64_t)app->heap_base);
if (app->functions) {
HtPP *seen = ht_pp_new0 ();
DartFunction *fn;
R_VEC_FOREACH (app->functions, fn) {
if (!fn->name) {
continue;
}
dump_func_r2 (sb, fn, quiet);
dump_func_r2 (sb, seen, fn, quiet);
}
ht_pp_free (seen);
}
r_strbuf_append (sb, "dr x27=`e anal.gp`\n");
@@ -182,6 +237,9 @@ static void dump_func_json(PJ *pj, const DartFunction *fn) {
if (fn->size) {
pj_kn (pj, "size", fn->size);
}
if (R_STR_ISNOTEMPTY (fn->signature)) {
pj_ks (pj, "signature", fn->signature);
}
pj_end (pj);
}
@@ -216,6 +274,7 @@ char *dart_dumper_dump_funcs(DartApp *app, int fmt) {
}
RStrBuf *sb = r_strbuf_new ("");
HtPP *seen = ht_pp_new0 ();
R_VEC_FOREACH (app->functions, fn) {
if (!fn->name) {
continue;
@@ -224,16 +283,251 @@ char *dart_dumper_dump_funcs(DartApp *app, int fmt) {
break;
}
if (fmt == 'r') {
dump_func_r2 (sb, fn, app->dctx.quiet);
dump_func_r2 (sb, seen, fn, app->dctx.quiet);
} else {
dump_func_text (sb, fn);
}
count++;
}
ht_pp_free (seen);
return drain_trimmed_strbuf (sb);
}
void dart_dumper_apply_to_core(DartApp *app) {
// Map a single Dart type token to a C-ish spelling radare2's `afs` accepts.
static void dart_type_to_c(RStrBuf *sb, const char *tok) {
if (R_STR_ISEMPTY (tok) || !strcmp (tok, "dynamic")) {
r_strbuf_append (sb, "void *");
} else if (!strcmp (tok, "void")) {
r_strbuf_append (sb, "void");
} else if (!strcmp (tok, "int")) {
r_strbuf_append (sb, "int64_t");
} else if (!strcmp (tok, "double")) {
r_strbuf_append (sb, "double");
} else if (!strcmp (tok, "bool")) {
r_strbuf_append (sb, "bool");
} else if (!strcmp (tok, "String")) {
r_strbuf_append (sb, "char *");
} else {
// Object / user-class type: an opaque pointer named after the class.
char *safe = strdup (tok);
r_name_filter (safe, 0);
r_strbuf_appendf (sb, "%s *", R_STR_ISNOTEMPTY (safe)? safe: "void");
free (safe);
}
}
// Turn a recovered Dart signature "RET(T,T,...)" into a radare2 `afs` prototype
// for the already filtered function name `name` and return it, or NULL if the
// signature is not in that canonical form.
static char *dart_signature_to_afs_proto(const char *name, const char *sig) {
if (R_STR_ISEMPTY (sig) || strchr (sig, ' ') || strchr (sig, '=')) {
return NULL; // not our canonical "RET(T,...)" form
}
const char *lp = strchr (sig, '(');
if (!lp) {
return NULL;
}
char *ret = r_str_ndup (sig, (int) (lp - sig));
if (R_STR_ISEMPTY (ret)) {
free (ret);
return NULL;
}
char *inside = strdup (lp + 1);
char *rp = strrchr (inside, ')');
if (rp) {
*rp = '\0';
}
RStrBuf *sb = r_strbuf_new ("");
dart_type_to_c (sb, ret);
r_strbuf_appendf (sb, " %s(", name);
int argc = 0;
if (R_STR_ISNOTEMPTY (inside)) {
RList *args = r_str_split_list (inside, ",", 0);
RListIter *it;
char *a;
r_list_foreach (args, it, a) {
if (argc > 0) {
r_strbuf_append (sb, ", ");
}
dart_type_to_c (sb, r_str_trim_head_ro (a));
// The first parameter of an instance method is the receiver.
if (argc == 0) {
r_strbuf_append (sb, " this");
} else {
r_strbuf_appendf (sb, " arg%d", argc);
}
argc++;
}
r_list_free (args);
}
r_strbuf_append (sb, ")");
free (ret);
free (inside);
return r_strbuf_drain (sb);
}
// Same scan as is_type() in radare2's libr/anal/type.c.
static char *dart_parsed_type_kind(char *s) {
char *p;
if ((p = strstr (s, "=type")) || (p = strstr (s, "=struct")) || (p = strstr (s, "=union")) ||
(p = strstr (s, "=enum")) || (p = strstr (s, "=typedef")) || (p = strstr (s, "=func"))) {
return p;
}
return NULL;
}
// `afs` stores a prototype with r_anal_save_parsed_type, which first calls
// r_anal_remove_parsed_type for every type the parsed C declares. When that type
// already exists, the removal r_type_del()s it and then sorts and scans the
// whole type database for leftover subkeys. For these prototypes r_type_del has
// already removed every key, so the scan finds nothing, but it cost
// O(types log types) for every repeated name (anonymous closures, and names the
// C parser shortens at ':' such as `dyn:call` -> `call`), making the signature
// pass quadratic. Deleting those types here first, with the parser `afs` uses,
// lets `afs` take its early return and leaves the same type database.
static void dart_types_predelete(RAnal *anal, const char *proto) {
Sdb *tdb = anal->sdb_types;
if (!tdb) {
return;
}
char *code = r_str_newf ("%s;", proto);
char *err = NULL;
char *parsed = r_anal_cparse (anal, code, &err);
free (code);
free (err);
if (!parsed) {
return;
}
char *cur = parsed;
while ((cur = dart_parsed_type_kind (cur))) {
char *name = cur++;
*name = 0;
while (name > parsed && *(name - 1) != '\n') {
name--;
}
if (sdb_const_get (tdb, name, 0)) {
r_type_del (tdb, name);
}
}
free (parsed);
}
static bool dart_flag_found_cb(RFlagItem *fi, void *user) {
(void)fi;
*(bool *)user = true;
return false;
}
// With anal.trycatch enabled every `af` scans all flags twice looking for
// try.<addr>.catch/filter handler flags. -AAA creates flags for every Dart
// method, string and pool reference, so across tens of thousands of functions
// those scans dominated the pass. When no try.* flag exists the scans cannot
// find a handler, so disabling the option for the pass changes nothing else.
bool dart_core_trycatch_suspend(RCore *core) {
const bool prev = r_config_get_b (core->config, "anal.trycatch");
if (prev) {
bool found = false;
r_flag_foreach_prefix (core->flags, "try.", 4, dart_flag_found_cb, &found);
if (!found) {
r_config_set_b (core->config, "anal.trycatch", false);
}
}
return prev;
}
void dart_core_trycatch_restore(RCore *core, bool prev) {
r_config_set_b (core->config, "anal.trycatch", prev);
}
static int dart_fn_addr_desc(const void *a, const void *b) {
const DartFunction *fa = *(const DartFunction *const *)a;
const DartFunction *fb = *(const DartFunction *const *)b;
if (fa->addr < fb->addr) {
return 1;
}
if (fa->addr > fb->addr) {
return -1;
}
return 0;
}
// Define a function at `addr` if needed and set its recovered signature so that
// `afsj`/`afcrj` report real parameter and return types. Functions are analysed
// in descending address order: a Dart function whose analysis overruns into the
// next one then stops at the already-defined higher function instead of merging.
void dart_dumper_apply_signatures(DartApp *app) {
if (!app || !app->core || !app->functions) {
return;
}
RCore *core = app->core;
size_t total = RVecDartFunction_length (app->functions);
if (!total) {
return;
}
DartFunction **order = (DartFunction **)malloc (total * sizeof (DartFunction *));
if (!order) {
return;
}
size_t n = 0;
DartFunction *fn;
R_VEC_FOREACH (app->functions, fn) {
if (fn->addr && R_STR_ISNOTEMPTY (fn->signature)) {
order[n++] = fn;
}
}
qsort (order, n, sizeof (DartFunction *), dart_fn_addr_desc);
HtUP *names = method_flagnames (app);
// Keep the analysis local to each function start; the surrounding pass owns
// broader discovery. String ESIL emulation is very slow and irrelevant to
// prototypes, so disable it for the duration of this pass.
// Variable recovery, call-following and jump tables are not needed for the
// prototypes either, and var recovery alone is about half of this `af` cost.
int prev_hasnext = r_config_get_i (core->config, "anal.hasnext");
int prev_emustr = r_config_get_i (core->config, "emu.str");
bool prev_calls = r_config_get_b (core->config, "anal.calls");
bool prev_vars = r_config_get_b (core->config, "anal.vars");
bool prev_jmptbl = r_config_get_b (core->config, "anal.jmp.tbl");
r_config_set_i (core->config, "anal.hasnext", 0);
r_config_set_i (core->config, "emu.str", 0);
r_config_set_b (core->config, "anal.calls", false);
r_config_set_b (core->config, "anal.vars", false);
r_config_set_b (core->config, "anal.jmp.tbl", false);
const bool prev_trycatch = dart_core_trycatch_suspend (core);
for (size_t i = 0; i < n; i++) {
fn = order[i];
const char *flagname = ht_up_find (names, fn->addr, NULL);
char *name = strdup (flagname? flagname: "fcn");
r_name_filter (name, 0);
if (R_STR_ISEMPTY (name)) {
free (name);
name = strdup ("fcn");
}
char *proto = dart_signature_to_afs_proto (name, fn->signature);
if (!proto) {
free (name);
continue;
}
if (!r_anal_get_function_at (core->anal, fn->addr)) {
r_core_cmdf (core, "af @ 0x%" PFMT64x, fn->addr);
}
if (r_anal_get_function_at (core->anal, fn->addr)) {
dart_types_predelete (core->anal, proto);
r_core_cmdf (core, "\"afs %s\" @ 0x%" PFMT64x, proto, fn->addr);
}
free (proto);
free (name);
}
r_config_set_i (core->config, "anal.hasnext", prev_hasnext);
r_config_set_i (core->config, "emu.str", prev_emustr);
r_config_set_b (core->config, "anal.calls", prev_calls);
r_config_set_b (core->config, "anal.vars", prev_vars);
r_config_set_b (core->config, "anal.jmp.tbl", prev_jmptbl);
dart_core_trycatch_restore (core, prev_trycatch);
ht_up_free (names);
free (order);
}
void dart_dumper_apply_to_core(DartApp *app, bool apply_signatures) {
if (!app || !app->core) {
return;
}
@@ -245,17 +539,25 @@ void dart_dumper_apply_to_core(DartApp *app) {
}
if (app->functions) {
HtUP *names = method_flagnames (app);
DartFunction *fn;
R_VEC_FOREACH (app->functions, fn) {
if (!fn->name) {
const char *flagname = ht_up_find (names, fn->addr, NULL);
if (!fn->name || !flagname) {
continue;
}
char *flagname = make_method_flagname (fn->name);
r_flag_set (core->flags, flagname, fn->addr, fn->size);
r_meta_set_string (core->anal, R_META_TYPE_COMMENT, fn->addr, fn->name);
free (flagname);
}
ht_up_free (names);
}
r_core_cmd0 (core, "e emu.str=true");
// Applying signatures forces analysis (af) of every typed function, which is
// slow on a large snapshot; only do it at the deepest level (-AAA). Lighter
// consumers (e.g. the reflow decompiler) read per-function signatures from
// the fast `-fj` dump instead.
if (apply_signatures) {
dart_dumper_apply_signatures (app);
}
}
+3
View File
@@ -101,6 +101,9 @@ void dart_obf_fini(DartCtx *ctx) {
ht_pp_free (ctx->obf_by_obfuscated);
ctx->obf_by_obfuscated = NULL;
ctx->obf_map_tried = false;
// Release the read_mem window cache (scoped to one command invocation).
dart_read_cache_free (ctx->rmem);
ctx->rmem = NULL;
}
char *dart_obf_resolve(DartCtx *ctx, const char *name) {
+77 -33
View File
@@ -50,7 +50,7 @@ typedef struct {
ut64 index;
ut8 bits;
DartPpInfo pp;
DartModernPoolSlotInfo slot;
ModernPoolSlot slot;
} DartPpSlotInfo;
static void dart_object_info_fini(DartObjectInfo *info) {
@@ -76,7 +76,7 @@ static void dart_pp_slot_info_fini(DartPpSlotInfo *info) {
return;
}
dart_pp_info_fini (&info->pp);
dart_modern_pool_slot_info_fini (&info->slot);
modern_slot_fini (&info->slot);
memset (info, 0, sizeof (*info));
}
@@ -85,19 +85,6 @@ static int dart_object_heap_tag(DartCtx *ctx) {
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++) {
@@ -147,6 +134,17 @@ static bool dart_object_parse_pp_spec(const char *spec, ut64 *out) {
return dart_object_parse_u64 (p + 3, out);
}
static bool dart_object_parse_ref_spec(const char *spec, ut64 *out) {
if (R_STR_ISEMPTY (spec) || !out) {
return false;
}
const char *p = r_str_trim_head_ro (spec);
if (tolower ((ut8)p[0]) != 'r' || tolower ((ut8)p[1]) != 'e' || tolower ((ut8)p[2]) != 'f' || p[3] != ':') {
return false;
}
return dart_object_parse_u64 (p + 4, out);
}
static char *dart_object_bytes_preview(const ut8 *buf, ut64 len) {
RStrBuf *sb = r_strbuf_new ("");
for (ut64 i = 0; i < len; i++) {
@@ -265,7 +263,7 @@ static bool dart_object_decode_at(DartCtx *ctx, ut64 addr, DartObjectInfo *out)
}
out->addr = addr;
out->header = r_read_le64 (hdr);
out->cid = dart_object_cid_from_header (ctx, out->header);
out->cid = (int)dart_cid_from_object_header (out->header);
if (out->cid <= 0 || out->cid >= 0xfffff) {
return false;
}
@@ -357,14 +355,14 @@ static bool dart_pp_slot_decode(DartCtx *ctx, ut64 offset, DartPpSlotInfo *slot)
if (slot->bits_ok) {
DartSnapshotHeader sh = { 0 };
if (dart_snapshot_header_read (ctx, slot->pp.snapshot_base, &sh) && sh.ok) {
const DartModernClusterRequest req = {
const ModernReq 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);
slot->slot_ok = modern_resolve_pp_slot (&req, offset, &slot->slot);
}
}
return true;
@@ -391,11 +389,13 @@ static void dart_object_json_object_fields(PJ *pj, const DartObjectInfo *obj) {
}
}
static void dart_object_json_value(PJ *pj, const DartValueInfo *value, bool decode_raw) {
static void dart_object_json_value(PJ *pj, const DartValueInfo *value, bool decode_raw, bool snapshot_ref) {
pj_ko (pj, "value");
pj_kn (pj, "raw", value->raw);
pj_kb (pj, "decoded", decode_raw);
if (!decode_raw) {
if (snapshot_ref) {
pj_ks (pj, "kind", "snapshot_ref");
} else if (!decode_raw) {
pj_ks (pj, "kind", "object_pool_ref");
} else if (value->selected >= 0) {
pj_ks (pj, "kind", "object");
@@ -443,7 +443,7 @@ static void dart_object_json_pool_slot(PJ *pj, const DartPpSlotInfo *slot) {
}
pj_kb (pj, "resolved", slot->slot_ok);
if (slot->slot_ok) {
const DartModernPoolSlotInfo *si = &slot->slot;
const ModernPoolSlot *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);
@@ -478,7 +478,7 @@ static void dart_object_json_pool_slot(PJ *pj, const DartPpSlotInfo *slot) {
pj_end (pj);
}
static char *dart_object_dump_json(const char *spec, const DartValueInfo *value, const DartPpSlotInfo *slot, bool decode_raw) {
static char *dart_object_dump_json(const char *spec, const DartValueInfo *value, const DartPpSlotInfo *slot, ModernValueGraph *graph, bool decode_raw) {
PJ *pj = pj_new ();
if (!pj) {
return strdup ("{\"error\":\"Failed to create JSON\"}");
@@ -495,6 +495,8 @@ static char *dart_object_dump_json(const char *spec, const DartValueInfo *value,
pj_ks (pj, "kind", "pp_slot");
}
dart_object_json_pool_slot (pj, slot);
} else if (graph) {
pj_ks (pj, "kind", "snapshot_ref");
} else if (decode_raw && value->selected >= 0) {
pj_ks (pj, "kind", "object");
} else if (value->is_smi) {
@@ -502,7 +504,8 @@ static char *dart_object_dump_json(const char *spec, const DartValueInfo *value,
} else {
pj_ks (pj, "kind", "unknown");
}
dart_object_json_value (pj, value, decode_raw);
dart_object_json_value (pj, value, decode_raw, graph && (!slot || !slot->present));
modern_value_graph_json (pj, graph);
pj_end (pj);
return pj_drain (pj);
}
@@ -565,7 +568,7 @@ static void dart_object_text_pool_slot(RStrBuf *sb, const DartPpSlotInfo *slot)
r_strbuf_appendf (sb, "pp.bits: 0x%02x\n", (unsigned int)slot->bits);
}
if (slot->slot_ok) {
const DartModernPoolSlotInfo *si = &slot->slot;
const ModernPoolSlot *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);
@@ -594,7 +597,7 @@ static void dart_object_text_pool_slot(RStrBuf *sb, const DartPpSlotInfo *slot)
}
}
static char *dart_object_dump_text(const char *spec, const DartValueInfo *value, const DartPpSlotInfo *slot, bool decode_raw, bool quiet) {
static char *dart_object_dump_text(const char *spec, const DartValueInfo *value, const DartPpSlotInfo *slot, ModernValueGraph *graph, 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)) {
@@ -608,6 +611,10 @@ static char *dart_object_dump_text(const char *spec, const DartValueInfo *value,
return r_strbuf_drain (sb);
}
}
if (graph) {
r_strbuf_append (sb, modern_value_graph_root_kind (graph));
return r_strbuf_drain (sb);
}
r_strbuf_appendf (sb, "raw=0x%" PFMT64x, value->raw);
return r_strbuf_drain (sb);
}
@@ -615,16 +622,19 @@ static char *dart_object_dump_text(const char *spec, const DartValueInfo *value,
if (slot && slot->present) {
dart_object_text_pool_slot (sb, slot);
}
dart_object_text_value (sb, value, decode_raw);
if (!graph || (slot && slot->present)) {
dart_object_text_value (sb, value, decode_raw);
}
modern_value_graph_text (sb, graph);
return r_strbuf_drain (sb);
}
static char *dart_object_dump_r2(const DartValueInfo *value, const DartPpSlotInfo *slot, bool decode_raw) {
static char *dart_object_dump_r2(const DartValueInfo *value, const DartPpSlotInfo *slot, ModernValueGraph *graph, 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;
const ModernPoolSlot *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);
@@ -650,6 +660,9 @@ static char *dart_object_dump_r2(const DartValueInfo *value, const DartPpSlotInf
} else if (decode_raw && value->is_smi) {
r_strbuf_appendf (sb, "'CC Dart Smi raw=0x%" PFMT64x " value=%" PFMT64d "\n", value->raw, value->smi);
}
if (graph) {
r_strbuf_appendf (sb, "'CC Dart snapshot value kind=%s\n", modern_value_graph_root_kind (graph));
}
return r_strbuf_drain (sb);
}
@@ -670,8 +683,10 @@ char *dart_pool_dump_object(DartCtx *ctx, const char *spec, int fmt) {
DartVerLayout *layout_owned = ctx->layout? NULL: dart_ctx_init_layout (ctx, &layout_tmp);
ut64 raw = 0;
ut64 pp_offset = 0;
ut64 snapshot_ref = 0;
bool is_pp = dart_object_parse_pp_spec (spec, &pp_offset);
if (!is_pp) {
bool is_ref = dart_object_parse_ref_spec (spec, &snapshot_ref);
if (!is_pp && !is_ref) {
if (!dart_object_parse_u64 (spec, &raw)) {
if (layout_owned) {
dart_ctx_fini_layout (ctx, layout_owned);
@@ -684,6 +699,15 @@ char *dart_pool_dump_object(DartCtx *ctx, const char *spec, int fmt) {
if (raw >= DART_SYNTHETIC_PP_BASE) {
pp_offset = raw - DART_SYNTHETIC_PP_BASE;
is_pp = true;
} else {
DartPpInfo pp_info = { 0 };
if (dart_resolve_pp_info (ctx, &pp_info)) {
if (raw >= pp_info.base && raw - pp_info.base < pp_info.size) {
pp_offset = raw - pp_info.base;
is_pp = true;
}
dart_pp_info_fini (&pp_info);
}
}
}
DartValueInfo value;
@@ -698,15 +722,35 @@ char *dart_pool_dump_object(DartCtx *ctx, const char *spec, int fmt) {
decode_raw = ! (slot.slot_ok && slot.slot.behavior == 0 && slot.slot.type == 1);
}
}
dart_value_decode (ctx, raw, &value);
if (!is_ref) {
dart_value_decode (ctx, raw, &value);
} else {
decode_raw = false;
}
ModernValueGraph *graph = NULL;
if (is_ref || (slot.slot_ok && slot.slot.behavior == 0 && slot.slot.type == 1)) {
const ut64 snapshot_base = is_pp? slot.pp.snapshot_base: ctx->iso_data;
DartSnapshotHeader sh = { 0 };
if (snapshot_base && dart_snapshot_header_read (ctx, snapshot_base, &sh) && sh.ok) {
const ModernReq req = {
ctx,
sh.cluster_start,
snapshot_base + sh.total_len,
sh.nc,
sh.nb
};
graph = modern_value_graph_new (&req, is_ref? snapshot_ref: slot.slot.ref);
}
}
char *out = NULL;
if (fmt == 'j') {
out = dart_object_dump_json (spec, &value, is_pp? &slot: NULL, decode_raw);
out = dart_object_dump_json (spec, &value, is_pp? &slot: NULL, graph, decode_raw);
} else if (fmt == 'r') {
out = dart_object_dump_r2 (&value, is_pp? &slot: NULL, decode_raw);
out = dart_object_dump_r2 (&value, is_pp? &slot: NULL, graph, decode_raw);
} else {
out = dart_object_dump_text (spec, &value, is_pp? &slot: NULL, decode_raw, ctx->quiet);
out = dart_object_dump_text (spec, &value, is_pp? &slot: NULL, graph, decode_raw, ctx->quiet);
}
modern_value_graph_free (graph);
dart_value_info_fini (&value);
dart_pp_slot_info_fini (&slot);
if (layout_owned) {
+327 -89
View File
@@ -2,6 +2,10 @@
#include "dart_pool_parse_priv.h"
#ifdef R_BIN_ATTR_MIXIN
#define R2_HAS_BIN_ATTR 1
#endif
static DartFieldInfo *dart_field_info_clone(const DartFieldInfo *fi) {
if (!fi) {
return NULL;
@@ -47,10 +51,58 @@ static void dart_interface_info_free(void *p) {
}
}
typedef enum {
kFieldCid_extract = 10,
kLibraryCid_extract = 12,
} ExtraCids;
// Cluster classes the legacy (non-OBJECT_HEADER) parser knows how to decode,
// resolved for the snapshot's own layout. Absent classes resolve to -1 and then
// match nothing, which is the point: comparing against a hardcoded id silently
// decoded the wrong cluster on every release that shifted the numbering.
typedef struct {
int string;
int one_byte_string;
int two_byte_string;
int class_;
int field;
int function;
int type;
int function_type;
int type_parameter;
int type_arguments;
int array;
int immutable_array;
int library;
} LegacyClusterCids;
static LegacyClusterCids legacy_cluster_cids(const DartVerLayout *layout) {
const LegacyClusterCids lc = {
.string = dart_cid_get (layout, DART_CID_STRING),
.one_byte_string = dart_cid_get (layout, DART_CID_ONE_BYTE_STRING),
.two_byte_string = dart_cid_get (layout, DART_CID_TWO_BYTE_STRING),
.class_ = dart_cid_get (layout, DART_CID_CLASS),
.field = dart_cid_get (layout, DART_CID_FIELD),
.function = dart_cid_get (layout, DART_CID_FUNCTION),
.type = dart_cid_get (layout, DART_CID_TYPE),
.function_type = dart_cid_get (layout, DART_CID_FUNCTION_TYPE),
.type_parameter = dart_cid_get (layout, DART_CID_TYPE_PARAMETER),
.type_arguments = dart_cid_get (layout, DART_CID_TYPE_ARGUMENTS),
.array = dart_cid_get (layout, DART_CID_ARRAY),
.immutable_array = dart_cid_get (layout, DART_CID_IMMUTABLE_ARRAY),
.library = dart_cid_get (layout, DART_CID_LIBRARY),
};
return lc;
}
static inline bool legacy_cid_is(ut64 cid, int resolved) {
return resolved >= 0 && cid == (ut64)resolved;
}
// Clusters whose decoders may legitimately give up part-way (variable-length
// payloads we do not fully model); the rest are treated as stream corruption.
static bool legacy_cid_recoverable(ut64 cid, const LegacyClusterCids *lc) {
return legacy_cid_is (cid, lc->function) ||
legacy_cid_is (cid, lc->function_type) ||
legacy_cid_is (cid, lc->type_arguments) ||
legacy_cid_is (cid, lc->array) ||
legacy_cid_is (cid, lc->immutable_array);
}
#define DART_TYPE_CLASS_ID_SHIFT 3
#define DART_TYPE_CLASS_ID_MASK ((1U << 20) - 1)
@@ -415,7 +467,7 @@ static int decode_type_cluster_ext(ClusterStream *s, DartCtx *ctx, RList *type_l
for (ut64 i = 0; i < count; i++) {
DartTypeInfo *ti = R_NEW0 (DartTypeInfo);
ti->ref_id = (*ref_counter)++;
ti->kind = kTypeCid;
ti->kind = DART_TYPE_KIND_TYPE;
ut64 type_test_stub_ref = 0;
ut64 hash_ref = 0;
ut64 args_ref = 0;
@@ -451,7 +503,7 @@ static int decode_type_parameter_cluster_ext(ClusterStream *s, DartCtx *ctx, RLi
for (ut64 i = 0; i < count; i++) {
DartTypeInfo *ti = R_NEW0 (DartTypeInfo);
ti->ref_id = (*ref_counter)++;
ti->kind = kTypeParameterCid;
ti->kind = DART_TYPE_KIND_TYPE_PARAMETER;
ut64 type_test_stub_ref = 0;
ut64 hash_ref = 0;
ut64 owner_ref = 0;
@@ -518,7 +570,7 @@ static int decode_function_type_cluster_ext(ClusterStream *s, DartCtx *ctx, RLis
for (ut64 i = 0; i < count; i++) {
DartTypeInfo *ti = R_NEW0 (DartTypeInfo);
ti->ref_id = (*ref_counter)++;
ti->kind = kFunctionTypeCid;
ti->kind = DART_TYPE_KIND_FUNCTION_TYPE;
ut64 type_test_stub_ref = 0;
ut64 hash_ref = 0;
if (!cs_read_ref_id (s, &type_test_stub_ref) || !cs_read_ref_id (s, &hash_ref) || !cs_read_ref_id (s, &ti->type_parameters_ref) || !cs_read_ref_id (s, &ti->result_type_ref) || !cs_read_ref_id (s, &ti->parameter_types_ref) || !cs_read_ref_id (s, &ti->named_parameter_names_ref)) {
@@ -658,23 +710,27 @@ static char *dup_ref_string_obf(DartCtx *ctx, ut64 ref) {
return value;
}
static const char *builtin_type_name(ut64 cid) {
switch (cid) {
case kStringCid:
case kOneByteStringCid:
case kTwoByteStringCid:
// `Type.type_class_id` is a class id packed into the type's flags, not a ref, so
// it is compared against this layout's real ids.
static const char *builtin_type_name(DartCtx *ctx, ut64 cid) {
const DartVerLayout *layout = ctx? ctx->layout: NULL;
if (legacy_cid_is (cid, dart_cid_get (layout, DART_CID_STRING)) ||
legacy_cid_is (cid, dart_cid_get (layout, DART_CID_ONE_BYTE_STRING)) ||
legacy_cid_is (cid, dart_cid_get (layout, DART_CID_TWO_BYTE_STRING))) {
return "String";
case kArrayCid:
case kImmutableArrayCid:
case kGrowableObjectArrayCid:
return "List";
case kMintCid:
return "int";
case kDoubleCid:
return "double";
default:
return NULL;
}
if (legacy_cid_is (cid, dart_cid_get (layout, DART_CID_ARRAY)) ||
legacy_cid_is (cid, dart_cid_get (layout, DART_CID_IMMUTABLE_ARRAY)) ||
legacy_cid_is (cid, dart_cid_get (layout, DART_CID_GROWABLE_OBJECT_ARRAY))) {
return "List";
}
if (legacy_cid_is (cid, dart_cid_get (layout, DART_CID_MINT))) {
return "int";
}
if (legacy_cid_is (cid, dart_cid_get (layout, DART_CID_DOUBLE))) {
return "double";
}
return NULL;
}
static DartTypeInfo *find_type_by_ref(RList *type_list, ut64 ref) {
@@ -855,7 +911,7 @@ static char *resolve_type_name_depth(DartCtx *ctx, RList *class_list, RList *typ
if (R_STR_ISNOTEMPTY (ti->name)) {
return strdup (ti->name);
}
if (ti->kind == kTypeParameterCid) {
if (ti->kind == DART_TYPE_KIND_TYPE_PARAMETER) {
bool is_function_param = (ti->flags & DART_TYPE_PARAMETER_FUNCTION_FLAG) != 0;
const char *base_prefix = is_function_param? "F": "C";
const char *index_prefix = is_function_param? "Y": "X";
@@ -868,7 +924,7 @@ static char *resolve_type_name_depth(DartCtx *ctx, RList *class_list, RList *typ
}
return name;
}
if (ti->kind == kFunctionTypeCid) {
if (ti->kind == DART_TYPE_KIND_FUNCTION_TYPE) {
char *name = resolve_function_type_name (ctx, class_list, type_list, type_args_list, array_list, ti, depth);
if (name) {
ti->name = strdup (name);
@@ -876,7 +932,7 @@ static char *resolve_type_name_depth(DartCtx *ctx, RList *class_list, RList *typ
return name;
}
char *name = NULL;
const char *builtin = builtin_type_name (ti->type_class_ref);
const char *builtin = builtin_type_name (ctx, ti->type_class_ref);
if (builtin) {
name = strdup (builtin);
} else {
@@ -1319,7 +1375,7 @@ static void resolve_class_interfaces(DartCtx *ctx, RList *class_list, RList *typ
char *name = resolve_type_name (ctx, class_list, type_list, type_args_list, array_list, type_ref);
ut64 class_ref = 0;
DartTypeInfo *ti = find_type_by_ref (type_list, type_ref);
if (ti && ti->kind == kTypeCid) {
if (ti && ti->kind == DART_TYPE_KIND_TYPE) {
DartClassInfo *target_ci = find_class_by_ref (class_list, ti->type_class_ref);
if (target_ci) {
class_ref = target_ci->ref_id;
@@ -1432,6 +1488,46 @@ static void resolve_class_and_field_names(DartCtx *ctx, RList *class_list, RList
}
}
// Function fills only carry an InstructionTable slot; decode the table once
// and turn each method's slot into its entry address.
static void resolve_method_entry_points(DartCtx *ctx, RList *class_list) {
const int dump_snapshot_json = ctx->dump_snapshot_json;
const int dump_fns_limit = ctx->dump_fns_limit;
ctx->dump_snapshot_json = 0;
ctx->dump_fns_limit = 0;
RVecDartInstructionTableEntry *it = dart_pool_extract_instruction_table (ctx);
ctx->dump_snapshot_json = dump_snapshot_json;
ctx->dump_fns_limit = dump_fns_limit;
if (!it) {
return;
}
ut64 count = 0;
DartInstructionTableEntry *ie;
R_VEC_FOREACH (it, ie) {
count = R_MAX (count, ie->index + 1);
}
// Function::code_index is the raw InstructionTable slot, not the
// first_entry_with_code-relative index kept in ie->code_index. Slots below
// first_entry_with_code lost their Code object (discarded in AOT) but
// still own real instructions, so map them too.
ut64 *addr_by_code_index = calloc (count + 1, sizeof (ut64));
R_VEC_FOREACH (it, ie) {
addr_by_code_index[ie->index] = ie->address;
}
RListIter *iter, *miter;
DartClassInfo *ci;
DartMethodInfo *mi;
r_list_foreach (class_list, iter, ci) {
r_list_foreach (ci->methods, miter, mi) {
if (!mi->entry_point && mi->code_index < count) {
mi->entry_point = addr_by_code_index[mi->code_index];
}
}
}
free (addr_by_code_index);
dart_instruction_table_list_free (it);
}
RList *dart_pool_extract_classes(DartCtx *ctx) {
if (!ctx || !ctx->core) {
return NULL;
@@ -1464,8 +1560,8 @@ RList *dart_pool_extract_classes(DartCtx *ctx) {
ut64 total_refs = sh.nb + sh.no + 16;
ctx->refs_count = total_refs;
ctx->refs = (void **)calloc (total_refs, sizeof (void *));
const DartModernClusterRequest req = { ctx, sh.cluster_start, snapshot_base + sh.total_len, sh.nc, sh.nb };
modern_classes = dart_modern_extract_classes_from_clusters (&req, class_list);
const ModernReq req = { ctx, sh.cluster_start, snapshot_base + sh.total_len, sh.nc, sh.nb };
modern_classes = modern_extract_classes (&req, class_list);
if (modern_classes) {
if (ctx->verbose > 0) {
int modern_fields = 0;
@@ -1504,71 +1600,50 @@ RList *dart_pool_extract_classes(DartCtx *ctx) {
.end = snapshot_base + sh.total_len
};
ut64 ref_counter = sh.nb + 1;
const LegacyClusterCids lc = legacy_cluster_cids (ctx->layout);
for (ut64 ci2 = 0; ci2 < sh.nc && stream.cursor < stream.end; ci2++) {
uint32_t tags = 0;
if (!cs_read_u32 (&stream, &tags)) {
ut32 tags = 0;
bool tags_read = synthetic_legacy_parse
? cs_read_u32 (&stream, &tags)
: cs_read_tagged32 (&stream, &tags);
if (!tags_read) {
break;
}
uint32_t cid = (tags >> 12) & 0xFFFFF;
const ut32 cid = dart_cid_from_tags (ctx, tags);
bool is_canonical = ((tags >> 1) & 1) != 0;
(void)is_canonical;
if (ctx->verbose > 1) {
fprintf (stderr, "[r2flutter] Cluster %" PRIu64 ": cid=%u (Class=%d,Field=%d,String=%d)\n", ci2, cid, kClassCid, kFieldCid_extract, kOneByteStringCid);
fprintf (stderr, "[r2flutter] Cluster %" PRIu64 ": cid=%u (Class=%d,Field=%d,String=%d)\n", ci2, cid, lc.class_, lc.field, lc.one_byte_string);
}
int rc = 0;
switch (cid) {
case kOneByteStringCid:
case kTwoByteStringCid:
case kStringCid:
if (legacy_cid_is (cid, lc.one_byte_string) || legacy_cid_is (cid, lc.two_byte_string) || legacy_cid_is (cid, lc.string)) {
rc = decode_string_cluster (&stream, ctx, &ref_counter, false);
break;
case kClassCid:
} else if (legacy_cid_is (cid, lc.class_)) {
rc = decode_class_cluster_ext (&stream, ctx, class_list, &ref_counter);
break;
case kFieldCid_extract:
} else if (legacy_cid_is (cid, lc.field)) {
rc = decode_field_cluster_ext (&stream, ctx, field_list, &ref_counter);
break;
case kFunctionCid:
} else if (legacy_cid_is (cid, lc.function)) {
rc = decode_function_cluster_ext (&stream, ctx, method_list, &ref_counter);
if (rc < 0) {
rc = 0;
skip_generic_cluster (&stream);
}
break;
case kTypeCid:
} else if (legacy_cid_is (cid, lc.type)) {
rc = decode_type_cluster_ext (&stream, ctx, type_list, &ref_counter);
break;
case kFunctionTypeCid:
} else if (legacy_cid_is (cid, lc.function_type)) {
rc = decode_function_type_cluster_ext (&stream, ctx, type_list, &ref_counter);
if (rc < 0) {
rc = 0;
skip_generic_cluster (&stream);
}
break;
case kTypeParameterCid:
} else if (legacy_cid_is (cid, lc.type_parameter)) {
rc = decode_type_parameter_cluster_ext (&stream, ctx, type_list, &ref_counter);
break;
case kTypeArgumentsCid:
} else if (legacy_cid_is (cid, lc.type_arguments)) {
rc = decode_type_arguments_cluster_ext (&stream, ctx, type_args_list, &ref_counter);
if (rc < 0) {
rc = 0;
skip_generic_cluster (&stream);
}
break;
case kArrayCid:
case kImmutableArrayCid:
} else if (legacy_cid_is (cid, lc.array) || legacy_cid_is (cid, lc.immutable_array)) {
rc = decode_array_cluster_ext (&stream, ctx, array_list, &ref_counter);
if (rc < 0) {
rc = 0;
skip_generic_cluster (&stream);
}
break;
case kLibraryCid_extract:
} else if (legacy_cid_is (cid, lc.library)) {
rc = decode_library_cluster_ext (&stream, ctx, libraries, &ref_counter);
break;
default:
} else {
skip_generic_cluster (&stream);
}
// A cluster we recognise but cannot decode is not fatal: skip its
// bytes and carry on rather than abandoning the whole snapshot.
if (rc < 0 && legacy_cid_recoverable (cid, &lc)) {
rc = 0;
skip_generic_cluster (&stream);
break;
}
if (rc < 0) {
break;
@@ -1643,15 +1718,14 @@ RList *dart_pool_extract_classes(DartCtx *ctx) {
r_list_free (method_list);
r_list_free (field_list);
if (ctx->dump_fields && r_list_length (class_list) > 0) {
ut64 kAlign = ctx->layout && ctx->layout->max_alignment? (ut64)ctx->layout->max_alignment: 16;
ut64 data_image_base = snapshot_base + ((sh.total_len + (kAlign - 1)) & ~ (kAlign - 1));
ut64 data_image_base = dart_snapshot_data_image_base (snapshot_base, sh.total_len);
ut64 data_image_end = ctx->iso_instr? ctx->iso_instr: (data_image_base + (4ULL << 20));
scan_fields_from_data_image (ctx, class_list, data_image_base, data_image_end);
scan_methods_from_data_image (ctx, class_list, data_image_base, data_image_end);
if (ctx->vm_data) {
DartSnapshotHeader vm_sh;
if (dart_snapshot_header_read (ctx, ctx->vm_data, &vm_sh)) {
ut64 vm_data_base = ctx->vm_data + ((vm_sh.total_len + (kAlign - 1)) & ~ (kAlign - 1));
ut64 vm_data_base = dart_snapshot_data_image_base (ctx->vm_data, vm_sh.total_len);
ut64 vm_data_end = ctx->vm_instr? ctx->vm_instr: (vm_data_base + (4ULL << 20));
scan_fields_from_data_image (ctx, class_list, vm_data_base, vm_data_end);
scan_methods_from_data_image (ctx, class_list, vm_data_base, vm_data_end);
@@ -1659,12 +1733,18 @@ RList *dart_pool_extract_classes(DartCtx *ctx) {
}
}
dart_ctx_fini_layout (ctx, layout_owned);
if (modern_classes) {
resolve_method_entry_points (ctx, class_list);
}
return class_list;
}
static void dump_class_json(PJ *pj, const DartClassInfo *ci) {
pj_o (pj);
pj_kn (pj, "ref", ci->ref_id);
if (ci->class_id) {
pj_ki (pj, "class_id", ci->class_id);
}
if (ci->name) {
pj_ks (pj, "name", ci->name);
}
@@ -1677,10 +1757,15 @@ static void dump_class_json(PJ *pj, const DartClassInfo *ci) {
}
pj_end (pj);
}
if (ci->super_class_name || ci->super_class_ref) {
if (ci->super_class_name || ci->super_class_ref || ci->super_type_ref) {
pj_k (pj, "super");
pj_o (pj);
pj_kn (pj, "ref", ci->super_class_ref);
if (ci->super_class_ref) {
pj_kn (pj, "ref", ci->super_class_ref);
}
if (ci->super_type_ref) {
pj_kn (pj, "type_ref", ci->super_type_ref);
}
if (ci->super_class_name) {
pj_ks (pj, "name", ci->super_class_name);
}
@@ -1711,8 +1796,14 @@ static void dump_class_json(PJ *pj, const DartClassInfo *ci) {
pj_k (pj, "layout");
pj_o (pj);
pj_ki (pj, "instance_size", ci->instance_size);
if (ci->next_field_offset) {
pj_ki (pj, "next_field_offset", ci->next_field_offset);
}
pj_ki (pj, "type_params", ci->num_type_parameters);
pj_ki (pj, "type_arg_offset", ci->type_argument_offset);
if (ci->field_bitmap_present) {
pj_kn (pj, "field_bitmap", ci->field_bitmap);
}
pj_end (pj);
pj_k (pj, "flags");
pj_o (pj);
@@ -1968,6 +2059,104 @@ static void append_comment_cmd(RStrBuf *sb, ut64 addr, const char *msg) {
free (b64);
}
#ifdef R2_HAS_BIN_ATTR
static ut64 dart_class_attrbits(const DartClassInfo *ci) {
ut64 a = 0;
if (ci->flags & DART_CLASS_ABSTRACT) {
a |= R_BIN_ATTR_ABSTRACT;
}
if (ci->flags & DART_CLASS_ENUM) {
a |= R_BIN_ATTR_ENUM;
}
if (ci->flags & DART_CLASS_MIXIN) {
a |= R_BIN_ATTR_MIXIN;
}
return a;
}
static ut64 dart_field_attrbits(const DartFieldInfo *fi) {
ut64 a = 0;
if (fi->flags & DART_FIELD_STATIC) {
a |= R_BIN_ATTR_STATIC;
}
if (fi->flags & DART_FIELD_FINAL) {
a |= R_BIN_ATTR_FINAL;
}
if (fi->flags & DART_FIELD_CONST) {
a |= R_BIN_ATTR_CONST;
}
if (fi->flags & DART_FIELD_LATE) {
a |= R_BIN_ATTR_LATE;
}
return a;
}
static ut64 dart_method_attrbits(const DartMethodInfo *mi) {
const char *kind = method_kind_name (mi->kind_tag);
ut64 a = method_is_static (mi)? R_BIN_ATTR_STATIC: 0;
if (strstr (kind, "Getter")) {
a |= R_BIN_ATTR_GETTER;
} else if (strstr (kind, "Setter")) {
a |= R_BIN_ATTR_SETTER;
} else if (!strcmp (kind, "Constructor")) {
a |= R_BIN_ATTR_CONSTRUCTOR;
}
return a;
}
#endif
// the dart_*_ic_args helpers build the ic+ argument string (leading space, may be empty)
// using the attribute names that r_bin_attr_forclass/symbol/field parse back
static char *dart_class_ic_args(const DartClassInfo *ci) {
#ifdef R2_HAS_BIN_ATTR
RStrBuf *sb = r_strbuf_new ("");
if (ci->instance_size > 0) {
r_strbuf_appendf (sb, " size=%u", ci->instance_size);
}
const char *ns = ci->library_name;
if (R_STR_ISNOTEMPTY (ns) && !ns[strspn (ns, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.:/-")]) {
r_strbuf_appendf (sb, " ns=%s", ns);
}
char *attrs = r_bin_attr_tostring (dart_class_attrbits (ci), false);
if (R_STR_ISNOTEMPTY (attrs)) {
r_strbuf_appendf (sb, " %s", attrs);
}
free (attrs);
return r_strbuf_drain (sb);
#else
(void)ci;
return strdup ("");
#endif
}
static char *dart_field_ic_args(const DartFieldInfo *fi) {
#ifdef R2_HAS_BIN_ATTR
char *attrs = r_bin_attr_tostring (dart_field_attrbits (fi), false);
char *res = r_str_newf (" %s%s%s", field_c_type (fi), R_STR_ISNOTEMPTY (attrs)? " ": "", r_str_get (attrs));
free (attrs);
return res;
#else
return r_str_newf (" %s", field_c_type (fi));
#endif
}
static char *dart_method_ic_args(const DartMethodInfo *mi) {
#ifdef R2_HAS_BIN_ATTR
char *attrs = r_bin_attr_tostring (dart_method_attrbits (mi), false);
if (R_STR_ISEMPTY (attrs)) {
free (attrs);
return strdup ("");
}
char *res = r_str_newf (" %s", attrs);
free (attrs);
return res;
#else
(void)mi;
return strdup ("");
#endif
}
static void append_ic_inheritance_cmd(RStrBuf *sb, const char *class_name, const char *base_name) {
if (!sb || R_STR_ISEMPTY (class_name) || R_STR_ISEMPTY (base_name)) {
return;
@@ -1981,15 +2170,19 @@ static void append_ic_field_cmd(RStrBuf *sb, const char *class_name, const DartF
return;
}
char *field_name = member_export_name (fi->name, "field");
r_strbuf_appendf (sb, "ic+%s..%s %s @ 0x%x\n", class_name, field_name, field_c_type (fi), fi->offset);
char *args = dart_field_ic_args (fi);
r_strbuf_appendf (sb, "'@0x%x'ic+%s..%s%s\n", fi->offset, class_name, field_name, args);
free (args);
free (field_name);
}
static void append_ic_method_cmd(RStrBuf *sb, const char *class_name, const char *method_name, ut64 addr) {
static void append_ic_method_cmd(RStrBuf *sb, const char *class_name, const char *method_name, ut64 addr, const DartMethodInfo *mi) {
if (!sb || R_STR_ISEMPTY (class_name) || R_STR_ISEMPTY (method_name)) {
return;
}
r_strbuf_appendf (sb, "ic+%s.%s @ 0x%" PFMT64x "\n", class_name, method_name, addr);
char *args = dart_method_ic_args (mi);
r_strbuf_appendf (sb, "ic+%s.%s%s @ 0x%" PFMT64x "\n", class_name, method_name, args, addr);
free (args);
}
static void dump_class_r2_metadata(RStrBuf *sb, const DartClassInfo *ci, bool quiet) {
@@ -2001,7 +2194,9 @@ static void dump_class_r2_metadata(RStrBuf *sb, const DartClassInfo *ci, bool qu
if (!quiet) {
r_strbuf_appendf (sb, "# r2 class %s original=%s\n", class_ref, ci->name);
}
r_strbuf_appendf (sb, "ic+%s @ 0\n", class_ref);
char *cargs = dart_class_ic_args (ci);
r_strbuf_appendf (sb, "ic+%s%s @ 0\n", class_ref, cargs);
free (cargs);
r_strbuf_appendf (sb, "ac %s\n", class_ref);
#if 0
// TOO SLOW
@@ -2058,7 +2253,7 @@ static void dump_class_r2_metadata(RStrBuf *sb, const DartClassInfo *ci, bool qu
const char *method_ref = R_STR_ISNOTEMPTY (method_name)? method_name: "method";
char *cc = method_dyncc (ci, mi);
ut64 addr = method_export_addr (mi);
append_ic_method_cmd (sb, class_ref, method_ref, addr);
append_ic_method_cmd (sb, class_ref, method_ref, addr, mi);
if (!quiet) {
r_strbuf_appendf (sb, "# method %s.%s kind=%s mode=%s cc=%s", class_ref, method_ref, method_kind_name (mi->kind_tag), method_mode_name (mi), cc);
if (R_STR_ISNOTEMPTY (mi->signature)) {
@@ -2248,6 +2443,7 @@ char *dart_pool_dump_classes(DartCtx *ctx, int fmt) {
dump_class_text (sb, ci, fmt, type_view, quiet);
}
if (fmt == 'r') {
r_strbuf_append (sb, "e bin.lang=dart\n");
if (!quiet) {
r_strbuf_append (sb, "# Dart class symbols and analysis metadata\n");
}
@@ -2296,8 +2492,19 @@ static RBinClass *core_bin_get_or_add_class(RCore *core, const char *class_name,
}
klass->origin = R_BIN_CLASS_ORIGIN_USER;
klass->addr = 0;
klass->instance_size = ci? ci->instance_size: 0;
klass->index = r_list_length (klasses);
#ifdef R2_HAS_BIN_ATTR
if (ci) {
klass->attr.size = ci->instance_size;
klass->attr.flags = dart_class_attrbits (ci);
klass->attr.lang = R_BIN_LANG_DART;
if (R_STR_ISNOTEMPTY (ci->library_name)) {
klass->attr.ns = strdup (ci->library_name);
}
}
#else
klass->instance_size = ci? ci->instance_size: 0;
#endif
r_list_append (klasses, klass);
return klass;
}
@@ -2361,9 +2568,16 @@ static void core_apply_bin_field(RBinClass *klass, const DartFieldInfo *fi, int
field->vaddr = fi->offset;
field->paddr = fi->offset;
field->value = fi->offset;
#ifdef R2_HAS_BIN_ATTR
field->attr.size = 8;
field->attr.offset = fi->offset;
field->attr.kind = R_BIN_FIELD_KIND_FIELD;
field->attr.flags = dart_field_attrbits (fi);
#else
field->size = 8;
field->offset = fi->offset;
field->kind = R_BIN_FIELD_KIND_FIELD;
#endif
field->type = r_bin_name_new (R_STR_ISNOTEMPTY (fi->type_name)? fi->type_name: "dynamic");
(*count)++;
}
@@ -2389,7 +2603,12 @@ static void core_apply_bin_method(RBinClass *klass, const DartMethodInfo *mi, in
sym->type = "method";
sym->paddr = addr;
sym->vaddr = addr;
#ifdef R2_HAS_BIN_ATTR
sym->attr.flags = dart_method_attrbits (mi);
sym->attr.lang = R_BIN_LANG_DART;
#else
sym->size = 0;
#endif
(*count)++;
}
free (method_name);
@@ -2484,14 +2703,26 @@ static void core_apply_type(RCore *core, const DartClassInfo *ci, const char *cl
.name = field_name,
.type = strdup (field_c_type (fi)),
.offset = fi->offset,
#if R2_ABIVERSION >= 125
.bitsize = 64,
#else
.size = 64,
};
#endif
};
#if R2_ABIVERSION >= 125
RAnalStructMember *slot = RVecAnalTypeMember_emplace_back (&type->struct_data.members);
#else
RAnalStructMember *slot = RVecAnalStructMember_emplace_back (&type->struct_data.members);
#endif
if (slot) {
*slot = member;
(*field_count)++;
} else {
#if R2_ABIVERSION >= 125
anal_type_member_fini (&member);
#else
anal_struct_member_fini (&member);
#endif
}
}
}
@@ -2547,7 +2778,9 @@ static void core_apply_ic_field(RCore *core, const char *class_name, const DartF
return;
}
char *field_name = member_export_name (fi->name, "field");
core_apply_ic_command (core, r_str_newf ("ic+%s..%s %s @ 0x%x", class_name, field_name, field_c_type (fi), fi->offset));
char *args = dart_field_ic_args (fi);
core_apply_ic_command (core, r_str_newf ("'@0x%x'ic+%s..%s%s", fi->offset, class_name, field_name, args));
free (args);
free (field_name);
}
@@ -2557,7 +2790,9 @@ static void core_apply_ic_method(RCore *core, const char *class_name, const Dart
}
char *method_name = member_export_name (mi->name, "method");
ut64 addr = method_export_addr (mi);
core_apply_ic_command (core, r_str_newf ("ic+%s.%s @ 0x%" PFMT64x, class_name, method_name, addr));
char *args = dart_method_ic_args (mi);
core_apply_ic_command (core, r_str_newf ("ic+%s.%s%s @ 0x%" PFMT64x, class_name, method_name, args, addr));
free (args);
free (method_name);
}
@@ -2565,7 +2800,9 @@ static void core_apply_ic_class(RCore *core, const DartClassInfo *ci, const char
if (!core || !ci || R_STR_ISEMPTY (class_name)) {
return;
}
core_apply_ic_command (core, r_str_newf ("ic+%s @ 0", class_name));
char *cargs = dart_class_ic_args (ci);
core_apply_ic_command (core, r_str_newf ("ic+%s%s @ 0", class_name, cargs));
free (cargs);
if (R_STR_ISNOTEMPTY (ci->super_class_name)) {
char *super_name = class_export_name (ci->super_class_name);
core_apply_ic_inheritance (core, class_name, super_name);
@@ -2612,6 +2849,7 @@ int dart_pool_apply_classes_to_core(DartCtx *ctx) {
int method_count = 0;
int type_field_count = 0;
int anal_method_count = 0;
r_core_cmd0 (ctx->core, "e bin.lang=dart");
RListIter *it;
DartClassInfo *ci;
r_list_foreach (classes, it, ci) {
-194
View File
@@ -16,22 +16,6 @@ void free_dart_string(void *p) {
}
}
static void free_dart_class(void *p) {
DartClass *dc = (DartClass *)p;
if (dc) {
free (dc->name);
free (dc);
}
}
static void free_dart_func(void *p) {
DartPoolFunction *df = (DartPoolFunction *)p;
if (df) {
free (df->name);
free (df);
}
}
int decode_string_cluster(ClusterStream *s, DartCtx *ctx, ut64 *ref_counter, bool is_canonical) {
(void)is_canonical;
ut64 count = 0;
@@ -98,88 +82,6 @@ int decode_string_cluster(ClusterStream *s, DartCtx *ctx, ut64 *ref_counter, boo
return 0;
}
static int decode_class_cluster(ClusterStream *s, DartCtx *ctx, ut64 *ref_counter, bool is_canonical) {
(void)is_canonical;
ut64 count = 0;
if (!cs_read_unsigned (s, &count)) {
return -1;
}
if (count == 0 || count > 50000) {
return 0;
}
if (ctx->verbose > 1) {
fprintf (stderr, "[r2flutter] Class cluster: count=%" PRIu64 "\n", count);
}
for (ut64 i = 0; i < count; i++) {
DartClass *dc = R_NEW0 (DartClass);
dc->ref_id = (*ref_counter)++;
uint32_t instance_size = 0;
cs_read_u32 (s, &instance_size);
dc->instance_size = (int)instance_size;
ut64 name_ref = 0;
cs_read_ref_id (s, &name_ref);
dc->name_ref = name_ref;
ut64 library_ref = 0;
cs_read_ref_id (s, &library_ref);
dc->library_ref = library_ref;
ut64 skip_count = 6;
for (ut64 j = 0; j < skip_count; j++) {
ut64 dummy = 0;
cs_read_ref_id (s, &dummy);
}
if (ctx->classes) {
r_list_append (ctx->classes, dc);
}
if (ctx->refs && dc->ref_id < ctx->refs_count) {
ctx->refs[dc->ref_id] = dc;
}
}
return 0;
}
static int decode_function_cluster(ClusterStream *s, DartCtx *ctx, ut64 *ref_counter, ut64 iso_instr, bool is_canonical) {
(void)is_canonical;
ut64 count = 0;
if (!cs_read_unsigned (s, &count)) {
return -1;
}
if (count == 0 || count > 100000) {
return 0;
}
if (ctx->verbose > 1) {
fprintf (stderr, "[r2flutter] Function cluster: count=%" PRIu64 "\n", count);
}
for (ut64 i = 0; i < count; i++) {
DartPoolFunction *df = R_NEW0 (DartPoolFunction);
df->ref_id = (*ref_counter)++;
ut64 name_ref = 0;
cs_read_ref_id (s, &name_ref);
df->name_ref = name_ref;
ut64 owner_ref = 0;
cs_read_ref_id (s, &owner_ref);
df->owner_ref = owner_ref;
ut64 code_idx = 0;
cs_read_unsigned (s, &code_idx);
if (code_idx > 0 && iso_instr > 0) {
df->entry_point = iso_instr + (code_idx - 1) * 4;
}
ut64 skip_refs = 4;
for (ut64 j = 0; j < skip_refs; j++) {
ut64 dummy = 0;
cs_read_ref_id (s, &dummy);
}
uint32_t kind_tag = 0;
cs_read_u32 (s, &kind_tag);
if (ctx->functions) {
r_list_append (ctx->functions, df);
}
if (ctx->refs && df->ref_id < ctx->refs_count) {
ctx->refs[df->ref_id] = df;
}
}
return 0;
}
void skip_generic_cluster(ClusterStream *stream) {
ut64 count = 0;
if (cs_read_unsigned (stream, &count)) {
@@ -198,99 +100,3 @@ void skip_generic_cluster(ClusterStream *stream) {
}
}
}
int deserialize_clusters(DartCtx *ctx, ut64 cluster_start, ut64 cluster_end, ut64 num_clusters, ut64 iso_instr) {
if (!ctx || !ctx->core || cluster_start >= cluster_end) {
return -1;
}
ctx->strings = r_list_newf (free_dart_string);
ctx->classes = r_list_newf (free_dart_class);
ctx->functions = r_list_newf (free_dart_func);
ut64 total_refs = ctx->num_base_objects + ctx->num_objects + 16;
ctx->refs_count = total_refs;
ctx->refs = (void **)calloc (total_refs, sizeof (void *));
ClusterStream stream = {
.ctx = ctx,
.cursor = cluster_start,
.end = cluster_end
};
ut64 ref_counter = ctx->num_base_objects + 1;
for (ut64 ci = 0; ci < num_clusters && stream.cursor < stream.end; ci++) {
uint32_t tags = 0;
if (!cs_read_u32 (&stream, &tags)) {
if (ctx->verbose > 0) {
fprintf (stderr, "[r2flutter] Failed to read cluster tag at %" PRIu64 "\n", ci);
}
break;
}
uint32_t cid = (tags >> 12) & 0xFFFFF;
bool is_canonical = ((tags >> 1) & 1) != 0;
if (ctx->verbose > 1) {
fprintf (stderr, "[r2flutter] Cluster %" PRIu64 ": cid=%u canonical=%d cursor=0x%" PFMT64x "\n", ci, cid, is_canonical, stream.cursor);
}
int rc = 0;
switch (cid) {
case kOneByteStringCid:
case kTwoByteStringCid:
case kStringCid:
rc = decode_string_cluster (&stream, ctx, &ref_counter, is_canonical);
break;
case kClassCid:
rc = decode_class_cluster (&stream, ctx, &ref_counter, is_canonical);
break;
case kFunctionCid:
rc = decode_function_cluster (&stream, ctx, &ref_counter, iso_instr, is_canonical);
break;
default:
skip_generic_cluster (&stream);
break;
}
if (rc < 0) {
if (ctx->verbose > 0) {
fprintf (stderr, "[r2flutter] Error decoding cluster cid=%u\n", cid);
}
}
}
if (ctx->verbose > 0) {
fprintf (stderr, "[r2flutter] Decoded: strings=%d classes=%d functions=%d\n", ctx->strings? r_list_length (ctx->strings): 0, ctx->classes? r_list_length (ctx->classes): 0, ctx->functions? r_list_length (ctx->functions): 0);
}
return 0;
}
void resolve_names(DartCtx *ctx) {
if (!ctx || !ctx->refs) {
return;
}
if (ctx->classes) {
RListIter *it;
DartClass *dc;
r_list_foreach (ctx->classes, it, dc) {
if (!dc || dc->name) {
continue;
}
if (dc->name_ref > 0 && dc->name_ref < ctx->refs_count) {
DartString *ds = (DartString *)ctx->refs[dc->name_ref];
if (ds && ds->value) {
dc->name = strdup (ds->value);
dart_obf_apply (ctx, &dc->name);
}
}
}
}
if (ctx->functions) {
RListIter *it;
DartPoolFunction *df;
r_list_foreach (ctx->functions, it, df) {
if (!df || df->name) {
continue;
}
if (df->name_ref > 0 && df->name_ref < ctx->refs_count) {
DartString *ds = (DartString *)ctx->refs[df->name_ref];
if (ds && ds->value) {
df->name = strdup (ds->value);
dart_obf_apply (ctx, &df->name);
}
}
}
}
}
+6 -19
View File
@@ -21,21 +21,6 @@ void init_function_layout(DartCtx *ctx, DartFunctionLayout *fl) {
}
}
static ut32 extract_cid_from_header(DartCtx *ctx, ut64 header) {
if (!ctx || !ctx->layout) {
return 0;
}
switch (ctx->layout->tag_style) {
case DART_TAG_STYLE_OBJECT_HEADER:
return (ut32) ((header >> 12) & 0xFFFFF);
case DART_TAG_STYLE_CID_SHIFT1:
return (ut32) (header >> 1);
case DART_TAG_STYLE_CID_INT32:
default:
return (ut32) ((header >> 12) & 0xFFFFF);
}
}
static bool read_object_pointer(DartCtx *ctx, const ut8 *buf, ut32 off, bool use_compressed, ut64 data_base, ut64 data_end, bool restrict_range, ut64 *out_addr) {
(void)ctx;
if (use_compressed) {
@@ -79,7 +64,8 @@ bool read_data_image_field(DartCtx *ctx, ut64 pos, ut64 data_start, ut64 data_en
return false;
}
ut64 header = r_read_le64 (hdr);
if ((int) ((header >> 12) & 0xFFFFF) != kFieldCid) {
const int cid_field = dart_cid_get (ctx->layout, DART_CID_FIELD);
if (cid_field < 0 || (int)dart_cid_from_object_header (header) != cid_field) {
return false;
}
bool use_compressed = ctx->compressed_word_size == 4;
@@ -172,9 +158,10 @@ bool read_data_image_method(DartCtx *ctx, ut64 pos, ut64 data_start, ut64 data_e
if (!read_mem (ctx, pos, buf, sizeof (buf))) {
return false;
}
const int resolved_function_cid = dart_cid_get (ctx->layout, DART_CID_FUNCTION);
const int cid_function = resolved_function_cid >= 0? resolved_function_cid: kFunctionCid;
if ((int)extract_cid_from_header (ctx, r_read_le64 (buf)) != cid_function) {
// No Function id for this layout means we cannot tell a Function object from
// anything else; report nothing rather than match against a guessed id.
const int cid_function = dart_cid_get (ctx->layout, DART_CID_FUNCTION);
if (cid_function < 0 || (int)dart_cid_from_object_header (r_read_le64 (buf)) != cid_function) {
return false;
}
ut64 entry = r_read_le64 (buf + fl->entry_off);
+190 -32
View File
@@ -3,6 +3,21 @@
#include "dart_pool_parse_priv.h"
static bool looks_like_data_snapshot(DartCtx *ctx, ut64 base, ut64 *out_total_len) {
DartSnapshotHeader outer;
if (!dart_snapshot_fingerprint_read (ctx, base, &outer)) {
return false;
}
if (!outer.kind || outer.kind > 4) {
return false;
}
const bool known_layout = dart_version_from_hash (outer.hash) != NULL ||
R_STR_ISNOTEMPTY (ctx? ctx->dart_version_override: NULL);
if (!known_layout) {
if (out_total_len) {
*out_total_len = outer.total_len;
}
return true;
}
DartSnapshotHeader hdr;
if (!dart_snapshot_header_read (ctx, base, &hdr)) {
return false;
@@ -22,6 +37,16 @@ static bool looks_like_data_snapshot(DartCtx *ctx, ut64 base, ut64 *out_total_le
return true;
}
static bool dart_snapshot_is_single(DartCtx *ctx, ut64 base) {
DartSnapshotHeader outer;
if (!base || !dart_snapshot_fingerprint_read (ctx, base, &outer)) {
return false;
}
const char *version = dart_version_from_hash (outer.hash);
const DartVerLayout *layout = version? dart_profile_from_version (version): NULL;
return layout && layout->single_snapshot;
}
static void pick_vm_iso_by_size(const ut64 *addrs, const ut64 *lens, int count, ut64 *vm_out, ut64 *iso_out) {
if (count < 1) {
return;
@@ -48,6 +73,18 @@ static void pick_vm_iso_by_size(const ut64 *addrs, const ut64 *lens, int count,
}
}
static ut64 rebase_bin_addr(DartCtx *ctx, ut64 addr) {
if (!ctx || !ctx->core || !ctx->core->bin || !ctx->core->io || !addr) {
return addr;
}
ut64 baddr = r_bin_get_baddr (ctx->core->bin);
if (!baddr || addr >= baddr || baddr > UT64_MAX - addr) {
return addr;
}
ut64 rebased = baddr + addr;
return r_io_v2p (ctx->core->io, rebased) != UT64_MAX? rebased: addr;
}
static int collect_snapshot_magics_in_range(DartCtx *ctx, ut64 start, ut64 size, ut64 *found_addrs, int found_cap, int found_cnt) {
if (!ctx || !found_addrs || found_cap <= 0 || found_cnt >= found_cap || size < 4) {
return found_cnt;
@@ -61,8 +98,22 @@ static int collect_snapshot_magics_in_range(DartCtx *ctx, ut64 start, ut64 size,
}
for (int j = 0; j <= toread - 4; j += 4) {
uint32_t val = r_read_le32 (buf + j);
if (val == DART_SNAPSHOT_MAGIC) {
found_addrs[found_cnt++] = addr + j;
if (val != DART_SNAPSHOT_MAGIC) {
continue;
}
const ut64 hit = addr + j;
// The read window overlaps the previous chunk by 16 bytes so a magic
// straddling a boundary is not missed; dedup so those overlap bytes
// (and Mach-O segment/section double-coverage) don't count twice.
bool dup = false;
for (int d = 0; d < found_cnt; d++) {
if (found_addrs[d] == hit) {
dup = true;
break;
}
}
if (!dup) {
found_addrs[found_cnt++] = hit;
if (found_cnt >= found_cap) {
return found_cnt;
}
@@ -72,6 +123,89 @@ static int collect_snapshot_magics_in_range(DartCtx *ctx, ut64 start, ut64 size,
return found_cnt;
}
// A Dart Image starts with a kObjectStartAlignment (64)-byte header whose first
// host word is ImageSize (header + payload) and second is
// InstructionsSectionOffset. Bare-instructions AOT lays the two instruction
// images consecutively at the start of the first executable section:
// [VmInstructions][IsolateInstructions], each padded up to this alignment.
#define DART_IMAGE_ALIGN 64
#define DART_IMAGE_HEADER_MIN 64
static ut64 dart_align_up(ut64 v, ut64 a) {
return (v + (a - 1)) & ~ (a - 1);
}
// Read an Image header at addr and return its ImageSize in *size_out, true only
// if it is a plausible instructions image: it fits before the first data image
// and its InstructionsSection offset (word 1) is a small nonzero aligned value
// inside the image.
static bool dart_image_at(DartCtx *ctx, ut64 addr, ut64 data_lo, ut64 *size_out) {
ut8 hdr[16];
if (!read_mem (ctx, addr, hdr, sizeof (hdr))) {
return false;
}
ut64 size = r_read_le64 (hdr), isoff = r_read_le64 (hdr + 8);
*size_out = size;
return size >= DART_IMAGE_HEADER_MIN && addr + size <= data_lo &&
isoff >= 8 && (isoff & 7) == 0 && isoff < size && isoff < 0x10000;
}
// When the four kDart* symbols are stripped, the magic scan can only find the
// data snapshots (instruction images carry no magic). Recover vm_instr/iso_instr
// structurally: vm_instr is the start of the first executable section, and the
// isolate instructions image follows the VM one and ends where the first data
// image begins. Every step is cross-checked; if anything is off we leave the
// addresses at 0, so a wrong/unknown layout degrades to "no IT names" rather
// than junk addresses. No-op when symbols already resolved the instructions.
static void locate_instr_images_structural(DartCtx *ctx) {
if (!ctx || !ctx->core || !ctx->core->bin || ctx->vm_instr || ctx->iso_instr) {
return;
}
ut64 data_lo = ctx->vm_data && ctx->iso_data
? R_MIN (ctx->vm_data, ctx->iso_data)
: (ctx->vm_data? ctx->vm_data: ctx->iso_data);
if (!data_lo) {
return;
}
RVecRBinSection *sections = r_bin_get_sections_vec (ctx->core->bin);
if (!sections) {
return;
}
ut64 vm_instr = 0, sz1 = 0, sz2 = 0;
RBinSection *sec;
R_VEC_FOREACH (sections, sec) {
if (sec && !sec->is_segment && sec->vaddr && sec->vsize && (sec->perm & R_PERM_X)) {
vm_instr = rebase_bin_addr (ctx, sec->vaddr);
break;
}
}
if (!vm_instr || vm_instr >= data_lo || !dart_image_at (ctx, vm_instr, data_lo, &sz1)) {
return;
}
if (dart_snapshot_is_single (ctx, ctx->iso_data? ctx->iso_data: ctx->vm_data)) {
ctx->iso_instr = vm_instr;
if (ctx->verbose > 0) {
fprintf (stderr, "[r2flutter] located single instruction image structurally: snapshot_instr=0x%" PFMT64x "\n", (ut64)vm_instr);
}
return;
}
ut64 iso_instr = dart_align_up (vm_instr + sz1, DART_IMAGE_ALIGN);
if (iso_instr <= vm_instr || iso_instr >= data_lo || !dart_image_at (ctx, iso_instr, data_lo, &sz2)) {
return;
}
// The isolate instructions image is the last text image: it must end right
// where the first data image begins (within a page of alignment slack).
ut64 iso_end = dart_align_up (iso_instr + sz2, DART_IMAGE_ALIGN);
if (iso_end > data_lo || data_lo - iso_end > 0x4000) {
return;
}
ctx->vm_instr = vm_instr;
ctx->iso_instr = iso_instr;
if (ctx->verbose > 0) {
fprintf (stderr, "[r2flutter] located instruction images structurally: vm_instr=0x%" PFMT64x " iso_instr=0x%" PFMT64x "\n", (ut64)vm_instr, (ut64)iso_instr);
}
}
int find_snapshots(DartCtx *ctx) {
if (!ctx || !ctx->core) {
return -1;
@@ -92,6 +226,8 @@ int find_snapshots(DartCtx *ctx) {
"_kDartIsolateSnapshotInstructions",
"DartIsolateSnapshotInstructions",
};
const char *single_data_names[2] = { "_kDartSnapshotData", "DartSnapshotData" };
const char *single_text_names[2] = { "_kDartSnapshotText", "DartSnapshotText" };
ut64 *outs[4] = { &ctx->vm_data, &ctx->vm_instr, &ctx->iso_data, &ctx->iso_instr };
if (core->bin) {
RVecRBinSymbol *v = r_bin_get_symbols_vec (core->bin);
@@ -106,9 +242,20 @@ int find_snapshots(DartCtx *ctx) {
continue;
}
for (int k = 0; k < 8; k++) {
if (!strcmp (nm, names[k])) {
// Only assign when this symbol carries an address: Mach-O
// often lists a name twice and a later vaddr==0 duplicate
// must not erase the good value we already resolved.
if (!strcmp (nm, names[k]) && sym->vaddr) {
int idx = k / 2;
*outs[idx] = sym->vaddr? sym->vaddr: 0;
*outs[idx] = rebase_bin_addr (ctx, sym->vaddr);
}
}
for (int k = 0; k < 2; k++) {
if (!strcmp (nm, single_data_names[k]) && sym->vaddr) {
ctx->iso_data = rebase_bin_addr (ctx, sym->vaddr);
}
if (!strcmp (nm, single_text_names[k]) && sym->vaddr) {
ctx->iso_instr = rebase_bin_addr (ctx, sym->vaddr);
}
}
}
@@ -117,6 +264,9 @@ int find_snapshots(DartCtx *ctx) {
if (ctx->vm_data && ctx->vm_instr && ctx->iso_data && ctx->iso_instr) {
return 0;
}
if (ctx->iso_data && ctx->iso_instr && dart_snapshot_is_single (ctx, ctx->iso_data)) {
return 0;
}
RVecRBinSection *sections = r_bin_get_sections_vec (core->bin);
ut64 found_addrs[32];
@@ -127,7 +277,12 @@ int find_snapshots(DartCtx *ctx) {
if (!sec || !sec->vaddr || !sec->vsize) {
continue;
}
ut64 vaddr = sec->vaddr;
// r_bin_get_sections_vec returns both segments and their child
// sections on Mach-O; scanning both covers the same bytes twice.
if (sec->is_segment) {
continue;
}
ut64 vaddr = rebase_bin_addr (ctx, sec->vaddr);
ut64 size = sec->vsize;
if (ctx->verbose > 0) {
fprintf (stderr, "[r2flutter] scanning section '%s' vaddr=0x%" PFMT64x " size=0x%" PFMT64x "\n", sec->name? sec->name: "(null)", (ut64)vaddr, (ut64)size);
@@ -148,40 +303,43 @@ int find_snapshots(DartCtx *ctx) {
}
}
if (found_cnt >= 1) {
// Only data snapshots carry the snapshot magic; instruction images do
// not. The old code classified every non-data magic hit as an
// "instructions" snapshot and fed its bogus total_len into the vm/iso
// picker, so a stray f5f5dcdc byte run could poison vm_instr/iso_instr.
// Derive only the data snapshots here; instruction images come from
// symbols (and, once implemented, structural section location).
ut64 data_addrs[4];
ut64 data_lens[4];
int data_cnt = 0;
ut64 instr_addrs[4];
ut64 instr_lens[4];
int instr_cnt = 0;
for (int i = 0; i < found_cnt; i++) {
ut8 hdr2[16];
if (!read_mem (ctx, found_addrs[i] + 4, hdr2, sizeof (hdr2))) {
continue;
}
ut64 total_len = r_read_le64 (hdr2) + 4;
ut64 classified_len = 0;
bool is_data = looks_like_data_snapshot (ctx, found_addrs[i], &classified_len);
if (is_data) {
if (data_cnt < 4) {
data_addrs[data_cnt] = found_addrs[i];
data_lens[data_cnt] = total_len;
data_cnt++;
}
} else {
if (instr_cnt < 4) {
instr_addrs[instr_cnt] = found_addrs[i];
instr_lens[instr_cnt] = total_len;
instr_cnt++;
}
for (int i = 0; i < found_cnt && data_cnt < 4; i++) {
ut64 total_len = 0;
if (looks_like_data_snapshot (ctx, found_addrs[i], &total_len)) {
data_addrs[data_cnt] = found_addrs[i];
data_lens[data_cnt] = total_len;
data_cnt++;
}
}
if (data_cnt >= 1) {
pick_vm_iso_by_size (data_addrs, data_lens, data_cnt, &ctx->vm_data, &ctx->iso_data);
}
if (instr_cnt >= 1) {
pick_vm_iso_by_size (instr_addrs, instr_lens, instr_cnt, &ctx->vm_instr, &ctx->iso_instr);
// Preserve any addresses already resolved from symbols; only fill
// the ones still unset so scan guesses cannot overwrite good values.
ut64 scan_vm_data = 0;
ut64 scan_iso_data = 0;
if (data_cnt == 1 && dart_snapshot_is_single (ctx, data_addrs[0])) {
scan_iso_data = data_addrs[0];
} else {
pick_vm_iso_by_size (data_addrs, data_lens, data_cnt, &scan_vm_data, &scan_iso_data);
}
if (!ctx->vm_data) {
ctx->vm_data = scan_vm_data;
}
if (!ctx->iso_data) {
ctx->iso_data = scan_iso_data;
}
}
// Symbols are absent (we reached the scan): try to recover the
// instruction images from the section layout so IT name recovery works.
locate_instr_images_structural (ctx);
if (ctx->vm_data || ctx->iso_data || ctx->vm_instr || ctx->iso_instr) {
return 0;
}
+21 -17
View File
@@ -23,6 +23,16 @@ static char *read_it_name(DartCtx *ctx, ut64 addr) {
return NULL;
}
static char *resolve_it_entry_signature(DartCtx *ctx, const DartInstructionTableEntry *entry) {
if (ctx && ctx->signature_by_code_index && entry->has_code && entry->code_index < ctx->signature_by_code_index_count) {
const char *sig = ctx->signature_by_code_index[entry->code_index];
if (R_STR_ISNOTEMPTY (sig)) {
return strdup (sig);
}
}
return NULL;
}
static char *resolve_it_entry_name(DartCtx *ctx, HtUP *sym_by_addr, ut64 data_image_base, const DartInstructionTableEntry *entry) {
char *out = NULL;
if (ctx && ctx->name_by_code_index && entry->has_code && entry->code_index < ctx->name_by_code_index_count) {
@@ -31,7 +41,10 @@ static char *resolve_it_entry_name(DartCtx *ctx, HtUP *sym_by_addr, ut64 data_im
out = strdup (ns);
}
}
if (sym_by_addr) {
// A stripped Mach-O/ELF often contributes synthetic func.<address>
// symbols for every function start. Keep a Dart-derived name when the
// cluster graph supplied one; RBin is only a fallback for unmapped slots.
if (!out && sym_by_addr) {
RBinSymbol *bs = (RBinSymbol *)ht_up_find (sym_by_addr, entry->address, NULL);
if (bs && bs->name) {
const char *resolved = r_bin_name_tostring (bs->name);
@@ -147,22 +160,9 @@ static bool read_it_data_header_from_string_object(DartCtx *ctx, ut64 obj_addr,
return false;
}
ut64 tags = r_read_le64 (objhdr);
ut32 cid = 0;
switch (ctx->layout->tag_style) {
case DART_TAG_STYLE_OBJECT_HEADER:
cid = (ut32) ((tags >> 12) & 0xFFFFF);
break;
case DART_TAG_STYLE_CID_SHIFT1:
cid = (ut32) (tags >> 1);
break;
case DART_TAG_STYLE_CID_INT32:
default:
cid = (ut32) ((tags >> 12) & 0xFFFFF);
break;
}
const int resolved_string_cid = dart_cid_get (ctx->layout, DART_CID_ONE_BYTE_STRING);
const int string_cid = resolved_string_cid >= 0? resolved_string_cid: kOneByteStringCid;
if ((int)cid != string_cid) {
const ut32 cid = dart_cid_from_object_header (tags);
const int string_cid = dart_cid_get (ctx->layout, DART_CID_ONE_BYTE_STRING);
if (string_cid < 0 || (int)cid != string_cid) {
return false;
}
ut64 len_smi = r_read_le64 (objhdr + 8);
@@ -402,8 +402,10 @@ int dart_it_emit_fixed(const DartItEmitRequest *req) {
.has_code = has_code,
};
entry.name = resolve_it_entry_name (ctx, req->sym_by_addr, req->data_image_base, &entry);
entry.signature = resolve_it_entry_signature (ctx, &entry);
emit_it_entry_record (&entry, req);
free (entry.name);
free (entry.signature);
emitted++;
}
return 0;
@@ -462,8 +464,10 @@ int dart_it_emit_varint(const DartItEmitRequest *req) {
.has_code = has_code,
};
entry.name = resolve_it_entry_name (ctx, req->sym_by_addr, req->data_image_base, &entry);
entry.signature = resolve_it_entry_signature (ctx, &entry);
emit_it_entry_record (&entry, req);
free (entry.name);
free (entry.signature);
emitted++;
}
return 0;
+2695 -600
View File
File diff suppressed because it is too large. Load diff
+86 -51
View File
@@ -13,55 +13,13 @@ typedef struct {
static void init_layout_hints(LayoutHints *lh) {
memset (lh, 0, sizeof (*lh));
lh->ep_offs[lh->ep_offs_n++] = 0x10;
lh->ep_offs[lh->ep_offs_n++] = 0x18;
lh->ep_offs[lh->ep_offs_n++] = 0x20;
lh->owner_offs[lh->owner_offs_n++] = 0x10;
lh->owner_offs[lh->owner_offs_n++] = 0x18;
lh->owner_offs[lh->owner_offs_n++] = 0x20;
lh->name_offs[lh->name_offs_n++] = 0x10;
lh->name_offs[lh->name_offs_n++] = 0x18;
lh->name_offs[lh->name_offs_n++] = 0x20;
}
static void read_json_u64_array(const RJson *parent, const char *key, ut64 *out, int *out_n, int max) {
const RJson *arr = r_json_get (parent, key);
if (arr && arr->type == R_JSON_ARRAY) {
*out_n = 0;
size_t n = arr->children.count;
for (size_t i = 0; i < n && *out_n < max; i++) {
const RJson *el = r_json_item (arr, i);
if (el && el->type == R_JSON_INTEGER) {
out[(*out_n)++] = (ut64)el->num.u_value;
}
}
}
}
static void enrich_layout_hints_from_json(LayoutHints *lh, const char *hash) {
if (!lh || !hash) {
return;
}
char *s = r_file_slurp ("r2flutter/offsets.json", NULL);
if (!s) {
s = r_file_slurp ("offsets.json", NULL);
}
if (!s) {
return;
}
RJson *j = r_json_parse (s);
if (!j) {
free (s);
return;
}
const RJson *item = r_json_get (r_json_get (j, "hashes"), hash);
if (item) {
read_json_u64_array (item, "code_entry_point_offsets", lh->ep_offs, &lh->ep_offs_n, 8);
read_json_u64_array (item, "code_owner_offsets", lh->owner_offs, &lh->owner_offs_n, 8);
read_json_u64_array (item, "function_name_offsets", lh->name_offs, &lh->name_offs_n, 8);
}
r_json_free (j);
free (s);
lh->ep_offs[lh->ep_offs_n++] = 8;
lh->ep_offs[lh->ep_offs_n++] = 24;
lh->ep_offs[lh->ep_offs_n++] = 16;
lh->ep_offs[lh->ep_offs_n++] = 32;
lh->owner_offs[lh->owner_offs_n++] = 56;
lh->name_offs[lh->name_offs_n++] = 24;
lh->name_offs[lh->name_offs_n++] = 8;
}
static bool read_heap_ptr(DartCtx *ctx, ut64 addr, ut64 data_image_base, ut64 *out_abs) {
@@ -102,10 +60,88 @@ static bool read_heap_ptr(DartCtx *ctx, ut64 addr, ut64 data_image_base, ut64 *o
return true;
}
// Returns 1 for a decoded text string, 0 when addr is not a recognised 64-bit
// string object, and -1 for a recognised string whose payload is not text.
static int try_read_dart_string_object64(DartCtx *ctx, ut64 addr, char *out, int outsz) {
if (!ctx || !ctx->layout || ctx->compressed_word_size != 8) {
return false;
}
ut8 hdr[16];
if (!read_mem (ctx, addr, hdr, sizeof (hdr))) {
return false;
}
const ut32 cid = dart_cid_from_object_header (r_read_le64 (hdr));
const int one_byte_cid = dart_cid_get (ctx->layout, DART_CID_ONE_BYTE_STRING);
const int two_byte_cid = dart_cid_get (ctx->layout, DART_CID_TWO_BYTE_STRING);
const bool is_two_byte = two_byte_cid >= 0 && cid == (ut32)two_byte_cid;
if (!is_two_byte && (one_byte_cid < 0 || cid != (ut32)one_byte_cid)) {
return false;
}
const ut64 length_smi = r_read_le64 (hdr + 8);
if ((length_smi & 1) != 0) {
return -1;
}
const ut64 length = length_smi >> 1;
if (!length || length > 1024) {
return -1;
}
const ut64 bytes_len = is_two_byte? length * 2: length;
ut8 *bytes = (ut8 *)malloc ((size_t)bytes_len);
if (!bytes || !read_mem (ctx, addr + 16, bytes, (int)bytes_len)) {
free (bytes);
return -1;
}
if (is_two_byte) {
for (ut64 i = 0; i < length; i++) {
const ut16 code = r_read_le16 (bytes + (i * 2));
if (code < 0x20 || (code >= 0x7f && code < 0xa0)) {
free (bytes);
return -1;
}
}
char *utf8 = dart_utf16le_to_utf8 (bytes, bytes_len);
free (bytes);
if (R_STR_ISEMPTY (utf8)) {
free (utf8);
return -1;
}
r_str_ncpy (out, utf8, outsz);
free (utf8);
return true;
}
int written = 0;
for (ut64 i = 0; i < length; i++) {
const ut8 ch = bytes[i];
if (ch < 0x20 || (ch >= 0x7f && ch < 0xa0)) {
free (bytes);
return -1;
}
if (ch < 0x80) {
if (written + 1 >= outsz) {
break;
}
out[written++] = (char)ch;
} else {
if (written + 2 >= outsz) {
break;
}
out[written++] = (char) (0xc0 | (ch >> 6));
out[written++] = (char) (0x80 | (ch & 0x3f));
}
}
out[written] = '\0';
free (bytes);
return true;
}
bool try_read_dart_string(DartCtx *ctx, ut64 addr, char *out, int outsz) {
if (!ctx || !out || outsz <= 1) {
return false;
}
const int object_string = try_read_dart_string_object64 (ctx, addr, out, outsz);
if (object_string) {
return object_string > 0;
}
ut8 hdr[32];
if (!read_mem (ctx, addr, hdr, sizeof (hdr))) {
return false;
@@ -177,7 +213,6 @@ HtUP *scan_code_names(DartCtx *ctx, ut64 data_image_base, ut64 data_image_end) {
}
LayoutHints lh;
init_layout_hints (&lh);
enrich_layout_hints_from_json (&lh, ctx->snapshot_hash);
HtUP *name_by_ep = ht_up_new0 ();
if (!name_by_ep) {
return NULL;
@@ -342,7 +377,7 @@ void collect_data_names_with_r2(DartCtx *ctx, ut64 data_image_base, ut64 data_im
continue;
}
char *line, *saveptr = NULL;
for (line = strtok_r (out, "\n", &saveptr); line; line = strtok_r (NULL, "\n", &saveptr)) {
for (line = r_str_tok_r (out, "\n", &saveptr); line; line = r_str_tok_r (NULL, "\n", &saveptr)) {
if (!r_str_startswith (line, "0x")) {
continue;
}
+576 -202
View File
File diff suppressed because it is too large. Load diff
+88 -64
View File
@@ -17,62 +17,40 @@ typedef struct {
ut64 end;
} ClusterStream;
static inline ut32 dart_cid_from_tags(const DartCtx *ctx, ut64 tags) {
const DartTagStyle style = ctx && ctx->layout? ctx->layout->tag_style: DART_TAG_STYLE_OBJECT_HEADER;
switch (style) {
case DART_TAG_STYLE_CID_INT32:
return (ut32)tags;
case DART_TAG_STYLE_CID_SHIFT1:
return (ut32) (tags >> 1);
case DART_TAG_STYLE_OBJECT_HEADER:
default:
return (ut32) ((tags >> 12) & 0xfffffU);
}
}
// Which flavour of type a DartTypeInfo holds. Not snapshot class ids: these
// never leave the parser, so they carry no version dependency.
typedef enum {
kIllegalCid = 0,
kClassCid = 5,
kPatchClassCid = 6,
kFunctionCid = 7,
kClosureDataCid = 8,
kFfiTrampolineDataCid = 9,
kFieldCid = 10,
kScriptCid = 11,
kLibraryCid = 12,
kNamespaceCid = 13,
kKernelProgramInfoCid = 14,
kCodeCid = 40,
kInstructionsCid = 41,
kObjectPoolCid = 45,
kCodeSourceMapCid = 49,
kPcDescriptorsCid = 50,
kStringCid = 72,
kOneByteStringCid = 73,
kTwoByteStringCid = 74,
kArrayCid = 75,
kImmutableArrayCid = 76,
kGrowableObjectArrayCid = 77,
kMintCid = 78,
kDoubleCid = 79,
kTypedDataBaseCid = 80,
kTypeCid = 110,
kFunctionTypeCid = 111,
kRecordTypeCid = 112,
kTypeParametersCid = 113,
kTypeParameterCid = 114,
kTypeArgumentsCid = 115,
kNumPredefinedCids = 128
} DartCid;
typedef struct {
ut64 ref_id;
ut64 name_ref;
ut64 library_ref;
char *name;
int instance_size;
} DartClass;
typedef struct {
ut64 ref_id;
ut64 name_ref;
ut64 owner_ref;
ut64 code_ref;
ut64 entry_point;
char *name;
} DartPoolFunction;
DART_TYPE_KIND_TYPE = 1,
DART_TYPE_KIND_FUNCTION_TYPE,
DART_TYPE_KIND_TYPE_PARAMETER,
} DartTypeKind;
#define DART_SNAPSHOT_MAGIC 0xdcdcf5f5
#define DART_SNAPSHOT_FIXED_SIZE (4 + 8 + 8)
#define DART_SNAPSHOT_HASH_SIZE 32
#define DART_SNAPSHOT_FEATURES_SCAN_MAX 2048
#define DART_IMAGE_ALIGNMENT 64
static inline ut32 dart_cid_from_object_header(ut64 tags) {
return (ut32) ((tags >> 12) & 0xfffffU);
}
static inline ut64 dart_snapshot_data_image_base(ut64 snapshot_base, ut64 snapshot_len) {
return snapshot_base + ((snapshot_len + (DART_IMAGE_ALIGNMENT - 1)) & ~ (DART_IMAGE_ALIGNMENT - 1));
}
typedef struct {
bool ok;
@@ -84,6 +62,8 @@ typedef struct {
ut64 nb;
ut64 no;
ut64 nc;
ut64 ncc;
ut64 field_table_len;
ut64 itlen;
ut64 itdata;
ut64 cluster_start;
@@ -185,7 +165,40 @@ typedef struct {
char *resolved_name;
int resolved_cid;
ut64 resolved_code_index;
} DartModernPoolSlotInfo;
} ModernPoolSlot;
typedef struct {
ut64 pp_offset;
ut64 pp_base;
ut64 pool_ref;
ut64 pool_index;
ut64 entry_index;
ut64 target_ref;
const char *target_kind;
ut64 field_index;
ut64 string_ref;
ut64 string_addr;
ut32 string_flags;
bool via_collection;
const char *string_value;
} ModernPoolStrRef;
typedef void(*ModernPoolStrRefCb)(const ModernPoolStrRef *info, void *user);
typedef struct {
ut64 pp_offset;
ut64 pool_ref;
ut64 pool_index;
ut64 entry_index;
ut64 ref;
const char *kind; // resolved kind: "string", "function", "class", "code", ...
const char *name; // resolved name/value, or NULL (valid only during the callback)
int cid;
ut64 code_index; // UT64_MAX when the entry does not resolve to code
ut64 value_addr; // snapshot address of the resolved object, or 0
} ModernPoolRefInfo;
typedef void(*ModernPoolRefCb)(const ModernPoolRefInfo *info, void *user);
typedef void(*DartInstructionTableEntryCallback)(const DartInstructionTableEntry *entry, void *user);
@@ -210,7 +223,9 @@ typedef struct {
ut64 cluster_end;
ut64 num_clusters;
ut64 num_base_objects;
} DartModernClusterRequest;
} ModernReq;
typedef struct modern_value_graph_t ModernValueGraph;
typedef struct {
DartCtx *ctx;
@@ -223,7 +238,7 @@ typedef struct {
const char *r2_scope;
RStrBuf *sb;
PJ *pj;
} DartModernClusterSummaryRequest;
} ModernSummaryReq;
bool read_mem(DartCtx *ctx, ut64 addr, void *buf, int len);
bool read_u32_at(DartCtx *ctx, ut64 addr, ut32 *out);
@@ -232,7 +247,8 @@ bool dart_read_unsigned_at(DartCtx *ctx, ut64 addr, ut64 *out_val, ut64 *out_nex
bool dart_read_unsigned_buf(const ut8 *buf, ut64 size, ut64 pos, ut64 *out_val, ut64 *out_next);
char *dart_utf16le_to_utf8(const ut8 *buf, ut64 size);
bool dart_snapshot_header_read(DartCtx *ctx, ut64 base, DartSnapshotHeader *out);
bool dart_snapshot_header_read_buf(const ut8 *buf, ut64 size, DartSnapshotHeader *out);
bool dart_snapshot_fingerprint_read(DartCtx *ctx, ut64 base, DartSnapshotHeader *out);
bool dart_snapshot_header_read_buf(const ut8 *buf, ut64 size, const DartVerLayout *layout, DartSnapshotHeader *out);
DartVerLayout *dart_ctx_init_layout(DartCtx *ctx, DartVerLayout *tmp);
void dart_ctx_fini_layout(DartCtx *ctx, DartVerLayout *owned);
int find_snapshots(DartCtx *ctx);
@@ -262,18 +278,26 @@ bool cs_read_bytes(ClusterStream *s, ut8 *buf, int len);
void free_dart_string(void *p);
int decode_string_cluster(ClusterStream *s, DartCtx *ctx, ut64 *ref_counter, bool is_canonical);
void skip_generic_cluster(ClusterStream *stream);
int deserialize_clusters(DartCtx *ctx, ut64 cluster_start, ut64 cluster_end, ut64 num_clusters, ut64 iso_instr);
void resolve_names(DartCtx *ctx);
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);
bool modern_supported(DartCtx *ctx);
bool modern_emit_summary(const ModernSummaryReq *req);
bool modern_probe_snapshot(const ModernReq *req, ut64 num_objects, ut64 *semantic_score);
bool modern_build_pp(const ModernReq *req, DartPpInfo *out);
bool modern_resolve_pp_slot(const ModernReq *req, ut64 pp_off, ModernPoolSlot *out);
void modern_slot_fini(ModernPoolSlot *slot);
ModernValueGraph *modern_value_graph_new(const ModernReq *req, ut64 root_ref);
void modern_value_graph_free(ModernValueGraph *graph);
void modern_value_graph_json(PJ *pj, ModernValueGraph *graph);
void modern_value_graph_text(RStrBuf *sb, ModernValueGraph *graph);
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);
DartStringCategory dart_string_classify_value(const char *s);
void init_function_layout(DartCtx *ctx, DartFunctionLayout *fl);
+215 -48
View File
@@ -1,11 +1,87 @@
/* r2flutter - MIT - Copyright 2026 - pancake */
#include <ctype.h>
#include "dart_pool_parse_priv.h"
// Window size for the read cache. Cluster streams are decoded one byte at a
// time, so serving those reads from a prefetched window turns millions of
// r_io_read_at calls into a handful.
#define RMEM_WINDOW (1024 * 1024)
// Several windows are kept because decoders interleave distant regions, e.g.
// a cluster alloc stream and the RO data image it indexes. With a single
// window every such step refilled it twice.
#define RMEM_WINDOWS 4
typedef struct {
ut8 *buf;
ut64 addr;
ut64 used; // last access tick, for least-recently-used eviction
int len;
} DartReadWindow;
typedef struct dart_read_cache_t {
DartReadWindow w[RMEM_WINDOWS];
ut64 tick;
} DartReadCache;
void dart_read_cache_free(DartReadCache *cache) {
if (!cache) {
return;
}
for (int i = 0; i < RMEM_WINDOWS; i++) {
free (cache->w[i].buf);
}
free (cache);
}
bool read_mem(DartCtx *ctx, ut64 addr, void *buf, int len) {
if (!ctx || !ctx->core || !buf || len <= 0) {
return false;
}
// Reads larger than a window bypass the cache.
if (len > RMEM_WINDOW) {
return r_io_read_at (ctx->core->io, addr, (ut8 *)buf, len);
}
if (!ctx->rmem) {
ctx->rmem = R_NEW0 (DartReadCache);
}
DartReadCache *cache = ctx->rmem;
cache->tick++;
// Fast path: request fully contained in a cached window.
DartReadWindow *victim = &cache->w[0];
for (int i = 0; i < RMEM_WINDOWS; i++) {
DartReadWindow *w = &cache->w[i];
if (w->len && addr >= w->addr) {
ut64 off = addr - w->addr;
if (off + (ut64)len <= (ut64)w->len) {
w->used = cache->tick;
memcpy (buf, w->buf + off, (size_t)len);
return true;
}
}
if (w->used < victim->used) {
victim = w;
}
}
if (!victim->buf) {
victim->buf = malloc (RMEM_WINDOW);
if (!victim->buf) {
return r_io_read_at (ctx->core->io, addr, (ut8 *)buf, len);
}
}
// Refill the least recently used window at addr. r_io_read_at only
// succeeds when the whole range is readable, so a successful window read
// means every byte in it is valid and safe to serve. On failure (e.g. near
// the end of a mapped region) fall back to an exact read and drop that
// window, whose buffer the failed read may have clobbered.
if (r_io_read_at (ctx->core->io, addr, victim->buf, RMEM_WINDOW)) {
victim->addr = addr;
victim->len = RMEM_WINDOW;
victim->used = cache->tick;
memcpy (buf, victim->buf, (size_t)len);
return true;
}
victim->len = 0;
return r_io_read_at (ctx->core->io, addr, (ut8 *)buf, len);
}
@@ -106,7 +182,71 @@ char *dart_utf16le_to_utf8(const ut8 *buf, ut64 size) {
return out;
}
bool dart_snapshot_header_read(DartCtx *ctx, ut64 base, DartSnapshotHeader *out) {
static const DartVerLayout *dart_snapshot_header_layout(DartCtx *ctx, const char *hash) {
if (ctx && ctx->layout) {
return ctx->layout;
}
if (ctx && R_STR_ISNOTEMPTY (ctx->dart_version_override)) {
const DartVerLayout *layout = dart_profile_from_version (ctx->dart_version_override);
if (layout) {
return layout;
}
}
const char *version = dart_version_from_hash (hash);
const DartVerLayout *layout = version? dart_profile_from_version (version): NULL;
return layout? layout: dart_newest_profile ();
}
static bool dart_snapshot_header_set_fields(DartSnapshotHeader *out, const DartVerLayout *layout, const ut64 *fields, int count) {
if (!out || !layout || !fields || count != layout->header_fields) {
return false;
}
switch (layout->header_style) {
case DART_HEADER_STYLE_210:
out->nb = fields[0];
out->no = fields[1];
out->nc = fields[2];
out->field_table_len = fields[3];
break;
case DART_HEADER_STYLE_212:
if (UT64_MAX - fields[2] < fields[3]) {
return false;
}
out->nb = fields[0];
out->no = fields[1];
out->ncc = fields[2];
out->nc = fields[2] + fields[3];
out->field_table_len = fields[4];
break;
case DART_HEADER_STYLE_214:
out->nb = fields[0];
out->no = fields[1];
out->nc = fields[2];
out->field_table_len = fields[3];
out->itlen = fields[4];
break;
case DART_HEADER_STYLE_216:
out->nb = fields[0];
out->no = fields[1];
out->nc = fields[2];
out->field_table_len = fields[3];
out->itlen = fields[4];
out->itdata = fields[5];
break;
case DART_HEADER_STYLE_MODERN:
out->nb = fields[0];
out->no = fields[1];
out->nc = fields[2];
out->itlen = fields[3];
out->itdata = fields[4];
break;
default:
return false;
}
return true;
}
static bool dart_snapshot_outer_read(DartCtx *ctx, ut64 base, DartSnapshotHeader *out, ut64 *out_cursor) {
if (!ctx || !out) {
return false;
}
@@ -119,17 +259,34 @@ bool dart_snapshot_header_read(DartCtx *ctx, ut64 base, DartSnapshotHeader *out)
if (out->magic != DART_SNAPSHOT_MAGIC) {
return false;
}
out->total_len = r_read_le64 (hdr + 4) + 4;
const ut64 stored_len = r_read_le64 (hdr + 4);
if (stored_len > UT64_MAX - 4) {
return false;
}
out->total_len = stored_len + 4;
if (out->total_len < DART_SNAPSHOT_FIXED_SIZE + DART_SNAPSHOT_HASH_SIZE + 1 || out->total_len > (1ULL << 34) || base > UT64_MAX - out->total_len) {
return false;
}
RIOMap *map = r_io_map_get_at (ctx->core->io, base);
if (map && base + out->total_len - 1 > r_io_map_to (map)) {
return false;
}
out->kind = r_read_le64 (hdr + 12);
ut64 cursor = base + DART_SNAPSHOT_FIXED_SIZE;
if (!read_mem (ctx, cursor, out->hash, DART_SNAPSHOT_HASH_SIZE)) {
return false;
}
out->hash[DART_SNAPSHOT_HASH_SIZE] = '\0';
for (int i = 0; i < DART_SNAPSHOT_HASH_SIZE; i++) {
if (!isxdigit ((ut8)out->hash[i])) {
return false;
}
}
cursor += DART_SNAPSHOT_HASH_SIZE;
ut8 b = 0;
int scanned = 0;
while (scanned < DART_SNAPSHOT_FEATURES_SCAN_MAX) {
const ut64 snapshot_end = base + out->total_len;
while (scanned < DART_SNAPSHOT_FEATURES_SCAN_MAX && cursor + (ut64)scanned < snapshot_end) {
if (!read_mem (ctx, cursor + scanned, &b, 1)) {
return false;
}
@@ -138,7 +295,7 @@ bool dart_snapshot_header_read(DartCtx *ctx, ut64 base, DartSnapshotHeader *out)
}
scanned++;
}
if (b) {
if (b || cursor + (ut64)scanned >= snapshot_end) {
return false;
}
int tocopy = R_MIN (scanned, (int)sizeof (out->flags) - 1);
@@ -147,20 +304,36 @@ bool dart_snapshot_header_read(DartCtx *ctx, ut64 base, DartSnapshotHeader *out)
}
out->flags[tocopy] = '\0';
cursor += (ut64)scanned + 1;
if (cursor >= snapshot_end) {
return false;
}
if (out_cursor) {
*out_cursor = cursor;
}
return true;
}
bool dart_snapshot_fingerprint_read(DartCtx *ctx, ut64 base, DartSnapshotHeader *out) {
return dart_snapshot_outer_read (ctx, base, out, NULL);
}
bool dart_snapshot_header_read(DartCtx *ctx, ut64 base, DartSnapshotHeader *out) {
ut64 cursor = 0;
if (!dart_snapshot_outer_read (ctx, base, out, &cursor)) {
return false;
}
const DartVerLayout *layout = dart_snapshot_header_layout (ctx, out->hash);
if (!layout || layout->header_fields < 4 || layout->header_fields > 6) {
return false;
}
ut64 next = cursor;
if (!dart_read_unsigned_at (ctx, next, &out->nb, &next)) {
return false;
ut64 fields[6] = { 0 };
for (int i = 0; i < layout->header_fields; i++) {
if (!dart_read_unsigned_at (ctx, next, &fields[i], &next)) {
return false;
}
}
if (!dart_read_unsigned_at (ctx, next, &out->no, &next)) {
return false;
}
if (!dart_read_unsigned_at (ctx, next, &out->nc, &next)) {
return false;
}
if (!dart_read_unsigned_at (ctx, next, &out->itlen, &next)) {
return false;
}
if (!dart_read_unsigned_at (ctx, next, &out->itdata, &next)) {
if (!dart_snapshot_header_set_fields (out, layout, fields, layout->header_fields)) {
return false;
}
out->cluster_start = next;
@@ -168,7 +341,7 @@ bool dart_snapshot_header_read(DartCtx *ctx, ut64 base, DartSnapshotHeader *out)
return true;
}
bool dart_snapshot_header_read_buf(const ut8 *buf, ut64 size, DartSnapshotHeader *out) {
bool dart_snapshot_header_read_buf(const ut8 *buf, ut64 size, const DartVerLayout *layout, DartSnapshotHeader *out) {
if (!buf || !out || size < DART_SNAPSHOT_FIXED_SIZE + DART_SNAPSHOT_HASH_SIZE + 1) {
return false;
}
@@ -202,20 +375,20 @@ bool dart_snapshot_header_read_buf(const ut8 *buf, ut64 size, DartSnapshotHeader
}
out->flags[tocopy] = '\0';
cursor += (ut64)scanned + 1;
if (!layout) {
layout = dart_snapshot_header_layout (NULL, out->hash);
}
if (!layout || layout->header_fields < 4 || layout->header_fields > 6) {
return false;
}
ut64 next = cursor;
if (!dart_read_unsigned_buf (buf, size, next, &out->nb, &next)) {
return false;
ut64 fields[6] = { 0 };
for (int i = 0; i < layout->header_fields; i++) {
if (!dart_read_unsigned_buf (buf, size, next, &fields[i], &next)) {
return false;
}
}
if (!dart_read_unsigned_buf (buf, size, next, &out->no, &next)) {
return false;
}
if (!dart_read_unsigned_buf (buf, size, next, &out->nc, &next)) {
return false;
}
if (!dart_read_unsigned_buf (buf, size, next, &out->itlen, &next)) {
return false;
}
if (!dart_read_unsigned_buf (buf, size, next, &out->itdata, &next)) {
if (!dart_snapshot_header_set_fields (out, layout, fields, layout->header_fields)) {
return false;
}
if (next >= out->total_len) {
@@ -257,6 +430,9 @@ bool cs_read_unsigned(ClusterStream *s, ut64 *out) {
}
bool cs_read_ref_id(ClusterStream *s, ut64 *out) {
if (s && s->ctx && s->ctx->layout && s->ctx->layout->ref_encoding == DART_REF_ENCODING_UNSIGNED) {
return cs_read_unsigned (s, out);
}
int64_t result = 0;
for (int i = 0; i < 5; i++) {
ut8 raw = 0;
@@ -307,33 +483,24 @@ bool cs_read_tagged32(ClusterStream *s, ut32 *out) {
return false;
}
// ReadStream::Read<int64_t>: 7-bit groups, then a final byte holding a signed
// value in [-64, 63] biased by kEndByteMarker (192).
bool cs_read_tagged64(ClusterStream *s, int64_t *out) {
ut8 raw = 0;
if (!cs_read_u8 (s, &raw)) {
return false;
}
int8_t b = (int8_t)raw;
if (b < 0) {
if (out) {
*out = (int64_t)b + 192;
}
return true;
}
int64_t v = (int64_t)raw;
int shift = 7;
for (int i = 1; i < 10; i++) {
ut64 v = 0;
int shift = 0;
for (int i = 0; i < 10; i++) {
ut8 raw = 0;
if (!cs_read_u8 (s, &raw)) {
return false;
}
b = (int8_t)raw;
if (b < 0) {
v |= ((int64_t)b + 192) << shift;
if (raw > 0x7f) {
v |= (ut64) ((int64_t)raw - 192) << shift;
if (out) {
*out = v;
*out = (int64_t)v;
}
return true;
}
v |= ((int64_t)raw) << shift;
v |= (ut64)raw << shift;
shift += 7;
}
return false;
+116 -80
View File
@@ -26,7 +26,17 @@ void dart_string_list_free(RList *list) {
r_list_free (list);
}
#define DART_STRING_SCAN_LIMIT 50000
typedef struct {
RList *list;
HtUP *seen_addrs;
ut64 ref_counter;
ut64 limit; // 0 means unlimited
} StringScan;
static bool string_scan_full(const StringScan *ss) {
return ss->limit && (ut64)r_list_length (ss->list) >= ss->limit;
}
DartStringCategory dart_string_classify_value(const char *s) {
if (R_STR_ISEMPTY (s)) {
return DART_STRING_CAT_UNKNOWN;
@@ -57,7 +67,7 @@ static const char *string_category_name(DartStringCategory cat) {
}
}
static void scan_utf16_strings(const ut8 *buf, ut64 base, ut64 size, RList *list, HtUP *seen_addrs, ut64 *ref_counter);
static void scan_utf16_strings(const ut8 *buf, ut64 base, ut64 size, StringScan *ss);
static bool should_scan_section(const RBinSection *sec);
static bool is_common_text_punct(ut8 ch) {
@@ -146,31 +156,28 @@ static void packed_string_record_fini(PackedStringRecord *rec) {
rec->value = NULL;
}
static void append_string_info(RList *list, HtUP *seen_addrs, const char *value, ut32 len, ut32 flags, ut64 addr, DartStringCategory cat, ut64 *ref_counter) {
if (!list || !value) {
static void append_string_info(StringScan *ss, const char *value, ut32 len, ut32 flags, ut64 addr, DartStringCategory cat) {
if (!value || string_scan_full (ss)) {
return;
}
if (r_list_length (list) >= DART_STRING_SCAN_LIMIT) {
return;
}
if (seen_addrs && addr && ht_up_find (seen_addrs, addr, NULL)) {
if (addr && ht_up_find (ss->seen_addrs, addr, NULL)) {
return;
}
DartStringInfo *si = R_NEW0 (DartStringInfo);
si->ref_id = ref_counter? (*ref_counter)++: 0;
si->ref_id = ss->ref_counter++;
si->length = len;
si->flags = flags | DART_STRING_CANONICAL;
si->address = addr;
si->category = cat;
si->references = r_list_newf ((RListFree)dart_string_ref_free);
si->value = strdup (value);
r_list_append (list, si);
if (seen_addrs && addr) {
ht_up_insert (seen_addrs, addr, si);
r_list_append (ss->list, si);
if (addr) {
ht_up_insert (ss->seen_addrs, addr, si);
}
}
static void scan_ascii_strings(const ut8 *buf, ut64 base, ut64 size, RList *list, HtUP *seen_addrs, ut64 *ref_counter) {
static void scan_ascii_strings(const ut8 *buf, ut64 base, ut64 size, StringScan *ss) {
if (!buf || !size) {
return;
}
@@ -190,7 +197,7 @@ static void scan_ascii_strings(const ut8 *buf, ut64 base, ut64 size, RList *list
memcpy (tmp, buf + start, length);
tmp[length] = '\0';
if (looks_like_text (tmp)) {
append_string_info (list, seen_addrs, tmp, (ut32)length, 0, base + start, dart_string_classify_value (tmp), ref_counter);
append_string_info (ss, tmp, (ut32)length, 0, base + start, dart_string_classify_value (tmp));
}
free (tmp);
}
@@ -334,12 +341,12 @@ static bool parse_packed_string_record_text(const ut8 *buf, ut64 size, ut64 pos,
return out->value != NULL;
}
static void scan_packed_string_runs(const ut8 *buf, ut64 base, ut64 size, RList *list, HtUP *seen_addrs, ut64 *ref_counter) {
if (!buf || !size || !list) {
static void scan_packed_string_runs(const ut8 *buf, ut64 base, ut64 size, StringScan *ss) {
if (!buf || !size) {
return;
}
ut64 pos = 0;
while (pos < size && r_list_length (list) < DART_STRING_SCAN_LIMIT) {
while (pos < size && !string_scan_full (ss)) {
PackedStringRecord probe = { 0 };
if (!parse_packed_string_record_text (buf, size, pos, &probe)) {
pos++;
@@ -372,9 +379,9 @@ static void scan_packed_string_runs(const ut8 *buf, ut64 base, ut64 size, RList
skips++;
}
if (n_records >= DART_PACKED_STRING_MIN_RUN) {
for (int i = 0; i < n_records && r_list_length (list) < DART_STRING_SCAN_LIMIT; i++) {
for (int i = 0; i < n_records && !string_scan_full (ss); i++) {
PackedStringRecord *rec = &records[i];
append_string_info (list, seen_addrs, rec->value, rec->length, rec->flags, base + rec->payload_off, dart_string_classify_value (rec->value), ref_counter);
append_string_info (ss, rec->value, rec->length, rec->flags, base + rec->payload_off, dart_string_classify_value (rec->value));
}
pos = run_end;
} else {
@@ -386,8 +393,8 @@ static void scan_packed_string_runs(const ut8 *buf, ut64 base, ut64 size, RList
}
}
static void scan_packed_strings_from_snapshot(DartCtx *ctx, ut64 snapshot_base, RList *list, HtUP *seen_addrs, ut64 *ref_counter) {
if (!ctx || !snapshot_base || !list) {
static void scan_packed_strings_from_snapshot(DartCtx *ctx, ut64 snapshot_base, StringScan *ss) {
if (!ctx || !snapshot_base) {
return;
}
ut8 hdrbuf[12];
@@ -410,30 +417,30 @@ static void scan_packed_strings_from_snapshot(DartCtx *ctx, ut64 snapshot_base,
return;
}
DartSnapshotHeader hdr;
if (!dart_snapshot_header_read_buf (buf, total_len, &hdr)) {
if (!dart_snapshot_header_read_buf (buf, total_len, ctx->layout, &hdr)) {
free (buf);
return;
}
if (ctx->verbose > 1) {
fprintf (stderr, "[r2flutter] packed string scan snapshot=0x%" PFMT64x " cluster_start=0x%" PFMT64x " total=0x%" PFMT64x "\n", snapshot_base, snapshot_base + hdr.cluster_start, hdr.total_len);
}
scan_packed_string_runs (buf + hdr.cluster_start, snapshot_base + hdr.cluster_start, hdr.total_len - hdr.cluster_start, list, seen_addrs, ref_counter);
scan_packed_string_runs (buf + hdr.cluster_start, snapshot_base + hdr.cluster_start, hdr.total_len - hdr.cluster_start, ss);
free (buf);
}
#define DART_SNAPSHOT_SCAN_MAX (64ULL << 20)
#define DART_DATA_IMAGE_SCAN_MAX (8ULL << 20)
static void scan_string_buffer(const ut8 *buf, ut64 base, ut64 size, RList *list, HtUP *seen_addrs, ut64 *ref_counter) {
if (!buf || !size || !list) {
static void scan_string_buffer(const ut8 *buf, ut64 base, ut64 size, StringScan *ss) {
if (!buf || !size) {
return;
}
scan_ascii_strings (buf, base, size, list, seen_addrs, ref_counter);
scan_utf16_strings (buf, base, size, list, seen_addrs, ref_counter);
scan_ascii_strings (buf, base, size, ss);
scan_utf16_strings (buf, base, size, ss);
}
static void scan_snapshot_region(DartCtx *ctx, ut64 start, ut64 size, RList *list, HtUP *seen_addrs, ut64 *ref_counter) {
if (!ctx || !ctx->core || !ctx->core->bin || !start || !size || !list || size > DART_SNAPSHOT_SCAN_MAX) {
static void scan_snapshot_region(DartCtx *ctx, ut64 start, ut64 size, StringScan *ss) {
if (!ctx || !ctx->core || !ctx->core->bin || !start || !size || size > DART_SNAPSHOT_SCAN_MAX) {
return;
}
ut64 end = start + size;
@@ -468,16 +475,16 @@ static void scan_snapshot_region(DartCtx *ctx, ut64 start, ut64 size, RList *lis
free (buf);
continue;
}
scan_string_buffer (buf, chunk_start, chunk_size, list, seen_addrs, ref_counter);
scan_string_buffer (buf, chunk_start, chunk_size, ss);
free (buf);
}
}
static void scan_strings_from_snapshot_window(DartCtx *ctx, ut64 snapshot_base, ut64 upper_bound, RList *list, HtUP *seen_addrs, ut64 *ref_counter) {
if (!ctx || !snapshot_base || !list) {
static void scan_strings_from_snapshot_window(DartCtx *ctx, ut64 snapshot_base, ut64 upper_bound, StringScan *ss) {
if (!ctx || !snapshot_base) {
return;
}
ut64 before_count = r_list_length (list);
ut64 before_count = r_list_length (ss->list);
DartSnapshotHeader sh;
if (!dart_snapshot_header_read (ctx, snapshot_base, &sh)) {
goto fallback;
@@ -486,10 +493,10 @@ static void scan_strings_from_snapshot_window(DartCtx *ctx, ut64 snapshot_base,
goto fallback;
}
if (ctx->compressed_word_size == 4) {
scan_packed_strings_from_snapshot (ctx, snapshot_base, list, seen_addrs, ref_counter);
scan_packed_strings_from_snapshot (ctx, snapshot_base, ss);
return;
}
scan_snapshot_region (ctx, snapshot_base + sh.cluster_start, sh.total_len - sh.cluster_start, list, seen_addrs, ref_counter);
scan_snapshot_region (ctx, snapshot_base + sh.cluster_start, sh.total_len - sh.cluster_start, ss);
ut64 align = ctx->layout && ctx->layout->max_alignment? (ut64)ctx->layout->max_alignment: 16;
if (align == 0) {
align = 16;
@@ -497,18 +504,18 @@ static void scan_strings_from_snapshot_window(DartCtx *ctx, ut64 snapshot_base,
ut64 data_start = snapshot_base + ((sh.total_len + (align - 1)) & ~ (align - 1));
ut64 data_end = upper_bound;
if (data_end > data_start && (data_end - data_start) <= DART_DATA_IMAGE_SCAN_MAX) {
scan_snapshot_region (ctx, data_start, data_end - data_start, list, seen_addrs, ref_counter);
scan_snapshot_region (ctx, data_start, data_end - data_start, ss);
}
if ((ut64)r_list_length (list) > before_count) {
if ((ut64)r_list_length (ss->list) > before_count) {
return;
}
fallback:
if (upper_bound > snapshot_base + 0x100 && (upper_bound - (snapshot_base + 0x100)) <= DART_DATA_IMAGE_SCAN_MAX) {
scan_snapshot_region (ctx, snapshot_base + 0x100, upper_bound - (snapshot_base + 0x100), list, seen_addrs, ref_counter);
scan_snapshot_region (ctx, snapshot_base + 0x100, upper_bound - (snapshot_base + 0x100), ss);
}
}
static void scan_utf16_strings(const ut8 *buf, ut64 base, ut64 size, RList *list, HtUP *seen_addrs, ut64 *ref_counter) {
static void scan_utf16_strings(const ut8 *buf, ut64 base, ut64 size, StringScan *ss) {
if (!buf || size < 8) {
return;
}
@@ -536,7 +543,7 @@ static void scan_utf16_strings(const ut8 *buf, ut64 base, ut64 size, RList *list
if (utf8 && units >= 4 && utf16le_has_ascii_profile (buf, start, start + (units * 2))) {
ut32 ulen = (ut32)strlen (utf8);
if (ulen >= 4 && ulen <= 512 && looks_like_text (utf8)) {
append_string_info (list, seen_addrs, utf8, ulen, DART_STRING_TWO_BYTE, base + start, dart_string_classify_value (utf8), ref_counter);
append_string_info (ss, utf8, ulen, DART_STRING_TWO_BYTE, base + start, dart_string_classify_value (utf8));
}
}
r_strbuf_fini (&sb);
@@ -583,16 +590,15 @@ RList *dart_pool_extract_strings(DartCtx *ctx) {
return NULL;
}
RList *string_list = r_list_newf ((RListFree)dart_string_info_free);
HtUP *seen_addrs = ht_up_new0 ();
if (!seen_addrs) {
return string_list;
}
RVecRBinSection *sections = r_bin_get_sections_vec (ctx->core->bin);
if (!sections) {
ht_up_free (seen_addrs);
return string_list;
}
ut64 ref_counter = 0;
StringScan ss = {
.list = string_list,
.seen_addrs = ht_up_new0 (),
.limit = ctx->str_scan_limit
};
if (find_snapshots (ctx) == 0 && ctx->vm_data) {
DartVerLayout layout_tmp;
DartVerLayout *layout_owned = dart_ctx_init_layout (ctx, &layout_tmp);
@@ -608,14 +614,14 @@ RList *dart_pool_extract_strings(DartCtx *ctx) {
} else if (ctx->iso_instr > ctx->iso_data) {
iso_upper = ctx->iso_instr;
}
scan_strings_from_snapshot_window (ctx, ctx->vm_data, vm_upper, string_list, seen_addrs, &ref_counter);
scan_strings_from_snapshot_window (ctx, ctx->iso_data, iso_upper, string_list, seen_addrs, &ref_counter);
scan_strings_from_snapshot_window (ctx, ctx->vm_data, vm_upper, &ss);
scan_strings_from_snapshot_window (ctx, ctx->iso_data, iso_upper, &ss);
if (r_list_length (string_list) == 0) {
if (vm_upper > ctx->vm_data + 0x100) {
scan_snapshot_region (ctx, ctx->vm_data + 0x100, vm_upper - (ctx->vm_data + 0x100), string_list, seen_addrs, &ref_counter);
scan_snapshot_region (ctx, ctx->vm_data + 0x100, vm_upper - (ctx->vm_data + 0x100), &ss);
}
if (iso_upper > ctx->iso_data + 0x100) {
scan_snapshot_region (ctx, ctx->iso_data + 0x100, iso_upper - (ctx->iso_data + 0x100), string_list, seen_addrs, &ref_counter);
scan_snapshot_region (ctx, ctx->iso_data + 0x100, iso_upper - (ctx->iso_data + 0x100), &ss);
}
}
dart_ctx_fini_layout (ctx, layout_owned);
@@ -637,9 +643,9 @@ RList *dart_pool_extract_strings(DartCtx *ctx) {
free (buf);
continue;
}
scan_string_buffer (buf, sec->vaddr, size, string_list, seen_addrs, &ref_counter);
scan_string_buffer (buf, sec->vaddr, size, &ss);
free (buf);
if (r_list_length (string_list) >= DART_STRING_SCAN_LIMIT) {
if (string_scan_full (&ss)) {
break;
}
}
@@ -648,17 +654,17 @@ RList *dart_pool_extract_strings(DartCtx *ctx) {
if (size > 0 && size <= DART_SNAPSHOT_SCAN_MAX) {
ut8 *buf = (ut8 *)malloc ((size_t)size);
if (buf && read_mem (ctx, 0, buf, (int)size)) {
scan_string_buffer (buf, 0, size, string_list, seen_addrs, &ref_counter);
scan_string_buffer (buf, 0, size, &ss);
}
free (buf);
}
}
ht_up_free (seen_addrs);
ht_up_free (ss.seen_addrs);
r_list_sort (string_list, (RListComparator)string_info_addr_cmp);
return string_list;
}
RList *dart_pool_extract_object_pool_strings(DartCtx *ctx) {
RList *dart_pool_extract_pool_strings(DartCtx *ctx) {
if (!ctx || !ctx->core || !ctx->core->bin) {
return NULL;
}
@@ -667,7 +673,7 @@ RList *dart_pool_extract_object_pool_strings(DartCtx *ctx) {
if (find_snapshots (ctx) == 0 && ctx->vm_data) {
DartVerLayout layout_tmp;
DartVerLayout *layout_owned = dart_ctx_init_layout (ctx, &layout_tmp);
if (dart_modern_is_supported_snapshot (ctx)) {
if (modern_supported (ctx)) {
const ut64 snapshots[] = {
ctx->vm_data,
ctx->iso_data
@@ -682,8 +688,8 @@ RList *dart_pool_extract_object_pool_strings(DartCtx *ctx) {
if (!seen_refs) {
continue;
}
const DartModernClusterRequest req = { ctx, sh.cluster_start, base + sh.total_len, sh.nc, sh.nb };
(void)dart_modern_extract_object_pool_strings_from_clusters (&req, string_list, seen_refs, &ref_counter);
const ModernReq req = { ctx, sh.cluster_start, base + sh.total_len, sh.nc, sh.nb };
(void)modern_extract_pool_strings (&req, string_list, seen_refs, &ref_counter);
ht_up_free (seen_refs);
}
}
@@ -832,50 +838,80 @@ static void dump_string_text(RStrBuf *sb, const DartStringInfo *si, int fmt, boo
}
}
static bool string_matches(const DartCtx *ctx, const RRegex *rx, const DartStringInfo *si) {
if (!si || !si->value) {
return false;
}
if (ctx->str_minlen > 0 && si->length < (ut32)ctx->str_minlen) {
return false;
}
if (ctx->str_maxlen > 0 && si->length > (ut32)ctx->str_maxlen) {
return false;
}
return !rx || r_regex_exec (rx, si->value, 0, NULL, 0) == 0;
}
static char *dump_strings_list(DartCtx *ctx, RList *strings, int fmt) {
const bool refs = ctx && ctx->dump_string_refs;
RRegex *rx = NULL;
if (R_STR_ISNOTEMPTY (ctx->str_filter)) {
rx = r_regex_new (ctx->str_filter, "e");
if (!rx) {
R_LOG_ERROR ("Invalid string filter regex: %s", ctx->str_filter);
return NULL;
}
}
const bool refs = ctx->dump_string_refs;
const bool quiet = ctx->quiet;
PJ *pj = NULL;
RStrBuf *sb = NULL;
if (fmt == 'j') {
if (!strings || r_list_length (strings) == 0) {
return strdup ("[]");
}
PJ *pj = pj_new ();
pj = pj_new ();
pj_a (pj);
RListIter *it;
DartStringInfo *si;
r_list_foreach (strings, it, si) {
if (si) {
dump_string_json (pj, si, refs);
}
}
pj_end (pj);
return pj_drain (pj);
} else {
sb = r_strbuf_new (fmt == 'r' && !quiet? "# Dart Strings\n": "");
}
if (!strings) {
return strdup ("# No strings found\n");
}
const bool quiet = ctx && ctx->quiet;
RStrBuf *sb = r_strbuf_new (fmt == 'r' && !quiet? "# Dart Strings\n": "");
ut64 count = 0;
RListIter *it;
DartStringInfo *si;
r_list_foreach (strings, it, si) {
dump_string_text (sb, si, fmt, quiet, refs);
if (ctx->str_limit && count >= ctx->str_limit) {
break;
}
if (!string_matches (ctx, rx, si)) {
continue;
}
if (pj) {
dump_string_json (pj, si, refs);
} else {
dump_string_text (sb, si, fmt, quiet, refs);
}
count++;
}
r_regex_free (rx);
if (pj) {
pj_end (pj);
return pj_drain (pj);
}
if (fmt == 'r' && !quiet) {
r_strbuf_appendf (sb, "# Total: %d strings\n", r_list_length (strings));
r_strbuf_appendf (sb, "# Total: %" PFMT64u " strings\n", count);
}
return r_strbuf_drain (sb);
}
char *dart_pool_dump_strings(DartCtx *ctx, int fmt) {
RList *strings = dart_pool_extract_object_pool_strings (ctx);
RList *strings = dart_pool_extract_pool_strings (ctx);
char *out = dump_strings_list (ctx, strings, fmt);
dart_string_list_free (strings);
return out;
}
char *dart_pool_dump_strings_fuzzy(DartCtx *ctx, int fmt) {
// stop carving early only when every scanned string is going to be printed
const bool filtered = ctx->str_minlen > 0 || ctx->str_maxlen > 0 || R_STR_ISNOTEMPTY (ctx->str_filter);
ctx->str_scan_limit = filtered? 0: ctx->str_limit;
DartRecoveryModel model = { 0 };
dart_recovery_model_load (ctx, &model, DART_RECOVERY_STRINGS | DART_RECOVERY_CLASSES | DART_RECOVERY_STRING_REFS);
ctx->str_scan_limit = 0;
char *out = dump_strings_list (ctx, model.strings, fmt);
dart_recovery_model_fini (&model);
return out;
+405 -56
View File
@@ -26,6 +26,19 @@ typedef struct {
char *fn_name;
} DartPoolUseInfo;
typedef struct {
ut64 pp_off;
RList *uses;
} DartPpUseBucket;
typedef struct {
DartCtx *ctx;
HtUP *buckets;
RList *xrefs;
ut64 *count;
ut64 limit;
} DartSerializedPoolXrefCtx;
static void dart_xref_info_free(void *p) {
DartXrefInfo *xi = (DartXrefInfo *)p;
if (xi) {
@@ -45,6 +58,12 @@ static void dart_pool_use_info_free(void *p) {
free (ui);
}
static void dart_pp_use_bucket_free(void *p) {
DartPpUseBucket *bucket = (DartPpUseBucket *)p;
r_list_free (bucket->uses);
free (bucket);
}
static char *xref_join_names(const char *owner, const char *name) {
if (R_STR_ISNOTEMPTY (owner) && R_STR_ISNOTEMPTY (name)) {
return r_str_newf ("%s.%s", owner, name);
@@ -178,19 +197,18 @@ static void collect_data_image_xrefs(DartCtx *ctx, DartRecoveryModel *model, RLi
}
DartVerLayout layout_tmp;
DartVerLayout *layout_owned = dart_ctx_init_layout (ctx, &layout_tmp);
ut64 kAlign = ctx->layout && ctx->layout->max_alignment? (ut64)ctx->layout->max_alignment: 16;
RList *seen_fields = r_list_newf (free);
HtUP *seen_ep = ht_up_new0 ();
DartSnapshotHeader sh;
if (dart_snapshot_header_read (ctx, ctx->iso_data, &sh)) {
ut64 data_image_base = ctx->iso_data + ((sh.total_len + (kAlign - 1)) & ~ (kAlign - 1));
ut64 data_image_base = dart_snapshot_data_image_base (ctx->iso_data, sh.total_len);
ut64 data_image_end = ctx->iso_instr? ctx->iso_instr: (data_image_base + (4ULL << 20));
collect_field_scan_xrefs (ctx, model, xrefs, seen_fields, count, limit, data_image_base, data_image_end);
collect_method_scan_xrefs (ctx, model, xrefs, seen_ep, count, limit, data_image_base, data_image_end);
}
if (ctx->vm_data && !xref_limit_reached (*count, limit) &&
dart_snapshot_header_read (ctx, ctx->vm_data, &sh)) {
ut64 vm_data_base = ctx->vm_data + ((sh.total_len + (kAlign - 1)) & ~ (kAlign - 1));
ut64 vm_data_base = dart_snapshot_data_image_base (ctx->vm_data, sh.total_len);
ut64 vm_data_end = ctx->vm_instr? ctx->vm_instr: (vm_data_base + (4ULL << 20));
collect_field_scan_xrefs (ctx, model, xrefs, seen_fields, count, limit, vm_data_base, vm_data_end);
collect_method_scan_xrefs (ctx, model, xrefs, seen_ep, count, limit, vm_data_base, vm_data_end);
@@ -240,6 +258,191 @@ static bool xref_parse_pp_offset(const char *opstr, ut64 *out) {
return true;
}
static char *xref_lower_dup(const char *s) {
if (R_STR_ISEMPTY (s)) {
return NULL;
}
char *d = strdup (s);
for (char *p = d; *p; p++) {
*p = (char)tolower ((ut8)*p);
if (*p == '#') {
*p = ' ';
}
}
return d;
}
static void xref_skip_spaces(const char **p) {
while (p && *p && (**p == ' ' || **p == '\t')) {
(*p)++;
}
}
static bool xref_parse_reg_token(const char **p, int *out) {
if (!p || !*p || !out) {
return false;
}
xref_skip_spaces (p);
if (**p != 'x' && **p != 'w') {
return false;
}
(*p)++;
if (!isdigit ((ut8) * *p)) {
return false;
}
int reg = 0;
while (isdigit ((ut8) * *p)) {
reg = (reg * 10) + (**p - '0');
(*p)++;
}
if (reg < 0 || reg > 30) {
return false;
}
*out = reg;
return true;
}
static bool xref_parse_comma(const char **p) {
xref_skip_spaces (p);
if (!p || !*p || **p != ',') {
return false;
}
(*p)++;
return true;
}
static bool xref_parse_number_token(const char **p, ut64 *out) {
if (!p || !*p || !out) {
return false;
}
xref_skip_spaces (p);
bool neg = false;
if (**p == '-') {
neg = true;
(*p)++;
}
const char *start = *p;
int base = 10;
if (r_str_startswith (start, "0x")) {
base = 16;
*p += 2;
start = *p;
while (isxdigit ((ut8) * *p)) {
(*p)++;
}
} else {
while (isdigit ((ut8) * *p)) {
(*p)++;
}
}
if (*p == start) {
return false;
}
ut64 v = strtoull (start, NULL, base);
*out = neg? (ut64) (- (st64)v): v;
return true;
}
static bool xref_parse_add_pp_offset(const char *text, const bool reg_valid[31], const ut64 reg_off[31], int *dst_out, ut64 *off_out) {
char *lower = xref_lower_dup (text);
if (!lower) {
return false;
}
const char *p = lower;
if (r_str_startswith (p, "add ")) {
p += 4;
}
int dst = -1;
int src = -1;
ut64 imm = 0;
bool ok = xref_parse_reg_token (&p, &dst) &&
xref_parse_comma (&p) &&
xref_parse_reg_token (&p, &src) &&
xref_parse_comma (&p) &&
xref_parse_number_token (&p, &imm);
if (ok) {
xref_skip_spaces (&p);
if (*p == ',') {
p++;
xref_skip_spaces (&p);
}
if (r_str_startswith (p, "lsl")) {
p += 3;
ut64 shift = 0;
if (xref_parse_number_token (&p, &shift) && shift < 64) {
imm <<= shift;
}
}
ut64 base = 0;
if (src == 27) {
base = 0;
} else if (src >= 0 && src < 31 && reg_valid[src]) {
base = reg_off[src];
} else {
ok = false;
}
if (ok) {
if (dst_out) {
*dst_out = dst;
}
if (off_out) {
*off_out = base + imm;
}
}
}
free (lower);
return ok;
}
static bool xref_parse_mem_pp_offset(const char *text, const bool reg_valid[31], const ut64 reg_off[31], ut64 *off_out) {
char *lower = xref_lower_dup (text);
if (!lower) {
return false;
}
bool ok = false;
const char *b = strchr (lower, '[');
while (b && !ok) {
const char *p = b + 1;
int reg = -1;
if (xref_parse_reg_token (&p, &reg)) {
ut64 base = 0;
if (reg == 27) {
ok = true;
} else if (reg >= 0 && reg < 31 && reg_valid[reg]) {
base = reg_off[reg];
ok = true;
}
if (ok) {
ut64 imm = 0;
if (xref_parse_comma (&p)) {
(void)xref_parse_number_token (&p, &imm);
}
if (off_out) {
*off_out = base + imm;
}
}
}
b = strchr (b + 1, '[');
}
free (lower);
return ok;
}
static int xref_parse_dest_reg(const char *text) {
char *lower = xref_lower_dup (text);
if (!lower) {
return -1;
}
const char *p = lower;
while (isalpha ((ut8)*p)) {
p++;
}
int reg = -1;
(void)xref_parse_reg_token (&p, &reg);
free (lower);
return reg;
}
static ut64 xref_object_pool_base(DartCtx *ctx) {
if (!ctx || !ctx->core || !ctx->core->config) {
return 0;
@@ -295,80 +498,97 @@ static const char *xref_pool_flag_kind(const char *name, const char **dst_type,
return NULL;
}
static bool xref_pool_use_seen(HtUP *seen, ut64 at, ut64 pp_off) {
ut64 key = at ^ (pp_off << 8) ^ (pp_off >> 8);
if (ht_up_find (seen, key, NULL)) {
static bool xref_pool_use_seen(HtUP *seen, ut64 at) {
// Each instruction contributes at most one PP use. Key by its address so
// distinct sites cannot collide through a lossy address/offset combination.
if (ht_up_find (seen, at, NULL)) {
return true;
}
ht_up_insert (seen, key, (void *)1);
ht_up_insert (seen, at, (void *)1);
return false;
}
static void collect_pool_uses_from_entry(DartCtx *ctx, const DartInstructionTableEntry *entry, RList *uses, HtUP *seen) {
if (!ctx || !ctx->core || !entry || !entry->address || !uses || !seen) {
// `end_addr` is the next entry's address, or the instructions-image end for
// the last entry. Scan the whole payload without crossing into its neighbour.
static void collect_pool_uses_from_entry(DartCtx *ctx, const DartInstructionTableEntry *entry, ut64 end_addr, RList *uses, HtUP *seen) {
if (!ctx || !ctx->core || !entry || !entry->address || end_addr <= entry->address || !uses || !seen) {
return;
}
r_strf_var (cmd, 128, "pdj 96 @ 0x%" PFMT64x, entry->address);
char *s = r_core_cmd_str (ctx->core, cmd);
if (!s) {
return;
}
RJson *j = r_json_parse (s);
if (!j) {
free (s);
return;
}
const RJson *ops = r_json_get (j, "ops");
const RJson *arr = ops? ops: j;
for (size_t i = 0;; i++) {
const RJson *item = r_json_item (arr, i);
if (!item) {
// Decode the function's instructions one at a time via r_core_anal_op and
// feed op->mnemonic to the operand parsers, rather than rendering the whole
// window to `pdj` JSON and parsing that back. Same disassembly text, minus
// the JSON build/parse round-trip; matches the -AAA path in flutter_analysis.
bool reg_valid[31] = { false };
ut64 reg_off[31] = { 0 };
ut64 at = entry->address;
while (at < end_addr) {
RAnalOp *op = r_core_anal_op (ctx->core, at, R_ARCH_OP_MASK_BASIC | R_ARCH_OP_MASK_DISASM);
if (!op || op->size <= 0 || (ut64)op->size > end_addr - at || R_STR_ISEMPTY (op->mnemonic)) {
r_anal_op_free (op);
break;
}
const char *opstr = r_json_get_str (item, "opstr");
if (R_STR_ISEMPTY (opstr)) {
opstr = r_json_get_str (item, "opcode");
}
const char *m = op->mnemonic;
ut64 pp_off = 0;
if (!xref_parse_pp_offset (opstr, &pp_off)) {
continue;
bool have_pp = xref_parse_pp_offset (m, &pp_off) ||
xref_parse_mem_pp_offset (m, reg_valid, reg_off, &pp_off);
if (have_pp && !xref_pool_use_seen (seen, at)) {
DartPoolUseInfo *ui = R_NEW0 (DartPoolUseInfo);
ui->at = at;
ui->fn_index = entry->index;
ui->pp_off = pp_off;
ui->fn_name = R_STR_ISNOTEMPTY (entry->name)? strdup (entry->name): xref_it_label (entry->index);
r_list_append (uses, ui);
}
ut64 at = (ut64)r_json_get_num (item, "offset");
if (!at) {
at = entry->address;
int add_dst = -1;
ut64 add_off = 0;
if (xref_parse_add_pp_offset (m, reg_valid, reg_off, &add_dst, &add_off) && add_dst >= 0 && add_dst < 31) {
reg_valid[add_dst] = true;
reg_off[add_dst] = add_off;
} else {
const int dst = xref_parse_dest_reg (m);
if (dst >= 0 && dst < 31) {
reg_valid[dst] = false;
}
}
if (xref_pool_use_seen (seen, at, pp_off)) {
continue;
}
DartPoolUseInfo *ui = R_NEW0 (DartPoolUseInfo);
ui->at = at;
ui->fn_index = entry->index;
ui->pp_off = pp_off;
ui->fn_name = R_STR_ISNOTEMPTY (entry->name)? strdup (entry->name): xref_it_label (entry->index);
r_list_append (uses, ui);
at += op->size;
r_anal_op_free (op);
}
r_json_free (j);
free (s);
}
static RList *collect_pool_uses(DartCtx *ctx, RVecDartInstructionTableEntry *entries, ut64 limit) {
static ut64 xref_instructions_end(DartCtx *ctx) {
ut64 size = 0;
if (find_snapshots (ctx) != 0 || !ctx->iso_instr || !read_u64_at (ctx, ctx->iso_instr, &size) || size < DART_IMAGE_ALIGNMENT || size > UT64_MAX - ctx->iso_instr) {
return 0;
}
const ut64 end = ctx->iso_instr + size;
RIOMap *map = r_io_map_get_at (ctx->core->io, ctx->iso_instr);
return map && end - 1 <= r_io_map_to (map)? end: 0;
}
static RList *collect_pool_uses(DartCtx *ctx, RVecDartInstructionTableEntry *entries) {
RList *uses = r_list_newf (dart_pool_use_info_free);
if (!ctx || !entries || RVecDartInstructionTableEntry_length (entries) == 0) {
return uses;
}
HtUP *seen = ht_up_new0 ();
ut64 scanned = 0;
ut64 scan_limit = limit > 0? limit: 4096;
DartInstructionTableEntry *entry;
R_VEC_FOREACH (entries, entry) {
if (!entry->has_code || !entry->address) {
const ut64 image_end = xref_instructions_end (ctx);
const ut64 nentries = RVecDartInstructionTableEntry_length (entries);
for (ut64 i = 0; i < nentries; i++) {
const DartInstructionTableEntry *entry = RVecDartInstructionTableEntry_at (entries, i);
if (!entry || !entry->address) {
continue;
}
if (scan_limit > 0 && scanned >= scan_limit) {
break;
// Entries are emitted in ascending address order, so the next one with
// a distinct address bounds this one, including discarded-Code entries.
ut64 end_addr = image_end;
for (ut64 k = i + 1; k < nentries; k++) {
const DartInstructionTableEntry *nx = RVecDartInstructionTableEntry_at (entries, k);
if (nx && nx->address > entry->address) {
end_addr = image_end? R_MIN (image_end, nx->address): nx->address;
break;
}
}
collect_pool_uses_from_entry (ctx, entry, uses, seen);
scanned++;
collect_pool_uses_from_entry (ctx, entry, end_addr, uses, seen);
}
ht_up_free (seen);
return uses;
@@ -428,15 +648,101 @@ static bool resolve_pool_entry_xref(DartCtx *ctx, DartRecoveryModel *model, RLis
return false;
}
static void xref_serialized_pool_string_cb(const ModernPoolStrRef *info, void *user) {
DartSerializedPoolXrefCtx *ux = (DartSerializedPoolXrefCtx *)user;
if (!info || !ux || !ux->buckets || !ux->xrefs || !ux->count || xref_limit_reached (*ux->count, ux->limit)) {
return;
}
if (!info->string_addr || R_STR_ISEMPTY (info->string_value)) {
return;
}
DartPpUseBucket *bucket = ht_up_find (ux->buckets, info->pp_offset, NULL);
if (!bucket || !bucket->uses) {
return;
}
RListIter *it;
DartPoolUseInfo *use;
r_list_foreach (bucket->uses, it, use) {
if (!use || xref_limit_reached (*ux->count, ux->limit)) {
continue;
}
append_xref_info (ux->xrefs, ux->count, ux->limit, "code", "code.string", "code", use->fn_name, use->fn_index, use->at, "string", info->string_value, info->string_ref, info->string_addr);
}
}
static bool collect_serialized_object_pool_xrefs(DartCtx *ctx, RList *uses, RList *xrefs, ut64 *count, ut64 limit) {
if (!ctx || !ctx->core || !uses || !xrefs || xref_limit_reached (*count, limit)) {
return false;
}
if (find_snapshots (ctx) != 0) {
return false;
}
HtUP *wanted = ht_up_new0 ();
RList *buckets = r_list_newf (dart_pp_use_bucket_free);
RListIter *it;
DartPoolUseInfo *use;
r_list_foreach (uses, it, use) {
if (!use) {
continue;
}
DartPpUseBucket *bucket = ht_up_find (wanted, use->pp_off, NULL);
if (!bucket) {
bucket = R_NEW0 (DartPpUseBucket);
bucket->pp_off = use->pp_off;
bucket->uses = r_list_new ();
r_list_append (buckets, bucket);
ht_up_insert (wanted, use->pp_off, bucket);
}
r_list_append (bucket->uses, use);
}
DartVerLayout layout_tmp;
DartVerLayout *layout_owned = ctx->layout? NULL: dart_ctx_init_layout (ctx, &layout_tmp);
DartSerializedPoolXrefCtx ux = {
.ctx = ctx,
.buckets = wanted,
.xrefs = xrefs,
.count = count,
.limit = limit,
};
bool ok = false;
const ut64 snapshots[] = {
ctx->vm_data,
ctx->iso_data
};
for (size_t i = 0; i < R_ARRAY_SIZE (snapshots) && !xref_limit_reached (*count, limit); i++) {
ut64 base = snapshots[i];
DartSnapshotHeader sh = { 0 };
if (!base || !dart_snapshot_header_read (ctx, base, &sh) || !sh.ok) {
continue;
}
const ModernReq req = {
.ctx = ctx,
.cluster_start = sh.cluster_start,
.cluster_end = base + sh.total_len,
.num_clusters = sh.nc,
.num_base_objects = sh.nb,
};
ok |= modern_collect_pool_strrefs (&req, wanted, xref_serialized_pool_string_cb, &ux);
}
if (layout_owned) {
dart_ctx_fini_layout (ctx, layout_owned);
}
r_list_free (buckets);
ht_up_free (wanted);
return ok;
}
static void collect_object_pool_xrefs(DartCtx *ctx, DartRecoveryModel *model, RList *xrefs, ut64 *count, ut64 limit) {
if (!ctx || !ctx->core || !model || !model->it_entries || !xrefs || xref_limit_reached (*count, limit)) {
return;
}
RList *uses = collect_pool_uses (ctx, model->it_entries);
(void)collect_serialized_object_pool_xrefs (ctx, uses, xrefs, count, limit);
ut64 pool_base = xref_object_pool_base (ctx);
if (!pool_base) {
r_list_free (uses);
return;
}
RList *uses = collect_pool_uses (ctx, model->it_entries, limit);
ut64 heap_base = xref_flag_addr (ctx->core, "app.heap_base");
RListIter *it;
DartPoolUseInfo *use;
@@ -462,7 +768,11 @@ static RList *dart_pool_extract_xrefs(DartCtx *ctx) {
const ut64 limit = ctx->dump_fns_limit > 0? (ut64)ctx->dump_fns_limit: 0;
ut64 count = 0;
DartRecoveryModel model = { 0 };
dart_recovery_model_load (ctx, &model, DART_RECOVERY_STRINGS | DART_RECOVERY_CLASSES | DART_RECOVERY_CLASS_FIELDS | DART_RECOVERY_IT);
dart_recovery_model_load (ctx, &model, DART_RECOVERY_STRINGS | DART_RECOVERY_CLASSES | DART_RECOVERY_CLASS_FIELDS);
// Even limited output needs the full table to bound every scanned payload.
DartCtx work = *ctx;
work.dump_fns_limit = 0;
model.it_entries = dart_pool_extract_instruction_table (&work);
RList *classes = model.classes;
RVecDartInstructionTableEntry *it_entries = model.it_entries;
if (classes) {
@@ -693,3 +1003,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 <string>` 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;
}
+11 -6
View File
@@ -181,11 +181,16 @@ static void sbom_add_snapshot_runtime(DartCtx *ctx, RList *components) {
if (ctx->vm_data && R_STR_ISEMPTY (ctx->snapshot_hash)) {
DartSnapshotHeader hdr;
if (dart_snapshot_header_read (ctx, ctx->vm_data, &hdr)) {
memcpy (ctx->snapshot_hash, hdr.hash, 32);
ctx->snapshot_hash[32] = '\0';
memcpy (ctx->snapshot_hash_actual, hdr.hash, 32);
ctx->snapshot_hash_actual[32] = '\0';
if (R_STR_ISNOTEMPTY (ctx->snapshot_hash_override)) {
r_str_ncpy (ctx->snapshot_hash, ctx->snapshot_hash_override, sizeof (ctx->snapshot_hash));
} else {
r_str_ncpy (ctx->snapshot_hash, ctx->snapshot_hash_actual, sizeof (ctx->snapshot_hash));
}
}
}
const char *version = dart_version_from_hash (ctx->snapshot_hash);
const char *version = dart_ctx_version (ctx);
char evidence[96];
snprintf (evidence, sizeof (evidence), "snapshot_hash=%s", ctx->snapshot_hash[0]? ctx->snapshot_hash: "unknown");
sbom_add_component (components, "runtime", "dart-sdk", version, "snapshot_header", version? 100: 70, evidence);
@@ -354,7 +359,7 @@ static void sbom_scan_pubspec_lock(RList *components, const char *path) {
bool in_packages = false;
char *current = NULL;
char *saveptr = NULL;
for (char *line = strtok_r (yaml, "\n", &saveptr); line; line = strtok_r (NULL, "\n", &saveptr)) {
for (char *line = r_str_tok_r (yaml, "\n", &saveptr); line; line = r_str_tok_r (NULL, "\n", &saveptr)) {
if (!strcmp (line, "packages:")) {
in_packages = true;
continue;
@@ -465,7 +470,7 @@ static void sbom_dump_component_json(PJ *pj, const DartSbomComponent *c) {
}
static char *sbom_dump_json(DartCtx *ctx, RList *components, const char *input_path) {
const char *version = dart_version_from_hash (ctx->snapshot_hash);
const char *version = dart_ctx_version (ctx);
PJ *pj = pj_new ();
pj_o (pj);
pj_ks (pj, "format", "r2flutter-recovered-sbom");
@@ -525,7 +530,7 @@ static char *sbom_dump_text(DartCtx *ctx, RList *components) {
const bool quiet = ctx && ctx->quiet;
RStrBuf *sb = r_strbuf_new ("");
if (!quiet) {
const char *version = dart_version_from_hash (ctx->snapshot_hash);
const char *version = dart_ctx_version (ctx);
r_strbuf_append (sb, "# Dart/Flutter Recovered Components\n");
r_strbuf_append (sb, "# complete: false\n");
r_strbuf_append (sb, "# versions: explicit metadata only; AOT snapshots usually do not serialize pub package versions\n");
+178 -98
View File
@@ -2,8 +2,8 @@
// Dart SDK version detection and layout profiles
//
// Supported Dart SDK versions: 2.10.0 - 3.10.7
// Based on unflutter's version profiles and CID tables.
// Supported Dart SDK versions: 2.10.0 - 3.14.0
// Based on Dart SDK source tags plus external snapshot-hash references.
#include <r_core.h>
#include "../../include/r2flutter/dart_version.h"
@@ -14,111 +14,153 @@ void dart_version_set_verbose(int level) {
G_VERBOSE = level;
}
static bool parse_dart_version(const char *version, int *major, int *minor, int *patch) {
char tail = 0;
return version && sscanf (version, "%d.%d.%d%c", major, minor, patch, &tail) == 3;
}
int dart_version_compare(const char *a, const char *b) {
int av[3], bv[3];
if (!parse_dart_version (a, &av[0], &av[1], &av[2]) || !parse_dart_version (b, &bv[0], &bv[1], &bv[2])) {
return -2;
}
for (int i = 0; i < 3; i++) {
if (av[i] < bv[i]) {
return -1;
}
if (av[i] > bv[i]) {
return 1;
}
}
return 0;
}
// Known snapshot hashes mapped to Dart SDK versions
// Sources: unflutter version.go, reFlutter enginehash.csv, blutter precompiled SDKs
static const struct {
const char *hash;
const char *version;
} known_hashes[] = {
// Dart 2.10.x (Flutter 1.22.x)
// Dart 2.10.x
{ "8ee4ef7a67df9845fba331734198a953", "2.10.0" },
// Dart 2.13.x (Flutter 2.2.x - 2.3.x)
{ "e4a09dbf2bb120fe4674e0576617a0dc", "2.13.0" },
{ "d979fa911b038a8682e98cc8c8569261", "2.10.0" },
// Dart 2.12.x
{ "5b97292b25f0a715613b7a28e0734f77", "2.12.0" },
{ "e036be9d5df68283e1a043e348fa7ff4", "2.12.0" },
// Dart 2.13.x
{ "0c7f1aa25fc597a7b3b9a19ed390afa1", "2.13.0" },
{ "34f6eec64e9371856eaaa278ccf56538", "2.13.0" },
{ "7a5b240780941844bae88eca5dbaa7b8", "2.13.0" },
// Dart 2.14.x (Flutter 2.4.x - 2.5.x)
{ "9cf77f4405212c45daf608e1cd646852", "2.14.0" },
{ "e4a09dbf2bb120fe4674e0576617a0dc", "2.13.0" },
// Dart 2.14.x
{ "659a72e41e3276e882709901c27de33d", "2.14.0" },
// Dart 2.15.x (Flutter 2.6.x - 2.7.x)
{ "f10776149bf76be288def3c2ca73bdc1", "2.15.0" },
{ "9cf77f4405212c45daf608e1cd646852", "2.14.0" },
{ "c03fb294bfdaca2b44c552ad35e52d45", "2.14.0" },
// Dart 2.15.x
{ "24d9d411c2f90c8fbe8907f99e89d4b0", "2.15.0" },
// Dart 2.16.x (Flutter 2.8.x - 2.16.x)
{ "d56742caf7b3b3f4bd2df93a9bbb5503", "2.16.0" },
{ "7b96ef6dda75bdd1d70be8c2f7cb814e", "2.15.0" },
{ "f10776149bf76be288def3c2ca73bdc1", "2.15.0" },
// Dart 2.16.x
{ "3318fe66091c0ffbb64faec39976cb7d", "2.16.0" },
{ "6093345773c65683f60c20c8e62cbe4a", "2.16.0" },
{ "adf563436d12ba0d50ea5beb7f3be1bb", "2.16.0" },
// Dart 2.17.x (Flutter 2.17.x)
{ "d56742caf7b3b3f4bd2df93a9bbb5503", "2.16.0" },
// Dart 2.17.x
{ "0eb277b50d6ce4069261c69150ce52c9", "2.17.0" },
{ "1441d6b13b8623fa7fbf61433abebd31", "2.17.6" },
{ "a0cb0c928b23bc17a26e062b351dc44d", "2.17.6" },
{ "ded6ef11c73fdc638d6ff6d3ad22a67b", "2.17.6" },
// Dart 2.18.x (Flutter 3.3.x)
// Dart 2.18.x
{ "287683014dfbd311f6400c655c97a26d", "2.18.6" },
{ "6a9b5a03a7e784a4558b10c769f188d9", "2.18.0" },
{ "77bd0a83c49bfc555a44a0740f15003a", "2.18.0" },
{ "8e50e448b241be23b9e990094f4dca39", "2.18.0" },
{ "b0e899ec5a90e4661501f0b69e9dd70f", "2.18.0" },
{ "b6d0a1f034d158b0d37b51d559379697", "2.18.0" },
{ "8e50e448b241be23b9e990094f4dca39", "2.18.0" },
{ "6a9b5a03a7e784a4558b10c769f188d9", "2.18.0" },
// Dart 2.19.x (Flutter 3.7.x)
{ "adb4292f3ec25074ca70abcd2d5c7251", "2.19.0" },
{ "f91b8b03bf7f30a5e983fd19b23d978d", "2.18.2" },
// Dart 2.19.x
{ "501ef5cbd64ca70b6b42672346af6a8a", "2.19.0" },
// Dart 3.0.x (Flutter 3.10.x - 3.12.x)
{ "adb4292f3ec25074ca70abcd2d5c7251", "2.19.0" },
{ "afcf461a16dfa5e88f88a2aa68764c99", "2.19.0" },
{ "e727fae79fb45aac639252a9453b785e", "2.19.1" },
// Dart 3.0.x
{ "16ad76edd19b537bf6ea64fdd31977a7", "3.0.5" },
{ "36b0375d284ee2af0d0fffc6e6e48fde", "3.0.5" },
{ "617d044d9faa87a98c29f8aad5d2f220", "3.0.3" },
{ "90b56a561f70cd55e972cb49b79b3d8b", "3.0.5" },
{ "aa64af18e7d086041ac127cc4bc50c5e", "3.0.5" },
{ "36b0375d284ee2af0d0fffc6e6e48fde", "3.0.5" },
{ "16ad76edd19b537bf6ea64fdd31977a7", "3.0.5" },
// Dart 3.1.x (Flutter 3.13.x)
{ "7dbbeeb8ef7b91338640dca3927636de", "3.1.0" },
// Dart 3.2.x (Flutter 3.16.x)
{ "ce5d37cf26ea4faac543cd216f3b96d1", "3.0.0" },
// Dart 3.1.x
{ "5191d02bb1d36a9f8609aa7495894e76", "3.1.0" },
{ "7dbbeeb8ef7b91338640dca3927636de", "3.1.3" },
// Dart 3.2.x
{ "9d6362266b78e4198f784da1a28fd82b", "3.2.0" },
{ "f71c76320d35b65f1164dbaa6d95fe09", "3.2.5" },
// Dart 3.3.x (Flutter 3.19.x)
// Dart 3.3.x
{ "77b867742ae1ace67fbbb9a9a0d0334e", "3.3.0" },
{ "ee1eb666c76a5cb7746faf39d0b97547", "3.3.0" },
// Dart 3.4.x (Flutter 3.22.x)
// Dart 3.4.x
{ "d20a1be77c3d3c41b2a5accaee1ce549", "3.4.3" },
// Dart 3.5.x (Flutter 3.24.x)
{ "fe53c4fdba846005b70514aec3840296", "3.4.0" },
// Dart 3.5.x
{ "71dcc635f2e5dca494525aa13ca9006b", "3.5.0" },
{ "80a49c7111088100a233b2ae788e1f48", "3.5.0" },
{ "cda356e9bae476c70de33809fd92e009", "3.5.0" },
// Dart 3.6.x
{ "2858c2c0920495f00b9bce9edf6a8cd9", "3.6.2" },
// Dart 3.6.x (Flutter 3.27.x)
{ "30c051cb9192f6d6812be3f0b87a6356", "3.6.0" },
{ "f956f595844a2f845a55707faaaa51e4", "3.6.2" },
// Dart 3.7.x (Flutter 3.29.x)
// Dart 3.7.x
{ "bae16a6858d8c0fe2c437b275eb807cd", "3.7.0" },
{ "d91c0e6f35f0eb2e44124e8f42aa44a7", "3.7.0" },
// Dart 3.8.x (Flutter 3.32.x)
// Dart 3.8.x
{ "830f4f59e7969c70b595182826435c19", "3.8.1" },
// Dart 3.9.x (Flutter 3.35.x)
{ "fa07b8a2d866963d1117ecdcea9287c8", "3.8.0" },
// Dart 3.9.x
{ "97ff04a728735e6b6b098bdf983faaba", "3.9.2" },
// Dart 3.10.x (Flutter 3.38.x)
// Dart 3.10.x
{ "1ce86630892e2dca9a8543fdb8ed8e22", "3.10.7" },
// Dart 3.11.x
{ "78da37fed6bf1489361a312568249f3f", "3.11.5" },
// Dart 3.12.x
{ "41be3daaabd524b8aa7423bc24584957", "3.12.0" },
{ "ace654289f5abc240509fc941453ebc5", "3.12.1" },
// Dart 3.13.x
{ "0451907c2eaa8467e848c0067bfe8ed4", "3.13.0" },
// Dart 3.14.x
{ "7a1ea3f6f5cf1089a7f6e55d7f20dbfd", "3.14.0" },
{ NULL, NULL }
};
// Version profiles with CID tables - based on unflutter's version.go
// Range-start version profiles. CID tables are generated in dart_cid.c.
static const DartVerLayout version_profiles[] = {
// Dart 2.10.0 - raw int32 tag, 4 header fields, pre-FunctionType split
{ "", "2.10.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_INT32, 4, 4, 6, 16, 80, 81, 82, 78, 53, 20, 156 },
// Dart 2.13.0 - raw int32 tag, 5 header fields, split canonical
{ "", "2.13.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_INT32, 5, 4, 6, 15, 77, 78, 79, 75, 51, 18, 148 },
// Dart 2.14.0 - shift1 tag, 5 header fields
{ "", "2.14.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, 4, 6, 16, 81, 82, 83, 79, 54, 20, 152 },
// Dart 2.15.0 - shift1 tag, 5 header fields, NativePointer inserted
{ "", "2.15.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, 5, 7, 17, 82, 83, 84, 79, 55, 21, 153 },
// Dart 2.16.0 - shift1 tag, 6 header fields, adds ConstMap/ConstSet
{ "", "2.16.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 6, 5, 7, 17, 84, 85, 86, 81, 55, 21, 154 },
// Dart 2.17.6 - shift1 tag, 6 header fields
{ "", "2.17.6", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 6, 5, 7, 17, 89, 90, 91, 86, 59, 21, 158 },
// Dart 2.18.0 - shift1 tag, 5 header fields, adds SuspendState
{ "", "2.18.0", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, 5, 7, 17, 90, 91, 92, 87, 59, 21, 159 },
// Dart 2.19.0 - shift1 tag, 5 header fields, adds RecordType/Record
{ "", "2.19.0", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, 5, 7, 17, 92, 93, 94, 89, 60, 21, 176 },
// Dart 3.0.5 - shift1 tag, 5 header fields, adds WeakArray
{ "", "3.0.5", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, 5, 7, 18, 93, 94, 95, 90, 61, 22, 177 },
// Dart 3.1.0 - shift1 tag, 5 header fields
{ "", "3.1.0", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, 5, 7, 18, 92, 93, 94, 89, 60, 22, 176 },
// Dart 3.2.5 - shift1 tag, 5 header fields, PoolType swapped
{ "", "3.2.5", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, 5, 7, 18, 92, 93, 94, 89, 60, 22, 176 },
// Dart 3.3.0 - shift1 tag, 5 header fields
{ "", "3.3.0", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, 5, 7, 18, 92, 93, 94, 89, 60, 22, 176 },
// Dart 3.4.3 - ObjectHeader tag, 5 header fields
{ "", "3.4.3", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, 5, 7, 18, 92, 93, 94, 89, 60, 22, 174 },
// Dart 3.5.0 - ObjectHeader tag, 5 header fields
{ "", "3.5.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, 5, 7, 18, 92, 93, 94, 89, 60, 22, 174 },
// Dart 3.6.2 - ObjectHeader tag, 5 header fields
{ "", "3.6.2", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, 5, 7, 18, 93, 94, 95, 90, 61, 23, 175 },
// Dart 3.7.0 - ObjectHeader tag, 5 header fields
{ "", "3.7.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, 5, 7, 18, 93, 94, 95, 90, 61, 23, 175 },
// Dart 3.8.1 - ObjectHeader tag, 5 header fields
{ "", "3.8.1", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, 5, 7, 18, 93, 94, 95, 90, 61, 23, 175 },
// Dart 3.9.2 - ObjectHeader tag, 5 header fields
{ "", "3.9.2", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, 5, 7, 18, 93, 94, 95, 90, 61, 23, 175 },
// Dart 3.10.7 - ObjectHeader tag, 5 header fields
{ "", "3.10.7", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, 5, 7, 18, 93, 94, 95, 90, 61, 23, 175 },
{ { 0 }, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
{ "", "2.10.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_INT32, 4, DART_HEADER_STYLE_210, DART_REF_ENCODING_UNSIGNED, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_DEFERRED_ONLY, 7, 0, true, false, false, false, false }, // Dart 2.10.0
{ "", "2.12.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_INT32, 5, DART_HEADER_STYLE_212, DART_REF_ENCODING_UNSIGNED, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_DEFERRED_ONLY, 7, 0, true, false, false, false, false }, // Dart 2.12.0
{ "", "2.13.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_INT32, 5, DART_HEADER_STYLE_212, DART_REF_ENCODING_UNSIGNED, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_DEFERRED_ONLY, 7, 0, true, false, false, false, false }, // Dart 2.13.0
{ "", "2.14.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, DART_HEADER_STYLE_214, DART_REF_ENCODING_UNSIGNED, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_STATE_DEFERRED, 7, 0, true, false, false, false, false }, // Dart 2.14.0
{ "", "2.15.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, DART_HEADER_STYLE_214, DART_REF_ENCODING_UNSIGNED, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_STATE_DEFERRED, 7, 0, true, false, false, false, false }, // Dart 2.15.0
{ "", "2.16.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 6, DART_HEADER_STYLE_216, DART_REF_ENCODING_UNSIGNED, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_STATE_DEFERRED, 7, 0, false, false, false, false, false }, // Dart 2.16.0
{ "", "2.17.0", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 6, DART_HEADER_STYLE_216, DART_REF_ENCODING_UNSIGNED, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, false }, // Dart 2.17.0
{ "", "2.18.0", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, false }, // Dart 2.18.0
{ "", "2.18.2", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, false }, // Dart 2.18.2
{ "", "2.18.3", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, false }, // Dart 2.18.3
{ "", "2.19.0", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 2.19.0
{ "", "3.0.0", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.0.0
{ "", "3.1.0", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.1.0
{ "", "3.2.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_LOW7_SWAPPED, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.2.0
{ "", "3.3.0", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.3.0
{ "", "3.4.0", 8, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.4.0
{ "", "3.5.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.5.0
{ "", "3.6.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.6.0
{ "", "3.7.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.7.0
{ "", "3.8.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.8.0
{ "", "3.9.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.9.0
{ "", "3.10.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.10.0
{ "", "3.11.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.11.0
{ "", "3.12.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.12.0
{ "", "3.13.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 2, false, true, true, true, true }, // Dart 3.13.0
{ "", "3.14.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 2, false, true, true, true, true }, // Dart 3.14.0
{ { 0 }, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
const char *dart_version_from_hash(const char *hash) {
@@ -133,23 +175,71 @@ const char *dart_version_from_hash(const char *hash) {
return NULL;
}
const DartVerLayout *dart_newest_profile(void) {
const DartVerLayout *newest = version_profiles;
for (int i = 0; version_profiles[i].dart_version; i++) {
newest = &version_profiles[i];
}
return newest;
}
const DartVerLayout *dart_fingerprint_profile(void) {
const DartVerLayout *baseline = version_profiles;
for (int i = 0; version_profiles[i].dart_version; i++) {
if (!version_profiles[i].single_snapshot) {
baseline = &version_profiles[i];
}
}
return baseline;
}
const char *dart_version_source_str(int source) {
switch (source) {
case DART_VERSION_SOURCE_EXACT:
return "exact-hash";
case DART_VERSION_SOURCE_OVERRIDE:
return "override";
case DART_VERSION_SOURCE_PROBE:
return "structural-probe";
case DART_VERSION_SOURCE_FINGERPRINT:
return "fingerprint";
default:
return "unknown";
}
}
int dart_profile_count(void) {
int count = 0;
while (version_profiles[count].dart_version) {
count++;
}
return count;
}
const DartVerLayout *dart_profile_at(int index) {
return index >= 0 && index < dart_profile_count ()? &version_profiles[index]: NULL;
}
const DartVerLayout *dart_profile_from_version(const char *version) {
if (!version) {
return NULL;
}
const DartVerLayout *best = NULL;
for (int i = 0; version_profiles[i].dart_version; i++) {
if (!strcmp (version_profiles[i].dart_version, version)) {
return &version_profiles[i];
const int cmp = dart_version_compare (version, version_profiles[i].dart_version);
if (cmp == -2) {
return NULL;
}
if (cmp < 0) {
break;
}
best = &version_profiles[i];
}
return NULL;
return best? best: version_profiles;
}
DartVerLayout *dart_pick_layout_by_hash(const char *hash) {
DartVerLayout *dvl = calloc (1, sizeof (DartVerLayout));
if (!dvl) {
return NULL;
}
DartVerLayout *dart_layout_from_hash(const char *hash) {
DartVerLayout *dvl = R_NEW0 (DartVerLayout);
const char *version = dart_version_from_hash (hash);
const DartVerLayout *profile = version? dart_profile_from_version (version): NULL;
if (profile) {
@@ -162,26 +252,16 @@ DartVerLayout *dart_pick_layout_by_hash(const char *hash) {
}
return dvl;
}
// Default to v3.9.2 layout for unknown hashes
dvl->compressed_word_size = 4;
dvl->heap_object_tag = 1;
dvl->max_alignment = 16;
dvl->it_cap = 20000;
dvl->tag_style = DART_TAG_STYLE_OBJECT_HEADER;
dvl->header_fields = 5;
dvl->cid_class = 5;
dvl->cid_function = 7;
dvl->cid_code = 18;
dvl->cid_string = 93;
dvl->cid_one_byte_string = 94;
dvl->cid_two_byte_string = 95;
dvl->cid_array = 90;
dvl->cid_mint = 61;
dvl->cid_object_pool = 23;
dvl->num_predefined_cids = 175;
dvl->dart_version = "unknown";
// A single-snapshot profile changes discovery as well as cluster framing, so
// an unknown hash must not inherit one without structural evidence. Keep the
// newest compatible split-snapshot baseline until probing selects a layout.
const DartVerLayout *baseline = dart_fingerprint_profile ();
memcpy (dvl, baseline, sizeof (DartVerLayout));
if (hash && *hash) {
r_str_ncpy (dvl->hash, hash, sizeof (dvl->hash));
}
if (G_VERBOSE > 0) {
fprintf (stderr, "[r2flutter] Unknown snapshot hash, using v3.9.2 defaults (hash=%s)\n", hash? hash: "(null)");
fprintf (stderr, "[r2flutter] Unknown snapshot hash, fingerprinting as compatible profile %s (hash=%s)\n", baseline->dart_version, hash? hash: "(null)");
}
return dvl;
}
+12 -4
View File
@@ -1,9 +1,13 @@
CC ?= gcc
CFLAGS += -Wall -Wextra -O2 -fPIC
CFLAGS += -I../../include -I../../include/r2flutter
CFLAGS += $(shell pkg-config --cflags r_core 2>/dev/null || echo "-I/usr/local/include/libr")
R2_CFLAGS ?= $(shell r2 -H R2_CFLAGS 2>/dev/null)
R2_LDFLAGS ?= $(shell r2 -H R2_LDFLAGS 2>/dev/null)
R2_LIBS ?= $(shell r2 -H R2_LIBS 2>/dev/null)
R2_PLUGDIR ?= $(shell r2 -H R2_USER_PLUGINS)
LDFLAGS += $(shell pkg-config --libs r_core 2>/dev/null || echo "-L/usr/local/lib -lr_core -lr_util -ldl")
CFLAGS += $(R2_CFLAGS)
LDFLAGS += $(R2_LDFLAGS)
R2_PLUGIN_LIBS = $(R2_LIBS)
DEPFLAGS = -MMD -MP
# Detect platform for shared library extension
@@ -11,6 +15,9 @@ UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
EXT_SO = dylib
LDFLAGS += -dynamiclib -undefined dynamic_lookup
# Resolve radare2 symbols from the host. Linking a second installation's
# libr_* dylibs into a core plugin corrupts ABI-compatible host structures.
R2_PLUGIN_LIBS =
else
EXT_SO = so
LDFLAGS += -shared
@@ -25,11 +32,12 @@ CORE_PLUGIN = core_flutter.$(EXT_SO)
# Library
R2FLUTTER_LIB_DIR = ../../build
R2FLUTTER_LIB = $(R2FLUTTER_LIB_DIR)/libr2flutter.a
R2FLUTTER_LIB_TARGET = build/libr2flutter.a
all: $(CORE_PLUGIN)
$(CORE_PLUGIN): $(CORE_OBJ) $(R2FLUTTER_LIB)
$(CC) $(LDFLAGS) -o $@ $(CORE_OBJ) $(R2FLUTTER_LIB)
$(CC) $(LDFLAGS) -o $@ $(CORE_OBJ) $(R2FLUTTER_LIB) $(R2_PLUGIN_LIBS) $(LDLIBS)
core_flutter.o: core_flutter.c
$(CC) $(CFLAGS) $(DEPFLAGS) -c $< -o $@
@@ -38,7 +46,7 @@ flutter_analysis.o: flutter_analysis.c flutter_analysis.h
$(CC) $(CFLAGS) $(DEPFLAGS) -c $< -o $@
$(R2FLUTTER_LIB):
$(MAKE) -C ../.. $(R2FLUTTER_LIB_DIR)/libr2flutter.a
$(MAKE) -C ../.. $(R2FLUTTER_LIB_TARGET)
user-install: $(CORE_PLUGIN)
mkdir -p $(R2_PLUGDIR)
+152 -34
View File
@@ -11,6 +11,11 @@
#define R2FLUTTER_CFG_MAPFILE "r2flutter.mapfile"
#define R2FLUTTER_CFG_NAMEPOOL "r2flutter.namepool"
#define R2FLUTTER_CFG_PROFILE "r2flutter.profile"
#define R2FLUTTER_CFG_STR_LIMIT "r2flutter.str.limit"
#define R2FLUTTER_CFG_STR_MINLEN "r2flutter.str.minlen"
#define R2FLUTTER_CFG_STR_MAXLEN "r2flutter.str.maxlen"
#define R2FLUTTER_CFG_STR_FILTER "r2flutter.str.filter"
typedef struct {
char action;
@@ -20,6 +25,8 @@ typedef struct {
int string_depth;
bool string_refs;
bool help;
bool subcmd_help;
char invalid_flag;
char *obf_map_path;
char *object_spec;
} R2FlutterCmd;
@@ -31,55 +38,105 @@ static bool r2flutter_core_init(RCorePluginSession *cps) {
r_config_node_desc (node, "Flutter obfuscation map JSON path");
node = r_config_set_b (cfg, R2FLUTTER_CFG_NAMEPOOL, false);
r_config_node_desc (node, "Enable heuristic name-pool fallback resolution");
node = r_config_set (cfg, R2FLUTTER_CFG_PROFILE, "");
r_config_node_desc (node, "Override Dart snapshot profile by 32-byte hash or Dart version");
node = r_config_set_i (cfg, R2FLUTTER_CFG_STR_LIMIT, 0);
r_config_node_desc (node, "Maximum number of strings to print (0 = unlimited)");
node = r_config_set_i (cfg, R2FLUTTER_CFG_STR_MINLEN, 0);
r_config_node_desc (node, "Skip printing strings shorter than this length");
node = r_config_set_i (cfg, R2FLUTTER_CFG_STR_MAXLEN, 0);
r_config_node_desc (node, "Skip printing strings longer than this length (0 = unlimited)");
node = r_config_set (cfg, R2FLUTTER_CFG_STR_FILTER, "");
r_config_node_desc (node, "Only print strings matching this extended regex");
r_config_lock (cfg, true);
return true;
}
static bool r2flutter_core_fini(RCorePluginSession *cps) {
#if R2_PLUGIN_INCORE
// removing config vars causes an UAF. this is a temporal workaround
#else
RConfig *cfg = cps->core->config;
r_config_lock (cfg, false);
r_config_rm (cfg, R2FLUTTER_CFG_MAPFILE);
r_config_rm (cfg, R2FLUTTER_CFG_NAMEPOOL);
r_config_rm (cfg, R2FLUTTER_CFG_PROFILE);
r_config_rm (cfg, R2FLUTTER_CFG_STR_LIMIT);
r_config_rm (cfg, R2FLUTTER_CFG_STR_MINLEN);
r_config_rm (cfg, R2FLUTTER_CFG_STR_MAXLEN);
r_config_rm (cfg, R2FLUTTER_CFG_STR_FILTER);
r_config_lock (cfg, true);
#endif
return true;
}
static void r2flutter_apply_config(RCore *core, DartCtx *dctx) {
dctx->obf_map_path = r_config_get (core->config, R2FLUTTER_CFG_MAPFILE);
dctx->use_name_pool = r_config_get_b (core->config, R2FLUTTER_CFG_NAMEPOOL);
dctx->str_limit = r_config_get_i (core->config, R2FLUTTER_CFG_STR_LIMIT);
dctx->str_minlen = (int)r_config_get_i (core->config, R2FLUTTER_CFG_STR_MINLEN);
dctx->str_maxlen = (int)r_config_get_i (core->config, R2FLUTTER_CFG_STR_MAXLEN);
dctx->str_filter = r_config_get (core->config, R2FLUTTER_CFG_STR_FILTER);
const char *profile = r_config_get (core->config, R2FLUTTER_CFG_PROFILE);
if (R_STR_ISNOTEMPTY (profile) && !dart_ctx_set_profile_override (dctx, profile)) {
R_LOG_WARN ("Ignoring unsupported Dart snapshot profile override: %s", profile);
}
}
static void r2flutter_help(RCore *core);
static void r2flutter_subcmd_help(RCore *core, char action) {
switch (action) {
case 'i':
r_cons_printf (core->cons,
"Usage: r2flutter -i[jr*] dump instruction table entries\n"
"| -i plain text output\n"
"| -ie dump Dart code entrypoint; with -* mark instruction snapshots as dword arrays\n"
"| -ij JSON output\n"
"| -ir, -i* r2 commands output\n");
break;
case 'e':
r_cons_printf (core->cons,
"Usage: r2flutter -ie[jr*] dump Dart code entrypoint\n"
"| -ie plain text output\n"
"| -iej JSON output\n"
"| -ier, -ie* r2 commands output; mark instruction snapshots as dword arrays\n");
break;
default:
r2flutter_help (core);
break;
}
}
static void r2flutter_help(RCore *core) {
r_cons_printf (core->cons,
"Usage: r2flutter [-jqrnv] [-l N] [-m file] <action>\n"
"| r2flutter show same -h help message\n"
"| r2flutter -j <act> output JSON for dump actions\n"
"| r2flutter -q <act> compact output; quiet analysis logs\n"
"| r2flutter -r <act> output r2 commands for dump actions\n"
"| r2flutter -n use heuristic name-pool fallback; names may be wrong\n"
"| r2flutter -v increase parser verbosity\n"
"Usage: r2flutter [qjr*] <action>\n"
"| r2flutter -A apply flags, function names and signatures\n"
"| r2flutter -AA also seed anal.gp and apply ObjectPool xrefs\n"
"| r2flutter -AAA also run the full register-tracking scan (slow)\n"
"| r2flutter -c[jr*] dump classes\n"
"| r2flutter -C apply Dart classes, fields, methods and types\n"
"| r2flutter -D prof override Dart snapshot profile by hash or version\n"
"| r2flutter -E[jr*] dump Dart code entrypoint\n"
"| r2flutter -f[jr*] dump recovered functions\n"
"| r2flutter -H[HH] dump Dart AOT snapshot header info\n"
"| r2flutter -i[jr*] dump instruction table entries\n"
"| r2flutter -ie[jr*] dump Dart code entrypoint; with -r/-* mark instruction snapshots as dword arrays\n"
"| r2flutter -l N limit function/instruction-table/xref output\n"
"| r2flutter -m file load Flutter obfuscation map JSON\n"
"| r2flutter -A analyze dart snapshot and apply flags/comments\n"
"| r2flutter -AA analyze with field extraction enabled\n"
"| r2flutter -AAA run Dart-aware code analysis and recover code refs\n"
"| r2flutter -c dump classes\n"
"| r2flutter -C apply Dart classes, fields, methods and types\n"
"| r2flutter -f dump recovered functions\n"
"| r2flutter -H dump Dart AOT snapshot header info\n"
"| r2flutter -HH dump Dart AOT snapshot header and cluster layout\n"
"| 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 -n use heuristic name-pool fallback; names may be wrong\n"
"| r2flutter -O spec decode a tagged pointer, PP slot, or snapshot ref:N\n"
"| r2flutter -p[jr*] 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"
"| r2flutter -T dump string-based type names\n"
"| r2flutter -S[jr*] dump best-effort recovered SBOM/components\n"
"| r2flutter -T[jr*] dump string-based type names\n"
"| r2flutter -x[jr*] dump metadata/data-image xrefs; with -z, include string refs/ax in -r/-*\n"
"| r2flutter -z[jr*] dump reliable ObjectPool-referenced strings\n"
"| r2flutter -zz[jr*] dump all fuzzy/carved strings (-xzz includes refs/ax in -r/-*)\n"
"| r2flutter -h show this help\n"
"| r2flutter -v increase parser verbosity\n"
"| r2flutter -V show version\n"
"| r2flutter -x dump metadata/data-image xrefs; with -z, include string refs/ax in -r\n"
"| r2flutter -z dump reliable ObjectPool-referenced strings (-q prints values only)\n"
"| r2flutter -zz dump all fuzzy/carved strings (-xzz includes refs/ax in -r)\n");
"Modifiers: q=quiet, j=json, r/*=r2 commands\n");
}
static bool r2flutter_analyze(RCore *core, DartCtx *dctx, int quiet) {
@@ -96,7 +153,7 @@ static bool r2flutter_analyze(RCore *core, DartCtx *dctx, int quiet) {
}
}
dart_dumper_apply_to_core (app);
dart_dumper_apply_to_core (app, false);
dart_app_free (app);
return true;
@@ -108,7 +165,7 @@ static bool r2flutter_dump_r2script(RCore *core, DartCtx *dctx) {
return false;
}
dart_app_load_info (app);
char *script = dart_dumper_dump4radare2 (app);
char *script = dart_dumper_dump_r2 (app);
if (script) {
r_cons_print (core->cons, script);
free (script);
@@ -163,6 +220,8 @@ static bool r2flutter_parse_cmd(const char *args, DartCtx *dctx, R2FlutterCmd *c
cmd->string_depth = 0;
cmd->string_refs = false;
cmd->help = false;
cmd->subcmd_help = false;
cmd->invalid_flag = 0;
cmd->obf_map_path = NULL;
cmd->object_spec = NULL;
char *dup = strdup (args);
@@ -177,6 +236,10 @@ static bool r2flutter_parse_cmd(const char *args, DartCtx *dctx, R2FlutterCmd *c
continue;
}
if (word[0] != '-') {
if (!strcmp (word, "?")) {
cmd->subcmd_help = true;
break;
}
continue;
}
const char *flags = word + 1;
@@ -191,6 +254,9 @@ static bool r2flutter_parse_cmd(const char *args, DartCtx *dctx, R2FlutterCmd *c
case 'c':
cmd->action = flag;
break;
case 'E':
cmd->action = 'e';
break;
case 'C':
cmd->action = flag;
break;
@@ -215,6 +281,14 @@ static bool r2flutter_parse_cmd(const char *args, DartCtx *dctx, R2FlutterCmd *c
dctx->dump_it = true;
cmd->action = flag;
break;
case 'e':
if (cmd->action == 'i') {
cmd->action = flag;
} else {
cmd->invalid_flag = flag;
cmd->help = true;
}
break;
case 'O':
{
const char *arg = r2flutter_opt_arg (tail, words, nwords, &i);
@@ -231,6 +305,16 @@ static bool r2flutter_parse_cmd(const char *args, DartCtx *dctx, R2FlutterCmd *c
case 'p':
cmd->action = flag;
break;
case 'D':
{
const char *arg = r2flutter_opt_arg (tail, words, nwords, &i);
if (!arg || !dart_ctx_set_profile_override (dctx, arg)) {
cmd->help = true;
break;
}
j += strlen (tail);
break;
}
case 'j':
cmd->fmt = 'j';
break;
@@ -265,6 +349,7 @@ static bool r2flutter_parse_cmd(const char *args, DartCtx *dctx, R2FlutterCmd *c
dctx->quiet = 1;
break;
case 'r':
case '*':
cmd->fmt = 'r';
break;
case 'R':
@@ -297,11 +382,16 @@ static bool r2flutter_parse_cmd(const char *args, DartCtx *dctx, R2FlutterCmd *c
cmd->action = flag;
cmd->string_depth++;
break;
case '?':
cmd->subcmd_help = true;
cmd->help = true;
break;
default:
cmd->invalid_flag = flag;
cmd->help = true;
break;
}
if (cmd->help || (R_STR_ISNOTEMPTY (tail) && (flag == 'l' || flag == 'm' || flag == 'O'))) {
if (cmd->help || (R_STR_ISNOTEMPTY (tail) && (flag == 'l' || flag == 'm' || flag == 'O' || flag == 'D'))) {
break;
}
}
@@ -323,13 +413,28 @@ static bool r2flutter_run_cmd(RCore *core, DartCtx *dctx, const R2FlutterCmd *cm
r2flutter_help (core);
return true;
case 'A':
// Tiered analysis:
// -A flags, function names and signatures (fast)
// -AA + anal.gp seeding and the ObjectPool xref graph (data-image
// string references), without the expensive per-function scan
// -AAA + full register-tracking scan (PP-slot comments, field/arg
// tracking, null/bool annotations, indirect-call resolution)
if (cmd->analysis_depth >= 2) {
dctx->dump_fields = 1;
}
// Seed anal.gp/gpseed/roregs with the ObjectPool base for every -A depth
// so plain-r2 analysis can resolve [x27, off] pool loads.
r2flutter_setup_pp_gp (core, dctx);
if (cmd->analysis_depth >= 3) {
r2flutter_analysis_run (core, dctx, dctx->quiet);
} else {
r2flutter_analyze (core, dctx, dctx->quiet);
if (cmd->analysis_depth >= 2) {
int graph_xrefs = r2flutter_apply_pool_xrefs (core, dctx);
if (!dctx->quiet) {
R_LOG_INFO ("r2flutter: applied %d ObjectPool xrefs", graph_xrefs);
}
}
}
return true;
case 'C':
@@ -342,6 +447,9 @@ static bool r2flutter_run_cmd(RCore *core, DartCtx *dctx, const R2FlutterCmd *cm
dctx->dump_fields = 1;
out = dart_pool_dump_classes (dctx, cmd->fmt);
break;
case 'e':
out = dart_pool_dump_entry (dctx, cmd->fmt);
break;
case 'f':
out = r2flutter_dump_functions (core, dctx, cmd->fmt);
break;
@@ -356,13 +464,13 @@ static bool r2flutter_run_cmd(RCore *core, DartCtx *dctx, const R2FlutterCmd *cm
out = dart_pool_dump_object (dctx, cmd->object_spec, cmd->fmt);
break;
case 'p':
out = dart_pool_dump_pp (dctx, cmd->fmt);
out = dart_pool_dump_pool (dctx, cmd->fmt);
break;
case 'R':
r2flutter_dump_r2script (core, dctx);
return true;
case 'S':
out = dart_pool_dump_sbom (dctx, NULL, cmd->fmt);
out = dart_pool_dump_sbom (dctx, core->io && core->io->desc? core->io->desc->name: NULL, cmd->fmt);
break;
case 'z':
out = cmd->string_depth >= 2? dart_pool_dump_strings_fuzzy (dctx, cmd->fmt): dart_pool_dump_strings (dctx, cmd->fmt);
@@ -416,7 +524,16 @@ static bool r_cmd_r2flutter_call(RCorePluginSession *cps, const char *input) {
bool ret = true;
R2FlutterCmd cmd = { 0 };
if (!r2flutter_parse_cmd (args, &dctx, &cmd) || cmd.help) {
r2flutter_help (core);
if (cmd.subcmd_help) {
r2flutter_subcmd_help (core, cmd.action);
} else if (cmd.invalid_flag) {
r_core_return_invalid_command (core, "r2flutter", cmd.invalid_flag);
ret = false;
} else {
r2flutter_help (core);
}
} else if (cmd.subcmd_help) {
r2flutter_subcmd_help (core, cmd.action);
} else {
ret = r2flutter_run_cmd (core, &dctx, &cmd);
}
@@ -425,8 +542,8 @@ static bool r_cmd_r2flutter_call(RCorePluginSession *cps, const char *input) {
free (cmd.object_spec);
return ret;
}
r2flutter_help (core);
return true;
r_core_return_invalid_command (core, "r2flutter", ch0);
return false;
}
RCorePlugin r_core_plugin_r2flutter = {
@@ -447,6 +564,7 @@ R_API RLibStruct radare_plugin = {
.type = R_LIB_TYPE_CORE,
.data = &r_core_plugin_r2flutter,
.version = R2_VERSION,
.abiversion = R2_ABIVERSION
.abiversion = R2_ABIVERSION,
.pkgname = "r2flutter"
};
#endif
+673 -76
View File
@@ -19,8 +19,10 @@ typedef struct {
const DartFieldInfo *field;
const DartStringInfo *string;
const char *type_name;
ut64 pp_off;
ut64 pp_off; // this register is a pool POINTER: PP base + pp_off (from x27 / add)
bool has_pp_off;
ut64 pp_slot; // this register holds the VALUE loaded from PP slot pp_slot (object)
bool has_pp_slot;
} FlutterTrackedValue;
typedef struct {
@@ -41,6 +43,7 @@ typedef struct {
ut64 imm;
bool has_mem;
bool has_imm;
int etype; // effective op type (x86-64 `mov reg,[mem]` reclassified as LOAD/STORE)
} FlutterOpInfo;
typedef struct {
@@ -54,6 +57,34 @@ typedef struct {
ut64 comments;
} FlutterAnalStats;
typedef struct {
ut64 string_addr;
char *string_value;
bool indirect;
} FlutterPpStringRef;
typedef struct {
HtUP *by_pp_off;
} FlutterPpStringMap;
// Non-string resolution of PP slots (functions, classes, code, ...), so that
// analysis comments can name what a slot holds instead of a bare offset, and
// code/function slots can gain a real xref to their target.
typedef struct {
char *kind;
char *name;
ut64 code_index;
ut64 value_addr;
} FlutterPpRef;
typedef struct {
HtUP *by_pp_off; // pp_off -> FlutterPpRef
HtUP *addr_by_code_index; // code_index -> normalized code address
ut64 pp_base; // ObjectPool base address, so a [PP, off] load can xref pp_base+off
bool is_x64; // x86-64 keeps the heap tag in r15, so a [r15, off] offset is one
// less than the arm64/collector offset; add it back before lookup/xref.
} FlutterPpRefMap;
typedef struct {
HtUP *seen;
RVecFlutterEntry *entries;
@@ -63,6 +94,29 @@ static void flutter_stack_slot_free(void *p) {
free (p);
}
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_bucket_free, NULL);
}
static void flutter_pp_string_map_fini(FlutterPpStringMap *map) {
if (!map) {
return;
}
ht_up_free (map->by_pp_off);
memset (map, 0, sizeof (*map));
}
static void flutter_model_fini(FlutterAnalModel *model) {
dart_recovery_model_fini (model);
}
@@ -105,8 +159,44 @@ static bool flutter_reg_is_stack(const char *name) {
return !strcmp (name, "x15") || !strcmp (name, "sp") || !strcmp (name, "x29") || !strcmp (name, "fp");
}
// Dart's pool pointer (PP): x27 on arm64, r15 on x86-64.
static bool flutter_reg_is_pp(const char *name) {
return !strcmp (name, "x27");
return !strcmp (name, "x27") || !strcmp (name, "r15");
}
// x22 is Dart AOT's NULL_REG: it caches Object::null() for the whole program.
static bool flutter_reg_is_null(const char *name) {
return !strcmp (name, "x22");
}
// The VM lays null, true and false out as adjacent heap objects
// (runtime/vm/pointer_tagging.h): true = null + 4*word_size, false = null +
// 6*word_size. On arm64 AOT the object-alignment word is the 8-byte target word
// regardless of pointer compression, so these are 0x20 / 0x30 on every Dart
// version (verified on cws=8 and cws=4 snapshots).
#define DART_NULL_TRUE_OFFSET 0x20
#define DART_NULL_FALSE_OFFSET 0x30
// Map an x86-64 general-purpose register name (64/32-bit) to a stable slot. Dart
// AOT on x64 uses the same tracker; names never collide with arm64 (x/w vs
// r/rXX), and a single binary is one arch, so sharing the 0..15 slot space is
// safe.
static int flutter_x64_reg_index(const char *name) {
static const char *const names[16] = {
"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi",
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"
};
static const char *const names32[16] = {
"eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi",
"r8d", "r9d", "r10d", "r11d", "r12d", "r13d", "r14d", "r15d"
};
int i;
for (i = 0; i < 16; i++) {
if (!strcmp (name, names[i]) || !strcmp (name, names32[i])) {
return i;
}
}
return -1;
}
static int flutter_reg_index(const char *name) {
@@ -125,6 +215,9 @@ static int flutter_reg_index(const char *name) {
return idx;
}
}
if (name[0] == 'r' || name[0] == 'e') {
return flutter_x64_reg_index (name);
}
return -1;
}
@@ -214,68 +307,161 @@ static bool flutter_parse_shifted_imm(const char *line, ut64 *imm) {
if (!lsl) {
return false;
}
const char *hash = strchr (lsl, '#');
if (!hash) {
// RArchValue.imm may already include the shift (depending on the ARM
// decoder). Read the unshifted third operand before applying it once.
const char *arg = strchr (line, ',');
arg = arg? strchr (arg + 1, ','): NULL;
if (!arg || arg >= lsl) {
return false;
}
int shift = atoi (hash + 1);
if (shift <= 0 || shift >= 63) {
arg++;
while (isspace ((ut8)*arg) || *arg == '#') {
arg++;
}
char *end;
ut64 value = strtoull (arg, &end, 0);
if (end == arg) {
return false;
}
*imm <<= shift;
const char *p = lsl + 3;
while (isspace ((ut8)*p)) {
p++;
}
if (*p == '#') {
p++;
}
if (!isdigit ((ut8)*p)) {
return false;
}
ut64 shift = strtoull (p, NULL, 0);
if (shift >= 64) {
return false;
}
*imm = value << shift;
return true;
}
static void flutter_opinfo_add_reg(FlutterOpInfo *info, const char *reg) {
if (R_STR_ISEMPTY (reg) || info->n_regs >= (int)R_ARRAY_SIZE (info->regs)) {
return;
}
r_str_ncpy (info->regs[info->n_regs], reg, sizeof (info->regs[0]));
info->n_regs++;
}
static void flutter_opinfo_add_value(FlutterOpInfo *info, const RArchValue *value, bool add_reg) {
if (!value) {
return;
}
if (add_reg && R_STR_ISNOTEMPTY (value->reg)) {
flutter_opinfo_add_reg (info, value->reg);
}
if (value->imm) {
info->imm = (ut64)value->imm;
info->has_imm = true;
}
}
static void flutter_parse_mem_operand(const char *line, FlutterOpInfo *info) {
const char *p = R_STR_ISNOTEMPTY (line)? strchr (line, '['): NULL;
if (!p) {
return;
}
p++;
while (isspace ((ut8)*p)) {
p++;
}
const char *reg = p;
while (isalnum ((ut8)*p) || *p == '_') {
p++;
}
const size_t reg_len = p - reg;
if (!reg_len || reg_len >= sizeof (info->mem_base)) {
return;
}
memcpy (info->mem_base, reg, reg_len);
info->mem_base[reg_len] = '\0';
info->has_mem = true;
while (isspace ((ut8)*p)) {
p++;
}
// arm64 writes `[x27, 0x610]`, x86-64 writes `[r15 + 0x610]`.
if (*p != ',' && *p != '+') {
return;
}
p++;
while (isspace ((ut8)*p) || *p == '#') {
p++;
}
char *end = NULL;
st64 disp = (st64)strtoll (p, &end, 0);
if (end != p) {
info->mem_disp = disp;
}
}
static void flutter_parse_first_operand_reg(const char *line, FlutterOpInfo *info) {
const char *p = R_STR_ISNOTEMPTY (line)? strchr (line, ' '): NULL;
if (!p) {
return;
}
while (isspace ((ut8)*p)) {
p++;
}
const char *reg = p;
while (isalnum ((ut8)*p) || *p == '_') {
p++;
}
const size_t reg_len = p - reg;
if (!reg_len || reg_len >= sizeof (info->regs[0])) {
return;
}
char name[sizeof (info->regs[0])];
memcpy (name, reg, reg_len);
name[reg_len] = '\0';
flutter_opinfo_add_reg (info, name);
}
static bool flutter_parse_opinfo(const RAnalOp *op, FlutterOpInfo *info) {
if (!op || !info) {
return false;
}
memset (info, 0, sizeof (*info));
const char *opex = r_strbuf_get ((RStrBuf *)&op->opex);
if (R_STR_ISEMPTY (opex)) {
return false;
}
char *opexdup = strdup (opex);
RJson *j = r_json_parse (opexdup);
if (!j) {
free (opexdup);
return false;
}
const RJson *ops = r_json_get (j, "operands");
for (size_t i = 0;; i++) {
const RJson *item = r_json_item (ops, i);
if (!item) {
break;
}
const char *type = r_json_get_str (item, "type");
if (R_STR_ISEMPTY (type)) {
continue;
}
if (!strcmp (type, "reg")) {
const char *value = r_json_get_str (item, "value");
if (R_STR_ISNOTEMPTY (value) && info->n_regs < 4) {
r_str_ncpy (info->regs[info->n_regs], value, sizeof (info->regs[0]));
info->n_regs++;
}
continue;
}
if (!strcmp (type, "mem")) {
const char *base = r_json_get_str (item, "base");
if (R_STR_ISNOTEMPTY (base)) {
r_str_ncpy (info->mem_base, base, sizeof (info->mem_base));
info->has_mem = true;
}
info->mem_disp = (st64)r_json_get_num (item, "disp");
continue;
}
if (!strcmp (type, "imm")) {
info->imm = r_json_get_num (item, "value");
info->has_imm = true;
int type = op->type & R_ANAL_OP_TYPE_MASK;
// x86-64 types a memory load/store as MOV; reclassify `mov reg, [mem]` as a
// LOAD and `mov [mem], reg` as a STORE by where the brackets sit relative to
// the operand separator, so the arm64-shaped load/store logic applies.
if (type == R_ANAL_OP_TYPE_MOV && R_STR_ISNOTEMPTY (op->mnemonic)) {
const char *br = strchr (op->mnemonic, '[');
const char *comma = strchr (op->mnemonic, ',');
if (br && comma) {
type = br > comma? R_ANAL_OP_TYPE_LOAD: R_ANAL_OP_TYPE_STORE;
}
}
r_json_free (j);
free (opexdup);
if (info->has_imm && R_STR_ISNOTEMPTY (op->mnemonic)) {
info->etype = type;
const RArchValue *value = RVecRArchValue_at (&op->dsts, 0);
if (type != R_ANAL_OP_TYPE_STORE) {
flutter_opinfo_add_value (info, value, true);
}
const RArchValue *src;
R_VEC_FOREACH (&op->srcs, src) {
const bool add_reg = type != R_ANAL_OP_TYPE_LOAD;
flutter_opinfo_add_value (info, src, add_reg);
}
if (type == R_ANAL_OP_TYPE_LOAD || type == R_ANAL_OP_TYPE_STORE) {
flutter_parse_mem_operand (op->mnemonic, info);
}
if (type == R_ANAL_OP_TYPE_STORE && info->n_regs == 0) {
flutter_parse_first_operand_reg (op->mnemonic, info);
}
if (!info->has_imm && type == R_ANAL_OP_TYPE_ADD && op->val) {
info->imm = op->val;
info->has_imm = true;
}
if (info->n_regs == 0 && (type == R_ANAL_OP_TYPE_RCALL || type == R_ANAL_OP_TYPE_UCALL || type == R_ANAL_OP_TYPE_ICALL || type == R_ANAL_OP_TYPE_IRCALL)) {
flutter_parse_first_operand_reg (op->mnemonic, info);
}
if (type == R_ANAL_OP_TYPE_ADD && info->has_imm && R_STR_ISNOTEMPTY (op->mnemonic)) {
(void)flutter_parse_shifted_imm (op->mnemonic, &info->imm);
}
return info->n_regs > 0 || info->has_mem || info->has_imm;
@@ -337,13 +523,44 @@ static void flutter_add_ref(RCore *core, ut64 from, ut64 to, RAnalRefType type)
r_anal_xrefs_set (core->anal, from, to, type);
}
// Annotate the Dart NULL_REG (x22) idioms so booleans and null checks read
// clearly: `add d, x22, #0x20/#0x30` materialises true/false, `mov d, x22`
// loads null, and a compare against x22 is a null check.
static void flutter_annotate_null_reg(RCore *core, ut64 at, int optype, const FlutterOpInfo *info, FlutterAnalStats *stats) {
if (!core || !info) {
return;
}
if (optype == R_ANAL_OP_TYPE_ADD && info->n_regs >= 2 && info->has_imm && flutter_reg_is_null (info->regs[1])) {
const char *b = info->imm == DART_NULL_TRUE_OFFSET? "true": info->imm == DART_NULL_FALSE_OFFSET? "false": NULL;
if (b) {
char *msg = r_str_newf ("dart: %s", b);
flutter_append_comment (core, at, msg, stats);
free (msg);
}
return;
}
if (optype == R_ANAL_OP_TYPE_MOV && info->n_regs >= 2 && flutter_reg_is_null (info->regs[1])) {
flutter_append_comment (core, at, "dart: null", stats);
return;
}
if (optype == R_ANAL_OP_TYPE_CMP) {
int i;
for (i = 0; i < info->n_regs; i++) {
if (flutter_reg_is_null (info->regs[i])) {
flutter_append_comment (core, at, "dart: compare with null", stats);
return;
}
}
}
}
static bool flutter_model_load(FlutterAnalModel *model, RCore *core, DartCtx *dctx) {
if (!model || !core || !dctx) {
return false;
}
DartCtx ctx = *dctx;
ctx.core = core;
dart_recovery_model_load (&ctx, model, DART_RECOVERY_STRINGS | DART_RECOVERY_CLASSES | DART_RECOVERY_CLASS_FIELDS | DART_RECOVERY_METHOD_INDEX);
dart_recovery_model_load (&ctx, model, DART_RECOVERY_STRINGS | DART_RECOVERY_CLASSES | DART_RECOVERY_CLASS_FIELDS | DART_RECOVERY_METHOD_INDEX | DART_RECOVERY_IT);
if (!model->strings || !model->classes) {
flutter_model_fini (model);
return false;
@@ -351,6 +568,183 @@ static bool flutter_model_load(FlutterAnalModel *model, RCore *core, DartCtx *dc
return true;
}
static void flutter_pp_string_map_add(FlutterPpStringMap *map, const ModernPoolStrRef *info) {
if (!map || !map->by_pp_off || !info || R_STR_ISEMPTY (info->string_value) || !info->string_addr) {
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);
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) {
flutter_pp_string_map_add (user, info);
}
static bool flutter_collect_pp_strings_from_snapshot(DartCtx *ctx, ut64 snapshot_base, HtUP *wanted, FlutterPpStringMap *map) {
if (!ctx || !snapshot_base || !map) {
return false;
}
DartSnapshotHeader sh = { 0 };
if (!dart_snapshot_header_read (ctx, snapshot_base, &sh) || !sh.ok) {
return false;
}
const ModernReq req = {
.ctx = ctx,
.cluster_start = sh.cluster_start,
.cluster_end = snapshot_base + sh.total_len,
.num_clusters = sh.nc,
.num_base_objects = sh.nb,
};
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) {
if (!ctx || !map || find_snapshots (ctx) != 0) {
return;
}
// When we init the layout here it may live in layout_tmp (a stack buffer)
// with dart_ctx_init_layout returning NULL. We must still clear ctx->layout
// on the way out, or a later consumer would dereference a dangling pointer.
DartVerLayout layout_tmp;
bool layout_ours = !ctx->layout;
DartVerLayout *layout_owned = layout_ours? dart_ctx_init_layout (ctx, &layout_tmp): NULL;
if (!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_ours) {
dart_ctx_fini_layout (ctx, layout_owned);
}
}
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;
}
static void flutter_pp_ref_free(HtUPKv *kv) {
FlutterPpRef *ref = kv->value;
free (ref->kind);
free (ref->name);
free (ref);
}
static void flutter_pp_ref_map_init(FlutterPpRefMap *map) {
memset (map, 0, sizeof (*map));
map->by_pp_off = ht_up_new (NULL, flutter_pp_ref_free, NULL);
map->addr_by_code_index = ht_up_new0 ();
}
static void flutter_pp_ref_map_fini(FlutterPpRefMap *map) {
if (!map) {
return;
}
ht_up_free (map->by_pp_off);
ht_up_free (map->addr_by_code_index);
memset (map, 0, sizeof (*map));
}
static void flutter_pp_ref_cb(const ModernPoolRefInfo *info, void *user) {
FlutterPpRefMap *map = user;
if (!map || !map->by_pp_off || !info || R_STR_ISEMPTY (info->kind)) {
return;
}
// Strings are handled by the dedicated string map; skip them here so the
// two maps stay cheap and the string path keeps priority.
if (!strcmp (info->kind, "string") || ht_up_find (map->by_pp_off, info->pp_offset, NULL)) {
return;
}
FlutterPpRef *ref = R_NEW0 (FlutterPpRef);
ref->kind = strdup (info->kind);
ref->name = R_STR_ISNOTEMPTY (info->name)? strdup (info->name): NULL;
ref->code_index = info->code_index;
ref->value_addr = info->value_addr;
ht_up_insert (map->by_pp_off, info->pp_offset, ref);
}
static void flutter_pp_ref_map_load(DartCtx *ctx, FlutterPpRefMap *map) {
if (!ctx || !map || find_snapshots (ctx) != 0) {
return;
}
// See flutter_pp_string_map_load: clear ctx->layout on exit when we init it,
// since it may point at the stack buffer layout_tmp.
DartVerLayout layout_tmp;
bool layout_ours = !ctx->layout;
DartVerLayout *layout_owned = layout_ours? dart_ctx_init_layout (ctx, &layout_tmp): NULL;
// The ObjectPool lives in the isolate snapshot; fall back to the VM snapshot
// like flutter_pp_string_map_load does.
const ut64 bases[2] = { ctx->iso_data, ctx->vm_data };
for (int i = 0; i < 2; i++) {
ut64 snapshot_base = bases[i];
DartSnapshotHeader sh = { 0 };
if (!snapshot_base || !dart_snapshot_header_read (ctx, snapshot_base, &sh) || !sh.ok) {
continue;
}
const ModernReq req = {
.ctx = ctx,
.cluster_start = sh.cluster_start,
.cluster_end = snapshot_base + sh.total_len,
.num_clusters = sh.nc,
.num_base_objects = sh.nb,
};
if (modern_collect_direct_pool_refs (&req, flutter_pp_ref_cb, map)) {
break;
}
}
// Map code_index -> entrypoint address via the instruction table, so a
// code/function slot can be xref'd to its real target.
RVecDartInstructionTableEntry *entries = dart_pool_extract_instruction_table (ctx);
if (entries) {
DartInstructionTableEntry *entry;
R_VEC_FOREACH (entries, entry) {
if (!entry->has_code || !entry->address) {
continue;
}
// Strip the low Thumb/tag bit, matching dart_app's code-addr normalization.
ut64 addr = (entry->address & 1ULL)? entry->address - 1: entry->address;
ht_up_update (map->addr_by_code_index, entry->code_index, (void *)addr);
}
dart_instruction_table_list_free (entries);
}
// The ObjectPool base, so a [PP, off] load can xref the pool slot at
// pp_base + off. r2flutter_setup_pp_gp already resolved it into anal.gp.
if (ctx->core) {
map->pp_base = (ut64)r_config_get_i (ctx->core->config, "anal.gp");
const char *arch = r_config_get (ctx->core->config, "asm.arch");
const int bits = (int)r_config_get_i (ctx->core->config, "asm.bits");
map->is_x64 = arch && bits == 64 && (!strcmp (arch, "x86") || !strcmp (arch, "x64"));
}
if (layout_ours) {
dart_ctx_fini_layout (ctx, layout_owned);
}
}
static const FlutterPpRef *flutter_pp_ref_map_get(FlutterPpRefMap *map, ut64 pp_off) {
return map && map->by_pp_off? ht_up_find (map->by_pp_off, pp_off, NULL): NULL;
}
static ut64 flutter_pp_ref_code_addr(FlutterPpRefMap *map, ut64 code_index) {
if (!map || !map->addr_by_code_index || code_index == UT64_MAX) {
return 0;
}
bool found = false;
ut64 addr = (ut64)ht_up_find (map->addr_by_code_index, code_index, &found);
return found? addr: 0;
}
static void flutter_apply_model_to_core(FlutterAnalModel *model, RCore *core, FlutterAnalStats *stats) {
if (!model || !core) {
return;
@@ -510,6 +904,17 @@ static RVecFlutterEntry *flutter_collect_entries(RCore *core, const DartApp *app
flutter_collect_entries_from_flags (seen, entries, core);
flutter_collect_entries_from_app (seen, entries, app);
flutter_collect_entries_from_model (seen, entries, model);
// Entries before first_entry_with_code still contain executable Dart code;
// only their Code objects were discarded by the precompiler.
if (model->it_entries) {
DartInstructionTableEntry *entry;
R_VEC_FOREACH (model->it_entries, entry) {
if (entry->address && !ht_up_find (seen, entry->address, NULL)) {
RVecFlutterEntry_push_back (entries, &entry->address);
ht_up_insert (seen, entry->address, entries);
}
}
}
ht_up_free (seen);
return entries;
}
@@ -531,6 +936,18 @@ static void flutter_ensure_functions(RCore *core, RVecFlutterEntry *entries) {
if (restore_log_level) {
r_log_set_level (R_LOG_LEVEL_INFO);
}
const bool prev_trycatch = dart_core_trycatch_suspend (core);
// `af` only needs to recover each function's basic blocks here; the Dart
// scan tracks its own args/fields and resolves calls from the snapshot, so
// r2's variable analysis, call-following and jump-table recovery are pure
// overhead - disabling them roughly halves this af storm, the dominant cost
// of -AAA, without changing the recovered blocks.
bool prev_calls = r_config_get_b (core->config, "anal.calls");
bool prev_vars = r_config_get_b (core->config, "anal.vars");
bool prev_jmptbl = r_config_get_b (core->config, "anal.jmp.tbl");
r_config_set_b (core->config, "anal.calls", false);
r_config_set_b (core->config, "anal.vars", false);
r_config_set_b (core->config, "anal.jmp.tbl", false);
ut64 *addrp;
R_VEC_FOREACH (entries, addrp) {
if (!*addrp) {
@@ -540,6 +957,10 @@ static void flutter_ensure_functions(RCore *core, RVecFlutterEntry *entries) {
r_core_cmdf (core, "af @ 0x%" PFMT64x, *addrp);
}
}
r_config_set_b (core->config, "anal.calls", prev_calls);
r_config_set_b (core->config, "anal.vars", prev_vars);
r_config_set_b (core->config, "anal.jmp.tbl", prev_jmptbl);
dart_core_trycatch_restore (core, prev_trycatch);
if (restore_log_level) {
r_log_set_level (old_level);
}
@@ -740,7 +1161,41 @@ 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) {
// Annotate a non-string PP slot: name what the slot holds and, when it points
// at a code/data object we can locate, add a navigable xref to the target.
// Returns true when the slot resolved to a known entry.
static bool flutter_annotate_pp_ref(RCore *core, FlutterPpRefMap *pp_refs, ut64 at, ut64 pp_off, bool is_call, FlutterAnalStats *stats) {
const FlutterPpRef *ref = flutter_pp_ref_map_get (pp_refs, pp_off);
if (!ref) {
return false;
}
ut64 target = flutter_pp_ref_code_addr (pp_refs, ref->code_index);
const char *label = NULL;
if (target) {
RFlagItem *fi = r_flag_get_at (core->flags, target, false);
label = (fi && R_STR_ISNOTEMPTY (fi->name))? fi->name: ref->name;
flutter_add_ref (core, at, target, is_call? R_ANAL_REF_TYPE_CALL: (R_ANAL_REF_TYPE_DATA | R_ANAL_REF_TYPE_READ));
} else if (ref->value_addr) {
label = ref->name;
flutter_add_ref (core, at, ref->value_addr, R_ANAL_REF_TYPE_DATA | R_ANAL_REF_TYPE_READ);
} else {
label = ref->name;
}
const char *what = is_call? "indirect call via PP+0x": "PP slot +0x";
char *msg;
if (R_STR_ISNOTEMPTY (label)) {
char *escaped = r_str_escape_utf8 (label, false, true);
msg = r_str_newf ("dart: %s%" PFMT64x " %s %s", what, pp_off, ref->kind, escaped);
free (escaped);
} else {
msg = r_str_newf ("dart: %s%" PFMT64x " %s", what, pp_off, ref->kind);
}
flutter_append_comment (core, at, msg, stats);
free (msg);
return true;
}
static bool flutter_process_pp_load(RCore *core, FlutterAnalState *state, FlutterPpStringMap *pp_strings, FlutterPpRefMap *pp_refs, ut64 at, const FlutterOpInfo *info, FlutterAnalStats *stats) {
if (!state || !info || info->n_regs < 1 || !info->has_mem) {
return false;
}
@@ -758,14 +1213,44 @@ static bool flutter_process_pp_load(RCore *core, FlutterAnalState *state, ut64 a
}
pp_off = state->regs[base].pp_off + (ut64)info->mem_disp;
}
// On x86-64 the heap tag is carried in r15, so the instruction offset is one
// less than the collector/arm64 slot offset. Add it back to get the
// canonical offset used for resolution maps and the slot address.
const ut64 slot_off = pp_off + (pp_refs && pp_refs->is_x64? 1: 0);
// The destination now holds the pool OBJECT at pp_off, not a PP pointer.
// Marking it has_pp_off would make a later [dst, field] load look like a
// bogus pool slot (e.g. a Code entry-point deref read as PP+off+7).
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);
state->regs[dst].has_pp_slot = true;
state->regs[dst].pp_slot = slot_off;
// Link the instruction to the ObjectPool slot it reads (pp_base + off), so
// `axt` on the slot shows the loading code; the slot's own ref to its value
// (applied by -AA) then chains through to the string/object. Works on any
// arch since pp_base + off is the slot address regardless of index scheme.
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);
}
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 " %sstring \"%s\"", pp_off, string_ref->indirect? "contains ": "", escaped);
flutter_append_comment (core, at, msg, stats);
free (msg);
free (escaped);
}
stats->pp_refs++;
return true;
}
@@ -821,7 +1306,29 @@ static bool flutter_process_field_store(RCore *core, FlutterAnalModel *model, Fl
return true;
}
static void flutter_process_indirect_call(RCore *core, FlutterAnalState *state, ut64 at, const FlutterOpInfo *info, FlutterAnalStats *stats) {
// Keep the pool-slot taint across a dereference of a pool object, such as the
// `ldr xN, [xN, #entry_point]` that extracts a Code object's entry point right
// before an indirect call. Without this the slot association is lost.
static bool flutter_process_pp_slot_deref(FlutterAnalState *state, const FlutterOpInfo *info) {
if (!state || !info || info->n_regs < 1 || !info->has_mem) {
return false;
}
int base = flutter_reg_index (info->mem_base);
if (base < 0 || !state->regs[base].has_pp_slot) {
return false;
}
int dst = flutter_reg_index (info->regs[0]);
if (dst < 0) {
return false;
}
ut64 slot = state->regs[base].pp_slot;
memset (&state->regs[dst], 0, sizeof (state->regs[dst]));
state->regs[dst].has_pp_slot = true;
state->regs[dst].pp_slot = slot;
return true;
}
static void flutter_process_indirect_call(RCore *core, FlutterAnalState *state, FlutterPpRefMap *pp_refs, ut64 at, const FlutterOpInfo *info, FlutterAnalStats *stats) {
if (!core || !state || !info || info->n_regs < 1) {
return;
}
@@ -829,15 +1336,23 @@ static void flutter_process_indirect_call(RCore *core, FlutterAnalState *state,
if (reg < 0) {
return;
}
if (state->regs[reg].has_pp_off) {
char *msg = r_str_newf ("dart: indirect call via PP+0x%" PFMT64x, state->regs[reg].pp_off);
flutter_append_comment (core, at, msg, stats);
free (msg);
// The call target is the object held in the register (a Code/Function
// loaded from a PP slot), possibly after an entry-point deref. Use the slot
// that value came from, not a PP-pointer offset.
if (state->regs[reg].has_pp_slot) {
ut64 slot = state->regs[reg].pp_slot;
// A call through a resolved code slot gets a real CALL xref and the
// target name; otherwise keep the plain via-PP note.
if (!flutter_annotate_pp_ref (core, pp_refs, at, slot, true, stats)) {
char *msg = r_str_newf ("dart: indirect call via PP+0x%" PFMT64x, slot);
flutter_append_comment (core, at, msg, stats);
free (msg);
}
stats->pp_refs++;
}
}
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, FlutterPpRefMap *pp_refs, RAnalFunction *fcn, FlutterAnalStats *stats) {
if (!core || !model || !fcn || !stats) {
return;
}
@@ -855,9 +1370,15 @@ static void flutter_scan_function(RCore *core, FlutterAnalModel *model, RAnalFun
}
HtUP *seen_ops = ht_up_new0 ();
RListIter *it;
RAnalBlock *bb;
#if R2_ABIVERSION >= 168
RAnalBlock **it;
R_VEC_FOREACH (&fcn->bbs, it) {
bb = *it;
#else
RListIter *it;
r_list_foreach (fcn->bbs, it, bb) {
#endif
if (!bb || bb->size == 0) {
continue;
}
@@ -866,7 +1387,7 @@ static void flutter_scan_function(RCore *core, FlutterAnalModel *model, RAnalFun
at += 4;
continue;
}
RAnalOp *op = r_core_anal_op (core, at, R_ARCH_OP_MASK_BASIC | R_ARCH_OP_MASK_OPEX | R_ARCH_OP_MASK_DISASM);
RAnalOp *op = r_core_anal_op (core, at, R_ARCH_OP_MASK_BASIC | R_ARCH_OP_MASK_VAL | R_ARCH_OP_MASK_DISASM);
if (!op || op->size <= 0) {
r_anal_op_free (op);
break;
@@ -884,8 +1405,13 @@ static void flutter_scan_function(RCore *core, FlutterAnalModel *model, RAnalFun
if (has_info && info.has_imm) {
flutter_process_direct_ref (core, model, at, info.imm, stats);
}
if (has_info) {
flutter_annotate_null_reg (core, at, op->type & R_ANAL_OP_TYPE_MASK, &info, stats);
}
switch (op->type & R_ANAL_OP_TYPE_MASK) {
// Use the effective type so x86-64 memory MOVs dispatch as LOAD/STORE.
int etype = has_info? info.etype: (int)(op->type & R_ANAL_OP_TYPE_MASK);
switch (etype) {
case R_ANAL_OP_TYPE_MOV:
if (!flutter_process_mov (&state, &info) && has_info && info.n_regs > 0) {
int dst = flutter_reg_index (info.regs[0]);
@@ -898,9 +1424,10 @@ 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, pp_refs, at, &info, stats) &&
!flutter_process_stack_load (&state, &info) &&
!flutter_process_field_load (core, model, &state, at, &info, stats) &&
!flutter_process_pp_slot_deref (&state, &info) &&
has_info && info.n_regs > 0) {
int dst = flutter_reg_index (info.regs[0]);
if (dst >= 0) {
@@ -923,7 +1450,7 @@ static void flutter_scan_function(RCore *core, FlutterAnalModel *model, RAnalFun
case R_ANAL_OP_TYPE_ICALL:
case R_ANAL_OP_TYPE_IRCALL:
if (has_info) {
flutter_process_indirect_call (core, &state, at, &info, stats);
flutter_process_indirect_call (core, &state, pp_refs, at, &info, stats);
}
flutter_state_clobber_callers (&state);
break;
@@ -938,7 +1465,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, FlutterPpRefMap *pp_refs, RVecFlutterEntry *entries, FlutterAnalStats *stats) {
if (!core || !model || !entries || !stats) {
return;
}
@@ -953,11 +1480,67 @@ 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, pp_refs, fcn, stats);
}
ht_up_free (seen_fcns);
}
// Expose the reconstructed ObjectPool as r2's global pointer so that plain-r2
// analysis and ESIL resolve `[x27, off]` loads to pool addresses, matching how
// `r2flutter -p -r` already wires it up. x27 (PP) is the only Dart reserved
// register with a static address; x22 (NULL_REG) and x26 (THR) are runtime
// pointers and cannot be seeded this way.
void r2flutter_setup_pp_gp(RCore *core, DartCtx *dctx) {
if (!core || !dctx) {
return;
}
// Resolve on a shallow copy: dart_resolve_pp_info populates the read cache
// (ctx->rmem) and can leave a stale layout pointer in the context. The real
// dctx is copied by value into the analysis DartApp, so polluting it here
// would double-free that cache. The copy shares heap-owned pointers with
// dctx, so we only release what we allocated into it (its own rmem).
DartCtx probe = *dctx;
probe.rmem = NULL;
probe.layout = NULL;
// Dart's PP register is x27 on arm64 and r15 on x86-64.
const char *arch = r_config_get (core->config, "asm.arch");
const int bits = (int)r_config_get_i (core->config, "asm.bits");
const bool is_x64 = arch && bits == 64 && (!strcmp (arch, "x86") || !strcmp (arch, "x64"));
const char *pp_reg = is_x64? "r15": "x27";
DartPpInfo pp = { 0 };
if (dart_resolve_pp_info (&probe, &pp) && pp.base) {
char roregs[32];
snprintf (roregs, sizeof (roregs), "%s,gp,zero", pp_reg);
r_config_set_i (core->config, "anal.gp", pp.base);
r_config_set (core->config, "anal.gpseed", pp_reg);
r_config_set (core->config, "anal.roregs", roregs);
r_flag_set (core->flags, "PP", pp.base, 1);
}
dart_pp_info_fini (&pp);
dart_read_cache_free (probe.rmem);
}
// Apply the recovered ObjectPool xref graph (including data-image references
// such as a const object pointing at a string) so `axt` resolves pool strings
// that no instruction loads directly. Runs on a clean copy of the context:
// extraction needs the pristine state the standalone `-x` command sees, and the
// copy shares heap-owned pointers so only its own read cache is freed. Cheap
// relative to the function scan, which is why -AA can afford it. Returns the
// number of xrefs applied.
int r2flutter_apply_pool_xrefs(RCore *core, DartCtx *dctx) {
if (!core || !dctx) {
return 0;
}
DartCtx xrctx = *dctx;
xrctx.core = core;
xrctx.rmem = NULL;
xrctx.layout = NULL;
xrctx.dump_string_refs = true;
int applied = dart_pool_apply_xrefs (&xrctx);
dart_read_cache_free (xrctx.rmem);
return applied;
}
bool r2flutter_analysis_run(RCore *core, DartCtx *dctx, bool quiet) {
R_RETURN_VAL_IF_FAIL (core && dctx, false);
DartApp *app = dart_app_new_from_core (core, dctx);
@@ -966,7 +1549,7 @@ bool r2flutter_analysis_run(RCore *core, DartCtx *dctx, bool quiet) {
return false;
}
dart_app_load_info (app);
dart_dumper_apply_to_core (app);
dart_dumper_apply_to_core (app, true);
FlutterAnalModel model;
if (!flutter_model_load (&model, core, &app->dctx)) {
@@ -979,22 +1562,36 @@ 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);
FlutterPpRefMap pp_refs;
flutter_pp_ref_map_init (&pp_refs);
flutter_pp_ref_map_load (&app->dctx, &pp_refs);
RVecFlutterEntry *entries = flutter_collect_entries (core, app, &model);
flutter_ensure_functions (core, entries);
flutter_scan_functions (core, &model, entries, &stats);
dart_dumper_name_functions (app);
flutter_scan_functions (core, &model, &pp_strings, &pp_refs, entries, &stats);
int graph_xrefs = r2flutter_apply_pool_xrefs (core, dctx);
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);
flutter_pp_string_map_fini (&pp_strings);
flutter_pp_ref_map_fini (&pp_refs);
flutter_model_fini (&model);
dart_app_free (app);
return true;
+2
View File
@@ -5,5 +5,7 @@
#include "../../include/r2flutter/dart_r2.h"
bool r2flutter_analysis_run(RCore *core, DartCtx *dctx, bool quiet);
void r2flutter_setup_pp_gp(RCore *core, DartCtx *dctx);
int r2flutter_apply_pool_xrefs(RCore *core, DartCtx *dctx);
#endif
+49 -24
View File
@@ -10,35 +10,33 @@
#include "../r2/flutter_analysis.h"
static const char usage_text[] =
"Usage: %s [options] <libapp_path_or_dir>\n"
"Usage: %s [-jrv*] <libapp_path_or_dir>\n"
"Modifiers:\n"
" -h Show help\n"
" -j Output in JSON format\n"
" -n Heuristic fallback for unknown functions; may assign wrong names\n"
" -q Compact output; suppress non-essential detail\n"
" -r Output r2 commands for the selected action\n"
" -n Heuristic fallback for unknown functions; may assign wrong names\n"
" -v Verbose (stderr debug info)\n"
" -vv More verbose (dump headers)\n"
" -V Show version\n"
"Actions:\n"
" -A Analyze Dart snapshot and apply flags/comments\n"
" -AA Analyze with field extraction enabled\n"
" -AAA Run Dart-aware code analysis and recover code refs\n"
" -c Print extracted class information\n"
" -f Print all extracted functions (addr name)\n"
" -H Print Dart AOT snapshot header info\n"
" -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"
" -A[AA] Analyze Dart snapshot and apply flags/comments\n"
" -c[jr*] Print extracted class information\n"
" -f[jr*] Print all extracted functions (addr name)\n"
" -H[HH] Print Dart AOT snapshot header info\n"
" -i[jr*] Print instruction table entries (-ii same as -i)\n"
" -ie[jr*], -E[jr*] Print Dart code entrypoint\n"
" -O <addr|pp+off|ref:N> Decode a tagged pointer, PP slot, or snapshot reference\n"
" -p[jr*] Print reconstructed ObjectPool PP value\n"
" -R Print radare2 script for snapshot analysis\n"
" -S Print best-effort recovered SBOM/components\n"
" -T Print string-based type names\n"
" -x Print metadata/data-image xrefs; with -z, include string refs/ax in -r\n"
" -z Print reliable ObjectPool-referenced strings (-q prints values only)\n"
" -zz Print all fuzzy/carved extracted strings (-xzz includes refs/ax in -r)\n"
" -S[jr*] Print best-effort recovered SBOM/components\n"
" -T[jr*] Print string-based type names\n"
" -x[jr*] Print metadata/data-image xrefs; with -z, include string refs/ax in -r\n"
" -z[jr*] Print reliable ObjectPool-referenced strings (-q prints values only)\n"
" -zz[jr*] Print all fuzzy/carved extracted strings (-xzz includes refs/ax in -r)\n"
"Options:\n"
" -D <hash|version> Override Dart snapshot profile for analysis\n"
" -l <N> Limit output to N items\n"
" -m <file> Load Flutter obfuscation map JSON\n";
@@ -84,8 +82,8 @@ static char *resolve_input_path(const char *s, DartCtx *dctx, bool *extracted_in
return NULL;
}
DartAppEmbeddedPayload payload;
if (dart_app_find_macho_embedded_dart (s, &payload)) {
char *inner = dart_app_extract_embedded_payload (s, &payload);
if (dart_app_find_macho_dart (s, &payload)) {
char *inner = dart_app_extract_payload (s, &payload);
if (!inner) {
R_LOG_ERROR ("Failed to extract embedded Dart Mach-O from: %s", s);
return NULL;
@@ -117,7 +115,7 @@ int main(int argc, char **argv) {
.no_stubs = true
};
RGetopt opt;
r_getopt_init (&opt, argc, (const char **)argv, "AcfhHijnm:O:pqrRSzTvVxl:");
r_getopt_init (&opt, argc, (const char **)argv, "AcD:eEfHhijnm:O:pqrRSzTvVxl:");
int c;
while ((c = r_getopt_next (&opt)) != -1) {
switch (c) {
@@ -128,6 +126,9 @@ int main(int argc, char **argv) {
case 'c':
action = c;
break;
case 'E':
action = 'e';
break;
case 'f':
action = c;
break;
@@ -142,6 +143,14 @@ int main(int argc, char **argv) {
action = c;
dctx.dump_it = true;
break;
case 'e':
if (action == 'i') {
action = c;
} else {
R_LOG_ERROR ("Invalid flag: -%c", c);
return 1;
}
break;
case 'O':
action = c;
object_spec = opt.arg;
@@ -149,11 +158,18 @@ int main(int argc, char **argv) {
case 'p':
action = c;
break;
case 'D':
if (!dart_ctx_set_profile_override (&dctx, opt.arg)) {
R_LOG_ERROR ("Unsupported Dart snapshot profile override: %s", opt.arg);
return 1;
}
break;
case 'j':
fmt = c;
break;
case 'l':
dctx.dump_fns_limit = atoi (opt.arg);
dctx.str_limit = r_num_get (NULL, opt.arg);
break;
case 'n':
dctx.use_name_pool = true;
@@ -200,6 +216,7 @@ int main(int argc, char **argv) {
print_usage (argv[0]);
return 1;
default:
R_LOG_ERROR ("Invalid flag: -%c", opt.opt);
return 1;
}
}
@@ -237,6 +254,11 @@ int main(int argc, char **argv) {
free_resolved_path (libapp_path, extracted_inner);
return 1;
}
// r2flutter does its own Dart-aware string/symbol extraction and never
// consumes RBin's string list, so skip the costly RBin string scan that
// r_core_bin_load runs over the whole binary (dominant load-time cost on
// large libapp.so / App images).
r_config_set_b (core->config, "bin.strings", false);
r_core_bin_load (core, NULL, 0);
dctx.core = core;
@@ -286,7 +308,7 @@ int main(int argc, char **argv) {
R_LOG_INFO ("Loaded %zu functions from Dart snapshot", count);
}
}
dart_dumper_apply_to_core (app);
dart_dumper_apply_to_core (app, false);
break;
case 'z':
output = string_depth >= 2? dart_pool_dump_strings_fuzzy (&dctx, fmt): dart_pool_dump_strings (&dctx, fmt);
@@ -296,6 +318,9 @@ int main(int argc, char **argv) {
dctx.dump_fields = 1;
output = dart_pool_dump_classes (&dctx, fmt);
break;
case 'e':
output = dart_pool_dump_entry (&app->dctx, fmt);
break;
case 'T':
dctx.dump_classes = 3;
output = dart_pool_dump_classes (&dctx, fmt);
@@ -315,7 +340,7 @@ int main(int argc, char **argv) {
output = dart_pool_dump_object (&app->dctx, object_spec, fmt);
break;
case 'p':
output = dart_pool_dump_pp (&app->dctx, fmt);
output = dart_pool_dump_pool (&app->dctx, fmt);
break;
case 'x':
output = dart_pool_dump_xrefs (&dctx, fmt);
@@ -325,7 +350,7 @@ int main(int argc, char **argv) {
if (dctx.verbose) {
R_LOG_ERROR ("Dumping radare2 script");
}
char *s = dart_dumper_dump4radare2 (app);
char *s = dart_dumper_dump_r2 (app);
printf ("%s\n", s);
free (s);
break;
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,12 @@
{
"name": "appstrings-android-w0rdle",
"cwd": "test",
"cmd": ["../bin/r2flutter", "-q", "-z", "bins/android/w0rdle/libapp.so"],
"timeout": 30,
"expect": {
"contains": [
"assets/allowed_guesses.txt",
"ph0wn CTF is awesome"
]
}
}
+18
View File
@@ -0,0 +1,18 @@
{
"name": "dump-classes-macos",
"cwd": "test",
"cmd": ["../bin/r2flutter", "-c", "bins/macos/hello/hello.aot"],
"timeout": 30,
"expect": {
"contains": [
"class _Closure@0150898 (size=64)",
"+0x28 dynamic _context@0150898",
"class _Uri@0150898 (size=96)",
"+0x0 dynamic _portMap@1026248 static final"
],
"not_contains": [
"class _Uint16List (size=0)",
"class FutureOr (size=0)"
]
}
}
@@ -0,0 +1,12 @@
{
"name": "dump-funcs-android-w0rdle",
"cwd": "test",
"cmd": ["../bin/r2flutter", "-q", "-f", "bins/android/w0rdle/libapp.so"],
"timeout": 30,
"expect": {
"contains": [
"method.WordService.getWordToFind",
"method.Flutterdle.updateAfterSuccessfulGuess"
]
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
"timeout": 30,
"expect": {
"contains": [
"func.0000fe28",
"method._HashMap_3220832.addAll",
"0xfe28"
]
}
+1 -1
View File
@@ -5,7 +5,7 @@
"timeout": 30,
"expect": {
"contains": [
"func.0000e768",
"method.PointerEvent.get:radiusMinor",
"0xe768"
]
}
+1 -1
View File
@@ -4,6 +4,6 @@
"cmd": ["../bin/r2flutter", "-l", "3", "-i", "bins/ios/Runner.app"],
"timeout": 20,
"expect": {
"equals": "# length=7670 first_entry_with_code=0 canonical_stack_map_entries_offset=160751\n0 0xe768 code func.0000e768\n1 0xe770 code func.0000e770\n2 0xe7a0 code func.0000e7a0"
"equals": "# length=7670 first_entry_with_code=0 canonical_stack_map_entries_offset=160751\n0 0xe768 code method.PointerEvent.get:radiusMinor\n1 0xe770 code func.0000e770\n2 0xe7a0 code method.PointerEvent.get:distance"
}
}
+1 -1
View File
@@ -4,6 +4,6 @@
"cmd": ["../bin/r2flutter", "-j", "-l", "2", "-i", "bins/ios/Runner.app"],
"timeout": 20,
"expect": {
"equals": "{\"length\":7670,\"first_entry_with_code\":0,\"canonical_stack_map_entries_offset\":160751,\"entries\":[{\"index\":0,\"code_index\":0,\"address\":59240,\"pc_offset\":104,\"stack_map_offset\":61376,\"kind\":\"code\",\"name\":\"func.0000e768\"},{\"index\":1,\"code_index\":1,\"address\":59248,\"pc_offset\":112,\"stack_map_offset\":61376,\"kind\":\"code\",\"name\":\"func.0000e770\"}]}"
"equals": "{\"length\":7670,\"first_entry_with_code\":0,\"canonical_stack_map_entries_offset\":160751,\"entries\":[{\"index\":0,\"code_index\":0,\"address\":59240,\"pc_offset\":104,\"stack_map_offset\":61376,\"kind\":\"code\",\"name\":\"method.PointerEvent.get:radiusMinor\"},{\"index\":1,\"code_index\":1,\"address\":59248,\"pc_offset\":112,\"stack_map_offset\":61376,\"kind\":\"code\",\"name\":\"func.0000e770\"}]}"
}
}
+1 -1
View File
@@ -4,6 +4,6 @@
"cmd": ["../bin/r2flutter", "-r", "-l", "2", "-i", "bins/ios/Runner.app"],
"timeout": 20,
"expect": {
"equals": "# Dart InstructionTable entries\n# length=7670 first_entry_with_code=0 canonical_stack_map_entries_offset=160751\n# it[0] func.0000e768\nf it.code_0 = 0xe768\n# it[1] func.0000e770\nf it.code_1 = 0xe770"
"equals": "# Dart InstructionTable entries\n# length=7670 first_entry_with_code=0 canonical_stack_map_entries_offset=160751\n# it[0] method.PointerEvent.get:radiusMinor\nf it.code_0 = 0xe768\n# it[1] func.0000e770\nf it.code_1 = 0xe770"
}
}
@@ -0,0 +1,14 @@
{
"name": "dump-strings-android-w0rdle",
"cwd": "test",
"cmd": ["../bin/r2flutter", "-q", "-z", "bins/android/w0rdle/libapp.so"],
"timeout": 30,
"expect": {
"contains": [
"ph0wn",
"y@cht",
"answer",
"ph0wn{you_w1n_darts_stage1}"
]
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"name": "header-android-w0rdle",
"cwd": "test",
"cmd": ["../bin/r2flutter", "-H", "bins/android/w0rdle/libapp.so"],
"timeout": 30,
"expect": {
"contains": [
"dart_version: 2.18.0",
"tag_style: CID_SHIFT1",
"cid_string: 90",
"cid_object_pool: 21"
]
}
}
+3 -3
View File
@@ -5,10 +5,10 @@
"timeout": 30,
"expect": {
"contains": [
"snapshot_hash=78da37fed6bf1489361a312568249f3f dart_version=unknown",
"snapshot_hash=78da37fed6bf1489361a312568249f3f dart_version=3.11.5",
"detail: object_pool_entries",
"34 cid=23 alloc=object_pool fill=none count=1",
"object_pool_decode: fill_not_parsed"
"34 cid=23 alloc=object_pool fill=object_pool count=1",
"object_pool ref=9145 index=0 length=1380"
]
}
}
@@ -0,0 +1,14 @@
{
"name": "header-json-android-w0rdle",
"cwd": "test",
"cmd": ["../bin/r2flutter", "-Hj", "bins/android/w0rdle/libapp.so"],
"timeout": 30,
"expect": {
"json": {
"dart_version": "2.18.0",
"hash": "b0e899ec5a90e4661501f0b69e9dd70f",
"cws": 4,
"tag_style": "CID_SHIFT1"
}
}
}
+2 -5
View File
@@ -7,13 +7,10 @@
"contains": [
"Usage:",
"-i",
"-H",
"-HH",
"-HHH",
"-H[HH]",
"-p",
"-S",
"-AA",
"-AAA"
"-A[AA]"
],
"not_contains": [
"--no-stubs"
+14
View File
@@ -0,0 +1,14 @@
{
"name": "methods-android-w0rdle",
"cwd": "test",
"cmd": ["../bin/r2flutter", "-q", "-f", "bins/android/w0rdle/libapp.so"],
"timeout": 30,
"expect": {
"contains": [
"method.WordService.getWordToFind",
"method.WordService._readFile_746434692",
"method.Flutterdle.didWin",
"method.CachingAssetBundle.loadString"
]
}
}
+1 -1
View File
@@ -4,6 +4,6 @@
"cmd": ["../bin/r2flutter", "-j", "-f", "-l", "3", "-m", "custom/obf-map.txt", "bins/ios/Runner.app"],
"timeout": 30,
"expect": {
"equals": "[{\"addr\":59240,\"name\":\"func.0000e768\"},{\"addr\":59248,\"name\":\"func.0000e770\"},{\"addr\":59296,\"name\":\"func.0000e7a0\"}]"
"equals": "[{\"addr\":59240,\"name\":\"method.PointerEvent.get:radiusMinor\",\"signature\":\"dynamic(PointerEvent)\"},{\"addr\":59248,\"name\":\"func.0000e770\"},{\"addr\":59296,\"name\":\"method.PointerEvent.get:distance\",\"signature\":\"dynamic(PointerEvent)\"}]"
}
}
+12
View File
@@ -0,0 +1,12 @@
{
"name": "objdecode-android-w0rdle",
"cwd": "test",
"cmd": ["../bin/r2flutter", "-O", "pp+36864", "bins/android/w0rdle/libapp.so"],
"timeout": 30,
"expect": {
"contains": [
"target.kind: string",
"target.name: \"floatingActionButtonTheme\""
]
}
}
@@ -0,0 +1,12 @@
{
"name": "objdecode-func-android-w0rdle",
"cwd": "test",
"cmd": ["../bin/r2flutter", "-O", "pp+1240", "bins/android/w0rdle/libapp.so"],
"timeout": 30,
"expect": {
"contains": [
"target.kind: function",
"target.name: \"method."
]
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"name": "pool-android-w0rdle",
"cwd": "test",
"cmd": ["../bin/r2flutter", "-p", "bins/android/w0rdle/libapp.so"],
"timeout": 30,
"expect": {
"contains": [
"length: 12351 entries"
],
"not_contains": [
"PP not resolved"
]
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"name": "pool-ios-authpass-shift1",
"cwd": "test",
"cmd": ["../bin/r2flutter", "-p", "bins/ios/AuthPass.app/Frameworks/App.framework/App"],
"timeout": 30,
"expect": {
"contains": [
"length: 43268 entries"
],
"not_contains": [
"PP not resolved"
]
}
}
@@ -0,0 +1,12 @@
{
"name": "signatures-android-w0rdle",
"cwd": "test",
"cmd": ["../bin/r2flutter", "-q", "-j", "-f", "bins/android/w0rdle/libapp.so"],
"timeout": 30,
"expect": {
"contains": [
"\"signature\":\"String(_StringBase@0150898,dynamic)\"",
"\"signature\":\"dynamic(WordService)\""
]
}
}
+13
View File
@@ -0,0 +1,13 @@
NAME=r2-analysis-string-comment
FILE=bins/android/first/libapp.so
ARGS=-NN -e scr.color=0 -e bin.relocs.apply=true
CMDS=<<EOF
L ../src/r2/core_flutter.so
L ../src/r2/core_flutter.dylib
r2flutter -AAA
pd 1 @ 0x8b914~&_nativeGetFloat64,dart: string[0]
EOF
EXPECT=<<EOF
0x0008b914
EOF
RUN
+13
View File
@@ -0,0 +1,13 @@
NAME=r2-analysis-pp-string-comment-ios
FILE=bins/ios/Runner.app/Frameworks/App.framework/App
ARGS=-NN -e scr.color=0 -e bin.relocs.apply=true
CMDS=<<EOF
L ../src/r2/core_flutter.so
L ../src/r2/core_flutter.dylib
r2flutter -AAA
pd 1 @ 0x176d0~&PP slot,string,https[0]
EOF
EXPECT=<<EOF
0x000176d0
EOF
RUN
+102
View File
@@ -0,0 +1,102 @@
NAME=r2-authpass-tls-flags
FILE=bins/ios/AuthPass.app/Frameworks/App.framework/App
ARGS=-NN -e bin.relocs.apply=true
CMDS=<<EOF
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -A
f~onBadCertificateWrapper~method
f~registerBadCertificateCallback~method~!_registerBadCertificateCallback_
f~_registerBadCertificateCallback_~method
f~_secureHandshake_~method
EOF
EXPECT=<<EOF
0x003199d4 0 method._RawSecureSocket_14069316._onBadCertificateWrapper_14069316
0x003194ec 0 method._SecureFilterImpl_14069316.registerBadCertificateCallback
0x0031954c 0 method._SecureFilterImpl_14069316._registerBadCertificateCallback_14069316
0x0031749c 0 method._RawSecureSocket_14069316._secureHandshake_14069316
EOF
RUN
NAME=r2-authpass-callsite-xrefs
FILE=bins/ios/AuthPass.app/Frameworks/App.framework/App
ARGS=-NN -e bin.relocs.apply=true
CMDS=<<EOF
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -A
pd 1 @ 0x3199bc~onBadCertificateWrapper[0]
pd 1 @ 0x316cd8~registerBadCertificateCallback[0]
pd 1 @ 0x319530~_registerBadCertificateCallback_[0]
pd 1 @ 0x317028~_secureHandshake_[0]
pd 1 @ 0x317514~_secureHandshake_[0]
pd 1 @ 0x3178b4~_secureHandshake_[0]
pd 1 @ 0x317e04~_secureHandshake_[0]
EOF
EXPECT=<<EOF
0x003199bc
0x00316cd8
0x00319530
0x00317028
0x00317514
0x003178b4
0x00317e04
EOF
RUN
NAME=r2-authpass-handshake-disasm
FILE=bins/ios/AuthPass.app/Frameworks/App.framework/App
ARGS=-NN -e bin.relocs.apply=true
CMDS=<<EOF
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -A
pd 1 @ 0x3174e4
pd 1 @ 0x31752c
pd 1 @ 0x317554
pd 1 @ 0x3175ac
EOF
EXPECT=<<EOF
0x003174e4 3f060094 bl sym.func.00318de0 ; method._SecureFilterImpl_14069316.handshake ; method._SecureFilterImpl_14069316.handshake(0x0, 0x0, 0x0, 0x0)
0x0031752c c1c20091 add x1, x22, 0x30
0x00317554 13000094 bl sym.func.003175a0 ; method._RawSecureSocket_14069316._scheduleFilter_14069316 ; method._RawSecureSocket_14069316._scheduleFilter_14069316(0x0, 0x0, 0x0, 0x0)
0x003175ac c0820091 add x0, x22, 0x20 ; mach0_segment64_0
EOF
RUN
NAME=r2-authpass-wrapper-disasm
FILE=bins/ios/AuthPass.app/Frameworks/App.framework/App
ARGS=-NN -e bin.relocs.apply=true
CMDS=<<EOF
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -A
pd 1 @ 0x3199f0
pd 1 @ 0x319a10
pd 1 @ 0x319a1c
EOF
EXPECT=<<EOF
0x003199f0 01f045f8 ldur x1, [x0, 0x5f]
0x00319a10 40003fd6 blr x2 ; app.base(0x0, 0x0, 0x0, 0x0)
0x00319a1c c0035fd6 ret
EOF
RUN
NAME=r2-authpass-qjs-bypass
FILE=bins/ios/AuthPass.app/Frameworks/App.framework/App
ARGS=-NN -e bin.relocs.apply=true
CMDS=<<EOF
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -A
f~onBadCertificateWrapper~method
e io.cache=true
s 0x3199d4
wx c0820091c0035fd6
p8 8
EOF
EXPECT=<<EOF
0x003199d4 0 method._RawSecureSocket_14069316._onBadCertificateWrapper_14069316
c0820091c0035fd6
EOF
RUN
+36
View File
@@ -0,0 +1,36 @@
NAME=serialized-class-layout-metadata
FILE=malloc://0x94
CMDS=<<EOF
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
wx 50414421f5f5dcdc8c000000000000000200000000000000303435313930376332656161383436376538343863303036376266653865643470726f647563742061726d3634206e6f2d636f6d707265737365642d706f696e746572730086828180800020c182818080808080808280838080807022c4c4c3c2c1c0c08584808080808080858086808080070040c0c5c4c0c0c0c0
r2flutter -q -j -c~{=}ref,class_id,layout.,flags.toplevel
o malloc://0x7c
wx 50414421f5f5dcdc74000000000000000200000000000000303435313930376332656161383436376538343863303036376266653865643470726f647563742061726d3634206e6f2d636f6d707265737365642d706f696e746572730086818180800020c181818080808080808280838080807022c4c4c3c2c1c0c0
r2flutter -q -j -c
r2flutter -q -j -HHH~{snapshots[0].clusters[0].fill_status}
EOF
EXPECT=<<EOF
json[0].ref = 7;
json[0].class_id = 70000;
json[0].library.ref = 2;
json[0].super.type_ref = 3;
json[0].layout.instance_size = 32;
json[0].layout.next_field_offset = 24;
json[0].layout.type_params = 1;
json[0].layout.type_arg_offset = 16;
json[0].layout.field_bitmap = 5;
json[0].flags.toplevel = false;
json[1].ref = 8;
json[1].class_id = 1048583;
json[1].library.ref = 5;
json[1].super.type_ref = 6;
json[1].layout.instance_size = 40;
json[1].layout.next_field_offset = 32;
json[1].layout.type_params = 0;
json[1].layout.type_arg_offset = 0;
json[1].flags.toplevel = true;
[]
failed
EOF
RUN
+8 -4
View File
@@ -1,7 +1,9 @@
NAME=r2-classes-android
FILE=-
FILE=bins/android/first/libapp.so
CMDS=<<EOF
!!../bin/r2flutter -r -c bins/android/first 2>/dev/null | sed -n '1,7p'
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -r -c~:0..7
EOF
EXPECT=<<EOF
# Dart classes extracted from snapshot
@@ -15,9 +17,11 @@ EOF
RUN
NAME=text-classes-android
FILE=-
FILE=bins/android/first/libapp.so
CMDS=<<EOF
!!../bin/r2flutter -c bins/android/first 2>/dev/null | head -6
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -c~:0..6
EOF
EXPECT=<<EOF
class _Uint16List (size=0)
+20 -16
View File
@@ -1,30 +1,34 @@
NAME=text-classes-ios
FILE=-
FILE=bins/ios/Runner.app/Frameworks/App.framework/App
CMDS=<<EOF
!!../bin/r2flutter -c bins/ios/Runner.app 2>/dev/null | sed -n '1,6p'
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -c~:0..6
EOF
EXPECT=<<EOF
class _Uint16List (size=0)
class _ExternalInt64Array (size=0)
class FutureOr (size=0)
class _Set (size=0)
class Int32List. (size=0)
class Float32List (size=0)
class Pointer (size=32)
methods:
0x00115f2c get:hashCode (GetterFunction)
0x001299fc == (RegularFunction)
class NativeType (size=16)
class NativeFunction (size=16)
EOF
RUN
NAME=r2-classes-ios
FILE=-
FILE=bins/ios/Runner.app/Frameworks/App.framework/App
CMDS=<<EOF
!!../bin/r2flutter -r -c bins/ios/Runner.app 2>/dev/null | head -7
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -r -c~:0..7
EOF
EXPECT=<<EOF
# Dart classes extracted from snapshot
# class _Uint16List (size=0)
"td struct dart__Uint16List { };"
# class _ExternalInt64Array (size=0)
"td struct dart__ExternalInt64Array { };"
# class FutureOr (size=0)
"td struct dart_FutureOr { };"
# class Pointer (size=32)
# type_params: 1
"td struct dart_Pointer { };"
# method 0x00115f2c get:hashCode (GetterFunction)
# method 0x001299fc == (RegularFunction)
# class NativeType (size=16)
EOF
RUN
+27 -12
View File
@@ -1,35 +1,50 @@
NAME=text-classes-mafia
FILE=-
CMDS=!!../bin/r2flutter -c bins/android/mafia 2>/dev/null | head -6
FILE=bins/android/mafia/libapp.so
CMDS=<<EOF
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -c~:0..6
EOF
EXPECT=<<EOF
class Pointer (size=32)
methods:
0x00000000 get:hashCode (GetterFunction)
0x00000000 == (RegularFunction)
0x00c6ab64 get:hashCode (GetterFunction)
0x00d8bde8 == (RegularFunction)
class NativeType (size=16)
class NativeFunction (size=16)
EOF
RUN
NAME=text-classes-mafia-modern-members
FILE=-
CMDS=!!../bin/r2flutter -c bins/android/mafia 2>/dev/null | awk '/class _RawReceivePort@1026248/{raw=1} raw && /_portMap@1026248 static final/{field=1} /firstMatch/{method=1} END { print "field=" field + 0; print "method=" method + 0 }'
FILE=bins/android/mafia/libapp.so
CMDS=<<EOF
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -c~?&_RawReceivePort@1026248,size=32
r2flutter -c~?_portMap@1026248
r2flutter -c~?firstMatch
EOF
EXPECT=<<EOF
field=1
method=1
1
1
1
EOF
RUN
NAME=r2-classes-mafia
FILE=-
CMDS=!!../bin/r2flutter -r -c bins/android/mafia 2>/dev/null | head -7
FILE=bins/android/mafia/libapp.so
CMDS=<<EOF
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -r -c~:0..7
EOF
EXPECT=<<EOF
# Dart classes extracted from snapshot
# class Pointer (size=32)
# type_params: 1
"td struct dart_Pointer { };"
# method 0x00000000 get:hashCode (GetterFunction)
# method 0x00000000 == (RegularFunction)
# method 0x00c6ab64 get:hashCode (GetterFunction)
# method 0x00d8bde8 == (RegularFunction)
# class NativeType (size=16)
EOF
RUN
+39 -12
View File
@@ -1,19 +1,46 @@
NAME=r2-plugin-C-mafia-future
FILE=-
FILE=bins/android/mafia/libapp.so
ARGS=-NN -e bin.relocs.apply=true
CMDS=<<EOF
!!sh -c "p=../src/r2/core_flutter.dylib; [ -f \"\$p\" ] || p=../src/r2/core_flutter.so; r2 -NN -q -escr.color=0 -e bin.relocs.apply=true -i \"\$p\" -qc \"r2flutter -C\" -qc \"ic dart_Future\" -qc q bins/android/mafia/libapp.so 2>&1"
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -C
icqq~dart_Future
EOF
EXPECT=<<EOF
INFO: Applied 8986 Dart classes, 1756 fields, 26609 methods
dart_FutureOr
dart_FutureProviderRef
dart_FutureProvider
dart_FutureBuilder
dart_FutureProviderFamilyBuilder
dart_FutureProviderFamily
dart_FutureHandlerProviderElementMixin
dart_FutureProviderElement
dart_Future
EOF
RUN
# Remove BROKEN=1 when radare2 6.2.2 is released.
NAME=r2-plugin-C-mafia-future-method-attributes
BROKEN=1
FILE=bins/android/mafia/libapp.so
ARGS=-NN -e bin.relocs.apply=true
CMDS=<<EOF
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -C
ic dart_Future
EOF
EXPECT=<<EOF
class dart_Future
0x00000000 method dart_Future Future_error
0x00000000 method dart_Future Future_
0x00000000 method dart_Future _anonymous_closure_
0x00000000 method dart_Future Future_value
0x00000000 method dart_Future wait
0x00000000 method dart_Future handleError
0x00000000 method dart_Future Future_delayed
0x00000000 method dart_Future Future_microtask
0x00000000 method dart_Future Future_sync
0x005d4f54 method dart_Future sC Future_error
0x005d70cc method dart_Future sC Future_
0x005d71b8 method dart_Future s _anonymous_closure_
0x005e4a98 method dart_Future sC Future_value
0x0060d7d4 method dart_Future s wait
0x0060dfac method dart_Future s handleError
0x0062edcc method dart_Future sC Future_delayed
0x006d5018 method dart_Future sC Future_microtask
0x00943c08 method dart_Future sC Future_sync
EOF
RUN
+26
View File
@@ -0,0 +1,26 @@
NAME=versioned-code-cluster-layouts
FILE=malloc://0x75
CMDS=<<EOF
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
wx 50414421f5f5dcdc6d000000000000000300000000000000356239373239326232356630613731353631336237613238653037333466373770726f647563742061726d3634206e6f2d636f6d707265737365642d706f696e74657273008284818187ce8180d1818184878080808080808081002c82
r2flutter -q -HHH -l 3~alloc=code:0
r2flutter -q -HHH -l 3~bits=:0
o malloc://0x76
wx 50414421f5f5dcdc6e000000000000000300000000000000323464396434313163326639306338666265383930376639396538396434623070726f647563742061726d3634206e6f2d636f6d707265737365642d706f696e74657273008284828785e281c080ea818184878080808080808081002c82
r2flutter -q -HHH -l 3~alloc=code:0
r2flutter -q -HHH -l 3~bits=:0
o malloc://0x75
wx 50414421f5f5dcdc6d000000000000000300000000000000313434316436623133623836323366613766626636313433336162656264333170726f647563742061726d3634206e6f2d636f6d707265737365642d706f696e7465727300828482878589e281c080ea81818780808080808081002c82
r2flutter -q -HHH -l 3~alloc=code:0
r2flutter -q -HHH -l 3~bits=:0
EOF
EXPECT=<<EOF
0 cid=14 alloc=code fill=code count=1 refs=3..4 flags=canonical alloc=0x63..0x65 fill=0x68..0x71 fill_status=ok main=1
[0] pool_off=0xf pp_off=0x10 bits=0x00 type=tagged_object patch=patchable behavior=snapshotable stream=0x72 value=0x73 ref=300 resolved_kind=out_of_range
0 cid=17 alloc=code fill=code count=1 refs=3..4 flags=- alloc=0x63..0x66 fill=0x69..0x72 fill_status=ok main=1
[0] pool_off=0xf pp_off=0x10 bits=0x00 type=tagged_object patch=patchable behavior=snapshotable stream=0x73 value=0x74 ref=300 resolved_kind=out_of_range
0 cid=17 alloc=code fill=code count=1 refs=3..4 flags=- alloc=0x64..0x67 fill=0x6a..0x71 fill_status=ok main=1
[0] pool_off=0xf pp_off=0x10 bits=0x00 type=tagged_object patch=patchable behavior=snapshotable stream=0x72 value=0x73 ref=300 resolved_kind=out_of_range
EOF
RUN
+21
View File
@@ -0,0 +1,21 @@
NAME=discovery-stripped-ios-structural-instr
FILE=bins/ios/Runner.app/Frameworks/App.framework/App
CMDS=<<EOF
s 0
o: 0x321920
wv4 0 @ 0x298
wv4 0 @ 0x29c
wv4 0 @ 0x2ac
ooi
is~?kDart
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -j -H~{=}vm_instr,iso_data,iso_instr
EOF
EXPECT=<<EOF
0
json.vm_instr = 16384;
json.iso_data = 1807872;
json.iso_instr = 59136;
EOF
RUN
+30
View File
@@ -0,0 +1,30 @@
NAME=dart-313-early-base-object-clusters
FILE=malloc://0x8d
CMDS=<<EOF
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
wx 50414421f5f5dcdc85000000000000000200000000000000303435313930376332656161383436376538343863303036376266653865643470726f647563742061726d3634206e6f2d636f6d707265737365642d706f696e746572730082858585890060c581800060c681820020ca810000cb810060c5818180828181c1bfbfbfc0c2c5c6c7c3818101810181
r2flutter -q -j -HHH -l 8~{snapshots[0].clusters_parsed}
r2flutter -q -j -HHH -l 8~{snapshots[0].clusters[0].fill_status}
r2flutter -q -j -HHH -l 8~{snapshots[0].clusters[1].alloc}
r2flutter -q -j -HHH -l 8~{snapshots[0].clusters[1].fill}
r2flutter -q -j -HHH -l 8~{snapshots[0].clusters[1].fill_status}
r2flutter -q -j -HHH -l 8~{snapshots[0].clusters[2].fill_status}
r2flutter -q -j -HHH -l 8~{snapshots[0].clusters[3].fill_status}
r2flutter -q -j -HHH -l 8~{snapshots[0].clusters[4].fill_status}
r2flutter -q -j -p~{cluster_index}
r2flutter -q -j -O PP+0x10~{pp_slot.cluster_index}
EOF
EXPECT=<<EOF
true
ok
variable
local_var_descriptors
ok
ok
ok
ok
4
4
EOF
RUN
+44
View File
@@ -0,0 +1,44 @@
NAME=entrypoint-ios
FILE=bins/ios/Runner.app/Frameworks/App.framework/App
CMDS=<<EOF
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -qE
EOF
EXPECT=<<EOF
0xe768
EOF
RUN
NAME=json-entrypoint-ios
FILE=bins/ios/Runner.app/Frameworks/App.framework/App
CMDS=<<EOF
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -jE
EOF
EXPECT=<<EOF
{"entrypoint":59240,"index":0,"code_index":0,"kind":"code","name":"method.PointerEvent.get:radiusMinor","vm_instr":16384,"vm_instr_size":42736,"iso_instr":59136,"iso_instr_size":1712608}
EOF
RUN
NAME=r2-entrypoint-ios
FILE=bins/ios/Runner.app/Frameworks/App.framework/App
CMDS=<<EOF
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -rE
EOF
EXPECT=<<EOF
# Dart AOT entrypoint and instruction snapshot data
fs dart
f dart.entrypoint = 0xe768
CC Dart code entrypoint it[0] method.PointerEvent.get:radiusMinor @ 0xe768
f dart.vm_instr = 0x4000
Cd 4 10684 @ 0x4000
CC Dart vm_instr snapshot instructions dword array size=0xa6f0 @ 0x4000
f dart.iso_instr = 0xe700
Cd 4 428152 @ 0xe700
CC Dart iso_instr snapshot instructions dword array size=0x1a21e0 @ 0xe700
EOF
RUN
+62 -34
View File
@@ -1,7 +1,9 @@
NAME=text-fields-synthetic
FILE=-
FILE=bins/synthetic/field_snapshot.bin
CMDS=<<EOF
!!../bin/r2flutter -c bins/synthetic/field_snapshot.bin 2>/dev/null
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -c
EOF
EXPECT=<<EOF
class TestClass implements TestInterface (size=48)
@@ -20,9 +22,11 @@ EOF
RUN
NAME=json-fields-synthetic
FILE=-
FILE=bins/synthetic/field_snapshot.bin
CMDS=<<EOF
!!../bin/r2flutter -j -c bins/synthetic/field_snapshot.bin 2>/dev/null
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -j -c
EOF
EXPECT=<<EOF
[{"ref":12,"name":"TestClass","library":{"ref":11,"name":"package:test"},"interfaces":[{"ref":23,"class_ref":13,"name":"TestInterface"}],"layout":{"instance_size":48,"type_params":1,"type_arg_offset":0},"flags":{"abstract":false,"enum":false,"mixin":false,"toplevel":false},"fields":[{"name":"fieldA","type":"int","offset":16,"flags":{"static":false,"final":false,"const":false,"late":false}},{"name":"items","type":"List<int>","offset":24,"flags":{"static":false,"final":false,"const":false,"late":false}},{"name":"genericItems","type":"List<X0>","offset":32,"flags":{"static":false,"final":false,"const":false,"late":false}},{"name":"callback","type":"(int) => String","offset":40,"flags":{"static":false,"final":false,"const":false,"late":false}}],"methods":[{"name":"compute","entry":0,"owner":"TestClass","signature":"(int) => String","kind_tag":0,"kind":"RegularFunction"}]},{"ref":13,"name":"TestInterface","library":{"ref":11,"name":"package:test"},"layout":{"instance_size":0,"type_params":0,"type_arg_offset":0},"flags":{"abstract":false,"enum":false,"mixin":false,"toplevel":false}}]
@@ -30,9 +34,11 @@ EOF
RUN
NAME=strings-fields-synthetic
FILE=-
FILE=bins/synthetic/field_snapshot.bin
CMDS=<<EOF
!!../bin/r2flutter -zz bins/synthetic/field_snapshot.bin 2>/dev/null
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -zz
EOF
EXPECT=<<EOF
0x00000018 74 :app "00000000000000000000000000000000product arm64 macos no-compressed-pointers"
@@ -49,9 +55,11 @@ EOF
RUN
NAME=json-strings-fields-synthetic
FILE=-
FILE=bins/synthetic/field_snapshot.bin
CMDS=<<EOF
!!../bin/r2flutter -j -xzz bins/synthetic/field_snapshot.bin 2>/dev/null
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -j -xzz
EOF
EXPECT=<<EOF
[{"ref":0,"len":74,"value":"00000000000000000000000000000000product arm64 macos no-compressed-pointers","category":"app","two_byte":false,"canonical":true,"addr":24},{"ref":1,"len":9,"value":"TestClass","category":"lib","two_byte":false,"canonical":true,"addr":110,"refs":[{"obj":12,"type":"class","kind":"class.name","name":"TestClass"}]},{"ref":2,"len":12,"value":"package:test","category":"lib","two_byte":false,"canonical":true,"addr":120,"refs":[{"obj":11,"type":"library","kind":"library.name","name":"package:test"}]},{"ref":3,"len":6,"value":"fieldA","category":"lib","two_byte":false,"canonical":true,"addr":133,"refs":[{"obj":25,"type":"field","kind":"field.name","name":"TestClass.fieldA"}]},{"ref":4,"len":5,"value":"items","category":"lib","two_byte":false,"canonical":true,"addr":144,"refs":[{"obj":26,"type":"field","kind":"field.name","name":"TestClass.items"}]},{"ref":5,"len":12,"value":"genericItems","category":"lib","two_byte":false,"canonical":true,"addr":150,"refs":[{"obj":27,"type":"field","kind":"field.name","name":"TestClass.genericItems"}]},{"ref":6,"len":8,"value":"callback","category":"lib","two_byte":false,"canonical":true,"addr":163,"refs":[{"obj":28,"type":"field","kind":"field.name","name":"TestClass.callback"}]},{"ref":7,"len":7,"value":"compute","category":"lib","two_byte":false,"canonical":true,"addr":172,"refs":[{"obj":29,"type":"function","kind":"method.name","name":"TestClass.compute"}]},{"ref":8,"len":13,"value":"TestInterface","category":"lib","two_byte":false,"canonical":true,"addr":180,"refs":[{"obj":12,"type":"class","kind":"class.interface","name":"TestClass"},{"obj":13,"type":"class","kind":"class.name","name":"TestInterface"}]}]
@@ -59,9 +67,11 @@ EOF
RUN
NAME=xrefs-fields-synthetic
FILE=-
FILE=bins/synthetic/field_snapshot.bin
CMDS=<<EOF
!!../bin/r2flutter -x bins/synthetic/field_snapshot.bin 2>/dev/null
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -x
EOF
EXPECT=<<EOF
metadata class.name class TestClass [ref=12] -> string TestClass [ref=2] @ 0x6e
@@ -88,46 +98,64 @@ EOF
RUN
NAME=r2-fields-synthetic-class-metadata
FILE=-
FILE=bins/synthetic/field_snapshot.bin
ARGS=-NN
CMDS=<<EOF
!!../bin/r2flutter -r -c bins/synthetic/field_snapshot.bin 2>/dev/null | grep -E '^(# Dart class symbols|ic\+dart_TestClass @ 0|ic\+dart_TestInterface @ 0|ic\+dart_TestClass:dart_TestInterface|ac dart_TestClass$|acb dart_TestClass dart_TestInterface 0|ic\+dart_TestClass\.\.callback void \* @ 0x28|ic\+dart_TestClass.compute @ 0x0|# field dart_TestClass.callback|# method dart_TestClass.compute)'
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -C
e bin.lang
ic
t dart_TestClass
EOF
EXPECT=<<EOF
# Dart class symbols and analysis metadata
ic+dart_TestClass @ 0
ac dart_TestClass
ic+dart_TestInterface @ 0
ic+dart_TestClass:dart_TestInterface
acb dart_TestClass dart_TestInterface 0
# field dart_TestClass.callback +0x28 type=(int) => String instance
ic+dart_TestClass..callback void * @ 0x28
ic+dart_TestClass.compute @ 0x0
# method dart_TestClass.compute kind=RegularFunction mode=instance cc=dyncc:x0+8,^:x0!T0 signature=(int) => String
ic+dart_TestInterface @ 0
dart
0x00000000 [0x00000000 - 0x00000000] 0 dart class 0 dart_TestClass :: dart_TestInterface
0x00000000 dart method 0 compute
0x00000010 dart field 0 fieldA
0x00000018 dart field 1 items
0x00000020 dart field 2 genericItems
0x00000028 dart field 3 callback
0x00000000 [0x00000000 - 0x00000000] 0 dart class 1 dart_TestInterface
'pf [16].qppp fieldA items genericItems callback
EOF
RUN
NAME=r2-fields-synthetic-safe-type-script
FILE=-
FILE=bins/synthetic/field_snapshot.bin
CMDS=<<EOF
!!../bin/r2flutter -r -c bins/synthetic/field_snapshot.bin 2>/dev/null | awk 'BEGIN { td = 0; bad = 0 } /^"td struct dart_TestClass \{ uint8_t _pad0\[0x10\]; int64_t fieldA; void \* items; void \* genericItems; void \* callback; \};"$/ { td = 1 } /^af @/ || /^afc / || /td struct\.dart/ || /\(int\) => String callback @/ { bad = 1 } END { print "td=" td; print "bad=" bad }'
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -r -c~?td struct dart_TestClass
r2flutter -r -c~?^af
r2flutter -r -c~?struct.dart
r2flutter -r -c~?callback
EOF
EXPECT=<<EOF
td=1
bad=0
1
0
0
3
EOF
RUN
NAME=r2-plugin-C-fields-synthetic
FILE=-
FILE=bins/synthetic/field_snapshot.bin
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 -C\" -qc ic -qc \"t dart_TestClass\" bins/synthetic/field_snapshot.bin 2>/dev/null" | awk '/class .* dart_TestClass( :: dart_TestInterface)?$/ { cls = 1 } /class .* dart_TestClass :: dart_TestInterface$/ { super = 1 } /method.* compute$/ { method = 1 } /field.* fieldA$/ { field = 1 } /^pf \[16\]\.qppp fieldA items genericItems callback$/ { type = 1 } END { print "class=" cls + 0; print "super=" super + 0; print "method=" method + 0; print "field=" field + 0; print "type=" type + 0 }'
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -C
ic~?&class,dart_TestClass
ic~?&dart_TestClass,dart_TestInterface
ic~?&method,compute
ic~?&field,fieldA
t dart_TestClass~?fieldA items genericItems callback
EOF
EXPECT=<<EOF
class=1
super=1
method=1
field=1
type=1
1
1
1
1
1
EOF
RUN
+4 -2
View File
@@ -1,7 +1,9 @@
NAME=text-funcs-android
FILE=-
FILE=bins/android/first/libapp.so
CMDS=<<EOF
!!../bin/r2flutter -f -l 3 bins/android/first 2>/dev/null
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -f -l 3
EOF
EXPECT=<<EOF
0x66768 Object.noSuchMethod
+12 -8
View File
@@ -1,21 +1,25 @@
NAME=text-funcs-authpass
FILE=-
FILE=bins/ios/AuthPass.app/Frameworks/App.framework/App
CMDS=<<EOF
!!../bin/r2flutter -f -l 3 bins/ios/AuthPass.app
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -f -l 3
EOF
EXPECT=<<EOF
0xfe28 func.0000fe28
0xfed4 func.0000fed4
0xff50 func.0000ff50
0xfe28 method._HashMap_3220832.addAll
0xfed4 method._HashMap_3220832.dyn:forEach
0xff50 method.MapBase.dyn:map
EOF
RUN
NAME=json-funcs-authpass
FILE=-
FILE=bins/ios/AuthPass.app/Frameworks/App.framework/App
CMDS=<<EOF
!!../bin/r2flutter -j -f -l 3 bins/ios/AuthPass.app
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -j -f -l 3
EOF
EXPECT=<<EOF
[{"addr":65064,"name":"func.0000fe28"},{"addr":65236,"name":"func.0000fed4"},{"addr":65360,"name":"func.0000ff50"}]
[{"addr":65064,"name":"method._HashMap_3220832.addAll","signature":"dynamic(_HashMap@3220832)"},{"addr":65236,"name":"method._HashMap_3220832.dyn:forEach","signature":"dynamic(_HashMap@3220832,dynamic)"},{"addr":65360,"name":"method.MapBase.dyn:map","signature":"dynamic(MapBase,dynamic)"}]
EOF
RUN
+14
View File
@@ -0,0 +1,14 @@
NAME=text-funcs-authpass-semantic
FILE=bins/ios/AuthPass.app/Frameworks/App.framework/App
CMDS=<<EOF
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -q -f~_secureHandshake_,registerBadCertificateCallback,_registerBadCertificateCallback_,_onBadCertificateWrapper_
EOF
EXPECT=<<EOF
0x31749c method._RawSecureSocket_14069316._secureHandshake_14069316
0x3194ec method._SecureFilterImpl_14069316.registerBadCertificateCallback
0x31954c method._SecureFilterImpl_14069316._registerBadCertificateCallback_14069316
0x3199d4 method._RawSecureSocket_14069316._onBadCertificateWrapper_14069316
EOF
RUN
+11 -7
View File
@@ -1,21 +1,25 @@
NAME=json-funcs-ios
FILE=-
FILE=bins/ios/Runner.app/Frameworks/App.framework/App
CMDS=<<EOF
!!../bin/r2flutter -j -f -l 3 bins/ios/Runner.app 2>/dev/null
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -j -f -l 3
EOF
EXPECT=<<EOF
[{"addr":59240,"name":"func.0000e768"},{"addr":59248,"name":"func.0000e770"},{"addr":59296,"name":"func.0000e7a0"}]
[{"addr":59240,"name":"method.PointerEvent.get:radiusMinor","signature":"dynamic(PointerEvent)"},{"addr":59248,"name":"func.0000e770"},{"addr":59296,"name":"method.PointerEvent.get:distance","signature":"dynamic(PointerEvent)"}]
EOF
RUN
NAME=r2-funcs-ios
FILE=-
FILE=bins/ios/Runner.app/Frameworks/App.framework/App
CMDS=<<EOF
!!../bin/r2flutter -r -f -l 2 bins/ios/Runner.app 2>/dev/null
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -r -f -l 2
EOF
EXPECT=<<EOF
f method.func.0000e768 = 0xe768
'@0xe768'CC func.0000e768
f method.PointerEvent.get:radiusMinor = 0xe768
'@0xe768'CC method.PointerEvent.get:radiusMinor
f method.func.0000e770 = 0xe770
'@0xe770'CC func.0000e770
EOF
+33 -17
View File
@@ -1,7 +1,9 @@
NAME=text-funcs-mafia
FILE=-
FILE=bins/android/mafia/libapp.so
CMDS=<<EOF
!!../bin/r2flutter -f -l 3 bins/android/mafia 2>/dev/null
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -f -l 3
EOF
EXPECT=<<EOF
0x5b69c0 method.DateTime.compareTo
@@ -11,9 +13,11 @@ EOF
RUN
NAME=r2-funcs-mafia
FILE=-
FILE=bins/android/mafia/libapp.so
CMDS=<<EOF
!!../bin/r2flutter -r -f -l 2 bins/android/mafia 2>/dev/null
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -r -f -l 2
EOF
EXPECT=<<EOF
f method.DateTime.compareTo = 0x5b69c0
@@ -24,9 +28,11 @@ EOF
RUN
NAME=quiet-r2-funcs-mafia
FILE=-
FILE=bins/android/mafia/libapp.so
CMDS=<<EOF
!!../bin/r2flutter -qrf -l 2 bins/android/mafia 2>/dev/null
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -qrf -l 2
EOF
EXPECT=<<EOF
f method.DateTime.compareTo = 0x5b69c0
@@ -35,12 +41,14 @@ EOF
RUN
NAME=json-funcs-mafia
FILE=-
FILE=bins/android/mafia/libapp.so
CMDS=<<EOF
!!../bin/r2flutter -j -f -l 3 bins/android/mafia 2>/dev/null
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -j -f -l 3
EOF
EXPECT=<<EOF
[{"addr":5990848,"name":"method.DateTime.compareTo"},{"addr":5991076,"name":"method.Duration.compareTo"},{"addr":5991268,"name":"method.Duration.dyn:_"}]
[{"addr":5990848,"name":"method.DateTime.compareTo","signature":"dynamic(DateTime)"},{"addr":5991076,"name":"method.Duration.compareTo","signature":"dynamic(Duration)"},{"addr":5991268,"name":"method.Duration.dyn:_","signature":"dynamic(Duration,dynamic)"}]
EOF
RUN
@@ -48,9 +56,11 @@ RUN
# stub.Allocate<ClassName>Stub, not shifted into a neighboring method name by
# the name-pool fallback. 0x5b6bf4 is the AllocateDurationStub in this fixture.
NAME=funcs-mafia-allocate-stub
FILE=-
FILE=bins/android/mafia/libapp.so
CMDS=<<EOF
!!../bin/r2flutter -f -q bins/android/mafia 2>/dev/null | grep -E '^0x5b6bf4 '
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -f -q~0x5b6bf4
EOF
EXPECT=<<EOF
0x5b6bf4 stub.AllocateDurationStub
@@ -60,9 +70,11 @@ RUN
# The same address must NOT appear under any method.fn_* fallback name, which
# was the bug before the Code-owner-cid fix.
NAME=funcs-mafia-allocate-stub-not-fallback
FILE=-
FILE=bins/android/mafia/libapp.so
CMDS=<<EOF
!!../bin/r2flutter -f -q bins/android/mafia 2>/dev/null | grep -c '^0x5b6bf4 method.fn_'
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -f -q~?&0x5b6bf4,method.fn_
EOF
EXPECT=<<EOF
0
@@ -72,9 +84,11 @@ RUN
# Type-test stubs (Code owner is an AbstractType) must be synthesized as
# stub.typetest_* rather than pulling from the name pool.
NAME=funcs-mafia-typetest-stub-present
FILE=-
FILE=bins/android/mafia/libapp.so
CMDS=<<EOF
!!../bin/r2flutter -f -q bins/android/mafia 2>/dev/null | grep -c ' stub.typetest_'
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -f -q~?stub.typetest_
EOF
EXPECT=<<EOF
1277
@@ -85,9 +99,11 @@ RUN
# the count is 4096; if it drops noticeably (e.g. back to zero) the owner-cid
# lookup has regressed.
NAME=funcs-mafia-allocate-stub-count
FILE=-
FILE=bins/android/mafia/libapp.so
CMDS=<<EOF
!!../bin/r2flutter -f -q bins/android/mafia 2>/dev/null | grep -c ' stub.Allocate'
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -f -q~?stub.Allocate
EOF
EXPECT=<<EOF
4096
+4 -2
View File
@@ -1,7 +1,9 @@
NAME=text-funcs-mafia-closure
FILE=-
FILE=bins/android/mafia/libapp.so
CMDS=<<EOF
!!../bin/r2flutter -f -q bins/android/mafia 2>/dev/null | grep -E '0x5b72b0|0x5b74cc'
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -f -q~0x5b72b0,0x5b74cc
EOF
EXPECT=<<EOF
0x5b72b0 method.FlutterError._anon_closure
+12 -6
View File
@@ -1,7 +1,9 @@
NAME=funcs-mafia-name-pool-window
FILE=-
FILE=bins/android/mafia/libapp.so
CMDS=<<EOF
!!../bin/r2flutter -f -q -n -l 6 bins/android/mafia 2>/dev/null
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -f -q -n -l 6
EOF
EXPECT=<<EOF
0x5b69c0 method.DateTime.compareTo
@@ -14,15 +16,19 @@ EOF
RUN
NAME=funcs-mafia-name-pool-stub-counts
FILE=-
FILE=bins/android/mafia/libapp.so
CMDS=<<EOF
!!../bin/r2flutter -f -q -n bins/android/mafia 2>/dev/null | awk '$1=="0x5b6bf4" || $1=="0x5b6f6c" || $1=="0x5bb750" {print} / stub\.Allocate/ {alloc++} / stub\.typetest_/ {type++} END {print "alloc=" alloc; print "typetest=" type}'
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -f -q -n~0x5b6bf4,0x5b6f6c,0x5bb750
r2flutter -f -q -n~?stub.Allocate
r2flutter -f -q -n~?stub.typetest_
EOF
EXPECT=<<EOF
0x5b6bf4 stub.AllocateDurationStub
0x5b6f6c stub.typetest_13
0x5bb750 stub.AllocateStringBufferStub
alloc=4096
typetest=1277
4096
1277
EOF
RUN
+4 -3
View File
@@ -1,7 +1,9 @@
NAME=funcs-mafia-vm-symbol-operators
FILE=-
FILE=bins/android/mafia/libapp.so
CMDS=<<EOF
!!../bin/r2flutter -f -q bins/android/mafia 2>/dev/null | awk '$1=="0xe11f60" || $1=="0xe1205c" || $1=="0xe12174" || $1=="0xe12220" || $1=="0xe122a8" {print} $1=="0xe12220" || $1=="0xe122a8" {names[$2]=1} END {n=0; for (name in names) {n++} print "unique_greaterthan=" n}'
L ../src/r2/core_flutter.dylib
L ../src/r2/core_flutter.so
r2flutter -f -q~0xe11f60,0xe1205c,0xe12174,0xe12220,0xe122a8
EOF
EXPECT=<<EOF
0xe11f60 method._Double_0150898.op_add
@@ -9,6 +11,5 @@ EXPECT=<<EOF
0xe12174 method._Double_0150898.op_gt
0xe12220 method._Double_0150898._greaterThan_0150898
0xe122a8 method._IntegerImplementation_0150898.op_gt
unique_greaterthan=2
EOF
RUN
Loaded 100 of 167 files, more files were not shown because too many files have changed in this diff. Show more