Add technical paper + link from README
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
This commit is contained in:
@@ -105,6 +105,12 @@ data/location/LocationProvider.kt fused location (ported from OVERWATCH)
|
|||||||
ui/ + MainActivity.kt Compose UI (Catppuccin Mocha)
|
ui/ + MainActivity.kt Compose UI (Catppuccin Mocha)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Paper
|
||||||
|
|
||||||
|
A full technical write-up — the per-ecosystem detection methodology (how VIGIL
|
||||||
|
tracks each tracker type) and the harder modeling engine for what AirGuard and the
|
||||||
|
built-in detectors miss — is in **[paper/vigil.md](paper/vigil.md)**.
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
Standard Android/Gradle. A committed debug keystore signs CI and local builds
|
Standard Android/Gradle. A committed debug keystore signs CI and local builds
|
||||||
|
|||||||
+550
@@ -0,0 +1,550 @@
|
|||||||
|
# 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.*
|
||||||
Reference in New Issue
Block a user