pymobiledevice3: Replace most external tools with pymobiledevice3

This commit is contained in:
zqxwce
2026-03-17 14:18:35 +02:00
committed by zqxwce
parent 981f2cfcc9
commit 20d3f1a217
26 changed files with 444 additions and 378 deletions

24
.gitmodules vendored
View File

@@ -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

View File

@@ -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"
# ═══════════════════════════════════════════════════════════════════

View File

@@ -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:

View File

@@ -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 # SSHdropbear
iproxy 2222 22 # SSH脱獄版Sileo で openssh-server を入れた場合)
iproxy 5901 5901 # VNC
iproxy 5910 5910 # RPC
python3 -m pymobiledevice3 usbmux forward 2222 22222 # SSHdropbear
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、メッセージなどがダウンロード・インストールできません**

View File

@@ -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, 메시지 등)을 다운로드하거나 설치할 수 없습니다.**

View File

@@ -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 # SSHdropbear
iproxy 2222 22 # SSH越狱版在 Sileo 中安装 openssh-server 后)
iproxy 5901 5901 # VNC
iproxy 5910 5910 # RPC
python3 -m pymobiledevice3 usbmux forward 2222 22222 # SSHdropbear
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、信息等无法下载或安装。**

View File

@@ -1,3 +1,6 @@
typer
capstone
keystone-engine
pyimg4
pymobiledevice3>=9.5.0
ipsw-parser

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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 }
};

299
scripts/pymobiledevice3_bridge.py Executable file
View File

@@ -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:]))

View File

@@ -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.")

View File

@@ -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: <unset>"
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[@]}"

View File

@@ -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/^/ /'

View File

@@ -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

View File

@@ -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 ""

View File

@@ -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 ""