Share Notes

chundev

View the Project on GitHub latteouka/share-notes

pymobiledevice3 Wi-Fi GPS Spoofing 完整攻略 — iOS 16 到 iOS 18 的三條通道與踩坑記錄

日期:2026-04-12 環境:macOS (Apple Silicon) / pymobiledevice3 4.14+ / Python 3.11 → 3.13 / iPhone (iOS 16.7) + iPad (iOS 18.6)


TL;DR

用 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 到完全無線操作的完整過程,包括所有踩到的坑和解法。


iOS Location Simulation 的 Wire Protocol

不管走哪條通道,最底層送到 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。


兩條通道的架構差異

iOS ≤16:Legacy DtSimulateLocation

Mac                          iPhone
┌──────────┐    USB/WiFi     ┌──────────────────┐
│ usbmuxd  │ ──lockdown──→  │ lockdownd        │
│          │                 │   ↓               │
│ Python   │ ──TCP:62078──→  │ DeveloperDiskImage│
│ script   │                 │   ↓               │
│          │   set(lat,lon)  │ simulatelocation  │
│          │ ──────────────→ │   ↓               │
│          │                 │ CoreLocation      │
└──────────┘                 └──────────────────┘

前置條件:

  1. USB trust pairing(「信任此電腦」)
  2. Developer Mode 啟用(iOS 16+ 才有這個開關,預設隱藏)
  3. DeveloperDiskImage (DDI) 掛載到 /Developer
  4. com.apple.dt.simulatelocation developer service 可用

特點:

iOS 17+:DVT LocationSimulation

Mac                              iPad
┌──────────┐   USB/WiFi QUIC    ┌──────────────────┐
│ tunneld  │ ──RemoteXPC────→   │ remoted          │
│  (sudo)  │   tunnel           │   ↓               │
│          │                    │ RSD               │
│ Python   │ ──DVT channel──→  │   ↓               │
│ script   │                    │ DTXService        │
│          │   set(lat,lon)     │ LocationSimulation│
│          │ ───────────────→   │   ↓               │
│          │                    │ CoreLocation      │
└──────────┘                    └──────────────────┘

前置條件:

  1. USB trust pairing
  2. Developer Mode 啟用
  3. sudo pymobiledevice3 remote tunneld 在背景跑(需要 sudo 建立 utun 介面)
  4. RemoteXPC pairing(首次 USB 連線時自動完成)
  5. 不需要 DDI 掛載

特點:


Wi-Fi 無線操作的完整設定流程

共通前置

  1. Mac 和 iOS 裝置在同一個 Wi-Fi 網路
  2. Router 沒有 AP Client Isolation(很多 router 預設開啟)
  3. 裝置的 lockdown Wi-Fi 設定已啟用
# 啟用 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,
)

EnableWifiConnections vs EnableWifiDebugging 的差異:

  • EnableWifiConnections:允許 Wi-Fi lockdown 連線(基礎開關)
  • EnableWifiDebugging:讓裝置在 Wi-Fi 介面上廣播 _apple-mobdev2._tcp. Bonjour 服務

兩個都要開。只開 EnableWifiConnections 的話,Bonjour 只會在 USB-adjacent link-local 介面上廣播(169.254.x.xfe80::...),拔線就不見了。

iOS 16 Wi-Fi:Pair Record 匯出法

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)

AP Client Isolation 踩坑

症狀:Mac 和 iPhone 都在同一個 SSID(例如 MyWiFi),EnableWifiConnectionsEnableWifiDebugging 都設了 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+:Python 3.13 PSK Cipher 需求

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 版本 × 通道 × Wi-Fi 支援矩陣

  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

Developer Mode 啟用(iOS 16+)

iOS 16 起,Developer Mode 預設隱藏。需要用 pymobiledevice3 先「reveal」才能在 Settings 裡看到:

# 讓「開發者模式」選項出現在 Settings
pymobiledevice3 amfi reveal-developer-mode

# 確認狀態
pymobiledevice3 amfi developer-mode-status

啟用後需要重開機 + 在裝置上再次確認 + 輸入 passcode。這是一次性的。


DDI 掛載的權限問題(iOS 16)

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 的行為

iOS 的 simulate-location 是 write-only + stateful

事件 位置是否保留
set() 後 USB 保持連接 ✓ 保留
set() 後 USB 拔掉(有 Wi-Fi session) ✓ 保留
set() 後 USB 拔掉(沒 Wi-Fi session) ✗ 回歸真實 GPS
clear() 被呼叫 ✗ 回歸真實 GPS
裝置重開機 ✗ 回歸真實 GPS

這就是為什麼 Wi-Fi session 很重要 — 它是「拔線不斷線」的唯一非越獄解法。


學到的事


參考資料