Skip to content

Sign in as an emulated Mac: Apple's macOS CoreADI under Unicorn, on the phone - #274

Open
lazzurs wants to merge 3 commits into
parawanderer:mainfrom
lazzurs:fix/248-sign-in-as-an-emulated-mac
Open

lazzurs wants to merge 3 commits into
parawanderer:mainfrom
lazzurs:fix/248-sign-in-as-an-emulated-mac

Conversation

@lazzurs

@lazzurs lazzurs commented Oct 21, 2026

Copy link
Copy Markdown

Fixes #248.

Since 2026-10-12 Apple only issues the Find My token (searchPartyToken) to a sign-in whose Anisette machine was minted by a Mac or an iPhone. An Android-minted machine gets mmeAuthToken alone and report fetches return 401 (malmeloo/FindMy.py#278). Since about 2026-10-20 the GSA edge also returns 503 for any X-MMe-Client-Info whose OS token is Android (probe results on #261). So the Android ADI libraries can't reach Find My any more, whatever headers are sent.

This runs Apple's macOS CoreADI on the phone instead, under an x86_64 emulator - the "derived library that can be run on android directly" from #248, built off sacca97/os-findmy-e.

How it works

  • libmacadi.so (app/src/main/cpp/macadi/): a C++ port of os-findmy-e's Rust adi-core - Mach-O loader, Unicorn x86_64 VM with the handful of macOS calls the library makes, and the CoreADI dispatcher framing. Unicorn 2.1.4 is fetched by CMake at build time, pinned by SHA-256, and only linked into this library.
  • The library comes from Apple at runtime, 3.5 MB, one request. It is the x86_64 CoreADI in iTunes 12.8.3 for Mac, which Apple still serves. In that DMG the CoreADI.pkg payload is stored uncompressed and contiguous, so a single HTTP range request gets it. The payload and the extracted library are both pinned by SHA-256. scripts/locate_mac_coreadi.py re-derives the offsets (from Apple, or from a local copy of the DMG) and reads its constants from the Java sources. Nothing of Apple's is in the repo or the APK.
  • EmulatedMacAnisette replaces LocalAnisette in AppDependencies. It provisions one emulated Mac with akd's headers (from os-findmy-e's gsa.py), keeps its identity and ADI store under files/anisette/mac/ for the life of the install, and serves the OTP, machine id and Apple-assigned routing number to the existing Python sign-in.
  • Python: hsc 409 with no au on GSA complete is treated as a second factor (the pinned FindMy.py otherwise skips 2FA); the routing number comes from Java; CloudKit and the keychain session get a view of the account whose device_uuid is a UUID, because a Mac's device id (00006000-…) isn't one and FindMy.py's CloudKit client parses it as one.

Things to decide

  • Licensing. Unicorn is GPL-2.0, so an APK with libmacadi.so carries GPL-2.0 code. The ported code is os-findmy-e's (MIT); its notice is in app/src/main/cpp/macadi/NOTICE.md.
  • The emulated Mac is a made-up Mac. Each install registers one fake MacBook Pro on the user's Apple account (serial 0PENTAGV…, as before). Apple could start fingerprinting this; it has changed the rules four times in five weeks.
  • LocalAnisette is left in place but unused. Happy to remove it, or keep it as a fallback if Apple relaxes the Android rule.
  • Run the account's loop on its own thread so iCloud calls stop queueing behind fetches #273 also adds an AGENTS.md rule 21; whichever lands second renumbers.

Testing

  • On a Pixel, with a real account: fresh install → library fetched from Apple → emulated Mac provisioned → sign-in with password and 2FA → tags listed from the iCloud account. Two bugs found that way and fixed here:
    • provisioning leaves the legacy adi.pb in the store, and while it exists the library reports "not provisioned", so the first sign-in after provisioning sent an empty machine id (ec -80009);
    • the CloudKit device id above (badly formed hexadecimal UUID string).
  • CI on the fork (Sign in as an emulated Mac (fork CI run before the upstream PR) lazzurs/OpenTagViewer#2): static checks, JVM tests, Chaquopy bridge tests, debug APK, instrumented suite, native stubs, ADI on bionic, and the new macadi-host.yml, which range-fetches the library from Apple exactly as the app does, checks both hashes, builds the native core for the host and loads the library ("not provisioned").
  • New tests: MacCoreAdiFetcherTest (cpio walk, universal-binary slice, refusing a server that ignores the range), test_emulated_mac.py (409 shim, routing, CloudKit device id view).

Not verified: how long a provisioned emulated Mac and its token stay accepted over days; more than one account; the instrumented suite exercises none of this because it fakes the Anisette source; and a byte-for-byte comparison of the C++ port against os-findmy-e's output (the device sign-in is the evidence it behaves the same).

Credit to @sacca97 for os-findmy-e, which this is ported from, and to @hoorayman and @poterpan on #248 for establishing that a Mac-minted machine is what gets searchPartyToken.

🤖 Generated with Claude Code

Rob Lazzurs and others added 3 commits October 21, 2026 22:53
A C++ port of sacca97/os-findmy-e's adi-core (MIT, see macadi/NOTICE.md): a
Mach-O loader, a Unicorn x86_64 VM with the few pieces of macOS userland the
library calls, and the CoreADI dispatcher framing. Unicorn 2.1.4 (GPL-2.0) is
fetched at build time, pinned by SHA-256, and linked only into libmacadi.so.

The library itself is never in the repository or the build. In iTunes 12.8.3
for Mac the package holding it is stored uncompressed and contiguous, so it is
one HTTP range request of 3.5 MB; scripts/locate_mac_coreadi.py re-derives the
offsets and checks both pinned hashes, and macadi-host.yml builds the core for
the host and loads the library into it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012JDrrt9ff7WwPepHs6B2Gx
Since 2026-10-12 Apple issues searchPartyToken only to Mac- or iPhone-minted
Anisette, and the edge now refuses any client claiming Android, so the Android
ADI path cannot reach Find My. EmulatedMacAnisette fetches the macOS library
from Apple, provisions one emulated Mac with akd's headers, keeps its identity
and store for the life of the install, and is what AppDependencies hands out.

Also: read Grand Slam's hsc 409 without au as a second factor; send the routing
number Apple assigned at provisioning; give CloudKit a UUID, since a Mac's
device id is not one; drop the legacy adi.pb before every session, which
otherwise made a freshly provisioned machine report unprovisioned (ec -80009).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012JDrrt9ff7WwPepHs6B2Gx

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App: Can't log in

1 participant