0fa6a2dd2a
paper/vigil.md: per-ecosystem detection methodology (Apple/FMDN/Samsung/Tile/DULT) and the rotation-clone presence engine (identity-agnostic detection, the rotation-rate squeeze, FP analysis, honest floor). Draft working paper accompanying the reference implementation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0187UiEtasyowhEBYs6s9iF5
551 lines
31 KiB
Markdown
551 lines
31 KiB
Markdown
# VIGIL: On-Device Temporal Detection of Personal Location Trackers, Including Identity-Rotating Clones
|
||
|
||
**Kara Zajac** · Black Flag Intel
|
||
Draft v0.1 · 2026-07-15 · *working paper, accompanies the VIGIL reference implementation*
|
||
|
||
---
|
||
|
||
## Abstract
|
||
|
||
Consumer item-trackers — Apple AirTag, Tile, Samsung Galaxy SmartTag, and the
|
||
Google Find My Device network — have become instruments of stalking: in a survey
|
||
of intimate-partner-violence survivors, 44% of those stalked reported physical
|
||
location tracking, with vehicles the most common hiding place. Every deployed
|
||
countermeasure (Apple *Tracker Detect*, the iOS *Find My* and Android *Unknown
|
||
Tracker* alerts, and the open-source *AirGuard*) shares a structural assumption:
|
||
it **keys on a device identity** and alarms when the *same* identifier is seen
|
||
repeatedly while moving with the victim. This assumption fails in two directions.
|
||
It is *spatial* where the true signal is *temporal*; and it is defeated outright
|
||
by identity-rotating clones — a commodity ESP32 firmware that cycles ~2,000 Find
|
||
My keys (a fresh one every ~30 s) tracked a victim for five days while raising
|
||
**zero** alerts on every tool tested.
|
||
|
||
We present **VIGIL**, an Android application that reframes the problem as
|
||
*temporal co-movement*: not "is a tracker near me" but "has *the same* tracker
|
||
been at enough of *my* distinct places, over enough time, while close enough to
|
||
be on me." VIGIL contributes (1) a cross-ecosystem, fully on-device detector that
|
||
parses five BLE wire formats and gates alerts on an RSSI proximity test that
|
||
prior tools omit, together with a learned, on-device *baseline* that auto-trusts
|
||
the trackers that live where the user lives; and (2) an identity-agnostic
|
||
**presence engine** that detects rotating-key clones by the one thing identity
|
||
churn cannot hide — an unbroken, coherent, co-moving RF *presence* — and a
|
||
**rotation-rate "squeeze"** argument showing that running an identity detector and
|
||
a churn detector together leaves no rotation rate that evades both. VIGIL holds no
|
||
network permission: the tracker database, the learned baseline, and all inference
|
||
run and remain on the device. We describe the per-ecosystem detection methodology,
|
||
the temporal model, the presence engine, the implementation, and a proposed
|
||
evaluation, and we are explicit about the adaptive-adversary floor below which
|
||
single-session on-device detection is information-theoretically impossible.
|
||
|
||
---
|
||
|
||
## 1. Introduction
|
||
|
||
The same crowd-sourced location networks that help people find lost keys also let
|
||
a motivated abuser find a person. Apple's Find My network turns a US$29 coin-sized
|
||
beacon into a globe-spanning tracker that piggybacks on hundreds of millions of
|
||
other people's iPhones; Google's Find My Device network does the same across the
|
||
Android install base. Placed in a target's bag, coat, or car, such a beacon
|
||
reports the target's location without any infrastructure of the attacker's own.
|
||
|
||
Platform vendors responded with **unwanted-tracking alerts**, and the security
|
||
community produced **AirGuard** (TU Darmstadt), the best open-source detector.
|
||
These tools work, in their measured evaluations, *slowly and narrowly*: an
|
||
in-the-wild study found an iPhone took 5 h 41 m to alert on an AirTag (and only on
|
||
reaching home Wi-Fi), never detected a Chipolo, and that Samsung's own app never
|
||
found its own SmartTag. All of them share the same core algorithm — **count
|
||
repeat sightings of one BLE identifier; alarm past a threshold** — and all of
|
||
them inherit its two structural weaknesses:
|
||
|
||
1. **Spatial, not temporal.** They reason about *presence*, so they need
|
||
hand-tuned thresholds to avoid alarming on the many trackers legitimately
|
||
around us. The discriminating fact — that a *stalking* tracker recurs across
|
||
the victim's *changing* locations — is used only weakly.
|
||
2. **Identity-bound.** Because they track an identifier, any tracker that
|
||
*rotates* its identifier faster than the accumulation window is invisible.
|
||
Positive Security's *Find You* clone exploits exactly this: ~2,000 pre-loaded
|
||
Find My public keys, one broadcast every ~30 s, so every sighting looks like a
|
||
brand-new device. In a five-day carry test it defeated iOS, Apple's Tracker
|
||
Detect, and AirGuard's background detection.
|
||
|
||
**VIGIL** is built on the opposite premise. Where OVERWATCH (its sibling project)
|
||
asks *what is watching this place*, VIGIL asks *what has been with me* — it is a
|
||
temporal detector. Its contributions:
|
||
|
||
- **A cross-ecosystem, on-device temporal detector.** VIGIL parses Apple Find My,
|
||
Google FMDN, Samsung SmartTag, Tile, and the unified DULT format, filters to the
|
||
separated-from-owner state where signalled, and escalates a device only when it
|
||
clears a **co-movement test** across the user's distinct geographic cells — gated
|
||
by an **RSSI proximity check** that AirGuard omits, which rejects the "tracker in
|
||
a passing car" false positive.
|
||
- **A learned, on-device baseline.** VIGIL learns the places a user dwells and
|
||
auto-trusts trackers that recur there across days — the user's own AirTag, a
|
||
partner's Tile — collapsing the dominant benign false-positive class without a
|
||
server and without the user tagging anything.
|
||
- **A presence engine for rotating clones.** VIGIL detects the attack class that
|
||
defeats every deployed tool, by abandoning identity tracking in favour of
|
||
identity-agnostic *presence continuity*, and we give a squeeze argument
|
||
(§5.4) that closes the evasion gap left by either detector alone.
|
||
- **A privacy architecture with no network.** VIGIL requests no `INTERNET`
|
||
permission. There is no server, no account, and no telemetry.
|
||
|
||
We are equally explicit about what VIGIL *cannot* do (§5.6, §10): a patient,
|
||
low-duty-cycle adaptive adversary can push detection from minutes-latency
|
||
single-session to days-latency multi-session, and no on-device passive detector
|
||
escapes that floor.
|
||
|
||
---
|
||
|
||
## 2. Background and threat model
|
||
|
||
### 2.1 BLE trackers and the separated state
|
||
|
||
An item-tracker is a Bluetooth-Low-Energy beacon. When **near its owner** it is
|
||
connected or advertises a non-locatable payload; when **separated** from its owner
|
||
it broadcasts a payload that any nearby "finder" phone can encrypt-and-upload to
|
||
the vendor's network, letting the owner retrieve its location. Two facts make
|
||
passive third-party detection possible at all:
|
||
|
||
1. Only the **separated** state matters for stalking — a tracker with its owner is
|
||
not following *you* — and separated trackers advertise loudly (every ~2 s).
|
||
2. To be findable, separated trackers **rotate their identifier slowly.** The
|
||
Apple/Google **DULT** specification, and each vendor's implementation, rotate
|
||
the advertised address/key roughly **once per 24 hours** in the separated state
|
||
(versus ~15 minutes near-owner). A separated AirTag holds one identity for a
|
||
full day, re-rolling at 04:00 local. This slow rotation is *deliberate* — it is
|
||
what lets a victim's phone recognise the same tracker over time — and it is the
|
||
invariant the clone attack violates and that VIGIL's presence engine exploits.
|
||
|
||
### 2.2 Threat model
|
||
|
||
**Adversary.** Plants one or more trackers on the victim (bag, vehicle, clothing).
|
||
May use (a) a genuine, unmodified tracker; (b) a genuine tracker with a physically
|
||
disabled speaker; or (c) a modified/cloned emitter (custom firmware, rotating
|
||
keys, silent). The adversary controls the tracker but not the victim's phone.
|
||
|
||
**Defender.** The victim's Android phone, running VIGIL, **passively** scanning
|
||
BLE. It never transmits to or connects to the tracker for detection (an optional,
|
||
user-initiated GATT "make it ring" is out of the passive path). It has no server.
|
||
|
||
**Goal.** Alert the victim, with useful latency and a tolerable false-positive
|
||
rate, that a tracker is travelling with them — *including* when the tracker rotates
|
||
its identity or is silent.
|
||
|
||
**Non-goals.** VIGIL does not track individuals, does not deanonymise tracker
|
||
owners beyond the vendor-provided reactive flows, and does not claim to defeat a
|
||
patient adaptive adversary in a single session (§5.6).
|
||
|
||
---
|
||
|
||
## 3. Per-ecosystem detection
|
||
|
||
VIGIL's scanner (`scan/BleTrackerScanner.kt`) runs a hardware-filtered BLE scan —
|
||
the controller wakes the app only on a signature match, which makes screen-off
|
||
scanning deliverable — and hands each match to a stateless parser
|
||
(`scan/TrackerParser.kt`) that recognises five wire formats. For each ecosystem we
|
||
describe (a) the on-air **signature** VIGIL matches, (b) how it reads the
|
||
**separated** state, and (c) the **identity** it keys on for temporal correlation.
|
||
|
||
### 3.1 Apple Find My / AirTag
|
||
|
||
**Signature.** BLE manufacturer-specific data under Apple company id `0x004C`,
|
||
message type `0x12` (offline finding), payload length `0x19` (25 bytes). After
|
||
Android strips the two company-id bytes, the payload is
|
||
`[type=0x12, length=0x19, status, key(22), keyTopBits, hint]`.
|
||
|
||
**Separated state.** The **status byte** carries a "maintained" bit (bit 2): set
|
||
when the owner was in range within the current rotation period, cleared when
|
||
separated. VIGIL treats a cleared/absent maintained bit as separated. (Setting the
|
||
status byte to `0x00` is a known first-party-alert-suppression trick, so VIGIL
|
||
does not treat the bit as ground truth for *benign-ness* — only for *relevance*.)
|
||
|
||
**Identity.** The advertised P-224 public key **is** the identity, and — uniquely
|
||
— the BLE MAC is *derived from the key*, so both rotate together. VIGIL keys on a
|
||
hash of the advertised key bytes, which is stable across the ~24 h separated
|
||
epoch. The 04:00 re-roll intentionally severs continuity; §5 addresses bridging it.
|
||
|
||
### 3.2 Google Find My Device network (FMDN / "Find Hub")
|
||
|
||
**Signature.** BLE service data (AD type `0x16`) under 16-bit UUID `0xFEAA` (the
|
||
Eddystone UUID; pairing uses a different service, `0xFE2C`). The first
|
||
service-data byte is the frame type.
|
||
|
||
**Separated state.** VIGIL reads it **in cleartext**: frame `0x40` = normal, frame
|
||
`0x41` = unwanted-tracking/separated. A subtlety with defensive consequences: the
|
||
`0x41` state is *server-triggered* — Google decides a tag has been separated
|
||
(~≥30 min) and a nearby Android device flips it. A freshly-separated stock tag is
|
||
therefore still rotating its EID (~every 1024 s) and MAC together and is **not
|
||
passively re-linkable** until flipped to `0x41`, at which point the MAC freezes for
|
||
24 h. VIGIL keys FMDN identity on the MAC, valid within the separated epoch.
|
||
|
||
### 3.3 Samsung Galaxy SmartTag / SmartTag2
|
||
|
||
**Signature.** BLE service data under UUID `0xFD5A` (the Samsung company id
|
||
`0x0075` is emitted by all Samsung gear and is not tag-unique; the service UUID is
|
||
the anchor). A connectable/setup tag also exposes a `smarttag` device name.
|
||
|
||
**Separated state.** The first service-data byte encodes a 3-bit state (bits 5–7):
|
||
`2` = lost (after 15 min), `3` = "overmature-lost" (after 24 h; triggers Samsung's
|
||
own anti-stalking), `4`–`6` = paired/connected. VIGIL treats states 2–3 as
|
||
separated. In overmature state the rotating privacy id becomes daily-static,
|
||
giving a ~24 h re-link window; in the first 24 h everything rotates every 15 min
|
||
and is not passively re-linkable.
|
||
|
||
### 3.4 Tile
|
||
|
||
**Signature.** BLE service data under `0xFEED` (activated), `0xFEEC`
|
||
(pre-activation), or the legacy `0xFE84`.
|
||
|
||
**Separated state.** *None.* A Tile emits no separated/lost-state beacon — it is
|
||
"always findable," advertising identically whether with its owner or lost.
|
||
|
||
**Identity.** Tile's decisive property: the **BLE MAC is static and never
|
||
rotates**, in any state. This makes Tile the easiest ecosystem to correlate (an
|
||
indefinite persistent key) but the one with no separated flag, so VIGIL treats
|
||
every Tile as a live candidate and relies entirely on persistence + co-movement.
|
||
Notably, Tile's "Anti-Theft Mode" — which hides a Tile from Tile's *own*
|
||
Scan-and-Secure and from platform alerts — changes nothing at the BLE layer, so a
|
||
third-party scanner such as VIGIL detects an anti-theft Tile identically to a
|
||
normal one, closing a documented stalking gap.
|
||
|
||
### 3.5 DULT (unified, emerging)
|
||
|
||
**Signature.** BLE service data under UUID `0xFCB2`.
|
||
|
||
**Separated state.** The near-owner bit is the least-significant bit of byte 14
|
||
(`1` = near owner, `0` = separated); a Network-ID byte identifies the vendor
|
||
network. VIGIL parses this for forward compatibility as trackers migrate to the
|
||
unified format.
|
||
|
||
### 3.6 Partner tags and summary
|
||
|
||
Chipolo, Pebblebee, eufy, Motorola, Jio, and Rolling Square do **not** carry a
|
||
brand-specific signature: each SKU advertises as the *network* it was provisioned
|
||
to (Apple `0x004C`/`0x12` or Google FMDN `0xFEAA`). VIGIL detects the network, not
|
||
the brand; legacy proprietary SKUs (classic Chipolo, original JioTag) join no
|
||
crowd network and are out of scope.
|
||
|
||
| Ecosystem | Signature | Separated signal | MAC rotation (separated) | Re-link window |
|
||
|---|---|---|---|---|
|
||
| Apple Find My | mfg `0x004C`, type `0x12` | status "maintained" bit cleared | ~24 h (re-roll 04:00), MAC = key | ~24 h |
|
||
| Google FMDN | svc data `0xFEAA`, frame `0x41` | cleartext frame `0x41` (server-gated) | 24 h once `0x41`; else ~1024 s | ~24 h once separated |
|
||
| Samsung SmartTag | svc data `0xFD5A` | state byte 2/3 (lost/overmature) | 24 h once overmature; else 15 min | ~24 h once overmature |
|
||
| Tile | svc data `0xFEED`/`0xFEEC` | none (always findable) | **none — static MAC** | indefinite |
|
||
| DULT (unified) | svc data `0xFCB2` | byte-14 LSB = 0 | ~24 h separated | ~24 h |
|
||
|
||
---
|
||
|
||
## 4. The temporal co-movement model
|
||
|
||
### 4.1 The reframing
|
||
|
||
A tracker being *near* a user is uninformative — modern environments are saturated
|
||
with them. The stalking signal is **persistence across the user's own changing
|
||
locations**: because the phone is (almost) always on the user, counting a
|
||
tracker's presence across the user's *distinct places* over time *is* the
|
||
co-movement measure, and it needs no second trajectory.
|
||
|
||
### 4.2 The test
|
||
|
||
Each parsed sighting is geotagged with a coarse fused-location fix and written to
|
||
an on-device store (`data/db/VigilDatabase.kt`) as `(trackerId, timestamp, rssi,
|
||
separated, lat, lon, geohash7)`, retained 14 days. Over the trailing 24 h a
|
||
tracker is scored on:
|
||
|
||
- **Effective sightings** — raw sightings debounced to at most one per 15 min, so a
|
||
2 s-interval beacon cannot trivially inflate the count. Require ≥ 3.
|
||
- **Distinct places** — the number of distinct **geohash-7 cells** (~153 m) among
|
||
its sightings. Require ≥ N, with N = 2/3/4 by sensitivity (high/medium/low).
|
||
- **Span** — first-to-last minutes. Require ≥ T, with T = 30/45/90 min.
|
||
- **RSSI proximity gate** — the smoothed peak RSSI must at least once cross an
|
||
on-body/in-bag floor (−90/−85/−80 dBm by sensitivity). **This is the piece
|
||
AirGuard omits**, and it is what distinguishes a tracker *on* the user from one
|
||
that merely shared a road with them at two lights.
|
||
|
||
A device escalates `OBSERVED → SUSPICIOUS → ALERTING` (`detect/CoMovementEvaluator.kt`).
|
||
ALERTING requires all four conditions; SUSPICIOUS requires repeated close sightings
|
||
without yet enough spatial/temporal spread. Non-separated (near-owner) sightings
|
||
never escalate. These thresholds are adapted from AirGuard's field-tuned model
|
||
(≥3 sightings, N distinct locations ≥150 m apart, T minutes) with the added RSSI
|
||
gate.
|
||
|
||
### 4.3 The learned baseline and the allowlist
|
||
|
||
Two trust signals suppress the dominant benign class — the trackers that *belong*
|
||
in the user's life.
|
||
|
||
- **Allowlist.** The user marks a tracker "This is mine"; it never alerts again.
|
||
- **Learned baseline** (`detect/BaselineManager.kt`). VIGIL counts location-tagged
|
||
sightings per geohash-6 cell; a well-visited cell becomes an **anchor** (home,
|
||
work). A tracker seen at an anchor on ≥ 3 distinct calendar days is auto-marked
|
||
*baseline-safe* and excluded from alerting. This learns "the AirTag that is
|
||
always in my house" without a server and without the user labelling anything.
|
||
(The current visit-count anchor heuristic is a v1; the roadmap replaces it with
|
||
stay-point/dwell clustering.)
|
||
|
||
---
|
||
|
||
## 5. The rotation-clone presence engine
|
||
|
||
This section addresses the attack class that defeats every deployed detector, and
|
||
is the paper's core modeling contribution. The full design is in
|
||
`docs/detection-rotation-clone.md`; we summarise the argument here.
|
||
|
||
### 5.1 The attack and why identity detectors fail
|
||
|
||
The *Find You* clone is one physical ESP32 radio that broadcasts a pool of ~2,000
|
||
Apple Find My public keys, cycling to a new one every ~30 s. To any identity-keyed
|
||
detector each key is a new device seen once, so the "same identifier ≥ 3 times"
|
||
counter never accumulates and the detector stays silent — measured at zero alerts
|
||
over five days.
|
||
|
||
### 5.2 The exploitable invariant
|
||
|
||
The defence begins with a standards fact: DULT, Apple, and Google **mandate ~24 h
|
||
rotation in the separated state**. A legitimate separated tracker therefore holds
|
||
one identity for a full day; a clone rotating every 30 s runs **~2,880× faster
|
||
than any specification-compliant device**. This is not a heuristic "that seems
|
||
suspicious" — it is a *categorical* violation of a standardised invariant, giving
|
||
a clean anomaly baseline.
|
||
|
||
### 5.3 Presence, not identity
|
||
|
||
VIGIL stops tracking *who* is present and starts tracking *whether one radio is
|
||
continuously present*. A rotating clone, to function as a tracker, must emit an
|
||
**unbroken stream of separated-state beacons at roughly stable close-range RSSI
|
||
that moves with the victim across locations where the ambient tracker population
|
||
fully turns over.** We formalise three identity-agnostic quantities:
|
||
|
||
- **Novel-identifier churn rate** `λ_novel` — the rate of never-before-seen
|
||
separated identities (over a 5-min window against a 30-min novelty set). A real
|
||
separated tracker ≈ 0.0007/min; the clone ≈ 2/min.
|
||
- **Presence continuity** — the duty cycle with which *some* separated beacon
|
||
occupies the close band (RSSI ≥ ~−65 dBm), and the longest unbroken run.
|
||
- **Seamless-novel-handover chain** — the number of times the close-band identity
|
||
*changes* to a **novel** id with **no RSSI discontinuity** (`|Δrssi| ≤ 8 dB`)
|
||
and **no time gap** (`Δt ≤ 6 s`). One physical radio produces a long chain of
|
||
such seamless handovers; a crowd of many radios at many distances produces
|
||
gappy, RSSI-discontinuous handovers. This chain is the tell no amount of identity
|
||
churn can fake while remaining a functioning tracker.
|
||
|
||
The physics that make co-movement a hard discriminator: a static tag or an
|
||
independently-moving passer can remain in the close band (radius R ≈ 10 m) for at
|
||
most `2R / v_rel` — ≈ 1.5 s at driving speed. Only a co-moving emitter (relative
|
||
velocity ≈ 0) has unbounded dwell. So a close-band presence sustained across ≥ 3
|
||
distinct cells while the user is moving *cannot* be explained by any single
|
||
ambient source.
|
||
|
||
### 5.4 The pipeline and the squeeze
|
||
|
||
A three-stage streaming pipeline (all O(1)-ish per sighting, on-device): a **CUSUM
|
||
change-point** on `λ_novel` versus a rolling personal baseline *triggers* an
|
||
identity-agnostic **presence-track confirmer** (counting seamless novel handovers
|
||
and RSSI coherence), which alarms only behind a hard **co-movement gate** (net
|
||
displacement ≥ 500 m across ≥ 3 cells while moving).
|
||
|
||
The robustness argument is the **rotation-rate squeeze.** VIGIL runs the classic
|
||
identity detector (which catches any identity persisting > ~3–5 min) *and* the
|
||
churn detector (which catches sustained novel-churn above ~0.5–1/min)
|
||
simultaneously:
|
||
|
||
```
|
||
rotation period: 30 s ─── 2 min ─── 5 min ──────────── 24 h
|
||
└ churn path catches ┘
|
||
└ overlap ┘
|
||
└────── identity path catches ──────┘
|
||
```
|
||
|
||
**No rotation period evades both.** Fast rotation is caught by churn; slow rotation
|
||
by identity; the middle by both. Moreover the presence-continuity path is
|
||
rotation-*rate*-agnostic — slowing rotation lowers `λ_novel` but does nothing to
|
||
the unbroken coherent co-moving track. The adversary's only real lever is
|
||
*continuity*, not rate.
|
||
|
||
### 5.5 False positives
|
||
|
||
The engine's viability rests on dense-urban and transit false positives. Four
|
||
discriminators, strongest first: (1) the **seamless-handover chain** — a crowd is
|
||
many radios and cannot fake one continuous `(t, rssi)` track across 500 m; (2)
|
||
**co-movement across turnover** — bounded ambient dwell means a presence spanning
|
||
≥ 3 non-adjacent cells is not any single source; (3) **RSSI coherence** — one
|
||
radio yields a smooth RSSI random-walk, a crowd a high-variance cloud; (4)
|
||
**stationary suppression** — no motion, no alarm, which removes the entire
|
||
apartment/at-home class. The genuinely hard case is **live rush-hour transit**,
|
||
where boarding churn, vehicular co-movement, and continuous occupancy align. The
|
||
resolution is a **mode-change gate**: a transit crowd cannot follow the victim
|
||
*off the train and down the street*, so escalation to a hard alert is withheld
|
||
until the coherent co-moving presence survives a detected context transition. This
|
||
trades **latency** for transit-FP suppression — a deliberate, disclosed choice,
|
||
not a false alarm.
|
||
|
||
### 5.6 The honest floor
|
||
|
||
Every evasion degrades the adversary's own tracker (slower/duty-cycled rotation
|
||
and reduced TX power mean fewer, coarser location fixes). Below some duty cycle — a
|
||
few randomly-timed, random-power beacons per 15 min — the emitter becomes
|
||
information-theoretically indistinguishable, *within a single session*, from sparse
|
||
ambient pass-bys, and detection must move to **cross-session recurrence** (the same
|
||
anomalous weakly-co-moving presence recurring day after day along the victim's
|
||
routes), which costs **days of latency** and requires opt-in persistent storage.
|
||
This is the honest floor: a patient, low-duty-cycle adaptive adversary forces
|
||
VIGIL from minutes-latency single-session detection to days-latency multi-session
|
||
detection. VIGIL states this rather than claiming an impossible guarantee.
|
||
|
||
---
|
||
|
||
## 6. Implementation
|
||
|
||
VIGIL is a native Android/Kotlin app (min SDK 26), reusing the scanning and
|
||
service architecture of OVERWATCH.
|
||
|
||
```
|
||
scan/TrackerSignatures.kt BLE hex signatures for every ecosystem
|
||
scan/TrackerParser.kt ScanResult -> TrackerObservation (five wire formats)
|
||
scan/BleTrackerScanner.kt hardware-filtered scan (screen-off capable)
|
||
service/ScanService.kt foreground service; geotags, persists, alerts, vibrates
|
||
data/db/VigilDatabase.kt Room: trackers, 14-day sightings, baseline places
|
||
data/TrackerRepository.kt ingest + baseline + evaluate on each sighting
|
||
detect/CoMovementEvaluator the co-movement test + RSSI proximity gate
|
||
detect/BaselineManager.kt anchor-place learning -> auto-trust household tags
|
||
detect/PresenceEngine.kt (next) the rotation-clone engine of §5
|
||
```
|
||
|
||
**Privacy properties.** The manifest declares **no `INTERNET` permission**; VIGIL
|
||
cannot open a socket. All state — trackers, sightings, baseline, inference — lives
|
||
in an on-device SQLite database and never leaves the phone. `BLUETOOTH_SCAN` is
|
||
declared *without* `neverForLocation`, because detection is intentionally
|
||
location-correlated (and because that flag would filter out the very beacon-shaped
|
||
advertisements VIGIL parses). VIGIL only listens: it never advertises, probes, or
|
||
connects to a tracker on the detection path.
|
||
|
||
---
|
||
|
||
## 7. Proposed evaluation
|
||
|
||
VIGIL is a prototype; the following methodology is specified but **not yet
|
||
executed**, and no detection-rate claims are made here.
|
||
|
||
- **Red-team emitter.** A parameterised OpenHaystack/*Find You* ESP32 clone with
|
||
sweepable rotation period (30 s … 24 h), TX-power variance, and duty cycle. Carried,
|
||
it is a real co-moving emitter — ground-truth positive without endangering anyone.
|
||
- **Ambient baselines.** Raw sighting logs across environment classes (rural drive,
|
||
suburban, dense-urban walk, bus, rush-hour subway, highway, apartment block,
|
||
crowd), each with paired emitter-on/off runs.
|
||
- **Record/replay harness.** The presence engine is a pure
|
||
`List<Sighting> → List<ScoredEvent>` function; ambient logs are recorded once and
|
||
replayed to tune thresholds deterministically and offline in Kotlin, and
|
||
attack-in-context scenarios are synthesised by merging clean and emitter-only
|
||
traces.
|
||
- **Metrics.** True-positive rate at a fixed false-positive budget (headline:
|
||
TPR at ≤ 1 FP per active-carry-day, per class); detection latency
|
||
(attach → first alert); ROC/AUC; robustness curves versus rotation period, TX
|
||
power, and duty cycle; and ablations isolating each discriminator.
|
||
|
||
---
|
||
|
||
## 8. Comparison to prior art
|
||
|
||
| Capability | Apple Tracker Detect | iOS / Android built-in | AirGuard | **VIGIL** |
|
||
|---|---|---|---|---|
|
||
| Cross-ecosystem | Find My only | AirTag + FMDN + DULT partners (not Tile) | yes | yes |
|
||
| Background / automatic | no (manual scan) | yes | yes | yes |
|
||
| Fully on-device / no account | yes | yes | yes | **yes (no network permission)** |
|
||
| RSSI proximity gate on alerts | no | undisclosed | **no** | **yes** |
|
||
| Learned baseline (auto-trust) | no | no | manual ignore only | **yes** |
|
||
| Detects rotating-key clones | no | no | no | **designed (§5)** |
|
||
| Covers Tile anti-theft | no | no | yes (static MAC) | yes |
|
||
| Survivor-grade evidence export | no | no | limited | roadmap |
|
||
|
||
Cross-ecosystem, offline operation, and temporal co-movement are *not* novel —
|
||
AirGuard has them. VIGIL differentiates on the pieces AirGuard's architecture
|
||
forecloses: the RSSI gate, the learned baseline, and above all the identity-agnostic
|
||
presence engine that targets the clone attack.
|
||
|
||
---
|
||
|
||
## 9. Privacy and ethics
|
||
|
||
VIGIL is a defensive, survivor-oriented tool. It maps and reasons about
|
||
*trackers*, never about people, and holds no network to which anything could be
|
||
exfiltrated. The learned baseline is a privacy *feature* — it exists so the app
|
||
does not need to phone home to know which tags are "normal." The dual-use surface
|
||
(BLE parsing that could in principle be repurposed for tracking) is mitigated by
|
||
the passive-only, no-transmit, no-network design. As with all anti-stalking tools,
|
||
VIGIL should be paired with survivor-safety guidance: a detected tracker can
|
||
indicate an abuser with physical access, and "make it ring / remove the battery"
|
||
advice must be given with situational-safety caveats.
|
||
|
||
---
|
||
|
||
## 10. Limitations
|
||
|
||
- **Single-session floor (§5.6).** A patient low-duty-cycle adaptive adversary is
|
||
only detectable across sessions.
|
||
- **Near-owner and freshly-separated tags** are not passively re-linkable (Apple
|
||
near-owner; default FMDN before the `0x41` flip; Samsung in the first 24 h).
|
||
- **Wire-format offsets** are drawn from published reverse-engineering and, for
|
||
SmartTag2 and 2024+ Tile, inferred from older captures; they require empirical
|
||
validation before production trust.
|
||
- **Background scan latency** is bounded by Android's ~15-min background floor
|
||
absent a persistent foreground service; motion-triggered scanning (roadmap)
|
||
reduces it.
|
||
- **Not yet evaluated.** §7 is a plan, not a result.
|
||
|
||
---
|
||
|
||
## 11. Related work
|
||
|
||
Heinrich et al. reverse-engineered Apple Find My ("Who Can Find My Devices?", PETS
|
||
2021) and built AirGuard ("Protecting Android Users from Stalking Attacks by Apple
|
||
Find My Devices", WiSec 2022), later measuring real-world stalking prevalence and
|
||
detector efficacy ("Please Unstalk Me", PETS 2024). Mayberry/Shafqat et al.
|
||
("Who Tracks the Trackers?" / "Track You", PETS 2023) analysed AirTag anti-stalking
|
||
timing. Yu et al. (USENIX Security 2024) reverse-engineered Samsung SmartTag, and
|
||
Georgia Tech (2025) analysed Tile's static-MAC linkability. Bräunlein/Positive
|
||
Security demonstrated the rotating-key clone ("Find You"). Naturalistic evaluations
|
||
("Stop Following Me!", EuroUSEC 2024) found deployed detectors rarely useful. The
|
||
Apple/Google **DULT** IETF effort standardises the separated-state signalling and a
|
||
baseline detection algorithm. VIGIL builds directly on this line and extends it to
|
||
the identity-rotating adversary that all of it currently misses. Change-point
|
||
detection (Page 1954; Basseville & Nikiforov 1993) and bounded-memory distinct
|
||
counting (Datar et al.; Flajolet et al.) underpin the presence engine; the
|
||
identity-free "sustained close presence" reasoning is anticipated by
|
||
contact-tracing designs (DP-3T; Google/Apple Exposure Notification).
|
||
|
||
---
|
||
|
||
## 12. Conclusion
|
||
|
||
The anti-stalking detectors people rely on are spatial where the threat is
|
||
temporal, and identity-bound where the adversary can rotate identities. VIGIL
|
||
reframes detection as temporal co-movement, adds the proximity gate and learned
|
||
baseline that make that reframing practical on-device and private, and introduces
|
||
a presence engine that detects the rotating-key clone by the one property it cannot
|
||
hide. The rotation-rate squeeze shows the combination leaves no safe rotation rate;
|
||
the honest floor shows where even this stops. We release VIGIL as a working
|
||
prototype and an open design, and invite the empirical evaluation §7 lays out.
|
||
|
||
---
|
||
|
||
## References
|
||
|
||
1. A. Heinrich, M. Stute, T. Kornhuber, M. Hollick. "Who Can Find My Devices? Security and Privacy of Apple's Crowd-Sourced Bluetooth Location Tracking System." *PoPETs* 2021. https://petsymposium.org/popets/2021/popets-2021-0045.pdf
|
||
2. A. Heinrich, N. Bittner, M. Hollick. "AirGuard — Protecting Android Users from Stalking Attacks by Apple Find My Devices." *ACM WiSec* 2022. https://arxiv.org/pdf/2202.11813 · code: https://github.com/seemoo-lab/AirGuard
|
||
3. A. Heinrich, L. Würsching, M. Hollick. "Please Unstalk Me: Understanding Stalking with Bluetooth Trackers and Democratizing Anti-Stalking Protection." *PoPETs* 2024. https://petsymposium.org/popets/2024/popets-2024-0082.pdf
|
||
4. N. Shafqat et al. "Track You: A Deep Dive into Safety Alerts for Apple AirTags." *PoPETs* 2023. https://petsymposium.org/popets/2023/popets-2023-0102.pdf
|
||
5. T. Yu et al. "Analyzing the Security of Samsung SmartTag." *USENIX Security* 2024. https://arxiv.org/pdf/2210.14702
|
||
6. Kumar, Raymaker, Specter. Tile protocol analysis. 2025. https://arxiv.org/html/2510.00350v1
|
||
7. F. Bräunlein / Positive Security. "Find You: Building a stealth AirTag clone." https://positive.security/blog/find-you · https://github.com/positive-security/find-you
|
||
8. A. Catley. "AirTag reverse engineering." https://adamcatley.com/AirTag.html
|
||
9. IETF DULT WG. "Detecting Unwanted Location Trackers" (accessory-protocol; threat-model). https://datatracker.ietf.org/wg/dult/
|
||
10. Google. "Find My Device network (Fast Pair FMDN) specification." https://developers.google.com/nearby/fast-pair/specifications/extensions/fmdn
|
||
11. SEEMOO-Lab. "OpenHaystack." https://github.com/seemoo-lab/openhaystack
|
||
12. "Stop Following Me! Evaluating the Effectiveness of Anti-Stalking Features." *EuroUSEC* 2024. https://arxiv.org/abs/2312.07157
|
||
13. E. S. Page. "Continuous Inspection Schemes." *Biometrika* 41, 1954. (CUSUM.)
|
||
14. M. Basseville, I. Nikiforov. *Detection of Abrupt Changes: Theory and Application.* 1993.
|
||
15. M. Datar, A. Gionis, P. Indyk, R. Motwani. "Maintaining Stream Statistics over Sliding Windows." *SODA* 2002.
|
||
|
||
*This working paper accompanies the VIGIL reference implementation. The rotation-clone
|
||
engine (§5) is fully specified in `docs/detection-rotation-clone.md` and is designed
|
||
but not yet wired into the shipping build.*
|