|
| 1 | +# APK_REVERSE_ENGINEERING_PROCEDURE.md — Pixel Buds APK Analysis Guide |
| 2 | + |
| 3 | +**Purpose:** step-by-step procedure to pull, store, decompile, and analyze the official Pixel Buds |
| 4 | +companion app APK, in order to fill in the confidence-rated placeholders in `REVERSE_ENGINEERING.md` |
| 5 | +and, once wire-correlated, `PROTOCOL.md`. This document is the *how* — `REVERSE_ENGINEERING.md` is |
| 6 | +the *what we found*, and `reverse-engineering/APK_VERSIONS.md` is the *which APK version(s) we've |
| 7 | +looked at*. |
| 8 | + |
| 9 | +This procedure follows `AGENTS.md` §6/`DECISIONS.md` ADR-017's AI-assistance boundary throughout: |
| 10 | +an AI session may run the mechanical steps below (pulling, hashing, decompiling, running `pbtk`, |
| 11 | +keyword/string searching, explaining already-surfaced code or disassembly — including native `.so` |
| 12 | +disassembly output, per ADR-017 §4), but never decides which candidate is relevant to the protocol |
| 13 | +and never decides that something becomes a recorded HYPOTHESIS in `REVERSE_ENGINEERING.md`. Both of |
| 14 | +those remain the maintainer's calls at every step marked **[Maintainer decision]** below. |
| 15 | + |
| 16 | +--- |
| 17 | + |
| 18 | +## 1. Prerequisites |
| 19 | + |
| 20 | +### 1.1 On your computer |
| 21 | +- [ ] **JADX**, **apktool**, and **pbtk** installed (`WORKSTATION_PREPARATIONS.md`'s "Reverse |
| 22 | + engineering tools: JADX, apktool, pbtk" section) — pbtk's own dependencies (Python ≥ 3.10, |
| 23 | + PySide6, python-protobuf, and `jad`/`dex2jar` for some extractor scripts) confirmed there too. |
| 24 | +- [ ] **Android platform-tools** (`adb`) installed and on your `PATH` (already required by |
| 25 | + `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §1.1). |
| 26 | +- [ ] `sha256sum` (standard on Fedora). |
| 27 | + |
| 28 | +### 1.2 On the phone |
| 29 | +- [ ] The official Pixel Buds companion app installed under the maintainer's own Google account, |
| 30 | + on the maintainer's own device — this is the provenance basis `PROJECT_RULES.md` §8 rule 20 |
| 31 | + requires; see `reverse-engineering/APK_VERSIONS.md`'s provenance column. |
| 32 | +- [ ] USB debugging enabled (same as `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §1.2). |
| 33 | + |
| 34 | +### 1.3 Reading this session should already have done |
| 35 | +- [ ] `AGENTS.md` §4/§6 (proto-schema extraction rules, sign-off requirement). |
| 36 | +- [ ] `DECISIONS.md` ADR-017 (the current AI-assistance boundary — supersedes the older ADR-003). |
| 37 | +- [ ] `REVERSE_ENGINEERING.md`'s own template and status legend. |
| 38 | + |
| 39 | +--- |
| 40 | + |
| 41 | +## 2. Pulling and Storing a New APK Version |
| 42 | + |
| 43 | +1. Find the package name and installed version: |
| 44 | + ```bash |
| 45 | + adb shell dumpsys package com.google.android.apps.wearables.maestro.companion | grep -E "versionName|versionCode" |
| 46 | + ``` |
| 47 | +2. Find the APK path(s) — **a companion app this size may be split** (base + density/language/ABI |
| 48 | + splits), so `pm path` can legitimately return more than one line. Pull **all** of them: |
| 49 | + ```bash |
| 50 | + adb shell pm path com.google.android.apps.wearables.maestro.companion |
| 51 | + # package:/data/app/.../base.apk |
| 52 | + # package:/data/app/.../split_config.xxhdpi.apk <- pull this one too, if present |
| 53 | + mkdir -p "reverse-engineering/apk/v<versionName>-<versionCode>/" |
| 54 | + adb pull /data/app/.../base.apk "reverse-engineering/apk/v<versionName>-<versionCode>/" |
| 55 | + adb pull /data/app/.../split_config.xxhdpi.apk "reverse-engineering/apk/v<versionName>-<versionCode>/" # repeat per split |
| 56 | + ``` |
| 57 | +3. Hash every pulled file and record the result: |
| 58 | + ```bash |
| 59 | + sha256sum reverse-engineering/apk/v<versionName>-<versionCode>/*.apk |
| 60 | + ``` |
| 61 | +4. **[Maintainer decision — recording provenance is mechanical, but confirm the row is accurate]** |
| 62 | + Add a row to `reverse-engineering/APK_VERSIONS.md`'s table: version dir, file(s), SHA-256 per |
| 63 | + file, versionName/versionCode, pull date, source device, and provenance (how it was obtained — |
| 64 | + e.g. "Play Store, maintainer's own Google account, installed on the maintainer's own device"). |
| 65 | +5. Remember: **nothing under `reverse-engineering/apk/` is ever committed** — it's fully covered by |
| 66 | + `.gitignore` (the APK itself, `jadx-output/`, `apktool-output/`, `pbtk-output/`). Only the |
| 67 | + `reverse-engineering/APK_VERSIONS.md` row you just added is git-tracked. |
| 68 | + |
| 69 | +### 2.1 Diff / re-check pass against the previous version |
| 70 | + |
| 71 | +A new companion-app release doesn't mean starting analysis from zero. Before decompiling from |
| 72 | +scratch: |
| 73 | + |
| 74 | +1. If a previous version's `apktool-output/` still exists locally, diff the two versions' DEX class |
| 75 | + lists (e.g. `diff <(unzip -l v<old>/base.apk | grep '\.dex$') <(unzip -l v<new>/base.apk | grep '\.dex$')`, |
| 76 | + or compare `apktool`'s own `smali/` directory trees after decompiling both) to see whether the |
| 77 | + set of classes changed at all before re-running a full keyword search. |
| 78 | +2. Re-check every class/finding already recorded in `REVERSE_ENGINEERING.md`'s "Identified relevant |
| 79 | + classes" section and "Correlation status with `PROTOCOL.md`" table against the new version — note |
| 80 | + whether each one still exists, moved, or was removed/renamed, rather than assuming prior findings |
| 81 | + still apply unchanged. |
| 82 | +3. **[Maintainer decision]** Whether a shifted/renamed class still represents the same behavior (and |
| 83 | + whether any existing `PROTOCOL.md` FACT needs re-verification against the new version) is a |
| 84 | + relevance judgment — flag candidates, don't silently carry forward or silently invalidate a prior |
| 85 | + finding. |
| 86 | + |
| 87 | +--- |
| 88 | + |
| 89 | +## 3. Decompiling and Schema Extraction |
| 90 | + |
| 91 | +```bash |
| 92 | +cd "reverse-engineering/apk/v<versionName>-<versionCode>/" |
| 93 | + |
| 94 | +# Readable Kotlin/Java (primary source for keyword search and citations) |
| 95 | +jadx -d jadx-output/ base.apk |
| 96 | + |
| 97 | +# Resources, manifest, smali (fallback when JADX misdecompiles something) |
| 98 | +apktool d base.apk -o apktool-output/ |
| 99 | + |
| 100 | +# .proto schema extraction (see WORKSTATION_PREPARATIONS.md for the real, confirmed pbtk scope — |
| 101 | +# it is NOT limited to Java/DEX; pbtk-from-binary targets native .so files too, though whether it |
| 102 | +# actually succeeds against libmaestro/libgfps's specific binaries is unconfirmed until tried) |
| 103 | +pbtk-jar-extract base.apk pbtk-output/ |
| 104 | +pbtk-from-binary apktool-output/lib/<abi>/libmaestro.so pbtk-output/ # once §4's native .so pass locates it |
| 105 | +``` |
| 106 | + |
| 107 | +Record the exact **tool versions used** (`jadx --version`, `apktool --version`, pbtk's `pipx list` |
| 108 | +output) in `reverse-engineering/APK_VERSIONS.md`'s row for this version — decompiler output (line |
| 109 | +numbers, class layout) can shift between versions, so a file+line citation is only reproducible if |
| 110 | +the decompiler version is pinned too. |
| 111 | + |
| 112 | +--- |
| 113 | + |
| 114 | +## 4. Keyword Search and Search-Efficiency Techniques |
| 115 | + |
| 116 | +This is the mechanical step ADR-017 permits an AI session to run directly. Search results are |
| 117 | +**candidates**, not findings — every candidate goes to the maintainer for the relevance call before |
| 118 | +anything is recorded in `REVERSE_ENGINEERING.md`. |
| 119 | + |
| 120 | +1. **Start from `AndroidManifest.xml`** (`apktool-output/AndroidManifest.xml`, not the obfuscated |
| 121 | + binary form) — find services/receivers registered against Bluetooth-related intents/actions |
| 122 | + first. This anchors which classes are worth reading closely before any blind keyword search. |
| 123 | +2. **Search for string literals, not just class/method names** — log tags, notification channel |
| 124 | + names, broadcast action strings, and SharedPreferences keys survive ProGuard/R8 obfuscation even |
| 125 | + when class names don't (obfuscation renames identifiers, not string constants). |
| 126 | +3. **Check `qzed/pbpctrl`'s public documentation first**, as a research accelerant — protocol |
| 127 | + *knowledge* only, per `AGENTS.md` §12/`README.md`'s attribution boundary (never copy code; every |
| 128 | + candidate this surfaces still needs independent re-verification against this project's own |
| 129 | + APK/captures, not taken on `pbpctrl`'s word). |
| 130 | +4. Search `jadx-output/` for the keyword list in `REVERSE_ENGINEERING.md` §Method (BLE/GATT classes, |
| 131 | + RFCOMM/Fast-Pair classes, HID classes, package-name fragments, `.proto`-generated-class markers). |
| 132 | +5. **Apply the exclusion list below during this step itself** — don't collect candidates from these |
| 133 | + areas and filter them out later; skip past them at search time. |
| 134 | +6. Present candidates to the maintainer: file path, line number, the matched keyword/string, and a |
| 135 | + one-line note on why it looked relevant. **[Maintainer decision]** which candidates get written up |
| 136 | + in `REVERSE_ENGINEERING.md`, and at what confidence tier. |
| 137 | + |
| 138 | +### 4.1 Exclusion list — noise to skip past, not to investigate |
| 139 | + |
| 140 | +A fully decompiled app exposes far more surface area than any single capture. The following are |
| 141 | +explicitly **out of scope** (`PROJECT.md` non-goals, `DECISIONS.md` ADR-008) — do not spend search |
| 142 | +or read time on them even if they surface incidentally: |
| 143 | + |
| 144 | +- `AccountLinking` (any package/class matching this name) |
| 145 | +- `OwnershipTransfer` (any package/class matching this name) |
| 146 | +- `AccessoryNonOwner` (the Accessory Non-Owner Service — see ADR-008) |
| 147 | +- Any Firebase-, Analytics-, or Crashlytics-named package or class (`com.google.firebase.*`, |
| 148 | + `*Analytics*`, `*Crashlytics*`, or equivalent — see `AGENTS.md` §1's Zero-GMS rule) |
| 149 | + |
| 150 | +If one of these surfaces incidentally while searching for something else, note it as |
| 151 | +"out-of-scope, skipped" (mirroring how `DECISIONS.md` ADR-008 already treats incidental |
| 152 | +Account-Linking/Non-Owner traffic in captures) and move on — do not follow the call graph into it. |
| 153 | + |
| 154 | +--- |
| 155 | + |
| 156 | +## 5. Analysis Approach |
| 157 | + |
| 158 | +1. Write up each maintainer-approved candidate in `REVERSE_ENGINEERING.md`'s "Identified relevant |
| 159 | + classes" section, using its template — **every finding cites the exact decompiled file and line |
| 160 | + number** (e.g. `jadx-output/sources/com/google/.../Xy2.java:142`), not only a class name. |
| 161 | +2. Label every finding FACT / HYPOTHESIS / ASSUMPTION / OPEN QUESTION per `PROJECT_RULES.md` §1 — |
| 162 | + static analysis alone is never 🟢 FACT for a *protocol* claim (only for "this code exists and |
| 163 | + looks like X"); a protocol-behavior claim needs capture correlation first. |
| 164 | +3. For every HYPOTHESIS recorded, add a short note on how it could be confirmed or refuted against a |
| 165 | + capture — which action/Test-ID (`TESTPLAN_BLUETOOTH_HCI_SNOOP.md`) would need to be captured — |
| 166 | + per `REVERSE_ENGINEERING.md`'s template (see `PROJECT_RULES.md` §4's hypothesis-test discipline, |
| 167 | + already used for captures). |
| 168 | +4. Once a finding is cross-checked against a real capture, promote it **directly** into `PROTOCOL.md` |
| 169 | + — there is no intermediate working-notes buffer (`PROTOCOL_NOTES.md` was retired 2026-08-15, see |
| 170 | + `PROJECT_RULES.md` §2 rule 5). Update `REVERSE_ENGINEERING.md`'s "Correlation status with |
| 171 | + `PROTOCOL.md`" table so the same finding is never independently "rediscovered" in both documents. |
| 172 | +5. **[Maintainer decision, per `AGENTS.md` §6]** Promoting anything to 🟢 FACT in `PROTOCOL.md`, or |
| 173 | + writing/superseding a `DECISIONS.md` ADR, still requires explicit maintainer sign-off — an AI |
| 174 | + session may propose and draft, never commit either as settled. |
| 175 | + |
| 176 | +--- |
| 177 | + |
| 178 | +## 6. Notes & Gotchas |
| 179 | + |
| 180 | +- **Never hand-reconstruct a `.proto` schema from getter/setter names alone** — field *names* |
| 181 | + recovered from JADX are not proof of the actual wire field *numbers*, which determine binary |
| 182 | + compatibility. Use `pbtk`'s actual extraction output (§3), not a guessed schema. |
| 183 | +- **JADX can misdecompile obfuscated/optimized constructs** — when a decompiled method looks |
| 184 | + suspicious or incomplete, cross-check against the `apktool` smali output before trusting it. |
| 185 | +- **Reflection-based code stays invisible to static analysis** — if a call site is never found |
| 186 | + despite a class clearly needing one, that's a sign dynamic analysis (Frida) may be needed; log any |
| 187 | + such experiment in the relevant capture's `CAP-NNN-FINDINGS.md` first, per `PROJECT_RULES.md` §4. |
| 188 | +- **Native `.so` disassembly is in scope for AI mechanical assistance** (`DECISIONS.md` ADR-017 §4), |
| 189 | + on the same terms as DEX/Java work: search, list, and explain disassembly output; never decide |
| 190 | + relevance or promote a finding. Whether `pbtk-from-binary` actually succeeds against |
| 191 | + `libmaestro`/`libgfps`'s specific binaries (vs. a stripped protobuf-lite descriptor pool) is |
| 192 | + unconfirmed until tried — see `WORKSTATION_PREPARATIONS.md`. |
| 193 | +- **A class or method existing in the APK does not prove it's exercised by any specific action** in |
| 194 | + `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` — treat every static finding as 🟡 HYPOTHESIS until a capture |
| 195 | + shows the corresponding traffic (`REVERSE_ENGINEERING.md`'s own "Known limitations" section). |
| 196 | +- **Nothing under `reverse-engineering/apk/` is ever committed** — not the APK, not `jadx-output/`, |
| 197 | + not `apktool-output/`, not `pbtk-output/`. Only `reverse-engineering/APK_VERSIONS.md` and this |
| 198 | + procedure document are git-tracked. Double-check `git status` before committing anything from a |
| 199 | + reverse-engineering session. |
| 200 | +- **Never independently promote a finding to 🟢 FACT, and never commit a new/superseding |
| 201 | + `DECISIONS.md` ADR** — `AGENTS.md` §6/§15, unaffected by ADR-017's mechanical-assistance boundary. |
| 202 | + |
| 203 | +--- |
| 204 | +https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/APK_REVERSE_ENGINEERING_PROCEDURE.md - https://tedsluis.github.io/opencontrolpixelbudspro2/APK_REVERSE_ENGINEERING_PROCEDURE |
0 commit comments