Phase D.6.6-research-c iter-16 findings — host-side pair-state survey; CoreDevice trust is an Ed25519-keyed CUPairedPeer store matched via live Bonjour advert¶
Status: verified — 2026-04-24
Read-only filesystem, SQLite, keychain, log show, and fs_usage
(SIP disabled) inspection on havoc during a live
xcrun devicectl device info details --device <CoreDevice-UUID>
trigger. Identified the authoritative pair-state store as
/var/db/lockdown/RemotePairing/user_{UID}/peers/<CoreDevice-UUID>.plist
— an NSKeyedArchiver-serialized CUPairedPeer record carrying
an Ed25519 public key. Peer records for our iPhone exist at this
path but devicectl still reports pairingState: unpaired
because remotepairingd matches peers by currently-observed
Bonjour _remotepairing._tcp.local adverts, and nothing on the
host currently advertises that service with an identity that
matches our on-disk peer record (all observed adverts in the
log window resolve to identity nil, udid nil → unauth device).
CoreDevice's on-disk SQLite cache
(~/Library/Developer/CoreDevice/Devices/db.sqlite) is read
during the devicectl call but its local_device_info_cache
and user_managed_metadata tables are both empty — it is a
cache, not the source of truth. The
/Library/Bluetooth/…ledevices.paired.db BT-LE paired-device DB
also has zero rows. Plantability assessment:
UNKNOWN without an iter-17 write-side test; the file format
is plantable, but the live Ed25519-signed RemotePairing
handshake between host and device almost certainly happens at
connection time and verifies a possession proof we cannot forge
without the device's private key.
TL;DR¶
iter-15 promoted H-PairState to the primary working hypothesis. iter-16 is the read-only filesystem pass demanded by that hypothesis.
Four layers of pair/trust state were located and structurally surveyed:
/var/db/lockdown/— contains onlySystemConfiguration.plist(266 B; one key:SystemBUID, a host-scope UUID) and aRemotePairing/subtree. No UDID-keyed pair records exist anywhere. The classic Apple-lockdown USB pair-record layout (one XML plist per paired device, keyed by hardware UDID) is gone in this macOS build./var/db/lockdown/RemotePairing/user_{UID}/peers/<CoreDevice-UUID>.plist— the authoritative peer store. Keyed by the 36-char CoreDevice UUID (not the hardware UDID). Schema: NSKeyedArchiver graph with root classCUPairedPeerholding 7 fields including a 32-B Ed25519 public key (pk) and a 16-B Bluetooth IRK (altIRK). Corresponding host identity lives in/var/db/lockdown/RemotePairing/user_{UID}/identity.plistwith a root object holding our own public + private Ed25519 keys.~/Library/Developer/CoreDevice/Devices/db.sqlite— 4-table cache DB owned byCoreDeviceService. Data tables (local_device_info_cache,user_managed_metadata) hold 0 rows. fs_usage confirms the file IS opened duringdevicectlbut empty contents mean it cannot be the gate./Library/Bluetooth/com.apple.MobileBluetooth.ledevices.paired.db— BT-LE paired-device DB used bybluetoothd.PairedDevicesrow count: 0. The USB-tethered iPhone is not BT-paired with this host.
The iPhone is USB-connected, iOS 17.4+ RSD tunnel-mode only, and
never currently BT-paired but a peer record was minted at some
earlier point (plist mtime is 2026-04-06, stable for weeks).
remotepairingd is continuously scanning
_remotepairing._tcp.local Bonjour adverts (observed in log show
as a tight ~8 s cycle, every advert resolving to identity nil, udid
nil). None of those adverts match the iPhone because neither
pymobiledevice3's tunneld nor SPIKE-mode iosmux-backend advertises
an RSD-over-Bonjour service. remotepairingd's internal "is this
peer paired" check is: does any currently-resolved Bonjour advert
match a peer ident NSUUID on disk? The answer is no → unauth
device → CoreDevice reports pairingState: unpaired.
System keychain has zero entries related to iPhone / CoreDevice
/ RemotePairing / MobileDevice. The Ed25519 key pair for
RemotePairing lives inline in the plist file, not in keychain.
That is a significant iOS 17+ design change from the classic
<UDID>.plist + keychain-identity model.
Method¶
A suite of small havoc-side shell scripts under
/tmp/iosmux-d16-sec*-*.sh, each scp'd to havoc-root and executed
there. All scripts are read-only (no rm, mv, > to system
paths; only > to /tmp/iosmux-d16-*.out scratch under /tmp).
SIP is disabled on havoc (csrutil status: disabled — confirmed in
sec4b output) — which is what enabled fs_usage to trace the signed
Apple daemons. Without SIP-disabled, fs_usage silently returns 0
lines for system daemons. This is a precondition for reproducing
iter-16.
Privacy discipline:
- File paths, sizes, mtimes, owner/group, permissions reported verbatim.
- Plist field names reported verbatim (class interfaces are public API).
- Field types + lengths + shape hints reported.
- UDID
00008110-0004596E22A0401Eand CoreDevice UUIDE8A190DD-64F5-44A4-8D57-28E99E316D60reported — consistent with iter-14 / iter-15 findings. - Ed25519 public/private key bytes, BT IRK bytes, plist content hashes: NOT reported. Redacted from the agent's scratch outputs before checking anything into the repo.
SystemBUIDvalue: redacted as<SystemBUID>— host-scope identifier, easy cross-correlator if leaked.
Results¶
Section A — /var/db/lockdown/ inventory¶
/var/db/lockdown/ drwx-----x _usbmuxd:_usbmuxd
RemotePairing/ drwxrwxrwx _usbmuxd:_usbmuxd
user_501/ drwx------@ nullweft:_usbmuxd
identity.plist 418 B, Apple binary plist
peers/
E8A190DD-64F5-44A4-8D57-28E99E316D60.plist 597 B
user_0/ drwx------@ root:_usbmuxd
peers/
E8A190DD-64F5-44A4-8D57-28E99E316D60.plist 597 B
SystemConfiguration.plist 266 B, XML
No UDID-keyed plist exists anywhere in this tree. The following paths (in every permutation we checked) are absent:
/var/db/lockdown/00008110-0004596E22A0401E.plist
/var/db/lockdown/Records/<UDID>.plist
/var/db/lockdown/pair_records/<UDID>.plist
/var/db/lockdown/RemotePairing/<UDID>.plist
SystemConfiguration.plist holds exactly one key: SystemBUID
(host-scope usbmuxd UUID; value redacted).
Section B — RemotePairing peer record schema¶
The peer plist is an NSKeyedArchiver-serialized object graph with
root class CUPairedPeer. After walking the $objects array and
resolving plistlib.UID references:
CUPairedPeer (CoreUtils "paired peer")
├── altIRK : <data, 16 B> # BT Identity Resolving Key
├── dateModified : <NSDate>
├── idStr : <string, 36 chars> # UUID shape (matches CoreDevice UUID)
├── ident : <NSUUID, 16 B> # same UUID in binary form
├── model : <string, 10 chars> # ASCII, hardware model tag
├── name : <string, 6 chars> # "iPhone"
└── pk : <data, 32 B> # Ed25519 public key of the peer
identity.plist is the host's own identity, also NSKeyedArchiver:
<root, CUIdentity-ish>
├── ident : <NSUUID, 16 B> # host identity UUID
├── pk : <data, 32 B> # host Ed25519 public key
└── sk : <data, 32 B> # host Ed25519 PRIVATE key (inline)
Key observations:
- Ed25519-based trust. 32-byte
pk= Ed25519 public key. 32-bytesk(identity) = Ed25519 seed. The host'ssklives inline in the plist file, not in the keychain. This is the inverse of the classic lockdown model where<UDID>.plistheld only public-ish material and the private key was inSystem.keychainunderMobileDevice Backup Identities. - Peer record is keyed by CoreDevice UUID, not by hardware UDID.
The file name
E8A190DD-….plistmatches the CoreDevice UUID reported byxcrun devicectl list devices. altIRKis a 16-B BT IRK. Presence means the record was originally minted during a Bluetooth LE pairing dance. The iPhone in this apparatus was probably paired via BT at some earlier point, then used BT-free (USB-only) since. The IRK is used for BT address resolution; the record's existence on disk is a strong indicator the peer was once known.pkis 32 B which matches Ed25519 / Curve25519 sizes, not the 1024/2048-bit RSA keys used in classic MobileDevice pairing. This is the iOS 17+ RemotePairing cryptographic suite.- Two peer-record copies:
user_501/(nullweft) anduser_0/(root). Both 597 B but differ byte-for-byte — per-user variations indateModifiedor$objectsarrangement.
The user_* directories are drwx------@ with extended attributes
(the @ marker is macOS's xattr flag, and parent RemotePairing/
is drwxrwxrwx which is suspicious but xattr-protected). Apple's
DataVault subsystem projects these into per-user sandboxes via
RemotePairingDataVaultHelper (observed process ID in section G).
Writes to these files go through the DataVault helper, not direct
filesystem syscalls from user-scope processes.
Section C — CoreDevice SQLite cache¶
Four paths hold a db.sqlite file with identical schema:
~/Library/Developer/CoreDevice/Devices/db.sqlite 49 152 B
/var/root/Library/Developer/CoreDevice/Devices/db.sqlite 49 152 B
~/Library/Containers/com.apple.CoreDevice.CoreDeviceService/Data/Library/Developer/CoreDevice/Devices/db.sqlite 49 152 B
/var/root/Library/Containers/com.apple.CoreDevice.CoreDeviceService/Data/Library/Developer/CoreDevice/Devices/db.sqlite 49 152 B
Schema (opened via file:...?mode=ro&immutable=1 URI, zero write
risk):
CREATE TABLE schema_history (
identifier TEXT NOT NULL UNIQUE,
appliedAt TEXT NOT NULL);
CREATE TABLE train (
build TEXT NOT NULL,
program TEXT NOT NULL,
name TEXT NOT NULL);
CREATE TABLE local_device_info_cache (
deviceIdentifierJSON TEXT PRIMARY KEY NOT NULL,
deviceInfoJSON TEXT NOT NULL);
CREATE TABLE user_managed_metadata (
deviceIdentifierJSON TEXT PRIMARY KEY NOT NULL,
userManagedMetadataJSON TEXT NOT NULL);
Row counts across all four DBs:
| table | rows |
|---|---|
schema_history |
7 |
train |
0 |
local_device_info_cache |
0 |
user_managed_metadata |
0 |
Data tables are empty. This DB is evidently a cache for faster
devicectl list devices info retrieval; it is not where trust state
lives. It is read on every devicectl invocation (guarded_open_np
observed in fs_usage during the trigger) but the empty rows mean
there is no stored pairingState field here.
Section D — System keychain¶
/Library/Keychains/System.keychain (52 648 B, 18 entries total):
| class | count |
|---|---|
genp (generic password) |
11 |
class 0x80001000 |
3 |
class 0x00000010 |
2 |
class 0x0000000F |
2 |
Certificate labels present:
"Apple Worldwide Developer Relations Certification Authority""com.apple.kerberos.kdc""com.apple.systemdefault"
Zero matches for iPhone, iOS, RemotePair, MobileDevice,
CoreDevice, the hardware UDID, or the CoreDevice UUID. Zero
identities (priv-key + cert pairs). The iOS 17+ RemotePairing
cryptographic material lives in the RemotePairing plists, NOT in
keychain.
User-scope login keychain (~/Library/Keychains/login.keychain) was
not inspected — it is locked without the user's password, and
opening it would need user approval. But given the RemotePairing
model keeps keys inline in plists, user-scope keychain is unlikely
to be the trust gate.
Section E — what pairingState: unpaired reads from¶
fs_usage with SIP-disabled captured 4 178 lines during a
devicectl device info details --device <CoreDevice-UUID> call.
Filtered for lockdown | pair | RemotePair | CoreDevice/Devices |
keychain | db.sqlite:
Processes touching files of interest:
- CoreDeviceService (launched on-demand by the
devicectlXPC):open/stat/accesson/Library/Apple/System/Library/PrivateFrameworks/RemotePairing.framework/...guarded_open_npon~/Library/Developer/CoreDevice/Devices/db.sqlite(primary) and 10×stat64on-journal/-walside filesmkdirat/fstatat64/lstat64on~/Library/Developer/CoreDevice/Devices/(idempotent prep)openonremotepairingd.xpc/Contents/Info.plist(Swift / XPC class-resolution)- NO open/read of
/var/db/lockdown/RemotePairing/user_501/peers/E8A190DD-….plistduring the 20 s window.
- remotepairingd (pid 786, continuously running): one
closeevent at the window edge; no opens of the peer file in the window. - bluetoothd: background
RdData[S]/WrData[S]on/Library/Bluetooth/com.apple.MobileBluetooth.ledevices.paired.db-shm— classic periodic BT state write. - remoted (pid 118): zero file I/O in the window.
Critical finding: during the devicectl trigger, neither
remotepairingd nor CoreDeviceService reads the peer plist from
disk. The peer registry is evidently loaded into remotepairingd's
memory once at daemon launch (this agrees with mtime 2026-04-06 on
the peer files — they have been stable for weeks) and queried
in-memory for every subsequent pair-state check.
Section F — log show evidence¶
Observable log pattern from remotepairingd over a 10 min window
(filtered by process == "remotepairingd" and grep
pair|trust|identity|peer):
remotepairingd[786] [...:remotepairingd]
Resolved bonjour advert <UUID> to identity nil, udid nil
remotepairingd[786] [...:remotepairingd]
Unable to resolve bonjour advert <UUID> to known identity,
tracking as unauth device
This loop fires every ~8 s, cycling through
_remotepairing._tcp.local Bonjour adverts on en0 (the host's
physical Ethernet). Every advert resolves to identity nil, udid
nil — meaning remotepairingd looked up the advert in its
in-memory peer registry, found no match on ident (NSUUID), and
categorised the source as unauth.
The absence of any advert that resolves to our iPhone's peer
record is the direct cause of pairingState: unpaired. Neither
pymobiledevice3 tunneld nor SPIKE-mode iosmux-backend publishes
a _remotepairing._tcp.local mDNS record, so remotepairingd has
nothing to match against its loaded peer registry.
Section G — daemon process map¶
From pgrep -lf:
remotepairingd(pid 786) — runs asnullweft(user 501), loadedRemotePairing.framework,MobileDevice.framework,DeviceInterface.framework,Mercury.framework. Active BT/mDNS scanner. User-domain daemon.com.apple.dt.RemotePairingDataVaultHelper(pid 788) — projects/var/db/lockdown/RemotePairing/user_*/*into per-user sandbox paths.CoreDeviceService— not running between calls; launched on-demand by eachdevicectlinvocation.remoted(pid 118) — always running; consumes the RSD tunnel traffic and routes it toCoreDeviceService. In iter-12/14/15 the backend Handshake reachesremoted.
Interpretation¶
The iOS 17+ / macOS 15+ pair-state model is a Bonjour-resolved, Ed25519-signed RemotePairing handshake, not the classic USB-serial-number + lockdown-pair-record model. The pair-state check is:
- iPhone advertises
_remotepairing._tcp.localwith anidentTXT field (or similar) over one of: BT LE, USB tunnel interface, or Wi-Fi. remotepairingdresolves the advert, extractsident, matches it against the in-memory peer-records loaded from/var/db/lockdown/RemotePairing/user_{UID}/peers/*.plist.- On match,
remotepairingdinitiates an Ed25519 challenge-response using the host'ssk+ the peer'spkfrom the peer plist, and the device's side of the handshake uses its own private key (held in iPhone secure enclave) + the host's public key that was exchanged during the original BT pairing. - Only on successful Ed25519 handshake does CoreDevice escalate the
connection state to
pairingState: paired/tunnelState: availableand then back-connect to the advertised service ports.
In iter-12/14/15 we observed CDS (com.apple.remote.* XPC) produce
a valid Handshake to our SPIKE backend — but this Handshake is the
RemoteXPC / RSD Handshake, which is a much cheaper connect-time
exchange that does NOT include the Ed25519 _remotepairing
challenge-response. CoreDeviceService is built on top of RemoteXPC
and only starts full service once remotepairingd asserts the
connection is to a known peer. No peer assertion → no
CoreDeviceService connection → pairingState: unpaired.
This explains perfectly why wire-level patching of the Handshake
(iter-12/14/15) cannot move the needle: the gate lives at the
RemotePairing layer, below RSD, and neither our backend nor
tunneld publishes anything remotepairingd recognises.
Plantability assessment — UNKNOWN, leaning UNFEASIBLE-WITHOUT-DEVICE-KEY¶
To flip the reported pair-state we would need to persuade
remotepairingd that the iPhone is a paired peer. The attack
surface is:
- Plant a fake peer plist: technically feasible. NSKeyedArchiver
format is stable; we could forge an entry keyed by a new UUID with
a known
pkand populatealtIRK/ident/model/name. Howeverremotepairingdloads peer records on startup and would need to be restarted for a planted record to take effect (or there might be an mDNS-triggered reload path worth investigating). - Advertise the planted identity via mDNS: would have to start a
_remotepairing._tcp.localBonjour service that matches the planted peer'sidentNSUUID.dns-sdor a small Go program withgo-mdnshandles the publish side. - Complete the Ed25519 challenge-response: this is the real
blocker. The device side expects a message signed by the
corresponding private key. The device holds its own
skin secure enclave at device setup. If we plant a peer with a key pair we control, we can sign on OUR side — but the DEVICE will not accept OUR signature because WE are not the device it paired with.
So: planting a peer record is necessary but not sufficient for the full pair flow. The iPhone's side is a real cryptographic check with no software gate we can reach.
What planting COULD do, usefully:
- Flip the host-side perception of pair-state: if
remotepairingdaccepts a planted peer + our mDNS advertising,CoreDeviceServicemight producepairingState: pairedas long as the subsequent Ed25519 handshake has not been attempted yet, or if CoreDevice caches pair-state independently of handshake success. - Provide a testbed for the RSD Handshake layer: if metadata
queries unblock, we can at minimum observe whether the back-connect
to
:50367happens under a pair-state-asserted condition.
The minimum test to decide: iter-17, brief, reversible planting.
Hypothesis disposition¶
- H-HS narrow (UDID patch alone): dead (iter-14).
- H-HS multi-field: dead (iter-15).
- H-PairState gross form ("CDS consults a host-side DB keyed by UDID"): refuted at the surface — no UDID-keyed DB entry exists.
- H-PairState refined ("CDS consults RemotePairing peer records,
keyed by CoreDevice UUID, matched through a live Bonjour advert"):
strongly supported. Peer record exists. Advert does not. Pair-state
is
unpairedbecause the advert-matching step fails. - H-Planted-Peer ("plant a peer + fake advert → flip pair-state"): UNKNOWN; blocked by the live Ed25519 handshake for any actual command, but may flip metadata-query pair-state independently. Needs iter-17.
Anomalies / risks / blockers worth flagging¶
fs_usageneeds SIP disabled. Havoc is SIP-off; a SIP-on apparatus blocks this diagnostic entirely.timeoutcommand is absent on macOS. First sec4 script failed silently; had to rewrite with manual&+kill. Already noted infeedback_no_inline_shell_complexity.md; this iter confirms the pattern applies beyond the ssh-command case to all multi-step macOS scripts.- Containerised daemon view:
remotepairingdruns sandboxed under usernullweftwith a DataVault-projected mount of/var/db/lockdown/RemotePairing/user_501/*into the sandbox. Any planting needs to target the DataVault-authoritative path (/var/db/lockdown/RemotePairing/user_*/), not the in-sandbox projection. Writes via ssh havoc-root to the authoritative path should propagate. - Two peer record copies (user_501 and user_0) of identical size but different content. If we plant, we need to plant in both.
- Peer record includes a BT IRK. That field will only match
something if the iPhone is BT-discoverable. If iter-17's planted
advert is via USB-tunnel rather than BT,
altIRKmay be ignored for the match. remotepairingdrestart risk: user memory ruleno_launchctl_bootoutbansbootout;kickstart -kis milder but still restarts a user-domain daemon. Worth a per-step approval pass in iter-17.
Status¶
- Step A (survey
/var/db/lockdown/): delivered. - Step B (locate CoreDevice paired-devices store): delivered —
authoritative store is
RemotePairing/user_*/peers/*.plist, CoreDevice SQLite is cache-only. - Step C (keychain scan): delivered, negative result (keychain not used).
- Step D (trace what
pairingState: unpairedreads from): delivered via fs_usage +log show+lsof; the read path is in-memory onremotepairingd's loaded peer registry, gated on live Bonjour advert resolution. - Step E (plantability assessment): UNKNOWN; iter-17 proposed.
Proposed next step (iter-17, write-side)¶
- Backup both
user_0/peers/*.plistanduser_501/peers/*.plist+ bothidentity.plistto~/backups/iosmux/d16-snapshot/on Linux host. Memory rule: backup BEFORE any write. - Generate a throwaway Ed25519 key pair on Linux host; serialize a
fake
CUPairedPeerplist usingplistlibwith NSKeyedArchiver schema; file name = arbitrary UUID,ident= that UUID,idStr= same UUID string,pk= public key of the fake pair,altIRK= 16 B random,model= 10-char ASCII,name= "Test", plus plausibledateModified. scpthe fake plist to havoc, copy into/var/db/lockdown/RemotePairing/user_501/peers/<fake-uuid>.plist(request approval before any write).- Start an mDNS
_remotepairing._tcp.localadvert on havoc withdns-sdthat publishes the fake UUID asident. launchctl kickstart -k gui/501/com.apple.CoreDevice.remotepairingdto force re-read of peer registry (separate approval gate).- Observe
remotepairingdlog for advert resolution pattern. - Run
xcrun devicectl device info details --device <CoreDevice-UUID>. ObservepairingState. Log the outcome. - Restore all peer plists from backup,
launchctl kickstartthe daemon again.
Reproduce¶
Preconditions:
- havoc VM reachable via
havoc-rootSSH alias. - SIP must be disabled (
csrutil status: disabled) for fs_usage to work. - iPhone registered in CoreDevice (verify with
ssh havoc xcrun devicectl list devices; one row expected).
Scripts held local on havoc under /tmp/iosmux-d16-sec*-*.sh
(the iter-16 scratch suite). Outputs under /tmp/iosmux-d16-*.out.
None checked into the repo — they contain live BT IRK bytes,
Ed25519 key material, and peer identifiers.
Artefacts (held local, not committed)¶
/tmp/iosmux-d16-sec1-lockdown.out (inventory)
/tmp/iosmux-d16-sec1b-peers.out (peer/identity schema, privacy-safe)
/tmp/iosmux-d16-sec2-coredevice.out (CoreDevice path hunt)
/tmp/iosmux-d16-sec2b-coredevice-deep.out
/tmp/iosmux-d16-sec2c-sqlite.out (schema + 0-row confirmation)
/tmp/iosmux-d16-sec2d-containers.out
/tmp/iosmux-d16-sec3-keychain.out (System.keychain survey)
/tmp/iosmux-d16-sec4-fsusage.out (first attempt — macOS-no-timeout artifact)
/tmp/iosmux-d16-sec4b-logs.out (log show + lsof + launchctl list)
/tmp/iosmux-d16-sec4c-fsusage2.out (4178 lines raw fs_usage)
/tmp/iosmux-d16-sec4d-bluetooth.out (BT-LE DB schemas, 0 rows)
/tmp/iosmux-d16-sec5-finalize.out (NSKeyedArchiver graph walk)