Skip to content

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:

  1. /var/db/lockdown/ — contains only SystemConfiguration.plist (266 B; one key: SystemBUID, a host-scope UUID) and a RemotePairing/ 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.
  2. /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 class CUPairedPeer holding 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.plist with a root object holding our own public + private Ed25519 keys.
  3. ~/Library/Developer/CoreDevice/Devices/db.sqlite — 4-table cache DB owned by CoreDeviceService. Data tables (local_device_info_cache, user_managed_metadata) hold 0 rows. fs_usage confirms the file IS opened during devicectl but empty contents mean it cannot be the gate.
  4. /Library/Bluetooth/com.apple.MobileBluetooth.ledevices.paired.db — BT-LE paired-device DB used by bluetoothd. PairedDevices row 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-0004596E22A0401E and CoreDevice UUID E8A190DD-64F5-44A4-8D57-28E99E316D60 reported — 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.
  • SystemBUID value: 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-byte sk (identity) = Ed25519 seed. The host's sk lives inline in the plist file, not in the keychain. This is the inverse of the classic lockdown model where <UDID>.plist held only public-ish material and the private key was in System.keychain under MobileDevice Backup Identities.
  • Peer record is keyed by CoreDevice UUID, not by hardware UDID. The file name E8A190DD-….plist matches the CoreDevice UUID reported by xcrun devicectl list devices.
  • altIRK is 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.
  • pk is 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) and user_0/ (root). Both 597 B but differ byte-for-byte — per-user variations in dateModified or $objects arrangement.

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 devicectl XPC):
    • open/stat/access on /Library/Apple/System/Library/PrivateFrameworks/RemotePairing.framework/...
    • guarded_open_np on ~/Library/Developer/CoreDevice/Devices/db.sqlite (primary) and 10× stat64 on -journal / -wal side files
    • mkdirat/fstatat64/lstat64 on ~/Library/Developer/CoreDevice/Devices/ (idempotent prep)
    • open on remotepairingd.xpc/Contents/Info.plist (Swift / XPC class-resolution)
    • NO open/read of /var/db/lockdown/RemotePairing/user_501/peers/E8A190DD-….plist during the 20 s window.
  • remotepairingd (pid 786, continuously running): one close event 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 as nullweft (user 501), loaded RemotePairing.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 each devicectl invocation.
  • remoted (pid 118) — always running; consumes the RSD tunnel traffic and routes it to CoreDeviceService. In iter-12/14/15 the backend Handshake reaches remoted.

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:

  1. iPhone advertises _remotepairing._tcp.local with an ident TXT field (or similar) over one of: BT LE, USB tunnel interface, or Wi-Fi.
  2. remotepairingd resolves the advert, extracts ident, matches it against the in-memory peer-records loaded from /var/db/lockdown/RemotePairing/user_{UID}/peers/*.plist.
  3. On match, remotepairingd initiates an Ed25519 challenge-response using the host's sk + the peer's pk from 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.
  4. Only on successful Ed25519 handshake does CoreDevice escalate the connection state to pairingState: paired / tunnelState: available and 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 pk and populate altIRK / ident / model / name. However remotepairingd loads 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.local Bonjour service that matches the planted peer's ident NSUUID. dns-sd or a small Go program with go-mdns handles 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 sk in 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 remotepairingd accepts a planted peer + our mDNS advertising, CoreDeviceService might produce pairingState: paired as 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 :50367 happens 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 unpaired because 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_usage needs SIP disabled. Havoc is SIP-off; a SIP-on apparatus blocks this diagnostic entirely.
  • timeout command is absent on macOS. First sec4 script failed silently; had to rewrite with manual & + kill. Already noted in feedback_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: remotepairingd runs sandboxed under user nullweft with 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, altIRK may be ignored for the match.
  • remotepairingd restart risk: user memory rule no_launchctl_bootout bans bootout; kickstart -k is 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: unpaired reads from): delivered via fs_usage + log show + lsof; the read path is in-memory on remotepairingd'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)

  1. Backup both user_0/peers/*.plist and user_501/peers/*.plist + both identity.plist to ~/backups/iosmux/d16-snapshot/ on Linux host. Memory rule: backup BEFORE any write.
  2. Generate a throwaway Ed25519 key pair on Linux host; serialize a fake CUPairedPeer plist using plistlib with 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 plausible dateModified.
  3. scp the fake plist to havoc, copy into /var/db/lockdown/RemotePairing/user_501/peers/<fake-uuid>.plist (request approval before any write).
  4. Start an mDNS _remotepairing._tcp.local advert on havoc with dns-sd that publishes the fake UUID as ident.
  5. launchctl kickstart -k gui/501/com.apple.CoreDevice.remotepairingd to force re-read of peer registry (separate approval gate).
  6. Observe remotepairingd log for advert resolution pattern.
  7. Run xcrun devicectl device info details --device <CoreDevice-UUID>. Observe pairingState. Log the outcome.
  8. Restore all peer plists from backup, launchctl kickstart the daemon again.

Reproduce

Preconditions:

  • havoc VM reachable via havoc-root SSH 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)