Reverse-engineering Find My People to stalk a friend, cause I can

lalitmaganti1 pts0 comments

Reverse-engineering Find My People to stalk my ex a friend, cause I can | zerotistic zerotistic As it can often be, I was bored. I wanted to look into a complex system, and had no idea which. Me and a friend have been sharing our locations to each other’s through Apple’s “Find My”. I asked if he was okay with me piping it into some dumb automations. He said yes, so the plan was to draw a few geofences around places he goes and make Discord announce whenever he arrived or left.

totally accurate and not made up dms (thanks es3n1n for the UI inspiration)

zerotistic<br>Today at 18:42

yo dumb question: are you okay with me using the Find My location you're sharing with me in a little Linux automation thing?

mostly geofences and Discord messages when you arrive or leave places

Lymdun<br>Today at 18:43

Lymdun<br>lymdun<br>ABOUT MEthe goat 🐐

↳zerotistic are you okay with me using…

yeah lmao go for it

just don't publish where I live obviously 💀

👍1

18:43

deal 🤝

Replying to ×

Nothing to see here.

I already had a tiny Steam tracker doing basically the same thing with game activity, so I assumed Find My would be another authenticated request, some JSON, and an evening of work (epic foreshadowing).

I started with the normal iCloud web API and it happily returned my own Apple devices and their locations, but Find My People was nowhere in it. I thought I’d be smart and look into what other people have done. Turns out things aren’t easy as it looks like nobody has done this before (or well, not fully, you’ll see what I mean).

I did not have a Mac to run or instrument FindMy.app, so I started from existing open-source clients and requests against Apple. Later, when guessing field names stopped being funny, I also worked through decompilations of fmfd, findmylocated, and searchpartyd. I’ll link the exact source whenever one of the open-source clients comes up. The loop was mostly: keep the session fixed, change one field or encoding, and see whether Apple’s status code moved.

Warning (Scope)The client only reads an already accepted share on my Apple Account. It has no methods for sending invitations, changing shares, adding family members, creating geofences on Apple, or performing device actions. The geofencing happens locally after decryption.

Starting with the Friends API

The first thing that looked useful was an old initClient call used by fmfd:

/fmipservice/friends/fmfd///initClient

//initClient">

For context, fmfd is the Find My Friends daemon. Even though the app is called Find My now, this API still lives under Apple’s old MobileMe namespace. The dsid in the URL is just the account’s numeric Directory Services ID.

Logging into iCloud gave me a pile of MobileMe tokens for different services. A few of them sounded right, so I tried the obvious ones:

Terminal windowmmeFMFAppToken:401

mmeAuthToken:401

searchPartyToken:401

Three tokens, three 401s. Looking at the MobileMe delegate exchange made the reason pretty clear: the login acts as a token broker and gives each iCloud service its own credentials. Apparently, having “Find My” somewhere in the name wasn’t enough.

Even with the correct token, the request still wasn’t complete. initClient wanted the account’s Find My Friends (FMF) host, the courier token for this client’s Apple Push Notification service (APNs) connection, a fairly detailed client context, and a weird bundle of headers called anisette . The courier token identifies this client to APNs, and the topic tokens used to filter pushes are derived from it.

Anisette is basically extra proof that the request came from a provisioned Apple-like client. Some values stay tied to the emulated machine and another looks like a short-lived one-time code. It isn’t the password or a Find My token, but Apple rejects the request without it. FindMy.py generates those headers here and keeps the provisioned identity here. The deviceUDID below is just the Unique Device Identifier (UDID) I assigned to the emulated receiver.

reduced initClient request1

"clientContext": {

"appName": "findmylocated",

"apsToken": "",

"callerHandleId": "",

"contextBundleApp": "com.apple.findmy.findmylocated",

"currentTime": 1787130000000,

"deviceUDID": "",

"productType": "MacBookPro18,3",

10

"osVersion": "14.6"

11

},

12

"serverContext": {

13

"authToken": "",

14

"clientId": "",

15

"prsId": ""

16

17

", "callerHandleId": "", "contextBundleApp": "com.apple.findmy.findmylocated", "currentTime": 1787130000000, "deviceUDID": "", "productType": "MacBookPro18,3", "osVersion": "14.6" }, "serverContext": { "authToken": "", "clientId": "", "prsId": "" }}">

The route still says friends/fmfd, but current clients build that context in findmylocated. currentTime is Unix milliseconds, and subsequent refreshes also echo the model version Apple returned in X-FMF-Model-Version.

Once I had all of that matching what the native daemons send, Apple finally returned the accepted share:

initClient...

apple find initclient fmfd findmylocated client

Related Articles