From e843b817a8213970a945a23c3c4bf5a336c5b9fe Mon Sep 17 00:00:00 2001 From: "Shane B." Date: Mon, 31 Aug 2026 09:46:52 +0200 Subject: [PATCH] Collect what is known about tags in owner-connected mode The one case this app cannot handle: a tag that is with its owner, so it is not in the Offline Finding network and there is nothing on Apple's servers to fetch however far back anyone looks. Everything about it was scattered across two blog series, four papers, three GitHub threads and a FRIDA repository, and none of it says which parts are settled. **The finding worth the whole document** is stek29's, in FindMy.py #88: the proof of concept everyone reaches for writes GATT to a characteristic that is not present on a real AirTag with its owner nearby, because it is the *unauthorised* sound command. Authorised ringing - the owner's own tag, sitting next to them - is a different protocol over L2CAP. Somebody starting from the obvious PoC would spend a week finding that out. That has a bearing on #139, which plays a nearby accessory's sound over GATT, so it is said in the document rather than left to be noticed. Also collects: what a nearby tag actually broadcasts and how that differs from a separated one (primary key every 15 minutes, secondary key daily at 04:00); Adam Catley on the first six key bytes travelling as the BLE address; the WOOT'22 firmware work behind seemoo-lab/airtag and what its jailbreak requirement really is; and AirGuard, which malmeloo says already rings tags from Android - marked unverified, because that claim is the strongest lead here and it should not be taken on trust. Nothing was read out of rustpush, apple-private-apis or export-findmy, per the clean-room note in docs/findmy-export/README.md. stek29's public comments are quoted; his branches are not. Index row added per rule 10. Co-Authored-By: Claude Opus 5 --- docs/README.md | 1 + docs/owner-connected-tags.md | 146 +++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 docs/owner-connected-tags.md diff --git a/docs/README.md b/docs/README.md index 1b570e28..8100175c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,6 +13,7 @@ no natural home in a source comment. | [android-import-handover.md](./android-import-handover.md) | What the Android app has to do to catch up with the exporter: the shared package's Chaquopy wiring, encrypted bundles, self-generated tags. A task brief - delete it when the work lands. | | [anisette-native-android.md](./anisette-native-android.md) | Proof-of-concept log for running Apple's ADI libraries in-process on Android, and the measurements that came out of it. Historical; the implementation shipped. | | [findmy-export/](./findmy-export/) | Clean-room specification of the protocol for reading FindMy accessory keys from iCloud without a Mac. Stage 1 written; five stages to go. | +| [owner-connected-tags.md](./owner-connected-tags.md) | Reading list for the one case the app cannot handle: matching or ringing a tag while it is with its owner, so the Find My network never sees it. Links plus what each one actually establishes — including that authorised and unauthorised ringing are different protocols. | ## If you add a document here diff --git a/docs/owner-connected-tags.md b/docs/owner-connected-tags.md new file mode 100644 index 00000000..eeac4f05 --- /dev/null +++ b/docs/owner-connected-tags.md @@ -0,0 +1,146 @@ +# Matching and ringing a tag while it is in owner-connected mode + +> **An investigation record, not a plan.** Nothing here is scheduled and nobody is assigned. It +> exists so that whoever does look into this starts from what is already known rather than +> rediscovering it — including one finding that would otherwise cost a week on the wrong protocol. + +A reading list. Everything here is about the case the app does **not** solve: a tag +that is with its owner, so the Find My network never sees it. The separated case is solved and +none of this is about that. + +## Why this is a separate problem + +An AirTag has three states, and they behave differently on the radio: + +| State | What it does | +| --- | --- | +| **Connected** | in contact with an owner device — e.g. during setup | +| **Nearby** | recently in contact, still close. Broadcasts a **primary key**, rotating roughly every 15 minutes | +| **Separated** | away from the owner. Broadcasts a **secondary key**, rotating daily at 04:00 local, and the Offline Finding network starts reporting it | + +**A nearby tag is not in the Offline Finding network at all.** No stranger's iPhone reports it, +so there is nothing on Apple's servers to fetch however far back you look — which is why Apple's +own app falls back to "last seen by your device" rather than a network position. Anything in this +area has to happen over Bluetooth, locally, on the device standing next to the tag. + +--- + +## The states, and what is broadcast in each + +**[FindMy.py #137 — "Nearby, separated, lost?"](https://github.com/malmeloo/FindMy.py/issues/137)** +malmeloo's own one-paragraph statement of the problem: nearby tags *limit the information they +broadcast*, are excluded from OF, and therefore cannot be located by the library — hence the +offline Bluetooth scanner existing at all. The most authoritative short answer, and the place to +start. + +**[The Binary Hick — Endtroducing… Lost Apples](https://thebinaryhick.blog/2025/11/30/endtroducing-lost-apples/)** +and **[Old Dog, New Tricks — Lost Apples 2.0](https://thebinaryhick.blog/2026/03/22/old-dog-new-tricks-lost-apples-2-0/)** +Joshua Hickman, from a digital-forensics angle. Where the three-state model above comes from, +including the primary-key/15-minute versus secondary-key/daily-at-04:00 split. 2.0 also documents +a **powered-down iPhone** being seen over Find My, which is its own rabbit hole. + +**[Adam Catley — Apple AirTag Reverse Engineering](https://adamcatley.com/AirTag.html)** +The comprehensive hardware and BLE teardown. The detail that matters here: the first 6 bytes of +the P-224 public key are carried as the **BLE device address** rather than in the payload, so only +23 of the 29 bytes appear in the advertisement — and the tag only starts broadcasting the finding +advertisement once away from its owner. + +--- + +## Ringing a tag over BLE + +**[FindMy.py #88 — Request "ring" the device](https://github.com/malmeloo/FindMy.py/issues/88)** +The whole thread is the state of the art, and it is worth reading in order because the +understanding changes as it goes. malmeloo starts at *"there is a public BLE endpoint… not very +difficult"* and *"there's not really any security involved, because other people should be able to +ring the device as well"*, and it gets more complicated from there. + +**[stek29's comment, 13 June 2026](https://github.com/malmeloo/FindMy.py/issues/88#issuecomment-4699342119)** +— **the single most useful thing on this list.** He tested the proof of concept against real +AirTags **with the owner device nearby** and it failed: the characteristic it writes to, +`4F860003-943B-49EF-BED4-2F730304427A`, was not present on the device at all. His own correction +is the finding: + +> I just realised this PoC is about unauthorized sound command, not about ringing when AirTag is +> in "owner is nearby" mode. authorised sound playback seems to be a completely different beast, +> requiring l2cap communication + +So **unauthorised ringing (any passer-by) and authorised ringing (the owner, tag nearby) are two +different protocols**, and only the first is a simple GATT write. Anyone starting here should +assume the GATT path does not cover the case they care about. + +**[FindMy.py #211 — feat: Play sound on accessories](https://github.com/malmeloo/FindMy.py/pull/211)** +malmeloo's experimental branch (`feat/accessory-communication`), still a draft. His own note is +that he *"cannot get it to work reliably"* and suspects his Linux Bluetooth stack. This is the +unauthorised path. Installable as +`pip install git+https://github.com/malmeloo/FindMy.py#feat/accessory-communication`. + +**[seemoo-lab/airtag](https://github.com/seemoo-lab/airtag)** — Apache-2.0, JavaScript +The FRIDA instrumentation scripts, and **the resource stek29 named for the L2CAP side**. +`hook_durian.js` plays arbitrary sound sequences ("AirTechno") on a paired AirTag — i.e. it does +work in the connected case, which is the interesting part. The cost is the setup: **a jailbroken +iPhone** on iOS 14.6–14.8 (checkm8-supported hardware), paired with the AirTag, plus a host +running FRIDA. Not a route to a shipping feature, but the best available description of what the +authorised path actually looks like. + +--- + +## Implementations that reportedly already do this + +**[seemoo-lab/AirGuard](https://github.com/seemoo-lab/AirGuard)** — Kotlin, Apache-2.0, ~2.5k stars +An Android anti-stalking app that scans for nearby Find My accessories. **malmeloo states in #88 +that AirGuard supports ringing**, and that Apple's own Android app likely does too. If that is +right, it is an existence proof that this is doable from Android without a jailbreak, and its +source is the obvious place to look. *(Not verified line-by-line — worth confirming before relying +on it.)* + +**Apple's "Tracker Detect" for Android** — same claim, closed source, but it demonstrably interacts +with tags it does not own. + +**[Apple's Find My Network Accessory Program](https://developer.apple.com/find-my/)** +The official specification for **MFi third-party accessories**, which documents the GATT services +a licensed accessory exposes, including sound. Worth reading for the shape of the thing — but note +it describes third-party accessories rather than AirTags, and those carry factory certificates +precisely so that arbitrary software cannot drive the privileged paths. + +--- + +## Papers + +**[AirTag of the Clones: Shenanigans with Liberated Item Finders](https://www.researchgate.net/publication/362264435_AirTag_of_the_Clones_Shenanigans_with_Liberated_Item_Finders)** +— Roth, Freyer, Hollick, Classen. WOOT'22 / IEEE SPW 2022. The firmware-level work behind +`seemoo-lab/airtag`; a copy ships in that repo as `woot22-paper.pdf`. Covers getting code +execution on the AirTag itself, which is how the sound behaviour was characterised. +[Register write-up](https://www.theregister.com/2022/05/27/apple_airtag_sounds/) if you want the +short version. + +**[Who Can Find My Devices? Security and Privacy of Apple's Crowd-Sourced Bluetooth Location Tracking System](https://petsymposium.org/popets/2021/popets-2021-0045.pdf)** +— Heinrich, Stute, Kornhuber, Hollick. PoPETs 2021. The original protocol analysis that OpenHaystack +and everything downstream is built on. Background rather than directly about connected mode, but it +is the document that defines the terms everyone else uses. + +**[AirGuard — Protecting Android Users From Stalking Attacks By Apple Find My Devices](https://arxiv.org/abs/2202.11813)** +— Heinrich, Bittner, Hollick. The paper behind the app above, covering how Apple's own tracking +protection was reverse engineered. + +**[Track You: A Deep Dive into Safety Alerts for Apple AirTags](https://petsymposium.org/popets/2023/popets-2023-0102.pdf)** +— Shafqat et al., PoPETs 2023. Analyses the anti-stalking alerting, which is the other half of +"how does a device that does not own a tag interact with it". + +--- + +## In this repository + +- **[#48](https://github.com/parawanderer/OpenTagViewer/issues/48)** and **[#139](https://github.com/parawanderer/OpenTagViewer/pull/139)** (@ubrt) — local Bluetooth scanning and playing a nearby accessory's sound. + **[#139](https://github.com/parawanderer/OpenTagViewer/pull/139) is directly affected by stek29's finding**: if the GATT path only covers the unauthorised + command, then the owner-nearby case — the one that matters when your own tag is next to you — + is a different protocol. +- **[#17](https://github.com/parawanderer/OpenTagViewer/issues/17)** — the original "Play Sound" request, and where @ubrt's work started. +- **[#131](https://github.com/parawanderer/OpenTagViewer/issues/131)** — locating the owner's own iPhones, iPads and Macs. A different mechanism again (those + self-report to iCloud), but it is the other half of "why does this app show less than Apple's". + +--- + +*Compiled by Claude Code from a conversation with @parawanderer. Links checked at time of writing; +nothing here was read out of `rustpush`, `apple-private-apis` or `export-findmy`, per the +clean-room note in [`findmy-export/README.md`](./findmy-export/README.md).*