Skip to content

Phase D.6.5 — iPhone:50367 decoded: classic lockdown-over-TCP, advertised, 40/62 services share it

Status: verified — completed 2026-04-24

Pure local analysis on the iter-10 held-local pcap. Extracted both directions of the iPhone:50367 parallel service, applied five decoding lenses, and decoded the full Services dict from the greeting Handshake. Protocol on 50367 is classic lockdown-over-TCP (plaintext XML plist, 4-byte BE length prefix). Port 50367 is advertised in our Handshake Services dict under com.apple.mobile.lockdown.remote.trusted (UsesRemoteXPC=False, ServiceVersion=1). 40 of 62 services share this wire format — one handler in our Go backend unlocks 40 endpoints. Full writeup: findings.md.

D.6.6 implementation plan: add lockdown-over-TCP listener to the Go backend bound to port 50367, handle 3 verbs (RSDCheckin, QueryType, GetValue), serve cached device-info plist. ~1 day effort.

The answer to the D.6.x mystery

D.6.0-B showed CDS times out ~30s after our Handshake → UUID gate (D.6.1). D.6.1 fixed the UUID → CDS silent instead of timing out. D.6.2/D.6.3/D.6.4a ruled out trigger-type, back-connect hunger, and missing greeting-stream frames as explanations. D.6.5 finds what was actually missing: a parallel lockdown-over-TCP listener at the advertised service port.

When a host-side tool (pymobiledevice3 / CDS / devicectl) wants device state info (pair state, tunnel state, identity beyond what's already in the Handshake Properties), it:

  1. Reads the Services dict from our Handshake (includes port 50367)
  2. Opens a fresh TCP connection to that port
  3. Speaks 4-byte-BE-length-prefixed XML plist
  4. Expects a 3-verb protocol: RSDCheckin, QueryType, GetValue

SPIKE mode hasn't been hosting this service. That's why CDS reports "Mercury 1000 connection interrupted" — the greeting works, but the follow-up service isn't there.

The Services dict census

Decoded from the 14,124-byte Handshake DATA frame:

UsesRemoteXPC Count Wire format
True 22 HTTP/2 + XpcWrapper (RemoteXPC)
False 40 Classic lockdown-over-TCP (what 50367 speaks)

A single handler implementation in the Go backend covers 40 out of 62 services, including both .remote.trusted (port 50367 in our reference capture) and .remote.untrusted (port 50333) variants of lockdown itself.

The 3-verb protocol

Captured on iPhone:50367 during pymobiledevice3's lockdown info trigger:

client → iPhone (3 plists, 924 B total):
  {Label: "pymobiledevice3", Request: "RSDCheckin", ProtocolVersion: "2"}
  {Label: "pymobiledevice3", Request: "QueryType"}
  {Label: "pymobiledevice3", Request: "GetValue"}

iPhone → client (4 plists, 9,532 B total):
  {Request: "RSDCheckin"}               ← bare ack
  {Request: "StartService"}              ← unsolicited server push
  {Request: "QueryType", Type: "com.apple.mobile.lockdown"}
  {Request: "GetValue", Value: <88-key device dict>}

All 7 plists are plaintext XML, 4-byte BE length prefix. No TLS, no binary plist, no XpcWrapper. Trivial to implement.

Files

  • findings.md — full decode, flow tables, Services census, D.6.6 implementation sketch, open questions
  • this index.md

Raw decoded artifacts and scripts at /tmp/iosmux-d11-* — local-only, contain real device identifiers from the 88-key GetValue dict, never committed. Reproducible from the findings.md "How to reproduce" section.

Next step

D.6.6 — implement com.apple.mobile.lockdown.remote.trusted service handler in cmd/iosmux-backend/ using Go stdlib XML parsing + howett.net/plist. Source the 88-key device dict either from a tunneld-mediated snapshot at backend startup (correct path) or from synthesized constants + tunneld- advertised UDID (faster path, may fail CDS's field validation). Recommend the snapshot approach for MVP; falls back to synthesis if snapshot proves fiddly.

After D.6.6: re-run iter-8/9-style SPIKE triggers against the new backend, observe whether CDS now progresses past the handshake into actual service traffic on stream ⅓ of the greeting session, OR into additional RemoteXPC flows on other advertised ports.