pymobiledevice3: Replace most external tools with pymobiledevice3

This commit is contained in:
zqxwce
2026-04-03 13:47:09 +03:00
committed by zqxwce
parent 981f2cfcc9
commit 20d3f1a217
26 changed files with 444 additions and 378 deletions
-24
View File
@@ -22,27 +22,3 @@
[submodule "scripts/repos/insert_dylib"] [submodule "scripts/repos/insert_dylib"]
path = scripts/repos/insert_dylib path = scripts/repos/insert_dylib
url = https://github.com/tyilo/insert_dylib.git 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
+14 -20
View File
@@ -13,7 +13,7 @@ BACKUP_INCLUDE_IPSW ?= 0
FORCE ?= 0 FORCE ?= 0
RESTORE_UDID ?= # UDID for restore operations RESTORE_UDID ?= # UDID for restore operations
RESTORE_ECID ?= # ECID for restore operations RESTORE_ECID ?= # ECID for restore operations
IRECOVERY_ECID ?= # ECID for irecovery operations IRECOVERY_ECID ?= # ECID for ramdisk send operations
# ─── Build info ────────────────────────────────────────────────── # ─── Build info ──────────────────────────────────────────────────
GIT_HASH := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown") 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 INFO_PLIST := sources/Info.plist
ENTITLEMENTS := sources/vphone.entitlements ENTITLEMENTS := sources/vphone.entitlements
VENV := .venv VENV := .venv
LIMD_PREFIX := .limd
TOOLS_PREFIX := .tools TOOLS_PREFIX := .tools
IRECOVERY := $(LIMD_PREFIX)/bin/irecovery PMD3_BRIDGE := $(CURDIR)/$(SCRIPTS)/pymobiledevice3_bridge.py
IDEVICERESTORE := $(LIMD_PREFIX)/bin/idevicerestore
PYTHON := $(CURDIR)/$(VENV)/bin/python3 PYTHON := $(CURDIR)/$(VENV)/bin/python3
SWIFT_SOURCES := $(shell find sources -name '*.swift') SWIFT_SOURCES := $(shell find sources -name '*.swift')
# ─── Environment — prefer project-local binaries ──────────────── # ─── 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 ────────────────────────────────────────────────────── # ─── Default ──────────────────────────────────────────────────────
.PHONY: help .PHONY: help
@@ -53,7 +51,7 @@ help:
@echo " SUDO_PASSWORD=... Preload sudo credential for setup flow" @echo " SUDO_PASSWORD=... Preload sudo credential for setup flow"
@echo "" @echo ""
@echo "Setup (one-time):" @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 ""
@echo "Build:" @echo "Build:"
@echo " make build Build + sign vphone-cli" @echo " make build Build + sign vphone-cli"
@@ -91,7 +89,7 @@ help:
@echo "" @echo ""
@echo "Restore:" @echo "Restore:"
@echo " make restore_get_shsh Dump SHSH response from Apple" @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 ""
@echo "Ramdisk:" @echo "Ramdisk:"
@echo " make ramdisk_build Build signed SSH 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 sources/AppIcon.icns $(BUNDLE)/Contents/Resources/AppIcon.icns
@cp -f $(SCRIPTS)/vphoned/signcert.p12 $(BUNDLE)/Contents/Resources/signcert.p12 @cp -f $(SCRIPTS)/vphoned/signcert.p12 $(BUNDLE)/Contents/Resources/signcert.p12
@cp -f $$(command -v ldid) $(BUNDLE)/Contents/MacOS/ldid @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/ldid
@codesign --force --sign - $(BUNDLE)/Contents/MacOS/ideviceinstaller
@codesign --force --sign - $(BUNDLE)/Contents/MacOS/idevice_id
@codesign --force --sign - --entitlements $(ENTITLEMENTS) $(BUNDLE_BIN) @codesign --force --sign - --entitlements $(ENTITLEMENTS) $(BUNDLE_BIN)
@echo " bundled → $(BUNDLE)" @echo " bundled → $(BUNDLE)"
@@ -292,16 +286,16 @@ fw_patch_jb: patcher_build
.PHONY: restore_get_shsh restore .PHONY: restore_get_shsh restore
restore_get_shsh: restore_get_shsh:
cd $(VM_DIR) && "$(CURDIR)/$(IDEVICERESTORE)" \ cd $(VM_DIR) && "$(PYTHON)" "$(PMD3_BRIDGE)" restore-get-shsh \
$(if $(RESTORE_UDID),-u $(RESTORE_UDID),) \ --vm-dir . \
$(if $(RESTORE_ECID),-i $(RESTORE_ECID),) \ $(if $(RESTORE_UDID),--udid $(RESTORE_UDID),) \
-e -y ./iPhone*_Restore -t $(if $(RESTORE_ECID),--ecid $(RESTORE_ECID),)
restore: restore:
cd $(VM_DIR) && "$(CURDIR)/$(IDEVICERESTORE)" \ cd $(VM_DIR) && "$(PYTHON)" "$(PMD3_BRIDGE)" restore-update \
$(if $(RESTORE_UDID),-u $(RESTORE_UDID),) \ --vm-dir . \
$(if $(RESTORE_ECID),-i $(RESTORE_ECID),) \ $(if $(RESTORE_UDID),--udid $(RESTORE_UDID),) \
-e -y ./iPhone*_Restore $(if $(RESTORE_ECID),--ecid $(RESTORE_ECID),)
# ═══════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════
# Ramdisk # Ramdisk
@@ -313,7 +307,7 @@ ramdisk_build: patcher_build
cd $(VM_DIR) && RAMDISK_UDID="$(RAMDISK_UDID)" $(PYTHON) "$(CURDIR)/$(SCRIPTS)/ramdisk_build.py" . cd $(VM_DIR) && RAMDISK_UDID="$(RAMDISK_UDID)" $(PYTHON) "$(CURDIR)/$(SCRIPTS)/ramdisk_build.py" .
ramdisk_send: 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" zsh "$(CURDIR)/$(SCRIPTS)/ramdisk_send.sh"
# ═══════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════
+10 -10
View File
@@ -87,7 +87,7 @@ Boot into Recovery (long press power button), open Terminal, then choose one set
**Install dependencies:** **Install dependencies:**
```bash ```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. `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 ## Manual Setup
```bash ```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 build # build + sign vphone-cli
make vm_new # create VM directory with manifest (config.plist) make vm_new # create VM directory with manifest (config.plist)
# options: CPU=8 MEMORY=8192 DISK_SIZE=64 # options: CPU=8 MEMORY=8192 DISK_SIZE=64
@@ -146,7 +146,7 @@ make boot_dfu # boot VM in DFU mode (keep running)
```bash ```bash
# terminal 2 # terminal 2
make restore_get_shsh # fetch SHSH blob make restore_get_shsh # fetch SHSH blob
make restore # flash firmware via idevicerestore make restore # flash firmware via pymobiledevice3 restore backend
``` ```
## Install Custom Firmware ## Install Custom Firmware
@@ -164,11 +164,11 @@ sudo make ramdisk_build # build signed SSH ramdisk
make ramdisk_send # send to device 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 ```bash
# terminal 3 — keep running # terminal 3 — keep running
iproxy 2222 22 python3 -m pymobiledevice3 usbmux forward 2222 22
``` ```
```bash ```bash
@@ -211,13 +211,13 @@ shutdown -h now
make boot make boot
``` ```
In a separate terminal, start iproxy tunnels: In a separate terminal, start usbmux forward tunnels:
```bash ```bash
iproxy 2222 22222 # SSH (dropbear) python3 -m pymobiledevice3 usbmux forward 2222 22222 # SSH (dropbear)
iproxy 2222 22 # SSH (JB: if you install openssh-server from Sileo) python3 -m pymobiledevice3 usbmux forward 2222 22 # SSH (JB: if you install openssh-server from Sileo)
iproxy 5901 5901 # VNC python3 -m pymobiledevice3 usbmux forward 5901 5901 # VNC
iproxy 5910 5910 # RPC python3 -m pymobiledevice3 usbmux forward 5910 5910 # RPC
``` ```
Connect via: Connect via:
+20 -10
View File
@@ -75,10 +75,13 @@ Apple の Virtualization.framework と PCC の研究用 VM インフラを使用
sudo amfree --path [PATH_TO_VPHONE_DIR] sudo amfree --path [PATH_TO_VPHONE_DIR]
``` ```
このリポジトリでは、`make amfidont_allow_vphone` を実行すると
`amfidont` 用のエンコード済みパスと CDHash の許可設定をまとめて行えます。
**依存関係のインストール:** **依存関係のインストール:**
```bash ```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` にフォールバックします。 `scripts/fw_prepare.sh` は高速な多重接続ダウンロードのために `aria2c` を優先し、必要に応じて `curl` または `wget` にフォールバックします。
@@ -94,12 +97,14 @@ git clone --recurse-submodules https://github.com/Lakr233/vphone-cli.git
```bash ```bash
make setup_machine # 初回起動までを完全自動化(復元/ラムディスク/CFWを含む) make setup_machine # 初回起動までを完全自動化(復元/ラムディスク/CFWを含む)
# オプション:NONE_INTERACTIVE=1 SUDO_PASSWORD=... # オプション:NONE_INTERACTIVE=1 SUDO_PASSWORD=...
# DEV=1 で開発バリアント(+ TXM entitlement/デバッグバイパス)
# JB=1 で脱獄バリアント(dev + 完全セキュリティバイパス)
``` ```
## 手動セットアップ ## 手動セットアップ
```bash ```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 build # vphone-cli のビルド + 署名
make vm_new # VM ディレクトリとマニフェスト(config.plist)の作成 make vm_new # VM ディレクトリとマニフェスト(config.plist)の作成
# オプション:CPU=8 MEMORY=8192 DISK_SIZE=64 # オプション:CPU=8 MEMORY=8192 DISK_SIZE=64
@@ -135,7 +140,7 @@ make boot_dfu # DFUモードでVMを起動(実行したまま
```bash ```bash
# ターミナル 2 # ターミナル 2
make restore_get_shsh # SHSH blob の取得 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 # デバイスへ送信 make ramdisk_send # デバイスへ送信
``` ```
Ramdisk が起動したら(出力に `Running server` と表示されるはずです)、iproxy トンネル用に **3つ目のターミナル** を開き、ターミナル 2 から CFW をインストールします: Ramdisk が起動したら(出力に `Running server` と表示されるはずです)、usbmux トンネル用に **3つ目のターミナル** を開き、ターミナル 2 から CFW をインストールします:
```bash ```bash
# ターミナル 3 — 実行したままにする # ターミナル 3 — 実行したままにする
iproxy 2222 22 python3 -m pymobiledevice3 usbmux forward 2222 22
``` ```
```bash ```bash
@@ -200,13 +205,13 @@ shutdown -h now
make boot make boot
``` ```
別のターミナルで iproxy トンネルを開始します: 別のターミナルで usbmux 転送トンネルを開始します:
```bash ```bash
iproxy 2222 22222 # SSHdropbear python3 -m pymobiledevice3 usbmux forward 2222 22222 # SSHdropbear
iproxy 2222 22 # SSH(脱獄版:Sileo で openssh-server を入れた場合) python3 -m pymobiledevice3 usbmux forward 2222 22 # SSH(脱獄版:Sileo で openssh-server を入れた場合)
iproxy 5901 5901 # VNC python3 -m pymobiledevice3 usbmux forward 5901 5901 # VNC
iproxy 5910 5910 # RPC python3 -m pymobiledevice3 usbmux forward 5910 5910 # RPC
``` ```
以下で接続します: 以下で接続します:
@@ -247,6 +252,11 @@ AMFI/デバッグ制限が正しくバイパスされていません。以下の
- **方法 2(デバッグ制限のみ無効化):** - **方法 2(デバッグ制限のみ無効化):**
復旧モードで `csrutil enable --without debug`(完全な SIP 無効化は不要)を使用し、[`amfidont`](https://github.com/zqxwce/amfidont) または [`amfree`](https://github.com/retX0/amfree) をインストール/ロードして AMFI のその他の機能は有効のままにします。 復旧モードで `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、メッセージなど)がダウンロード・インストールできません** **Q: システムアプリ(App Store、メッセージなど)がダウンロード・インストールできません**
+20 -10
View File
@@ -75,10 +75,13 @@ PCC 리서치 VM 인프라와 Apple의 Virtualization.framework를 사용하여
sudo amfree --path [PATH_TO_VPHONE_DIR] sudo amfree --path [PATH_TO_VPHONE_DIR]
``` ```
이 저장소에서는 `make amfidont_allow_vphone`으로 `amfidont`에 필요한
인코딩 경로와 CDHash 허용 설정을 한 번에 적용할 수 있습니다.
**의존성(Dependencies) 설치:** **의존성(Dependencies) 설치:**
```bash ```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` 으로 폴백합니다. `scripts/fw_prepare.sh` 는 더 빠른 다중 연결 다운로드를 위해 `aria2c` 를 우선 사용하고, 필요하면 `curl` 또는 `wget` 으로 폴백합니다.
@@ -94,12 +97,14 @@ git clone --recurse-submodules https://github.com/Lakr233/vphone-cli.git
```bash ```bash
make setup_machine # "First Boot"까지의 전체 과정 자동화 (복원/Ramdisk/커스텀 펌웨어 포함) make setup_machine # "First Boot"까지의 전체 과정 자동화 (복원/Ramdisk/커스텀 펌웨어 포함)
# 옵션: NONE_INTERACTIVE=1 SUDO_PASSWORD=... # 옵션: NONE_INTERACTIVE=1 SUDO_PASSWORD=...
# DEV=1 개발 변형 (+ TXM 권한/디버그 우회)
# JB=1 탈옥 변형 (dev + 전체 보안 우회)
``` ```
## 수동 설정 ## 수동 설정
```bash ```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 build # vphone-cli 빌드 및 서명
make vm_new # VM 디렉토리 및 매니페스트(config.plist) 생성 make vm_new # VM 디렉토리 및 매니페스트(config.plist) 생성
# 옵션: CPU=8 MEMORY=8192 DISK_SIZE=64 # 옵션: CPU=8 MEMORY=8192 DISK_SIZE=64
@@ -135,7 +140,7 @@ make boot_dfu # VM을 DFU 모드로 부팅 (계속 실행 유지
```bash ```bash
# 터미널 2 # 터미널 2
make restore_get_shsh # SHSH blob 가져오기 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 # 장치로 전송 make ramdisk_send # 장치로 전송
``` ```
램디스크가 실행되면(출력에 `Running server`가 표시됨), **세 번째 터미널**을 열어 iproxy 터널을 시작한 후, 터미널 2에서 커스텀 펌웨어를 설치합니다: 램디스크가 실행되면(출력에 `Running server`가 표시됨), **세 번째 터미널**을 열어 usbmux 터널을 시작한 후, 터미널 2에서 커스텀 펌웨어를 설치합니다:
```bash ```bash
# 터미널 3 — 계속 실행 유지 # 터미널 3 — 계속 실행 유지
iproxy 2222 22 python3 -m pymobiledevice3 usbmux forward 2222 22
``` ```
```bash ```bash
@@ -200,13 +205,13 @@ shutdown -h now
make boot make boot
``` ```
별도의 터미널에서 iproxy 터널을 시작합니다: 별도의 터미널에서 usbmux 포워딩 터널을 시작합니다:
```bash ```bash
iproxy 2222 22222 # SSH (dropbear) python3 -m pymobiledevice3 usbmux forward 2222 22222 # SSH (dropbear)
iproxy 2222 22 # SSH (탈옥: Sileo에서 openssh-server를 설치한 경우) python3 -m pymobiledevice3 usbmux forward 2222 22 # SSH (탈옥: Sileo에서 openssh-server를 설치한 경우)
iproxy 5901 5901 # VNC python3 -m pymobiledevice3 usbmux forward 5901 5901 # VNC
iproxy 5910 5910 # RPC python3 -m pymobiledevice3 usbmux forward 5910 5910 # RPC
``` ```
다음을 통해 연결합니다: 다음을 통해 연결합니다:
@@ -247,6 +252,11 @@ AMFI/디버그 제한이 올바르게 우회되지 않았습니다. 다음 중
- **방법 2 (디버그 제한만 비활성화):** - **방법 2 (디버그 제한만 비활성화):**
복구 모드에서 `csrutil enable --without debug`(완전한 SIP 비활성화 없음)를 사용한 다음, [`amfidont`](https://github.com/zqxwce/amfidont) 또는 [`amfree`](https://github.com/retX0/amfree)를 설치/로드하여 AMFI의 나머지 기능은 활성 상태로 유지합니다. 복구 모드에서 `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, 메시지 등)을 다운로드하거나 설치할 수 없습니다.** **Q: 시스템 앱(App Store, 메시지 등)을 다운로드하거나 설치할 수 없습니다.**
+18 -10
View File
@@ -75,10 +75,13 @@
sudo amfree --path [PATH_TO_VPHONE_DIR] sudo amfree --path [PATH_TO_VPHONE_DIR]
``` ```
在本仓库中,可以运行 `make amfidont_allow_vphone` 一次性配置
`amfidont` 所需的编码路径与 CDHash 允许项。
**安装依赖:** **安装依赖:**
```bash ```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`。 `scripts/fw_prepare.sh` 会优先使用 `aria2c` 进行更快的多连接下载,必要时再回退到 `curl` 或 `wget`。
@@ -101,7 +104,7 @@ make setup_machine # 完全自动化完成"首次启动"流程(包
## 手动设置 ## 手动设置
```bash ```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 build # 构建并签名 vphone-cli
make vm_new # 创建 VM 目录及清单文件(config.plist make vm_new # 创建 VM 目录及清单文件(config.plist
# 选项:CPU=8 MEMORY=8192 DISK_SIZE=64 # 选项:CPU=8 MEMORY=8192 DISK_SIZE=64
@@ -137,7 +140,7 @@ make boot_dfu # 以 DFU 模式启动 VM(保持运行)
```bash ```bash
# 终端 2 # 终端 2
make restore_get_shsh # 获取 SHSH blob 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 # 发送到设备 make ramdisk_send # 发送到设备
``` ```
当 ramdisk 运行后(输出中应显示 `Running server`),打开**第三个终端**运行 iproxy 隧道,然后在终端 2 安装 CFW: 当 ramdisk 运行后(输出中应显示 `Running server`),打开**第三个终端**运行 usbmux 隧道,然后在终端 2 安装 CFW:
```bash ```bash
# 终端 3 —— 保持运行 # 终端 3 —— 保持运行
iproxy 2222 22 python3 -m pymobiledevice3 usbmux forward 2222 22
``` ```
```bash ```bash
@@ -202,13 +205,13 @@ shutdown -h now
make boot make boot
``` ```
在另一个终端中启动 iproxy 隧道: 在另一个终端中启动 usbmux 转发隧道:
```bash ```bash
iproxy 2222 22222 # SSHdropbear python3 -m pymobiledevice3 usbmux forward 2222 22222 # SSHdropbear
iproxy 2222 22 # SSH(越狱版:在 Sileo 中安装 openssh-server 后) python3 -m pymobiledevice3 usbmux forward 2222 22 # SSH(越狱版:在 Sileo 中安装 openssh-server 后)
iproxy 5901 5901 # VNC python3 -m pymobiledevice3 usbmux forward 5901 5901 # VNC
iproxy 5910 5910 # RPC python3 -m pymobiledevice3 usbmux forward 5910 5910 # RPC
``` ```
连接方式: 连接方式:
@@ -249,6 +252,11 @@ AMFI/调试限制未正确绕过。选择以下任一方式:
- **方式 2(仅禁用调试限制):** - **方式 2(仅禁用调试限制):**
在恢复模式中使用 `csrutil enable --without debug`(不完全禁用 SIP),然后安装/加载 [`amfidont`](https://github.com/zqxwce/amfidont) 或 [`amfree`](https://github.com/retX0/amfree),保持 AMFI 其他功能不变。 在恢复模式中使用 `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、信息等)无法下载或安装。** **问:系统应用(App Store、信息等)无法下载或安装。**
+3
View File
@@ -1,3 +1,6 @@
typer
capstone capstone
keystone-engine keystone-engine
pyimg4 pyimg4
pymobiledevice3>=9.5.0
ipsw-parser
+1 -1
View File
@@ -246,7 +246,7 @@ mkdir -p "$TEMP_DIR"
# ── Parse Cryptex paths from BuildManifest ───────────────────── # ── Parse Cryptex paths from BuildManifest ─────────────────────
echo "" echo ""
echo "[*] Parsing iPhone BuildManifest for Cryptex paths..." 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_SYSOS=$(echo "$CRYPTEX_PATHS" | head -1)
CRYPTEX_APPOS=$(echo "$CRYPTEX_PATHS" | tail -1) CRYPTEX_APPOS=$(echo "$CRYPTEX_PATHS" | tail -1)
echo " SystemOS: $CRYPTEX_SYSOS" echo " SystemOS: $CRYPTEX_SYSOS"
+1 -1
View File
@@ -227,7 +227,7 @@ mkdir -p "$TEMP_DIR"
# ── Parse Cryptex paths from BuildManifest ───────────────────── # ── Parse Cryptex paths from BuildManifest ─────────────────────
echo "" echo ""
echo "[*] Parsing iPhone BuildManifest for Cryptex paths..." 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_SYSOS=$(echo "$CRYPTEX_PATHS" | head -1)
CRYPTEX_APPOS=$(echo "$CRYPTEX_PATHS" | tail -1) CRYPTEX_APPOS=$(echo "$CRYPTEX_PATHS" | tail -1)
echo " SystemOS: $CRYPTEX_SYSOS" echo " SystemOS: $CRYPTEX_SYSOS"
+1 -1
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}"/*.dmg "$IPHONE_DIR"/ 2>/dev/null || true
cp -n "${CLOUDOS_DIR}"/Firmware/*.dmg.trustcache "$IPHONE_DIR/Firmware"/ 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 ..." echo "==> Generating hybrid plists ..."
python3 "$SCRIPT_DIR/fw_manifest.py" "$IPHONE_DIR" "$CLOUDOS_DIR" python3 "$SCRIPT_DIR/fw_manifest.py" "$IPHONE_DIR" "$CLOUDOS_DIR"
-13
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
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:]))
+1 -2
View File
@@ -634,8 +634,7 @@ def main():
sys.exit(1) sys.exit(1)
# Find SHSH # Find SHSH
shsh_dir = os.path.join(vm_dir, "shsh") shsh_path = find_shsh(vm_dir)
shsh_path = find_shsh(shsh_dir)
if not shsh_path: if not shsh_path:
print(f"[-] No SHSH blob found in {shsh_dir}/") print(f"[-] No SHSH blob found in {shsh_dir}/")
print(" Place your .shsh file in the shsh/ directory.") print(" Place your .shsh file in the shsh/ directory.")
+14 -67
View File
@@ -1,5 +1,5 @@
#!/bin/zsh #!/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] # Usage: ./ramdisk_send.sh [ramdisk_dir]
# #
@@ -7,12 +7,13 @@
# SPTM, TXM, trustcache, ramdisk, device tree, SEP, and kernel. # SPTM, TXM, trustcache, ramdisk, device tree, SEP, and kernel.
set -euo pipefail 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:-}" IRECOVERY_ECID="${IRECOVERY_ECID:-}"
RAMDISK_UDID="${RAMDISK_UDID:-${RESTORE_UDID:-}}" RAMDISK_UDID="${RAMDISK_UDID:-${RESTORE_UDID:-}}"
RAMDISK_DIR="${1:-Ramdisk}" RAMDISK_DIR="${1:-Ramdisk}"
IRECOVERY_ARGS=()
if [[ -n "$IRECOVERY_ECID" ]]; then if [[ -n "$IRECOVERY_ECID" ]]; then
IRECOVERY_ECID="${IRECOVERY_ECID#0x}" IRECOVERY_ECID="${IRECOVERY_ECID#0x}"
IRECOVERY_ECID="${IRECOVERY_ECID#0X}" IRECOVERY_ECID="${IRECOVERY_ECID#0X}"
@@ -21,8 +22,7 @@ if [[ -n "$IRECOVERY_ECID" ]]; then
exit 1 exit 1
} }
IRECOVERY_ECID="0x${IRECOVERY_ECID:u}" IRECOVERY_ECID="0x${IRECOVERY_ECID:u}"
IRECOVERY_ARGS=(-i "$IRECOVERY_ECID") echo "[*] Using ECID selector for ramdisk send: ${IRECOVERY_ECID}"
echo "[*] Using ECID selector for irecovery: ${IRECOVERY_ECID}"
fi fi
echo "[*] Identity context for ramdisk_send:" echo "[*] Identity context for ramdisk_send:"
@@ -37,72 +37,19 @@ else
echo " ECID: <unset>" echo " ECID: <unset>"
fi fi
irecovery_cmd() {
"$IRECOVERY" "${IRECOVERY_ARGS[@]}" "$@"
}
if [[ ! -d "$RAMDISK_DIR" ]]; then if [[ ! -d "$RAMDISK_DIR" ]]; then
echo "[-] Ramdisk directory not found: $RAMDISK_DIR" echo "[-] Ramdisk directory not found: $RAMDISK_DIR"
echo " Run 'make ramdisk_build' first." echo " Run 'make ramdisk_build' first."
exit 1 exit 1
fi fi
echo "[*] Sending ramdisk from $RAMDISK_DIR ..." if [[ ! -f "$PMD3_BRIDGE" ]]; then
echo "[-] pymobiledevice3 bridge script not found: $PMD3_BRIDGE"
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"
exit 1 exit 1
} fi
echo "[*] Using pymobiledevice3 backend for ramdisk send"
# 1. Load iBSS + iBEC (DFU → recovery) cmd=("$PYTHON" "$PMD3_BRIDGE" ramdisk-send --ramdisk-dir "$RAMDISK_DIR")
echo " [1/8] Loading iBSS..." if [[ -n "$IRECOVERY_ECID" ]]; then
irecovery_cmd -f "$RAMDISK_DIR/iBSS.vresearch101.RELEASE.img4" cmd+=(--ecid "$IRECOVERY_ECID")
fi
echo " [2/8] Loading iBEC..." "${cmd[@]}"
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."
-165
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/^/ /'
+34 -26
View File
@@ -53,6 +53,7 @@ BOOT_ANALYSIS_TIMEOUT="${BOOT_ANALYSIS_TIMEOUT:-300}"
BOOT_PROMPT_FALLBACK_TIMEOUT="${BOOT_PROMPT_FALLBACK_TIMEOUT:-60}" BOOT_PROMPT_FALLBACK_TIMEOUT="${BOOT_PROMPT_FALLBACK_TIMEOUT:-60}"
BOOT_BASH_PROMPT_REGEX="${BOOT_BASH_PROMPT_REGEX:-bash-[0-9]+(\.[0-9]+)+#}" 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}" 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_RAW="${NONE_INTERACTIVE:-0}"
NONE_INTERACTIVE=0 NONE_INTERACTIVE=0
JB_MODE=0 JB_MODE=0
@@ -69,6 +70,22 @@ require_cmd() {
command -v "$cmd" >/dev/null 2>&1 || die "Missing required command: $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() { normalize_ecid() {
local ecid="$1" local ecid="$1"
ecid="${ecid#0x}" ecid="${ecid#0x}"
@@ -124,15 +141,11 @@ load_device_identity() {
} }
list_usbmux_udids() { list_usbmux_udids() {
local idevice_id_bin local pmd3_python
idevice_id_bin="${PROJECT_ROOT}/.limd/bin/idevice_id" pmd3_python="$(find_python_for_pmd3 || true)"
[[ -x "$pmd3_python" ]] || die "pymobiledevice3 python runtime not found (run: make setup_tools)"
if [[ ! -x "$idevice_id_bin" ]]; then [[ -f "$PMD3_BRIDGE" ]] || die "Missing bridge script: $PMD3_BRIDGE"
idevice_id_bin="$(command -v idevice_id || true)" "$pmd3_python" "$PMD3_BRIDGE" usbmux-list 2>/dev/null | tr -d '\r' | sed '/^[[:space:]]*$/d'
fi
[[ -x "$idevice_id_bin" ]] || return 0
"$idevice_id_bin" -l 2>/dev/null | tr -d '\r' | sed '/^[[:space:]]*$/d'
} }
print_usbmux_udids() { print_usbmux_udids() {
@@ -685,8 +698,8 @@ install_brew_deps() {
require_cmd brew require_cmd brew
local deps=( local deps=(
ideviceinstaller wget gnu-tar openssl@3 ldid-procursus sshpass keystone autoconf automake pkg-config libtool git-lfs wget gnu-tar openssl@3 ldid-procursus sshpass keystone git-lfs
[email protected] [email protected] libusb ipsw
) )
echo "=== Installing Homebrew dependencies ===" echo "=== Installing Homebrew dependencies ==="
@@ -787,20 +800,15 @@ wait_for_post_restore_reboot() {
} }
wait_for_recovery() { wait_for_recovery() {
local irecovery="${PROJECT_ROOT}/.limd/bin/irecovery" local pmd3_python
local -a query_args pmd3_python="$(find_python_for_pmd3 || true)"
[[ -x "$irecovery" ]] || die "irecovery not found at $irecovery" [[ -x "$pmd3_python" ]] || die "pymobiledevice3 python runtime not found (run: make setup_tools)"
[[ -f "$PMD3_BRIDGE" ]] || die "Missing bridge script: $PMD3_BRIDGE"
if [[ -n "$DEVICE_ECID" ]]; then
query_args=(-i "0x${DEVICE_ECID}")
else
query_args=()
fi
echo "[*] Waiting for recovery/DFU endpoint..." echo "[*] Waiting for recovery/DFU endpoint..."
local i local i
for i in {1..90}; do 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" echo "[+] Device endpoint is reachable"
return return
fi fi
@@ -813,9 +821,6 @@ wait_for_recovery() {
} }
start_iproxy() { 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" [[ -n "$DEVICE_UDID" ]] || die "Device UDID is empty; cannot resolve iproxy target"
choose_ramdisk_ssh_port choose_ramdisk_ssh_port
@@ -833,8 +838,11 @@ start_iproxy() {
mkdir -p "$LOG_DIR" mkdir -p "$LOG_DIR"
: > "$IPROXY_LOG" : > "$IPROXY_LOG"
echo "[*] Starting iproxy ${RAMDISK_SSH_PORT} -> 22 (target_udid=${IPROXY_TARGET_UDID}, restore_udid=${DEVICE_UDID}, ecid=0x${DEVICE_ECID})..." local pmd3_python
("$iproxy_bin" -u "$IPROXY_TARGET_UDID" "$RAMDISK_SSH_PORT" 22 >"$IPROXY_LOG" 2>&1) & 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=$! IPROXY_PID=$!
sleep 1 sleep 1
+6 -10
View File
@@ -2,7 +2,8 @@
# setup_tools.sh — Install all required host tools for vphone-cli # setup_tools.sh — Install all required host tools for vphone-cli
# #
# Installs brew packages, builds trustcache from source, # 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 # Run: make setup_tools
@@ -24,7 +25,7 @@ ensure_repo_submodule() {
# ── Brew packages ────────────────────────────────────────────── # ── 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_PACKAGES=(aria2 gnu-tar openssl@3 ldid-procursus sshpass)
BREW_MISSING=() BREW_MISSING=()
@@ -44,7 +45,7 @@ fi
# ── Trustcache ───────────────────────────────────────────────── # ── Trustcache ─────────────────────────────────────────────────
echo "[2/5] trustcache" echo "[2/4] trustcache"
TRUSTCACHE_BIN="$TOOLS_PREFIX/bin/trustcache" TRUSTCACHE_BIN="$TOOLS_PREFIX/bin/trustcache"
if [[ -x "$TRUSTCACHE_BIN" ]]; then if [[ -x "$TRUSTCACHE_BIN" ]]; then
@@ -73,7 +74,7 @@ fi
# ── insert_dylib ─────────────────────────────────────────────── # ── insert_dylib ───────────────────────────────────────────────
echo "[3/5] insert_dylib" echo "[3/4] insert_dylib"
INSERT_DYLIB_BIN="$TOOLS_PREFIX/bin/insert_dylib" INSERT_DYLIB_BIN="$TOOLS_PREFIX/bin/insert_dylib"
if [[ -x "$INSERT_DYLIB_BIN" ]]; then if [[ -x "$INSERT_DYLIB_BIN" ]]; then
@@ -87,14 +88,9 @@ else
echo " Installed: $INSERT_DYLIB_BIN" echo " Installed: $INSERT_DYLIB_BIN"
fi fi
# ── Libimobiledevice ──────────────────────────────────────────
echo "[4/5] libimobiledevice"
bash "$SCRIPT_DIR/setup_libimobiledevice.sh"
# ── Python venv ──────────────────────────────────────────────── # ── Python venv ────────────────────────────────────────────────
echo "[5/5] Python venv" echo "[4/4] Python venv"
zsh "$SCRIPT_DIR/setup_venv.sh" zsh "$SCRIPT_DIR/setup_venv.sh"
echo "" echo ""
+2
View File
@@ -73,9 +73,11 @@ python3 -c "
from capstone import Cs, CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN from capstone import Cs, CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN
from keystone import Ks, KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN from keystone import Ks, KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN
from pyimg4 import IM4P from pyimg4 import IM4P
import pymobiledevice3
print(' capstone OK') print(' capstone OK')
print(' keystone OK') print(' keystone OK')
print(' pyimg4 OK') print(' pyimg4 OK')
print(' pmd3 OK')
" "
echo "" echo ""