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.
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.
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.
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.
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).
- 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).
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).
- 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.
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).
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.
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.
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.
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.
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.
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]>
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]>
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]>
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]>
* 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
- 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