CoreDevice XPC Protocol (Reverse-Engineered)¶
Date: 2026-04-06 (updated with nm/strings deep analysis)
Method: LLDB trace of devicectl, log stream capture, nm | swift demangle on CoreDevice binaries
Architecture Overview¶
devicectl / Xcode (client)
│
│ XPC (com.apple.CoreDevice.CoreDeviceService)
│ Transport: Mercury.SystemXPCPeerConnection
│ Messages: Mercury.XPCDictionary (Swift Codable with mangled type names)
│
▼
CoreDeviceService.xpc (user-level XPC service, RunLoopType=NSRunLoop, ServiceType=User)
│
├── ClientManager — tracks connected XPC peers, publishes ServiceEvents
├── ServiceDeviceManager — owns device lifecycle, browsers, identity management
│ ├── DeviceRepresentationBrowsers (installed at startup):
│ │ ├── RemotePairingDeviceRepresentationBrowser ← remotepairingd integration
│ │ ├── RestorableDeviceRefDeviceRepresentationBrowser ← AMRestore (DFU/recovery)
│ │ └── StaticDeviceRepresentationBrowser ← manual/static entries
│ └── DeviceRepresentationProviders (from plugins)
├── ServiceOperationManager — tracks async operations (install, DDI, etc.)
├── ActionConnectionCache — caches RemoteXPC connections to devices
├── CoreDeviceServiceStore — persistent state (SQLite)
└── CoreDeviceServicePluginManager (loads .coredeviceplugin bundles)
└── DeviceSpecPlugin.coredeviceplugin (only built-in plugin)
XPC Service Identity¶
- Bundle ID:
com.apple.CoreDevice.CoreDeviceService - Binary:
/Library/Developer/PrivateFrameworks/CoreDevice.framework/Versions/A/XPCServices/CoreDeviceService.xpc/Contents/MacOS/CoreDeviceService - Type: User-level XPC service (not a LaunchDaemon)
- ProcessType: Interactive
- No entitlement check for connection (accessible from any user process)
Mercury XPC Framework¶
CoreDevice uses an internal Apple framework called Mercury for XPC communication:
Mercury.SystemXPCPeerConnection— local XPC connection (client <-> CoreDeviceService)Mercury.RemoteXPCPeerConnection— remote XPC over tunnel (CoreDeviceService <-> device)Mercury.XPCDictionary— typed XPC dictionary wrapperMercury.XPCMessageDispatcher— routes incoming messages to handlersMercury.XPCSideChannel— side-channel for notifications (started/cancelled/triggered)Mercury.XPCFileTransfer/Mercury.XPCFileDescriptor— file transfer primitivesMercury.DynamicCodable— protocol for type-erased Codable serialization
Mercury.XPCDictionary Message Types¶
Extensions on Mercury.XPCDictionary in CoreDevice:
- .isStartedMessage — side channel "started" notification
- .isCancelledMessage — side channel "cancelled" notification
- .isMessageWithNotificationName — Darwin notification name
Message Format¶
All XPC messages are Swift Codable-serialized dictionaries:
CoreDeviceService uses Mercury.XPCMessageDispatcher<Mercury.SystemXPCPeerConnection>
to dispatch incoming messages. Messages that don't match ActionDeclarations are logged
as "Ignoring XPC message since it was not an ActionDeclaration".
ServiceEventKind (Complete Enumeration)¶
All events from service to client. CoreDeviceProtocols.ServiceEventKind (String-backed RawRepresentable):
| Event Kind | Direction | Description |
|---|---|---|
deviceManagerCheckInComplete |
service → client | Response to check-in, carries initial device snapshots |
deviceManagerFullyInitialized |
service → client | All browsers done, device list is final |
deviceManagerDevicesUpdate |
service → client | Device added or removed after initial check-in |
remoteDeviceStateUpdate |
service → client | Device state changed (capabilities, info, etc.) |
deviceStateChanged |
service → client | Lower-level device state transition |
deviceRequestUpdate |
service → client | Device availability request update |
pairingEvent |
service → client | Pairing state change |
operationStatusChanged |
service → client | Operation (install, DDI, etc.) status change |
operationStateUpdate |
service → client | Operation state transition |
serviceOperationManagerCheckInComplete |
service → client | Response to operation manager check-in |
Events are wrapped in CoreDevice.ServiceEvent (contains ServiceEventTypeCodingWrapper)
and published via ClientManager.publish(event:) to all connected peers.
ServiceRequestType (Client → Service Messages)¶
| Request Type | Description |
|---|---|
DeviceManagerCheckInRequest |
Register for device events (contains UUID identifier) |
ServiceOperationManagerCheckInRequest |
Register for operation events |
DeviceAvailabilityRequest |
Request device availability/assertion |
OperationManagerNewOperationEvent |
Client starts a new operation |
OperationManagerOperationUpdateEvent |
Client updates operation progress |
Complete Event Payloads¶
DeviceManagerCheckInRequest (client → service)¶
struct DeviceManagerCheckInRequest: ServiceRequestType, Codable {
var identifier: UUID // Client-generated session UUID
}
DeviceManagerCheckInCompleteEvent (service → client)¶
struct DeviceManagerCheckInCompleteEvent: ServiceEventType, Codable {
static let event: ServiceEventKind = .deviceManagerCheckInComplete
var checkInRequestIdentifier: UUID
var initialDeviceSnapshots: [DeviceStateSnapshot]
var serviceFullyInitialized: Bool
}
DeviceManagerFullyInitializedEvent (service → client)¶
struct DeviceManagerFullyInitializedEvent: ServiceEventType, Codable {
static let event: ServiceEventKind = .deviceManagerFullyInitialized
// No payload — just a signal
}
DevicesUpdateType (part of deviceManagerDevicesUpdate)¶
RemoteDeviceStateUpdatedEvent (service → client)¶
struct RemoteDeviceStateUpdatedEvent: ServiceEventType, Codable {
static let event: ServiceEventKind = .remoteDeviceStateUpdate
var deviceIdentifier: DeviceIdentifier
var state: DeviceStateSnapshot
}
DeviceStateSnapshot¶
struct DeviceStateSnapshot: Codable {
var deviceInfo: DeviceInfo // Name, model, OS version, UDID, etc.
var capabilities: Set<Capability> // What the device supports
var capabilityImplementations: [Capability: [String]] // How capabilities are provided
var monotonicIdentifier: Int64 // Monotonically increasing version
}
DeviceInfo (from CoreDeviceProtocols)¶
struct DeviceInfo: Codable {
var rsdDeviceInfo: RSDDeviceInfo? // RSD-specific info (name, uuid)
// ... plus model, OS version, platform, architecture, etc.
}
RSDDeviceInfo¶
Action Identifiers (Complete List)¶
Actions are capability-based operations the service can perform on devices. Each is identified by a reverse-DNS string:
| Action ID | Description |
|---|---|
com.apple.coredevice.action.acquireusageassertion |
Acquire tunnel usage assertion |
com.apple.coredevice.action.appinstall |
Install application |
com.apple.coredevice.action.connect |
Connect to device |
com.apple.coredevice.action.createservicesocket |
Create RemoteXPC service socket |
com.apple.coredevice.action.darwinnotificationobserve |
Observe Darwin notification |
com.apple.coredevice.action.darwinnotificationpost |
Post Darwin notification |
com.apple.coredevice.action.disconnect |
Disconnect from device |
com.apple.coredevice.action.disableddiservicesaction |
Disable DDI services |
com.apple.coredevice.action.enableddiservices |
Enable DDI services |
com.apple.coredevice.action.enterdfu |
Enter DFU mode |
com.apple.coredevice.action.exportvirtualmachinearchive |
Export VM archive |
com.apple.coredevice.action.fetchddimetadata |
Fetch DDI metadata |
com.apple.coredevice.action.fetchdyldsharedcachefiles |
Fetch dyld shared cache |
com.apple.coredevice.action.fetchmachodylibs |
Fetch Mach-O dylibs |
com.apple.coredevice.action.filenodedetails |
Get file node details |
com.apple.coredevice.action.gettrainname |
Get train name |
com.apple.coredevice.action.listfiles |
List files |
com.apple.coredevice.action.listusageassertions |
List active assertions |
com.apple.coredevice.action.lockstate |
Get lock state |
com.apple.coredevice.action.pair |
Pair with device |
com.apple.coredevice.action.provisiondevice |
Provision device |
com.apple.coredevice.action.receivefiles |
Receive files from device |
com.apple.coredevice.action.removehostddis |
Remove host DDIs |
com.apple.coredevice.action.removeprovisioneddevice |
Remove provisioned device |
com.apple.coredevice.action.rsyncfiles |
Rsync files |
com.apple.coredevice.action.snapshotcreation |
Create VM snapshot |
com.apple.coredevice.action.snapshotfetchscreenshots |
Fetch snapshot screenshots |
com.apple.coredevice.action.snapshotremove |
Remove VM snapshot |
com.apple.coredevice.action.snapshotresume |
Resume VM from snapshot |
com.apple.coredevice.action.snapshotsetname |
Set snapshot name |
com.apple.coredevice.action.tags |
Manage device tags |
com.apple.coredevice.action.transferfiles |
Transfer files to device |
com.apple.coredevice.action.unpair |
Unpair device |
com.apple.coredevice.action.updatehostddis |
Update host DDIs |
Device Discovery Pipeline (How Devices Get to Clients)¶
Service-Side (CoreDeviceService.xpc)¶
remotepairingd (system daemon)
│
│ RemotePairing.ConnectableDeviceBrowser (framework API)
│
▼
RemotePairingDeviceRepresentationBrowser
│ creates RemotePairingDeviceRepresentation for each device
│ (implements ServiceDeviceRepresentation protocol)
│ Fields: _cache (RemotePairingDeviceInfoCache), _mutableState, _deviceManagementQueue
│
├── ServiceDeviceRepresentation provides:
│ - selfReportedDeviceIdentifier: DeviceIdentifier
│ - effectiveDeviceIdentifier: DeviceIdentifier?
│ - currentStateSnapshot: DeviceStateSnapshot
│ - deviceInfo: DeviceInfo
│ - capabilities: Set<Capability>
│ - addDeviceRepresentationStateChangedHandler()
│ - setDeviceRepresentationRemovalHandler()
│ - mutateState(using:) -> DeviceStateSnapshot
│
▼
ServiceDeviceManager.install(browser:)
│ Browser.start() → callback with ServiceDeviceRepresentation
│ Identity management: offers to DeviceRepresentationProviders
│ Creates/updates RemoteDevice(snapshot:)
│
├── On device added:
│ ServiceDeviceManager publishes deviceManagerDevicesUpdate(.added)
│ with updated DeviceStateSnapshot
│
├── On device state change:
│ ServiceDeviceManager publishes remoteDeviceStateUpdate
│ via ServiceDeviceRepresentation state change handler
│
├── On device removed:
│ ServiceDeviceManager publishes deviceManagerDevicesUpdate(.removed)
│
▼
ClientManager.publish(event: ServiceEvent)
│ Sends to ALL connected XPC peers
▼
devicectl / Xcode (receives events via EventManager)
Client-Side (devicectl / Xcode)¶
1. Connect to com.apple.CoreDevice.CoreDeviceService XPC
2. Send DeviceManagerCheckInRequest { identifier: <uuid> }
3. Receive DeviceManagerCheckInCompleteEvent {
checkInRequestIdentifier: <uuid>,
initialDeviceSnapshots: [DeviceStateSnapshot],
serviceFullyInitialized: Bool
}
4. For each snapshot → DeviceManager creates RemoteDevice(snapshot:)
5. If !serviceFullyInitialized → wait for DeviceManagerFullyInitializedEvent
6. After fully initialized:
- DeviceManager.allDevices() returns current set
- DeviceManager.addedDevices → AsyncThrowingStream for new devices
- DeviceManager.deviceChanges → AsyncThrowingStream for updates
7. Ongoing: receive remoteDeviceStateUpdate / deviceManagerDevicesUpdate events
Live Trace: devicectl list devices (captured 2026-04-06)¶
T+0.000 devicectl → CoreDeviceService: XPC connection activated
(name=com.apple.CoreDevice.CoreDeviceService)
T+0.004 CoreDeviceService: peer connected [pid:33895]
"Ignoring XPC message since it was not an ActionDeclaration"
(This is the ProvisioningProvidersListRequest)
T+0.026 devicectl: "DeviceManager sending check-in request: 487F370F-..."
T+0.027 CoreDeviceService: "ServiceDeviceManager - Client connected: [33895] (no name).
Handling DeviceManagerCheckInRequest with identifier 487F370F-..."
T+0.028 CoreDeviceService: "Published DeviceManagerCheckInCompleteEvent
for DeviceManagerCheckInRequest with identifier 487F370F-..."
T+0.029 devicectl: "Received Event: ServiceEvent(
CoreDevice.DeviceManagerCheckInCompleteEvent(
checkInRequestIdentifier: 487F370F-...,
initialDeviceSnapshots: [],
serviceFullyInitialized: true))"
T+0.034 devicectl: "DeviceManager check-in completed successfully"
T+0.035 devicectl: "No devices found."
T+0.037 CoreDeviceService: peer [33895] connection invalidated (client exited)
Key findings from live trace:
- serviceFullyInitialized: true came immediately (no separate FullyInitializedEvent needed)
- This means CoreDeviceService was already running and fully initialized
- The initial CheckInCompleteEvent carries ALL currently known devices
- No separate device-list query exists — check-in IS the device query
Device Browsers (Complete List in CoreDeviceService.xpc)¶
1. RemotePairingDeviceRepresentationBrowser¶
- Source file:
CoreDeviceService/RemotePairingDeviceRepresentationBrowser.swift - Uses:
RemotePairing.ConnectableDeviceBrowser(from RemotePairing.framework) - Communicates with
remotepairingdsystem daemon - Creates
RemotePairingDeviceRepresentationfor each discovered device - Fields:
_cache(RemotePairingDeviceInfoCache),_mutableState,_deviceManagementQueue - Has
RemotePairingDeviceInfoCachewith SQLite storage (remotePairingIdentifier TEXT PRIMARY KEY) - Log messages: "Creating new ConnectableDeviceBrowser to resume browsing"
2. RestorableDeviceRefDeviceRepresentationBrowser¶
- Source file:
CoreDeviceService/RestorableDeviceRefDeviceRepresentationBrowser.swift(implicit) - Uses:
AMRestorableDeviceRegisterForNotificationsForDevices(MobileDevice.framework) - For DFU/recovery mode devices
- Has
_mobileDeviceInteractionQueue,_state - Logs: "Registering for device notifications with AMRestorableDeviceRegisterForNotificationsForDevices"
- Uses dispatch queue:
com.apple.dt.coredevice.RestorableDeviceRefDeviceRepresentationBrowser.mobiledevice
3. StaticDeviceRepresentationBrowser¶
- For statically-configured or manually-added device representations
- Has
.deviceRepresentationsarray - This is the most promising injection point
RSD (Remote Service Discovery) Integration¶
CoreDeviceService manages RSD connections to devices:
RSDBrowser— browses for RSD devices viaremote_deviceC APIRSDBrowser.findConnectedDevice(type:where:)— find specific deviceRSDDeviceWrapper— wrapper aroundOS_remote_deviceC objectRSDDeviceInfo— name + UUID extracted from RSD deviceRemoteDevice.createRSDDevice()— createsOS_remote_devicefrom RemoteDeviceCoreDeviceService.rsdDeviceWrapper(forClientIdentifier:)— get wrapper for a clientActionConnectionCache.getOrCreateRemoteXPCConnection(toDeviceIdentifiedBy:forFeatureIdentifiedBy:)— cached RemoteXPC connections
RSD connection lifecycle strings: - "Acquired tunnel connection to device." - "RSD connection reset: %s" - "RSD connection torn down: %s" - "Device became unavailable while resetting the RSD connection: %s" - "device tunnel disconnected: %s" - "Constructing RSDDeviceWrapper object" - "Browsing for reconnected RSD device"
Plugin System¶
Plugin Architecture¶
CoreDeviceServicePluginManagerProtocol— manages loaded pluginsCoreDeviceServicePluginProtocol— individual plugin interface- Plugins provide:
deviceRepresentationProviders,capabilityImplementationActionTypes,hostCapabilityImplementationProviders,deviceCapabilityImplementationProviders DeviceRepresentationProviderprotocol with.deviceRepresentationBrowserproperty
Plugin Search Paths (in order)¶
1. /Library/Developer/PrivateFrameworks/CoreDevice.framework/PlugIns/
2. /Library/Developer/PrivateFrameworks/CoreDevice.framework/Versions/A/AppleInternal/PlugIns/
3. /Library/Developer/CoreDevice/PlugIns/
4. /AppleInternal/Library/Developer/CoreDevice/PlugIns/
5. ~/Library/Developer/CoreDevice/PlugIns/ ← USER HOME, not SIP protected!
Built-in Plugin¶
DeviceSpecPlugin.coredeviceplugin— device specification support- Provides:
DeviceSpecCapabilityProvider,CommandsProvider - Does NOT provide
DeviceRepresentationProvider
Injection Strategies (HISTORICAL — current approach is dylib injection)¶
The four strategies originally considered have been exhausted:
- Custom CoreDevice Plugin — DEAD. CoreDevice.framework has no
.swiftmodule/.swiftinterface. Synthetic interfaces compiled but crashed at runtime due to protocol witness table mismatch with the real CoreDevice. - Fake remotepairingd Responses — DEAD. DataVaultHelper checks remotepairingd entitlements via Mercury XPC. Ad-hoc codesign yields empty entitlements view. Cannot produce DER provisioned entitlements without an Apple signing cert.
- StaticDeviceRepresentationBrowser Injection — Not investigated. The active injection path achieves the same result via dylib + DYLD_INTERPOSE without needing this.
- provisionDevice API — Not investigated. Same reason.
The active approach is LC_LOAD_DYLIB injection of iosmux_inject.dylib
into the CoreDeviceService binary (re-signed with the additional load command).
The dylib creates OS_remote_device directly, calls Swift class allocators via
inline asm, and hooks the few CDS-binary functions that need bypassing (CDS+0xCCB0,
CDS+0x5E2D0). All shared cache functions are intercepted via DYLD_INTERPOSE.
See plan-cds-rsd-inject.md for the full architecture.