mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-09-02 02:34:29 +00:00
general: Remove SSH-ramdisk CFW install path; host-mount is the sole flow
The ramdisk-based install (build/send an SSH ramdisk, iproxy-forward, then
push CFW files over SSH and flip the boot snapshot with snaputil in-VM) has
been fully replaced by the host-mount path: cfw_install_host.sh mounts the
VM's Disk.img on the host, places every file locally, and flips the boot
snapshot offline via tools/apfs_snap_rename.py. This removes all remaining
ramdisk generation, delivery, and usage — no legacy fallback.
Deleted:
- scripts/ramdisk_build.py, scripts/ramdisk_send.sh
Renamed:
- scripts/cfw_host_mode.sh -> scripts/cfw_transport.sh (was a conditional
"host-mode override"; now the sole, unconditionally-sourced transport)
setup_machine.sh: drop the USE_RAMDISK_CFW=1 branch and every iproxy/ramdisk
helper (usbmux UDID resolution, port picking, start/stop iproxy, wait-for-
ramdisk-ssh), all RAMDISK_*/IPROXY_* vars, the cleanup() iproxy handling, and
the orphaned cfw_install_target var. Only the host-mount cfw_install_host call
remains.
cfw_install{,_dev,_jb,_exp}.sh: delete the SSH transport (SSH_* vars, SSH_OPTS,
sshpass prereq/_sshpass/_ssh_retry, ssh_cmd/scp_to/scp_from/remote_file_exists/
remote_mount, wait_for_device_ssh_ready) and the dead ramdisk-mechanism body
blocks (snaputil snapshot flip, dropbearkey host-key pre-generation, halt-over-
SSH, CFW_SKIP_HALT). The transport is now sourced unconditionally from
cfw_transport.sh. dropbear -R generates host keys at first boot; the offline
apfs_snap_rename.py does the boot-source flip. Dropped the vestigial
CFW_HOST_MODE gate.
Also: pymobiledevice3_bridge.py (ramdisk-send command already gone), Makefile
(ramdisk targets/help/IRECOVERY_ECID removed), README + ja/ko/zh (install flow
rewritten to host-mount), AGENTS.md/CLAUDE.md architecture tree, and stale
comments in vphone_jb_setup.sh, VPhoneCLI.swift, apfs_snap_rename.py,
cfw_patch_post_restore_dt.py.
Verified booting via make setup_machine.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XZVS9oqVNmHJzpb8mFtKJx
This commit is contained in:
committed by
zqxwce
co-authored by
Claude Opus 4.8
parent
321c7117b3
commit
fcc30e1657
@@ -98,11 +98,11 @@ scripts/
|
||||
├── patches/ # Build-time patches (libirecovery)
|
||||
├── fw_prepare.sh # Download IPSWs, merge cloudOS into iPhone
|
||||
├── fw_manifest.py # Generate hybrid BuildManifest/Restore plists
|
||||
├── ramdisk_build.py # Build SSH ramdisk with trustcache (reuses Swift patch-component for TXM/base kernel)
|
||||
├── ramdisk_send.sh # Send ramdisk to device via irecovery
|
||||
├── cfw_install.sh # Install CFW (regular)
|
||||
├── cfw_install_dev.sh # Regular + rpcserver daemon
|
||||
├── cfw_install_jb.sh # Regular + jetsam fix + procursus
|
||||
├── cfw_install_exp.sh # JB + experimental research patches (hv_vmm rename, DT identity)
|
||||
├── cfw_install_host.sh # Host-mount CFW driver (attaches Disk.img, VM off; re-execs sudo)
|
||||
├── vm_create.sh # Create VM directory
|
||||
├── setup_machine.sh # Full automation (setup → first boot)
|
||||
├── setup_tools.sh # Install deps, build toolchain from submodules, create venv
|
||||
@@ -111,6 +111,9 @@ scripts/
|
||||
├── setup_libimobiledevice.sh # Build libimobiledevice stack from scripts/repos submodules
|
||||
└── tail_jb_patch_logs.sh # Tail JB patch log output
|
||||
|
||||
tools/
|
||||
└── apfs_snap_rename.py # Offline APFS boot-snapshot flip (used by cfw_install_host.sh)
|
||||
|
||||
research/ # Detailed firmware/patch documentation
|
||||
```
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ BACKUP_INCLUDE_IPSW ?= 0
|
||||
FORCE ?= 0
|
||||
RESTORE_UDID ?= # UDID for restore operations
|
||||
RESTORE_ECID ?= # ECID for restore operations
|
||||
IRECOVERY_ECID ?= # ECID for ramdisk send operations
|
||||
|
||||
# ─── Build info ──────────────────────────────────────────────────
|
||||
GIT_HASH := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||||
@@ -121,17 +120,12 @@ help:
|
||||
@echo " make restore Restore to device (pymobiledevice3 backend)"
|
||||
@echo " make restore_offline Restore offline — decrypts AEA images in place, uses cached .shsh blob"
|
||||
@echo ""
|
||||
@echo "Ramdisk:"
|
||||
@echo " make ramdisk_build Build signed SSH ramdisk"
|
||||
@echo " make ramdisk_send Send ramdisk to device"
|
||||
@echo ""
|
||||
@echo "CFW:"
|
||||
@echo " make cfw_install Install CFW mods via SSH"
|
||||
@echo " make cfw_install_dev Install CFW mods via SSH (dev mode)"
|
||||
@echo "CFW (host-mount install; VM must be off, re-execs sudo):"
|
||||
@echo " make cfw_install Install base CFW mods"
|
||||
@echo " make cfw_install_dev Install CFW mods (dev mode)"
|
||||
@echo " make cfw_install_jb Install CFW + JB extensions (jetsam/procursus/basebin)"
|
||||
@echo " make cfw_install_exp Install CFW + JB + EXP experimental (hv_vmm rename, post-restore DT, build spoof)"
|
||||
@echo " make cfw_install_host Ramdisk-free install: host-mount files + offline snapshot flip"
|
||||
@echo " Options: VARIANT=regular|dev|jb|exp (default exp) SPOOF_BUILD=<id> (exp)"
|
||||
@echo " make cfw_install_host Select variant: VARIANT=regular|dev|jb|exp (default exp) SPOOF_BUILD=<id> (exp)"
|
||||
@echo ""
|
||||
@echo "Variables: VM_DIR=$(VM_DIR) CPU=$(CPU) MEMORY=$(MEMORY) DISK_SIZE=$(DISK_SIZE)"
|
||||
|
||||
@@ -463,19 +457,6 @@ restore_offline:
|
||||
$(if $(RESTORE_UDID),--udid $(RESTORE_UDID),) \
|
||||
--ecid "$$ECID"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# Ramdisk
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
.PHONY: ramdisk_build ramdisk_send
|
||||
|
||||
ramdisk_build: patcher_build
|
||||
cd $(VM_DIR) && RAMDISK_UDID="$(RAMDISK_UDID)" $(PYTHON) "$(CURDIR)/$(SCRIPTS)/ramdisk_build.py" .
|
||||
|
||||
ramdisk_send:
|
||||
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"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# CFW
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
@@ -483,19 +464,19 @@ ramdisk_send:
|
||||
.PHONY: cfw_install cfw_install_dev cfw_install_jb cfw_install_exp cfw_install_host
|
||||
|
||||
cfw_install:
|
||||
cd $(VM_DIR) && $(if $(SSH_PORT),SSH_PORT="$(SSH_PORT)") _VPHONE_PATH="$$PATH" zsh "$(CURDIR)/$(SCRIPTS)/cfw_install.sh" .
|
||||
$(MAKE) cfw_install_host VARIANT=regular
|
||||
|
||||
cfw_install_dev:
|
||||
cd $(VM_DIR) && $(if $(SSH_PORT),SSH_PORT="$(SSH_PORT)") _VPHONE_PATH="$$PATH" zsh "$(CURDIR)/$(SCRIPTS)/cfw_install_dev.sh" .
|
||||
$(MAKE) cfw_install_host VARIANT=dev
|
||||
|
||||
cfw_install_jb:
|
||||
cd $(VM_DIR) && $(if $(SSH_PORT),SSH_PORT="$(SSH_PORT)") _VPHONE_PATH="$$PATH" zsh "$(CURDIR)/$(SCRIPTS)/cfw_install_jb.sh" .
|
||||
$(MAKE) cfw_install_host VARIANT=jb
|
||||
|
||||
cfw_install_exp:
|
||||
cd $(VM_DIR) && $(if $(SSH_PORT),SSH_PORT="$(SSH_PORT)") $(if $(SPOOF_BUILD),SPOOF_BUILD="$(SPOOF_BUILD)") _VPHONE_PATH="$$PATH" zsh "$(CURDIR)/$(SCRIPTS)/cfw_install_exp.sh" .
|
||||
$(MAKE) cfw_install_host VARIANT=exp SPOOF_BUILD="$(SPOOF_BUILD)"
|
||||
|
||||
# Ramdisk-free CFW install: place files via host mount + flip boot snapshot
|
||||
# offline. No boot_dfu/ramdisk_send/iproxy/SSH. VM must be off. Re-execs sudo.
|
||||
# CFW install: place files via host mount + flip the boot snapshot offline.
|
||||
# VM must be off; re-execs under sudo.
|
||||
# Options: VARIANT=regular|dev|jb|exp (default exp) SPOOF_BUILD=<id> (exp)
|
||||
cfw_install_host:
|
||||
$(if $(SPOOF_BUILD),SPOOF_BUILD="$(SPOOF_BUILD)") zsh "$(CURDIR)/$(SCRIPTS)/cfw_install_host.sh" --variant $(if $(VARIANT),$(VARIANT),exp) "$(VM_DIR_ABS)"
|
||||
|
||||
@@ -115,7 +115,7 @@ git clone --recurse-submodules https://github.com/Lakr233/vphone-cli.git
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
make setup_machine # full automation through "First Boot" (includes restore/ramdisk/CFW)
|
||||
make setup_machine # full automation through "First Boot" (includes restore/CFW)
|
||||
# options: NON_INTERACTIVE=1 SUDO_PASSWORD=...
|
||||
# LESS=1 for patchless variant (- AMFI, SSV, Img4, TXM bypasses)
|
||||
# DEV=1 for dev variant (+ TXM entitlement/debug bypasses)
|
||||
@@ -182,29 +182,15 @@ make restore # flash firmware via pymobiledevice3 restore backe
|
||||
|
||||
## Install Custom Firmware
|
||||
|
||||
Stop the DFU boot in terminal 1 (Ctrl+C), then boot into DFU again for the ramdisk:
|
||||
Once the restore completes, stop the DFU boot in terminal 1 (Ctrl+C) so the VM is
|
||||
fully powered off. The installer mounts the VM's `Disk.img` on the host, places
|
||||
all CFW files, and flips the boot snapshot offline — no DFU, ramdisk, or SSH — so
|
||||
it needs exclusive access to the disk.
|
||||
|
||||
```bash
|
||||
# terminal 1
|
||||
make boot_dfu # keep running
|
||||
```
|
||||
|
||||
```bash
|
||||
# terminal 2
|
||||
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 usbmux tunnel, then install CFW from terminal 2:
|
||||
|
||||
```bash
|
||||
# terminal 3 — keep running
|
||||
python3 -m pymobiledevice3 usbmux forward 2222 22
|
||||
```
|
||||
|
||||
```bash
|
||||
# terminal 2
|
||||
# terminal 2 (re-execs under sudo automatically)
|
||||
make cfw_install
|
||||
# or: make cfw_install_dev # development variant
|
||||
# or: make cfw_install_jb # jailbreak variant
|
||||
# or: make cfw_install_exp # experimental variant (JB + research stack)
|
||||
# or: SPOOF_BUILD=23F77 make cfw_install_exp # additionally rewrite ProductBuildVersion
|
||||
@@ -212,7 +198,7 @@ make cfw_install
|
||||
|
||||
## First Boot
|
||||
|
||||
Stop the DFU boot in terminal 1 (Ctrl+C), then:
|
||||
With the DFU boot stopped and CFW installed, boot the VM normally:
|
||||
|
||||
```bash
|
||||
make boot
|
||||
|
||||
+5
-22
@@ -103,7 +103,7 @@ git clone --recurse-submodules https://github.com/Lakr233/vphone-cli.git
|
||||
## クイックスタート
|
||||
|
||||
```bash
|
||||
make setup_machine # 初回起動までを完全自動化(復元/ラムディスク/CFWを含む)
|
||||
make setup_machine # 初回起動までを完全自動化(復元/CFWを含む)
|
||||
# オプション:NON_INTERACTIVE=1 SUDO_PASSWORD=...
|
||||
# LESS=1 で patchless バリアント(- AMFI, SSV, Img4, TXM バイパス)
|
||||
# DEV=1 で開発バリアント(+ TXM entitlement/デバッグバイパス)
|
||||
@@ -170,29 +170,12 @@ make restore # pymobiledevice3 restore バックエンドでフ
|
||||
|
||||
## カスタムファームウェアのインストール
|
||||
|
||||
ターミナル 1 の DFU 起動を停止し(Ctrl+C)、Ramdisk 用に再び DFU で起動します:
|
||||
復元が完了したら、ターミナル 1 の DFU 起動を停止(Ctrl+C)して VM を完全に電源オフにします。インストーラは VM の `Disk.img` をホスト側でマウントし、すべての CFW ファイルを配置してブートスナップショットをオフラインで切り替えます(DFU / Ramdisk / SSH は不要)。そのためディスクへの排他アクセスが必要です。
|
||||
|
||||
```bash
|
||||
# ターミナル 1
|
||||
make boot_dfu # 実行したままにする
|
||||
```
|
||||
|
||||
```bash
|
||||
# ターミナル 2
|
||||
sudo make ramdisk_build # 署名済みSSH Ramdisk のビルド
|
||||
make ramdisk_send # デバイスへ送信
|
||||
```
|
||||
|
||||
Ramdisk が起動したら(出力に `Running server` と表示されるはずです)、usbmux トンネル用に **3つ目のターミナル** を開き、ターミナル 2 から CFW をインストールします:
|
||||
|
||||
```bash
|
||||
# ターミナル 3 — 実行したままにする
|
||||
python3 -m pymobiledevice3 usbmux forward 2222 22
|
||||
```
|
||||
|
||||
```bash
|
||||
# ターミナル 2
|
||||
# ターミナル 2(自動的に sudo で再実行されます)
|
||||
make cfw_install
|
||||
# または: make cfw_install_dev # 開発バリアント
|
||||
# または: make cfw_install_jb # 脱獄バリアント
|
||||
# または: make cfw_install_exp # 実験バリアント(脱獄 + リサーチパッチスタック)
|
||||
# または: SPOOF_BUILD=23F77 make cfw_install_exp # ProductBuildVersion も書き換え
|
||||
@@ -200,7 +183,7 @@ make cfw_install
|
||||
|
||||
## 初回起動
|
||||
|
||||
ターミナル 1 の DFU 起動を停止し(Ctrl+C)、以下を実行します:
|
||||
DFU 起動を停止し CFW をインストールしたら、VM を通常起動します:
|
||||
|
||||
```bash
|
||||
make boot
|
||||
|
||||
+5
-22
@@ -103,7 +103,7 @@ git clone --recurse-submodules https://github.com/Lakr233/vphone-cli.git
|
||||
## 빠른 시작
|
||||
|
||||
```bash
|
||||
make setup_machine # "First Boot"까지의 전체 과정 자동화 (복원/Ramdisk/커스텀 펌웨어 포함)
|
||||
make setup_machine # "First Boot"까지의 전체 과정 자동화 (복원/커스텀 펌웨어 포함)
|
||||
# 옵션: NON_INTERACTIVE=1 SUDO_PASSWORD=...
|
||||
# LESS=1 Patchless 변형 (- AMFI, SSV, Img4, TXM 우회)
|
||||
# DEV=1 개발 변형 (+ TXM 권한/디버그 우회)
|
||||
@@ -170,29 +170,12 @@ make restore # pymobiledevice3 restore 백엔드로 펌웨어
|
||||
|
||||
## 커스텀 펌웨어 설치
|
||||
|
||||
터미널 1의 DFU 부팅을 중단(Ctrl+C)한 다음, 램디스크를 위해 다시 DFU로 부팅합니다:
|
||||
복원이 완료되면 터미널 1의 DFU 부팅을 중단(Ctrl+C)하여 VM을 완전히 종료합니다. 설치 프로그램은 VM의 `Disk.img`를 호스트에 마운트하여 모든 CFW 파일을 배치하고 부팅 스냅샷을 오프라인으로 전환합니다(DFU / 램디스크 / SSH 불필요). 따라서 디스크에 대한 독점 액세스가 필요합니다.
|
||||
|
||||
```bash
|
||||
# 터미널 1
|
||||
make boot_dfu # 계속 실행 유지
|
||||
```
|
||||
|
||||
```bash
|
||||
# 터미널 2
|
||||
sudo make ramdisk_build # 서명된 SSH 램디스크 빌드
|
||||
make ramdisk_send # 장치로 전송
|
||||
```
|
||||
|
||||
램디스크가 실행되면(출력에 `Running server`가 표시됨), **세 번째 터미널**을 열어 usbmux 터널을 시작한 후, 터미널 2에서 커스텀 펌웨어를 설치합니다:
|
||||
|
||||
```bash
|
||||
# 터미널 3 — 계속 실행 유지
|
||||
python3 -m pymobiledevice3 usbmux forward 2222 22
|
||||
```
|
||||
|
||||
```bash
|
||||
# 터미널 2
|
||||
# 터미널 2 (자동으로 sudo로 재실행됨)
|
||||
make cfw_install
|
||||
# 또는: make cfw_install_dev # 개발 변형
|
||||
# 또는: make cfw_install_jb # 탈옥 변형
|
||||
# 또는: make cfw_install_exp # 실험 변형 (탈옥 + 연구 패치 스택)
|
||||
# 또는: SPOOF_BUILD=23F77 make cfw_install_exp # 추가로 ProductBuildVersion 재작성
|
||||
@@ -200,7 +183,7 @@ make cfw_install
|
||||
|
||||
## 첫 부팅
|
||||
|
||||
터미널 1의 DFU 부팅을 중단(Ctrl+C)한 후 다음을 실행합니다:
|
||||
DFU 부팅을 중단하고 CFW를 설치한 후, VM을 정상 부팅합니다:
|
||||
|
||||
```bash
|
||||
make boot
|
||||
|
||||
+5
-22
@@ -103,7 +103,7 @@ git clone --recurse-submodules https://github.com/Lakr233/vphone-cli.git
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
make setup_machine # 完全自动化完成"首次启动"流程(包含 restore/ramdisk/CFW)
|
||||
make setup_machine # 完全自动化完成"首次启动"流程(包含 restore/CFW)
|
||||
# 选项:NON_INTERACTIVE=1 SUDO_PASSWORD=...
|
||||
# LESS=1 patchless 变体(- AMFI、SSV、Img4、TXM 绕过)
|
||||
# DEV=1 开发变体(+ TXM 权限/调试绕过)
|
||||
@@ -170,29 +170,12 @@ make restore # 通过 pymobiledevice3 restore 后端刷写固
|
||||
|
||||
## 安装自定义固件
|
||||
|
||||
在终端 1 中停止 DFU 引导(Ctrl+C),然后再次进入 DFU,用于 ramdisk:
|
||||
恢复完成后,在终端 1 中停止 DFU 引导(Ctrl+C),使 VM 完全关机。安装程序会在主机上挂载 VM 的 `Disk.img`,放置所有 CFW 文件,并离线切换启动快照(无需 DFU / ramdisk / SSH),因此需要对磁盘的独占访问。
|
||||
|
||||
```bash
|
||||
# 终端 1
|
||||
make boot_dfu # 保持运行
|
||||
```
|
||||
|
||||
```bash
|
||||
# 终端 2
|
||||
sudo make ramdisk_build # 构建签名的 SSH ramdisk
|
||||
make ramdisk_send # 发送到设备
|
||||
```
|
||||
|
||||
当 ramdisk 运行后(输出中应显示 `Running server`),打开**第三个终端**运行 usbmux 隧道,然后在终端 2 安装 CFW:
|
||||
|
||||
```bash
|
||||
# 终端 3 —— 保持运行
|
||||
python3 -m pymobiledevice3 usbmux forward 2222 22
|
||||
```
|
||||
|
||||
```bash
|
||||
# 终端 2
|
||||
# 终端 2(会自动通过 sudo 重新执行)
|
||||
make cfw_install
|
||||
# 或:make cfw_install_dev # 开发变体
|
||||
# 或:make cfw_install_jb # 越狱变体
|
||||
# 或:make cfw_install_exp # 实验变体(越狱 + 研究补丁栈)
|
||||
# 或:SPOOF_BUILD=23F77 make cfw_install_exp # 同时改写 ProductBuildVersion
|
||||
@@ -200,7 +183,7 @@ make cfw_install
|
||||
|
||||
## 首次启动
|
||||
|
||||
在终端 1 中停止 DFU 引导(Ctrl+C),然后:
|
||||
停止 DFU 引导并完成 CFW 安装后,正常启动 VM:
|
||||
|
||||
```bash
|
||||
make boot
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
# cfw_host_mode.sh — sourced by cfw_install*.sh when CFW_HOST_MODE=1.
|
||||
#
|
||||
# Replaces the SSH-to-ramdisk transport with local operations on the image
|
||||
# volumes mounted on the host (by the host driver). The rest of each
|
||||
# installer's logic (cfw.py patching, ldid signing, ipsw/aea cryptex decrypt,
|
||||
# hdiutil DMG mounts, xcrun builds, DSC/DT patchers) is already host-side and
|
||||
# runs unchanged. Must run as root. The boot-source flip the ramdisk did with
|
||||
# `snaputil` is done separately, offline, by tools/apfs_snap_rename.py.
|
||||
#
|
||||
# The host root (/) is read-only (SSV), so device paths /mnt1,/mnt3,/mnt5 are
|
||||
# remapped onto a writable scratch base (CFW_HOST_MNT). Only those exact
|
||||
# device tokens are remapped, so host source paths (e.g. .../mnt_sysos) are
|
||||
# left untouched.
|
||||
: "${CFW_HOST_CONTAINER:?CFW_HOST_MODE=1 but CFW_HOST_CONTAINER (host apfs container disk, e.g. disk20) unset}"
|
||||
CFW_HOST_MNT="${CFW_HOST_MNT:-/private/tmp/cfwhost}"
|
||||
/bin/mkdir -p "$CFW_HOST_MNT"
|
||||
_HOST_TAR="$(command -v gtar 2>/dev/null || echo /opt/homebrew/bin/gtar)"
|
||||
|
||||
_map() { # remap device /mntN tokens -> $CFW_HOST_MNT/mntN
|
||||
local s="$1"
|
||||
s="${s//\/mnt1/$CFW_HOST_MNT/mnt1}"
|
||||
s="${s//\/mnt2/$CFW_HOST_MNT/mnt2}"
|
||||
s="${s//\/mnt3/$CFW_HOST_MNT/mnt3}"
|
||||
s="${s//\/mnt5/$CFW_HOST_MNT/mnt5}"
|
||||
printf '%s' "$s"
|
||||
}
|
||||
|
||||
ssh_cmd() {
|
||||
local c="$*"
|
||||
case "$c" in
|
||||
*snaputil*) return 0 ;; # flip done offline
|
||||
*dropbearkey*) return 0 ;; # host keys made at first boot
|
||||
*dropbear_rsa_host_key*|*dropbear_ecdsa_host_key*) return 0 ;; # skip chmod of keys not created
|
||||
*/sbin/halt*) return 0 ;; # no VM to halt
|
||||
esac
|
||||
c="${c//\/usr\/bin\/tar/$_HOST_TAR}" # macOS bsdtar lacks GNU flags
|
||||
/bin/sh -c "$(_map "$c")"
|
||||
}
|
||||
scp_to() { /bin/cp -R "$(_map "$1")" "$(_map "$2")"; }
|
||||
scp_from() { /bin/cp "$(_map "$1")" "$(_map "$2")"; }
|
||||
remote_file_exists() { [[ -e "$(_map "$1")" ]]; }
|
||||
remote_mount() {
|
||||
local dev="$1" mnt opts="${3:-rw}"
|
||||
mnt="$(_map "$2")"
|
||||
local slice="${dev##*disk1}" # /dev/disk1sN -> sN
|
||||
local hostdev="/dev/${CFW_HOST_CONTAINER}${slice}"
|
||||
/bin/mkdir -p "$mnt"
|
||||
/sbin/mount | /usr/bin/grep -q " on $mnt " && return 0
|
||||
/sbin/mount_apfs -o "$opts" "$hostdev" "$mnt" 2>/dev/null || true
|
||||
/sbin/mount | /usr/bin/grep -q " on $mnt " || die "host mount failed: $hostdev -> $mnt"
|
||||
}
|
||||
wait_for_device_ssh_ready() { :; }
|
||||
+98
-203
@@ -1,14 +1,17 @@
|
||||
#!/bin/zsh
|
||||
# cfw_install.sh — Install base CFW modifications on vphone via SSH ramdisk.
|
||||
# cfw_install.sh — Install base CFW modifications on vphone.
|
||||
#
|
||||
# Installs Cryptexes, patches system binaries, installs jailbreak tools
|
||||
# and configures LaunchDaemons for persistent SSH/VNC access.
|
||||
#
|
||||
# Files are placed directly on the VM's Disk.img volumes, which cfw_install_host.sh
|
||||
# attaches and mounts on the host; the VM must be off.
|
||||
#
|
||||
# Safe to run multiple times — always patches from original .bak files,
|
||||
# keeps decrypted Cryptex DMGs cached, handles already-mounted filesystems.
|
||||
#
|
||||
# Prerequisites:
|
||||
# - Device booted into SSH ramdisk (make ramdisk_send)
|
||||
# - VM restored (make restore) and powered off
|
||||
# - `ipsw` tool installed (brew install blacktop/tap/ipsw)
|
||||
# - `aea` tool available (macOS 12+)
|
||||
# - Python: make setup_venv && source .venv/bin/activate
|
||||
@@ -22,7 +25,6 @@ set -euo pipefail
|
||||
|
||||
VM_DIR="${1:-.}"
|
||||
SCRIPT_DIR="${0:a:h}"
|
||||
CFW_SKIP_HALT="${CFW_SKIP_HALT:-0}"
|
||||
|
||||
# Resolve absolute paths
|
||||
VM_DIR="$(cd "$VM_DIR" && pwd)"
|
||||
@@ -48,22 +50,6 @@ CFW_INPUT="cfw_input"
|
||||
CFW_ARCHIVE="cfw_input.tar.zst"
|
||||
TEMP_DIR="$VM_DIR/.cfw_temp"
|
||||
|
||||
SSH_PORT="${SSH_PORT:-2222}"
|
||||
SSH_PASS="alpine"
|
||||
SSH_USER="root"
|
||||
SSH_HOST="localhost"
|
||||
SSH_RETRY="${SSH_RETRY:-3}"
|
||||
CFW_SSH_READY_TIMEOUT="${CFW_SSH_READY_TIMEOUT:-60}"
|
||||
CFW_SSH_READY_INTERVAL="${CFW_SSH_READY_INTERVAL:-2}"
|
||||
SSHPASS_BIN=""
|
||||
SSH_OPTS=(
|
||||
-o StrictHostKeyChecking=no
|
||||
-o UserKnownHostsFile=/dev/null
|
||||
-o PreferredAuthentications=password
|
||||
-o ConnectTimeout=30
|
||||
-q
|
||||
)
|
||||
|
||||
# ── Helpers ─────────────────────────────────────────────────────
|
||||
die() {
|
||||
echo "[-] $*" >&2
|
||||
@@ -72,75 +58,10 @@ die() {
|
||||
|
||||
check_prerequisites() {
|
||||
local missing=()
|
||||
command -v sshpass &>/dev/null || missing+=("sshpass")
|
||||
command -v ldid &>/dev/null || missing+=("ldid (brew install ldid-procursus)")
|
||||
if ((${#missing[@]} > 0)); then
|
||||
die "Missing required tools: ${missing[*]}. Run: make setup_tools"
|
||||
fi
|
||||
SSHPASS_BIN="$(command -v sshpass)"
|
||||
}
|
||||
|
||||
wait_for_device_ssh_ready() {
|
||||
local timeout interval elapsed
|
||||
timeout="$CFW_SSH_READY_TIMEOUT"
|
||||
interval="$CFW_SSH_READY_INTERVAL"
|
||||
elapsed=0
|
||||
|
||||
[[ "$timeout" == <-> ]] || die "CFW_SSH_READY_TIMEOUT must be an integer (seconds)"
|
||||
[[ "$interval" == <-> ]] || die "CFW_SSH_READY_INTERVAL must be an integer (seconds)"
|
||||
(( timeout > 0 )) || die "CFW_SSH_READY_TIMEOUT must be > 0"
|
||||
(( interval > 0 )) || die "CFW_SSH_READY_INTERVAL must be > 0"
|
||||
|
||||
echo "[*] Waiting for ramdisk SSH on ${SSH_USER}@${SSH_HOST}:${SSH_PORT} (timeout=${timeout}s)..."
|
||||
while (( elapsed < timeout )); do
|
||||
if "$SSHPASS_BIN" -p "$SSH_PASS" ssh \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
-o PreferredAuthentications=password \
|
||||
-o ConnectTimeout=5 \
|
||||
-q \
|
||||
-p "$SSH_PORT" \
|
||||
"$SSH_USER@$SSH_HOST" "echo ready" >/dev/null 2>&1
|
||||
then
|
||||
echo "[+] Ramdisk SSH is reachable"
|
||||
return
|
||||
fi
|
||||
sleep "$interval"
|
||||
(( elapsed += interval ))
|
||||
done
|
||||
|
||||
die "Ramdisk SSH is not reachable on ${SSH_HOST}:${SSH_PORT}. Make sure ramdisk is running (make ramdisk_send) and iproxy is forwarding ${SSH_PORT}->22."
|
||||
}
|
||||
|
||||
_sshpass() {
|
||||
"$SSHPASS_BIN" -p "$SSH_PASS" "$@"
|
||||
}
|
||||
|
||||
_ssh_retry() {
|
||||
local attempt rc label
|
||||
label=${2:-cmd}
|
||||
for ((attempt = 1; attempt <= SSH_RETRY; attempt++)); do
|
||||
"$@" && return 0
|
||||
rc=$?
|
||||
[[ $rc -ne 255 ]] && return $rc # real command failure — don't retry
|
||||
echo " [${label}] connection lost (attempt $attempt/$SSH_RETRY), retrying in 3s..." >&2
|
||||
sleep 3
|
||||
done
|
||||
return 255
|
||||
}
|
||||
|
||||
ssh_cmd() {
|
||||
_ssh_retry _sshpass ssh "${SSH_OPTS[@]}" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "$@"
|
||||
}
|
||||
scp_to() {
|
||||
_ssh_retry _sshpass scp -q "${SSH_OPTS[@]}" -P "$SSH_PORT" -r "$1" "$SSH_USER@$SSH_HOST:$2"
|
||||
}
|
||||
scp_from() {
|
||||
_ssh_retry _sshpass scp -q "${SSH_OPTS[@]}" -P "$SSH_PORT" "$SSH_USER@$SSH_HOST:$1" "$2"
|
||||
}
|
||||
|
||||
remote_file_exists() {
|
||||
ssh_cmd "test -f '$1'" 2>/dev/null
|
||||
}
|
||||
|
||||
ldid_sign() {
|
||||
@@ -187,19 +108,6 @@ assert_mount_under_vm() {
|
||||
esac
|
||||
}
|
||||
|
||||
# Mount device filesystem, tolerate already-mounted
|
||||
remote_mount() {
|
||||
local dev="$1" mnt="$2" opts="${3:-rw}"
|
||||
ssh_cmd "/bin/mkdir -p $mnt"
|
||||
if ssh_cmd "/sbin/mount | /usr/bin/grep -q ' on $mnt '"; then
|
||||
return 0
|
||||
fi
|
||||
ssh_cmd "/sbin/mount_apfs -o $opts $dev $mnt 2>/dev/null || true"
|
||||
if ! ssh_cmd "/sbin/mount | /usr/bin/grep -q ' on $mnt '"; then
|
||||
die "Failed to mount $dev at $mnt (opts=$opts). Make sure the ramdisk was booted with the expected patched kernel."
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Find restore directory ─────────────────────────────────────
|
||||
find_restore_dir() {
|
||||
for dir in "$VM_DIR"/iPhone*_Restore; do
|
||||
@@ -242,9 +150,24 @@ cleanup_on_exit() {
|
||||
}
|
||||
trap cleanup_on_exit EXIT
|
||||
|
||||
# Host-mode transport override (CFW_HOST_MODE=1): run install against image
|
||||
# volumes mounted locally on the host instead of a device over SSH.
|
||||
[[ -n "${CFW_HOST_MODE:-}" ]] && source "$SCRIPT_DIR/cfw_host_mode.sh"
|
||||
# The VM's Disk.img is attached on the host by cfw_install_host.sh; its APFS
|
||||
# volumes are mounted here and every file is placed with plain cp/chmod/etc.
|
||||
# (the VM is off — nothing runs "on the device").
|
||||
: "${CFW_HOST_CONTAINER:?CFW_HOST_CONTAINER unset — run via cfw_install_host.sh}"
|
||||
HOST_MNT="${CFW_HOST_MNT:-/private/tmp/cfwhost}"
|
||||
MNT1="$HOST_MNT/mnt1" # disk1s1 (System / rootfs)
|
||||
MNT3="$HOST_MNT/mnt3" # disk1s3
|
||||
TAR="$(command -v gtar 2>/dev/null || echo /opt/homebrew/bin/gtar)" # macOS bsdtar lacks GNU tar flags
|
||||
mkdir -p "$HOST_MNT"
|
||||
|
||||
# Mount an APFS volume of the attached image container at a host mount point.
|
||||
mount_vol() { # mount_vol <slice, e.g. s1> <mountpoint> [opts]
|
||||
local dev="/dev/${CFW_HOST_CONTAINER}$1" mnt="$2" opts="${3:-rw}"
|
||||
/bin/mkdir -p "$mnt"
|
||||
/sbin/mount | /usr/bin/grep -q " on $mnt " && return 0
|
||||
/sbin/mount_apfs -o "$opts" "$dev" "$mnt" 2>/dev/null || true
|
||||
/sbin/mount | /usr/bin/grep -q " on $mnt " || die "mount failed: $dev -> $mnt"
|
||||
}
|
||||
|
||||
# ════════════════════════════════════════════════════════════════
|
||||
# Main
|
||||
@@ -260,7 +183,6 @@ setup_cfw_input
|
||||
INPUT_DIR="$VM_DIR/$CFW_INPUT"
|
||||
echo "[+] Input resources: $INPUT_DIR"
|
||||
check_prerequisites
|
||||
wait_for_device_ssh_ready
|
||||
|
||||
mkdir -p "$TEMP_DIR"
|
||||
|
||||
@@ -277,45 +199,26 @@ echo " AppOS: $CRYPTEX_APPOS"
|
||||
echo ""
|
||||
echo "[1/7] Installing Cryptex (SystemOS + AppOS)..."
|
||||
|
||||
# Mount device rootfs first to check existing state
|
||||
# Mount the image's System volume first to check existing state
|
||||
echo " Mounting rootfs rw..."
|
||||
remote_mount /dev/disk1s1 /mnt1
|
||||
mount_vol s1 "$MNT1"
|
||||
|
||||
# Rename APFS update snapshot to orig-fs (idempotent)
|
||||
echo " Checking APFS snapshots..."
|
||||
SNAP_LIST=$(ssh_cmd "snaputil -l /mnt1 2>/dev/null" || true)
|
||||
if echo "$SNAP_LIST" | grep -q "^orig-fs$"; then
|
||||
echo " Snapshot 'orig-fs' already exists, skipping rename"
|
||||
else
|
||||
UPDATE_SNAP=$(echo "$SNAP_LIST" | awk '/^com\.apple\.os\.update-/{print; exit}')
|
||||
if [[ -n "$UPDATE_SNAP" ]]; then
|
||||
echo " Renaming snapshot: $UPDATE_SNAP -> orig-fs"
|
||||
ssh_cmd "snaputil -n '$UPDATE_SNAP' orig-fs /mnt1"
|
||||
# Verify rename succeeded
|
||||
if ! ssh_cmd "snaputil -l /mnt1 2>/dev/null" | grep -q "^orig-fs$"; then
|
||||
die "Failed to rename snapshot to orig-fs"
|
||||
fi
|
||||
echo " Snapshot renamed, remounting..."
|
||||
ssh_cmd "/sbin/umount /mnt1"
|
||||
remote_mount /dev/disk1s1 /mnt1
|
||||
echo " [+] Snapshot renamed to orig-fs"
|
||||
else
|
||||
echo " No com.apple.os.update- snapshot found, skipping"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if Cryptexes already exist on device (skip the slow copy if so)
|
||||
CRYPTEX_OS_COUNT=$(ssh_cmd "/bin/ls /mnt1/System/Cryptexes/OS/ 2>/dev/null | /usr/bin/wc -l" | tr -d ' ')
|
||||
CRYPTEX_APP_COUNT=$(ssh_cmd "/bin/ls /mnt1/System/Cryptexes/App/ 2>/dev/null | /usr/bin/wc -l" | tr -d ' ')
|
||||
# Check if Cryptexes already exist on the volume (skip the slow copy if so).
|
||||
# ls only runs when the dir exists, so its failure can't trip set -e/pipefail
|
||||
# (on a fresh install these dirs don't exist yet → counts stay 0).
|
||||
CRYPTEX_OS_COUNT=0
|
||||
CRYPTEX_APP_COUNT=0
|
||||
[[ -d "$MNT1/System/Cryptexes/OS" ]] && CRYPTEX_OS_COUNT=$(/bin/ls "$MNT1/System/Cryptexes/OS/" | /usr/bin/wc -l | tr -d ' ')
|
||||
[[ -d "$MNT1/System/Cryptexes/App" ]] && CRYPTEX_APP_COUNT=$(/bin/ls "$MNT1/System/Cryptexes/App/" | /usr/bin/wc -l | tr -d ' ')
|
||||
|
||||
if [[ "${CRYPTEX_OS_COUNT:-0}" -gt 0 && "${CRYPTEX_APP_COUNT:-0}" -gt 0 ]]; then
|
||||
echo " [*] Cryptexes already installed (OS=${CRYPTEX_OS_COUNT} entries, App=${CRYPTEX_APP_COUNT} entries), skipping"
|
||||
|
||||
# Still ensure dyld symlinks exist
|
||||
ssh_cmd "/bin/ln -sf ../../../System/Cryptexes/OS/System/Library/Caches/com.apple.dyld \
|
||||
/mnt1/System/Library/Caches/com.apple.dyld"
|
||||
ssh_cmd "/bin/ln -sf ../../../../System/Cryptexes/OS/System/DriverKit/System/Library/dyld \
|
||||
/mnt1/System/DriverKit/System/Library/dyld"
|
||||
/bin/ln -sf ../../../System/Cryptexes/OS/System/Library/Caches/com.apple.dyld \
|
||||
$MNT1/System/Library/Caches/com.apple.dyld
|
||||
/bin/ln -sf ../../../../System/Cryptexes/OS/System/DriverKit/System/Library/dyld \
|
||||
$MNT1/System/DriverKit/System/Library/dyld
|
||||
|
||||
echo " [+] Cryptex skipped (already present)"
|
||||
else
|
||||
@@ -356,21 +259,21 @@ else
|
||||
host_hdiutil attach -mountpoint "$MNT_APPOS" "$APPOS_DMG" -nobrowse -owners off \
|
||||
|| die "Failed to mount AppOS DMG. Run 'sudo -v' in a terminal and retry if hdiutil needs administrator privileges."
|
||||
|
||||
ssh_cmd "/bin/rm -rf /mnt1/System/Cryptexes/App /mnt1/System/Cryptexes/OS"
|
||||
ssh_cmd "/bin/mkdir -p /mnt1/System/Cryptexes/App /mnt1/System/Cryptexes/OS"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/System/Cryptexes/App /mnt1/System/Cryptexes/OS"
|
||||
/bin/rm -rf $MNT1/System/Cryptexes/App $MNT1/System/Cryptexes/OS
|
||||
/bin/mkdir -p $MNT1/System/Cryptexes/App $MNT1/System/Cryptexes/OS
|
||||
/bin/chmod 0755 $MNT1/System/Cryptexes/App $MNT1/System/Cryptexes/OS
|
||||
|
||||
# Copy Cryptex files to device
|
||||
# Copy Cryptex files onto the volume
|
||||
echo " Copying Cryptexes..."
|
||||
scp_to "$MNT_SYSOS/." "/mnt1/System/Cryptexes/OS"
|
||||
scp_to "$MNT_APPOS/." "/mnt1/System/Cryptexes/App"
|
||||
cp -R "$MNT_SYSOS/." "$MNT1/System/Cryptexes/OS"
|
||||
cp -R "$MNT_APPOS/." "$MNT1/System/Cryptexes/App"
|
||||
|
||||
# Create dyld symlinks (ln -sf is idempotent)
|
||||
echo " Creating dyld symlinks..."
|
||||
ssh_cmd "/bin/ln -sf ../../../System/Cryptexes/OS/System/Library/Caches/com.apple.dyld \
|
||||
/mnt1/System/Library/Caches/com.apple.dyld"
|
||||
ssh_cmd "/bin/ln -sf ../../../../System/Cryptexes/OS/System/DriverKit/System/Library/dyld \
|
||||
/mnt1/System/DriverKit/System/Library/dyld"
|
||||
/bin/ln -sf ../../../System/Cryptexes/OS/System/Library/Caches/com.apple.dyld \
|
||||
$MNT1/System/Library/Caches/com.apple.dyld
|
||||
/bin/ln -sf ../../../../System/Cryptexes/OS/System/DriverKit/System/Library/dyld \
|
||||
$MNT1/System/DriverKit/System/Library/dyld
|
||||
|
||||
# Unmount Cryptex DMGs
|
||||
echo " Unmounting Cryptex DMGs..."
|
||||
@@ -385,21 +288,21 @@ echo ""
|
||||
echo "[2/7] Patching seputil..."
|
||||
|
||||
# Always patch from .bak (original unpatched binary)
|
||||
if ! remote_file_exists "/mnt1/usr/libexec/seputil.bak"; then
|
||||
if ! [[ -e "$MNT1/usr/libexec/seputil.bak" ]]; then
|
||||
echo " Creating backup..."
|
||||
ssh_cmd "/bin/cp /mnt1/usr/libexec/seputil /mnt1/usr/libexec/seputil.bak"
|
||||
/bin/cp $MNT1/usr/libexec/seputil $MNT1/usr/libexec/seputil.bak
|
||||
fi
|
||||
|
||||
scp_from "/mnt1/usr/libexec/seputil.bak" "$TEMP_DIR/seputil"
|
||||
cp "$MNT1/usr/libexec/seputil.bak" "$TEMP_DIR/seputil"
|
||||
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-seputil "$TEMP_DIR/seputil"
|
||||
ldid_sign "$TEMP_DIR/seputil" "com.apple.seputil"
|
||||
scp_to "$TEMP_DIR/seputil" "/mnt1/usr/libexec/seputil"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/usr/libexec/seputil"
|
||||
cp -R "$TEMP_DIR/seputil" "$MNT1/usr/libexec/seputil"
|
||||
/bin/chmod 0755 $MNT1/usr/libexec/seputil
|
||||
|
||||
# Rename gigalocker (mv to same name is fine on re-run)
|
||||
echo " Renaming gigalocker..."
|
||||
remote_mount /dev/disk1s3 /mnt3
|
||||
ssh_cmd '/bin/mv /mnt3/*.gl /mnt3/AA.gl 2>/dev/null || true'
|
||||
mount_vol s3 "$MNT3"
|
||||
mv "$MNT3"/*.gl(N) "$MNT3/AA.gl" 2>/dev/null || true
|
||||
|
||||
echo " [+] seputil patched"
|
||||
|
||||
@@ -407,21 +310,21 @@ echo " [+] seputil patched"
|
||||
echo ""
|
||||
echo "[3/7] Installing AppleParavirtGPUMetalIOGPUFamily..."
|
||||
|
||||
scp_to "$INPUT_DIR/custom/AppleParavirtGPUMetalIOGPUFamily.tar" "/mnt1"
|
||||
ssh_cmd "/usr/bin/tar --preserve-permissions --no-overwrite-dir \
|
||||
-xf /mnt1/AppleParavirtGPUMetalIOGPUFamily.tar -C /mnt1"
|
||||
cp -R "$INPUT_DIR/custom/AppleParavirtGPUMetalIOGPUFamily.tar" "$MNT1"
|
||||
"$TAR" --preserve-permissions --no-overwrite-dir \
|
||||
-xf $MNT1/AppleParavirtGPUMetalIOGPUFamily.tar -C $MNT1
|
||||
|
||||
BUNDLE="/mnt1/System/Library/Extensions/AppleParavirtGPUMetalIOGPUFamily.bundle"
|
||||
BUNDLE="$MNT1/System/Library/Extensions/AppleParavirtGPUMetalIOGPUFamily.bundle"
|
||||
# Clean macOS resource fork files (._* files from tar xattrs)
|
||||
ssh_cmd "find $BUNDLE -name '._*' -delete 2>/dev/null || true"
|
||||
ssh_cmd "/usr/sbin/chown -R 0:0 $BUNDLE"
|
||||
ssh_cmd "/bin/chmod 0755 $BUNDLE"
|
||||
ssh_cmd "/bin/chmod 0755 $BUNDLE/libAppleParavirtCompilerPluginIOGPUFamily.dylib"
|
||||
ssh_cmd "/bin/chmod 0755 $BUNDLE/AppleParavirtGPUMetalIOGPUFamily"
|
||||
ssh_cmd "/bin/chmod 0755 $BUNDLE/_CodeSignature"
|
||||
ssh_cmd "/bin/chmod 0644 $BUNDLE/_CodeSignature/CodeResources"
|
||||
ssh_cmd "/bin/chmod 0644 $BUNDLE/Info.plist"
|
||||
ssh_cmd "/bin/rm -f /mnt1/AppleParavirtGPUMetalIOGPUFamily.tar"
|
||||
find $BUNDLE -name '._*' -delete 2>/dev/null || true
|
||||
/usr/sbin/chown -R 0:0 $BUNDLE
|
||||
/bin/chmod 0755 $BUNDLE
|
||||
/bin/chmod 0755 $BUNDLE/libAppleParavirtCompilerPluginIOGPUFamily.dylib
|
||||
/bin/chmod 0755 $BUNDLE/AppleParavirtGPUMetalIOGPUFamily
|
||||
/bin/chmod 0755 $BUNDLE/_CodeSignature
|
||||
/bin/chmod 0644 $BUNDLE/_CodeSignature/CodeResources
|
||||
/bin/chmod 0644 $BUNDLE/Info.plist
|
||||
/bin/rm -f $MNT1/AppleParavirtGPUMetalIOGPUFamily.tar
|
||||
|
||||
echo " [+] GPU driver installed"
|
||||
|
||||
@@ -429,16 +332,14 @@ echo " [+] GPU driver installed"
|
||||
echo ""
|
||||
echo "[4/7] Installing iosbinpack64..."
|
||||
|
||||
scp_to "$INPUT_DIR/jb/iosbinpack64.tar" "/mnt1"
|
||||
ssh_cmd "/usr/bin/tar --preserve-permissions --no-overwrite-dir \
|
||||
-xf /mnt1/iosbinpack64.tar -C /mnt1"
|
||||
ssh_cmd "/bin/rm -f /mnt1/iosbinpack64.tar"
|
||||
cp -R "$INPUT_DIR/jb/iosbinpack64.tar" "$MNT1"
|
||||
"$TAR" --preserve-permissions --no-overwrite-dir \
|
||||
-xf $MNT1/iosbinpack64.tar -C $MNT1
|
||||
/bin/rm -f $MNT1/iosbinpack64.tar
|
||||
|
||||
echo " Setting up dropbear host keys..."
|
||||
ssh_cmd "/bin/mkdir -p /mnt3/dropbear"
|
||||
ssh_cmd "if [ ! -f /mnt3/dropbear/dropbear_rsa_host_key ]; then /usr/local/bin/dropbearkey -t rsa -f /mnt3/dropbear/dropbear_rsa_host_key >/dev/null; fi"
|
||||
ssh_cmd "if [ ! -f /mnt3/dropbear/dropbear_ecdsa_host_key ]; then /usr/local/bin/dropbearkey -t ecdsa -f /mnt3/dropbear/dropbear_ecdsa_host_key >/dev/null; fi"
|
||||
ssh_cmd "/bin/chmod 600 /mnt3/dropbear/dropbear_rsa_host_key /mnt3/dropbear/dropbear_ecdsa_host_key"
|
||||
# dropbear host keys are generated on first boot by dropbear -R; just ensure
|
||||
# the key directory exists for it to write into.
|
||||
/bin/mkdir -p $MNT3/dropbear
|
||||
|
||||
echo " [+] iosbinpack64 installed"
|
||||
|
||||
@@ -447,16 +348,16 @@ echo ""
|
||||
echo "[5/7] Patching launchd_cache_loader..."
|
||||
|
||||
# Always patch from .bak (original unpatched binary)
|
||||
if ! remote_file_exists "/mnt1/usr/libexec/launchd_cache_loader.bak"; then
|
||||
if ! [[ -e "$MNT1/usr/libexec/launchd_cache_loader.bak" ]]; then
|
||||
echo " Creating backup..."
|
||||
ssh_cmd "/bin/cp /mnt1/usr/libexec/launchd_cache_loader /mnt1/usr/libexec/launchd_cache_loader.bak"
|
||||
/bin/cp $MNT1/usr/libexec/launchd_cache_loader $MNT1/usr/libexec/launchd_cache_loader.bak
|
||||
fi
|
||||
|
||||
scp_from "/mnt1/usr/libexec/launchd_cache_loader.bak" "$TEMP_DIR/launchd_cache_loader"
|
||||
cp "$MNT1/usr/libexec/launchd_cache_loader.bak" "$TEMP_DIR/launchd_cache_loader"
|
||||
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-launchd-cache-loader "$TEMP_DIR/launchd_cache_loader"
|
||||
ldid_sign "$TEMP_DIR/launchd_cache_loader" "com.apple.launchd_cache_loader"
|
||||
scp_to "$TEMP_DIR/launchd_cache_loader" "/mnt1/usr/libexec/launchd_cache_loader"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/usr/libexec/launchd_cache_loader"
|
||||
cp -R "$TEMP_DIR/launchd_cache_loader" "$MNT1/usr/libexec/launchd_cache_loader"
|
||||
/bin/chmod 0755 $MNT1/usr/libexec/launchd_cache_loader
|
||||
|
||||
echo " [+] launchd_cache_loader patched"
|
||||
|
||||
@@ -465,16 +366,16 @@ echo ""
|
||||
echo "[6/7] Patching mobileactivationd..."
|
||||
|
||||
# Always patch from .bak (original unpatched binary)
|
||||
if ! remote_file_exists "/mnt1/usr/libexec/mobileactivationd.bak"; then
|
||||
if ! [[ -e "$MNT1/usr/libexec/mobileactivationd.bak" ]]; then
|
||||
echo " Creating backup..."
|
||||
ssh_cmd "/bin/cp /mnt1/usr/libexec/mobileactivationd /mnt1/usr/libexec/mobileactivationd.bak"
|
||||
/bin/cp $MNT1/usr/libexec/mobileactivationd $MNT1/usr/libexec/mobileactivationd.bak
|
||||
fi
|
||||
|
||||
scp_from "/mnt1/usr/libexec/mobileactivationd.bak" "$TEMP_DIR/mobileactivationd"
|
||||
cp "$MNT1/usr/libexec/mobileactivationd.bak" "$TEMP_DIR/mobileactivationd"
|
||||
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-mobileactivationd "$TEMP_DIR/mobileactivationd"
|
||||
ldid_sign "$TEMP_DIR/mobileactivationd"
|
||||
scp_to "$TEMP_DIR/mobileactivationd" "/mnt1/usr/libexec/mobileactivationd"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/usr/libexec/mobileactivationd"
|
||||
cp -R "$TEMP_DIR/mobileactivationd" "$MNT1/usr/libexec/mobileactivationd"
|
||||
/bin/chmod 0755 $MNT1/usr/libexec/mobileactivationd
|
||||
|
||||
echo " [+] mobileactivationd patched"
|
||||
|
||||
@@ -514,8 +415,8 @@ ldid \
|
||||
-S"$VPHONED_SRC/entitlements.plist" \
|
||||
-M "-K$VM_DIR/$CFW_INPUT/signcert.p12" \
|
||||
"$TEMP_DIR/vphoned"
|
||||
scp_to "$TEMP_DIR/vphoned" "/mnt1/usr/bin/vphoned"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/usr/bin/vphoned"
|
||||
cp -R "$TEMP_DIR/vphoned" "$MNT1/usr/bin/vphoned"
|
||||
/bin/chmod 0755 $MNT1/usr/bin/vphoned
|
||||
# Keep a copy of the signed binary for host-side auto-update
|
||||
cp "$TEMP_DIR/vphoned" "$VM_DIR/.vphoned.signed"
|
||||
echo " [+] vphoned installed (signed copy at .vphoned.signed)"
|
||||
@@ -528,32 +429,32 @@ for plist in bash.plist dropbear.plist trollvnc.plist rpcserver_ios.plist; do
|
||||
cp "$INPUT_DIR/jb/LaunchDaemons/dropbear.plist" "$plist_src"
|
||||
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-dropbear-plist "$plist_src"
|
||||
fi
|
||||
scp_to "$plist_src" "/mnt1/System/Library/LaunchDaemons/"
|
||||
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/LaunchDaemons/$plist"
|
||||
cp -R "$plist_src" "$MNT1/System/Library/LaunchDaemons/"
|
||||
/bin/chmod 0644 $MNT1/System/Library/LaunchDaemons/$plist
|
||||
done
|
||||
scp_to "$VPHONED_SRC/vphoned.plist" "/mnt1/System/Library/LaunchDaemons/"
|
||||
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/LaunchDaemons/vphoned.plist"
|
||||
cp -R "$VPHONED_SRC/vphoned.plist" "$MNT1/System/Library/LaunchDaemons/"
|
||||
/bin/chmod 0644 $MNT1/System/Library/LaunchDaemons/vphoned.plist
|
||||
|
||||
# Always patch launchd.plist from .bak (original)
|
||||
echo " Patching launchd.plist..."
|
||||
if ! remote_file_exists "/mnt1/System/Library/xpc/launchd.plist.bak"; then
|
||||
if ! [[ -e "$MNT1/System/Library/xpc/launchd.plist.bak" ]]; then
|
||||
echo " Creating backup..."
|
||||
ssh_cmd "/bin/cp /mnt1/System/Library/xpc/launchd.plist /mnt1/System/Library/xpc/launchd.plist.bak"
|
||||
/bin/cp $MNT1/System/Library/xpc/launchd.plist $MNT1/System/Library/xpc/launchd.plist.bak
|
||||
fi
|
||||
|
||||
scp_from "/mnt1/System/Library/xpc/launchd.plist.bak" "$TEMP_DIR/launchd.plist"
|
||||
cp "$MNT1/System/Library/xpc/launchd.plist.bak" "$TEMP_DIR/launchd.plist"
|
||||
cp "$VPHONED_SRC/vphoned.plist" "$INPUT_DIR/jb/LaunchDaemons/"
|
||||
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" inject-daemons "$TEMP_DIR/launchd.plist" "$INPUT_DIR/jb/LaunchDaemons"
|
||||
scp_to "$TEMP_DIR/launchd.plist" "/mnt1/System/Library/xpc/launchd.plist"
|
||||
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/xpc/launchd.plist"
|
||||
cp -R "$TEMP_DIR/launchd.plist" "$MNT1/System/Library/xpc/launchd.plist"
|
||||
/bin/chmod 0644 $MNT1/System/Library/xpc/launchd.plist
|
||||
|
||||
echo " [+] LaunchDaemons installed"
|
||||
|
||||
# ═══════════ CLEANUP ═════════════════════════════════════════
|
||||
echo ""
|
||||
echo "[*] Unmounting device filesystems..."
|
||||
ssh_cmd "/sbin/umount /mnt1 2>/dev/null || true"
|
||||
ssh_cmd "/sbin/umount /mnt3 2>/dev/null || true"
|
||||
echo "[*] Unmounting image volumes..."
|
||||
/sbin/umount $MNT1 2>/dev/null || true
|
||||
/sbin/umount $MNT3 2>/dev/null || true
|
||||
|
||||
# Keep .cfw_temp/Cryptex*.dmg cached (slow to re-create)
|
||||
# Only remove temp binaries
|
||||
@@ -566,11 +467,5 @@ rm -f "$TEMP_DIR/seputil" \
|
||||
|
||||
echo ""
|
||||
echo "[+] CFW installation complete!"
|
||||
echo " Reboot to apply changes."
|
||||
echo " Boot to apply changes."
|
||||
echo " After boot, SSH will be available on port 22222 (password: alpine)"
|
||||
|
||||
if [[ "$CFW_SKIP_HALT" == "1" ]]; then
|
||||
echo "[*] CFW_SKIP_HALT=1, skipping halt."
|
||||
else
|
||||
ssh_cmd "/sbin/halt" || true
|
||||
fi
|
||||
|
||||
+95
-168
@@ -1,14 +1,17 @@
|
||||
#!/bin/zsh
|
||||
# cfw_install.sh — Install base CFW modifications on vphone via SSH ramdisk.
|
||||
# cfw_install_dev.sh — Install base CFW modifications on vphone (dev variant).
|
||||
#
|
||||
# Installs Cryptexes, patches system binaries, installs jailbreak tools
|
||||
# and configures LaunchDaemons for persistent SSH/VNC access.
|
||||
#
|
||||
# Files are placed directly on the VM's Disk.img volumes, which cfw_install_host.sh
|
||||
# attaches and mounts on the host; the VM must be off.
|
||||
#
|
||||
# Safe to run multiple times — always patches from original .bak files,
|
||||
# keeps decrypted Cryptex DMGs cached, handles already-mounted filesystems.
|
||||
#
|
||||
# Prerequisites:
|
||||
# - Device booted into SSH ramdisk (make ramdisk_send)
|
||||
# - VM restored (make restore) and powered off
|
||||
# - `ipsw` tool installed (brew install blacktop/tap/ipsw)
|
||||
# - `aea` tool available (macOS 12+)
|
||||
# - Python: make setup_venv && source .venv/bin/activate
|
||||
@@ -22,7 +25,6 @@ set -euo pipefail
|
||||
|
||||
VM_DIR="${1:-.}"
|
||||
SCRIPT_DIR="${0:a:h}"
|
||||
CFW_SKIP_HALT="${CFW_SKIP_HALT:-0}"
|
||||
|
||||
# Resolve absolute paths
|
||||
VM_DIR="$(cd "$VM_DIR" && pwd)"
|
||||
@@ -43,20 +45,6 @@ CFW_INPUT="cfw_input"
|
||||
CFW_ARCHIVE="cfw_input.tar.zst"
|
||||
TEMP_DIR="$VM_DIR/.cfw_temp"
|
||||
|
||||
SSH_PORT="${SSH_PORT:-2222}"
|
||||
SSH_PASS="alpine"
|
||||
SSH_USER="root"
|
||||
SSH_HOST="localhost"
|
||||
SSH_RETRY="${SSH_RETRY:-3}"
|
||||
SSHPASS_BIN=""
|
||||
SSH_OPTS=(
|
||||
-o StrictHostKeyChecking=no
|
||||
-o UserKnownHostsFile=/dev/null
|
||||
-o PreferredAuthentications=password
|
||||
-o ConnectTimeout=30
|
||||
-q
|
||||
)
|
||||
|
||||
# ── Helpers ─────────────────────────────────────────────────────
|
||||
die() {
|
||||
echo "[-] $*" >&2
|
||||
@@ -65,43 +53,10 @@ die() {
|
||||
|
||||
check_prerequisites() {
|
||||
local missing=()
|
||||
command -v sshpass &>/dev/null || missing+=("sshpass")
|
||||
command -v ldid &>/dev/null || missing+=("ldid (brew install ldid-procursus)")
|
||||
if ((${#missing[@]} > 0)); then
|
||||
die "Missing required tools: ${missing[*]}. Run: make setup_tools"
|
||||
fi
|
||||
SSHPASS_BIN="$(command -v sshpass)"
|
||||
}
|
||||
|
||||
_sshpass() {
|
||||
"$SSHPASS_BIN" -p "$SSH_PASS" "$@"
|
||||
}
|
||||
|
||||
_ssh_retry() {
|
||||
local attempt rc label
|
||||
label=${2:-cmd}
|
||||
for ((attempt = 1; attempt <= SSH_RETRY; attempt++)); do
|
||||
"$@" && return 0
|
||||
rc=$?
|
||||
[[ $rc -ne 255 ]] && return $rc # real command failure — don't retry
|
||||
echo " [${label}] connection lost (attempt $attempt/$SSH_RETRY), retrying in 3s..." >&2
|
||||
sleep 3
|
||||
done
|
||||
return 255
|
||||
}
|
||||
|
||||
ssh_cmd() {
|
||||
_ssh_retry _sshpass ssh "${SSH_OPTS[@]}" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "$@"
|
||||
}
|
||||
scp_to() {
|
||||
_ssh_retry _sshpass scp -q "${SSH_OPTS[@]}" -P "$SSH_PORT" -r "$1" "$SSH_USER@$SSH_HOST:$2"
|
||||
}
|
||||
scp_from() {
|
||||
_ssh_retry _sshpass scp -q "${SSH_OPTS[@]}" -P "$SSH_PORT" "$SSH_USER@$SSH_HOST:$1" "$2"
|
||||
}
|
||||
|
||||
remote_file_exists() {
|
||||
ssh_cmd "test -f '$1'" 2>/dev/null
|
||||
}
|
||||
|
||||
ldid_sign() {
|
||||
@@ -138,19 +93,6 @@ assert_mount_under_vm() {
|
||||
esac
|
||||
}
|
||||
|
||||
# Mount device filesystem, tolerate already-mounted
|
||||
remote_mount() {
|
||||
local dev="$1" mnt="$2" opts="${3:-rw}"
|
||||
ssh_cmd "/bin/mkdir -p $mnt"
|
||||
if ssh_cmd "/sbin/mount | /usr/bin/grep -q ' on $mnt '"; then
|
||||
return 0
|
||||
fi
|
||||
ssh_cmd "/sbin/mount_apfs -o $opts $dev $mnt 2>/dev/null || true"
|
||||
if ! ssh_cmd "/sbin/mount | /usr/bin/grep -q ' on $mnt '"; then
|
||||
die "Failed to mount $dev at $mnt (opts=$opts). Make sure the ramdisk was booted with the expected patched kernel."
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Find restore directory ─────────────────────────────────────
|
||||
find_restore_dir() {
|
||||
for dir in "$VM_DIR"/iPhone*_Restore; do
|
||||
@@ -213,9 +155,24 @@ cleanup_on_exit() {
|
||||
}
|
||||
trap cleanup_on_exit EXIT
|
||||
|
||||
# Host-mode transport override (CFW_HOST_MODE=1): run install against image
|
||||
# volumes mounted locally on the host instead of a device over SSH.
|
||||
[[ -n "${CFW_HOST_MODE:-}" ]] && source "$SCRIPT_DIR/cfw_host_mode.sh"
|
||||
# The VM's Disk.img is attached on the host by cfw_install_host.sh; its APFS
|
||||
# volumes are mounted here and every file is placed with plain cp/chmod/etc.
|
||||
# (the VM is off — nothing runs "on the device").
|
||||
: "${CFW_HOST_CONTAINER:?CFW_HOST_CONTAINER unset — run via cfw_install_host.sh}"
|
||||
HOST_MNT="${CFW_HOST_MNT:-/private/tmp/cfwhost}"
|
||||
MNT1="$HOST_MNT/mnt1" # disk1s1 (System / rootfs)
|
||||
MNT3="$HOST_MNT/mnt3" # disk1s3
|
||||
TAR="$(command -v gtar 2>/dev/null || echo /opt/homebrew/bin/gtar)" # macOS bsdtar lacks GNU tar flags
|
||||
mkdir -p "$HOST_MNT"
|
||||
|
||||
# Mount an APFS volume of the attached image container at a host mount point.
|
||||
mount_vol() { # mount_vol <slice, e.g. s1> <mountpoint> [opts]
|
||||
local dev="/dev/${CFW_HOST_CONTAINER}$1" mnt="$2" opts="${3:-rw}"
|
||||
/bin/mkdir -p "$mnt"
|
||||
/sbin/mount | /usr/bin/grep -q " on $mnt " && return 0
|
||||
/sbin/mount_apfs -o "$opts" "$dev" "$mnt" 2>/dev/null || true
|
||||
/sbin/mount | /usr/bin/grep -q " on $mnt " || die "mount failed: $dev -> $mnt"
|
||||
}
|
||||
|
||||
# ════════════════════════════════════════════════════════════════
|
||||
# Main
|
||||
@@ -283,25 +240,25 @@ sudo ${SUDO_ASKPASS:+-A} hdiutil attach -mountpoint "$MNT_SYSOS" "$SYSOS_DMG" -n
|
||||
echo " Mounting AppOS..."
|
||||
sudo ${SUDO_ASKPASS:+-A} hdiutil attach -mountpoint "$MNT_APPOS" "$APPOS_DMG" -nobrowse -owners off
|
||||
|
||||
# Mount device rootfs (tolerate already-mounted)
|
||||
# Mount the image's System volume (tolerate already-mounted)
|
||||
echo " Mounting rootfs rw..."
|
||||
remote_mount /dev/disk1s1 /mnt1
|
||||
mount_vol s1 "$MNT1"
|
||||
|
||||
# Patch launchd jetsum guard
|
||||
echo ""
|
||||
echo " Patching launchd (jetsam guard)..."
|
||||
|
||||
if ! remote_file_exists "/mnt1/sbin/launchd.bak"; then
|
||||
if ! [[ -e "$MNT1/sbin/launchd.bak" ]]; then
|
||||
echo " Creating backup..."
|
||||
ssh_cmd "/bin/cp /mnt1/sbin/launchd /mnt1/sbin/launchd.bak"
|
||||
/bin/cp $MNT1/sbin/launchd $MNT1/sbin/launchd.bak
|
||||
fi
|
||||
|
||||
scp_from "/mnt1/sbin/launchd.bak" "$TEMP_DIR/launchd"
|
||||
cp "$MNT1/sbin/launchd.bak" "$TEMP_DIR/launchd"
|
||||
|
||||
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-launchd-jetsam "$TEMP_DIR/launchd"
|
||||
ldid_sign "$TEMP_DIR/launchd"
|
||||
scp_to "$TEMP_DIR/launchd" "/mnt1/sbin/launchd"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/sbin/launchd"
|
||||
cp -R "$TEMP_DIR/launchd" "$MNT1/sbin/launchd"
|
||||
/bin/chmod 0755 $MNT1/sbin/launchd
|
||||
|
||||
echo " [+] launchd patched"
|
||||
|
||||
@@ -309,54 +266,32 @@ echo " [+] launchd patched"
|
||||
echo ""
|
||||
echo " Patch debugserver entitlements..."
|
||||
|
||||
scp_from "/mnt1/usr/libexec/debugserver" "$TEMP_DIR/debugserver"
|
||||
cp "$MNT1/usr/libexec/debugserver" "$TEMP_DIR/debugserver"
|
||||
ldid -e "$TEMP_DIR/debugserver" > "$TEMP_DIR/debugserver-entitlements.plist"
|
||||
plutil -remove seatbelt-profiles "$TEMP_DIR/debugserver-entitlements.plist" || true
|
||||
plutil -insert task_for_pid-allow -bool YES "$TEMP_DIR/debugserver-entitlements.plist" || true
|
||||
ldid_sign_ent "$TEMP_DIR/debugserver" "$TEMP_DIR/debugserver-entitlements.plist"
|
||||
scp_to "$TEMP_DIR/debugserver" "/mnt1/usr/libexec/debugserver"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/usr/libexec/debugserver"
|
||||
cp -R "$TEMP_DIR/debugserver" "$MNT1/usr/libexec/debugserver"
|
||||
/bin/chmod 0755 $MNT1/usr/libexec/debugserver
|
||||
|
||||
echo " [+] debugserver entitlements patched"
|
||||
|
||||
# Rename APFS update snapshot to orig-fs (idempotent)
|
||||
echo " Checking APFS snapshots..."
|
||||
SNAP_LIST=$(ssh_cmd "snaputil -l /mnt1 2>/dev/null" || true)
|
||||
if echo "$SNAP_LIST" | grep -q "^orig-fs$"; then
|
||||
echo " Snapshot 'orig-fs' already exists, skipping rename"
|
||||
else
|
||||
UPDATE_SNAP=$(echo "$SNAP_LIST" | grep "^com\.apple\.os\.update-" | head -1)
|
||||
if [[ -n "$UPDATE_SNAP" ]]; then
|
||||
echo " Renaming snapshot: $UPDATE_SNAP -> orig-fs"
|
||||
ssh_cmd "snaputil -n '$UPDATE_SNAP' orig-fs /mnt1"
|
||||
# Verify rename succeeded
|
||||
if ! ssh_cmd "snaputil -l /mnt1 2>/dev/null" | grep -q "^orig-fs$"; then
|
||||
die "Failed to rename snapshot to orig-fs"
|
||||
fi
|
||||
echo " Snapshot renamed, remounting..."
|
||||
ssh_cmd "/sbin/umount /mnt1"
|
||||
remote_mount /dev/disk1s1 /mnt1
|
||||
echo " [+] Snapshot renamed to orig-fs"
|
||||
else
|
||||
echo " No com.apple.os.update- snapshot found, skipping"
|
||||
fi
|
||||
fi
|
||||
|
||||
ssh_cmd "/bin/rm -rf /mnt1/System/Cryptexes/App /mnt1/System/Cryptexes/OS"
|
||||
ssh_cmd "/bin/mkdir -p /mnt1/System/Cryptexes/App /mnt1/System/Cryptexes/OS"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/System/Cryptexes/App /mnt1/System/Cryptexes/OS"
|
||||
/bin/rm -rf $MNT1/System/Cryptexes/App $MNT1/System/Cryptexes/OS
|
||||
/bin/mkdir -p $MNT1/System/Cryptexes/App $MNT1/System/Cryptexes/OS
|
||||
/bin/chmod 0755 $MNT1/System/Cryptexes/App $MNT1/System/Cryptexes/OS
|
||||
|
||||
# Copy Cryptex files to device
|
||||
# Copy Cryptex files onto the volume
|
||||
echo " Copying Cryptexes..."
|
||||
scp_to "$MNT_SYSOS/." "/mnt1/System/Cryptexes/OS"
|
||||
scp_to "$MNT_APPOS/." "/mnt1/System/Cryptexes/App"
|
||||
cp -R "$MNT_SYSOS/." "$MNT1/System/Cryptexes/OS"
|
||||
cp -R "$MNT_APPOS/." "$MNT1/System/Cryptexes/App"
|
||||
|
||||
# Create dyld symlinks (ln -sf is idempotent)
|
||||
echo " Creating dyld symlinks..."
|
||||
ssh_cmd "/bin/ln -sf ../../../System/Cryptexes/OS/System/Library/Caches/com.apple.dyld \
|
||||
/mnt1/System/Library/Caches/com.apple.dyld"
|
||||
ssh_cmd "/bin/ln -sf ../../../../System/Cryptexes/OS/System/DriverKit/System/Library/dyld \
|
||||
/mnt1/System/DriverKit/System/Library/dyld"
|
||||
/bin/ln -sf ../../../System/Cryptexes/OS/System/Library/Caches/com.apple.dyld \
|
||||
$MNT1/System/Library/Caches/com.apple.dyld
|
||||
/bin/ln -sf ../../../../System/Cryptexes/OS/System/DriverKit/System/Library/dyld \
|
||||
$MNT1/System/DriverKit/System/Library/dyld
|
||||
|
||||
# Unmount Cryptex DMGs
|
||||
echo " Unmounting Cryptex DMGs..."
|
||||
@@ -370,21 +305,21 @@ echo ""
|
||||
echo "[2/7] Patching seputil..."
|
||||
|
||||
# Always patch from .bak (original unpatched binary)
|
||||
if ! remote_file_exists "/mnt1/usr/libexec/seputil.bak"; then
|
||||
if ! [[ -e "$MNT1/usr/libexec/seputil.bak" ]]; then
|
||||
echo " Creating backup..."
|
||||
ssh_cmd "/bin/cp /mnt1/usr/libexec/seputil /mnt1/usr/libexec/seputil.bak"
|
||||
/bin/cp $MNT1/usr/libexec/seputil $MNT1/usr/libexec/seputil.bak
|
||||
fi
|
||||
|
||||
scp_from "/mnt1/usr/libexec/seputil.bak" "$TEMP_DIR/seputil"
|
||||
cp "$MNT1/usr/libexec/seputil.bak" "$TEMP_DIR/seputil"
|
||||
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-seputil "$TEMP_DIR/seputil"
|
||||
ldid_sign "$TEMP_DIR/seputil" "com.apple.seputil"
|
||||
scp_to "$TEMP_DIR/seputil" "/mnt1/usr/libexec/seputil"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/usr/libexec/seputil"
|
||||
cp -R "$TEMP_DIR/seputil" "$MNT1/usr/libexec/seputil"
|
||||
/bin/chmod 0755 $MNT1/usr/libexec/seputil
|
||||
|
||||
# Rename gigalocker (mv to same name is fine on re-run)
|
||||
echo " Renaming gigalocker..."
|
||||
remote_mount /dev/disk1s3 /mnt3
|
||||
ssh_cmd '/bin/mv /mnt3/*.gl /mnt3/AA.gl 2>/dev/null || true'
|
||||
mount_vol s3 "$MNT3"
|
||||
mv "$MNT3"/*.gl(N) "$MNT3/AA.gl" 2>/dev/null || true
|
||||
|
||||
echo " [+] seputil patched"
|
||||
|
||||
@@ -392,21 +327,21 @@ echo " [+] seputil patched"
|
||||
echo ""
|
||||
echo "[3/7] Installing AppleParavirtGPUMetalIOGPUFamily..."
|
||||
|
||||
scp_to "$INPUT_DIR/custom/AppleParavirtGPUMetalIOGPUFamily.tar" "/mnt1"
|
||||
ssh_cmd "/usr/bin/tar --preserve-permissions --no-overwrite-dir \
|
||||
-xf /mnt1/AppleParavirtGPUMetalIOGPUFamily.tar -C /mnt1"
|
||||
cp -R "$INPUT_DIR/custom/AppleParavirtGPUMetalIOGPUFamily.tar" "$MNT1"
|
||||
"$TAR" --preserve-permissions --no-overwrite-dir \
|
||||
-xf $MNT1/AppleParavirtGPUMetalIOGPUFamily.tar -C $MNT1
|
||||
|
||||
BUNDLE="/mnt1/System/Library/Extensions/AppleParavirtGPUMetalIOGPUFamily.bundle"
|
||||
BUNDLE="$MNT1/System/Library/Extensions/AppleParavirtGPUMetalIOGPUFamily.bundle"
|
||||
# Clean macOS resource fork files (._* files from tar xattrs)
|
||||
ssh_cmd "find $BUNDLE -name '._*' -delete 2>/dev/null || true"
|
||||
ssh_cmd "/usr/sbin/chown -R 0:0 $BUNDLE"
|
||||
ssh_cmd "/bin/chmod 0755 $BUNDLE"
|
||||
ssh_cmd "/bin/chmod 0755 $BUNDLE/libAppleParavirtCompilerPluginIOGPUFamily.dylib"
|
||||
ssh_cmd "/bin/chmod 0755 $BUNDLE/AppleParavirtGPUMetalIOGPUFamily"
|
||||
ssh_cmd "/bin/chmod 0755 $BUNDLE/_CodeSignature"
|
||||
ssh_cmd "/bin/chmod 0644 $BUNDLE/_CodeSignature/CodeResources"
|
||||
ssh_cmd "/bin/chmod 0644 $BUNDLE/Info.plist"
|
||||
ssh_cmd "/bin/rm -f /mnt1/AppleParavirtGPUMetalIOGPUFamily.tar"
|
||||
find $BUNDLE -name '._*' -delete 2>/dev/null || true
|
||||
/usr/sbin/chown -R 0:0 $BUNDLE
|
||||
/bin/chmod 0755 $BUNDLE
|
||||
/bin/chmod 0755 $BUNDLE/libAppleParavirtCompilerPluginIOGPUFamily.dylib
|
||||
/bin/chmod 0755 $BUNDLE/AppleParavirtGPUMetalIOGPUFamily
|
||||
/bin/chmod 0755 $BUNDLE/_CodeSignature
|
||||
/bin/chmod 0644 $BUNDLE/_CodeSignature/CodeResources
|
||||
/bin/chmod 0644 $BUNDLE/Info.plist
|
||||
/bin/rm -f $MNT1/AppleParavirtGPUMetalIOGPUFamily.tar
|
||||
|
||||
echo " [+] GPU driver installed"
|
||||
|
||||
@@ -414,16 +349,14 @@ echo " [+] GPU driver installed"
|
||||
echo ""
|
||||
echo "[4/7] Installing iosbinpack64..."
|
||||
|
||||
scp_to "$INPUT_DIR/jb/iosbinpack64.tar" "/mnt1"
|
||||
ssh_cmd "/usr/bin/tar --preserve-permissions --no-overwrite-dir \
|
||||
-xf /mnt1/iosbinpack64.tar -C /mnt1"
|
||||
ssh_cmd "/bin/rm -f /mnt1/iosbinpack64.tar"
|
||||
cp -R "$INPUT_DIR/jb/iosbinpack64.tar" "$MNT1"
|
||||
"$TAR" --preserve-permissions --no-overwrite-dir \
|
||||
-xf $MNT1/iosbinpack64.tar -C $MNT1
|
||||
/bin/rm -f $MNT1/iosbinpack64.tar
|
||||
|
||||
echo " Setting up dropbear host keys..."
|
||||
ssh_cmd "/bin/mkdir -p /mnt3/dropbear"
|
||||
ssh_cmd "if [ ! -f /mnt3/dropbear/dropbear_rsa_host_key ]; then /usr/local/bin/dropbearkey -t rsa -f /mnt3/dropbear/dropbear_rsa_host_key >/dev/null; fi"
|
||||
ssh_cmd "if [ ! -f /mnt3/dropbear/dropbear_ecdsa_host_key ]; then /usr/local/bin/dropbearkey -t ecdsa -f /mnt3/dropbear/dropbear_ecdsa_host_key >/dev/null; fi"
|
||||
ssh_cmd "/bin/chmod 600 /mnt3/dropbear/dropbear_rsa_host_key /mnt3/dropbear/dropbear_ecdsa_host_key"
|
||||
# dropbear host keys are generated on first boot by dropbear -R; just ensure
|
||||
# the key directory exists for it to write into.
|
||||
/bin/mkdir -p $MNT3/dropbear
|
||||
|
||||
echo " [+] iosbinpack64 installed"
|
||||
|
||||
@@ -432,16 +365,16 @@ echo ""
|
||||
echo "[5/7] Patching launchd_cache_loader..."
|
||||
|
||||
# Always patch from .bak (original unpatched binary)
|
||||
if ! remote_file_exists "/mnt1/usr/libexec/launchd_cache_loader.bak"; then
|
||||
if ! [[ -e "$MNT1/usr/libexec/launchd_cache_loader.bak" ]]; then
|
||||
echo " Creating backup..."
|
||||
ssh_cmd "/bin/cp /mnt1/usr/libexec/launchd_cache_loader /mnt1/usr/libexec/launchd_cache_loader.bak"
|
||||
/bin/cp $MNT1/usr/libexec/launchd_cache_loader $MNT1/usr/libexec/launchd_cache_loader.bak
|
||||
fi
|
||||
|
||||
scp_from "/mnt1/usr/libexec/launchd_cache_loader.bak" "$TEMP_DIR/launchd_cache_loader"
|
||||
cp "$MNT1/usr/libexec/launchd_cache_loader.bak" "$TEMP_DIR/launchd_cache_loader"
|
||||
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-launchd-cache-loader "$TEMP_DIR/launchd_cache_loader"
|
||||
ldid_sign "$TEMP_DIR/launchd_cache_loader" "com.apple.launchd_cache_loader"
|
||||
scp_to "$TEMP_DIR/launchd_cache_loader" "/mnt1/usr/libexec/launchd_cache_loader"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/usr/libexec/launchd_cache_loader"
|
||||
cp -R "$TEMP_DIR/launchd_cache_loader" "$MNT1/usr/libexec/launchd_cache_loader"
|
||||
/bin/chmod 0755 $MNT1/usr/libexec/launchd_cache_loader
|
||||
|
||||
echo " [+] launchd_cache_loader patched"
|
||||
|
||||
@@ -450,16 +383,16 @@ echo ""
|
||||
echo "[6/7] Patching mobileactivationd..."
|
||||
|
||||
# Always patch from .bak (original unpatched binary)
|
||||
if ! remote_file_exists "/mnt1/usr/libexec/mobileactivationd.bak"; then
|
||||
if ! [[ -e "$MNT1/usr/libexec/mobileactivationd.bak" ]]; then
|
||||
echo " Creating backup..."
|
||||
ssh_cmd "/bin/cp /mnt1/usr/libexec/mobileactivationd /mnt1/usr/libexec/mobileactivationd.bak"
|
||||
/bin/cp $MNT1/usr/libexec/mobileactivationd $MNT1/usr/libexec/mobileactivationd.bak
|
||||
fi
|
||||
|
||||
scp_from "/mnt1/usr/libexec/mobileactivationd.bak" "$TEMP_DIR/mobileactivationd"
|
||||
cp "$MNT1/usr/libexec/mobileactivationd.bak" "$TEMP_DIR/mobileactivationd"
|
||||
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-mobileactivationd "$TEMP_DIR/mobileactivationd"
|
||||
ldid_sign "$TEMP_DIR/mobileactivationd"
|
||||
scp_to "$TEMP_DIR/mobileactivationd" "/mnt1/usr/libexec/mobileactivationd"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/usr/libexec/mobileactivationd"
|
||||
cp -R "$TEMP_DIR/mobileactivationd" "$MNT1/usr/libexec/mobileactivationd"
|
||||
/bin/chmod 0755 $MNT1/usr/libexec/mobileactivationd
|
||||
|
||||
echo " [+] mobileactivationd patched"
|
||||
|
||||
@@ -496,8 +429,8 @@ if [[ "$needs_vphoned_build" == "1" ]]; then
|
||||
fi
|
||||
cp "$VPHONED_BIN" "$TEMP_DIR/vphoned"
|
||||
ldid_sign_ent "$TEMP_DIR/vphoned" "$VPHONED_SRC/entitlements.plist"
|
||||
scp_to "$TEMP_DIR/vphoned" "/mnt1/usr/bin/vphoned"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/usr/bin/vphoned"
|
||||
cp -R "$TEMP_DIR/vphoned" "$MNT1/usr/bin/vphoned"
|
||||
/bin/chmod 0755 $MNT1/usr/bin/vphoned
|
||||
# Keep a copy of the signed binary for host-side auto-update
|
||||
cp "$TEMP_DIR/vphoned" "$VM_DIR/.vphoned.signed"
|
||||
echo " [+] vphoned installed (signed copy at .vphoned.signed)"
|
||||
@@ -510,32 +443,32 @@ for plist in bash.plist dropbear.plist trollvnc.plist rpcserver_ios.plist; do
|
||||
cp "$INPUT_DIR/jb/LaunchDaemons/dropbear.plist" "$plist_src"
|
||||
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-dropbear-plist "$plist_src"
|
||||
fi
|
||||
scp_to "$plist_src" "/mnt1/System/Library/LaunchDaemons/"
|
||||
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/LaunchDaemons/$plist"
|
||||
cp -R "$plist_src" "$MNT1/System/Library/LaunchDaemons/"
|
||||
/bin/chmod 0644 $MNT1/System/Library/LaunchDaemons/$plist
|
||||
done
|
||||
scp_to "$VPHONED_SRC/vphoned.plist" "/mnt1/System/Library/LaunchDaemons/"
|
||||
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/LaunchDaemons/vphoned.plist"
|
||||
cp -R "$VPHONED_SRC/vphoned.plist" "$MNT1/System/Library/LaunchDaemons/"
|
||||
/bin/chmod 0644 $MNT1/System/Library/LaunchDaemons/vphoned.plist
|
||||
|
||||
# Always patch launchd.plist from .bak (original)
|
||||
echo " Patching launchd.plist..."
|
||||
if ! remote_file_exists "/mnt1/System/Library/xpc/launchd.plist.bak"; then
|
||||
if ! [[ -e "$MNT1/System/Library/xpc/launchd.plist.bak" ]]; then
|
||||
echo " Creating backup..."
|
||||
ssh_cmd "/bin/cp /mnt1/System/Library/xpc/launchd.plist /mnt1/System/Library/xpc/launchd.plist.bak"
|
||||
/bin/cp $MNT1/System/Library/xpc/launchd.plist $MNT1/System/Library/xpc/launchd.plist.bak
|
||||
fi
|
||||
|
||||
scp_from "/mnt1/System/Library/xpc/launchd.plist.bak" "$TEMP_DIR/launchd.plist"
|
||||
cp "$MNT1/System/Library/xpc/launchd.plist.bak" "$TEMP_DIR/launchd.plist"
|
||||
cp "$VPHONED_SRC/vphoned.plist" "$INPUT_DIR/jb/LaunchDaemons/"
|
||||
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" inject-daemons "$TEMP_DIR/launchd.plist" "$INPUT_DIR/jb/LaunchDaemons"
|
||||
scp_to "$TEMP_DIR/launchd.plist" "/mnt1/System/Library/xpc/launchd.plist"
|
||||
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/xpc/launchd.plist"
|
||||
cp -R "$TEMP_DIR/launchd.plist" "$MNT1/System/Library/xpc/launchd.plist"
|
||||
/bin/chmod 0644 $MNT1/System/Library/xpc/launchd.plist
|
||||
|
||||
echo " [+] LaunchDaemons installed"
|
||||
|
||||
# ═══════════ CLEANUP ═════════════════════════════════════════
|
||||
echo ""
|
||||
echo "[*] Unmounting device filesystems..."
|
||||
ssh_cmd "/sbin/umount /mnt1 2>/dev/null || true"
|
||||
ssh_cmd "/sbin/umount /mnt3 2>/dev/null || true"
|
||||
echo "[*] Unmounting image volumes..."
|
||||
/sbin/umount $MNT1 2>/dev/null || true
|
||||
/sbin/umount $MNT3 2>/dev/null || true
|
||||
|
||||
# Keep .cfw_temp/Cryptex*.dmg cached (slow to re-create)
|
||||
# Only remove temp binaries
|
||||
@@ -548,11 +481,5 @@ rm -f "$TEMP_DIR/seputil" \
|
||||
|
||||
echo ""
|
||||
echo "[+] CFW installation complete!"
|
||||
echo " Reboot to apply changes."
|
||||
echo " Boot to apply changes."
|
||||
echo " After boot, SSH will be available on port 22222 (password: alpine)"
|
||||
|
||||
if [[ "$CFW_SKIP_HALT" == "1" ]]; then
|
||||
echo "[*] CFW_SKIP_HALT=1, skipping halt."
|
||||
else
|
||||
ssh_cmd "/sbin/halt" || true
|
||||
fi
|
||||
|
||||
+111
-156
@@ -1,6 +1,7 @@
|
||||
#!/bin/zsh
|
||||
# cfw_install_exp.sh — Install base CFW + JB extensions + EXP experimental
|
||||
# patches on vphone via SSH ramdisk.
|
||||
# patches on vphone. Files are placed directly on the VM's Disk.img volumes,
|
||||
# which cfw_install_host.sh attaches and mounts on the host; the VM must be off.
|
||||
#
|
||||
# Runs the base CFW installer first (phases 1-7), the JB-specific
|
||||
# modifications (launchd jetsam patch, dylib injection, procursus bootstrap,
|
||||
@@ -12,7 +13,7 @@
|
||||
# slot re-attest of the standalone Mach-O.
|
||||
# - EXP-JB-6 : post-restore DT identity rewrite (root model /
|
||||
# target-type / compatible[0]) inside
|
||||
# /mnt5/.../devicetree.img4.
|
||||
# $MNT5/.../devicetree.img4.
|
||||
# - EXP-JB-7 : optional ProductBuildVersion rewrite in
|
||||
# SystemVersion.plist (gated on the SPOOF_BUILD env var).
|
||||
#
|
||||
@@ -133,8 +134,8 @@ fi
|
||||
|
||||
# Now run the regular CFW install. It will see the cached (patched)
|
||||
# CryptexSystemOS.dmg and use it as-is, so the patched DSC chunks land
|
||||
# in /mnt1/System/Cryptexes/OS on the device.
|
||||
CFW_SKIP_HALT=1 zsh "$SCRIPT_DIR/cfw_install.sh" "$VM_DIR"
|
||||
# in $MNT1/System/Cryptexes/OS.
|
||||
zsh "$SCRIPT_DIR/cfw_install.sh" "$VM_DIR"
|
||||
|
||||
# ════════════════════════════════════════════════════════════════
|
||||
# Step 2: JB-specific phases
|
||||
@@ -150,20 +151,6 @@ CFW_JB_ARCHIVE="cfw_jb_input.tar.zst"
|
||||
TEMP_DIR="$VM_DIR/.cfw_temp"
|
||||
DISABLE_LAUNCHD_HOOK="${DISABLE_LAUNCHD_HOOK:-0}"
|
||||
|
||||
SSH_PORT="${SSH_PORT:-2222}"
|
||||
SSH_PASS="alpine"
|
||||
SSH_USER="root"
|
||||
SSH_HOST="localhost"
|
||||
SSH_RETRY="${SSH_RETRY:-3}"
|
||||
SSHPASS_BIN=""
|
||||
SSH_OPTS=(
|
||||
-o StrictHostKeyChecking=no
|
||||
-o UserKnownHostsFile=/dev/null
|
||||
-o PreferredAuthentications=password
|
||||
-o ConnectTimeout=30
|
||||
-q
|
||||
)
|
||||
|
||||
# ── Helpers ─────────────────────────────────────────────────────
|
||||
die() {
|
||||
echo "[-] $*" >&2
|
||||
@@ -172,44 +159,11 @@ die() {
|
||||
|
||||
check_prerequisites() {
|
||||
local missing=()
|
||||
command -v sshpass &>/dev/null || missing+=("sshpass")
|
||||
command -v ldid &>/dev/null || missing+=("ldid (brew install ldid-procursus)")
|
||||
command -v xcrun &>/dev/null || missing+=("xcrun (Xcode command line tools)")
|
||||
if ((${#missing[@]} > 0)); then
|
||||
die "Missing required tools: ${missing[*]}. Run: make setup_tools"
|
||||
fi
|
||||
SSHPASS_BIN="$(command -v sshpass)"
|
||||
}
|
||||
|
||||
_sshpass() {
|
||||
"$SSHPASS_BIN" -p "$SSH_PASS" "$@"
|
||||
}
|
||||
|
||||
_ssh_retry() {
|
||||
local attempt rc label
|
||||
label=${2:-cmd}
|
||||
for ((attempt = 1; attempt <= SSH_RETRY; attempt++)); do
|
||||
"$@" && return 0
|
||||
rc=$?
|
||||
[[ $rc -ne 255 ]] && return $rc # real command failure — don't retry
|
||||
echo " [${label}] connection lost (attempt $attempt/$SSH_RETRY), retrying in 3s..." >&2
|
||||
sleep 3
|
||||
done
|
||||
return 255
|
||||
}
|
||||
|
||||
ssh_cmd() {
|
||||
_ssh_retry _sshpass ssh "${SSH_OPTS[@]}" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "$@"
|
||||
}
|
||||
scp_to() {
|
||||
_ssh_retry _sshpass scp -q "${SSH_OPTS[@]}" -P "$SSH_PORT" -r "$1" "$SSH_USER@$SSH_HOST:$2"
|
||||
}
|
||||
scp_from() {
|
||||
_ssh_retry _sshpass scp -q "${SSH_OPTS[@]}" -P "$SSH_PORT" "$SSH_USER@$SSH_HOST:$1" "$2"
|
||||
}
|
||||
|
||||
remote_file_exists() {
|
||||
ssh_cmd "test -f '$1'" 2>/dev/null
|
||||
}
|
||||
|
||||
ldid_sign() {
|
||||
@@ -250,7 +204,7 @@ build_tweakloader() {
|
||||
}
|
||||
|
||||
# Builds the libvcamcaptured.dylib injected into /usr/libexec/cameracaptured
|
||||
# via the TweakLoader allowlist. Output goes to TEMP_DIR; caller scp's the
|
||||
# via the TweakLoader allowlist. Output goes to TEMP_DIR; caller copies the
|
||||
# binary + companion plist to procursus/Library/MobileSubstrate/DynamicLibraries.
|
||||
build_libvcamcaptured() {
|
||||
local src="$SCRIPT_DIR/vcamcaptured/libvcamcaptured.m"
|
||||
@@ -319,20 +273,8 @@ build_libcamfix() {
|
||||
echo "$out"
|
||||
}
|
||||
|
||||
remote_mount() {
|
||||
local dev="$1" mnt="$2" opts="${3:-rw}"
|
||||
ssh_cmd "/bin/mkdir -p $mnt"
|
||||
if ssh_cmd "/sbin/mount | /usr/bin/grep -q ' on $mnt '"; then
|
||||
return 0
|
||||
fi
|
||||
ssh_cmd "/sbin/mount_apfs -o $opts $dev $mnt 2>/dev/null || true"
|
||||
if ! ssh_cmd "/sbin/mount | /usr/bin/grep -q ' on $mnt '"; then
|
||||
die "Failed to mount $dev at $mnt (opts=$opts). Make sure the ramdisk was booted with the expected patched kernel."
|
||||
fi
|
||||
}
|
||||
|
||||
get_boot_manifest_hash() {
|
||||
ssh_cmd "/bin/ls /mnt5 2>/dev/null" | awk 'length($0)==96{print; exit}'
|
||||
/bin/ls $MNT5 2>/dev/null | awk 'length($0)==96{print; exit}'
|
||||
}
|
||||
|
||||
# ── Setup JB input resources ──────────────────────────────────
|
||||
@@ -370,9 +312,25 @@ apply_dev_overlay() {
|
||||
die "Dev overlay not found (cfw_dev/rpcserver_ios)"
|
||||
}
|
||||
|
||||
# Host-mode transport override (CFW_HOST_MODE=1): run install against image
|
||||
# volumes mounted locally on the host instead of a device over SSH.
|
||||
[[ -n "${CFW_HOST_MODE:-}" ]] && source "$SCRIPT_DIR/cfw_host_mode.sh"
|
||||
# The VM's Disk.img is attached on the host by cfw_install_host.sh; its APFS
|
||||
# volumes are mounted here and every file is placed with plain cp/chmod/etc.
|
||||
# (the VM is off — nothing runs "on the device").
|
||||
: "${CFW_HOST_CONTAINER:?CFW_HOST_CONTAINER unset — run via cfw_install_host.sh}"
|
||||
HOST_MNT="${CFW_HOST_MNT:-/private/tmp/cfwhost}"
|
||||
MNT1="$HOST_MNT/mnt1" # disk1s1 (System / rootfs)
|
||||
MNT3="$HOST_MNT/mnt3" # disk1s3
|
||||
MNT5="$HOST_MNT/mnt5" # disk1s5 (per-boot-manifest OS dir / procursus bootstrap)
|
||||
TAR="$(command -v gtar 2>/dev/null || echo /opt/homebrew/bin/gtar)" # macOS bsdtar lacks GNU tar flags
|
||||
mkdir -p "$HOST_MNT"
|
||||
|
||||
# Mount an APFS volume of the attached image container at a host mount point.
|
||||
mount_vol() { # mount_vol <slice, e.g. s1> <mountpoint> [opts]
|
||||
local dev="/dev/${CFW_HOST_CONTAINER}$1" mnt="$2" opts="${3:-rw}"
|
||||
/bin/mkdir -p "$mnt"
|
||||
/sbin/mount | /usr/bin/grep -q " on $mnt " && return 0
|
||||
/sbin/mount_apfs -o "$opts" "$dev" "$mnt" 2>/dev/null || true
|
||||
/sbin/mount | /usr/bin/grep -q " on $mnt " || die "mount failed: $dev -> $mnt"
|
||||
}
|
||||
|
||||
# ── Check JB prerequisites ────────────────────────────────────
|
||||
command -v zstd >/dev/null 2>&1 || die "'zstd' not found (required for JB bootstrap phase)"
|
||||
@@ -385,19 +343,19 @@ check_prerequisites
|
||||
|
||||
mkdir -p "$TEMP_DIR"
|
||||
|
||||
# Mount device rootfs (may already be mounted from base install)
|
||||
remote_mount /dev/disk1s1 /mnt1
|
||||
# Mount the image's System volume (may already be mounted from base install)
|
||||
mount_vol s1 "$MNT1"
|
||||
|
||||
# ═══════════ JB-1 PATCH LAUNCHD (JETSAM + DYLIB INJECTION) ════
|
||||
echo ""
|
||||
echo "[JB-1] Patching launchd (jetsam guard + hook injection)..."
|
||||
|
||||
if ! remote_file_exists "/mnt1/sbin/launchd.bak"; then
|
||||
if ! [[ -e "$MNT1/sbin/launchd.bak" ]]; then
|
||||
echo " Creating backup..."
|
||||
ssh_cmd "/bin/cp /mnt1/sbin/launchd /mnt1/sbin/launchd.bak"
|
||||
/bin/cp $MNT1/sbin/launchd $MNT1/sbin/launchd.bak
|
||||
fi
|
||||
|
||||
scp_from "/mnt1/sbin/launchd.bak" "$TEMP_DIR/launchd"
|
||||
cp "$MNT1/sbin/launchd.bak" "$TEMP_DIR/launchd"
|
||||
|
||||
# Extract original entitlements before patching (must preserve for spawn permissions)
|
||||
echo " Extracting original entitlements..."
|
||||
@@ -428,8 +386,8 @@ if [[ -s "$TEMP_DIR/launchd.entitlements" ]]; then
|
||||
else
|
||||
ldid_sign "$TEMP_DIR/launchd"
|
||||
fi
|
||||
scp_to "$TEMP_DIR/launchd" "/mnt1/sbin/launchd"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/sbin/launchd"
|
||||
cp -R "$TEMP_DIR/launchd" "$MNT1/sbin/launchd"
|
||||
/bin/chmod 0755 $MNT1/sbin/launchd
|
||||
|
||||
echo " [+] launchd patched"
|
||||
|
||||
@@ -438,10 +396,10 @@ echo ""
|
||||
echo "[JB-2] Installing iosbinpack64..."
|
||||
|
||||
apply_dev_overlay
|
||||
scp_to "$VM_DIR/$CFW_INPUT/jb/iosbinpack64.tar" "/mnt1"
|
||||
ssh_cmd "/usr/bin/tar --preserve-permissions --no-overwrite-dir \
|
||||
-xf /mnt1/iosbinpack64.tar -C /mnt1"
|
||||
ssh_cmd "/bin/rm -f /mnt1/iosbinpack64.tar"
|
||||
cp -R "$VM_DIR/$CFW_INPUT/jb/iosbinpack64.tar" "$MNT1"
|
||||
"$TAR" --preserve-permissions --no-overwrite-dir \
|
||||
-xf $MNT1/iosbinpack64.tar -C $MNT1
|
||||
/bin/rm -f $MNT1/iosbinpack64.tar
|
||||
|
||||
echo " [+] iosbinpack64 installed"
|
||||
|
||||
@@ -449,13 +407,13 @@ echo " [+] iosbinpack64 installed"
|
||||
echo ""
|
||||
echo "[JB-3] Patching debugserver entitlements..."
|
||||
|
||||
scp_from "/mnt1/usr/libexec/debugserver" "$TEMP_DIR/debugserver"
|
||||
cp "$MNT1/usr/libexec/debugserver" "$TEMP_DIR/debugserver"
|
||||
ldid -e "$TEMP_DIR/debugserver" > "$TEMP_DIR/debugserver-entitlements.plist"
|
||||
plutil -remove seatbelt-profiles "$TEMP_DIR/debugserver-entitlements.plist" || true
|
||||
plutil -insert task_for_pid-allow -bool YES "$TEMP_DIR/debugserver-entitlements.plist" || true
|
||||
ldid_sign_ent "$TEMP_DIR/debugserver" "$TEMP_DIR/debugserver-entitlements.plist"
|
||||
scp_to "$TEMP_DIR/debugserver" "/mnt1/usr/libexec/debugserver"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/usr/libexec/debugserver"
|
||||
cp -R "$TEMP_DIR/debugserver" "$MNT1/usr/libexec/debugserver"
|
||||
/bin/chmod 0755 $MNT1/usr/libexec/debugserver
|
||||
|
||||
echo " [+] debugserver entitlements patched"
|
||||
|
||||
@@ -486,10 +444,10 @@ echo " [+] debugserver entitlements patched"
|
||||
echo ""
|
||||
echo "[EXP-JB-3.5] Patching watchdogd hv_vmm_present cache..."
|
||||
|
||||
scp_from "/mnt1/usr/libexec/watchdogd" "$TEMP_DIR/watchdogd"
|
||||
cp "$MNT1/usr/libexec/watchdogd" "$TEMP_DIR/watchdogd"
|
||||
"$SCRIPT_DIR/patch_hv_vmm_userland.sh" watchdogd "$TEMP_DIR/watchdogd"
|
||||
scp_to "$TEMP_DIR/watchdogd" "/mnt1/usr/libexec/watchdogd"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/usr/libexec/watchdogd"
|
||||
cp -R "$TEMP_DIR/watchdogd" "$MNT1/usr/libexec/watchdogd"
|
||||
/bin/chmod 0755 $MNT1/usr/libexec/watchdogd
|
||||
|
||||
echo " [+] watchdogd patched"
|
||||
|
||||
@@ -498,9 +456,9 @@ echo " [+] watchdogd patched"
|
||||
echo ""
|
||||
echo "[JB-4] Installing procursus bootstrap..."
|
||||
|
||||
remote_mount /dev/disk1s5 /mnt5
|
||||
mount_vol s5 "$MNT5"
|
||||
BOOT_HASH="$(get_boot_manifest_hash)"
|
||||
[[ -n "$BOOT_HASH" ]] || die "Could not find 96-char boot manifest hash in /mnt5"
|
||||
[[ -n "$BOOT_HASH" ]] || die "Could not find 96-char boot manifest hash in $MNT5"
|
||||
echo " Boot manifest hash: $BOOT_HASH"
|
||||
|
||||
BOOTSTRAP_ZST="$JB_INPUT_DIR/jb/bootstrap-iphoneos-arm64.tar.zst"
|
||||
@@ -510,27 +468,27 @@ SILEO_DEB="$JB_INPUT_DIR/jb/org.coolstar.sileo_2.5.1_iphoneos-arm64.deb"
|
||||
BOOTSTRAP_TAR="$TEMP_DIR/bootstrap-iphoneos-arm64.tar"
|
||||
zstd -d -f "$BOOTSTRAP_ZST" -o "$BOOTSTRAP_TAR"
|
||||
|
||||
scp_to "$BOOTSTRAP_TAR" "/mnt5/$BOOT_HASH/bootstrap-iphoneos-arm64.tar"
|
||||
cp -R "$BOOTSTRAP_TAR" "$MNT5/$BOOT_HASH/bootstrap-iphoneos-arm64.tar"
|
||||
if [[ -f "$SILEO_DEB" ]]; then
|
||||
scp_to "$SILEO_DEB" "/mnt5/$BOOT_HASH/org.coolstar.sileo_2.5.1_iphoneos-arm64.deb"
|
||||
cp -R "$SILEO_DEB" "$MNT5/$BOOT_HASH/org.coolstar.sileo_2.5.1_iphoneos-arm64.deb"
|
||||
fi
|
||||
|
||||
JB_DIR_NAME="jb-vphone"
|
||||
ssh_cmd "/bin/rm -rf /mnt5/$BOOT_HASH/jb"
|
||||
ssh_cmd "/bin/rm -rf /mnt5/$BOOT_HASH/$JB_DIR_NAME"
|
||||
ssh_cmd "/bin/mkdir -p /mnt5/$BOOT_HASH/$JB_DIR_NAME"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt5/$BOOT_HASH/$JB_DIR_NAME"
|
||||
ssh_cmd "/usr/sbin/chown 0:0 /mnt5/$BOOT_HASH/$JB_DIR_NAME"
|
||||
ssh_cmd "/usr/bin/tar --preserve-permissions -xf /mnt5/$BOOT_HASH/bootstrap-iphoneos-arm64.tar \
|
||||
-C /mnt5/$BOOT_HASH/$JB_DIR_NAME/"
|
||||
ssh_cmd "/bin/mv /mnt5/$BOOT_HASH/$JB_DIR_NAME/var /mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus"
|
||||
ssh_cmd "/bin/mv /mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus/jb/* /mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus 2>/dev/null || true"
|
||||
ssh_cmd "/bin/rm -rf /mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus/jb"
|
||||
ssh_cmd "/bin/rm -f /mnt5/$BOOT_HASH/bootstrap-iphoneos-arm64.tar"
|
||||
/bin/rm -rf $MNT5/$BOOT_HASH/jb
|
||||
/bin/rm -rf $MNT5/$BOOT_HASH/$JB_DIR_NAME
|
||||
/bin/mkdir -p $MNT5/$BOOT_HASH/$JB_DIR_NAME
|
||||
/bin/chmod 0755 $MNT5/$BOOT_HASH/$JB_DIR_NAME
|
||||
/usr/sbin/chown 0:0 $MNT5/$BOOT_HASH/$JB_DIR_NAME
|
||||
"$TAR" --preserve-permissions -xf $MNT5/$BOOT_HASH/bootstrap-iphoneos-arm64.tar \
|
||||
-C $MNT5/$BOOT_HASH/$JB_DIR_NAME/
|
||||
/bin/mv $MNT5/$BOOT_HASH/$JB_DIR_NAME/var $MNT5/$BOOT_HASH/$JB_DIR_NAME/procursus
|
||||
mv "$MNT5/$BOOT_HASH/$JB_DIR_NAME/procursus/jb"/*(N) "$MNT5/$BOOT_HASH/$JB_DIR_NAME/procursus" 2>/dev/null || true
|
||||
/bin/rm -rf $MNT5/$BOOT_HASH/$JB_DIR_NAME/procursus/jb
|
||||
/bin/rm -f $MNT5/$BOOT_HASH/bootstrap-iphoneos-arm64.tar
|
||||
rm -f "$BOOTSTRAP_TAR"
|
||||
|
||||
# NOTE: /var/jb symlink is created on first normal boot by vphone_jb_setup.sh
|
||||
# (Data volume is encrypted and not mountable from ramdisk).
|
||||
# (Data volume is encrypted and not mountable at install time).
|
||||
|
||||
echo " [+] procursus bootstrap installed"
|
||||
|
||||
@@ -543,9 +501,9 @@ if [[ -d "$BASEBIN_DIR" ]]; then
|
||||
|
||||
# Clean previous dylibs before re-uploading
|
||||
echo " Cleaning old /cores/ dylibs..."
|
||||
ssh_cmd "/bin/rm -rf /mnt1/cores"
|
||||
ssh_cmd "/bin/mkdir -p /mnt1/cores"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/cores"
|
||||
/bin/rm -rf $MNT1/cores
|
||||
/bin/mkdir -p $MNT1/cores
|
||||
/bin/chmod 0755 $MNT1/cores
|
||||
|
||||
# Install all pre-built dylibs from basebin payload
|
||||
for dylib in "$BASEBIN_DIR"/*.dylib; do
|
||||
@@ -553,8 +511,8 @@ if [[ -d "$BASEBIN_DIR" ]]; then
|
||||
dylib_name="$(basename "$dylib")"
|
||||
echo " Installing $dylib_name..."
|
||||
ldid_sign "$dylib"
|
||||
scp_to "$dylib" "/mnt1/cores/$dylib_name"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/cores/$dylib_name"
|
||||
cp -R "$dylib" "$MNT1/cores/$dylib_name"
|
||||
/bin/chmod 0755 $MNT1/cores/$dylib_name
|
||||
done
|
||||
|
||||
# Short alias for launchdhook (header space is tight)
|
||||
@@ -562,9 +520,9 @@ if [[ -d "$BASEBIN_DIR" ]]; then
|
||||
echo " Installing short launchdhook alias at /b..."
|
||||
cp "$BASEBIN_DIR/launchdhook.dylib" "$TEMP_DIR/b"
|
||||
ldid_sign "$TEMP_DIR/b"
|
||||
ssh_cmd "/bin/rm -f /mnt1/b"
|
||||
scp_to "$TEMP_DIR/b" "/mnt1/b"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/b"
|
||||
/bin/rm -f $MNT1/b
|
||||
cp -R "$TEMP_DIR/b" "$MNT1/b"
|
||||
/bin/chmod 0755 $MNT1/b
|
||||
fi
|
||||
|
||||
echo " [+] BaseBin hooks deployed"
|
||||
@@ -575,10 +533,10 @@ echo ""
|
||||
echo "[JB-4] Building and installing TweakLoader..."
|
||||
|
||||
TWEAKLOADER_OUT="$(build_tweakloader)"
|
||||
ssh_cmd "/bin/mkdir -p /mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus/usr/lib"
|
||||
scp_to "$TWEAKLOADER_OUT" "/mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus/usr/lib/TweakLoader.dylib"
|
||||
ssh_cmd "/usr/sbin/chown 0:0 /mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus/usr/lib/TweakLoader.dylib"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus/usr/lib/TweakLoader.dylib"
|
||||
/bin/mkdir -p $MNT5/$BOOT_HASH/$JB_DIR_NAME/procursus/usr/lib
|
||||
cp -R "$TWEAKLOADER_OUT" "$MNT5/$BOOT_HASH/$JB_DIR_NAME/procursus/usr/lib/TweakLoader.dylib"
|
||||
/usr/sbin/chown 0:0 $MNT5/$BOOT_HASH/$JB_DIR_NAME/procursus/usr/lib/TweakLoader.dylib
|
||||
/bin/chmod 0755 $MNT5/$BOOT_HASH/$JB_DIR_NAME/procursus/usr/lib/TweakLoader.dylib
|
||||
|
||||
echo " [+] TweakLoader installed to procursus/usr/lib/TweakLoader.dylib"
|
||||
|
||||
@@ -590,18 +548,18 @@ echo " [+] TweakLoader installed to procursus/usr/lib/TweakLoader.dylib"
|
||||
echo ""
|
||||
echo "[JB-4.1] Building and installing libvcamcaptured..."
|
||||
LIBVCAM_OUT="$(build_libvcamcaptured)"
|
||||
LIBVCAM_DIR="/mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus/Library/MobileSubstrate/DynamicLibraries"
|
||||
ssh_cmd "/bin/mkdir -p $LIBVCAM_DIR"
|
||||
scp_to "$LIBVCAM_OUT" "$LIBVCAM_DIR/libvcamcaptured.dylib"
|
||||
ssh_cmd "/usr/sbin/chown 0:0 $LIBVCAM_DIR/libvcamcaptured.dylib"
|
||||
ssh_cmd "/bin/chmod 0755 $LIBVCAM_DIR/libvcamcaptured.dylib"
|
||||
LIBVCAM_DIR="$MNT5/$BOOT_HASH/$JB_DIR_NAME/procursus/Library/MobileSubstrate/DynamicLibraries"
|
||||
/bin/mkdir -p $LIBVCAM_DIR
|
||||
cp -R "$LIBVCAM_OUT" "$LIBVCAM_DIR/libvcamcaptured.dylib"
|
||||
/usr/sbin/chown 0:0 $LIBVCAM_DIR/libvcamcaptured.dylib
|
||||
/bin/chmod 0755 $LIBVCAM_DIR/libvcamcaptured.dylib
|
||||
|
||||
# The plist tells TweakLoader to load the dylib only inside cameracaptured
|
||||
# (Filter.Executables = ["cameracaptured"]); keep it next to the dylib.
|
||||
LIBVCAM_PLIST="$SCRIPT_DIR/vcamcaptured/libvcamcaptured.plist"
|
||||
if [[ -f "$LIBVCAM_PLIST" ]]; then
|
||||
scp_to "$LIBVCAM_PLIST" "$LIBVCAM_DIR/libvcamcaptured.plist"
|
||||
ssh_cmd "/bin/chmod 0644 $LIBVCAM_DIR/libvcamcaptured.plist"
|
||||
cp -R "$LIBVCAM_PLIST" "$LIBVCAM_DIR/libvcamcaptured.plist"
|
||||
/bin/chmod 0644 $LIBVCAM_DIR/libvcamcaptured.plist
|
||||
fi
|
||||
echo " [+] libvcamcaptured installed to procursus/Library/MobileSubstrate/DynamicLibraries/"
|
||||
|
||||
@@ -614,14 +572,14 @@ echo " [+] libvcamcaptured installed to procursus/Library/MobileSubstrate/Dynam
|
||||
echo ""
|
||||
echo "[JB-4.2] Building and installing libcamfix..."
|
||||
LIBCAMFIX_OUT="$(build_libcamfix)"
|
||||
scp_to "$LIBCAMFIX_OUT" "$LIBVCAM_DIR/libcamfix.dylib"
|
||||
ssh_cmd "/usr/sbin/chown 0:0 $LIBVCAM_DIR/libcamfix.dylib"
|
||||
ssh_cmd "/bin/chmod 0755 $LIBVCAM_DIR/libcamfix.dylib"
|
||||
cp -R "$LIBCAMFIX_OUT" "$LIBVCAM_DIR/libcamfix.dylib"
|
||||
/usr/sbin/chown 0:0 $LIBVCAM_DIR/libcamfix.dylib
|
||||
/bin/chmod 0755 $LIBVCAM_DIR/libcamfix.dylib
|
||||
|
||||
LIBCAMFIX_PLIST="$SCRIPT_DIR/camfix/libcamfix.plist"
|
||||
if [[ -f "$LIBCAMFIX_PLIST" ]]; then
|
||||
scp_to "$LIBCAMFIX_PLIST" "$LIBVCAM_DIR/libcamfix.plist"
|
||||
ssh_cmd "/bin/chmod 0644 $LIBVCAM_DIR/libcamfix.plist"
|
||||
cp -R "$LIBCAMFIX_PLIST" "$LIBVCAM_DIR/libcamfix.plist"
|
||||
/bin/chmod 0644 $LIBVCAM_DIR/libcamfix.plist
|
||||
fi
|
||||
echo " [+] libcamfix installed to procursus/Library/MobileSubstrate/DynamicLibraries/"
|
||||
|
||||
@@ -633,17 +591,17 @@ echo "[JB-5] Deploying first-boot setup..."
|
||||
SETUP_SCRIPT="$SCRIPT_DIR/vphone_jb_setup.sh"
|
||||
SETUP_PLIST="$SCRIPT_DIR/vphone_jb_setup.plist"
|
||||
if [[ -f "$SETUP_SCRIPT" ]]; then
|
||||
scp_to "$SETUP_SCRIPT" "/mnt1/cores/vphone_jb_setup.sh"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/cores/vphone_jb_setup.sh"
|
||||
cp -R "$SETUP_SCRIPT" "$MNT1/cores/vphone_jb_setup.sh"
|
||||
/bin/chmod 0755 $MNT1/cores/vphone_jb_setup.sh
|
||||
echo " [+] vphone_jb_setup.sh -> /cores/"
|
||||
fi
|
||||
if [[ -f "$SETUP_PLIST" ]]; then
|
||||
scp_to "$SETUP_PLIST" "/mnt1/System/Library/LaunchDaemons/com.vphone.jb-setup.plist"
|
||||
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/LaunchDaemons/com.vphone.jb-setup.plist"
|
||||
cp -R "$SETUP_PLIST" "$MNT1/System/Library/LaunchDaemons/com.vphone.jb-setup.plist"
|
||||
/bin/chmod 0644 $MNT1/System/Library/LaunchDaemons/com.vphone.jb-setup.plist
|
||||
|
||||
# Inject into launchd.plist so launchd starts it at boot
|
||||
echo " Injecting com.vphone.jb-setup into launchd.plist..."
|
||||
scp_from "/mnt1/System/Library/xpc/launchd.plist" "$TEMP_DIR/launchd.plist"
|
||||
cp "$MNT1/System/Library/xpc/launchd.plist" "$TEMP_DIR/launchd.plist"
|
||||
"$PYTHON3" -c "
|
||||
import plistlib, sys
|
||||
with open(sys.argv[1], 'rb') as f:
|
||||
@@ -654,8 +612,8 @@ target.setdefault('LaunchDaemons', {})['/System/Library/LaunchDaemons/com.vphone
|
||||
with open(sys.argv[1], 'wb') as f:
|
||||
plistlib.dump(target, f, sort_keys=False)
|
||||
" "$TEMP_DIR/launchd.plist" "$SETUP_PLIST"
|
||||
scp_to "$TEMP_DIR/launchd.plist" "/mnt1/System/Library/xpc/launchd.plist"
|
||||
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/xpc/launchd.plist"
|
||||
cp -R "$TEMP_DIR/launchd.plist" "$MNT1/System/Library/xpc/launchd.plist"
|
||||
/bin/chmod 0644 $MNT1/System/Library/xpc/launchd.plist
|
||||
echo " [+] com.vphone.jb-setup.plist injected into launchd.plist"
|
||||
fi
|
||||
|
||||
@@ -673,9 +631,9 @@ fi
|
||||
# image4_validate_property_callback bypass patches accept any IM4P
|
||||
# contents on subsequent boots.
|
||||
#
|
||||
# /mnt5 is still mounted at this point in the install flow (the umount
|
||||
# happens in the CLEANUP block below). We scp the live devicetree.img4
|
||||
# down, patch it on the host, scp it back. Next boot, iBoot loads the
|
||||
# $MNT5 is still mounted at this point in the install flow (the umount
|
||||
# happens in the CLEANUP block below). We copy the live devicetree.img4
|
||||
# out, patch it on the host, copy it back. Next boot, iBoot loads the
|
||||
# modified DT, kernel populates machine_info from the new values, and
|
||||
# sysctl hw.machine / hw.product / hw.model flip to iPhone17,3 / D47 /
|
||||
# (whatever IOPlatformExpert resolves from compatible[0]=D47AP).
|
||||
@@ -688,14 +646,14 @@ fi
|
||||
if [[ -z "$BOOT_HASH" ]]; then
|
||||
echo " [-] BOOT_HASH not discoverable, skipping EXP-JB-6"
|
||||
else
|
||||
JB6_DT_REMOTE="/mnt5/$BOOT_HASH/usr/standalone/firmware/devicetree.img4"
|
||||
JB6_DT_REMOTE="$MNT5/$BOOT_HASH/usr/standalone/firmware/devicetree.img4"
|
||||
JB6_DT_LOCAL="$TEMP_DIR/devicetree.img4"
|
||||
if ssh_cmd "test -f '$JB6_DT_REMOTE'" 2>/dev/null; then
|
||||
scp_from "$JB6_DT_REMOTE" "$JB6_DT_LOCAL"
|
||||
if [[ -e "$JB6_DT_REMOTE" ]]; then
|
||||
cp "$JB6_DT_REMOTE" "$JB6_DT_LOCAL"
|
||||
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw_patch_post_restore_dt.py" "$JB6_DT_LOCAL"
|
||||
scp_to "$JB6_DT_LOCAL" "$JB6_DT_REMOTE"
|
||||
ssh_cmd "/usr/sbin/chown 0:0 $JB6_DT_REMOTE"
|
||||
ssh_cmd "/bin/chmod 0644 $JB6_DT_REMOTE"
|
||||
cp -R "$JB6_DT_LOCAL" "$JB6_DT_REMOTE"
|
||||
/usr/sbin/chown 0:0 $JB6_DT_REMOTE
|
||||
/bin/chmod 0644 $JB6_DT_REMOTE
|
||||
echo " [+] devicetree.img4 rewritten in place"
|
||||
else
|
||||
echo " [-] $JB6_DT_REMOTE not found, skipping EXP-JB-6"
|
||||
@@ -719,8 +677,8 @@ fi
|
||||
# Both are plain plist files (no Apple signature on individual plists),
|
||||
# so no image4 / cdHash / TXM concerns. Both volumes are writable at
|
||||
# install time:
|
||||
# /mnt1 (rootfs) — writable before the install-time seal is established
|
||||
# /mnt5 (preboot) — apfs writable
|
||||
# $MNT1 (rootfs) — writable before the install-time seal is established
|
||||
# $MNT5 (preboot) — apfs writable
|
||||
#
|
||||
# After this, Settings -> About -> Build, MG BuildVersion key, and every
|
||||
# framework that reads SystemVersion.plist see the new identifier.
|
||||
@@ -733,15 +691,15 @@ if [[ -n "${SPOOF_BUILD:-}" ]]; then
|
||||
echo "[EXP-JB-7] Rewriting ProductBuildVersion to $SPOOF_BUILD in SystemVersion plists..."
|
||||
|
||||
for jb7_remote in \
|
||||
"/mnt1/System/Library/CoreServices/SystemVersion.plist" \
|
||||
"/mnt5/Cryptexes/OS/System/Library/CoreServices/SystemVersion.plist"
|
||||
"$MNT1/System/Library/CoreServices/SystemVersion.plist" \
|
||||
"$MNT5/Cryptexes/OS/System/Library/CoreServices/SystemVersion.plist"
|
||||
do
|
||||
if ssh_cmd "test -f '$jb7_remote'" 2>/dev/null; then
|
||||
if [[ -e "$jb7_remote" ]]; then
|
||||
jb7_local="$TEMP_DIR/$(echo "$jb7_remote" | tr '/' '_').plist"
|
||||
scp_from "$jb7_remote" "$jb7_local"
|
||||
cp "$jb7_remote" "$jb7_local"
|
||||
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw_patch_build_version.py" \
|
||||
"$jb7_local" "$SPOOF_BUILD"
|
||||
scp_to "$jb7_local" "$jb7_remote"
|
||||
cp -R "$jb7_local" "$jb7_remote"
|
||||
else
|
||||
echo " [-] $jb7_remote not found, skipping"
|
||||
fi
|
||||
@@ -753,11 +711,10 @@ fi
|
||||
|
||||
# ═══════════ CLEANUP ═════════════════════════════════════════
|
||||
echo ""
|
||||
echo "[*] Unmounting device filesystems..."
|
||||
ssh_cmd "/sbin/umount /mnt1 2>/dev/null || true"
|
||||
ssh_cmd "/sbin/umount /mnt2 2>/dev/null || true"
|
||||
ssh_cmd "/sbin/umount /mnt3 2>/dev/null || true"
|
||||
ssh_cmd "/sbin/umount /mnt5 2>/dev/null || true"
|
||||
echo "[*] Unmounting image volumes..."
|
||||
/sbin/umount $MNT1 2>/dev/null || true
|
||||
/sbin/umount $MNT3 2>/dev/null || true
|
||||
/sbin/umount $MNT5 2>/dev/null || true
|
||||
|
||||
echo "[*] Cleaning up temp binaries..."
|
||||
rm -f "$TEMP_DIR/launchd" \
|
||||
@@ -765,7 +722,5 @@ rm -f "$TEMP_DIR/launchd" \
|
||||
|
||||
echo ""
|
||||
echo "[+] CFW + JB + EXP installation complete!"
|
||||
echo " Reboot to apply changes."
|
||||
echo " Boot to apply changes."
|
||||
echo " After boot, SSH will be available on port 22222 (password: alpine)"
|
||||
|
||||
ssh_cmd "/sbin/halt" || true
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
#!/bin/zsh
|
||||
# cfw_install_host.sh — ramdisk-free CFW install.
|
||||
# cfw_install_host.sh — CFW install by host-mounting the VM's Disk.img.
|
||||
#
|
||||
# Replaces the DFU + ramdisk_send + iproxy + SSH install path: places every
|
||||
# CFW file by mounting the VM's Disk.img on the host (CFW_HOST_MODE, see
|
||||
# cfw_host_mode.sh) and flips the boot snapshot offline
|
||||
# (tools/apfs_snap_rename.py) so the VM boots the live volume. The resulting
|
||||
# VM is identical to the ramdisk-installed one, minus the ramdisk round-trips.
|
||||
# Attaches the VM's Disk.img on the host and hands the container to the variant
|
||||
# installer (cfw_install*.sh), which mounts the APFS volumes and places every
|
||||
# CFW file directly. Then flips the boot snapshot offline
|
||||
# (tools/apfs_snap_rename.py) so the VM boots the live volume.
|
||||
#
|
||||
# Prereqs: VM restored (make restore) and powered off; host has gnu-tar, ipsw,
|
||||
# aea, ldid, zstd, project venv (make setup_tools). SIP disabled (project
|
||||
@@ -63,15 +62,15 @@ CONT="${SYS%s*}"
|
||||
echo "[*] attached: container=$CONT system=$SYS"
|
||||
|
||||
cleanup() {
|
||||
for m in /private/tmp/cfwhost/mnt1 /private/tmp/cfwhost/mnt2 /private/tmp/cfwhost/mnt3 /private/tmp/cfwhost/mnt5; do
|
||||
for m in /private/tmp/cfwhost/mnt1 /private/tmp/cfwhost/mnt3 /private/tmp/cfwhost/mnt5; do
|
||||
umount "$m" 2>/dev/null || true
|
||||
done
|
||||
hdiutil detach "$BASEDISK" 2>/dev/null || diskutil eject "$BASEDISK" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "[*] running $INSTALLER in CFW_HOST_MODE (files placed on host mounts)..."
|
||||
( cd "$VM_DIR" && CFW_HOST_MODE=1 CFW_HOST_CONTAINER="$CONT" _VPHONE_PATH="$P" \
|
||||
echo "[*] running $INSTALLER (files placed on host mounts)..."
|
||||
( cd "$VM_DIR" && CFW_HOST_CONTAINER="$CONT" _VPHONE_PATH="$P" \
|
||||
${SPOOF_BUILD:+SPOOF_BUILD="$SPOOF_BUILD"} zsh "$SCRIPT_DIR/$INSTALLER" . )
|
||||
|
||||
cleanup
|
||||
|
||||
+79
-123
@@ -1,9 +1,10 @@
|
||||
#!/bin/zsh
|
||||
# cfw_install_jb.sh — Install base CFW + JB extensions on vphone via SSH ramdisk.
|
||||
# cfw_install_jb.sh — Install base CFW + JB extensions on vphone.
|
||||
#
|
||||
# Runs the base CFW installer first (phases 1-7), then applies JB-specific
|
||||
# modifications: launchd jetsam patch, dylib injection, procursus bootstrap,
|
||||
# and BaseBin hook deployment.
|
||||
# and BaseBin hook deployment. Files are placed directly on the VM's Disk.img
|
||||
# volumes, which cfw_install_host.sh attaches and mounts on the host; VM off.
|
||||
#
|
||||
# Prerequisites (in addition to cfw_install.sh requirements):
|
||||
# - cfw_jb_input/ or resources/cfw_jb_input.tar.zst present
|
||||
@@ -31,11 +32,11 @@ _resolve_python3() {
|
||||
PYTHON3="$(_resolve_python3)"
|
||||
|
||||
# ════════════════════════════════════════════════════════════════
|
||||
# Step 1: Run base CFW install (skip halt — we continue with JB phases)
|
||||
# Step 1: Run base CFW install, then continue with JB phases
|
||||
# ════════════════════════════════════════════════════════════════
|
||||
echo "[*] cfw_install_jb.sh — Installing CFW + JB extensions..."
|
||||
echo ""
|
||||
CFW_SKIP_HALT=1 zsh "$SCRIPT_DIR/cfw_install.sh" "$VM_DIR"
|
||||
zsh "$SCRIPT_DIR/cfw_install.sh" "$VM_DIR"
|
||||
|
||||
# ════════════════════════════════════════════════════════════════
|
||||
# Step 2: JB-specific phases
|
||||
@@ -51,20 +52,6 @@ CFW_JB_ARCHIVE="cfw_jb_input.tar.zst"
|
||||
TEMP_DIR="$VM_DIR/.cfw_temp"
|
||||
DISABLE_LAUNCHD_HOOK="${DISABLE_LAUNCHD_HOOK:-0}"
|
||||
|
||||
SSH_PORT="${SSH_PORT:-2222}"
|
||||
SSH_PASS="alpine"
|
||||
SSH_USER="root"
|
||||
SSH_HOST="localhost"
|
||||
SSH_RETRY="${SSH_RETRY:-3}"
|
||||
SSHPASS_BIN=""
|
||||
SSH_OPTS=(
|
||||
-o StrictHostKeyChecking=no
|
||||
-o UserKnownHostsFile=/dev/null
|
||||
-o PreferredAuthentications=password
|
||||
-o ConnectTimeout=30
|
||||
-q
|
||||
)
|
||||
|
||||
# ── Helpers ─────────────────────────────────────────────────────
|
||||
die() {
|
||||
echo "[-] $*" >&2
|
||||
@@ -73,44 +60,11 @@ die() {
|
||||
|
||||
check_prerequisites() {
|
||||
local missing=()
|
||||
command -v sshpass &>/dev/null || missing+=("sshpass")
|
||||
command -v ldid &>/dev/null || missing+=("ldid (brew install ldid-procursus)")
|
||||
command -v xcrun &>/dev/null || missing+=("xcrun (Xcode command line tools)")
|
||||
if ((${#missing[@]} > 0)); then
|
||||
die "Missing required tools: ${missing[*]}. Run: make setup_tools"
|
||||
fi
|
||||
SSHPASS_BIN="$(command -v sshpass)"
|
||||
}
|
||||
|
||||
_sshpass() {
|
||||
"$SSHPASS_BIN" -p "$SSH_PASS" "$@"
|
||||
}
|
||||
|
||||
_ssh_retry() {
|
||||
local attempt rc label
|
||||
label=${2:-cmd}
|
||||
for ((attempt = 1; attempt <= SSH_RETRY; attempt++)); do
|
||||
"$@" && return 0
|
||||
rc=$?
|
||||
[[ $rc -ne 255 ]] && return $rc # real command failure — don't retry
|
||||
echo " [${label}] connection lost (attempt $attempt/$SSH_RETRY), retrying in 3s..." >&2
|
||||
sleep 3
|
||||
done
|
||||
return 255
|
||||
}
|
||||
|
||||
ssh_cmd() {
|
||||
_ssh_retry _sshpass ssh "${SSH_OPTS[@]}" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "$@"
|
||||
}
|
||||
scp_to() {
|
||||
_ssh_retry _sshpass scp -q "${SSH_OPTS[@]}" -P "$SSH_PORT" -r "$1" "$SSH_USER@$SSH_HOST:$2"
|
||||
}
|
||||
scp_from() {
|
||||
_ssh_retry _sshpass scp -q "${SSH_OPTS[@]}" -P "$SSH_PORT" "$SSH_USER@$SSH_HOST:$1" "$2"
|
||||
}
|
||||
|
||||
remote_file_exists() {
|
||||
ssh_cmd "test -f '$1'" 2>/dev/null
|
||||
}
|
||||
|
||||
ldid_sign() {
|
||||
@@ -150,20 +104,8 @@ build_tweakloader() {
|
||||
echo "$out"
|
||||
}
|
||||
|
||||
remote_mount() {
|
||||
local dev="$1" mnt="$2" opts="${3:-rw}"
|
||||
ssh_cmd "/bin/mkdir -p $mnt"
|
||||
if ssh_cmd "/sbin/mount | /usr/bin/grep -q ' on $mnt '"; then
|
||||
return 0
|
||||
fi
|
||||
ssh_cmd "/sbin/mount_apfs -o $opts $dev $mnt 2>/dev/null || true"
|
||||
if ! ssh_cmd "/sbin/mount | /usr/bin/grep -q ' on $mnt '"; then
|
||||
die "Failed to mount $dev at $mnt (opts=$opts). Make sure the ramdisk was booted with the expected patched kernel."
|
||||
fi
|
||||
}
|
||||
|
||||
get_boot_manifest_hash() {
|
||||
ssh_cmd "/bin/ls /mnt5 2>/dev/null" | awk 'length($0)==96{print; exit}'
|
||||
/bin/ls $MNT5 2>/dev/null | awk 'length($0)==96{print; exit}'
|
||||
}
|
||||
|
||||
# ── Setup JB input resources ──────────────────────────────────
|
||||
@@ -201,9 +143,25 @@ apply_dev_overlay() {
|
||||
die "Dev overlay not found (cfw_dev/rpcserver_ios)"
|
||||
}
|
||||
|
||||
# Host-mode transport override (CFW_HOST_MODE=1): run the JB phases against
|
||||
# image volumes mounted locally on the host instead of a device over SSH.
|
||||
[[ -n "${CFW_HOST_MODE:-}" ]] && source "$SCRIPT_DIR/cfw_host_mode.sh"
|
||||
# The VM's Disk.img is attached on the host by cfw_install_host.sh; its APFS
|
||||
# volumes are mounted here and every file is placed with plain cp/chmod/etc.
|
||||
# (the VM is off — nothing runs "on the device").
|
||||
: "${CFW_HOST_CONTAINER:?CFW_HOST_CONTAINER unset — run via cfw_install_host.sh}"
|
||||
HOST_MNT="${CFW_HOST_MNT:-/private/tmp/cfwhost}"
|
||||
MNT1="$HOST_MNT/mnt1" # disk1s1 (System / rootfs)
|
||||
MNT3="$HOST_MNT/mnt3" # disk1s3
|
||||
MNT5="$HOST_MNT/mnt5" # disk1s5 (per-boot-manifest OS dir / procursus bootstrap)
|
||||
TAR="$(command -v gtar 2>/dev/null || echo /opt/homebrew/bin/gtar)" # macOS bsdtar lacks GNU tar flags
|
||||
mkdir -p "$HOST_MNT"
|
||||
|
||||
# Mount an APFS volume of the attached image container at a host mount point.
|
||||
mount_vol() { # mount_vol <slice, e.g. s1> <mountpoint> [opts]
|
||||
local dev="/dev/${CFW_HOST_CONTAINER}$1" mnt="$2" opts="${3:-rw}"
|
||||
/bin/mkdir -p "$mnt"
|
||||
/sbin/mount | /usr/bin/grep -q " on $mnt " && return 0
|
||||
/sbin/mount_apfs -o "$opts" "$dev" "$mnt" 2>/dev/null || true
|
||||
/sbin/mount | /usr/bin/grep -q " on $mnt " || die "mount failed: $dev -> $mnt"
|
||||
}
|
||||
|
||||
# ── Check JB prerequisites ────────────────────────────────────
|
||||
command -v zstd >/dev/null 2>&1 || die "'zstd' not found (required for JB bootstrap phase)"
|
||||
@@ -216,19 +174,19 @@ check_prerequisites
|
||||
|
||||
mkdir -p "$TEMP_DIR"
|
||||
|
||||
# Mount device rootfs (may already be mounted from base install)
|
||||
remote_mount /dev/disk1s1 /mnt1
|
||||
# Mount the image's System volume (may already be mounted from base install)
|
||||
mount_vol s1 "$MNT1"
|
||||
|
||||
# ═══════════ JB-1 PATCH LAUNCHD (JETSAM + DYLIB INJECTION) ════
|
||||
echo ""
|
||||
echo "[JB-1] Patching launchd (jetsam guard + hook injection)..."
|
||||
|
||||
if ! remote_file_exists "/mnt1/sbin/launchd.bak"; then
|
||||
if ! [[ -e "$MNT1/sbin/launchd.bak" ]]; then
|
||||
echo " Creating backup..."
|
||||
ssh_cmd "/bin/cp /mnt1/sbin/launchd /mnt1/sbin/launchd.bak"
|
||||
/bin/cp $MNT1/sbin/launchd $MNT1/sbin/launchd.bak
|
||||
fi
|
||||
|
||||
scp_from "/mnt1/sbin/launchd.bak" "$TEMP_DIR/launchd"
|
||||
cp "$MNT1/sbin/launchd.bak" "$TEMP_DIR/launchd"
|
||||
|
||||
# Extract original entitlements before patching (must preserve for spawn permissions)
|
||||
echo " Extracting original entitlements..."
|
||||
@@ -259,8 +217,8 @@ if [[ -s "$TEMP_DIR/launchd.entitlements" ]]; then
|
||||
else
|
||||
ldid_sign "$TEMP_DIR/launchd"
|
||||
fi
|
||||
scp_to "$TEMP_DIR/launchd" "/mnt1/sbin/launchd"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/sbin/launchd"
|
||||
cp -R "$TEMP_DIR/launchd" "$MNT1/sbin/launchd"
|
||||
/bin/chmod 0755 $MNT1/sbin/launchd
|
||||
|
||||
echo " [+] launchd patched"
|
||||
|
||||
@@ -269,10 +227,10 @@ echo ""
|
||||
echo "[JB-2] Installing iosbinpack64..."
|
||||
|
||||
apply_dev_overlay
|
||||
scp_to "$VM_DIR/$CFW_INPUT/jb/iosbinpack64.tar" "/mnt1"
|
||||
ssh_cmd "/usr/bin/tar --preserve-permissions --no-overwrite-dir \
|
||||
-xf /mnt1/iosbinpack64.tar -C /mnt1"
|
||||
ssh_cmd "/bin/rm -f /mnt1/iosbinpack64.tar"
|
||||
cp -R "$VM_DIR/$CFW_INPUT/jb/iosbinpack64.tar" "$MNT1"
|
||||
"$TAR" --preserve-permissions --no-overwrite-dir \
|
||||
-xf $MNT1/iosbinpack64.tar -C $MNT1
|
||||
/bin/rm -f $MNT1/iosbinpack64.tar
|
||||
|
||||
echo " [+] iosbinpack64 installed"
|
||||
|
||||
@@ -280,13 +238,13 @@ echo " [+] iosbinpack64 installed"
|
||||
echo ""
|
||||
echo "[JB-3] Patching debugserver entitlements..."
|
||||
|
||||
scp_from "/mnt1/usr/libexec/debugserver" "$TEMP_DIR/debugserver"
|
||||
cp "$MNT1/usr/libexec/debugserver" "$TEMP_DIR/debugserver"
|
||||
ldid -e "$TEMP_DIR/debugserver" > "$TEMP_DIR/debugserver-entitlements.plist"
|
||||
plutil -remove seatbelt-profiles "$TEMP_DIR/debugserver-entitlements.plist" || true
|
||||
plutil -insert task_for_pid-allow -bool YES "$TEMP_DIR/debugserver-entitlements.plist" || true
|
||||
ldid_sign_ent "$TEMP_DIR/debugserver" "$TEMP_DIR/debugserver-entitlements.plist"
|
||||
scp_to "$TEMP_DIR/debugserver" "/mnt1/usr/libexec/debugserver"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/usr/libexec/debugserver"
|
||||
cp -R "$TEMP_DIR/debugserver" "$MNT1/usr/libexec/debugserver"
|
||||
/bin/chmod 0755 $MNT1/usr/libexec/debugserver
|
||||
|
||||
echo " [+] debugserver entitlements patched"
|
||||
|
||||
@@ -295,9 +253,9 @@ echo " [+] debugserver entitlements patched"
|
||||
echo ""
|
||||
echo "[JB-4] Installing procursus bootstrap..."
|
||||
|
||||
remote_mount /dev/disk1s5 /mnt5
|
||||
mount_vol s5 "$MNT5"
|
||||
BOOT_HASH="$(get_boot_manifest_hash)"
|
||||
[[ -n "$BOOT_HASH" ]] || die "Could not find 96-char boot manifest hash in /mnt5"
|
||||
[[ -n "$BOOT_HASH" ]] || die "Could not find 96-char boot manifest hash in $MNT5"
|
||||
echo " Boot manifest hash: $BOOT_HASH"
|
||||
|
||||
BOOTSTRAP_ZST="$JB_INPUT_DIR/jb/bootstrap-iphoneos-arm64.tar.zst"
|
||||
@@ -307,27 +265,27 @@ SILEO_DEB="$JB_INPUT_DIR/jb/org.coolstar.sileo_2.5.1_iphoneos-arm64.deb"
|
||||
BOOTSTRAP_TAR="$TEMP_DIR/bootstrap-iphoneos-arm64.tar"
|
||||
zstd -d -f "$BOOTSTRAP_ZST" -o "$BOOTSTRAP_TAR"
|
||||
|
||||
scp_to "$BOOTSTRAP_TAR" "/mnt5/$BOOT_HASH/bootstrap-iphoneos-arm64.tar"
|
||||
cp -R "$BOOTSTRAP_TAR" "$MNT5/$BOOT_HASH/bootstrap-iphoneos-arm64.tar"
|
||||
if [[ -f "$SILEO_DEB" ]]; then
|
||||
scp_to "$SILEO_DEB" "/mnt5/$BOOT_HASH/org.coolstar.sileo_2.5.1_iphoneos-arm64.deb"
|
||||
cp -R "$SILEO_DEB" "$MNT5/$BOOT_HASH/org.coolstar.sileo_2.5.1_iphoneos-arm64.deb"
|
||||
fi
|
||||
|
||||
JB_DIR_NAME="jb-vphone"
|
||||
ssh_cmd "/bin/rm -rf /mnt5/$BOOT_HASH/jb"
|
||||
ssh_cmd "/bin/rm -rf /mnt5/$BOOT_HASH/$JB_DIR_NAME"
|
||||
ssh_cmd "/bin/mkdir -p /mnt5/$BOOT_HASH/$JB_DIR_NAME"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt5/$BOOT_HASH/$JB_DIR_NAME"
|
||||
ssh_cmd "/usr/sbin/chown 0:0 /mnt5/$BOOT_HASH/$JB_DIR_NAME"
|
||||
ssh_cmd "/usr/bin/tar --preserve-permissions -xf /mnt5/$BOOT_HASH/bootstrap-iphoneos-arm64.tar \
|
||||
-C /mnt5/$BOOT_HASH/$JB_DIR_NAME/"
|
||||
ssh_cmd "/bin/mv /mnt5/$BOOT_HASH/$JB_DIR_NAME/var /mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus"
|
||||
ssh_cmd "/bin/mv /mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus/jb/* /mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus 2>/dev/null || true"
|
||||
ssh_cmd "/bin/rm -rf /mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus/jb"
|
||||
ssh_cmd "/bin/rm -f /mnt5/$BOOT_HASH/bootstrap-iphoneos-arm64.tar"
|
||||
/bin/rm -rf $MNT5/$BOOT_HASH/jb
|
||||
/bin/rm -rf $MNT5/$BOOT_HASH/$JB_DIR_NAME
|
||||
/bin/mkdir -p $MNT5/$BOOT_HASH/$JB_DIR_NAME
|
||||
/bin/chmod 0755 $MNT5/$BOOT_HASH/$JB_DIR_NAME
|
||||
/usr/sbin/chown 0:0 $MNT5/$BOOT_HASH/$JB_DIR_NAME
|
||||
"$TAR" --preserve-permissions -xf $MNT5/$BOOT_HASH/bootstrap-iphoneos-arm64.tar \
|
||||
-C $MNT5/$BOOT_HASH/$JB_DIR_NAME/
|
||||
/bin/mv $MNT5/$BOOT_HASH/$JB_DIR_NAME/var $MNT5/$BOOT_HASH/$JB_DIR_NAME/procursus
|
||||
mv "$MNT5/$BOOT_HASH/$JB_DIR_NAME/procursus/jb"/*(N) "$MNT5/$BOOT_HASH/$JB_DIR_NAME/procursus" 2>/dev/null || true
|
||||
/bin/rm -rf $MNT5/$BOOT_HASH/$JB_DIR_NAME/procursus/jb
|
||||
/bin/rm -f $MNT5/$BOOT_HASH/bootstrap-iphoneos-arm64.tar
|
||||
rm -f "$BOOTSTRAP_TAR"
|
||||
|
||||
# NOTE: /var/jb symlink is created on first normal boot by vphone_jb_setup.sh
|
||||
# (Data volume is encrypted and not mountable from ramdisk).
|
||||
# (Data volume is encrypted and not mountable at install time).
|
||||
|
||||
echo " [+] procursus bootstrap installed"
|
||||
|
||||
@@ -340,9 +298,9 @@ if [[ -d "$BASEBIN_DIR" ]]; then
|
||||
|
||||
# Clean previous dylibs before re-uploading
|
||||
echo " Cleaning old /cores/ dylibs..."
|
||||
ssh_cmd "/bin/rm -rf /mnt1/cores"
|
||||
ssh_cmd "/bin/mkdir -p /mnt1/cores"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/cores"
|
||||
/bin/rm -rf $MNT1/cores
|
||||
/bin/mkdir -p $MNT1/cores
|
||||
/bin/chmod 0755 $MNT1/cores
|
||||
|
||||
# Install all pre-built dylibs from basebin payload
|
||||
for dylib in "$BASEBIN_DIR"/*.dylib; do
|
||||
@@ -350,8 +308,8 @@ if [[ -d "$BASEBIN_DIR" ]]; then
|
||||
dylib_name="$(basename "$dylib")"
|
||||
echo " Installing $dylib_name..."
|
||||
ldid_sign "$dylib"
|
||||
scp_to "$dylib" "/mnt1/cores/$dylib_name"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/cores/$dylib_name"
|
||||
cp -R "$dylib" "$MNT1/cores/$dylib_name"
|
||||
/bin/chmod 0755 $MNT1/cores/$dylib_name
|
||||
done
|
||||
|
||||
# Short alias for launchdhook (header space is tight)
|
||||
@@ -359,9 +317,9 @@ if [[ -d "$BASEBIN_DIR" ]]; then
|
||||
echo " Installing short launchdhook alias at /b..."
|
||||
cp "$BASEBIN_DIR/launchdhook.dylib" "$TEMP_DIR/b"
|
||||
ldid_sign "$TEMP_DIR/b"
|
||||
ssh_cmd "/bin/rm -f /mnt1/b"
|
||||
scp_to "$TEMP_DIR/b" "/mnt1/b"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/b"
|
||||
/bin/rm -f $MNT1/b
|
||||
cp -R "$TEMP_DIR/b" "$MNT1/b"
|
||||
/bin/chmod 0755 $MNT1/b
|
||||
fi
|
||||
|
||||
echo " [+] BaseBin hooks deployed"
|
||||
@@ -372,10 +330,10 @@ echo ""
|
||||
echo "[JB-4] Building and installing TweakLoader..."
|
||||
|
||||
TWEAKLOADER_OUT="$(build_tweakloader)"
|
||||
ssh_cmd "/bin/mkdir -p /mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus/usr/lib"
|
||||
scp_to "$TWEAKLOADER_OUT" "/mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus/usr/lib/TweakLoader.dylib"
|
||||
ssh_cmd "/usr/sbin/chown 0:0 /mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus/usr/lib/TweakLoader.dylib"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus/usr/lib/TweakLoader.dylib"
|
||||
/bin/mkdir -p $MNT5/$BOOT_HASH/$JB_DIR_NAME/procursus/usr/lib
|
||||
cp -R "$TWEAKLOADER_OUT" "$MNT5/$BOOT_HASH/$JB_DIR_NAME/procursus/usr/lib/TweakLoader.dylib"
|
||||
/usr/sbin/chown 0:0 $MNT5/$BOOT_HASH/$JB_DIR_NAME/procursus/usr/lib/TweakLoader.dylib
|
||||
/bin/chmod 0755 $MNT5/$BOOT_HASH/$JB_DIR_NAME/procursus/usr/lib/TweakLoader.dylib
|
||||
|
||||
echo " [+] TweakLoader installed to procursus/usr/lib/TweakLoader.dylib"
|
||||
|
||||
@@ -387,17 +345,17 @@ echo "[JB-5] Deploying first-boot setup..."
|
||||
SETUP_SCRIPT="$SCRIPT_DIR/vphone_jb_setup.sh"
|
||||
SETUP_PLIST="$SCRIPT_DIR/vphone_jb_setup.plist"
|
||||
if [[ -f "$SETUP_SCRIPT" ]]; then
|
||||
scp_to "$SETUP_SCRIPT" "/mnt1/cores/vphone_jb_setup.sh"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/cores/vphone_jb_setup.sh"
|
||||
cp -R "$SETUP_SCRIPT" "$MNT1/cores/vphone_jb_setup.sh"
|
||||
/bin/chmod 0755 $MNT1/cores/vphone_jb_setup.sh
|
||||
echo " [+] vphone_jb_setup.sh -> /cores/"
|
||||
fi
|
||||
if [[ -f "$SETUP_PLIST" ]]; then
|
||||
scp_to "$SETUP_PLIST" "/mnt1/System/Library/LaunchDaemons/com.vphone.jb-setup.plist"
|
||||
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/LaunchDaemons/com.vphone.jb-setup.plist"
|
||||
cp -R "$SETUP_PLIST" "$MNT1/System/Library/LaunchDaemons/com.vphone.jb-setup.plist"
|
||||
/bin/chmod 0644 $MNT1/System/Library/LaunchDaemons/com.vphone.jb-setup.plist
|
||||
|
||||
# Inject into launchd.plist so launchd starts it at boot
|
||||
echo " Injecting com.vphone.jb-setup into launchd.plist..."
|
||||
scp_from "/mnt1/System/Library/xpc/launchd.plist" "$TEMP_DIR/launchd.plist"
|
||||
cp "$MNT1/System/Library/xpc/launchd.plist" "$TEMP_DIR/launchd.plist"
|
||||
"$PYTHON3" -c "
|
||||
import plistlib, sys
|
||||
with open(sys.argv[1], 'rb') as f:
|
||||
@@ -408,17 +366,17 @@ target.setdefault('LaunchDaemons', {})['/System/Library/LaunchDaemons/com.vphone
|
||||
with open(sys.argv[1], 'wb') as f:
|
||||
plistlib.dump(target, f, sort_keys=False)
|
||||
" "$TEMP_DIR/launchd.plist" "$SETUP_PLIST"
|
||||
scp_to "$TEMP_DIR/launchd.plist" "/mnt1/System/Library/xpc/launchd.plist"
|
||||
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/xpc/launchd.plist"
|
||||
cp -R "$TEMP_DIR/launchd.plist" "$MNT1/System/Library/xpc/launchd.plist"
|
||||
/bin/chmod 0644 $MNT1/System/Library/xpc/launchd.plist
|
||||
echo " [+] com.vphone.jb-setup.plist injected into launchd.plist"
|
||||
fi
|
||||
|
||||
# ═══════════ CLEANUP ═════════════════════════════════════════
|
||||
echo ""
|
||||
echo "[*] Unmounting device filesystems..."
|
||||
ssh_cmd "/sbin/umount /mnt1 2>/dev/null || true"
|
||||
ssh_cmd "/sbin/umount /mnt3 2>/dev/null || true"
|
||||
ssh_cmd "/sbin/umount /mnt5 2>/dev/null || true"
|
||||
echo "[*] Unmounting image volumes..."
|
||||
/sbin/umount $MNT1 2>/dev/null || true
|
||||
/sbin/umount $MNT3 2>/dev/null || true
|
||||
/sbin/umount $MNT5 2>/dev/null || true
|
||||
|
||||
echo "[*] Cleaning up temp binaries..."
|
||||
rm -f "$TEMP_DIR/launchd" \
|
||||
@@ -426,7 +384,5 @@ rm -f "$TEMP_DIR/launchd" \
|
||||
|
||||
echo ""
|
||||
echo "[+] CFW + JB installation complete!"
|
||||
echo " Reboot to apply changes."
|
||||
echo " Boot to apply changes."
|
||||
echo " After boot, SSH will be available on port 22222 (password: alpine)"
|
||||
|
||||
ssh_cmd "/sbin/halt" || true
|
||||
|
||||
@@ -15,8 +15,8 @@ mode cross-checks DT root `model` / `target-type` against the
|
||||
BuildManifest's signed identity and rejects the device on mismatch — but
|
||||
they are NOT boot-time-fatal. After restore completes, the existing iBSS /
|
||||
iBEC / LLB image4_validate_property_callback bypass patches accept any
|
||||
IM4P contents, so re-patching the DT on the booted-into-ramdisk system
|
||||
before the device reboots into the rootfs is safe.
|
||||
IM4P contents, so re-patching the DT offline (on the host-mounted restored
|
||||
filesystem) before the device boots into the rootfs is safe.
|
||||
|
||||
The `compatible` rewrite is a reorder, not a replacement: VPHONE600AP
|
||||
stays in the list (now as the second entry) so IOKit's platform-expert
|
||||
@@ -30,11 +30,11 @@ Layout summary:
|
||||
OR "VPHONE600AP\\0iPhone17,3\\0AppleVirtualPlatformARM\\0\\0" (48B, post-Tier1b)
|
||||
after: "D47AP\\0VPHONE600AP\\0AppleVirtualPlatformARM\\0" + 6 NUL pad (48B)
|
||||
|
||||
This script runs on the host. The install pipeline scp_from's the
|
||||
devicetree.img4 from `/mnt5/<boot-hash>/usr/standalone/firmware/`, this
|
||||
script edits it in place, and the install pipeline scp_to's it back to
|
||||
the same path. The boot-manifest-hash directory is the same one
|
||||
discovered by `get_boot_manifest_hash` in `cfw_install_jb.sh`.
|
||||
This script runs on the host. The install pipeline copies the
|
||||
devicetree.img4 out of the host-mounted `/mnt5/<boot-hash>/usr/standalone/firmware/`,
|
||||
this script edits it in place, and the pipeline copies it back to the same
|
||||
path. The boot-manifest-hash directory is the same one discovered by
|
||||
`get_boot_manifest_hash` in `cfw_install_jb.sh`.
|
||||
|
||||
Dependencies:
|
||||
pip install pyimg4
|
||||
|
||||
@@ -105,78 +105,6 @@ def wait_for_irecv(ecid: Optional[int], timeout: int, is_recovery: Optional[bool
|
||||
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"
|
||||
@@ -247,21 +175,6 @@ def recovery_probe_command(
|
||||
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(
|
||||
|
||||
@@ -1,888 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
build_ramdisk.py — Build a signed SSH ramdisk for vphone600.
|
||||
|
||||
Expects the VM restore tree to have already been patched by the Swift firmware pipeline.
|
||||
Extracts patched components, signs with SHSH, and builds SSH ramdisk.
|
||||
|
||||
Usage:
|
||||
python3 build_ramdisk.py [vm_directory]
|
||||
|
||||
Directory structure:
|
||||
./shsh/ — SHSH blobs (auto-discovered)
|
||||
./ramdisk_input/ — Tools and SSH resources (auto-setup from CFW)
|
||||
./ramdisk_builder_temp/ — Intermediate .raw files (cleaned up)
|
||||
./Ramdisk/ — Final signed IMG4 output
|
||||
|
||||
Prerequisites:
|
||||
pip install pyimg4
|
||||
Run make fw_patch / make fw_patch_dev / make fw_patch_jb first to patch boot-chain components.
|
||||
"""
|
||||
|
||||
import gzip
|
||||
import glob
|
||||
import os
|
||||
import plistlib
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from pyimg4 import IM4M, IM4P, IMG4
|
||||
|
||||
_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
# Configuration
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
|
||||
OUTPUT_DIR = "Ramdisk"
|
||||
TEMP_DIR = "ramdisk_builder_temp"
|
||||
INPUT_DIR = "ramdisk_input"
|
||||
RESTORED_EXTERNAL_PATH = "usr/local/bin/restored_external"
|
||||
RESTORED_EXTERNAL_SERIAL_MARKER = b"SSHRD_Script Sep 22 2022 18:56:50"
|
||||
DEFAULT_IBEC_BOOT_ARGS = b"serial=3 -v debug=0x2014e %s"
|
||||
|
||||
# Ramdisk boot-args
|
||||
RAMDISK_BOOT_ARGS = b"serial=3 rd=md0 debug=0x2014e -v wdt=-1 %s"
|
||||
|
||||
# IM4P fourccs for restore mode
|
||||
TXM_FOURCC = "trxm"
|
||||
KERNEL_FOURCC = "rkrn"
|
||||
RAMDISK_KERNEL_SUFFIX = ".ramdisk"
|
||||
RAMDISK_KERNEL_IMG4 = "krnl.ramdisk.img4"
|
||||
SUDO_PASSWORD = os.environ.get("VPHONE_SUDO_PASSWORD", None)
|
||||
|
||||
# Files to remove from ramdisk to save space
|
||||
RAMDISK_REMOVE = [
|
||||
"usr/bin/img4tool",
|
||||
"usr/bin/img4",
|
||||
"usr/sbin/dietappleh13camerad",
|
||||
"usr/sbin/dietappleh16camerad",
|
||||
"usr/local/bin/wget",
|
||||
"usr/local/bin/procexp",
|
||||
"usr/standalone/firmware/S6TUUP1",
|
||||
]
|
||||
|
||||
# Directories to re-sign in ramdisk
|
||||
SIGN_DIRS = [
|
||||
"usr/local/bin/*",
|
||||
"usr/local/lib/*",
|
||||
"usr/bin/*",
|
||||
"bin/*",
|
||||
"usr/lib/*",
|
||||
"sbin/*",
|
||||
"usr/sbin/*",
|
||||
"usr/libexec/*",
|
||||
]
|
||||
|
||||
# Compressed archive of ramdisk_input/ (located next to this script)
|
||||
INPUT_ARCHIVE = "ramdisk_input.tar.zst"
|
||||
PATCHER_BINARY_ENV = "VPHONE_PATCHER_BINARY"
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
# Setup — extract ramdisk_input/ from zstd archive if needed
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
def setup_input(vm_dir):
|
||||
"""Ensure ramdisk_input/ exists, extracting from .tar.zst if needed."""
|
||||
input_dir = os.path.join(vm_dir, INPUT_DIR)
|
||||
|
||||
if os.path.isdir(input_dir):
|
||||
return input_dir
|
||||
|
||||
# Look for archive next to this script, then in vm_dir
|
||||
for search_dir in (os.path.join(_SCRIPT_DIR, "resources"), _SCRIPT_DIR, vm_dir):
|
||||
archive = os.path.join(search_dir, INPUT_ARCHIVE)
|
||||
if os.path.isfile(archive):
|
||||
print(f" Extracting {INPUT_ARCHIVE}...")
|
||||
subprocess.run(
|
||||
["tar", "--zstd", "-xf", archive, "-C", vm_dir],
|
||||
check=True,
|
||||
)
|
||||
return input_dir
|
||||
|
||||
print(f"[-] Neither {INPUT_DIR}/ nor {INPUT_ARCHIVE} found.")
|
||||
print(f" Place {INPUT_ARCHIVE} next to this script or in the VM directory.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
# SHSH / signing helpers
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
def find_shsh(shsh_dir):
|
||||
"""Find first SHSH blob in directory."""
|
||||
for ext in ("*.shsh", "*.shsh2"):
|
||||
matches = sorted(glob.glob(os.path.join(shsh_dir, ext)))
|
||||
if matches:
|
||||
return matches[0]
|
||||
return None
|
||||
|
||||
|
||||
def extract_im4m(shsh_path, im4m_path):
|
||||
"""Extract IM4M manifest from SHSH blob (handles gzip-compressed)."""
|
||||
raw = open(shsh_path, "rb").read()
|
||||
if raw[:2] == b"\x1f\x8b":
|
||||
raw = gzip.decompress(raw)
|
||||
tmp = shsh_path + ".tmp"
|
||||
try:
|
||||
open(tmp, "wb").write(raw)
|
||||
subprocess.run(
|
||||
["pyimg4", "im4m", "extract", "-i", tmp, "-o", im4m_path],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
finally:
|
||||
if os.path.exists(tmp):
|
||||
os.remove(tmp)
|
||||
|
||||
|
||||
def sign_img4(im4p_path, img4_path, im4m_path, tag=None):
|
||||
"""Create IMG4 from IM4P + IM4M using pyimg4 Python API."""
|
||||
im4p = IM4P(open(im4p_path, "rb").read())
|
||||
if tag:
|
||||
im4p.fourcc = tag
|
||||
im4m = IM4M(open(im4m_path, "rb").read())
|
||||
img4 = IMG4(im4p=im4p, im4m=im4m)
|
||||
with open(img4_path, "wb") as f:
|
||||
f.write(img4.output())
|
||||
|
||||
|
||||
def run(cmd, **kwargs):
|
||||
"""Run a command, raising on failure."""
|
||||
return subprocess.run(cmd, check=True, **kwargs)
|
||||
|
||||
|
||||
def run_sudo(cmd, **kwargs):
|
||||
"""Run hdiutil directly first, then retry through sudo if required."""
|
||||
if not cmd or os.path.basename(cmd[0]) != "hdiutil":
|
||||
return run(cmd, **kwargs)
|
||||
|
||||
# SUDO_PASSWORD flow exports SUDO_ASKPASS: go straight to sudo -A so
|
||||
# hdiutil never runs unprivileged first (which triggers an auth prompt).
|
||||
if os.environ.get("SUDO_ASKPASS"):
|
||||
return run(["sudo", "-A", *cmd], **kwargs)
|
||||
|
||||
try:
|
||||
return run(cmd, **kwargs)
|
||||
except subprocess.CalledProcessError:
|
||||
if SUDO_PASSWORD:
|
||||
return run(
|
||||
["sudo", "-S", *cmd],
|
||||
input=f"{SUDO_PASSWORD}\n",
|
||||
text=True,
|
||||
**kwargs,
|
||||
)
|
||||
return run(["sudo", *cmd], **kwargs)
|
||||
|
||||
|
||||
def detach_mountpoint(mountpoint, required=False):
|
||||
"""Detach hdiutil mountpoints used during ramdisk builds."""
|
||||
try:
|
||||
run_sudo(["hdiutil", "detach", "-force", mountpoint], capture_output=True)
|
||||
except subprocess.CalledProcessError:
|
||||
if required:
|
||||
raise
|
||||
|
||||
|
||||
def ensure_path_within_vm(path, vm_dir, label):
|
||||
"""Fail if path escapes vm_dir."""
|
||||
vm_real = os.path.realpath(vm_dir)
|
||||
path_real = os.path.realpath(path)
|
||||
if path_real == vm_real or path_real.startswith(vm_real + os.sep):
|
||||
return
|
||||
print(f"[-] {label} must be inside VM dir")
|
||||
print(f" VM dir: {vm_real}")
|
||||
print(f" Path: {path_real}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def check_prerequisites():
|
||||
"""Verify required host tools are available."""
|
||||
missing = []
|
||||
for tool, pkg in [("gtar", "gnu-tar"), ("ldid", "ldid-procursus"), ("trustcache", "trustcache (make setup_tools)")]:
|
||||
if not shutil.which(tool):
|
||||
missing.append(f" {tool:12s} — {pkg}")
|
||||
if missing:
|
||||
print("[-] Missing required tools:")
|
||||
for m in missing:
|
||||
print(m)
|
||||
print("\n Run: make setup_tools")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def project_root():
|
||||
return os.path.abspath(os.path.join(_SCRIPT_DIR, ".."))
|
||||
|
||||
|
||||
def patcher_binary_path():
|
||||
override = os.environ.get(PATCHER_BINARY_ENV, "").strip()
|
||||
if override:
|
||||
return os.path.abspath(override)
|
||||
return os.path.join(project_root(), ".build", "debug", "vphone-cli")
|
||||
|
||||
|
||||
def run_swift_patch_component(component, src_path, output_path):
|
||||
"""Patch a single component via the Swift FirmwarePatcher CLI."""
|
||||
binary = patcher_binary_path()
|
||||
if not os.path.isfile(binary):
|
||||
print(f"[-] Swift patcher binary not found: {binary}")
|
||||
print(" Run: make patcher_build")
|
||||
sys.exit(1)
|
||||
|
||||
run(
|
||||
[
|
||||
binary,
|
||||
"patch-component",
|
||||
"--component",
|
||||
component,
|
||||
"--input",
|
||||
src_path,
|
||||
"--output",
|
||||
output_path,
|
||||
"--quiet",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def load_firmware(path):
|
||||
"""Load firmware file, auto-detecting IM4P vs raw."""
|
||||
with open(path, "rb") as f:
|
||||
raw = f.read()
|
||||
|
||||
try:
|
||||
im4p = IM4P(raw)
|
||||
if im4p.payload.compression:
|
||||
im4p.payload.decompress()
|
||||
return im4p, bytearray(im4p.payload.data), True, raw
|
||||
except Exception:
|
||||
return None, bytearray(raw), False, raw
|
||||
|
||||
|
||||
def _save_im4p_with_payp(path, fourcc, patched_data, original_raw):
|
||||
"""Repackage as LZFSE-compressed IM4P and append PAYP from original."""
|
||||
with (
|
||||
tempfile.NamedTemporaryFile(suffix=".raw", delete=False) as tmp_raw,
|
||||
tempfile.NamedTemporaryFile(suffix=".im4p", delete=False) as tmp_im4p,
|
||||
):
|
||||
tmp_raw_path = tmp_raw.name
|
||||
tmp_im4p_path = tmp_im4p.name
|
||||
tmp_raw.write(bytes(patched_data))
|
||||
|
||||
try:
|
||||
subprocess.run(
|
||||
[
|
||||
"pyimg4",
|
||||
"im4p",
|
||||
"create",
|
||||
"-i",
|
||||
tmp_raw_path,
|
||||
"-o",
|
||||
tmp_im4p_path,
|
||||
"-f",
|
||||
fourcc,
|
||||
"--lzfse",
|
||||
],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
output = bytearray(open(tmp_im4p_path, "rb").read())
|
||||
finally:
|
||||
os.unlink(tmp_raw_path)
|
||||
os.unlink(tmp_im4p_path)
|
||||
|
||||
payp_offset = original_raw.rfind(b"PAYP")
|
||||
if payp_offset >= 0:
|
||||
payp_data = original_raw[payp_offset - 10 :]
|
||||
output.extend(payp_data)
|
||||
old_len = int.from_bytes(output[2:5], "big")
|
||||
output[2:5] = (old_len + len(payp_data)).to_bytes(3, "big")
|
||||
print(f" [+] preserved PAYP ({len(payp_data)} bytes)")
|
||||
|
||||
with open(path, "wb") as f:
|
||||
f.write(output)
|
||||
|
||||
|
||||
def find_restore_dir(base_dir):
|
||||
for entry in sorted(os.listdir(base_dir)):
|
||||
full = os.path.join(base_dir, entry)
|
||||
if os.path.isdir(full) and "Restore" in entry:
|
||||
return full
|
||||
return None
|
||||
|
||||
|
||||
def find_file(base_dir, patterns, label):
|
||||
for pattern in patterns:
|
||||
matches = sorted(glob.glob(os.path.join(base_dir, pattern)))
|
||||
if matches:
|
||||
return matches[0]
|
||||
print(f"[-] {label} not found. Searched patterns:")
|
||||
for pattern in patterns:
|
||||
print(f" {os.path.join(base_dir, pattern)}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
# Firmware extraction and IM4P creation
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
def extract_to_raw(src_path, raw_path):
|
||||
"""Extract IM4P payload to .raw file. Returns (im4p_obj, data, original_raw)."""
|
||||
im4p, data, was_im4p, original_raw = load_firmware(src_path)
|
||||
with open(raw_path, "wb") as f:
|
||||
f.write(bytes(data))
|
||||
return im4p, data, original_raw
|
||||
|
||||
|
||||
def create_im4p_uncompressed(raw_data, fourcc, description, output_path):
|
||||
"""Create uncompressed IM4P from raw data."""
|
||||
new_im4p = IM4P(
|
||||
fourcc=fourcc,
|
||||
description=description,
|
||||
payload=bytes(raw_data),
|
||||
)
|
||||
with open(output_path, "wb") as f:
|
||||
f.write(new_im4p.output())
|
||||
|
||||
|
||||
def build_kernel_img4(kernel_src, output_dir, temp_dir, im4m_path, output_name, temp_tag):
|
||||
"""Build one signed kernel IMG4 from a kernelcache source file."""
|
||||
kc_raw = os.path.join(temp_dir, f"{temp_tag}.raw")
|
||||
kc_im4p = os.path.join(temp_dir, f"{temp_tag}.im4p")
|
||||
_, data, original_raw = extract_to_raw(kernel_src, kc_raw)
|
||||
print(f" source: {kernel_src}")
|
||||
print(f" format: IM4P, {len(data)} bytes")
|
||||
_save_im4p_with_payp(kc_im4p, KERNEL_FOURCC, data, original_raw)
|
||||
sign_img4(kc_im4p, os.path.join(output_dir, output_name), im4m_path)
|
||||
print(f" [+] {output_name}")
|
||||
|
||||
|
||||
def _find_pristine_cloudos_kernel():
|
||||
"""Find a pristine CloudOS vphone600 research kernel from project ipsws/."""
|
||||
env_path = os.environ.get("RAMDISK_BASE_KERNEL", "").strip()
|
||||
if env_path:
|
||||
p = os.path.abspath(env_path)
|
||||
if os.path.isfile(p):
|
||||
return p
|
||||
print(f" [!] RAMDISK_BASE_KERNEL set but not found: {p}")
|
||||
|
||||
project_root = os.path.abspath(os.path.join(_SCRIPT_DIR, ".."))
|
||||
patterns = [
|
||||
os.path.join(project_root, "ipsws", "PCC-CloudOS*", "kernelcache.research.vphone600"),
|
||||
os.path.join(project_root, "ipsws", "*CloudOS*", "kernelcache.research.vphone600"),
|
||||
]
|
||||
for pattern in patterns:
|
||||
matches = sorted(glob.glob(pattern))
|
||||
if matches:
|
||||
return matches[0]
|
||||
return None
|
||||
|
||||
|
||||
def derive_ramdisk_kernel_source(kc_src, temp_dir):
|
||||
"""Get source kernel for krnl.ramdisk.img4 entirely within ramdisk_build flow.
|
||||
|
||||
Priority:
|
||||
1) Existing legacy snapshot next to restore kernel (`*.ramdisk`)
|
||||
2) Derive from pristine CloudOS kernel by applying base KernelPatcher
|
||||
"""
|
||||
legacy_snapshot = f"{kc_src}{RAMDISK_KERNEL_SUFFIX}"
|
||||
if os.path.isfile(legacy_snapshot):
|
||||
print(f" found legacy ramdisk kernel snapshot: {legacy_snapshot}")
|
||||
return legacy_snapshot
|
||||
|
||||
pristine = _find_pristine_cloudos_kernel()
|
||||
if not pristine:
|
||||
print(" [!] pristine CloudOS kernel not found; skipping ramdisk-specific kernel image")
|
||||
return None
|
||||
|
||||
print(f" deriving ramdisk kernel from pristine source: {pristine}")
|
||||
out_path = os.path.join(temp_dir, f"kernelcache.research.vphone600{RAMDISK_KERNEL_SUFFIX}")
|
||||
run_swift_patch_component("kernel-base", pristine, out_path)
|
||||
print(" [+] base kernel patches applied for ramdisk variant")
|
||||
return out_path
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
# iBEC boot-args patching
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
def patch_ibec_bootargs(data):
|
||||
"""Replace normal boot-args with ramdisk boot-args in already-patched iBEC.
|
||||
|
||||
Finds the boot-args string written by the Swift firmware pipeline
|
||||
and overwrites it in-place. No hardcoded offsets needed — the ADRP+ADD
|
||||
instructions already point to the string location.
|
||||
"""
|
||||
off = data.find(DEFAULT_IBEC_BOOT_ARGS)
|
||||
if off < 0:
|
||||
print(f" [-] boot-args: existing string not found ({DEFAULT_IBEC_BOOT_ARGS.decode()!r})")
|
||||
return False
|
||||
|
||||
args = RAMDISK_BOOT_ARGS + b"\x00"
|
||||
data[off : off + len(args)] = args
|
||||
|
||||
# Zero out any leftover from the previous string
|
||||
end = off + len(args)
|
||||
while end < len(data) and data[end] != 0:
|
||||
data[end] = 0
|
||||
end += 1
|
||||
|
||||
print(f' boot-args -> "{RAMDISK_BOOT_ARGS.decode()}" at 0x{off:X}')
|
||||
return True
|
||||
|
||||
|
||||
def patch_restored_external_usbmux_label(mountpoint):
|
||||
"""Patch restored_external USBMux serial label when RAMDISK_UDID is provided."""
|
||||
target_udid = os.environ.get("RAMDISK_UDID", "").strip()
|
||||
if not target_udid:
|
||||
print(" [*] RAMDISK_UDID not set; keeping default restored_external USBMux label")
|
||||
return
|
||||
|
||||
try:
|
||||
target_bytes = target_udid.encode("ascii")
|
||||
except UnicodeEncodeError:
|
||||
print(f"[-] RAMDISK_UDID must be ASCII, got: {target_udid!r}")
|
||||
sys.exit(1)
|
||||
|
||||
marker_len = len(RESTORED_EXTERNAL_SERIAL_MARKER)
|
||||
if len(target_bytes) > marker_len:
|
||||
print(f"[-] RAMDISK_UDID too long for restored_external label ({len(target_bytes)} > {marker_len})")
|
||||
print(f" RAMDISK_UDID={target_udid}")
|
||||
sys.exit(1)
|
||||
|
||||
restored_external = os.path.join(mountpoint, RESTORED_EXTERNAL_PATH)
|
||||
if not os.path.isfile(restored_external):
|
||||
print(f"[-] Missing restored_external for USBMux label patch: {restored_external}")
|
||||
sys.exit(1)
|
||||
|
||||
with open(restored_external, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
off = data.find(RESTORED_EXTERNAL_SERIAL_MARKER)
|
||||
if off < 0:
|
||||
print("[-] Could not find default USBMux serial marker in restored_external")
|
||||
sys.exit(1)
|
||||
|
||||
if data.find(RESTORED_EXTERNAL_SERIAL_MARKER, off + 1) >= 0:
|
||||
print("[!] Multiple USBMux serial markers found in restored_external; patching first occurrence")
|
||||
|
||||
replacement = target_bytes + (b"\x00" * (marker_len - len(target_bytes)))
|
||||
patched = data[:off] + replacement + data[off + marker_len :]
|
||||
|
||||
with open(restored_external, "wb") as f:
|
||||
f.write(patched)
|
||||
|
||||
print(f" [+] Patched restored_external USBMux label to: {target_udid}")
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
# Ramdisk DMG building
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
def build_ramdisk(restore_dir, im4m_path, vm_dir, input_dir, output_dir, temp_dir):
|
||||
"""Build custom SSH ramdisk from restore DMG."""
|
||||
# Read RestoreRamDisk path dynamically from BuildManifest.plist
|
||||
bm_path = os.path.join(restore_dir, "BuildManifest.plist")
|
||||
with open(bm_path, "rb") as f:
|
||||
bm = plistlib.load(f)
|
||||
ramdisk_rel = bm["BuildIdentities"][0]["Manifest"]["RestoreRamDisk"]["Info"]["Path"]
|
||||
ramdisk_src = os.path.join(restore_dir, ramdisk_rel)
|
||||
mountpoint = os.path.join(vm_dir, "SSHRD")
|
||||
ramdisk_raw = os.path.join(temp_dir, "ramdisk.raw.dmg")
|
||||
ramdisk_custom = os.path.join(temp_dir, "ramdisk1.dmg")
|
||||
gtar_bin = shutil.which("gtar")
|
||||
ldid_bin = shutil.which("ldid")
|
||||
tc_bin = shutil.which("trustcache")
|
||||
|
||||
# Extract base ramdisk
|
||||
print(" Extracting base ramdisk...")
|
||||
run(
|
||||
["pyimg4", "im4p", "extract", "-i", ramdisk_src, "-o", ramdisk_raw],
|
||||
capture_output=True,
|
||||
)
|
||||
|
||||
ensure_path_within_vm(mountpoint, vm_dir, "Ramdisk mountpoint")
|
||||
os.makedirs(mountpoint, exist_ok=True)
|
||||
if os.path.exists(ramdisk_custom):
|
||||
os.remove(ramdisk_custom)
|
||||
|
||||
try:
|
||||
# Mount, create expanded copy
|
||||
print(" Mounting base ramdisk...")
|
||||
run_sudo(
|
||||
[
|
||||
"hdiutil",
|
||||
"attach",
|
||||
"-mountpoint",
|
||||
mountpoint,
|
||||
ramdisk_raw,
|
||||
"-nobrowse",
|
||||
"-owners",
|
||||
"off",
|
||||
]
|
||||
)
|
||||
|
||||
print(" Creating expanded ramdisk (254 MB)...")
|
||||
run_sudo(
|
||||
[
|
||||
"hdiutil",
|
||||
"create",
|
||||
"-size",
|
||||
"254m",
|
||||
"-imagekey",
|
||||
"diskimage-class=CRawDiskImage",
|
||||
"-format",
|
||||
"UDZO",
|
||||
"-fs",
|
||||
"APFS",
|
||||
"-layout",
|
||||
"NONE",
|
||||
"-srcfolder",
|
||||
mountpoint,
|
||||
"-copyuid",
|
||||
"root",
|
||||
ramdisk_custom,
|
||||
]
|
||||
)
|
||||
detach_mountpoint(mountpoint, required=True)
|
||||
|
||||
# Mount expanded, inject SSH
|
||||
print(" Mounting expanded ramdisk...")
|
||||
run_sudo(
|
||||
[
|
||||
"hdiutil",
|
||||
"attach",
|
||||
"-mountpoint",
|
||||
mountpoint,
|
||||
ramdisk_custom,
|
||||
"-nobrowse",
|
||||
"-owners",
|
||||
"off",
|
||||
]
|
||||
)
|
||||
|
||||
print(" Injecting SSH tools...")
|
||||
ssh_tar = os.path.join(input_dir, "ssh.tar.gz")
|
||||
run_sudo(
|
||||
[gtar_bin, "-x", "--no-overwrite-dir", "-f", ssh_tar, "-C", mountpoint]
|
||||
)
|
||||
patch_restored_external_usbmux_label(mountpoint)
|
||||
|
||||
# Remove unnecessary files
|
||||
for rel_path in RAMDISK_REMOVE:
|
||||
full = os.path.join(mountpoint, rel_path)
|
||||
if os.path.exists(full):
|
||||
if os.path.isdir(full):
|
||||
shutil.rmtree(full)
|
||||
else:
|
||||
os.remove(full)
|
||||
|
||||
# Re-sign Mach-O binaries
|
||||
print(" Re-signing Mach-O binaries...")
|
||||
signcert = os.path.join(input_dir, "signcert.p12")
|
||||
|
||||
for pattern in SIGN_DIRS:
|
||||
for path in glob.glob(os.path.join(mountpoint, pattern)):
|
||||
if os.path.isfile(path) and not os.path.islink(path):
|
||||
if (
|
||||
"Mach-O"
|
||||
in subprocess.run(
|
||||
["file", path],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
).stdout
|
||||
):
|
||||
subprocess.run(
|
||||
[ldid_bin, "-S", "-M", f"-K{signcert}", path],
|
||||
capture_output=True,
|
||||
)
|
||||
|
||||
# Fix sftp-server entitlements
|
||||
sftp_ents = os.path.join(input_dir, "sftp_server_ents.plist")
|
||||
sftp_server = os.path.join(mountpoint, "usr/libexec/sftp-server")
|
||||
if os.path.exists(sftp_server):
|
||||
run([ldid_bin, f"-S{sftp_ents}", "-M", f"-K{signcert}", sftp_server])
|
||||
|
||||
# Build trustcache
|
||||
print(" Building trustcache...")
|
||||
tc_raw = os.path.join(temp_dir, "sshrd.raw.tc")
|
||||
tc_im4p = os.path.join(temp_dir, "trustcache.im4p")
|
||||
|
||||
run([tc_bin, "create", tc_raw, mountpoint])
|
||||
run(
|
||||
["pyimg4", "im4p", "create", "-i", tc_raw, "-o", tc_im4p, "-f", "rtsc"],
|
||||
capture_output=True,
|
||||
)
|
||||
sign_img4(
|
||||
tc_im4p,
|
||||
os.path.join(output_dir, "trustcache.img4"),
|
||||
im4m_path,
|
||||
)
|
||||
print(f" [+] trustcache.img4")
|
||||
|
||||
finally:
|
||||
detach_mountpoint(mountpoint)
|
||||
|
||||
# Shrink and sign ramdisk
|
||||
run_sudo(["hdiutil", "resize", "-sectors", "min", ramdisk_custom])
|
||||
|
||||
print(" Signing ramdisk...")
|
||||
rd_im4p = os.path.join(temp_dir, "ramdisk.im4p")
|
||||
run(
|
||||
["pyimg4", "im4p", "create", "-i", ramdisk_custom, "-o", rd_im4p, "-f", "rdsk"],
|
||||
capture_output=True,
|
||||
)
|
||||
sign_img4(
|
||||
rd_im4p,
|
||||
os.path.join(output_dir, "ramdisk.img4"),
|
||||
im4m_path,
|
||||
)
|
||||
print(f" [+] ramdisk.img4")
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
# Main
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
def main():
|
||||
vm_dir = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else os.getcwd())
|
||||
|
||||
if not os.path.isdir(vm_dir):
|
||||
print(f"[-] Not a directory: {vm_dir}")
|
||||
sys.exit(1)
|
||||
|
||||
# Find SHSH
|
||||
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.")
|
||||
sys.exit(1)
|
||||
|
||||
# Find restore directory
|
||||
restore_dir = find_restore_dir(vm_dir)
|
||||
if not restore_dir:
|
||||
print(f"[-] No *Restore* directory found in {vm_dir}")
|
||||
sys.exit(1)
|
||||
|
||||
# Check host tools
|
||||
check_prerequisites()
|
||||
|
||||
# Setup input resources (copy from CFW if needed)
|
||||
print(f"[*] Setting up {INPUT_DIR}/...")
|
||||
input_dir = setup_input(vm_dir)
|
||||
|
||||
# Create temp and output directories
|
||||
temp_dir = os.path.join(vm_dir, TEMP_DIR)
|
||||
output_dir = os.path.join(vm_dir, OUTPUT_DIR)
|
||||
ensure_path_within_vm(temp_dir, vm_dir, "Temp directory")
|
||||
ensure_path_within_vm(output_dir, vm_dir, "Output directory")
|
||||
for d in (temp_dir, output_dir):
|
||||
if os.path.exists(d):
|
||||
shutil.rmtree(d)
|
||||
os.makedirs(d)
|
||||
|
||||
print(f"[*] VM directory: {vm_dir}")
|
||||
print(f"[*] Restore directory: {restore_dir}")
|
||||
print(f"[*] SHSH blob: {shsh_path}")
|
||||
|
||||
# Extract IM4M from SHSH
|
||||
im4m_path = os.path.join(temp_dir, "vphone.im4m")
|
||||
print(f"\n[*] Extracting IM4M from SHSH...")
|
||||
extract_im4m(shsh_path, im4m_path)
|
||||
|
||||
# ── 1. iBSS (already patched by patch_firmware.py) ───────────
|
||||
print(f"\n{'=' * 60}")
|
||||
print(f" 1. iBSS (already patched — extract & sign)")
|
||||
print(f"{'=' * 60}")
|
||||
ibss_src = find_file(
|
||||
restore_dir,
|
||||
[
|
||||
"Firmware/dfu/iBSS.vresearch101.RELEASE.im4p",
|
||||
],
|
||||
"iBSS",
|
||||
)
|
||||
ibss_raw = os.path.join(temp_dir, "iBSS.raw")
|
||||
ibss_im4p = os.path.join(temp_dir, "iBSS.im4p")
|
||||
im4p_obj, data, _ = extract_to_raw(ibss_src, ibss_raw)
|
||||
create_im4p_uncompressed(data, im4p_obj.fourcc, im4p_obj.description, ibss_im4p)
|
||||
sign_img4(
|
||||
ibss_im4p,
|
||||
os.path.join(output_dir, "iBSS.vresearch101.RELEASE.img4"),
|
||||
im4m_path,
|
||||
)
|
||||
print(f" [+] iBSS.vresearch101.RELEASE.img4")
|
||||
|
||||
# ── 2. iBEC (already patched — just fix boot-args for ramdisk)
|
||||
print(f"\n{'=' * 60}")
|
||||
print(f" 2. iBEC (patch boot-args for ramdisk)")
|
||||
print(f"{'=' * 60}")
|
||||
ibec_src = find_file(
|
||||
restore_dir,
|
||||
[
|
||||
"Firmware/dfu/iBEC.vresearch101.RELEASE.im4p",
|
||||
],
|
||||
"iBEC",
|
||||
)
|
||||
ibec_raw = os.path.join(temp_dir, "iBEC.raw")
|
||||
ibec_im4p = os.path.join(temp_dir, "iBEC.im4p")
|
||||
im4p_obj, data, _ = extract_to_raw(ibec_src, ibec_raw)
|
||||
patch_ibec_bootargs(data)
|
||||
create_im4p_uncompressed(data, im4p_obj.fourcc, im4p_obj.description, ibec_im4p)
|
||||
sign_img4(
|
||||
ibec_im4p,
|
||||
os.path.join(output_dir, "iBEC.vresearch101.RELEASE.img4"),
|
||||
im4m_path,
|
||||
)
|
||||
print(f" [+] iBEC.vresearch101.RELEASE.img4")
|
||||
|
||||
# ── 3. SPTM (sign only) ─────────────────────────────────────
|
||||
print(f"\n{'=' * 60}")
|
||||
print(f" 3. SPTM (sign only)")
|
||||
print(f"{'=' * 60}")
|
||||
sptm_src = find_file(
|
||||
restore_dir,
|
||||
[
|
||||
"Firmware/sptm.vresearch1.release.im4p",
|
||||
],
|
||||
"SPTM",
|
||||
)
|
||||
sign_img4(
|
||||
sptm_src,
|
||||
os.path.join(output_dir, "sptm.vresearch1.release.img4"),
|
||||
im4m_path,
|
||||
tag="sptm",
|
||||
)
|
||||
print(f" [+] sptm.vresearch1.release.img4")
|
||||
|
||||
# ── 4. DeviceTree (sign only) ────────────────────────────────
|
||||
print(f"\n{'=' * 60}")
|
||||
print(f" 4. DeviceTree (sign only)")
|
||||
print(f"{'=' * 60}")
|
||||
dt_src = find_file(
|
||||
restore_dir,
|
||||
[
|
||||
"Firmware/all_flash/DeviceTree.vphone600ap.im4p",
|
||||
],
|
||||
"DeviceTree",
|
||||
)
|
||||
sign_img4(
|
||||
dt_src,
|
||||
os.path.join(output_dir, "DeviceTree.vphone600ap.img4"),
|
||||
im4m_path,
|
||||
tag="rdtr",
|
||||
)
|
||||
print(f" [+] DeviceTree.vphone600ap.img4")
|
||||
|
||||
# ── 5. SEP (sign only) ───────────────────────────────────────
|
||||
print(f"\n{'=' * 60}")
|
||||
print(f" 5. SEP (sign only)")
|
||||
print(f"{'=' * 60}")
|
||||
sep_src = find_file(
|
||||
restore_dir,
|
||||
[
|
||||
"Firmware/all_flash/sep-firmware.vresearch101.RELEASE.im4p",
|
||||
],
|
||||
"SEP",
|
||||
)
|
||||
sign_img4(
|
||||
sep_src,
|
||||
os.path.join(output_dir, "sep-firmware.vresearch101.RELEASE.img4"),
|
||||
im4m_path,
|
||||
tag="rsep",
|
||||
)
|
||||
print(f" [+] sep-firmware.vresearch101.RELEASE.img4")
|
||||
|
||||
# ── 6. TXM (release variant — needs patching) ────────────────
|
||||
print(f"\n{'=' * 60}")
|
||||
print(f" 6. TXM (patch release variant)")
|
||||
print(f"{'=' * 60}")
|
||||
txm_src = find_file(
|
||||
restore_dir,
|
||||
[
|
||||
"Firmware/txm.iphoneos.release.im4p",
|
||||
],
|
||||
"TXM",
|
||||
)
|
||||
txm_raw = os.path.join(temp_dir, "txm.raw")
|
||||
txm_patched_raw = os.path.join(temp_dir, "txm.patched.raw")
|
||||
im4p_obj, data, _, original_raw = load_firmware(txm_src)
|
||||
with open(txm_raw, "wb") as f:
|
||||
f.write(bytes(data))
|
||||
print(f" source: {txm_src}")
|
||||
print(f" format: IM4P, {len(data)} bytes")
|
||||
run_swift_patch_component("txm", txm_src, txm_patched_raw)
|
||||
with open(txm_patched_raw, "rb") as f:
|
||||
patched_txm = f.read()
|
||||
txm_im4p = os.path.join(temp_dir, "txm.im4p")
|
||||
_save_im4p_with_payp(txm_im4p, TXM_FOURCC, patched_txm, original_raw)
|
||||
sign_img4(
|
||||
txm_im4p, os.path.join(output_dir, "txm.img4"), im4m_path
|
||||
)
|
||||
print(f" [+] txm.img4")
|
||||
|
||||
# ── 7. Kernelcache (already patched — repack with rkrn) ──────
|
||||
print(f"\n{'=' * 60}")
|
||||
print(f" 7. Kernelcache (already patched — repack as rkrn)")
|
||||
print(f"{'=' * 60}")
|
||||
kc_src = find_file(
|
||||
restore_dir,
|
||||
[
|
||||
"kernelcache.research.vphone600",
|
||||
],
|
||||
"kernelcache",
|
||||
)
|
||||
kc_ramdisk_src = derive_ramdisk_kernel_source(kc_src, temp_dir)
|
||||
if kc_ramdisk_src:
|
||||
print(f" building {RAMDISK_KERNEL_IMG4} from ramdisk kernel source")
|
||||
build_kernel_img4(
|
||||
kc_ramdisk_src,
|
||||
output_dir,
|
||||
temp_dir,
|
||||
im4m_path,
|
||||
RAMDISK_KERNEL_IMG4,
|
||||
"kcache_ramdisk",
|
||||
)
|
||||
print(" building krnl.img4 from restore kernel")
|
||||
|
||||
build_kernel_img4(
|
||||
kc_src,
|
||||
output_dir,
|
||||
temp_dir,
|
||||
im4m_path,
|
||||
"krnl.img4",
|
||||
"kcache",
|
||||
)
|
||||
|
||||
# ── 8. Ramdisk + Trustcache ──────────────────────────────────
|
||||
print(f"\n{'=' * 60}")
|
||||
print(f" 8. Ramdisk + Trustcache")
|
||||
print(f"{'=' * 60}")
|
||||
build_ramdisk(restore_dir, im4m_path, vm_dir, input_dir, output_dir, temp_dir)
|
||||
|
||||
# ── Cleanup ──────────────────────────────────────────────────
|
||||
print(f"\n[*] Cleaning up {TEMP_DIR}/...")
|
||||
shutil.rmtree(temp_dir, ignore_errors=True)
|
||||
sshrd_dir = os.path.join(vm_dir, "SSHRD")
|
||||
if os.path.exists(sshrd_dir):
|
||||
shutil.rmtree(sshrd_dir, ignore_errors=True)
|
||||
|
||||
# ── Summary ──────────────────────────────────────────────────
|
||||
print(f"\n{'=' * 60}")
|
||||
print(f" Ramdisk build complete!")
|
||||
print(f" Output: {output_dir}/")
|
||||
print(f"{'=' * 60}")
|
||||
for f in sorted(os.listdir(output_dir)):
|
||||
size = os.path.getsize(os.path.join(output_dir, f))
|
||||
print(f" {f:45s} {size:>10,} bytes")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,55 +0,0 @@
|
||||
#!/bin/zsh
|
||||
# ramdisk_send.sh — Send signed ramdisk components to device via pymobiledevice3.
|
||||
#
|
||||
# Usage: ./ramdisk_send.sh [ramdisk_dir]
|
||||
#
|
||||
# Expects device in DFU mode. Loads iBSS/iBEC, then boots with
|
||||
# SPTM, TXM, trustcache, ramdisk, device tree, SEP, and kernel.
|
||||
set -euo pipefail
|
||||
|
||||
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}"
|
||||
|
||||
if [[ -n "$IRECOVERY_ECID" ]]; then
|
||||
IRECOVERY_ECID="${IRECOVERY_ECID#0x}"
|
||||
IRECOVERY_ECID="${IRECOVERY_ECID#0X}"
|
||||
[[ "$IRECOVERY_ECID" =~ ^[0-9A-Fa-f]{1,16}$ ]] || {
|
||||
echo "[-] Invalid IRECOVERY_ECID: ${IRECOVERY_ECID}"
|
||||
exit 1
|
||||
}
|
||||
IRECOVERY_ECID="0x${IRECOVERY_ECID:u}"
|
||||
echo "[*] Using ECID selector for ramdisk send: ${IRECOVERY_ECID}"
|
||||
fi
|
||||
|
||||
echo "[*] Identity context for ramdisk_send:"
|
||||
if [[ -n "$RAMDISK_UDID" ]]; then
|
||||
echo " UDID: ${RAMDISK_UDID}"
|
||||
else
|
||||
echo " UDID: <unset>"
|
||||
fi
|
||||
if [[ -n "$IRECOVERY_ECID" ]]; then
|
||||
echo " ECID: ${IRECOVERY_ECID}"
|
||||
else
|
||||
echo " ECID: <unset>"
|
||||
fi
|
||||
|
||||
if [[ ! -d "$RAMDISK_DIR" ]]; then
|
||||
echo "[-] Ramdisk directory not found: $RAMDISK_DIR"
|
||||
echo " Run 'make ramdisk_build' first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$PMD3_BRIDGE" ]]; then
|
||||
echo "[-] pymobiledevice3 bridge script not found: $PMD3_BRIDGE"
|
||||
exit 1
|
||||
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[@]}"
|
||||
+11
-338
@@ -5,9 +5,8 @@
|
||||
# 1) Host deps + project setup/build
|
||||
# 2) vm_new + fw_prepare + fw_patch (or fw_patch_dev/ fw_patch_jb with --dev/--jb)
|
||||
# 3) DFU restore (boot_dfu + restore_get_shsh + restore)
|
||||
# 4) CFW install — default: ramdisk-free host-mount install + offline snapshot
|
||||
# flip (cfw_install_host, VM off). Legacy ramdisk path (boot_dfu +
|
||||
# ramdisk_build/send + iproxy + cfw_install*) is opt-in via USE_RAMDISK_CFW=1.
|
||||
# 4) CFW install — host-mount install + offline snapshot flip
|
||||
# (cfw_install_host, VM off)
|
||||
# 5) First boot launch (`make boot`) with printed in-guest commands
|
||||
|
||||
set -euo pipefail
|
||||
@@ -19,11 +18,9 @@ cd "$PROJECT_ROOT"
|
||||
|
||||
LOG_DIR="${PROJECT_ROOT}/setup_logs"
|
||||
DFU_LOG="${LOG_DIR}/boot_dfu.log"
|
||||
IPROXY_LOG=""
|
||||
BOOT_LOG="${LOG_DIR}/boot.log"
|
||||
|
||||
DFU_PID=""
|
||||
IPROXY_PID=""
|
||||
BOOT_PID=""
|
||||
BOOT_FIFO=""
|
||||
BOOT_FIFO_FD=""
|
||||
@@ -34,23 +31,9 @@ VM_DIR_ABS="${VM_DIR:A}"
|
||||
AUTO_KILL_VM_LOCKS="${AUTO_KILL_VM_LOCKS:-1}"
|
||||
POST_RESTORE_KILL_DELAY="${POST_RESTORE_KILL_DELAY:-30}"
|
||||
POST_KILL_SETTLE_DELAY="${POST_KILL_SETTLE_DELAY:-5}"
|
||||
RAMDISK_SSH_TIMEOUT="${RAMDISK_SSH_TIMEOUT:-60}"
|
||||
RAMDISK_SSH_INTERVAL="${RAMDISK_SSH_INTERVAL:-2}"
|
||||
RAMDISK_SSH_PORT="${RAMDISK_SSH_PORT:-}"
|
||||
RAMDISK_SSH_USER="${RAMDISK_SSH_USER:-root}"
|
||||
RAMDISK_SSH_PASS="${RAMDISK_SSH_PASS:-alpine}"
|
||||
IPROXY_UDID="${IPROXY_UDID:-}"
|
||||
IPROXY_DEVICE_WAIT_TIMEOUT="${IPROXY_DEVICE_WAIT_TIMEOUT:-90}"
|
||||
IPROXY_DEVICE_WAIT_INTERVAL="${IPROXY_DEVICE_WAIT_INTERVAL:-1}"
|
||||
RAMDISK_SSH_PORT_EXPLICIT=0
|
||||
if [[ -n "$RAMDISK_SSH_PORT" ]]; then
|
||||
RAMDISK_SSH_PORT_EXPLICIT=1
|
||||
fi
|
||||
|
||||
DEVICE_UDID=""
|
||||
DEVICE_ECID=""
|
||||
IPROXY_TARGET_UDID=""
|
||||
IPROXY_RESOLVE_REASON=""
|
||||
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]+)+#|:/[^ ]* root#}"
|
||||
@@ -148,174 +131,6 @@ load_device_identity() {
|
||||
echo "[+] Device identity loaded: UDID=${DEVICE_UDID} ECID=0x${DEVICE_ECID}"
|
||||
}
|
||||
|
||||
list_usbmux_udids() {
|
||||
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() {
|
||||
local -a udids
|
||||
local udid
|
||||
udids=(${(@f)$(list_usbmux_udids)})
|
||||
|
||||
if (( ${#udids[@]} == 0 )); then
|
||||
echo " (none)"
|
||||
return
|
||||
fi
|
||||
|
||||
for udid in "${udids[@]}"; do
|
||||
echo " - ${udid}"
|
||||
done
|
||||
}
|
||||
|
||||
try_resolve_iproxy_target_udid() {
|
||||
local -a usbmux_udids ecid_matches
|
||||
local udid
|
||||
local ecid_lower
|
||||
|
||||
IPROXY_TARGET_UDID=""
|
||||
IPROXY_RESOLVE_REASON=""
|
||||
|
||||
if [[ -n "$IPROXY_UDID" ]]; then
|
||||
IPROXY_TARGET_UDID="$IPROXY_UDID"
|
||||
IPROXY_RESOLVE_REASON="override"
|
||||
return 0
|
||||
fi
|
||||
|
||||
usbmux_udids=(${(@f)$(list_usbmux_udids)})
|
||||
if (( ${#usbmux_udids[@]} == 0 )); then
|
||||
IPROXY_RESOLVE_REASON="none"
|
||||
return 1
|
||||
fi
|
||||
|
||||
for udid in "${usbmux_udids[@]}"; do
|
||||
if [[ "$udid" == "$DEVICE_UDID" ]]; then
|
||||
IPROXY_TARGET_UDID="$udid"
|
||||
IPROXY_RESOLVE_REASON="restore_match"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
ecid_lower="${DEVICE_ECID:l}"
|
||||
ecid_matches=()
|
||||
for udid in "${usbmux_udids[@]}"; do
|
||||
if [[ "${udid:l}" == *"${ecid_lower}"* ]]; then
|
||||
ecid_matches+=("$udid")
|
||||
fi
|
||||
done
|
||||
|
||||
if (( ${#ecid_matches[@]} == 1 )); then
|
||||
IPROXY_TARGET_UDID="${ecid_matches[1]}"
|
||||
IPROXY_RESOLVE_REASON="ecid_match"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if (( ${#usbmux_udids[@]} == 1 )); then
|
||||
IPROXY_RESOLVE_REASON="single_mismatch"
|
||||
return 3
|
||||
fi
|
||||
|
||||
IPROXY_RESOLVE_REASON="ambiguous"
|
||||
return 2
|
||||
}
|
||||
|
||||
wait_for_iproxy_target_udid() {
|
||||
local timeout interval waited rc
|
||||
|
||||
timeout="$IPROXY_DEVICE_WAIT_TIMEOUT"
|
||||
interval="$IPROXY_DEVICE_WAIT_INTERVAL"
|
||||
waited=0
|
||||
|
||||
[[ "$timeout" == <-> ]] || die "IPROXY_DEVICE_WAIT_TIMEOUT must be an integer (seconds)"
|
||||
[[ "$interval" == <-> ]] || die "IPROXY_DEVICE_WAIT_INTERVAL must be an integer (seconds)"
|
||||
(( timeout > 0 )) || die "IPROXY_DEVICE_WAIT_TIMEOUT must be > 0"
|
||||
(( interval > 0 )) || die "IPROXY_DEVICE_WAIT_INTERVAL must be > 0"
|
||||
|
||||
echo "[*] Resolving iproxy target UDID (timeout=${timeout}s)..."
|
||||
while (( waited < timeout )); do
|
||||
if try_resolve_iproxy_target_udid; then
|
||||
echo "[*] USBMux IDs currently visible:"
|
||||
print_usbmux_udids
|
||||
case "$IPROXY_RESOLVE_REASON" in
|
||||
override)
|
||||
echo "[*] Using explicit IPROXY_UDID override: ${IPROXY_TARGET_UDID}"
|
||||
;;
|
||||
restore_match)
|
||||
echo "[+] iproxy target UDID matched restore UDID: ${IPROXY_TARGET_UDID}"
|
||||
;;
|
||||
ecid_match)
|
||||
echo "[+] iproxy target UDID matched ECID substring: ${IPROXY_TARGET_UDID}"
|
||||
;;
|
||||
esac
|
||||
return
|
||||
fi
|
||||
rc=$?
|
||||
|
||||
if (( waited == 0 || waited % 5 == 0 )); then
|
||||
case "$rc" in
|
||||
2)
|
||||
echo " waiting for USBMux disambiguation... ${waited}s elapsed"
|
||||
;;
|
||||
3)
|
||||
echo " waiting for restore UDID/ECID match (strict mode)... ${waited}s elapsed"
|
||||
;;
|
||||
*)
|
||||
echo " waiting for USBMux device... ${waited}s elapsed"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
sleep "$interval"
|
||||
(( waited += interval ))
|
||||
done
|
||||
|
||||
echo "[-] Timed out resolving iproxy target UDID after ${timeout}s."
|
||||
echo "[-] USBMux IDs currently visible:"
|
||||
print_usbmux_udids
|
||||
if [[ "$IPROXY_RESOLVE_REASON" == "single_mismatch" ]]; then
|
||||
die "Only non-matching USBMux device was visible. Strict identity isolation is enabled; wait for restore UDID/ECID or set IPROXY_UDID explicitly."
|
||||
fi
|
||||
if [[ "$IPROXY_RESOLVE_REASON" == "ambiguous" ]]; then
|
||||
die "Multiple USBMux devices detected and none uniquely matched restore UDID/ECID. Set IPROXY_UDID explicitly."
|
||||
fi
|
||||
die "No USBMux devices detected for iproxy. Ensure ramdisk has fully booted USB stack."
|
||||
}
|
||||
|
||||
port_is_listening() {
|
||||
local port="$1"
|
||||
lsof -n -t -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1
|
||||
}
|
||||
|
||||
pick_random_ssh_port() {
|
||||
local attempt port
|
||||
for attempt in {1..200}; do
|
||||
port=$((20000 + (RANDOM % 40000)))
|
||||
if ! port_is_listening "$port"; then
|
||||
echo "$port"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
choose_ramdisk_ssh_port() {
|
||||
if [[ -n "$RAMDISK_SSH_PORT" ]]; then
|
||||
[[ "$RAMDISK_SSH_PORT" == <-> ]] || die "RAMDISK_SSH_PORT must be an integer"
|
||||
(( RAMDISK_SSH_PORT >= 1 && RAMDISK_SSH_PORT <= 65535 )) \
|
||||
|| die "RAMDISK_SSH_PORT out of range: ${RAMDISK_SSH_PORT}"
|
||||
if port_is_listening "$RAMDISK_SSH_PORT"; then
|
||||
die "RAMDISK_SSH_PORT ${RAMDISK_SSH_PORT} is already in use"
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
RAMDISK_SSH_PORT="$(pick_random_ssh_port)" \
|
||||
|| die "Failed to allocate a random local SSH forward port"
|
||||
}
|
||||
|
||||
parse_bool() {
|
||||
local raw="${1:-0}"
|
||||
# zsh `${var:l}` lowercases value for tolerant bool parsing.
|
||||
@@ -491,11 +306,6 @@ cleanup() {
|
||||
BOOT_FIFO=""
|
||||
fi
|
||||
|
||||
if [[ -n "$IPROXY_PID" ]]; then
|
||||
stop_process_tree "$IPROXY_PID"
|
||||
IPROXY_PID=""
|
||||
fi
|
||||
|
||||
if [[ -n "$DFU_PID" ]]; then
|
||||
stop_process_tree "$DFU_PID"
|
||||
DFU_PID=""
|
||||
@@ -841,120 +651,6 @@ wait_for_recovery() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
start_iproxy() {
|
||||
[[ -n "$DEVICE_UDID" ]] || die "Device UDID is empty; cannot resolve iproxy target"
|
||||
|
||||
choose_ramdisk_ssh_port
|
||||
wait_for_iproxy_target_udid
|
||||
|
||||
if port_is_listening "$RAMDISK_SSH_PORT"; then
|
||||
if [[ "$RAMDISK_SSH_PORT_EXPLICIT" == "1" ]]; then
|
||||
die "RAMDISK_SSH_PORT ${RAMDISK_SSH_PORT} is already in use"
|
||||
fi
|
||||
RAMDISK_SSH_PORT="$(pick_random_ssh_port)" \
|
||||
|| die "Failed to allocate a free random local SSH forward port"
|
||||
fi
|
||||
|
||||
IPROXY_LOG="${LOG_DIR}/iproxy_${RAMDISK_SSH_PORT}.log"
|
||||
mkdir -p "$LOG_DIR"
|
||||
: > "$IPROXY_LOG"
|
||||
|
||||
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
|
||||
if ! kill -0 "$IPROXY_PID" 2>/dev/null; then
|
||||
echo "[-] iproxy exited early. Log:"
|
||||
tail -n 40 "$IPROXY_LOG" || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[+] iproxy running (pid=$IPROXY_PID, log=$IPROXY_LOG)"
|
||||
}
|
||||
|
||||
wait_for_ramdisk_ssh() {
|
||||
local sshpass_bin
|
||||
local waited=0
|
||||
|
||||
[[ "$RAMDISK_SSH_TIMEOUT" == <-> ]] || die "RAMDISK_SSH_TIMEOUT must be an integer (seconds)"
|
||||
[[ "$RAMDISK_SSH_INTERVAL" == <-> ]] || die "RAMDISK_SSH_INTERVAL must be an integer (seconds)"
|
||||
(( RAMDISK_SSH_TIMEOUT > 0 )) || die "RAMDISK_SSH_TIMEOUT must be > 0"
|
||||
(( RAMDISK_SSH_INTERVAL > 0 )) || die "RAMDISK_SSH_INTERVAL must be > 0"
|
||||
|
||||
sshpass_bin="$(command -v sshpass || true)"
|
||||
[[ -x "$sshpass_bin" ]] || die "sshpass not found (run: make setup_tools)"
|
||||
|
||||
echo "[*] Waiting for ramdisk SSH on ${RAMDISK_SSH_USER}@127.0.0.1:${RAMDISK_SSH_PORT} (timeout=${RAMDISK_SSH_TIMEOUT}s)..."
|
||||
while (( waited < RAMDISK_SSH_TIMEOUT )); do
|
||||
if [[ -n "$IPROXY_PID" ]] && ! kill -0 "$IPROXY_PID" 2>/dev/null; then
|
||||
echo "[-] iproxy process exited while waiting for ramdisk SSH."
|
||||
if [[ -n "$IPROXY_LOG" ]]; then
|
||||
echo "[-] iproxy log tail:"
|
||||
tail -n 40 "$IPROXY_LOG" 2>/dev/null || true
|
||||
fi
|
||||
die "iproxy exited before ramdisk SSH became ready."
|
||||
fi
|
||||
|
||||
if [[ -f "$DFU_LOG" ]] && grep -Eiq 'panic|kernel panic|stackshot succeeded|panic\.apple\.com' "$DFU_LOG"; then
|
||||
echo "[-] Detected panic markers in boot_dfu log while waiting for ramdisk SSH."
|
||||
echo "[-] boot_dfu log tail:"
|
||||
tail -n 80 "$DFU_LOG" 2>/dev/null || true
|
||||
die "Ramdisk boot appears to have panicked before SSH became ready."
|
||||
fi
|
||||
|
||||
if [[ -n "$DFU_PID" ]] && ! kill -0 "$DFU_PID" 2>/dev/null; then
|
||||
echo "[-] boot_dfu process exited while waiting for ramdisk SSH."
|
||||
echo "[-] boot_dfu log tail:"
|
||||
tail -n 80 "$DFU_LOG" 2>/dev/null || true
|
||||
die "DFU boot exited before ramdisk SSH became ready."
|
||||
fi
|
||||
|
||||
if "$sshpass_bin" -p "$RAMDISK_SSH_PASS" ssh \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
-o PreferredAuthentications=password \
|
||||
-o ConnectTimeout=5 \
|
||||
-q \
|
||||
-p "$RAMDISK_SSH_PORT" \
|
||||
"${RAMDISK_SSH_USER}@127.0.0.1" "echo ready" >/dev/null 2>&1
|
||||
then
|
||||
echo "[+] Ramdisk SSH is ready"
|
||||
return
|
||||
fi
|
||||
|
||||
if (( waited == 0 || waited % 10 == 0 )); then
|
||||
echo " waiting... ${waited}s elapsed"
|
||||
fi
|
||||
|
||||
sleep "$RAMDISK_SSH_INTERVAL"
|
||||
(( waited += RAMDISK_SSH_INTERVAL ))
|
||||
done
|
||||
|
||||
echo "[-] Timed out waiting for ramdisk SSH readiness."
|
||||
echo "[-] Identity context: restore_udid=${DEVICE_UDID}, ecid=0x${DEVICE_ECID}, iproxy_target_udid=${IPROXY_TARGET_UDID}"
|
||||
echo "[-] USBMux IDs at timeout:"
|
||||
print_usbmux_udids
|
||||
if [[ -n "$IPROXY_LOG" ]]; then
|
||||
echo "[-] iproxy log tail:"
|
||||
tail -n 40 "$IPROXY_LOG" 2>/dev/null || true
|
||||
fi
|
||||
echo "[-] boot_dfu log tail:"
|
||||
tail -n 60 "$DFU_LOG" 2>/dev/null || true
|
||||
die "Ramdisk SSH did not become ready in ${RAMDISK_SSH_TIMEOUT}s."
|
||||
}
|
||||
|
||||
stop_iproxy() {
|
||||
if [[ -n "$IPROXY_PID" ]] && kill -0 "$IPROXY_PID" 2>/dev/null; then
|
||||
echo "[*] Stopping iproxy (pid=$IPROXY_PID)..."
|
||||
stop_process_tree "$IPROXY_PID"
|
||||
fi
|
||||
IPROXY_PID=""
|
||||
}
|
||||
|
||||
parse_args() {
|
||||
local arg
|
||||
for arg in "$@"; do
|
||||
@@ -1019,8 +715,7 @@ main() {
|
||||
setup_sudo_noninteractive
|
||||
|
||||
local fw_patch_target="fw_patch"
|
||||
local cfw_install_target="cfw_install"
|
||||
local cfw_variant="regular" # variant for the ramdisk-free `cfw_install_host`
|
||||
local cfw_variant="regular" # variant for `cfw_install_host`
|
||||
local mode_label="base"
|
||||
|
||||
if (( JB_MODE + DEV_MODE + EXP_MODE + LESS_MODE > 1 )); then
|
||||
@@ -1029,22 +724,18 @@ main() {
|
||||
|
||||
if [[ "$JB_MODE" -eq 1 ]]; then
|
||||
fw_patch_target="fw_patch_jb"
|
||||
cfw_install_target="cfw_install_jb"
|
||||
cfw_variant="jb"
|
||||
mode_label="jailbreak"
|
||||
elif [[ "$DEV_MODE" -eq 1 ]]; then
|
||||
fw_patch_target="fw_patch_dev"
|
||||
cfw_install_target="cfw_install_dev"
|
||||
cfw_variant="dev"
|
||||
mode_label="dev"
|
||||
elif [[ "$EXP_MODE" -eq 1 ]]; then
|
||||
fw_patch_target="fw_patch_exp"
|
||||
cfw_install_target="cfw_install_exp"
|
||||
cfw_variant="exp"
|
||||
mode_label="experimental"
|
||||
elif [[ "$LESS_MODE" -eq 1 ]]; then
|
||||
fw_patch_target="fw_patch_less"
|
||||
cfw_install_target=""
|
||||
cfw_variant=""
|
||||
mode_label="less"
|
||||
fi
|
||||
@@ -1091,32 +782,14 @@ main() {
|
||||
echo "[*] Waiting ${POST_KILL_SETTLE_DELAY}s for cleanup before CFW install..."
|
||||
sleep "$POST_KILL_SETTLE_DELAY"
|
||||
|
||||
if [[ "${USE_RAMDISK_CFW:-0}" == "1" ]]; then
|
||||
# ── Legacy ramdisk CFW install (opt-in via USE_RAMDISK_CFW=1) ──
|
||||
echo ""
|
||||
echo "=== Ramdisk + CFW phase (legacy, USE_RAMDISK_CFW=1) ==="
|
||||
start_boot_dfu
|
||||
load_device_identity
|
||||
wait_for_recovery
|
||||
run_make "Ramdisk" ramdisk_build RAMDISK_UDID="$DEVICE_UDID"
|
||||
echo "[*] Ramdisk identity context: restore_udid=${DEVICE_UDID} ecid=0x${DEVICE_ECID}"
|
||||
run_make "Ramdisk" ramdisk_send IRECOVERY_ECID="0x$DEVICE_ECID" RAMDISK_UDID="$DEVICE_UDID"
|
||||
start_iproxy
|
||||
wait_for_ramdisk_ssh
|
||||
run_make "CFW install" "$cfw_install_target" SSH_PORT="$RAMDISK_SSH_PORT"
|
||||
stop_boot_dfu
|
||||
stop_iproxy
|
||||
else
|
||||
# ── Default: ramdisk-free host-mount CFW install + offline snapshot flip.
|
||||
# The VM is off after the restore phase, so we attach Disk.img on the
|
||||
# host, place all CFW files, and rename the boot snapshot offline — no
|
||||
# DFU/ramdisk_send/iproxy/SSH. cfw_install_host re-execs under sudo
|
||||
# (SUDO_ASKPASS from setup_sudo_noninteractive when SUDO_PASSWORD is set).
|
||||
echo ""
|
||||
echo "=== CFW install (host-mount, ramdisk-free) ==="
|
||||
check_vm_storage_locks
|
||||
run_make "CFW install" cfw_install_host VARIANT="$cfw_variant" SPOOF_BUILD="${SPOOF_BUILD:-}"
|
||||
fi
|
||||
# Host-mount CFW install + offline snapshot flip. The VM is off after the
|
||||
# restore phase, so we attach Disk.img on the host, place all CFW files, and
|
||||
# rename the boot snapshot offline. cfw_install_host re-execs under sudo
|
||||
# (SUDO_ASKPASS from setup_sudo_noninteractive when SUDO_PASSWORD is set).
|
||||
echo ""
|
||||
echo "=== CFW install (host-mount) ==="
|
||||
check_vm_storage_locks
|
||||
run_make "CFW install" cfw_install_host VARIANT="$cfw_variant" SPOOF_BUILD="${SPOOF_BUILD:-}"
|
||||
fi
|
||||
|
||||
if [[ "$LESS_MODE" -eq 0 || "$NO_BINPACK" -eq 0 ]]; then
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
# vphone_jb_setup.sh — First-boot JB finalization script.
|
||||
#
|
||||
# Deployed to /cores/ during cfw_install_jb.sh (ramdisk phase).
|
||||
# Deployed to /cores/ during cfw_install_jb.sh.
|
||||
# Runs automatically via LaunchDaemon on first normal boot.
|
||||
# Idempotent — safe to re-run on subsequent boots.
|
||||
#
|
||||
|
||||
@@ -203,7 +203,7 @@ struct PatchComponentCLI: ParsableCommand {
|
||||
// Production JB patching runs through `patch-firmware --variant jb`; this
|
||||
// standalone option exists so `tests/test_jb_kernel_patches.sh` can run the
|
||||
// JB kernel layer over a single kernelcache and dump records via --records-out.
|
||||
// (txm / kernel-base, by contrast, are also used by scripts/ramdisk_build.py.)
|
||||
// (txm / kernel-base, by contrast, are standalone single-component patchers.)
|
||||
case kernelJB = "kernel-jb"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# Offline APFS root-snapshot rename for vphone Disk.img (ramdisk-free CFW flip).
|
||||
# Offline APFS root-snapshot rename for vphone Disk.img (CFW boot-source flip).
|
||||
#
|
||||
# Renames the com.apple.os.update-<hash> system snapshot in place so the
|
||||
# (seal-enforcement-patched) guest kernel can't find the named root snapshot
|
||||
|
||||
Reference in New Issue
Block a user