Skip to content

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 mangledTypeName envelope structure
  • Where the action type lives in the dict
  • Where the device identifier lives
  • The reply format (Codable encoding of Output types)

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.