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:
zqxwce
2026-07-06 15:15:01 +03:00
committed by zqxwce
co-authored by Claude Opus 4.8
parent 321c7117b3
commit fcc30e1657
20 changed files with 450 additions and 2208 deletions
+5 -2
View File
@@ -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
```
+10 -29
View File
@@ -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)"
+8 -22
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
-52
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+8 -9
View File
@@ -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
View File
@@ -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
-87
View File
@@ -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(
-888
View File
@@ -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()
-55
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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.
#
+1 -1
View File
@@ -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 -1
View File
@@ -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