Plan: Next Steps — Action Interception, DeviceInfo, Service Connections¶
Date: 2026-04-13 Status: Workstreams 1+3 done. Workstream 2 needs redesign — original approach was based on wrong protocol assumption (see action-interception-full-picture.md).
Status of original three workstreams¶
Workstream 1: Hook Migration — DONE¶
All shared cache __TEXT patches eliminated. Replaced with __DATA,__interpose
section. dylib linked against RemoteServiceDiscovery.framework and RemoteXPC.framework
so the linker resolves bind records and dyld fills the interpose original slots.
| # | Function | New mechanism |
|---|---|---|
| 1 | remote_device_copy_service_names | DYLD_INTERPOSE |
| 2 | remote_device_copy_service | DYLD_INTERPOSE |
| 3 | remote_device_copy_property | DYLD_INTERPOSE |
| 4 | remote_device_heartbeat | DYLD_INTERPOSE |
| 5 | remote_service_create_connected_socket | DYLD_INTERPOSE |
| 6 | remote_service_connect_socket | DYLD_INTERPOSE |
| 7 | xpc_remote_connection_create_with_remote_service | DYLD_INTERPOSE |
Removed entirely (were redundant with CDS binary patches): - CoreDevice+0x30BEE0 unnamed function - CoreDevice+0x30C33C JE→NOP patch - CoreDeviceUtilities+0x3BCF0 DDI throw suppression
Safe hooks (CDS binary, not shared cache): - CDS+0xCCB0 helper hook - CDS+0x5E2D0 callq patch
Workstream 3: Service Connections — DONE¶
g_services[] is filled directly from the RSD Handshake Services dict at init
time (74 entries with port + UsesRemoteXPC + properties). Go relay daemon eliminated.
CDS calls remote_service_create_connected_socket(service)
→ [INTERPOSE] iosmux_remote_service_create_connected_socket(service)
→ iosmux_get_service_port(name) — lookup in g_services[]
→ TCP connect to [tunnel_ipv6]:port
→ return fd
For RemoteXPC services (UsesRemoteXPC=true), the interpose returns
xpc_remote_connection_t constructed via xpc_remote_connection_create_with_connected_fd
on the TCP fd. Verified end-to-end at the connection level.
Workstream 2: Action Dispatch — INVALID APPROACH, REDESIGN NEEDED¶
Original plan was to hook CoreDeviceUtilities.invoke(anyOf:usingContentsOf:) callsite
in CDS binary (CDS+0xB896), filter by reverse-DNS action string from the XPC dict, and
return static success for the 14 "static" actions.
This is wrong. The Xcode↔CDS connection uses Mercury Codable XPCDictionary with a
mangledTypeName envelope, not flat CoreDevice.featureIdentifier keys. Filtering by
xpc_dictionary_get_string(event, "CoreDevice.featureIdentifier") cannot work — those
keys do not exist on this connection.
The current state of the CDS+0xB896 hook (mov al, 1; ret) is the active bug causing
"error communicating with remote process" — it short-circuits the Swift async continuation,
Mercury never sends an XPC reply, Xcode times out.
See docs/research/action-interception-full-picture.md for the full synthesis and the
new strategy options (logging interpose to capture real Mercury format → Swift-level
invoke() hook OR XPC-level Mercury Codable parser).
Action routing intent (independent of mechanism)¶
The 34 known CoreDevice actions split into three groups regardless of how we intercept:
Static success — 14 actions¶
These can be answered without involving the iPhone:
| Action ID | Response intent |
|---|---|
| pair | "already paired" |
| unpair | success no-op |
| connect | set state=connected |
| disconnect | success no-op |
| enableddiservices | "already enabled" |
| disableddiservicesaction | success |
| fetchddimetadata | static metadata |
| updatehostddis | success |
| removehostddis | success |
| acquireusageassertion | success with fresh UUID — critical for _shadowUseAssertion |
| listusageassertions | empty list |
| tags | success |
| gettrainname | static value |
| darwinnotificationobserve / darwinnotificationpost | success |
Forward through tunnel — 8 actions¶
These need real iPhone service connections (use the existing service hooks):
| Action ID | Service required |
|---|---|
| createservicesocket | TCP to service port from Handshake |
| appinstall | com.apple.coredevice.appservice (RemoteXPC) |
| transferfiles | com.apple.coredevice.fileservice.control |
| receivefiles | same |
| rsyncfiles | same |
| listfiles | same |
| fetchdyldsharedcachefiles | com.apple.dt.fetchsymbols |
| fetchmachodylibs | same |
Reject — 9 actions¶
Not applicable to a network device:
| Action ID | Reason |
|---|---|
| enterdfu | Requires physical USB |
| snapshotcreation / fetchscreenshots / remove / resume / setname (5) | VM-only |
| exportvirtualmachinearchive | VM-only |
| provisiondevice / removeprovisioneddevice | Not applicable |
Open work in priority order¶
1. Test the wrapper read-overflow fix in isolation¶
Commit 59ca5cd fixed the g_hook_wrapper = *(void **)((uint8_t *)sdr + 104) heap
read overflow. This bug returned a garbage pointer to CDS via the CDS+0xCCB0 and
CDS+0x5E2D0 hooks. Some of the previously-attributed "Pair action crashes" may have
actually been "garbage wrapper crashes".
Deploy ONLY this fix (without touching CDS+0xB896 yet) and observe whether Pair behavior changes. May change the picture significantly.
2. Convert CDS+0xB896 from mov al, 1; ret to passthrough trampoline¶
Current: mov al, 1; ret — short-circuits invoke(), breaks Swift async.
Target: movabs r10, <orig_invoke>; jmp r10 — passes through to the real
invoke(anyOf:usingContentsOf:) so Swift async continuation completes properly
and Mercury can send an XPC reply.
This will surface the actual underlying crash (whatever it is) instead of masking it as a timeout. Per E+F research, this is the correct disposition: keep the hook infrastructure but make it benign until we have a proper higher-level interceptor.
3. Capture real Mercury XPC messages on the Xcode↔CDS connection¶
Install a logging-only INTERPOSE on xpc_connection_send_message and
xpc_connection_send_message_with_reply that filters by
xpc_connection_get_name() == "com.apple.CoreDevice.CoreDeviceService" and
dumps xpc_copy_description(message) to a log file. Click Pair in Xcode, capture
the full exchange. Analyze to learn:
- Exact
mangledTypeNameenvelope structure - Where the action type lives in the dict
- Where the device identifier lives
- The reply format (Codable encoding of
Outputtypes)
4. Design proper action interceptor based on captured data¶
Three options after we have real data:
A — XPC-level Mercury parser. Hook xpc_connection_set_event_handler (research A+B
confirmed how to do this safely), filter by mangled type name, build a Codable-formatted
reply. Hard because Mercury Codable wrapping is undocumented.
B — Swift-level invoke() hook. Hook ActionImplementation.invoke(usingContentsOf:)
per action type. Action is already decoded into a typed Swift object at this level.
Requires Swift async ABI knowledge (continuation handling).
C — Hybrid. Filter at XPC level by mangled type name, build response by copying a known-good response from a related action and substituting UUIDs.
5. Set missing DeviceInfo fields (UNVERIFIED HYPOTHESIS)¶
pair-button-and-cfnetwork.md lists 11 DeviceInfo setters we are not currently calling
(transportType, platform, deviceType, reality, osVersion, osBuild, udid, authenticationType,
developerModeStatus, bootState, isMobileDeviceOnly). The list is a hypothesis from
disassembly, not runtime-verified. Setting them may or may not affect Xcode behavior.
Defer until after action interception works — may not be needed at all if Xcode is
satisfied with what we already provide once _shadowUseAssertion is set via
acquireusageassertion.
6. Test app deploy through tunnel services¶
End-to-end deploy test once Xcode considers the device usable. Validates that the
existing service hook (raw TCP to tunnel service ports) actually works for
streaming_zip_conduit.shim.remote and friends.