mirror of
https://github.com/radareorg/r2flutter.git
synced 2026-11-07 06:26:00 -10:00
Add more documentation
This commit is contained in:
1 parent
9b9a112561
commit
b69ebf35a4
3 files changed
+630
-24
No files matched your search
+160
@@ -0,0 +1,160 @@
|
||||
## Version Identification in Dart/Flutter AOT Snapshots
|
||||
|
||||
### The Snapshot Hash (Checksum)
|
||||
|
||||
**What it is**: A **32-byte ASCII MD5 hash** (Snapshot_hash) generated at compile time using the `make_version.py` script in the Flutter Engine source . This hash is **not of the binary itself**, but rather a hash representing the **Dart VM version and snapshot format**.
|
||||
|
||||
**Where it's stored**:
|
||||
- **Android**: In `libapp.so` (the AOT snapshot) and `libflutter.so` (the Flutter engine)
|
||||
- **iOS**: In `App.framework/App` and `Flutter.framework/Flutter`
|
||||
- **Purpose**: Runtime compatibility checking between the engine and the snapshot
|
||||
|
||||
### How Tools Associate Hash with Version
|
||||
|
||||
#### 1. **reFlutter's Approach: Static CSV Table**
|
||||
reFlutter uses a manually maintained **`enginehash.csv`** file that maps:
|
||||
- `Snapshot_hash` → `Engine_commit` → Flutter version → Dart version
|
||||
|
||||
Example from their table:
|
||||
```csv
|
||||
Flutter_Version, Engine_commit, Snapshot_hash
|
||||
2.10.0, 5f105a6ca7a5ac7b8bc9b241f4c2d86f4188cf5c, d56742caf7b3b3f4bd2df93a9bbb5503
|
||||
```
|
||||
|
||||
**Update mechanism**: The table is **periodically updated manually** when new Flutter versions are released. If a hash isn't found, the tool reports "Engine not supported" .
|
||||
|
||||
#### 2. **unflutter's Approach: Constraint-Based Version Detection**
|
||||
unflutter uses a **different philosophy** - it doesn't rely on a downloadable table. Instead:
|
||||
|
||||
- The **version hash in the snapshot header** selects a **constraint set** (CID table, tag encoding, pointer compression rules)
|
||||
- Version-specific data is **statically defined in code** as version profiles:
|
||||
- Dart 2.10.0: CID-Int32, Uncompressed pointers, 4 header fields
|
||||
- Dart 2.18.0: CID-Shift1, Compressed pointers, Signed refs
|
||||
- Dart 3.4.3+: ObjectHeader, Compressed, Record types
|
||||
|
||||
**Key insight**: unflutter proves you can **statically define all version constraints** without runtime downloads because the snapshot format changes are deterministic and versioned.
|
||||
|
||||
#### 3. **Blutter's Approach: SDK Compilation**
|
||||
Blutter **compiles the matching Dart SDK** for each version:
|
||||
- Detects version hash from snapshot
|
||||
- Downloads/checkout the exact Dart SDK source for that version
|
||||
- Builds the VM library to parse the snapshot
|
||||
|
||||
This is the most accurate but slowest approach.
|
||||
|
||||
---
|
||||
|
||||
## What Data is Version-Associated?
|
||||
|
||||
### Version-Specific Data Structures
|
||||
|
||||
| Component | Version-Dependent? | Description |
|
||||
|-----------|-------------------|-------------|
|
||||
| **Snapshot Header** | Yes | Magic (0xf5f5dcdc), version hash (32 bytes), feature flags |
|
||||
| **CID Table** | Yes | Maps Class IDs to cluster handlers; changes per version |
|
||||
| **Tag Encoding** | Yes | How object tags are packed (CID-Int32, CID-Shift1, ObjectHeader) |
|
||||
| **Pointer Compression** | Yes | 32-bit vs 64-bit references (introduced in 2.18.0) |
|
||||
| **Cluster Grammar** | Yes | How alloc/fill phases encode object fields |
|
||||
| **THR (Thread) Fields** | Yes | Offset maps for thread-local storage access |
|
||||
| **Instruction Layout** | Partial | Stubs vs code regions, entry point formats |
|
||||
|
||||
### Static vs Dynamic Definition
|
||||
|
||||
**You can statically define everything.** Here's why:
|
||||
|
||||
1. **The version hash is in the snapshot itself** - you read it directly from the binary at a known offset
|
||||
2. **Format changes are versioned** - Dart uses semantic versioning, and snapshot format changes happen at specific version boundaries
|
||||
3. **Constraint sets are finite** - unflutter demonstrates this by having version profiles for every major Dart version from 2.10.0 to 3.10.7
|
||||
|
||||
### Version Hash Location in Binary
|
||||
|
||||
```
|
||||
Snapshot Header Structure:
|
||||
[0x00-0x03] Magic: 0xf5f5dcdc
|
||||
[0x04-0x23] Version Hash: 32-byte ASCII (e.g., "d56742caf7b3b3f4bd2df93a9bbb5503")
|
||||
[0x24-...] Feature flags, pointer size, etc.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Recommendation for Your Implementation
|
||||
|
||||
### Option 1: Static Constraint Tables (Recommended)
|
||||
Like unflutter, define version constraints statically:
|
||||
|
||||
```go
|
||||
// Example version profile structure
|
||||
var VersionProfiles = map[string]VersionProfile{
|
||||
"d56742caf7b3b3f4bd2df93a9bbb5503": {
|
||||
DartVersion: "2.16.0",
|
||||
TagStyle: "CID-Shift1",
|
||||
PointerSize: 8, // Uncompressed
|
||||
HeaderFields: 5,
|
||||
CIDTable: Pre210CIDTable,
|
||||
ClusterHandlers: Pre210ClusterHandlers,
|
||||
},
|
||||
"1441d6b13b8623fa7fbf61433abebd31": {
|
||||
DartVersion: "2.17.0",
|
||||
TagStyle: "CID-Shift1",
|
||||
PointerSize: 4, // Compressed pointers
|
||||
HeaderFields: 5,
|
||||
CIDTable: Post218CIDTable,
|
||||
ClusterHandlers: Post218ClusterHandlers,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Option 2: Hybrid Approach
|
||||
- **Static**: Core constraint tables for known versions
|
||||
- **Dynamic**: Download new version profiles when encountering unknown hashes (rare, only for bleeding-edge Flutter versions)
|
||||
|
||||
### Where to Get Version Data
|
||||
|
||||
1. **hadysata/flutter-versions** repo: Comprehensive table of Flutter version ↔ Dart version ↔ Snapshot hash ↔ Engine commit
|
||||
2. **mildsunrise/darter/info/versions.md**: Historical snapshot hash reference
|
||||
3. **Google Storage**: `storage.googleapis.com/flutter_infra_release/flutter/<engine_commit>/` for engine artifacts
|
||||
|
||||
### Critical Files in Dart SDK Source
|
||||
|
||||
If you need to extract version-specific logic from the Dart SDK source :
|
||||
- `runtime/vm/clustered_snapshot.cc`: Serialization/deserialization logic
|
||||
- `runtime/vm/raw_object.h`: Object tag definitions per version
|
||||
- `runtime/vm/object.cc`: Class ID assignments
|
||||
|
||||
---
|
||||
|
||||
## 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 .
|
||||
|
||||
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) .
|
||||
|
||||
---
|
||||
|
||||
## r2flutter Implementation
|
||||
|
||||
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)
|
||||
- Sources: unflutter version.go, reFlutter enginehash.csv, blutter precompiled SDKs
|
||||
|
||||
### Version Profiles
|
||||
Each supported Dart version has a profile with:
|
||||
- Tag encoding style (CID-Int32, CID-Shift1, ObjectHeader)
|
||||
- Header field count (4-6 depending on version)
|
||||
- CID table mapping (Class, Function, Code, String, Array, etc.)
|
||||
- Compressed pointer configuration
|
||||
- Number of predefined CIDs
|
||||
|
||||
### Supported Versions
|
||||
| Dart Version | Tag Style | Notes |
|
||||
|--------------|-----------|-------|
|
||||
| 2.10.0 | Int32 CID | Pre-FunctionType, single cluster loop |
|
||||
| 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 |
|
||||
|
||||
Unknown hashes fall back to v3.9.2 profile with ObjectHeader encoding.
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
## What is a Cluster in Dart AOT?
|
||||
|
||||
A **cluster** is the fundamental serialization unit in Dart's AOT snapshot format. It's a group of objects of the **same class/type** that are serialized together using a shared encoding scheme .
|
||||
|
||||
### The Two-Phase Cluster Serialization
|
||||
|
||||
Dart AOT snapshots use a two-phase process where clusters are the core organizational structure:
|
||||
|
||||
```
|
||||
Phase 1: ALLOC → Phase 2: FILL
|
||||
```
|
||||
|
||||
**Phase 1: Allocation (Alloc)**
|
||||
- Walks through clusters in **Class ID (CID) order**
|
||||
- Each cluster declares: *"I contain N objects of class X"*
|
||||
- Assigns sequential **reference IDs** to every object
|
||||
- **No actual data is read yet** - only counts and reservations
|
||||
|
||||
**Phase 2: Fill**
|
||||
- Walks through the same clusters again
|
||||
- Reads actual field values: string bytes, reference IDs, integers
|
||||
- Encoding varies by object type and Dart version
|
||||
|
||||
### Cluster Structure
|
||||
|
||||
```
|
||||
Cluster Header:
|
||||
- CID (Class ID): Identifies the class type (e.g., String, List, Code)
|
||||
- Count: Number of objects in this cluster
|
||||
- Flags: Encoding options
|
||||
|
||||
Cluster Body (Fill Phase):
|
||||
- Object 1 field data
|
||||
- Object 2 field data
|
||||
- ... N times
|
||||
```
|
||||
|
||||
### Why Clusters Exist
|
||||
|
||||
1. **Deduplication**: Objects of the same type share encoding logic
|
||||
2. **Sequential Access**: Better cache locality during deserialization
|
||||
3. **Compression of Metadata**: Class information stored once per cluster, not per object
|
||||
4. **Version Flexibility**: Different Dart versions can change cluster handlers independently
|
||||
|
||||
### Cluster Types (CID Examples)
|
||||
|
||||
| CID | Class | Description |
|
||||
|-----|-------|-------------|
|
||||
| 1-5 | Object, Class, Function, etc. | VM internal classes |
|
||||
| 50+ | String variants | One-byte, two-byte strings |
|
||||
| 60+ | Array types | TypedData, GrowableObjectArray |
|
||||
| 100+ | Code objects | Compiled function machine code |
|
||||
|
||||
### Real-World Example from unflutter
|
||||
|
||||
When unflutter parses a snapshot:
|
||||
```
|
||||
1. Reads snapshot header → finds version hash
|
||||
2. Loads CID table → maps CID → cluster handler
|
||||
3. For each cluster:
|
||||
- ALLOC: Reads count, assigns reference IDs
|
||||
- FILL: Parses field values based on CID-specific grammar
|
||||
4. Result: Complete object graph with all strings, classes, functions
|
||||
```
|
||||
|
||||
### Cluster Grammar Constraints
|
||||
|
||||
The unflutter tool treats clusters as **constraint-based parsing**:
|
||||
- **ELF invariants** eliminate invalid binaries
|
||||
- **Snapshot magic** (`0xf5f5dcdc`) confirms Dart format
|
||||
- **Version hash** selects exact CID table and encoding
|
||||
- **Cluster alloc counts** fix object population
|
||||
- **Fill parsing** recovers field values
|
||||
|
||||
### Key Insight for Reverse Engineering
|
||||
|
||||
**Clusters are why plain text extraction works**: Because clusters group objects by type, all strings of a certain type are stored in sequence with predictable offsets. This allows tools like Blutter and unflutter to walk the cluster and extract every string without executing the code.
|
||||
|
||||
The cluster format is **deterministic and version-specific**, which is why reverse engineering tools need to handle format changes across Dart versions explicitly .
|
||||
+391
-24
@@ -1,29 +1,396 @@
|
||||
# Support Matrix
|
||||
|
||||
r2flutter currently carries layout profiles for Dart AOT snapshots from **Dart 2.10.0 to 3.10.7** (roughly Flutter **1.22.x to 3.38.x**).
|
||||
This document summarizes what the current first-party source supports. It is a
|
||||
parser/layout support note, not a promise that every Flutter engine patch in a
|
||||
range has regression coverage.
|
||||
|
||||
This matrix describes parser/layout support, not exhaustive regression coverage for every engine patch release. Platform notes summarize the binary/container differences the current parser accounts for when applying the same Dart snapshot profile.
|
||||
r2flutter targets Dart AOT snapshots embedded in Flutter applications. It uses
|
||||
radare2 for binary loading and then reads Dart snapshot metadata, data images,
|
||||
instruction tables, and strings directly.
|
||||
|
||||
| Dart Version | Flutter Version | Tag Style | Notes | Platform Details |
|
||||
|--------------|-----------------|-----------|-------|------------------|
|
||||
| 2.10.0 | 1.22.x | Int32 CID | Pre-FunctionType split | Android: legacy `libapp.so` ELF snapshots in the pre-compressed-pointer era. iOS: same Int32 profile in Mach-O-style app binaries, typically with full-width pointers. |
|
||||
| 2.13.0 | 2.2.x - 2.3.x | Int32 CID | Split canonical clusters | Android: ELF parser still sees older non-ObjectHeader snapshots. iOS: same snapshot layout family, with platform differences mostly limited to Mach-O container handling. |
|
||||
| 2.14.0 | 2.4.x - 2.5.x | CID Shift1 | TypeParameters added | Android: first Shift1-era `libapp.so` layouts. iOS: matching Shift1 snapshot profile, usually without Android-style compressed-pointer behavior. |
|
||||
| 2.15.0 | 2.6.x - 2.7.x | CID Shift1 | NativePointer inserted | Android: ELF snapshots with updated CID table. iOS: same Dart layout profile applied through Mach-O snapshot discovery. |
|
||||
| 2.16.0 | 2.8.x - 2.16.x | CID Shift1 | ConstMap/ConstSet added | Android: broader Flutter 2.x coverage on `libapp.so`. iOS: same Shift1 decoding, with iOS-specific work mostly in binary loading rather than object layout. |
|
||||
| 2.17.6 | 2.17.x | CID Shift1 | WeakReference added | Android: final pre-2.18 range before compressed pointers become common. iOS: same snapshot family, generally still treated as full-width-pointer binaries. |
|
||||
| 2.18.0 | 3.3.x | CID Shift1 | SuspendState added | Android: compressed-pointer-era builds become common and cluster counts can grow significantly. iOS: still uses the same Shift1 layout profile, but current samples remain easier to walk due to smaller metadata. |
|
||||
| 2.19.0 | 3.7.x | CID Shift1 | RecordType/Record added | Android: ELF snapshots continue in the compressed-pointer era. iOS: same profile on Mach-O, with platform deltas still centered on container parsing and address mapping. |
|
||||
| 3.0.5 | 3.10.x - 3.12.x | CID Shift1 | WeakArray added | Android: Shift1 + compressed pointers remain the main path. iOS: Mach-O snapshots still use the matching Shift1 layout without moving to ObjectHeader yet. |
|
||||
| 3.1.0 | 3.13.x | CID Shift1 | TypeRef removed | Android: updated CID table in ELF snapshots. iOS: same layout transition applied in Mach-O binaries. |
|
||||
| 3.2.5 | 3.16.x | CID Shift1 | PoolType swapped | Android: late Shift1 profile before ObjectHeader. iOS: same late-Shift1 profile, still generally observed with full-width pointers. |
|
||||
| 3.3.0 | 3.19.x | CID Shift1 | Last Shift1 format | Android: last Shift1 generation before the ObjectHeader change. iOS: same final Shift1 profile on Mach-O binaries. |
|
||||
| 3.4.3 | 3.22.x | ObjectHeader | New tag encoding | Android: ObjectHeader + compressed pointers are the main modern `libapp.so` target. iOS: ObjectHeader too, but current samples keep full-width pointers and smaller cluster metadata. |
|
||||
| 3.5.0 | 3.24.x | ObjectHeader | ObjectHeader family stabilized | Android: modern ELF path with ObjectHeader parsing. iOS: same tag format with Mach-O container differences only. |
|
||||
| 3.6.2 | 3.27.x | ObjectHeader | Incremental CID/profile updates | Android: ObjectHeader remains the expected format. iOS: same ObjectHeader profile, typically with simpler address translation in current samples. |
|
||||
| 3.7.0 | 3.29.x | ObjectHeader | Ongoing ObjectHeader era | Android: modern compressed-pointer snapshots. iOS: same ObjectHeader layout profile in app binaries. |
|
||||
| 3.8.1 | 3.32.x | ObjectHeader | Ongoing ObjectHeader era | Android: ELF support continues with the same high-level parser flow. iOS: Mach-O support continues with the same snapshot profile selection. |
|
||||
| 3.9.2 | 3.35.x | ObjectHeader | Fallback baseline for unknown hashes | Android: default fallback profile for unknown modern ELF hashes. iOS: default fallback profile for unknown modern Mach-O hashes. |
|
||||
| 3.10.7 | 3.38.x | ObjectHeader | Current latest | Android: newest known `libapp.so` profile in-tree. iOS: newest known app-binary profile in-tree. |
|
||||
## Main Targets
|
||||
|
||||
Unknown snapshot hashes currently fall back to the **v3.9.2** profile with ObjectHeader encoding.
|
||||
| Area | Supported in source |
|
||||
|------|---------------------|
|
||||
| App containers | Android directories containing `libapp.so`; iOS `.app` directories containing `Frameworks/App.framework/App`; direct binary paths. |
|
||||
| Binary loaders | Whatever radare2 `RBin` opens, with explicit code paths for ELF-style symbols/sections and Mach-O app binaries. |
|
||||
| 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`. |
|
||||
| Obfuscation maps | Flutter/Dart `--save-obfuscation-map` JSON arrays are supported with `-o <file>` or `e r2flutter.mapfile=<file>` in the plugin. |
|
||||
|
||||
## Layout Profiles
|
||||
|
||||
`CWS` is the profile default compressed word size. The actual value can be
|
||||
overridden by snapshot feature flags: `compressed-pointer` sets CWS to 4 unless
|
||||
`no-compressed-pointer` or `no-compressed` is present; otherwise CWS is 8.
|
||||
Compressed ObjectHeader snapshots, and compressed Shift1 snapshots from Dart
|
||||
2.19 onward, can raise object alignment to 64 bytes.
|
||||
|
||||
The CID column is compacted as:
|
||||
|
||||
`class/function/code/string/one_byte_string/two_byte_string/array/mint/object_pool`
|
||||
|
||||
| Dart | Flutter range | Tag style | CWS | Header fields | Predefined CIDs | CID set | Source note |
|
||||
|------|---------------|-----------|-----|---------------|-----------------|---------|-------------|
|
||||
| 2.10.0 | 1.22.x | `CID_INT32` | 8 | 4 | 156 | `4/6/16/80/81/82/78/53/20` | Pre-FunctionType split. |
|
||||
| 2.13.0 | 2.2.x - 2.3.x | `CID_INT32` | 8 | 5 | 148 | `4/6/15/77/78/79/75/51/18` | Split canonical clusters. |
|
||||
| 2.14.0 | 2.4.x - 2.5.x | `CID_SHIFT1` | 8 | 5 | 152 | `4/6/16/81/82/83/79/54/20` | TypeParameters era. |
|
||||
| 2.15.0 | 2.6.x - 2.7.x | `CID_SHIFT1` | 8 | 5 | 153 | `5/7/17/82/83/84/79/55/21` | NativePointer inserted. |
|
||||
| 2.16.0 | 2.8.x - 2.16.x | `CID_SHIFT1` | 8 | 6 | 154 | `5/7/17/84/85/86/81/55/21` | ConstMap/ConstSet added. |
|
||||
| 2.17.6 | 2.17.x | `CID_SHIFT1` | 4 | 6 | 158 | `5/7/17/89/90/91/86/59/21` | WeakReference added. |
|
||||
| 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.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. |
|
||||
| 3.5.0 | 3.24.x | `OBJECT_HEADER` | 4 | 5 | 174 | `5/7/18/92/93/94/89/60/22` | ObjectHeader family. |
|
||||
| 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. |
|
||||
|
||||
All profiles use heap object tag `1`, default alignment `16`, and instruction
|
||||
table capacity `20000` before feature-flag or `offsets.json` adjustments.
|
||||
|
||||
## Known Snapshot Hashes
|
||||
|
||||
These hashes are mapped directly by `src/lib/dart_version.c`.
|
||||
|
||||
| Dart version | Hashes |
|
||||
|--------------|--------|
|
||||
| 2.10.0 | `8ee4ef7a67df9845fba331734198a953` |
|
||||
| 2.13.0 | `e4a09dbf2bb120fe4674e0576617a0dc`, `34f6eec64e9371856eaaa278ccf56538`, `7a5b240780941844bae88eca5dbaa7b8` |
|
||||
| 2.14.0 | `9cf77f4405212c45daf608e1cd646852`, `659a72e41e3276e882709901c27de33d` |
|
||||
| 2.15.0 | `f10776149bf76be288def3c2ca73bdc1`, `24d9d411c2f90c8fbe8907f99e89d4b0` |
|
||||
| 2.16.0 | `d56742caf7b3b3f4bd2df93a9bbb5503`, `3318fe66091c0ffbb64faec39976cb7d`, `adf563436d12ba0d50ea5beb7f3be1bb` |
|
||||
| 2.17.6 | `1441d6b13b8623fa7fbf61433abebd31`, `a0cb0c928b23bc17a26e062b351dc44d`, `ded6ef11c73fdc638d6ff6d3ad22a67b` |
|
||||
| 2.18.0 | `b0e899ec5a90e4661501f0b69e9dd70f`, `b6d0a1f034d158b0d37b51d559379697`, `8e50e448b241be23b9e990094f4dca39`, `6a9b5a03a7e784a4558b10c769f188d9` |
|
||||
| 2.19.0 | `adb4292f3ec25074ca70abcd2d5c7251`, `501ef5cbd64ca70b6b42672346af6a8a` |
|
||||
| 3.0.5 | `90b56a561f70cd55e972cb49b79b3d8b`, `aa64af18e7d086041ac127cc4bc50c5e`, `36b0375d284ee2af0d0fffc6e6e48fde`, `16ad76edd19b537bf6ea64fdd31977a7` |
|
||||
| 3.1.0 | `7dbbeeb8ef7b91338640dca3927636de` |
|
||||
| 3.2.5 | `f71c76320d35b65f1164dbaa6d95fe09` |
|
||||
| 3.3.0 | `ee1eb666c76a5cb7746faf39d0b97547` |
|
||||
| 3.4.3 | `d20a1be77c3d3c41b2a5accaee1ce549` |
|
||||
| 3.5.0 | `80a49c7111088100a233b2ae788e1f48`, `cda356e9bae476c70de33809fd92e009` |
|
||||
| 3.6.2 | `2858c2c0920495f00b9bce9edf6a8cd9`, `f956f595844a2f845a55707faaaa51e4` |
|
||||
| 3.7.0 | `d91c0e6f35f0eb2e44124e8f42aa44a7` |
|
||||
| 3.8.1 | `830f4f59e7969c70b595182826435c19` |
|
||||
| 3.9.2 | `97ff04a728735e6b6b098bdf983faaba` |
|
||||
| 3.10.7 | `1ce86630892e2dca9a8543fdb8ed8e22` |
|
||||
|
||||
`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`.
|
||||
|
||||
## Snapshot Discovery
|
||||
|
||||
Discovery first looks for these exact symbol spellings:
|
||||
|
||||
- `_kDartVmSnapshotData`
|
||||
- `DartVmSnapshotData`
|
||||
- `_kDartVmSnapshotInstructions`
|
||||
- `DartVmSnapshotInstructions`
|
||||
- `_kDartIsolateSnapshotData`
|
||||
- `DartIsolateSnapshotData`
|
||||
- `_kDartIsolateSnapshotInstructions`
|
||||
- `DartIsolateSnapshotInstructions`
|
||||
|
||||
If symbols are missing, readable sections are scanned in 4 KB windows for
|
||||
snapshot magic. Candidates that parse as data snapshots are split from
|
||||
instruction snapshots; the smaller data snapshot is treated as VM data and the
|
||||
larger as isolate data. The same size heuristic is used for instruction
|
||||
snapshots.
|
||||
|
||||
Header parsing reads:
|
||||
|
||||
- magic, total length, and snapshot kind
|
||||
- 32-byte hash
|
||||
- null-terminated feature string, capped at 2048 scanned bytes
|
||||
- base object count, object count, cluster count, instruction-table length,
|
||||
instruction-table data offset
|
||||
- cluster stream start
|
||||
|
||||
Cluster values are rejected or downgraded when they are obviously too large:
|
||||
cluster count must stay below 1,000,000 and object count below 10,000,000 for
|
||||
full deserialization.
|
||||
|
||||
## Extraction Support
|
||||
|
||||
### Functions and Names
|
||||
|
||||
Function discovery merges several sources:
|
||||
|
||||
- instruction-table entries
|
||||
- class method metadata recovered from clusters or data image scans
|
||||
- legacy clustered Function objects
|
||||
- loader symbols only when stubs are explicitly included internally
|
||||
|
||||
The default CLI and plugin set `no_stubs=true`, so loader-provided ELF/Mach-O
|
||||
stub symbols are skipped. That keeps output focused on Dart-derived names.
|
||||
|
||||
Recovered method names are normalized into radare2-safe names. Class-method
|
||||
names use the `method.<library>.<owner>.<leaf>` shape. Library names strip
|
||||
`package:` and trailing `.dart`; non-name characters collapse to underscores.
|
||||
Constructors, getters, setters, anonymous closures, and Dart operators are
|
||||
renamed into stable leaf names such as `ctor`, `get_name`, `set_name`,
|
||||
`_anon_closure`, `op_eq`, `op_add`, and `op_ushr`.
|
||||
|
||||
The optional `-n` / `r2flutter.namepool` fallback consumes `package:` and
|
||||
`dart:` strings from the data image as sequential names. It is intentionally
|
||||
opt-in because one wrong stub/function distinction shifts every later name.
|
||||
|
||||
### Instruction Table
|
||||
|
||||
`-i` exposes the instruction table. JSON output includes:
|
||||
|
||||
- table `length`
|
||||
- `first_entry_with_code`
|
||||
- `canonical_stack_map_entries_offset`
|
||||
- per-entry `index`, `code_index`, `address`, `pc_offset`,
|
||||
`stack_map_offset`, `kind`, and optional `name`
|
||||
|
||||
The decoder supports:
|
||||
|
||||
- linear fallback when `itdata == 0`: `iso_instr + index * 4`
|
||||
- fixed table data with 8-byte entries (`pc_offset`, `stack_map_offset`)
|
||||
- header probing near `data_image_base + itdata`
|
||||
- scanning OneByteString-like payloads for embedded table data
|
||||
- bounded fallback scans in the first data-image region
|
||||
- varint-encoded table fallback around the expected table address
|
||||
|
||||
Entry names are resolved in this order: modern cluster code-index map, loader
|
||||
symbol at the entrypoint, data-image code-name scan, nearby stack-map string,
|
||||
opt-in name pool, owner-kind stub names, then `method.fn_<index>`.
|
||||
|
||||
Modern compressed ObjectHeader snapshots also classify code owners so allocation
|
||||
stubs, type-test stubs, VM stubs, and regular functions do not consume the same
|
||||
name-pool stream.
|
||||
|
||||
### Modern ObjectHeader Naming
|
||||
|
||||
The modern cluster scanner is enabled only for snapshots with:
|
||||
|
||||
- `OBJECT_HEADER` tag style
|
||||
- CWS 4 compressed pointers
|
||||
|
||||
It walks allocation/fill records for strings, classes, libraries, patch
|
||||
classes, closure data, functions, code objects, arrays, typed data,
|
||||
object pools, contexts, records, and common runtime objects. Its primary goal
|
||||
is name recovery, not full object reconstruction.
|
||||
|
||||
It maps:
|
||||
|
||||
- Function owners to `method.<owner>.<name>`
|
||||
- closure data to `_anon_closure`
|
||||
- class-owned Code objects to `stub.Allocate<ClassName>Stub`
|
||||
- type-owned Code objects to `stub.TypeTest_<TypeName>`
|
||||
- ownerless Code objects to VM stubs
|
||||
|
||||
Full-width ObjectHeader snapshots still use the layout profile, data-image
|
||||
scans, instruction-table decoding, and string scans, but they do not take this
|
||||
compressed modern cluster naming path.
|
||||
|
||||
### Classes, Types, Fields, and Methods
|
||||
|
||||
`-c` dumps class metadata. `-T` prints a type-oriented view and runs enum
|
||||
recovery. `-C` in the radare2 plugin emits type definitions.
|
||||
|
||||
Class output can include:
|
||||
|
||||
- reference id and class name
|
||||
- library ref/name
|
||||
- superclass ref/name
|
||||
- instance size, type-parameter count, type-argument offset
|
||||
- flags: abstract, enum, mixin, top-level
|
||||
- enum values
|
||||
- fields with name, type, offset, and static/final/const/late flags
|
||||
- methods with entrypoint, owner, kind tag, and decoded method kind
|
||||
|
||||
Cluster extraction reads String, Class, Field, and Library records where the
|
||||
cluster layout is understood. If that does not produce classes, a string-based
|
||||
type-name fallback scans the VM-to-isolate data window.
|
||||
|
||||
Data-image scans add fields and methods back onto recovered classes. Field
|
||||
scans recognize Field objects and read owner/name strings and offsets. Method
|
||||
scans recognize Function objects, entrypoints, kind tags, owner class names,
|
||||
and method names.
|
||||
|
||||
Recognized method kind names are:
|
||||
|
||||
- `RegularFunction`
|
||||
- `ClosureFunction`
|
||||
- `ImplicitClosureFunction`
|
||||
- `GetterFunction`
|
||||
- `SetterFunction`
|
||||
- `Constructor`
|
||||
- `ImplicitGetter`
|
||||
- `ImplicitSetter`
|
||||
- `ImplicitStaticGetter`
|
||||
- `FieldInitializer`
|
||||
- `MethodExtractor`
|
||||
- `NoSuchMethodDispatcher`
|
||||
- `InvokeFieldDispatcher`
|
||||
- `IrregexpFunction`
|
||||
- `DynamicInvocationForwarder`
|
||||
- `FfiTrampoline`
|
||||
- `RecordFieldGetter`
|
||||
|
||||
Enum recovery is heuristic. It looks for type-name strings, trailing
|
||||
`TypeName.`, and `TypeName.value` pairs. Candidate names must look like Dart
|
||||
types, values must look like enum values, and suffixes such as `Action`,
|
||||
`Behavior`, `Direction`, `Kind`, `Mode`, `State`, `Status`, and `Style` boost
|
||||
confidence.
|
||||
|
||||
### Strings
|
||||
|
||||
`-s` dumps strings. The string scanner supports:
|
||||
|
||||
- ASCII strings
|
||||
- UTF-16LE strings converted to UTF-8
|
||||
- packed strings inside compressed snapshots
|
||||
- snapshot cluster/data windows
|
||||
- readable sections ending in `.__const`, `.__cstring`, `.rodata`, or
|
||||
`.data.rel.ro`
|
||||
|
||||
Strings are classified as:
|
||||
|
||||
- `rnt` for Dart runtime-looking strings (`dart:`, `dartvm`, `dart/`)
|
||||
- `lib` for package/library-looking strings (`package:`, `.dart`, slash paths)
|
||||
- `app` for human text with spaces or punctuation
|
||||
- `unknown` otherwise
|
||||
|
||||
String scans are bounded: 64 MB per snapshot region, 8 MB per data-image window,
|
||||
32 MB per section, and 50,000 total strings.
|
||||
|
||||
### Xrefs
|
||||
|
||||
`-x` combines metadata, data-image scans, strings, classes, and instruction
|
||||
table entries into cross-reference records.
|
||||
|
||||
Supported xref kinds include:
|
||||
|
||||
- `class.name`
|
||||
- `class.library`
|
||||
- `class.super`
|
||||
- `field.owner`
|
||||
- `field.name`
|
||||
- `field.type`
|
||||
- `method.owner`
|
||||
- `method.name`
|
||||
- `method.entry`
|
||||
- `it.code`
|
||||
- `it.stub`
|
||||
|
||||
Origins are reported as `metadata`, `data-image`, or `scan`.
|
||||
|
||||
### Obfuscation Maps
|
||||
|
||||
The obfuscation map loader expects a JSON array of alternating original and
|
||||
obfuscated strings. It builds an inverse map from obfuscated name to original
|
||||
name, then applies it to:
|
||||
|
||||
- function names
|
||||
- instruction-table names
|
||||
- class names
|
||||
- field names
|
||||
- method owner/name strings
|
||||
- dotted names component by component
|
||||
|
||||
## CLI Support
|
||||
|
||||
`bin/r2flutter` supports one action per invocation.
|
||||
|
||||
| Flag | Support |
|
||||
|------|---------|
|
||||
| `-h` | Help. |
|
||||
| `-V` | Version string. |
|
||||
| `-j` | JSON output where the selected action supports it. |
|
||||
| `-r` | radare2 command/script output where the selected action supports it. |
|
||||
| `-q` | Suppress non-essential stdout/log noise. |
|
||||
| `-v`, `-vv` | Increase stderr diagnostics; `-vv` also prints extra snapshot/header bytes. |
|
||||
| `-c` | Dump classes. |
|
||||
| `-f` | Dump recovered functions as `addr name`; with `-j`, emits objects with `addr`, `name`, and optional `size`. |
|
||||
| `-H` | Dump snapshot header/layout/container information. |
|
||||
| `-i` | Dump instruction-table entries. |
|
||||
| `-R` | Dump a radare2 script for applying method flags/comments and PP helpers. |
|
||||
| `-s` | Dump strings. |
|
||||
| `-T` | Dump type-oriented class output and run enum recovery. |
|
||||
| `-x` | Dump metadata/data-image xrefs. |
|
||||
| `-l <N>` | Limit function or instruction-table/xref output depending on the action. |
|
||||
| `-n` | Enable heuristic name-pool fallback for otherwise unnamed functions. |
|
||||
| `-o <file>` | Load a Flutter obfuscation map JSON file. |
|
||||
|
||||
Directory inputs are resolved as Android first (`libapp.so`), then iOS
|
||||
(`Frameworks/App.framework/App`).
|
||||
|
||||
## radare2 Plugin Support
|
||||
|
||||
The core plugin command is `r2flutter`. Running it with no flags analyzes the
|
||||
current file and applies flags/comments.
|
||||
|
||||
Config keys:
|
||||
|
||||
- `r2flutter.mapfile`: Flutter obfuscation map JSON path
|
||||
- `r2flutter.namepool`: enable heuristic name-pool fallback
|
||||
|
||||
Plugin commands:
|
||||
|
||||
| Command | Support |
|
||||
|---------|---------|
|
||||
| `r2flutter` | Analyze Dart snapshot, apply method flags/comments, and set `e emu.str=true`. |
|
||||
| `r2flutter -a` | Run Dart-aware code analysis and recover refs/comments. |
|
||||
| `r2flutter -H` | Print snapshot header info. |
|
||||
| `r2flutter -j` | Analyze quietly and print one JSON snapshot header line. |
|
||||
| `r2flutter -i` | Print instruction-table entries. |
|
||||
| `r2flutter -r` | Print radare2 script output. |
|
||||
| `r2flutter -f [n]` | List first `n` discovered functions, default 20. |
|
||||
| `r2flutter -q` | Analyze quietly. |
|
||||
| `r2flutter -n` | Analyze with name-pool fallback enabled. |
|
||||
| `r2flutter -c` | Dump classes as JSON. |
|
||||
| `r2flutter -C` | Dump classes as radare2 type definitions. |
|
||||
| `r2flutter -F` | Analyze with field extraction enabled. |
|
||||
| `r2flutter -s` | Dump strings as JSON. |
|
||||
| `r2flutter -t` | Dump strings as radare2 commands. |
|
||||
| `r2flutter -x` | Dump xrefs as text. |
|
||||
|
||||
The Dart-aware analysis pass creates or reuses functions at recovered Dart
|
||||
entrypoints, tracks AArch64 register values, follows PP-relative loads through
|
||||
`x27`, propagates simple stack values, annotates direct string/class/type/field
|
||||
refs, adds call xrefs, and comments indirect calls through PP slots.
|
||||
|
||||
## radare2 Script Output
|
||||
|
||||
The `-R` path emits:
|
||||
|
||||
- `e emu.str=true`
|
||||
- `app.base` and `app.heap_base` flags
|
||||
- `method.*` flags for recovered functions
|
||||
- comments at method entrypoints
|
||||
- `PP=x27` and PP offset helper flags found by disassembling functions and
|
||||
looking for loads from `[x27, imm]`
|
||||
|
||||
## Practical Limits
|
||||
|
||||
- The implementation is ARM64-oriented. Header and string extraction may work
|
||||
on other containers if radare2 maps the snapshots, but instruction-table
|
||||
naming, PP analysis, and code comments assume AArch64 conventions.
|
||||
- Modern cluster name reconstruction currently requires compressed
|
||||
ObjectHeader snapshots. Full-width ObjectHeader snapshots rely more heavily
|
||||
on data-image and string heuristics.
|
||||
- Legacy cluster parsing uses conservative decoders and skips unknown clusters;
|
||||
when class/function records cannot be trusted, the code falls back to bounded
|
||||
scans.
|
||||
- `-n` can produce plausible but wrong names. It is a last-resort fallback for
|
||||
samples where metadata cannot associate names with instruction-table slots.
|
||||
- This is not a Dart decompiler and does not parse Dart bytecode, kernel
|
||||
`.dill`, Flutter web JavaScript, or the Flutter engine itself.
|
||||
- Snapshot discovery prefers symbol names, then section scanning. Stripped or
|
||||
unusual packers can still require manual triage with `-v`, `-vv`, `-H`, and
|
||||
`-i`.
|
||||
Reference in new issue
Block a user