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)¶
- HTTP/2 connection preface (24 bytes)
- SETTINGS frame (same as server's)
- WINDOW_UPDATE stream=0, increment=983041
- HEADERS stream=1, END_HEADERS (opens ROOT_CHANNEL)
- DATA stream=1: XpcWrapper(empty dict, flags=0x101, msg_id=0)
- DATA stream=1: XpcWrapper(no payload, flags=0x0201 PING)
- HEADERS stream=3, END_HEADERS (opens REPLY_CHANNEL)
- 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