chundev
日期:2026-04-12 環境:macOS (Apple Silicon) / pymobiledevice3 4.14+ / Python 3.11 → 3.13 / iPhone (iOS 16.7) + iPad (iOS 18.6)
用 pymobiledevice3 做 iOS GPS location simulation,iOS 16 和 iOS 17+ 走的是完全不同的通道,Wi-Fi 無線操作的前置條件也不一樣。iOS 16 用 legacy DtSimulateLocation(TCP lockdown 直連),iOS 17+ 用 DVT LocationSimulation(RemoteXPC tunnel)。要讓裝置拔掉 USB 仍保持 spoof,iOS 16 需要 Wi-Fi TCP lockdown + pair record 手動匯出;iOS 18.2+ 因為移除了 QUIC 支援,必須用 TCP tunnel,而 TCP tunnel 的 PSK cipher 需要 Python 3.13+ 才有 — 這個坑幾乎沒有任何文件記載。
pymobiledevice3 是 libimobiledevice 的 Python 重寫,提供對 iOS 裝置的完整開發者存取,包括 location simulation(模擬 GPS 位置)。它底層利用的是 Apple 留給 Xcode 的開發者除錯通道 — 不需要越獄,但需要 Developer Mode 啟用 + 被信任的電腦。
這篇筆記記錄了在兩台不同 iOS 版本的裝置上,從 USB-only 到完全無線操作的完整過程,包括所有踩到的坑和解法。
不管走哪條通道,最底層送到 CoreLocation 的都是同一套極簡的二進位協議:
# 設定位置(command = 0)
uint32(0) ← start simulation
uint32(len(latitude_str)) + lat ← latitude as ASCII decimal
uint32(len(longitude_str)) + lon ← longitude as ASCII decimal
# 清除位置(command = 1)
uint32(1) ← stop simulation
三個欄位就是全部 — 沒有 altitude、heading、speed、accuracy。iOS 會從連續 tick 的 (lat, lon, timestamp) 自己推算 speed 和 course。
| 屬性 | 真實 GPS | simulate-location 送的 | 能偽裝? |
|---|---|---|---|
| 位置 (lat/lon) | 真的 | 你指定的 | ✓ |
| 速度 | 感測器 | delta 推算 | ✓(自然) |
| 航向 | 感測器 | delta 推算 | ✓ |
horizontalAccuracy |
5-30 m | 固定 65 m | ✗ |
altitude |
氣壓計+GPS | 0 | ✗ |
verticalAccuracy |
真的 | -1(未知) | ✗ |
horizontalAccuracy = 65 是 iOS 對「開發者模擬位置」的 magic value,真實 GPS 不會出現這個數字。anti-cheat 系統只要查這一個欄位就能打下大部分 spoofer。
DtSimulateLocationMac iPhone
┌──────────┐ USB/WiFi ┌──────────────────┐
│ usbmuxd │ ──lockdown──→ │ lockdownd │
│ │ │ ↓ │
│ Python │ ──TCP:62078──→ │ DeveloperDiskImage│
│ script │ │ ↓ │
│ │ set(lat,lon) │ simulatelocation │
│ │ ──────────────→ │ ↓ │
│ │ │ CoreLocation │
└──────────┘ └──────────────────┘
前置條件:
/Developercom.apple.dt.simulatelocation developer service 可用特點:
set() 開一個新的 lockdown service 連線,送完資料就結束auto-mount 下載LocationSimulationMac iPad
┌──────────┐ USB/WiFi QUIC ┌──────────────────┐
│ tunneld │ ──RemoteXPC────→ │ remoted │
│ (sudo) │ tunnel │ ↓ │
│ │ │ RSD │
│ Python │ ──DVT channel──→ │ ↓ │
│ script │ │ DTXService │
│ │ set(lat,lon) │ LocationSimulation│
│ │ ───────────────→ │ ↓ │
│ │ │ CoreLocation │
└──────────┘ └──────────────────┘
前置條件:
sudo pymobiledevice3 remote tunneld 在背景跑(需要 sudo 建立 utun 介面)特點:
_remotepairing._tcp. mDNS 自動發現裝置# 啟用 Wi-Fi 連線
pymobiledevice3 lockdown wifi-connections --state on
# 啟用 Wi-Fi debugging(讓 Bonjour 廣播到真正的 Wi-Fi 介面)
# 注意:這個 key 不能用 CLI 直接設,要用 Python API
from pymobiledevice3.lockdown import create_using_usbmux
ld = await create_using_usbmux()
await ld.set_value(
domain='com.apple.mobile.wireless_lockdown',
key='EnableWifiDebugging',
value=True,
)
EnableWifiConnectionsvsEnableWifiDebugging的差異:
EnableWifiConnections:允許 Wi-Fi lockdown 連線(基礎開關)EnableWifiDebugging:讓裝置在 Wi-Fi 介面上廣播_apple-mobdev2._tcp.Bonjour 服務兩個都要開。只開
EnableWifiConnections的話,Bonjour 只會在 USB-adjacent link-local 介面上廣播(169.254.x.x、fe80::...),拔線就不見了。
iOS 16 的 Wi-Fi lockdown 需要一份 pair record 才能驗證身份。USB pairing 的 record 存在 macOS 的 /var/db/lockdown/(SIP 保護,一般使用者讀不到),Wi-Fi TCP lockdown 讀不到這個路徑。
解法:用 USB 匯出 pair record 到使用者可讀的位置。
# 從 USB 匯出
UDID=$(pymobiledevice3 usbmux list | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['Identifier'])")
pymobiledevice3 lockdown save-pair-record ~/.pymobiledevice3/${UDID}.plist
# 確認 WiFiMACAddress 欄位存在(mobdev2 用這個 match Bonjour 廣播)
python3 -c "
import plistlib
from pathlib import Path
rec = plistlib.loads(Path('~/.pymobiledevice3/${UDID}.plist').expanduser().read_bytes())
print('WiFiMACAddress:', rec.get('WiFiMACAddress'))
"
pair record 放在 ~/.pymobiledevice3/ 底下後,get_mobdev2_lockdowns() 就能自動 match。
Python 連線範例:
import plistlib
from pymobiledevice3.lockdown import create_using_tcp
UDID = "your-device-udid"
record = plistlib.loads(Path(f"~/.pymobiledevice3/{UDID}.plist").expanduser().read_bytes())
ld = await create_using_tcp(
hostname="192.168.0.113", # 裝置的 Wi-Fi IP
autopair=False,
pair_record=record,
)
# 現在可以用 DtSimulateLocation(ld).set(lat, lon)
症狀:Mac 和 iPhone 都在同一個 SSID(例如 MyWiFi),EnableWifiConnections 和 EnableWifiDebugging 都設了 True,Bonjour 也有廣播。但用 nc -z <phone_ip> 62078 完全連不到。
Root cause: Router 的 AP Client Isolation(也叫 Station Isolation、Wireless Isolation)阻止同一 SSID 的裝置互相對話。每個裝置只能跟 gateway 通訊、不能跟彼此通訊。
診斷:
# 確認 Mac 的 Wi-Fi IP
ifconfig en0 | grep "inet "
# 掃描子網路上的 iOS lockdown port
for i in $(seq 1 254); do
(timeout 0.3 nc -z 192.168.0.$i 62078 && echo "$i open") &
done; wait
# 如果找不到任何回應 → 高度懷疑 AP isolation
修法: 進 router 後台關掉 Client Isolation。或換一個沒有開 isolation 的網路。
iOS 18.2 移除了 RemoteXPC 的 QUIC 協議支援,只留 TCP tunnel。TCP tunnel 的 TLS 使用 PSK (Pre-Shared Key) cipher,而 Python 的 ssl module 到 3.13 才支援 set_psk_client_callback()。
# Python 3.11 跑 Wi-Fi tunnel 的錯誤
SSLError: [SSL: NO_CIPHERS_AVAILABLE] no ciphers available
# tunneld 的警告訊息
QuicProtocolNotSupportedError: iOS 18.2+ removed QUIC protocol support.
Use TCP instead (requires python3.13+)
修法:
# 用 uv 安裝 Python 3.13
uv python install 3.13
# 重新安裝 pymobiledevice3 綁定 Python 3.13
uv tool install pymobiledevice3 --python 3.13 --force
# 確認 PSK 支援
python3.13 -c "
import ssl
print('OpenSSL:', ssl.OPENSSL_VERSION)
print('PSK:', hasattr(ssl.SSLContext, 'set_psk_client_callback'))
"
# 預期輸出:
# OpenSSL: OpenSSL 3.x.x
# PSK: True
注意: 如果舊的 pymobiledevice3 是用 sudo 跑過 tunneld,
~/.local/share/uv/tools/pymobiledevice3/底下可能有 root 權限的 cache 檔案。需要先sudo rm -rf ~/.local/share/uv/tools/pymobiledevice3再重裝。
| iOS ≤16 | iOS 17-18.1 | iOS 18.2+ | |
|---|---|---|---|
| 通道 | legacy DtSimulateLocation |
DVT LocationSimulation |
DVT LocationSimulation |
| 需要 DDI | ✓ | ✗ | ✗ |
| 需要 tunneld | ✗ | ✓ (sudo) | ✓ (sudo) |
| Tunnel 協議 | — | QUIC | TCP only |
| Python 最低版本 | 3.8 | 3.8 | 3.13 |
| Wi-Fi 拔線 | ✓ TCP lockdown | ✓ QUIC tunnel | ✓ TCP tunnel |
| Wi-Fi 前置 | pair record 匯出 | RemoteXPC pairing | RemoteXPC pairing + Python 3.13 |
iOS 16 起,Developer Mode 預設隱藏。需要用 pymobiledevice3 先「reveal」才能在 Settings 裡看到:
# 讓「開發者模式」選項出現在 Settings
pymobiledevice3 amfi reveal-developer-mode
# 確認狀態
pymobiledevice3 amfi developer-mode-status
啟用後需要重開機 + 在裝置上再次確認 + 輸入 passcode。這是一次性的。
pymobiledevice3 mounter auto-mount 預設把 DDI 存到 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/<version>/,但一般使用者對這個路徑沒有寫入權限。
解法:用 Python API 指定可寫路徑。
from pymobiledevice3.lockdown import create_using_usbmux
from pymobiledevice3.services.mobile_image_mounter import auto_mount_developer
ld = await create_using_usbmux()
await auto_mount_developer(ld, xcode=str(Path.home() / ".pmd_xcode"))
# DDI 會下載到 ~/.pmd_xcode/Contents/Developer/.../DeveloperDiskImage.dmg
iOS 的 simulate-location 是 write-only + stateful:
set(lat, lon) 之後,CoreLocation 整個系統都被覆蓋(不只特定 app)get() — 無法從 Mac 端查詢「手機現在以為自己在哪」| 事件 | 位置是否保留 |
|---|---|
set() 後 USB 保持連接 |
✓ 保留 |
set() 後 USB 拔掉(有 Wi-Fi session) |
✓ 保留 |
set() 後 USB 拔掉(沒 Wi-Fi session) |
✗ 回歸真實 GPS |
clear() 被呼叫 |
✗ 回歸真實 GPS |
| 裝置重開機 | ✗ 回歸真實 GPS |
這就是為什麼 Wi-Fi session 很重要 — 它是「拔線不斷線」的唯一非越獄解法。
EnableWifiConnections 和 EnableWifiDebugging 是兩個不同的開關,只開前者 Bonjour 只會在 USB link-local 介面上廣播。這個差異在 pymobiledevice3 的文件裡完全沒有記載。/var/db/lockdown/ 是 SIP 保護的 — USB 的 usbmuxd(以 root 跑)能讀,Wi-Fi TCP lockdown 讀不到。用 save-pair-record CLI 匯出到使用者目錄後,放在 ~/.pymobiledevice3/<UDID>.plist 讓 get_mobdev2_lockdowns() 自動 match。QuicProtocolNotSupportedError: iOS 18.2+ removed QUIC protocol support. Use TCP instead (requires python3.13+)),没有升級指南。解法是 uv tool install pymobiledevice3 --python 3.13 --force。ping 裝置 IP 完全沒回應但 DNS-SD 找得到裝置,99% 是 router 層的 isolation。start_tunnel_over_remotepairing() 是 Wi-Fi tunnel 的入口SSLContext.set_psk_client_callback(),這是 iOS 18.2 TCP tunnel 的前提