Skip to content

Commit 7c69d5c

Browse files
committed
decompiling the apk
1 parent 1083d04 commit 7c69d5c

10 files changed

Lines changed: 1439 additions & 59 deletions

File tree

CAPTURE_BLUETOOTH_HCI_SNOOP.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,68 @@ If it does not appear, that's an equally useful negative result, pointing back t
688688
physical action as the real trigger after all — either outcome closes this open question, per the
689689
three-way outcome guidance already used for Group Q's items 19–20.
690690

691+
#### Group AA — SDP UUID branch isolation for `gbm.a()`'s "default internal rfcomm socket" path (occasional, added 2026-08-30)
692+
693+
**Purpose:** `REVERSE_ENGINEERING.md`'s `gbm`/`fzd` entries and `DECISIONS.md` ADR-018 found the
694+
companion app's own decompiled code (`gbm.java:35-43`) picks between two internal RFCOMM sockets
695+
depending on which of two SDP UUIDs is present in the discovered set: "pigweed" (`25e97ff7-...`,
696+
confirmed = DLCI 0x02 in every capture so far) or "default" (`3a046f6d-...`, never observed on the
697+
wire anywhere — a raw-byte scan of all 26 capture files this project has, in every format
698+
(`*btsnoop_hci.log`, `*btsnooz_hci.log`, both nRF Connect logs), found zero occurrences in either
699+
byte order; see `REVERSE_ENGINEERING.md`'s `gbm` entry Open questions). This Group covers the two
700+
hypotheses that are safely testable with a single Pixel Buds Pro 2 unit on its current firmware
701+
(`release_5.203`):
702+
703+
- **`SDP-001`:** `gbm.a()`'s discovered-UUID set may come from an SDP browse whose content depends
704+
on *who* triggers it — the OS's own default pairing flow, vs. the companion app's own
705+
`fetchUuidsWithSdp()` call (`fxm.java:110`, which only fires when the HID UUID `0x1124` is
706+
absent). Every existing capture has the app already open, so this hasn't been isolated.
707+
- **`SDP-002`** (opportunistic): a firmware update might be what changes which UUID gets advertised
708+
at all — the "pigweed"/"default" pair reads plausibly as a pre-/post-migration artifact. Only
709+
testable the next time an actual OTA update becomes available, same caveat as `FWUPD-001`/
710+
`FWUPD-002`.
711+
712+
**Explicitly out of scope for this Group** — not safely or practically testable, recorded here so
713+
they aren't silently retried: deliberately downgrading firmware (unsupported by Google, real
714+
bricking risk — see `WORKSTATION_PREPARATIONS.md`'s Disaster Recovery section; never attempt this),
715+
and testing against a different physical unit or hardware generation (the maintainer owns one
716+
Pixel Buds Pro 2 — `PROJECT_RULES.md` §8's own-hardware scope). If `SDP-001`/`SDP-002` both come
717+
back negative, the leading remaining explanation is that the "default" branch is unreachable on any
718+
currently-shipping `release_5.203`+ unit — a static-analysis question (checking for a
719+
firmware-version gate elsewhere in the APK, `APK_REVERSE_ENGINEERING_PROCEDURE.md` §4), not a
720+
capture question, and out of this Group's scope.
721+
722+
1. **[`SDP-001`]** Force-stop the Pixel Buds companion app first (`Settings → Apps → Pixel Buds →
723+
Force stop`), so it cannot react to the pairing at all. Start Bluetooth HCI snoop logging (§2).
724+
"Forget" the Buds via system Bluetooth settings only — the same safe, repeatable action already
725+
used for `PAIR-001` (Group A #1), **not** `CASE-007`'s factory reset. Re-pair via system
726+
Bluetooth settings' "Pair new device" flow only. Note the exact time pairing completes.
727+
2. Keep observing for at least 60s after bonding completes, **without** opening the companion app
728+
or touching the buds/case — this is the window where the pre-app-fetch UUID set (if one exists
729+
and differs from the baseline) would show up.
730+
3. **Still `SDP-001`, second half of the same session:** now open the companion app normally and
731+
let it connect as usual — this reproduces every prior capture's baseline in the same log, for a
732+
direct in-session before/after comparison rather than relying on a separate session.
733+
4. Pull the bugreport (§3) once, at the end.
734+
5. **[`SDP-002`], opportunistic, separate session whenever a firmware update becomes available:**
735+
repeat the SDP-observation half of steps 1–2 (a fresh SDP browse doesn't require a fresh bond —
736+
simply reconnecting is enough if already bonded) immediately before installing the update, and
737+
again immediately after it completes and the Buds reconnect.
738+
739+
**Analysis:** pre-filter by address first, per §13's CLI-hygiene rule, then to `btsdp`:
740+
`tshark -r CAP-NNN-btsnoop_hci.log -Y "bluetooth.addr == 04:00:6e:cf:6e:07 and btsdp" -T fields -e frame.number -e frame.time_relative -e btsdp.data_element.value.uuid_128 -e btsdp.protocol.channel`
741+
(exact command already validated against `CAP-001`/`CAP-002`/`CAP-032` in `REVERSE_ENGINEERING.md`'s
742+
`gbm` entry). Three-way outcome, matching Group Y's own guidance above:
743+
- **Positive:** the pre-app-open window (step 2) shows the "default" UUID (`3a046f6d-...`, either
744+
byte order) instead of or alongside "pigweed" — closes the open question; write it up per the
745+
usual FACT/HYPOTHESIS discipline (`PROJECT_RULES.md` §1) before touching `PROTOCOL.md`.
746+
- **Negative:** still only "pigweed" (or no `btsdp` traffic at all, e.g. if the OS doesn't run a
747+
full SDP browse without the app prompting it) — consistent with every capture to date; narrows
748+
the explanation toward `SDP-002` or a static-analysis-only dead-code question rather than a
749+
UI-timing artifact.
750+
- **`SDP-002` positive:** a before/after firmware comparison shows the advertised UUID changing —
751+
directly explains the two-UUID code as a migration artifact.
752+
691753
### 4.2 Pixel 9a (GrapheneOS) — secondary/validation session
692754

693755
No app-driven commands are possible here, so this session focuses on connection-level
@@ -1060,6 +1122,7 @@ is how the 2026-08-18 `CAP-005`/`CAP-007`/`CAP-010` ID-reuse incident (see
10601122
| `CAP-030` | *planned* | either phone | TBD | TBD | TBD | Q (items #19–20) | `LOUD-001`, `ADAPT-002` | Group Q's two remaining items (item #18 already planned separately as `CAP-011`) — attempt to observe Loud Noise Protection and Adaptive Audio engaging; requires firmware ≥4.467 ||| planned |
10611123
| `CAP-031` | 2026-08-27 | Pixel 7a | 17 (⚪ assumed, not screen-confirmed) | release_5.203 | official Pixel Buds Companion App (version not visible on screen) | A (repeat, 3rd attempt) | `PAIR-001`, `PAIR-004`, incidental `BATT-004` | Third attempt at `CAP-001-FINDINGS.md` §6's original goal — HCI snoop logging started before any prior association with the device exists, this time with a live in-recording file-size-polling check added specifically to avoid `CAP-013`'s failure mode. **PROPOSAL — pending maintainer approval:** status/row text below proposed, not yet maintainer-approved. | `captures/CAP-031-2026-08-27_06-04-48_06-08-10-Group_A/CAP-031-btsnooz_hci.log` (**`btsnooz`-format, inferred 15–126-byte captured length per packet, same truncation issue as `CAP-012`/`CAP-013`; see `CAP-031-FINDINGS.md` §1**) | same file | analyzed — **partial: pre-clearing-action window not captured, a second consecutive failure of this method** — see `CAP-031-FINDINGS.md`/`CAP-031-EVENT-NOTES.md` in that folder. This session used a genuine narrow, per-device "Forget" (screenshot-confirmed, unlike `CAP-013`'s broader reset) and a live snoop-log file-size-polling check during recording, but the log's first frame (06:06:37.16) still starts **66s after** the on-screen Forget tap (06:05:31) — also after case-open, pair-button-press, and the entire first "Pair new device" scan attempt, none of which are logged. **Primary question (`CAP-001-FINDINGS.md` §6) remains 🔴 OPEN, not answered, untested a third time.** **Secondary question (`PAIR-004`) is reconfirmed: 🟢 CONFIRMED fresh classic SSP handshake** (frames 598–689, a sixth confirming instance) — no key-reuse path observed. Bonus, both negative results: `CAP-013`'s DLCI 0x02 ~61s-delay does **not** reproduce (opens 1.64s after DLCI 0x00, within the initial burst) and `CAP-013`'s unattributed second BLE link does **not** reproduce (exactly one LE link this session, to the Buds' own public address) — both now look like single-session artifacts. A fourth attempt is still needed, this time verifying snoop-log *content* freshness (not just file size) before the Forget tap. |
10621124
| `CAP-032` | 2026-08-27 | Pixel 7a | 17 (⚪ assumed, not screen-confirmed) | release_5.203 | official Pixel Buds Companion App (version not visible on screen) | A (repeat, 4th attempt) | `PAIR-001`, `PAIR-004`, incidental `BATT-004` | Fourth attempt at `CAP-001-FINDINGS.md` §6's original goal — this time extracted via the raw BTSnoop file path (`CAPTURE_BLUETOOTH_HCI_SNOOP.md` §3 step 3) rather than the `btsnooz.py` fallback used for `CAP-012`/`CAP-013`/`CAP-031`. **PROPOSAL — pending maintainer approval:** status/row text below proposed, not yet maintainer-approved. | `captures/CAP-032-2026-08-27_18-30-15_18-32-33-Group_A/CAP-032-btsnoop_hci.log` (**genuine raw, untruncated BTSnoop — `frame.cap_len == frame.len` for all 2,455 frames, no `capinfos`-inferred size cap; see `CAP-032-FINDINGS.md` §0.1**) | same file | analyzed — **success: pre-clearing-action window captured for the first time in four attempts** — see `CAP-032-FINDINGS.md`/`CAP-032-EVENT-NOTES.md` in that folder. The log's first frame (18:29:45.72) starts **~58s before** the on-screen Forget tap (18:30:42) and ~30s before the video itself begins. **Primary question (`CAP-001-FINDINGS.md` §6) is now answered for this session: 🟢 no prior BLE link or valid classic link key existed for the Buds anywhere in the covered pre-Forget window** (`CAP-032-FINDINGS.md` §0.3) — this does not reproduce `CAP-001`'s original finding (a clean counter-example, not a contradiction; `CAP-001`'s own session-specific puzzle remains independently open). **Secondary question (`PAIR-004`) reconfirmed: 🟢 CONFIRMED fresh classic SSP handshake** (frames 1090–1153, a seventh confirming instance). Bonus: the untruncated log fully decodes DLCI 0x08's battery push (`[100,1,1]`/`[100,1,2]`/`[57,1,3]` = Left/Right/Case, matching the on-screen reading exactly) and firmware/capability-identifier fields; a previously-undocumented vendor-specific HCI command (`0xFD57`/`0x0157`, frame 91) embeds the Buds' address 105ms into the log, structurally consistent with bulk bonded-device provisioning at BT-enable time, not a connection — recorded 🔴 OPEN QUESTION, not bearing on the primary question. The `btsnooz`-vs-raw extraction-path hypothesis (`CAP-013-FINDINGS.md` §1, `CAP-031-FINDINGS.md` §1) is supported by this one data point (`CAP-032-FINDINGS.md` §0.1/§7 Test C), not yet independently isolated. |
1125+
| `CAP-033` | *planned* | Pixel 7a | TBD | TBD | TBD (n/a for `SDP-001`'s first half — app force-stopped) | AA | `SDP-001`, `SDP-002`(opportunistic) | SDP UUID branch isolation for `gbm.a()`'s "default internal rfcomm socket" path — system-settings-only pairing (app force-stopped) to check whether the pre-app-fetch SDP UUID set ever differs from every existing capture's "pigweed"-only result (`REVERSE_ENGINEERING.md`'s `gbm`/`fzd` entries, `DECISIONS.md` ADR-018) ||| planned |
10631126

10641127
**Column notes:**
10651128

DECISIONS.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,5 +753,92 @@ motivated this).
753753
disassembly assistance being newly in scope is a deliberate, separately-recorded decision (this
754754
ADR's §4), not an incidental scope expansion.
755755

756+
## ADR-018 — DLCI 0x02 confirmed as the companion app's own internal RFCOMM channel (SDP UUID + APK-code correlation, 3 independent captures); channel *ownership* promoted to FACT, Sent-payload *content* remains HYPOTHESIS
757+
758+
> **Maintainer sign-off obtained 2026-08-30** (session record: maintainer selected "Option 2" from
759+
> the options below). This entry was originally drafted by an AI session as a labeled proposal
760+
> (`Status: Proposed`) per `AGENTS.md` §6, and is updated in place — not stacked as a new entry —
761+
> now that the maintainer has reviewed and decided, per this file's non-destructive-update
762+
> convention.
763+
764+
- **Date**: 2026-08-30
765+
- **Status**: Accepted — Option 2 (narrow promotion)
766+
- **Context**: `PROTOCOL.md` §2.2a already promoted DLCI 0x02's **framing mechanism** (HDLC flag/
767+
escape/LEB128-address/CRC-32) to 🟢 FACT (2026-08-12, `pbpctrl`-notes cross-reference +
768+
640/640-subframe CRC verification across `CAP-001``CAP-003`). What §2.2a/§2.3 explicitly left at
769+
🟡 HYPOTHESIS (strong) is a narrower claim: that this specific channel *is* `libmaestro`'s own
770+
settings channel, as opposed to some other Pigweed-RPC-based Google service sharing the same
771+
framing library. §2.2a states two paths to close that gap: (a) decode the opaque "Sent"-direction
772+
payload bytes and recognize an actual `libmaestro` method call, or (b) an isolated
773+
single-action capture correlating one "Sent" write to one specific user action. Neither had
774+
happened yet.
775+
An AI-run §4 keyword-search pass over `v1.0.955078536-10253511`'s decompiled APK (`DECISIONS.md`
776+
ADR-017's mechanical-assistance boundary; full write-up in `REVERSE_ENGINEERING.md`'s `fzd`/`gbm`/
777+
`gau`/`gbd`/`fxm`/`fsz`/`fut`/`fux`/`ghd`/`goq` entries) found the app's own RFCOMM-socket-selection
778+
logic: `gbm.java:35-43` picks between two internal RFCOMM sockets by checking which of two 128-bit
779+
UUIDs (each present in both a canonical and a byte-reversed form, `fzd.java:9`) is in the
780+
discovered SDP UUID set, logging **`"Provide pigweed internal rfcomm socket"`** for UUID
781+
`25e97ff7-24ce-4c4c-8951-f764a708f7b5` and **`"Provide default internal rfcomm socket"`** for a
782+
second, distinct UUID (`3a046f6d-24d2-7655-6534-0d7ecb759709`). Separately, `fsz.java:223` — a
783+
Kotlin function-reference metadata string that survived R8 renaming — literally names the app's
784+
own `com.google.android.apps.wearables.maestro.companion.pw.hdlc.RouteProto$Route` class and the
785+
upstream `dev.pigweed.pw_rpc.MethodClient` class, and `fux.java`/`fxm.java`/others enumerate real
786+
`maestro_pw.*` pw_rpc services (`Maestro`, `HeadGesture`, `EartipFitTest`, `Dosimeter`,
787+
`JitterBuffer`, `Multipoint`, `DynamicServerConfigService`) called through this same selection
788+
path.
789+
This "pigweed" UUID was then checked against 3 independent captures already in `captures/`
790+
(`CAP-001`, `CAP-002`, `CAP-032`; `bluetooth.addr == 04:00:6e:cf:6e:07`): in every session, the SDP
791+
Service Search Attribute Response lists `25e97ff7-24ce-4c4c-8951-f764a708f7b5`, its Protocol
792+
Descriptor List response resolves it to **RFCOMM server channel 1**, and `tshark`'s own
793+
`btrfcomm.dlci` field reads **`0x02`** for every frame once that channel opens (`CAP-001` frame
794+
1334 @ 42.545s; `CAP-032` frame 1645 @ 105.173s) — a direct wire reading, not the `2×channel`
795+
arithmetic applied blind. Full frame/timestamp citations are in `REVERSE_ENGINEERING.md`'s `gbm`
796+
entry and §UUID register. The second, "default"-labeled UUID (`3a046f6d-...`) was searched for
797+
(both byte orders) across all 23 raw `*btsnoop_hci.log` files under `captures/` and found in none
798+
of them — an open question, not explained by this pass.
799+
- **What this new evidence is, precisely — and what it is not:** it establishes, from the app's own
800+
compiled selection logic plus a reproducible SDP/RFCOMM wire correlation, that DLCI 0x02 is the
801+
specific RFCOMM channel *this companion app itself* selects and labels "pigweed," and that the app
802+
calls real `maestro_pw.*` pw_rpc services (including `WriteSetting`) through that same selection
803+
path. It does **not** decode the opaque "Sent"-direction payload bytes on DLCI 0x02, and does
804+
**not** correlate one specific "Sent" write to one specific user action — i.e. it does not satisfy
805+
either of §2.2a's two originally-stated paths (a)/(b) in the form they were written. It is a third,
806+
independent evidentiary path: static app-code correlation via the SDP layer, rather than payload
807+
decoding or capture isolation.
808+
- **Options considered** (maintainer's choice, not decided by this proposal):
809+
1. **Promote fully**: treat this SDP+code correlation as sufficient to move DLCI 0x02's
810+
channel-identity claim ("this is `libmaestro`'s channel") from 🟡 HYPOTHESIS (strong) to 🟢 FACT
811+
in `PROTOCOL.md` §2.2a/§2.3, on the reasoning that tying the DLCI directly to the app's own
812+
compiled selection logic and self-identifying log string is at least as strong as decoding one
813+
opaque payload would be.
814+
2. **Promote narrowly** (mirrors ADR-013's precedent of promoting only what's cleanly warranted):
815+
record as 🟢 FACT only that *this RFCOMM channel is the companion app's own internal channel,
816+
distinct from any other/generic Pigweed-based service* — leave "and its Sent-payload content is
817+
specifically `libmaestro`'s ANC/EQ/settings commands" at 🟡 HYPOTHESIS (strong) pending §2.2a's
818+
original paths (a)/(b).
819+
3. **Do not promote**: keep §2.2a/§2.3's status text exactly as-is, and append this SDP+code
820+
correlation to `PROTOCOL.md` purely as additional strengthening evidence for the existing 🟡
821+
HYPOTHESIS (strong) label, explicitly reserving promotion for actual payload-content decoding or
822+
an isolated single-action capture.
823+
- **Decision**: **Option 2, accepted.** Per `ARCHITECTURE.md` §2.1/`PROJECT_RULES.md` §1's
824+
promotion rules, `PROTOCOL.md` is updated to record 🟢 FACT that DLCI 0x02 is the Pixel Buds
825+
companion app's own internal RFCOMM channel — distinct from any other/generic Pigweed-based
826+
service — based on the SDP UUID (`25e97ff7-24ce-4c4c-8951-f764a708f7b5`) the app's own code
827+
(`gbm.java`/`fzd.java`) selects and labels "pigweed internal rfcomm socket," confirmed on the wire
828+
as RFCOMM channel 1 = DLCI 0x02 across `CAP-001`/`CAP-002`/`CAP-032`. **Not promoted:** that this
829+
channel's Sent-direction payload *content* specifically carries `libmaestro`'s ANC/EQ/settings
830+
commands — that stays 🟡 HYPOTHESIS (strong), pending §2.2a's original paths (a) decoding the
831+
opaque payloads via a pw_rpc/protobuf schema, or (b) an isolated single-action capture. See
832+
`PROTOCOL.md` §2.2a ("Channel ownership" finding), §2.3's three-channel table, the 2026-08-14
833+
addendum's Status line, and §4.2's EQ entry — all updated together for consistency, since they
834+
restate the same underlying claim.
835+
- **Consequences**: `ARCHITECTURE.md` §2.1's per-channel implementation gate is **not** unblocked
836+
for `FrameEncoder`/`FrameDecoder` work against DLCI 0x02's actual settings semantics — the opaque
837+
"Sent" payload content remains undecoded; only the channel-*identity* question is settled. This
838+
does give future work a firmer footing to state "this is `libmaestro`'s own channel" without
839+
hedging, when discussing which channel to target for payload-decoding work (§2.2a's paths (a)/(b)).
840+
Neither DLCI 0x08's still-🔴 open identity question nor the "default internal rfcomm socket"
841+
UUID's unexplained absence from every capture searched so far is affected by this decision.
842+
756843
---
757844
https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/DECISIONS.md - https://tedsluis.github.io/opencontrolpixelbudspro2/DECISIONS

0 commit comments

Comments
 (0)