diff --git a/AGENTS.md b/AGENTS.md index ecc3e51..87c1940 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 ``` diff --git a/Makefile b/Makefile index 24f535c..195510b 100644 --- a/Makefile +++ b/Makefile @@ -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= (exp)" + @echo " make cfw_install_host Select variant: VARIANT=regular|dev|jb|exp (default exp) SPOOF_BUILD= (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= (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)" diff --git a/README.md b/README.md index 0fa781f..c45496f 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/README_ja.md b/docs/README_ja.md index 91c64bb..efa5ca5 100644 --- a/docs/README_ja.md +++ b/docs/README_ja.md @@ -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 diff --git a/docs/README_ko.md b/docs/README_ko.md index b48699c..74d8853 100644 --- a/docs/README_ko.md +++ b/docs/README_ko.md @@ -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 diff --git a/docs/README_zh.md b/docs/README_zh.md index c863497..2d6d465 100644 --- a/docs/README_zh.md +++ b/docs/README_zh.md @@ -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 diff --git a/scripts/cfw_host_mode.sh b/scripts/cfw_host_mode.sh deleted file mode 100644 index 6183847..0000000 --- a/scripts/cfw_host_mode.sh +++ /dev/null @@ -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() { :; } diff --git a/scripts/cfw_install.sh b/scripts/cfw_install.sh index 7b96722..6dfcb97 100755 --- a/scripts/cfw_install.sh +++ b/scripts/cfw_install.sh @@ -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 [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 diff --git a/scripts/cfw_install_dev.sh b/scripts/cfw_install_dev.sh index 76fdb50..498139b 100755 --- a/scripts/cfw_install_dev.sh +++ b/scripts/cfw_install_dev.sh @@ -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 [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 diff --git a/scripts/cfw_install_exp.sh b/scripts/cfw_install_exp.sh index 6e642d5..c5da8f7 100755 --- a/scripts/cfw_install_exp.sh +++ b/scripts/cfw_install_exp.sh @@ -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 [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 diff --git a/scripts/cfw_install_host.sh b/scripts/cfw_install_host.sh index d640ece..91b75b9 100755 --- a/scripts/cfw_install_host.sh +++ b/scripts/cfw_install_host.sh @@ -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 diff --git a/scripts/cfw_install_jb.sh b/scripts/cfw_install_jb.sh index 47fcaa9..1381873 100755 --- a/scripts/cfw_install_jb.sh +++ b/scripts/cfw_install_jb.sh @@ -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 [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 diff --git a/scripts/patchers/cfw_patch_post_restore_dt.py b/scripts/patchers/cfw_patch_post_restore_dt.py index 9031116..84e141f 100644 --- a/scripts/patchers/cfw_patch_post_restore_dt.py +++ b/scripts/patchers/cfw_patch_post_restore_dt.py @@ -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//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//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 diff --git a/scripts/pymobiledevice3_bridge.py b/scripts/pymobiledevice3_bridge.py index 7f49772..52ef3ba 100755 --- a/scripts/pymobiledevice3_bridge.py +++ b/scripts/pymobiledevice3_bridge.py @@ -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( diff --git a/scripts/ramdisk_build.py b/scripts/ramdisk_build.py deleted file mode 100755 index e30358e..0000000 --- a/scripts/ramdisk_build.py +++ /dev/null @@ -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() diff --git a/scripts/ramdisk_send.sh b/scripts/ramdisk_send.sh deleted file mode 100755 index 76a9755..0000000 --- a/scripts/ramdisk_send.sh +++ /dev/null @@ -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: " -fi -if [[ -n "$IRECOVERY_ECID" ]]; then - echo " ECID: ${IRECOVERY_ECID}" -else - echo " ECID: " -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[@]}" diff --git a/scripts/setup_machine.sh b/scripts/setup_machine.sh index 1b4c560..c3307e6 100755 --- a/scripts/setup_machine.sh +++ b/scripts/setup_machine.sh @@ -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 diff --git a/scripts/vphone_jb_setup.sh b/scripts/vphone_jb_setup.sh index 1b9e9f8..ff8ca02 100755 --- a/scripts/vphone_jb_setup.sh +++ b/scripts/vphone_jb_setup.sh @@ -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. # diff --git a/sources/vphone-cli/VPhoneCLI.swift b/sources/vphone-cli/VPhoneCLI.swift index c24c04a..0468f56 100644 --- a/sources/vphone-cli/VPhoneCLI.swift +++ b/sources/vphone-cli/VPhoneCLI.swift @@ -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" } diff --git a/tools/apfs_snap_rename.py b/tools/apfs_snap_rename.py index 5a5685c..92d9342 100755 --- a/tools/apfs_snap_rename.py +++ b/tools/apfs_snap_rename.py @@ -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- system snapshot in place so the # (seal-enforcement-patched) guest kernel can't find the named root snapshot