Phase D.6.6 iter-17/18 findings — xcrun devicectl manage pair against the iosmux synthetic device is falsified end-to-end; bridge-not-proxy architecture confirmed¶
Status: verified — 2026-04-27
Two pair-flow attempts run on the havoc Mac VM against the live
iPhone (USB-NCM-tunnelled from the Linux host). Run A: with the
full iosmux_inject.dylib (150,112 B, sha 26ca1658…) loaded
into CoreDeviceService — instant kAMDNotConnectedError /
Mercury 1000, no iPhone-side Trust UI. Run B: same command
against the smaller iosmux_inject.dylib.disabled (95,904 B,
sha 97a8cfae…) swapped into the active path — Connection
refused 127.0.0.1:62078 from CDS, the synthetic
iPhone (iosmux) disappears from devicectl list devices
entirely (No devices found), and the stub injects' own log
line (iosmux_inject.dylib) [iosmux] FATAL: XPC proxy init
failed confirms the smaller dylib is not a no-op stub but
a different functional variant. Both runs re-confirm the
Phase S2.B / Stage S2 finding that iosmux is a bridge that
synthesises a device, not a transparent proxy of the physical
iPhone (already documented in
connection.md and
stage2.md "Phase B
realization"; this iter only adds empirical reinforcement).
TL;DR¶
iter-16 localised the trust gate to remotepairingd's in-memory peer
table matched against live _remotepairing._tcp.local Bonjour adverts
(see iter-16-pairstate-survey/findings.md).
iter-17 was the first write-side probe: plant a fake peer record,
publish a matching mDNS advert, see whether pairingState flips for
metadata-level queries before the Ed25519 challenge gate runs.
iter-17 was abandoned mid-flight after the planted advert was rejected
by remotepairingd with Ignoring bonjour advert without expected TXT
record keys — Apple expects identifier=/authTag=/ver=/minVer=/flags=,
the planted advert used ident=/id=/uuid=/udid=. authTag was
also discovered to rotate every ~8 s alongside the rotating identifier
(privacy feature in iOS 17+), so a static planted advert cannot match
even with corrected key names without solving authTag derivation
first. The planted artifacts (one E269C66F-…plist peer record and
one dns-sd registration) were captured to backups and cleaned up at
the start of iter-18.
iter-18 then took the obvious next step: stop trying to fake the
native pair-state machinery and just run xcrun devicectl manage
pair --device <CoreDevice-UUID>, the documented Apple way to drive
a first-time pair. With the user prepared to tap "Trust This
Computer" on the iPhone the moment the dialog appeared, two runs were
attempted under different inject loadouts. Both failed in
two-second windows; the iPhone-side Trust dialog never appeared
because CDS bailed before reaching the iOS Pair-Setup endpoint.
The dual failure re-confirms the architectural finding already
documented in
connection.md and
stage2.md "Phase B realization":
native CoreDevice pair flow against the iosmux synthetic device is
not the path to Xcode integration. The path is to extend the iosmux
Go backend to be the lockdown / pair-state authority for the
synthetic device itself — the Option δ shim, scoped by Phase D.6.6.
Apparatus state going into iter-17/18¶
- iPhone iOS 26.4.x on USB-NCM bridge from Linux host into Mac VM
havoc-vm. Real hardware UDID
00008110-…(25 chars), CoreDevice UUIDE8A190DD-64F5-44A4-8D57-28E99E316D60(36 chars). pymobiledevice3 remote tunneldrunning stock-mode on127.0.0.1:49151inside havoc.iosmux_inject.dylib(active, 150 KB, sha26ca1658) loaded into everyCoreDeviceServiceinstance via anLC_LOAD_DYLIBentry theinsert_dylibutility wrote into a patched copy of the binary at/Library/Developer/PrivateFrameworks/CoreDevice.framework/Versions/A/XPCServices/CoreDeviceService.xpc/Contents/MacOS/CoreDeviceService. The patched binary lives at the/Library/Developer/...framework root because the macOS build on this VM has no/System/Library/PrivateFrameworks/CoreDevice.framework(the Xcode-CLT install is the only CoreDevice framework on this machine, and was patched in place).- Pre-existing leftover peer records from earlier project iterations:
four byte-distinct copies of
E8A190DD-64F5-44A4-8D57-28E99E316D60.plist(NSKeyedArchiverCUPairedPeer) across/var/db/lockdown/RemotePairing/user_0/peers/,…/user_501/peers/, and the two container-sandbox roots fornullweftandrootusers ofcom.apple.CoreDevice.remotepairingd. Three of the four share content shafe6bc97c…(timestamp shift only); the user_0 copy is sha33b64dd0…. Samepk(Ed25519, prefix0aebefad…), samealtIRK(df641cca…), sameidentUUID. Origin of these records is unconfirmed and they are not relied on for any of the iter-17/18 conclusions below. - Host-side identities are split:
/var/db/lockdown/RemotePairing/user_501/identity.plist(sha74e21111…, mtime Apr 10) has different pk/sk than theContainers/com.apple.CoreDevice.remotepairingd/.../identity.plistcopies in nullweft and root sandboxes (both sha2d262157…, mtime Apr 6). SameidentUUIDc9877d69-…in all three; the pk/sk diverge. Container copy is the live oneremotepairingdreads (per iter-16 fs_usage).
Run A — xcrun devicectl manage pair with active 150 KB inject¶
Command (executed as nullweft, not via sudo, so CoreDeviceService
spawned in the user XPC domain):
Result inside ~2 s:
ERROR: An error occurred while communicating with a remote process.
(com.apple.dt.CoreDeviceError error 3 (0x03))
The connection was interrupted. (com.apple.Mercury.error error 1000 (0x3E8))
iPhone-side Trust dialog: never displayed. devicectl did not get far enough to ask the iPhone for one.
CoreDeviceService log around the failure (compacted):
(iosmux_inject.dylib) [iosmux] OS_remote_device created: name=iPhone (iosmux) ...
(iosmux_inject.dylib) [iosmux] DYLD_INTERPOSE active: 62 services from Handshake, tunnel=[fd88:b337:ebe5::1]
(iosmux_inject.dylib) [iosmux] === PoC registration complete. Device visible in Xcode. ===
[com.apple.mobiledevice:All] Device '00008110-0004596E22A0401E' is no longer valid;
cannot start remote service 'com.apple.mobile.notification_proxy'.
[com.apple.mobiledevice:All] Failed to start service via RemoteServiceDiscovery: 0xe800000b (kAMDNotConnectedError)
[com.apple.dt.coredevice:rsddevicewrapper] Error fetching developer mode status from device:
"Could not connect to the device."
Read: the inject does its PoC registration cleanly (the synthetic
device is visible to Xcode); CDS then attempts RSD-level service
calls keyed on the hardware UDID 00008110-0004596E22A0401E and
finds that the device with that UDID has nothing it can connect to
on the synthetic-device's RSD endpoint. The synthetic device is
behind a tunnel address ([fd88:b337:ebe5::1]) that has no
notification_proxy listener — the inject's PoC stops at "device
exists in Xcode," it does not stand up the per-service handlers CDS
expects post-pair.
This is consistent with the inject being a registration shim only. It is also the architecture this project has been operating under for weeks; iter-18's contribution is making that operational mode explicit.
Run B — same command with .disabled 96 KB stub swapped into the active path¶
Hypothesis tested: maybe the inject is the only thing blocking the real iPhone from reaching CDS through the normal RSD channel. If disabled, CDS would talk directly to tunneld → real iPhone, the native pair-setup ceremony would run, the user could tap Trust.
Swap performed (reversibly):
mv /Library/Developer/CoreDevice/iosmux_inject.dylib \
/Library/Developer/CoreDevice/iosmux_inject.dylib.iter18-was-active
cp -p /Library/Developer/CoreDevice/iosmux_inject.dylib.disabled \
/Library/Developer/CoreDevice/iosmux_inject.dylib
launchctl kickstart -k user/501/com.apple.CoreDevice.CoreDeviceService
(mv was used for the active → was-active step so dyld immediately
sees one canonical file at the load path; cp -p for stub →
active so the .disabled copy stays in place for repeatable
roll-forward / roll-back. The patched CoreDeviceService binary has
a hard LC_LOAD_DYLIB /Library/Developer/CoreDevice/iosmux_inject.dylib
entry — see otool -L in the swap script — so a missing file would
make CDS fail to launch entirely.)
Run B result:
$ xcrun devicectl list devices
No devices found.
$ xcrun devicectl manage pair --device E8A190DD-…
ERROR: ... (same Mercury 1000 shape as Run A)
Synthetic iPhone (iosmux) disappeared from devicectl list devices
entirely. CoreDeviceService log shows a different failure mode:
[com.apple.network:connection] [C1 ... url: http://127.0.0.1:62078/device-info ...] start
[com.apple.network:connection] nw_socket_handle_socket_event [C1:2] Socket received CONNRESET event
[com.apple.network:connection] nw_socket_handle_socket_event [C1:2] Socket SO_ERROR [61: Connection refused]
... (same again on /services)
(iosmux_inject.dylib) [iosmux] FATAL: XPC proxy init failed
Two facts emerge:
- The 96 KB
.disabledis not a no-op stub. It still installs itself into CDS, still tries to bring up its own XPC plumbing, and fails fatally duringXPC proxy init. Symbol-table diff confirms it:nmreports 267 symbols (__text0x7a10) for.disabledvs 411 symbols (__text0xf13b) for the 150 KB active copy. The smaller variant lacks the full SPIKE / RSD hook surface but still carries theIosmuxMDProxyObjective-C class — that's why the synthetic device's name was earlier suffixed with(iosmux)even though pair was failing. - CDS without functional inject hooks falls through to classic
usbmuxd at
127.0.0.1:62078, and that fallback dies withConnection refused. usbmuxd itself is running on this VM (launchctl print system/com.apple.usbmuxd → state = running, pid 146) but its USB-attached device set is empty: USB passthrough into a libvirt VM is not supported per OSX-KVM upstream (seefeedback_no_usb_passthrough.md), and even if it were, classic usbmuxd is dead at the protocol level for iOS 17+ (ADR-0001 andfeedback_design_decisions.md). So 62078 has no listener on either side of the USB question.
The active inject's job is therefore not just "register a synthetic device" but also stand in for the missing usbmuxd: it intercepts classic-lockdown API calls CDS would have made through usbmuxd and serves them out of its own emulated state. Removing the inject removes both halves of the bridge — there is no fallback path to "just talk to the real iPhone."
Reverted swap immediately after measurement (active → 150 KB, stub →
back to .disabled), kickstarted CDS again, confirmed
iPhone (iosmux) reappears in devicectl list devices. VM is back
in pre-iter18 stable state.
Backups taken¶
All host-side backups under ~/backups/iosmux/:
d17-pre-plant/(3 files, 1612 B): real iPhone-related plists pulled before the iter-17 plant attempt.d18-cleanup-pre/(4 files, 2074 B): four sandboxed-container copies of the E8A190DD peer + identity plists, captured before the iter-18 cleanup of the planted E269C66F record.d18-full-pre/iosmux-d18-stage-full/(8 plists, plus inventory + sha256): full filesystem snapshot of every RemotePairing-relevant plist on havoc, taken at the start of iter-18 — both lockdown-side (/var/db/lockdown/...) and container-side (the two sandboxed copies under nullweft and root). All sha256 verified host-side against the staging sha256 file.d18-inject-pre/(4 dylibs, ~340 KB): all four iosmux dylib variants on the VM (iosmux_inject.dylib+.bak+.disabled+iosmux_swift.dylib) at iter-18 start. Pulled directly viascp havoc-root:to host; no/tmpstaging.
Existing earlier backups still on disk (system-binaries-ios2641/,
vm-inject-20260408/, inject-pre-rsd-conn/,
remotepairingd-xpc-pre-insert/, RemotePairing.framework.full/,
etc.) cover earlier slices; none of them holds a true pre-patch
CoreDeviceService binary — system-binaries-ios2641/CoreDeviceService
sha-matches the patched one byte-for-byte and strings confirms it
contains /Library/Developer/CoreDevice/iosmux_inject.dylib already,
so it is a post-patch capture mislabelled by date. No pre-patch
original is currently available on the host.
Why this falsifies "transparent proxy" architecture¶
- Active inject path — synthetic device exists, inject intercepts enough to register it, but CDS's RSD calls go to a tunnel that has no real-device listeners on the per-service ports. Pair fails because the synthetic device cannot perform iOS Pair-Setup (it has no Pair-Setup endpoint).
- Stub inject path — synthetic device disappears, CDS falls
back to classic usbmuxd at 62078, that fails because usbmuxd has
no device. Pair cannot even start because there is nothing for
manage pairto reach.
There is no third inject mode in the codebase that yields "real iPhone exposed to CDS as itself." Producing one would require re-introducing usbmuxd-as-pair-flow (refused at protocol level for iOS 17+) or wiring CDS directly to tunneld's pair-flow path (tunneld has no such path; pymobiledevice3 has its own non-CoreDevice pair surface that bypasses CDS). Both are out of scope.
The only remaining way for xcrun devicectl manage pair to succeed
against a device routed through this VM is for the inject + iosmux
backend to be the pair endpoint for the synthetic device,
serving the lockdown-over-TCP three-verb API
(RSDCheckin / QueryType / GetValue) and declaring the synthetic
device's pairingState as paired without invoking iOS Pair-Setup
against the real device. That is the Option δ direction
(stage2.md "Option δ" section,
connection.md
"Phase S2.B note") and
the work scoped by Phase D.6.6-impl.
Implications for next iter¶
- iter-19+ does not attempt
xcrun devicectl manage pairagainst the synthetic device again. The expected outcome is now known and documented; the experiment has been falsified. - iter-19+ does not pursue mDNS-advert planting / authTag derivation
/ Ed25519 challenge replay as a way to flip pairingState through
remotepairingd. Same reason — those approaches presupposed the
transparent-proxy architecture (already falsified by Phase S2.B,
see
s2b-pair-attempt-log.md) that iter-18 falsified. - Phase D.6.6-impl is the next concrete step: extend the
cmd/iosmux-backend/Go binary to bind[::1]:50367(port already advertised in our HandshakeServicesdict undercom.apple.mobile.lockdown.remote.trustedper iter-11), implement the three-verb classic-lockdown protocol, and serve a coherent device dictionary (likely seeded from the captured 88-key dict iter-15 obtained viapymobiledevice3 remote rsd-infoagainst the real iPhone in a brief stock-tunneld window). Once that works,pairingState: pairedshould follow without any native iOS pair-setup ever running. - The iosmux backend's Go relay (
/Users/nullweft/iosmux/iosmux-relay) was not running during iter-18. Whether running it would have changed Run A'skAMDNotConnectedErroroutcome was not measured; iter-19 can re-confirm the failure with relay running, but the expected result is still failure for the same architectural reason — relay terminating an RSD tunnel does not turn the synthetic device into a real-iOS Pair-Setup endpoint.
Evidence trail¶
- Backups:
~/backups/iosmux/d18-full-pre/(host) — every RemotePairing plist on havoc at iter-18 start, sha256-verified. - Backups:
~/backups/iosmux/d18-inject-pre/(host) — all four iosmux dylib variants captured before swap. - Inject load mechanism:
otool -L /Library/Developer/PrivateFrameworks/CoreDevice.framework/Versions/A/XPCServices/CoreDeviceService.xpc/Contents/MacOS/CoreDeviceService— last entry is/Library/Developer/CoreDevice/iosmux_inject.dylib (compatibility version 0.0.0, current version 0.0.0). - Run A log capture (CDS / remotepairingd / remoted / devicectl
predicates, 4 min window): captured to
/tmp/iosmux-d18-pair-log.outon havoc. Contains thekAMDNotConnectedErrorchain and the inject's PoC registration trace. - Run B log capture: same predicate set, replayed after the swap.
Contains the
Connection refused 127.0.0.1:62078chain and the stub'sFATAL: XPC proxy init failedline. - Symbol diff:
nm /Library/Developer/CoreDevice/iosmux_inject.dylib.iter18-was-active | wc -l→ 411;nm /Library/Developer/CoreDevice/iosmux_inject.dylib | wc -l→ 267 (when stub was active). - usbmuxd state:
launchctl print system/com.apple.usbmuxd→ state = running, pid 146; no listener on127.0.0.1:62078perlsof -nP -iTCP:62078(empty). - Host backup
system-binaries-ios2641/CoreDeviceServicesha2561b4aa8a5f1e29078…matches the live patched binary on VM byte-for-byte;stringsof the host backup contains/Library/Developer/CoreDevice/iosmux_inject.dylib— i.e. it is a post-patch capture, not a pre-patch original.
References¶
connection.md"Phase S2.B note" — already documents the bridge-not-proxy architecture that this iter empirically re-confirmed.stage2.md"Phase B realization" and "Option δ" sections — same architectural framing, in the forward-plan voice.s2b-pair-attempt-log.md— the original Phase S2.B finding (RST on tunnel) that established this.- iter-15-multifield-patch/findings.md — wire-level identity matching falsification that escalated H-PairState.
- iter-16-pairstate-survey/findings.md — host-side localisation of the pair gate to RemotePairing peer store + Bonjour advert.
feedback_design_decisions.md(project memory, not in repo) — usbmuxd-as-pair REJECTED for iOS 17+. Cited for completeness; not linkable from public docs.- Apple
MobileDevice.frameworkerrno0xe800000b(kAMDNotConnectedError) — observed in Run A log, signals the device cannot be reached for the requested service.