From 20d3f1a217472281d32ed3d4c0528f6756548264 Mon Sep 17 00:00:00 2001 From: zqxwce Date: Tue, 17 Mar 2026 14:18:35 +0200 Subject: [PATCH] pymobiledevice3: Replace most external tools with pymobiledevice3 --- .gitmodules | 24 -- Makefile | 34 +-- README.md | 20 +- docs/README_ja.md | 30 ++- docs/README_ko.md | 30 ++- docs/README_zh.md | 28 +- requirements.txt | 3 + scripts/cfw_install.sh | 2 +- scripts/cfw_install_dev.sh | 2 +- scripts/fw_prepare.sh | 2 +- scripts/patches/libirecovery-pcc-vm.patch | 13 - scripts/pymobiledevice3_bridge.py | 299 ++++++++++++++++++++++ scripts/ramdisk_build.py | 3 +- scripts/ramdisk_send.sh | 81 +----- scripts/repos/idevicerestore | 1 - scripts/repos/libimobiledevice | 1 - scripts/repos/libimobiledevice-glue | 1 - scripts/repos/libirecovery | 1 - scripts/repos/libplist | 1 - scripts/repos/libtatsu | 1 - scripts/repos/libusbmuxd | 1 - scripts/repos/libzip | 1 - scripts/setup_libimobiledevice.sh | 165 ------------ scripts/setup_machine.sh | 60 +++-- scripts/setup_tools.sh | 16 +- scripts/setup_venv.sh | 2 + 26 files changed, 444 insertions(+), 378 deletions(-) delete mode 100644 scripts/patches/libirecovery-pcc-vm.patch create mode 100755 scripts/pymobiledevice3_bridge.py delete mode 160000 scripts/repos/idevicerestore delete mode 160000 scripts/repos/libimobiledevice delete mode 160000 scripts/repos/libimobiledevice-glue delete mode 160000 scripts/repos/libirecovery delete mode 160000 scripts/repos/libplist delete mode 160000 scripts/repos/libtatsu delete mode 160000 scripts/repos/libusbmuxd delete mode 160000 scripts/repos/libzip delete mode 100755 scripts/setup_libimobiledevice.sh diff --git a/.gitmodules b/.gitmodules index 7229601..c0d1d2e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,27 +22,3 @@ [submodule "scripts/repos/insert_dylib"] path = scripts/repos/insert_dylib url = https://github.com/tyilo/insert_dylib.git -[submodule "scripts/repos/libplist"] - path = scripts/repos/libplist - url = https://github.com/libimobiledevice/libplist.git -[submodule "scripts/repos/libimobiledevice-glue"] - path = scripts/repos/libimobiledevice-glue - url = https://github.com/libimobiledevice/libimobiledevice-glue.git -[submodule "scripts/repos/libusbmuxd"] - path = scripts/repos/libusbmuxd - url = https://github.com/libimobiledevice/libusbmuxd.git -[submodule "scripts/repos/libtatsu"] - path = scripts/repos/libtatsu - url = https://github.com/libimobiledevice/libtatsu.git -[submodule "scripts/repos/libimobiledevice"] - path = scripts/repos/libimobiledevice - url = https://github.com/libimobiledevice/libimobiledevice.git -[submodule "scripts/repos/libirecovery"] - path = scripts/repos/libirecovery - url = https://github.com/libimobiledevice/libirecovery.git -[submodule "scripts/repos/idevicerestore"] - path = scripts/repos/idevicerestore - url = https://github.com/libimobiledevice/idevicerestore.git -[submodule "scripts/repos/libzip"] - path = scripts/repos/libzip - url = https://github.com/nih-at/libzip.git diff --git a/Makefile b/Makefile index 02d7e06..408826e 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ BACKUP_INCLUDE_IPSW ?= 0 FORCE ?= 0 RESTORE_UDID ?= # UDID for restore operations RESTORE_ECID ?= # ECID for restore operations -IRECOVERY_ECID ?= # ECID for irecovery operations +IRECOVERY_ECID ?= # ECID for ramdisk send operations # ─── Build info ────────────────────────────────────────────────── GIT_HASH := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown") @@ -28,16 +28,14 @@ BUNDLE_BIN := $(BUNDLE)/Contents/MacOS/vphone-cli INFO_PLIST := sources/Info.plist ENTITLEMENTS := sources/vphone.entitlements VENV := .venv -LIMD_PREFIX := .limd TOOLS_PREFIX := .tools -IRECOVERY := $(LIMD_PREFIX)/bin/irecovery -IDEVICERESTORE := $(LIMD_PREFIX)/bin/idevicerestore +PMD3_BRIDGE := $(CURDIR)/$(SCRIPTS)/pymobiledevice3_bridge.py PYTHON := $(CURDIR)/$(VENV)/bin/python3 SWIFT_SOURCES := $(shell find sources -name '*.swift') # ─── Environment — prefer project-local binaries ──────────────── -export PATH := $(CURDIR)/$(TOOLS_PREFIX)/bin:$(CURDIR)/$(LIMD_PREFIX)/bin:$(CURDIR)/$(VENV)/bin:$(CURDIR)/.build/release:$(PATH) +export PATH := $(CURDIR)/$(TOOLS_PREFIX)/bin:$(CURDIR)/$(VENV)/bin:$(CURDIR)/.build/release:$(PATH) # ─── Default ────────────────────────────────────────────────────── .PHONY: help @@ -53,7 +51,7 @@ help: @echo " SUDO_PASSWORD=... Preload sudo credential for setup flow" @echo "" @echo "Setup (one-time):" - @echo " make setup_tools Install all tools (brew, submodule-sourced trustcache/insert_dylib/libimobiledevice, venv)" + @echo " make setup_tools Install all tools (brew, trustcache, insert_dylib, venv+pymobiledevice3)" @echo "" @echo "Build:" @echo " make build Build + sign vphone-cli" @@ -91,7 +89,7 @@ help: @echo "" @echo "Restore:" @echo " make restore_get_shsh Dump SHSH response from Apple" - @echo " make restore idevicerestore to device" + @echo " make restore Restore to device (pymobiledevice3 backend)" @echo "" @echo "Ramdisk:" @echo " make ramdisk_build Build signed SSH ramdisk" @@ -167,11 +165,7 @@ bundle: build $(INFO_PLIST) @cp -f sources/AppIcon.icns $(BUNDLE)/Contents/Resources/AppIcon.icns @cp -f $(SCRIPTS)/vphoned/signcert.p12 $(BUNDLE)/Contents/Resources/signcert.p12 @cp -f $$(command -v ldid) $(BUNDLE)/Contents/MacOS/ldid - @cp -f $$(command -v ideviceinstaller) $(BUNDLE)/Contents/MacOS/ideviceinstaller - @cp -f $$(command -v idevice_id) $(BUNDLE)/Contents/MacOS/idevice_id @codesign --force --sign - $(BUNDLE)/Contents/MacOS/ldid - @codesign --force --sign - $(BUNDLE)/Contents/MacOS/ideviceinstaller - @codesign --force --sign - $(BUNDLE)/Contents/MacOS/idevice_id @codesign --force --sign - --entitlements $(ENTITLEMENTS) $(BUNDLE_BIN) @echo " bundled → $(BUNDLE)" @@ -292,16 +286,16 @@ fw_patch_jb: patcher_build .PHONY: restore_get_shsh restore restore_get_shsh: - cd $(VM_DIR) && "$(CURDIR)/$(IDEVICERESTORE)" \ - $(if $(RESTORE_UDID),-u $(RESTORE_UDID),) \ - $(if $(RESTORE_ECID),-i $(RESTORE_ECID),) \ - -e -y ./iPhone*_Restore -t + cd $(VM_DIR) && "$(PYTHON)" "$(PMD3_BRIDGE)" restore-get-shsh \ + --vm-dir . \ + $(if $(RESTORE_UDID),--udid $(RESTORE_UDID),) \ + $(if $(RESTORE_ECID),--ecid $(RESTORE_ECID),) restore: - cd $(VM_DIR) && "$(CURDIR)/$(IDEVICERESTORE)" \ - $(if $(RESTORE_UDID),-u $(RESTORE_UDID),) \ - $(if $(RESTORE_ECID),-i $(RESTORE_ECID),) \ - -e -y ./iPhone*_Restore + cd $(VM_DIR) && "$(PYTHON)" "$(PMD3_BRIDGE)" restore-update \ + --vm-dir . \ + $(if $(RESTORE_UDID),--udid $(RESTORE_UDID),) \ + $(if $(RESTORE_ECID),--ecid $(RESTORE_ECID),) # ═══════════════════════════════════════════════════════════════════ # Ramdisk @@ -313,7 +307,7 @@ ramdisk_build: patcher_build cd $(VM_DIR) && RAMDISK_UDID="$(RAMDISK_UDID)" $(PYTHON) "$(CURDIR)/$(SCRIPTS)/ramdisk_build.py" . ramdisk_send: - cd $(VM_DIR) && IRECOVERY="$(CURDIR)/$(IRECOVERY)" IRECOVERY_ECID="$(IRECOVERY_ECID)" RAMDISK_UDID="$(RAMDISK_UDID)" RESTORE_UDID="$(RESTORE_UDID)" \ + cd $(VM_DIR) && PMD3_BRIDGE="$(PMD3_BRIDGE)" PYTHON="$(PYTHON)" IRECOVERY_ECID="$(IRECOVERY_ECID)" RAMDISK_UDID="$(RAMDISK_UDID)" RESTORE_UDID="$(RESTORE_UDID)" \ zsh "$(CURDIR)/$(SCRIPTS)/ramdisk_send.sh" # ═══════════════════════════════════════════════════════════════════ diff --git a/README.md b/README.md index a78725d..b1a5e58 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ Boot into Recovery (long press power button), open Terminal, then choose one set **Install dependencies:** ```bash -brew install aria2 ideviceinstaller wget gnu-tar openssl@3 ldid-procursus sshpass keystone autoconf automake pkg-config libtool cmake +brew install aria2 wget gnu-tar openssl@3 ldid-procursus sshpass keystone libusb ipsw ``` `scripts/fw_prepare.sh` prefers `aria2c` for faster multi-connection downloads and falls back to `curl` or `wget` when needed. @@ -110,7 +110,7 @@ make setup_machine # full automation through "First Boot" (includes r ## Manual Setup ```bash -make setup_tools # install brew deps (including aria2c), build trustcache + insert_dylib + libimobiledevice from submodule sources, create Python venv +make setup_tools # install brew deps, build trustcache + insert_dylib, create Python venv (pymobiledevice3, aria2c included) make build # build + sign vphone-cli make vm_new # create VM directory with manifest (config.plist) # options: CPU=8 MEMORY=8192 DISK_SIZE=64 @@ -146,7 +146,7 @@ make boot_dfu # boot VM in DFU mode (keep running) ```bash # terminal 2 make restore_get_shsh # fetch SHSH blob -make restore # flash firmware via idevicerestore +make restore # flash firmware via pymobiledevice3 restore backend ``` ## Install Custom Firmware @@ -164,11 +164,11 @@ sudo make ramdisk_build # build signed SSH ramdisk make ramdisk_send # send to device ``` -Once the ramdisk is running (you should see `Running server` in the output), open a **third terminal** for the iproxy tunnel, then install CFW from terminal 2: +Once the ramdisk is running (you should see `Running server` in the output), open a **third terminal** for the usbmux tunnel, then install CFW from terminal 2: ```bash # terminal 3 — keep running -iproxy 2222 22 +python3 -m pymobiledevice3 usbmux forward 2222 22 ``` ```bash @@ -211,13 +211,13 @@ shutdown -h now make boot ``` -In a separate terminal, start iproxy tunnels: +In a separate terminal, start usbmux forward tunnels: ```bash -iproxy 2222 22222 # SSH (dropbear) -iproxy 2222 22 # SSH (JB: if you install openssh-server from Sileo) -iproxy 5901 5901 # VNC -iproxy 5910 5910 # RPC +python3 -m pymobiledevice3 usbmux forward 2222 22222 # SSH (dropbear) +python3 -m pymobiledevice3 usbmux forward 2222 22 # SSH (JB: if you install openssh-server from Sileo) +python3 -m pymobiledevice3 usbmux forward 5901 5901 # VNC +python3 -m pymobiledevice3 usbmux forward 5910 5910 # RPC ``` Connect via: diff --git a/docs/README_ja.md b/docs/README_ja.md index c37d92b..b477d44 100644 --- a/docs/README_ja.md +++ b/docs/README_ja.md @@ -75,10 +75,13 @@ Apple の Virtualization.framework と PCC の研究用 VM インフラを使用 sudo amfree --path [PATH_TO_VPHONE_DIR] ``` + このリポジトリでは、`make amfidont_allow_vphone` を実行すると + `amfidont` 用のエンコード済みパスと CDHash の許可設定をまとめて行えます。 + **依存関係のインストール:** ```bash -brew install aria2 ideviceinstaller wget gnu-tar openssl@3 ldid-procursus sshpass keystone autoconf automake pkg-config libtool cmake +brew install aria2 wget gnu-tar openssl@3 ldid-procursus sshpass keystone libusb ipsw ``` `scripts/fw_prepare.sh` は高速な多重接続ダウンロードのために `aria2c` を優先し、必要に応じて `curl` または `wget` にフォールバックします。 @@ -94,12 +97,14 @@ git clone --recurse-submodules https://github.com/Lakr233/vphone-cli.git ```bash make setup_machine # 初回起動までを完全自動化(復元/ラムディスク/CFWを含む) # オプション:NONE_INTERACTIVE=1 SUDO_PASSWORD=... +# DEV=1 で開発バリアント(+ TXM entitlement/デバッグバイパス) +# JB=1 で脱獄バリアント(dev + 完全セキュリティバイパス) ``` ## 手動セットアップ ```bash -make setup_tools # brew の依存関係インストール(aria2c を含む)、submodule ソースから trustcache + insert_dylib + libimobiledevice をビルド、Python venv の作成 +make setup_tools # brew 依存関係のインストール、trustcache + insert_dylib のビルド、Python venv 作成(pymobiledevice3/aria2c を含む) make build # vphone-cli のビルド + 署名 make vm_new # VM ディレクトリとマニフェスト(config.plist)の作成 # オプション:CPU=8 MEMORY=8192 DISK_SIZE=64 @@ -135,7 +140,7 @@ make boot_dfu # DFUモードでVMを起動(実行したまま ```bash # ターミナル 2 make restore_get_shsh # SHSH blob の取得 -make restore # idevicerestore 経由でファームウェアを焼き込み +make restore # pymobiledevice3 restore バックエンドでファームウェアを焼き込み ``` ## カスタムファームウェアのインストール @@ -153,11 +158,11 @@ sudo make ramdisk_build # 署名済みSSH Ramdisk のビルド make ramdisk_send # デバイスへ送信 ``` -Ramdisk が起動したら(出力に `Running server` と表示されるはずです)、iproxy トンネル用に **3つ目のターミナル** を開き、ターミナル 2 から CFW をインストールします: +Ramdisk が起動したら(出力に `Running server` と表示されるはずです)、usbmux トンネル用に **3つ目のターミナル** を開き、ターミナル 2 から CFW をインストールします: ```bash # ターミナル 3 — 実行したままにする -iproxy 2222 22 +python3 -m pymobiledevice3 usbmux forward 2222 22 ``` ```bash @@ -200,13 +205,13 @@ shutdown -h now make boot ``` -別のターミナルで iproxy トンネルを開始します: +別のターミナルで usbmux 転送トンネルを開始します: ```bash -iproxy 2222 22222 # SSH(dropbear) -iproxy 2222 22 # SSH(脱獄版:Sileo で openssh-server を入れた場合) -iproxy 5901 5901 # VNC -iproxy 5910 5910 # RPC +python3 -m pymobiledevice3 usbmux forward 2222 22222 # SSH(dropbear) +python3 -m pymobiledevice3 usbmux forward 2222 22 # SSH(脱獄版:Sileo で openssh-server を入れた場合) +python3 -m pymobiledevice3 usbmux forward 5901 5901 # VNC +python3 -m pymobiledevice3 usbmux forward 5910 5910 # RPC ``` 以下で接続します: @@ -247,6 +252,11 @@ AMFI/デバッグ制限が正しくバイパスされていません。以下の - **方法 2(デバッグ制限のみ無効化):** 復旧モードで `csrutil enable --without debug`(完全な SIP 無効化は不要)を使用し、[`amfidont`](https://github.com/zqxwce/amfidont) または [`amfree`](https://github.com/retX0/amfree) をインストール/ロードして AMFI のその他の機能は有効のままにします。 + このリポジトリでは、`make amfidont_allow_vphone` により `amfidont` で必要なエンコード済みパスと CDHash の許可設定を自動で行えます。 + +**Q: `make boot` / `make boot_dfu` が `VZErrorDomain Code=2 "Virtualization is not available on this hardware."` で失敗します** + +ホスト自体が Apple 仮想マシン上で動作しているため、ネストされた Virtualization.framework のゲスト起動は利用できません。ネストされていない macOS 15+ ホストで実行してください。`make boot_host_preflight` ではこの状態を `Model Name: Apple Virtual Machine 1` と `kern.hv_vmm_present=1` として確認できます。現在は `boot_binary_check` により、該当ホストでは起動前に早期失敗します。 **Q: システムアプリ(App Store、メッセージなど)がダウンロード・インストールできません** diff --git a/docs/README_ko.md b/docs/README_ko.md index b369bc8..ad98072 100644 --- a/docs/README_ko.md +++ b/docs/README_ko.md @@ -75,10 +75,13 @@ PCC 리서치 VM 인프라와 Apple의 Virtualization.framework를 사용하여 sudo amfree --path [PATH_TO_VPHONE_DIR] ``` + 이 저장소에서는 `make amfidont_allow_vphone`으로 `amfidont`에 필요한 + 인코딩 경로와 CDHash 허용 설정을 한 번에 적용할 수 있습니다. + **의존성(Dependencies) 설치:** ```bash -brew install aria2 ideviceinstaller wget gnu-tar openssl@3 ldid-procursus sshpass keystone autoconf automake pkg-config libtool cmake +brew install aria2 wget gnu-tar openssl@3 ldid-procursus sshpass keystone libusb ipsw ``` `scripts/fw_prepare.sh` 는 더 빠른 다중 연결 다운로드를 위해 `aria2c` 를 우선 사용하고, 필요하면 `curl` 또는 `wget` 으로 폴백합니다. @@ -94,12 +97,14 @@ git clone --recurse-submodules https://github.com/Lakr233/vphone-cli.git ```bash make setup_machine # "First Boot"까지의 전체 과정 자동화 (복원/Ramdisk/커스텀 펌웨어 포함) # 옵션: NONE_INTERACTIVE=1 SUDO_PASSWORD=... +# DEV=1 개발 변형 (+ TXM 권한/디버그 우회) +# JB=1 탈옥 변형 (dev + 전체 보안 우회) ``` ## 수동 설정 ```bash -make setup_tools # brew 의존성 설치(aria2c 포함), submodule 소스에서 trustcache + insert_dylib + libimobiledevice 빌드, Python venv 생성 +make setup_tools # brew 의존성 설치, trustcache + insert_dylib 빌드, Python venv 생성(pymobiledevice3/aria2c 포함) make build # vphone-cli 빌드 및 서명 make vm_new # VM 디렉토리 및 매니페스트(config.plist) 생성 # 옵션: CPU=8 MEMORY=8192 DISK_SIZE=64 @@ -135,7 +140,7 @@ make boot_dfu # VM을 DFU 모드로 부팅 (계속 실행 유지 ```bash # 터미널 2 make restore_get_shsh # SHSH blob 가져오기 -make restore # idevicerestore를 통해 펌웨어 플래싱 +make restore # pymobiledevice3 restore 백엔드로 펌웨어 플래싱 ``` ## 커스텀 펌웨어 설치 @@ -153,11 +158,11 @@ sudo make ramdisk_build # 서명된 SSH 램디스크 빌드 make ramdisk_send # 장치로 전송 ``` -램디스크가 실행되면(출력에 `Running server`가 표시됨), **세 번째 터미널**을 열어 iproxy 터널을 시작한 후, 터미널 2에서 커스텀 펌웨어를 설치합니다: +램디스크가 실행되면(출력에 `Running server`가 표시됨), **세 번째 터미널**을 열어 usbmux 터널을 시작한 후, 터미널 2에서 커스텀 펌웨어를 설치합니다: ```bash # 터미널 3 — 계속 실행 유지 -iproxy 2222 22 +python3 -m pymobiledevice3 usbmux forward 2222 22 ``` ```bash @@ -200,13 +205,13 @@ shutdown -h now make boot ``` -별도의 터미널에서 iproxy 터널을 시작합니다: +별도의 터미널에서 usbmux 포워딩 터널을 시작합니다: ```bash -iproxy 2222 22222 # SSH (dropbear) -iproxy 2222 22 # SSH (탈옥: Sileo에서 openssh-server를 설치한 경우) -iproxy 5901 5901 # VNC -iproxy 5910 5910 # RPC +python3 -m pymobiledevice3 usbmux forward 2222 22222 # SSH (dropbear) +python3 -m pymobiledevice3 usbmux forward 2222 22 # SSH (탈옥: Sileo에서 openssh-server를 설치한 경우) +python3 -m pymobiledevice3 usbmux forward 5901 5901 # VNC +python3 -m pymobiledevice3 usbmux forward 5910 5910 # RPC ``` 다음을 통해 연결합니다: @@ -247,6 +252,11 @@ AMFI/디버그 제한이 올바르게 우회되지 않았습니다. 다음 중 - **방법 2 (디버그 제한만 비활성화):** 복구 모드에서 `csrutil enable --without debug`(완전한 SIP 비활성화 없음)를 사용한 다음, [`amfidont`](https://github.com/zqxwce/amfidont) 또는 [`amfree`](https://github.com/retX0/amfree)를 설치/로드하여 AMFI의 나머지 기능은 활성 상태로 유지합니다. + 이 저장소에서는 `make amfidont_allow_vphone`으로 `amfidont`에 필요한 인코딩 경로와 CDHash 허용 설정을 자동 적용할 수 있습니다. + +**Q: `make boot` / `make boot_dfu` 실행 시 `VZErrorDomain Code=2 "Virtualization is not available on this hardware."`로 실패합니다.** + +호스트 자체가 Apple 가상 머신에서 실행 중이기 때문에, 중첩된 Virtualization.framework 게스트 부팅은 지원되지 않습니다. 중첩이 아닌 macOS 15+ 호스트에서 실행하세요. `make boot_host_preflight`에서 `Model Name: Apple Virtual Machine 1` 및 `kern.hv_vmm_present=1`로 이를 확인할 수 있습니다. 현재는 이런 호스트에서 `boot_binary_check`가 VM 시작 전에 빠르게 실패 처리합니다. **Q: 시스템 앱(App Store, 메시지 등)을 다운로드하거나 설치할 수 없습니다.** diff --git a/docs/README_zh.md b/docs/README_zh.md index c757219..798ebee 100644 --- a/docs/README_zh.md +++ b/docs/README_zh.md @@ -75,10 +75,13 @@ sudo amfree --path [PATH_TO_VPHONE_DIR] ``` + 在本仓库中,可以运行 `make amfidont_allow_vphone` 一次性配置 + `amfidont` 所需的编码路径与 CDHash 允许项。 + **安装依赖:** ```bash -brew install aria2 ideviceinstaller wget gnu-tar openssl@3 ldid-procursus sshpass keystone autoconf automake pkg-config libtool cmake +brew install aria2 wget gnu-tar openssl@3 ldid-procursus sshpass keystone libusb ipsw ``` `scripts/fw_prepare.sh` 会优先使用 `aria2c` 进行更快的多连接下载,必要时再回退到 `curl` 或 `wget`。 @@ -101,7 +104,7 @@ make setup_machine # 完全自动化完成"首次启动"流程(包 ## 手动设置 ```bash -make setup_tools # 安装 brew 依赖(含 aria2c)、从 submodule 源码构建 trustcache + insert_dylib + libimobiledevice、创建 Python 虚拟环境 +make setup_tools # 安装 brew 依赖,构建 trustcache + insert_dylib,创建 Python 虚拟环境(含 pymobiledevice3/aria2c) make build # 构建并签名 vphone-cli make vm_new # 创建 VM 目录及清单文件(config.plist) # 选项:CPU=8 MEMORY=8192 DISK_SIZE=64 @@ -137,7 +140,7 @@ make boot_dfu # 以 DFU 模式启动 VM(保持运行) ```bash # 终端 2 make restore_get_shsh # 获取 SHSH blob -make restore # 通过 idevicerestore 刷写固件 +make restore # 通过 pymobiledevice3 restore 后端刷写固件 ``` ## 安装自定义固件 @@ -155,11 +158,11 @@ sudo make ramdisk_build # 构建签名的 SSH ramdisk make ramdisk_send # 发送到设备 ``` -当 ramdisk 运行后(输出中应显示 `Running server`),打开**第三个终端**运行 iproxy 隧道,然后在终端 2 安装 CFW: +当 ramdisk 运行后(输出中应显示 `Running server`),打开**第三个终端**运行 usbmux 隧道,然后在终端 2 安装 CFW: ```bash # 终端 3 —— 保持运行 -iproxy 2222 22 +python3 -m pymobiledevice3 usbmux forward 2222 22 ``` ```bash @@ -202,13 +205,13 @@ shutdown -h now make boot ``` -在另一个终端中启动 iproxy 隧道: +在另一个终端中启动 usbmux 转发隧道: ```bash -iproxy 2222 22222 # SSH(dropbear) -iproxy 2222 22 # SSH(越狱版:在 Sileo 中安装 openssh-server 后) -iproxy 5901 5901 # VNC -iproxy 5910 5910 # RPC +python3 -m pymobiledevice3 usbmux forward 2222 22222 # SSH(dropbear) +python3 -m pymobiledevice3 usbmux forward 2222 22 # SSH(越狱版:在 Sileo 中安装 openssh-server 后) +python3 -m pymobiledevice3 usbmux forward 5901 5901 # VNC +python3 -m pymobiledevice3 usbmux forward 5910 5910 # RPC ``` 连接方式: @@ -249,6 +252,11 @@ AMFI/调试限制未正确绕过。选择以下任一方式: - **方式 2(仅禁用调试限制):** 在恢复模式中使用 `csrutil enable --without debug`(不完全禁用 SIP),然后安装/加载 [`amfidont`](https://github.com/zqxwce/amfidont) 或 [`amfree`](https://github.com/retX0/amfree),保持 AMFI 其他功能不变。 + 在本仓库中,也可通过 `make amfidont_allow_vphone` 自动写入 `amfidont` 所需的编码路径与 CDHash 允许配置。 + +**问:`make boot` / `make boot_dfu` 启动后报错 `VZErrorDomain Code=2 "Virtualization is not available on this hardware."`。** + +这是因为宿主机本身运行在 Apple 虚拟机中,无法再进行嵌套 Virtualization.framework 来启动 guest。请在非嵌套的 macOS 15+ 主机上运行。可用 `make boot_host_preflight` 检查,若显示 `Model Name: Apple Virtual Machine 1` 和 `kern.hv_vmm_present=1` 即为该情况。当前版本会在此类宿主机上通过 `boot_binary_check` 在启动前快速失败。 **问:系统应用(App Store、信息等)无法下载或安装。** diff --git a/requirements.txt b/requirements.txt index f97f56b..0e9c977 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,6 @@ +typer capstone keystone-engine pyimg4 +pymobiledevice3>=9.5.0 +ipsw-parser diff --git a/scripts/cfw_install.sh b/scripts/cfw_install.sh index 9fafb7c..517a98b 100755 --- a/scripts/cfw_install.sh +++ b/scripts/cfw_install.sh @@ -246,7 +246,7 @@ mkdir -p "$TEMP_DIR" # ── Parse Cryptex paths from BuildManifest ───────────────────── echo "" echo "[*] Parsing iPhone BuildManifest for Cryptex paths..." -CRYPTEX_PATHS=$("$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" cryptex-paths "$RESTORE_DIR/BuildManifest-iPhone.plist") +CRYPTEX_PATHS=$("$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" cryptex-paths "$RESTORE_DIR/iPhone-BuildManifest.plist") CRYPTEX_SYSOS=$(echo "$CRYPTEX_PATHS" | head -1) CRYPTEX_APPOS=$(echo "$CRYPTEX_PATHS" | tail -1) echo " SystemOS: $CRYPTEX_SYSOS" diff --git a/scripts/cfw_install_dev.sh b/scripts/cfw_install_dev.sh index ffbb0e4..99d18da 100755 --- a/scripts/cfw_install_dev.sh +++ b/scripts/cfw_install_dev.sh @@ -227,7 +227,7 @@ mkdir -p "$TEMP_DIR" # ── Parse Cryptex paths from BuildManifest ───────────────────── echo "" echo "[*] Parsing iPhone BuildManifest for Cryptex paths..." -CRYPTEX_PATHS=$("$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" cryptex-paths "$RESTORE_DIR/BuildManifest-iPhone.plist") +CRYPTEX_PATHS=$("$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" cryptex-paths "$RESTORE_DIR/iPhone-BuildManifest.plist") CRYPTEX_SYSOS=$(echo "$CRYPTEX_PATHS" | head -1) CRYPTEX_APPOS=$(echo "$CRYPTEX_PATHS" | tail -1) echo " SystemOS: $CRYPTEX_SYSOS" diff --git a/scripts/fw_prepare.sh b/scripts/fw_prepare.sh index ee15770..81f2dab 100755 --- a/scripts/fw_prepare.sh +++ b/scripts/fw_prepare.sh @@ -576,7 +576,7 @@ cp "${CLOUDOS_DIR}"/Firmware/*.im4p "$IPHONE_DIR/Firmware"/ cp -n "${CLOUDOS_DIR}"/*.dmg "$IPHONE_DIR"/ 2>/dev/null || true cp -n "${CLOUDOS_DIR}"/Firmware/*.dmg.trustcache "$IPHONE_DIR/Firmware"/ 2>/dev/null || true -cp "$IPHONE_DIR/BuildManifest.plist" "$IPHONE_DIR/BuildManifest-iPhone.plist" +cp "$IPHONE_DIR/BuildManifest.plist" "$IPHONE_DIR/iPhone-BuildManifest.plist" echo "==> Generating hybrid plists ..." python3 "$SCRIPT_DIR/fw_manifest.py" "$IPHONE_DIR" "$CLOUDOS_DIR" diff --git a/scripts/patches/libirecovery-pcc-vm.patch b/scripts/patches/libirecovery-pcc-vm.patch deleted file mode 100644 index ad14266..0000000 --- a/scripts/patches/libirecovery-pcc-vm.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/src/libirecovery.c b/src/libirecovery.c -index bf9a0d6..1323891 100644 ---- a/src/libirecovery.c -+++ b/src/libirecovery.c -@@ -480,6 +480,8 @@ static struct irecv_device irecv_devices[] = { - /* Apple Vision Pro */ - { "RealityDevice14,1", "n301ap", 0x42, 0x8112, "Apple Vision Pro" }, - { "RealityDevice17,1", "n301aap", 0x42, 0x8142, "Apple Vision Pro (M5)" }, -+ /* Private Cloud Compute Research Environment */ -+ { "iPhone99,11", "vresearch101ap", 0x90, 0xFE01, "iPhone 99,11" }, - { NULL, NULL, -1, -1, NULL } - }; - diff --git a/scripts/pymobiledevice3_bridge.py b/scripts/pymobiledevice3_bridge.py new file mode 100755 index 0000000..3ee402a --- /dev/null +++ b/scripts/pymobiledevice3_bridge.py @@ -0,0 +1,299 @@ +import asyncio +import inspect +import plistlib +import sys +import time +from collections.abc import Awaitable +from pathlib import Path +from typing import Optional + +from ipsw_parser.ipsw import IPSW +from pymobiledevice3 import usbmux +from pymobiledevice3.exceptions import ( + ConnectionFailedError, + ConnectionFailedToUsbmuxdError, + IRecvNoDeviceConnectedError, + IncorrectModeError, +) +from pymobiledevice3.irecv import IRecv +from pymobiledevice3.lockdown import create_using_usbmux +from pymobiledevice3.restore.device import Device +from pymobiledevice3.restore.recovery import Behavior, Recovery +from pymobiledevice3.restore.restore import Restore +import typer + + +def parse_ecid(value: Optional[str]) -> Optional[int]: + if not value: + return None + raw = value.strip().lower() + if raw.startswith("0x"): + raw = raw[2:] + if not raw: + raise ValueError("ECID is empty") + if any(c not in "0123456789abcdef" for c in raw): + raise ValueError(f"Invalid ECID: {value}") + return int(raw, 16) + + +def normalize_udid(value: Optional[str]) -> Optional[str]: + return None if value is None else value.strip().upper() + + +def find_restore_dir(vm_dir: Path) -> Path: + candidates = sorted(p for p in vm_dir.glob("iPhone*_Restore") if p.is_dir()) + if not candidates: + raise FileNotFoundError(f"No iPhone*_Restore directory found in {vm_dir}") + if len(candidates) > 1: + raise RuntimeError( + "Multiple iPhone*_Restore directories found; keep only one active restore tree" + ) + return candidates[0] + + +async def resolve_device(ecid: Optional[int], udid: Optional[str]) -> Device: + udid_normalized = normalize_udid(udid) + + try: + devices = [d for d in await usbmux.list_devices() if d.connection_type == "USB"] + except ConnectionFailedToUsbmuxdError: + devices = [] + + for usb_device in devices: + serial = normalize_udid(getattr(usb_device, "serial", None)) + if udid_normalized and serial != udid_normalized: + continue + + try: + lockdown = await create_using_usbmux(serial=usb_device.serial, connection_type="USB") + except (ConnectionFailedError, IncorrectModeError): + continue + + lockdown_ecid = int(str(lockdown.ecid), 0) + if ecid is not None and lockdown_ecid != ecid: + continue + + return Device(lockdown=lockdown) + + if ecid is None and udid_normalized is not None: + raise RuntimeError( + "Target UDID not available over usbmux in lockdownd mode and ECID is unset; " + "set RESTORE_ECID for DFU/Recovery targeting" + ) + + return Device(irecv=IRecv(ecid=ecid)) + + +async def cmd_usbmux_list(usb_only: bool) -> None: + devices = await usbmux.list_devices() + for device in devices: + if usb_only and getattr(device, "connection_type", None) != "USB": + continue + serial = getattr(device, "serial", None) + if serial: + print(serial) + + +def wait_for_irecv(ecid: Optional[int], timeout: int, is_recovery: Optional[bool] = None) -> IRecv: + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + try: + return IRecv(ecid=ecid, timeout=2, is_recovery=is_recovery) + except (IRecvNoDeviceConnectedError, ValueError): + time.sleep(1) + mode_label = "recovery" if is_recovery else "dfu/recovery" + raise TimeoutError(f"Timed out waiting for {mode_label} endpoint") + + +def irecv_send_file(irecv: IRecv, image_path: Path) -> None: + data = image_path.read_bytes() + irecv.send_buffer(data) + + +def resolve_kernel_image(ramdisk_dir: Path) -> Path: + ramdisk_variant = ramdisk_dir / "krnl.ramdisk.img4" + if ramdisk_variant.exists(): + return ramdisk_variant + default_kernel = ramdisk_dir / "krnl.img4" + if default_kernel.exists(): + return default_kernel + raise FileNotFoundError(f"Kernel image not found in {ramdisk_dir}") + + +def cmd_ramdisk_send(ecid: Optional[int], ramdisk_dir: Path, timeout: int) -> None: + if not ramdisk_dir.is_dir(): + raise FileNotFoundError(f"Ramdisk directory not found: {ramdisk_dir}") + + kernel_img = resolve_kernel_image(ramdisk_dir) + + print(f"[*] Sending ramdisk from {ramdisk_dir}") + if kernel_img.name == "krnl.ramdisk.img4": + print(" [*] Using ramdisk kernel variant: krnl.ramdisk.img4") + + irecv = wait_for_irecv(ecid, timeout=timeout, is_recovery=False) + + # 1) DFU stage: iBSS + iBEC, then switch to recovery. + print(" [1/8] Loading iBSS...") + irecv_send_file(irecv, ramdisk_dir / "iBSS.vresearch101.RELEASE.img4") + + print(" [2/8] Loading iBEC...") + irecv_send_file(irecv, ramdisk_dir / "iBEC.vresearch101.RELEASE.img4") + irecv.send_command("go", b_request=1) + time.sleep(1) + + print(" [*] Waiting for device to reconnect in recovery...") + irecv = wait_for_irecv(ecid, timeout=timeout, is_recovery=True) + print(" [*] Reconnected in recovery") + + # 2) Recovery stage payload chain. + print(" [3/8] Loading SPTM...") + irecv_send_file(irecv, ramdisk_dir / "sptm.vresearch1.release.img4") + irecv.send_command("firmware") + + print(" [4/8] Loading TXM...") + irecv_send_file(irecv, ramdisk_dir / "txm.img4") + irecv.send_command("firmware") + + print(" [5/8] Loading trustcache...") + irecv_send_file(irecv, ramdisk_dir / "trustcache.img4") + irecv.send_command("firmware") + + print(" [6/8] Loading ramdisk...") + irecv_send_file(irecv, ramdisk_dir / "ramdisk.img4") + time.sleep(2) + irecv.send_command("ramdisk") + + print(" [7/8] Loading device tree...") + irecv_send_file(irecv, ramdisk_dir / "DeviceTree.vphone600ap.img4") + irecv.send_command("devicetree") + + print(" [8/8] Loading SEP...") + irecv_send_file(irecv, ramdisk_dir / "sep-firmware.vresearch101.RELEASE.img4") + irecv.send_command("firmware") + + print(" [*] Booting kernel...") + irecv_send_file(irecv, kernel_img) + irecv.send_command("bootx", b_request=1) + + print("[+] Boot sequence complete. Device should be booting into ramdisk.") + + +def derive_shsh_output(vm_dir: Path, ecid: Optional[int]) -> Path: + tag = f"{ecid:016X}" if ecid is not None else "auto" + return vm_dir / f"{tag}.shsh" + + +async def cmd_restore_get_shsh( + vm_dir: Path, ecid: Optional[int], udid: Optional[str], out: Optional[Path] +) -> None: + restore_dir = find_restore_dir(vm_dir) + ipsw = IPSW.create_from_path(str(restore_dir)) + device = await resolve_device(ecid, udid) + tss = await Recovery(ipsw, device, behavior=Behavior.Erase).fetch_tss_record() + + out_path = out or derive_shsh_output(vm_dir, device.get_ecid_value()) + with out_path.open("wb") as handle: + plistlib.dump(tss, handle) + + print(f"[+] SHSH saved: {out_path}") + + +async def cmd_restore_update(vm_dir: Path, ecid: Optional[int], udid: Optional[str], erase: bool) -> None: + restore_dir = find_restore_dir(vm_dir) + ipsw = IPSW.create_from_path(str(restore_dir)) + behavior = Behavior.Erase if erase else Behavior.Update + device = await resolve_device(ecid, udid) + await Restore(ipsw, device, behavior=behavior, ignore_fdr=False).update() + + +def require_ecid(value: str) -> Optional[int]: + try: + return parse_ecid(value) + except ValueError as exc: + raise typer.BadParameter(str(exc)) from exc + + +app = typer.Typer(help="pymobiledevice3 bridge for vphone", pretty_exceptions_enable=False) + + +@app.command("usbmux-list", help="List usbmux UDIDs") +def usbmux_list_command( + usb_only: bool = typer.Option( + True, + "--usb-only/--no-usb-only", + help="Include network devices with --no-usb-only.", + ), +) -> Awaitable[None]: + return cmd_usbmux_list(usb_only=usb_only) + + +@app.command("recovery-probe", help="Probe for DFU/recovery endpoint") +def recovery_probe_command( + ecid: Optional[str] = typer.Option(None, help="Hex ECID (with/without 0x)"), + timeout: int = typer.Option(2, help="Probe timeout in seconds"), +) -> None: + parsed_ecid = require_ecid(ecid) + wait_for_irecv(parsed_ecid, timeout=timeout) + + +@app.command("ramdisk-send", help="Send ramdisk chain over irecv") +def ramdisk_send_command( + ecid: Optional[str] = typer.Option(None, help="Hex ECID (with/without 0x)"), + timeout: int = typer.Option(90, help="Send timeout in seconds"), + ramdisk_dir: Path = typer.Option( + Path("Ramdisk"), + help="Ramdisk directory", + exists=False, + file_okay=False, + dir_okay=True, + ), +) -> None: + cmd_ramdisk_send(require_ecid(ecid), ramdisk_dir, timeout) + + +@app.command("restore-get-shsh", help="Fetch SHSH from prepared restore dir") +def restore_get_shsh_command( + vm_dir: Path = typer.Option( + Path("."), + help="VM directory", + exists=False, + file_okay=False, + dir_okay=True, + ), + ecid: Optional[str] = typer.Option(None, help="Hex ECID (with/without 0x)"), + udid: Optional[str] = typer.Option(None, help="Target USB UDID"), + out: Optional[Path] = typer.Option( + None, + help="Output SHSH path", + exists=False, + file_okay=True, + dir_okay=False, + ), +) -> Awaitable[None]: + return cmd_restore_get_shsh(vm_dir, require_ecid(ecid), udid, out) + + +@app.command("restore-update", help="Run erase/update restore from prepared dir") +def restore_update_command( + vm_dir: Path = typer.Option( + Path("."), + help="VM directory", + exists=False, + file_okay=False, + dir_okay=True, + ), + ecid: Optional[str] = typer.Option(None, help="Hex ECID (with/without 0x)"), + udid: Optional[str] = typer.Option(None, help="Target USB UDID"), + erase: bool = typer.Option(True, "--erase/--no-erase", help="Run update-in-place with --no-erase."), +) -> Awaitable[None]: + return cmd_restore_update(vm_dir, require_ecid(ecid), udid, erase=erase) + + +async def main(argv: list[str]) -> None: + result = app(args=argv, prog_name="pymobiledevice3_bridge.py", standalone_mode=False) + if inspect.isawaitable(result): + await result + + +if __name__ == "__main__": + asyncio.run(main(sys.argv[1:])) diff --git a/scripts/ramdisk_build.py b/scripts/ramdisk_build.py index 649476b..d72d6ef 100755 --- a/scripts/ramdisk_build.py +++ b/scripts/ramdisk_build.py @@ -634,8 +634,7 @@ def main(): sys.exit(1) # Find SHSH - shsh_dir = os.path.join(vm_dir, "shsh") - shsh_path = find_shsh(shsh_dir) + shsh_path = find_shsh(vm_dir) if not shsh_path: print(f"[-] No SHSH blob found in {shsh_dir}/") print(" Place your .shsh file in the shsh/ directory.") diff --git a/scripts/ramdisk_send.sh b/scripts/ramdisk_send.sh index 9015825..76a9755 100755 --- a/scripts/ramdisk_send.sh +++ b/scripts/ramdisk_send.sh @@ -1,5 +1,5 @@ #!/bin/zsh -# ramdisk_send.sh — Send signed ramdisk components to device via irecovery. +# ramdisk_send.sh — Send signed ramdisk components to device via pymobiledevice3. # # Usage: ./ramdisk_send.sh [ramdisk_dir] # @@ -7,12 +7,13 @@ # SPTM, TXM, trustcache, ramdisk, device tree, SEP, and kernel. set -euo pipefail -IRECOVERY="${IRECOVERY:-irecovery}" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PMD3_BRIDGE="${PMD3_BRIDGE:-${SCRIPT_DIR}/pymobiledevice3_bridge.py}" +PYTHON="${PYTHON:-python3}" IRECOVERY_ECID="${IRECOVERY_ECID:-}" RAMDISK_UDID="${RAMDISK_UDID:-${RESTORE_UDID:-}}" RAMDISK_DIR="${1:-Ramdisk}" -IRECOVERY_ARGS=() if [[ -n "$IRECOVERY_ECID" ]]; then IRECOVERY_ECID="${IRECOVERY_ECID#0x}" IRECOVERY_ECID="${IRECOVERY_ECID#0X}" @@ -21,8 +22,7 @@ if [[ -n "$IRECOVERY_ECID" ]]; then exit 1 } IRECOVERY_ECID="0x${IRECOVERY_ECID:u}" - IRECOVERY_ARGS=(-i "$IRECOVERY_ECID") - echo "[*] Using ECID selector for irecovery: ${IRECOVERY_ECID}" + echo "[*] Using ECID selector for ramdisk send: ${IRECOVERY_ECID}" fi echo "[*] Identity context for ramdisk_send:" @@ -37,72 +37,19 @@ else echo " ECID: " fi -irecovery_cmd() { - "$IRECOVERY" "${IRECOVERY_ARGS[@]}" "$@" -} - if [[ ! -d "$RAMDISK_DIR" ]]; then echo "[-] Ramdisk directory not found: $RAMDISK_DIR" echo " Run 'make ramdisk_build' first." exit 1 fi -echo "[*] Sending ramdisk from $RAMDISK_DIR ..." - -KERNEL_IMG="$RAMDISK_DIR/krnl.img4" -if [[ -f "$RAMDISK_DIR/krnl.ramdisk.img4" ]]; then - KERNEL_IMG="$RAMDISK_DIR/krnl.ramdisk.img4" - echo " [*] Using ramdisk kernel variant: $(basename "$KERNEL_IMG")" -fi -[[ -f "$KERNEL_IMG" ]] || { - echo "[-] Kernel image not found: $KERNEL_IMG" +if [[ ! -f "$PMD3_BRIDGE" ]]; then + echo "[-] pymobiledevice3 bridge script not found: $PMD3_BRIDGE" exit 1 -} - -# 1. Load iBSS + iBEC (DFU → recovery) -echo " [1/8] Loading iBSS..." -irecovery_cmd -f "$RAMDISK_DIR/iBSS.vresearch101.RELEASE.img4" - -echo " [2/8] Loading iBEC..." -irecovery_cmd -f "$RAMDISK_DIR/iBEC.vresearch101.RELEASE.img4" -irecovery_cmd -c go - -sleep 1 - -# 2. Load SPTM -echo " [3/8] Loading SPTM..." -irecovery_cmd -f "$RAMDISK_DIR/sptm.vresearch1.release.img4" -irecovery_cmd -c firmware - -# 3. Load TXM -echo " [4/8] Loading TXM..." -irecovery_cmd -f "$RAMDISK_DIR/txm.img4" -irecovery_cmd -c firmware - -# 4. Load trustcache -echo " [5/8] Loading trustcache..." -irecovery_cmd -f "$RAMDISK_DIR/trustcache.img4" -irecovery_cmd -c firmware - -# 5. Load ramdisk -echo " [6/8] Loading ramdisk..." -irecovery_cmd -f "$RAMDISK_DIR/ramdisk.img4" -sleep 2 -irecovery_cmd -c ramdisk - -# 6. Load device tree -echo " [7/8] Loading device tree..." -irecovery_cmd -f "$RAMDISK_DIR/DeviceTree.vphone600ap.img4" -irecovery_cmd -c devicetree - -# 7. Load SEP -echo " [8/8] Loading SEP..." -irecovery_cmd -f "$RAMDISK_DIR/sep-firmware.vresearch101.RELEASE.img4" -irecovery_cmd -c firmware - -# 8. Load kernel and boot -echo " [*] Booting kernel..." -irecovery_cmd -f "$KERNEL_IMG" -irecovery_cmd -c bootx - -echo "[+] Boot sequence complete. Device should be booting into ramdisk." +fi +echo "[*] Using pymobiledevice3 backend for ramdisk send" +cmd=("$PYTHON" "$PMD3_BRIDGE" ramdisk-send --ramdisk-dir "$RAMDISK_DIR") +if [[ -n "$IRECOVERY_ECID" ]]; then + cmd+=(--ecid "$IRECOVERY_ECID") +fi +"${cmd[@]}" diff --git a/scripts/repos/idevicerestore b/scripts/repos/idevicerestore deleted file mode 160000 index 405fcd1..0000000 --- a/scripts/repos/idevicerestore +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 405fcd17948ea4bd2571c76abd4451e8412b6260 diff --git a/scripts/repos/libimobiledevice b/scripts/repos/libimobiledevice deleted file mode 160000 index c4f1118..0000000 --- a/scripts/repos/libimobiledevice +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c4f111800d2b08d6c65ec075ad49c4a60e9e4309 diff --git a/scripts/repos/libimobiledevice-glue b/scripts/repos/libimobiledevice-glue deleted file mode 160000 index da770a7..0000000 --- a/scripts/repos/libimobiledevice-glue +++ /dev/null @@ -1 +0,0 @@ -Subproject commit da770a7687f35fbb981db4d7b47b1b032cd5c2c7 diff --git a/scripts/repos/libirecovery b/scripts/repos/libirecovery deleted file mode 160000 index b59ef48..0000000 --- a/scripts/repos/libirecovery +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b59ef4814525f487287da1609864f432cd79e3ed diff --git a/scripts/repos/libplist b/scripts/repos/libplist deleted file mode 160000 index 6e03a1d..0000000 --- a/scripts/repos/libplist +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6e03a1df6d1aa87c8f9e2b35f1a2ca60feca1c0e diff --git a/scripts/repos/libtatsu b/scripts/repos/libtatsu deleted file mode 160000 index 60a39f3..0000000 --- a/scripts/repos/libtatsu +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 60a39f36d719344360ec2e87563ed43f61f0530f diff --git a/scripts/repos/libusbmuxd b/scripts/repos/libusbmuxd deleted file mode 160000 index 93eb168..0000000 --- a/scripts/repos/libusbmuxd +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 93eb168bf6b07472d17781328c21df0c60300524 diff --git a/scripts/repos/libzip b/scripts/repos/libzip deleted file mode 160000 index f2bbf19..0000000 --- a/scripts/repos/libzip +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f2bbf19d9f0a1c1d3746961b8d937fd2395b873a diff --git a/scripts/setup_libimobiledevice.sh b/scripts/setup_libimobiledevice.sh deleted file mode 100755 index 657a61b..0000000 --- a/scripts/setup_libimobiledevice.sh +++ /dev/null @@ -1,165 +0,0 @@ -#!/bin/bash -# setup_libimobiledevice.sh — Build libimobiledevice toolchain (static) -# -# Produces: idevicerestore, irecovery, and related idevice* tools -# Prefix: .limd/ (override with LIMD_PREFIX env var) -# Source: scripts/repos/* git submodules (staged into .limd/src before build) -# Requires: autoconf automake libtool pkg-config cmake git - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" -REPOS_DIR="$SCRIPT_DIR/repos" - -PREFIX="${LIMD_PREFIX:-$PROJECT_DIR/.limd}" -SRC="$PREFIX/src" -LOG="$PREFIX/log" - -NPROC="$(sysctl -n hw.logicalcpu 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)" -SDKROOT="$(xcrun --sdk macosx --show-sdk-path)" - -OPENSSL_PREFIX="$(brew --prefix openssl@3 2>/dev/null || true)" -[[ -d "$OPENSSL_PREFIX" ]] || { - echo "[-] openssl@3 not found. Run: brew install openssl@3" >&2 - exit 1 -} - -export PKG_CONFIG_PATH="$PREFIX/lib/pkgconfig:$OPENSSL_PREFIX/lib/pkgconfig" -export CFLAGS="-mmacosx-version-min=14.0 -isysroot $SDKROOT" -export CPPFLAGS="$CFLAGS" -export LDFLAGS="-mmacosx-version-min=14.0" - -mkdir -p "$SRC" "$LOG" - -# ── Helpers ────────────────────────────────────────────────────── - -die() { - echo "[-] $*" >&2 - exit 1 -} - -check_tools() { - local missing=() - for cmd in autoconf automake pkg-config cmake git patch; do - command -v "$cmd" &>/dev/null || missing+=("$cmd") - done - command -v glibtoolize &>/dev/null || command -v libtoolize &>/dev/null || - missing+=("libtool(ize)") - ((${#missing[@]} == 0)) || die "Missing: ${missing[*]} — brew install ${missing[*]}" -} - -ensure_repo_submodule() { - local rel_path="$1" - local abs_path="$PROJECT_DIR/$rel_path" - - if [[ ! -e "$abs_path/.git" ]]; then - git -C "$PROJECT_DIR" submodule update --init --recursive "$rel_path" - fi -} - -stage_repo_source() { - local name="$1" - local src_dir="$REPOS_DIR/$name" - local dst_dir="$SRC/$name" - local version="" - - ensure_repo_submodule "scripts/repos/$name" - rm -rf "$dst_dir" - ditto "$src_dir" "$dst_dir" - - # Some autotools projects expect either git metadata or .tarball-version. - # Staged sources are intentionally detached from git, so preserve version info. - version="$(git -C "$src_dir" describe --tags --always 2>/dev/null || true)" - if [[ -n "$version" ]]; then - printf "%s\n" "$version" >"$dst_dir/.tarball-version" - fi - - rm -rf "$dst_dir/.git" -} - -build_lib() { - local name="$1" - shift - echo " $name" - cd "$SRC/$name" - ./autogen.sh --prefix="$PREFIX" \ - --enable-shared=no --enable-static=yes \ - "$@" >"$LOG/$name-configure.log" 2>&1 - make -j"$NPROC" >"$LOG/$name-build.log" 2>&1 - make install >"$LOG/$name-install.log" 2>&1 - cd "$SRC" -} - -# ── Preflight ──────────────────────────────────────────────────── - -check_tools -echo "Building libimobiledevice toolchain → $PREFIX" -echo "" -echo "Using submodule sources from scripts/repos/" -echo "" - -# ── 1. Core libraries ─────────────────────────────────────────── - -echo "[1/3] Core libraries (using homebrew openssl@3)" -for lib in libplist libimobiledevice-glue libusbmuxd libtatsu libimobiledevice; do - stage_repo_source "$lib" - case "$lib" in - libplist | libimobiledevice) build_lib "$lib" --without-cython ;; - *) build_lib "$lib" ;; - esac -done - -# ── 2. libirecovery (+ PCC research VM patch) ─────────────────── - -echo "[2/3] libirecovery + libzip" -stage_repo_source "libirecovery" - -# PR #150: register iPhone99,11 / vresearch101ap for PCC research VMs -if ! grep -q 'vresearch101ap' "$SRC/libirecovery/src/libirecovery.c"; then - if ! (cd "$SRC/libirecovery" && patch -p1 --batch --forward --dry-run <"$SCRIPT_DIR/patches/libirecovery-pcc-vm.patch" >/dev/null); then - die "Failed to validate libirecovery PCC patch — check context" - fi - if ! (cd "$SRC/libirecovery" && patch -p1 --batch --forward <"$SCRIPT_DIR/patches/libirecovery-pcc-vm.patch" >"$LOG/libirecovery-pcc-vm.patch.log" 2>&1); then - die "Failed to apply libirecovery PCC patch — see $LOG/libirecovery-pcc-vm.patch.log" - fi - grep -q 'vresearch101ap' "$SRC/libirecovery/src/libirecovery.c" || - die "libirecovery PCC patch command succeeded but expected marker is still missing" -fi -build_lib libirecovery - -# ── libzip (static, for idevicerestore, from submodule) ─────────── - -if [[ ! -f "$PREFIX/lib/pkgconfig/libzip.pc" ]]; then - echo " libzip" - stage_repo_source "libzip" - cmake -S "$SRC/libzip" -B "$SRC/libzip/build" \ - -DCMAKE_INSTALL_PREFIX="$PREFIX" -DCMAKE_OSX_SYSROOT="$SDKROOT" \ - -DBUILD_SHARED_LIBS=OFF -DBUILD_DOC=OFF -DBUILD_EXAMPLES=OFF \ - -DBUILD_REGRESS=OFF -DBUILD_TOOLS=OFF \ - -DENABLE_BZIP2=OFF -DENABLE_LZMA=OFF -DENABLE_ZSTD=OFF \ - -DENABLE_GNUTLS=OFF -DENABLE_MBEDTLS=OFF -DENABLE_OPENSSL=OFF \ - >"$LOG/libzip-cmake.log" 2>&1 - cmake --build "$SRC/libzip/build" -j"$NPROC" \ - >"$LOG/libzip-build.log" 2>&1 - cmake --install "$SRC/libzip/build" \ - >"$LOG/libzip-install.log" 2>&1 -fi - -# ── 3. idevicerestore ─────────────────────────────────────────── - -echo "[3/3] idevicerestore" -stage_repo_source "idevicerestore" -build_lib idevicerestore \ - libcurl_CFLAGS="-I$SDKROOT/usr/include" \ - libcurl_LIBS="-lcurl" \ - libcurl_VERSION="$(/usr/bin/curl-config --version | cut -d' ' -f2)" \ - zlib_CFLAGS="-I$SDKROOT/usr/include" \ - zlib_LIBS="-lz" \ - zlib_VERSION="1.2" - -# ── Done ───────────────────────────────────────────────────────── - -echo "" -echo "Installed to $PREFIX/bin/:" -ls "$PREFIX/bin/" | sed 's/^/ /' diff --git a/scripts/setup_machine.sh b/scripts/setup_machine.sh index e92092e..5bce50d 100755 --- a/scripts/setup_machine.sh +++ b/scripts/setup_machine.sh @@ -53,6 +53,7 @@ BOOT_ANALYSIS_TIMEOUT="${BOOT_ANALYSIS_TIMEOUT:-300}" BOOT_PROMPT_FALLBACK_TIMEOUT="${BOOT_PROMPT_FALLBACK_TIMEOUT:-60}" BOOT_BASH_PROMPT_REGEX="${BOOT_BASH_PROMPT_REGEX:-bash-[0-9]+(\.[0-9]+)+#}" BOOT_PANIC_REGEX="${BOOT_PANIC_REGEX:-panic|kernel panic|panic\\.apple\\.com|stackshot succeeded}" +PMD3_BRIDGE="${PMD3_BRIDGE:-${PROJECT_ROOT}/scripts/pymobiledevice3_bridge.py}" NONE_INTERACTIVE_RAW="${NONE_INTERACTIVE:-0}" NONE_INTERACTIVE=0 JB_MODE=0 @@ -69,6 +70,22 @@ require_cmd() { command -v "$cmd" >/dev/null 2>&1 || die "Missing required command: $cmd" } +find_python_for_pmd3() { + local candidate + for candidate in \ + "${PROJECT_ROOT}/.venv/bin/python3" \ + "$(command -v python3 2>/dev/null || true)" + do + [[ -n "$candidate" ]] || continue + [[ -x "$candidate" ]] || continue + if "$candidate" -c "import pymobiledevice3" >/dev/null 2>&1; then + echo "$candidate" + return 0 + fi + done + return 1 +} + normalize_ecid() { local ecid="$1" ecid="${ecid#0x}" @@ -124,15 +141,11 @@ load_device_identity() { } list_usbmux_udids() { - local idevice_id_bin - idevice_id_bin="${PROJECT_ROOT}/.limd/bin/idevice_id" - - if [[ ! -x "$idevice_id_bin" ]]; then - idevice_id_bin="$(command -v idevice_id || true)" - fi - [[ -x "$idevice_id_bin" ]] || return 0 - - "$idevice_id_bin" -l 2>/dev/null | tr -d '\r' | sed '/^[[:space:]]*$/d' + local pmd3_python + pmd3_python="$(find_python_for_pmd3 || true)" + [[ -x "$pmd3_python" ]] || die "pymobiledevice3 python runtime not found (run: make setup_tools)" + [[ -f "$PMD3_BRIDGE" ]] || die "Missing bridge script: $PMD3_BRIDGE" + "$pmd3_python" "$PMD3_BRIDGE" usbmux-list 2>/dev/null | tr -d '\r' | sed '/^[[:space:]]*$/d' } print_usbmux_udids() { @@ -685,8 +698,8 @@ install_brew_deps() { require_cmd brew local deps=( - ideviceinstaller wget gnu-tar openssl@3 ldid-procursus sshpass keystone autoconf automake pkg-config libtool git-lfs - python@3.13 + wget gnu-tar openssl@3 ldid-procursus sshpass keystone git-lfs + python@3.13 libusb ipsw ) echo "=== Installing Homebrew dependencies ===" @@ -787,20 +800,15 @@ wait_for_post_restore_reboot() { } wait_for_recovery() { - local irecovery="${PROJECT_ROOT}/.limd/bin/irecovery" - local -a query_args - [[ -x "$irecovery" ]] || die "irecovery not found at $irecovery" - - if [[ -n "$DEVICE_ECID" ]]; then - query_args=(-i "0x${DEVICE_ECID}") - else - query_args=() - fi + local pmd3_python + pmd3_python="$(find_python_for_pmd3 || true)" + [[ -x "$pmd3_python" ]] || die "pymobiledevice3 python runtime not found (run: make setup_tools)" + [[ -f "$PMD3_BRIDGE" ]] || die "Missing bridge script: $PMD3_BRIDGE" echo "[*] Waiting for recovery/DFU endpoint..." local i for i in {1..90}; do - if "$irecovery" "${query_args[@]}" -q >/dev/null 2>&1; then + if "$pmd3_python" "$PMD3_BRIDGE" recovery-probe --ecid "0x${DEVICE_ECID}" --timeout 2 >/dev/null 2>&1; then echo "[+] Device endpoint is reachable" return fi @@ -813,9 +821,6 @@ wait_for_recovery() { } start_iproxy() { - local iproxy_bin - iproxy_bin="${PROJECT_ROOT}/.limd/bin/iproxy" - [[ -x "$iproxy_bin" ]] || die "iproxy not found at $iproxy_bin (run: make setup_libimobiledevice)" [[ -n "$DEVICE_UDID" ]] || die "Device UDID is empty; cannot resolve iproxy target" choose_ramdisk_ssh_port @@ -833,8 +838,11 @@ start_iproxy() { mkdir -p "$LOG_DIR" : > "$IPROXY_LOG" - echo "[*] Starting iproxy ${RAMDISK_SSH_PORT} -> 22 (target_udid=${IPROXY_TARGET_UDID}, restore_udid=${DEVICE_UDID}, ecid=0x${DEVICE_ECID})..." - ("$iproxy_bin" -u "$IPROXY_TARGET_UDID" "$RAMDISK_SSH_PORT" 22 >"$IPROXY_LOG" 2>&1) & + local pmd3_python + pmd3_python="$(find_python_for_pmd3 || true)" + [[ -x "$pmd3_python" ]] || die "pymobiledevice3 python runtime not found (run: make setup_tools)" + echo "[*] Starting pymobiledevice3 usbmux forward ${RAMDISK_SSH_PORT} -> 22 (target_udid=${IPROXY_TARGET_UDID}, restore_udid=${DEVICE_UDID}, ecid=0x${DEVICE_ECID})..." + ("$pmd3_python" -m pymobiledevice3 usbmux forward --serial "$IPROXY_TARGET_UDID" "$RAMDISK_SSH_PORT" 22 >"$IPROXY_LOG" 2>&1) & IPROXY_PID=$! sleep 1 diff --git a/scripts/setup_tools.sh b/scripts/setup_tools.sh index 9f68271..54375b7 100644 --- a/scripts/setup_tools.sh +++ b/scripts/setup_tools.sh @@ -2,7 +2,8 @@ # setup_tools.sh — Install all required host tools for vphone-cli # # Installs brew packages, builds trustcache from source, -# builds insert_dylib from submodule source, builds libimobiledevice toolchain, and creates Python venv. +# builds insert_dylib from submodule source, and creates Python venv +# (including pymobiledevice3 restore/usbmux tooling). # # Run: make setup_tools @@ -24,7 +25,7 @@ ensure_repo_submodule() { # ── Brew packages ────────────────────────────────────────────── -echo "[1/5] Checking brew packages..." +echo "[1/4] Checking brew packages..." BREW_PACKAGES=(aria2 gnu-tar openssl@3 ldid-procursus sshpass) BREW_MISSING=() @@ -44,7 +45,7 @@ fi # ── Trustcache ───────────────────────────────────────────────── -echo "[2/5] trustcache" +echo "[2/4] trustcache" TRUSTCACHE_BIN="$TOOLS_PREFIX/bin/trustcache" if [[ -x "$TRUSTCACHE_BIN" ]]; then @@ -73,7 +74,7 @@ fi # ── insert_dylib ─────────────────────────────────────────────── -echo "[3/5] insert_dylib" +echo "[3/4] insert_dylib" INSERT_DYLIB_BIN="$TOOLS_PREFIX/bin/insert_dylib" if [[ -x "$INSERT_DYLIB_BIN" ]]; then @@ -87,14 +88,9 @@ else echo " Installed: $INSERT_DYLIB_BIN" fi -# ── Libimobiledevice ────────────────────────────────────────── - -echo "[4/5] libimobiledevice" -bash "$SCRIPT_DIR/setup_libimobiledevice.sh" - # ── Python venv ──────────────────────────────────────────────── -echo "[5/5] Python venv" +echo "[4/4] Python venv" zsh "$SCRIPT_DIR/setup_venv.sh" echo "" diff --git a/scripts/setup_venv.sh b/scripts/setup_venv.sh index 3ca8955..84d9de2 100755 --- a/scripts/setup_venv.sh +++ b/scripts/setup_venv.sh @@ -73,9 +73,11 @@ python3 -c " from capstone import Cs, CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN from keystone import Ks, KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN from pyimg4 import IM4P +import pymobiledevice3 print(' capstone OK') print(' keystone OK') print(' pyimg4 OK') +print(' pmd3 OK') " echo ""