Skip to content

RemoteXPC Protocol (Reverse-Engineered)

Date: 2026-04-07 Source: pymobiledevice3 remotexpc.py + live packet analysis Status: Full handshake documented, byte-level spec

Overview

RemoteXPC is HTTP/2 frames as transport for XPC binary messages. Used by iPhone RSD (port 58783) and tunnel services. No TLS on port 58783. No HPACK (empty HEADERS frames).

Endpoints

Port Access Services
58783 (CDC-NCM) Untrusted, plain TCP 7 basic services (lockdown untrusted, tunnel service)
Tunnel port (dynamic) Trusted, via RemotePairing All services (developer, debug, install, etc.)

XPC Wrapper Format (little-endian)

Offset  Size  Field
0       4     magic       = 0x29B00B92
4       4     flags       (XpcFlags bitmask)
8       8     size        (uint64, size of remainder - 8)
16      8     message_id  (uint64, per-stream counter)
24      ...   payload     (optional XpcPayload)

XPC Payload Format

Offset  Size  Field
0       4     magic            = 0x42133742
4       4     protocol_version = 0x00000005
8       ...   XpcObject        (root, usually DICTIONARY)

XpcFlags

ALWAYS_SET         = 0x00000001
PING               = 0x00000002
DATA_PRESENT       = 0x00000100
WANTING_REPLY      = 0x00010000
REPLY              = 0x00020000
FILE_TX_STREAM_REQ = 0x00100000
FILE_TX_STREAM_RESP= 0x00200000
INIT_HANDSHAKE     = 0x00400000

Full Handshake Sequence

Step 0: TCP connect

Plain TCP to iPhone link-local IPv6 port 58783.

Step 1: Server sends SETTINGS first

00 00 0c 04 00 00 00 00 00  (SETTINGS, length=12)
  00 03 00 00 00 64          MAX_CONCURRENT_STREAMS=100
  00 04 00 10 00 00          INITIAL_WINDOW_SIZE=1048576

Step 2: Client sends (all at once)

  1. HTTP/2 connection preface (24 bytes)
  2. SETTINGS frame (same as server's)
  3. WINDOW_UPDATE stream=0, increment=983041
  4. HEADERS stream=1, END_HEADERS (opens ROOT_CHANNEL)
  5. DATA stream=1: XpcWrapper(empty dict, flags=0x101, msg_id=0)
  6. DATA stream=1: XpcWrapper(no payload, flags=0x0201 PING)
  7. HEADERS stream=3, END_HEADERS (opens REPLY_CHANNEL)
  8. DATA stream=3: XpcWrapper(no payload, flags=0x00400001 INIT_HANDSHAKE)

Step 3: Server responds

  • WINDOW_UPDATE stream=0
  • SETTINGS ACK
  • HEADERS stream=1 (mirror ROOT_CHANNEL)
  • DATA stream=1: echo of empty dict
  • DATA stream=1: PING
  • HEADERS stream=3 (mirror REPLY_CHANNEL)
  • DATA stream=3: INIT_HANDSHAKE
  • DATA stream=1: PEER_INFO (device properties + service list)

Step 4: Client sends SETTINGS ACK

Handshake complete. Peer info received.

Stream IDs

  • Stream 1: ROOT_CHANNEL (client requests)
  • Stream 3: REPLY_CHANNEL (server events)
  • Odd streams: client-initiated
  • Even streams: server-initiated (file transfers)
  • message_id: per-stream counter, incremented after each send

Go Implementation Status

Already have (in iosmux codebase): - XPC binary encode/decode: internal/xpc/ (830 LOC) - XPC wrapper marshal/unmarshal: internal/xpc/wrapper.go - HTTP/2 raw frames: internal/rsd/frame.go - HTTP/2 constants: internal/rsd/constants.go

Need (~650-800 LOC): - internal/remotexpc/client.go — Connect(), PeerInfo(), SendReceive() - internal/remotexpc/handshake.go — 12-step sequence - internal/remotexpc/stream.go — frame multiplexer - internal/remotexpc/service.go — service list parsing