Skip to content

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 wrapper
  • Mercury.XPCMessageDispatcher — routes incoming messages to handlers
  • Mercury.XPCSideChannel — side-channel for notifications (started/cancelled/triggered)
  • Mercury.XPCFileTransfer / Mercury.XPCFileDescriptor — file transfer primitives
  • Mercury.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:

{
  "mangledTypeName": "<Swift mangled type name>",
  "value": { <Codable payload> }
}

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)

enum DevicesUpdateType: String, Codable {
    case added
    case removed
}

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

struct RSDDeviceInfo: Codable, Hashable {
    var name: String
    var uuid: UUID
}

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 remotepairingd system daemon
  • Creates RemotePairingDeviceRepresentation for each discovered device
  • Fields: _cache (RemotePairingDeviceInfoCache), _mutableState, _deviceManagementQueue
  • Has RemotePairingDeviceInfoCache with 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 .deviceRepresentations array
  • This is the most promising injection point

RSD (Remote Service Discovery) Integration

CoreDeviceService manages RSD connections to devices:

  • RSDBrowser — browses for RSD devices via remote_device C API
  • RSDBrowser.findConnectedDevice(type:where:) — find specific device
  • RSDDeviceWrapper — wrapper around OS_remote_device C object
  • RSDDeviceInfo — name + UUID extracted from RSD device
  • RemoteDevice.createRSDDevice() — creates OS_remote_device from RemoteDevice
  • CoreDeviceService.rsdDeviceWrapper(forClientIdentifier:) — get wrapper for a client
  • ActionConnectionCache.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 plugins
  • CoreDeviceServicePluginProtocol — individual plugin interface
  • Plugins provide: deviceRepresentationProviders, capabilityImplementationActionTypes, hostCapabilityImplementationProviders, deviceCapabilityImplementationProviders
  • DeviceRepresentationProvider protocol with .deviceRepresentationBrowser property

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:

  1. 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.
  2. 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.
  3. StaticDeviceRepresentationBrowser Injection — Not investigated. The active injection path achieves the same result via dylib + DYLD_INTERPOSE without needing this.
  4. 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.