mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-09-02 02:34:29 +00:00
* feat: add --root-popup to elevate CFW host-mount via macOS auth dialog Adds --root-popup to `cfw install` and `vm create`, elevating the CFW host-mount through macOS's native authentication dialog (osascript -> do shell script with administrator privileges) instead of the script's sudo re-exec. do shell script runs under a bare env, so the vars the bundled scripts read are forwarded inline, plus SUDO_USER so the script's chown-back still returns artifacts to the invoking user. On `vm create`, --sudo-password takes precedence. Co-Authored-By: Claude Opus 4.8 <[email protected]> * cfw: remove entire .cfw_temp on install cleanup Replaces the selective `rm -f` of individual temp binaries with `rm -rf "$TEMP_DIR"`, dropping the cached Cryptex DMGs along with the temp files. Co-Authored-By: Claude Opus 4.8 <[email protected]> --------- Co-authored-by: Claude Opus 4.8 <[email protected]>
828 lines
35 KiB
Bash
Executable File
828 lines
35 KiB
Bash
Executable File
#!/bin/zsh
|
|
# cfw_install_exp.sh — Install base CFW + JB extensions + EXP experimental
|
|
# 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,
|
|
# BaseBin hook deployment, JB-1..JB-5), and additionally the experimental
|
|
# phases — labeled `EXP-JB-N` to make their EXP-only scope obvious:
|
|
# - Pre-step : byte-5 mangle of kern.hv_vmm_present in DSC dylibs
|
|
# (paired with KernelEXPPatcher's kernel-side rename).
|
|
# - EXP-JB-3.5 : surgical 2-insn patch of watchdogd's hv_vmm cache +
|
|
# 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.
|
|
# - EXP-JB-7 : optional ProductBuildVersion rewrite in
|
|
# SystemVersion.plist (gated on the SPOOF_BUILD env var).
|
|
#
|
|
# Stages JB-1..JB-5 remain genuine jailbreak phases (inherited from the JB
|
|
# pipeline). Stages prefixed EXP-JB-* are EXP-exclusive — they do NOT run
|
|
# when the JB or DEV install scripts are used. JB and DEV remain on their
|
|
# pre-experimental baseline.
|
|
#
|
|
# Prerequisites (in addition to cfw_install.sh requirements):
|
|
# - cfw_jb_input/ or resources/cfw_jb_input.tar.zst present
|
|
# - zstd (for bootstrap decompression)
|
|
#
|
|
# Usage: make cfw_install_exp
|
|
set -euo pipefail
|
|
|
|
# ── Restore caller's PATH — Nix /etc/zshenv resets PATH on zsh startup ─
|
|
[[ -n "${_VPHONE_PATH:-}" ]] && export PATH="$_VPHONE_PATH"
|
|
VM_DIR="${1:-.}"
|
|
SCRIPT_DIR="${0:a:h}"
|
|
|
|
# ── Python resolver — prefer project venv over whatever is in PATH ─
|
|
# Resolves to .venv/bin/python3 relative to the project root (parent of
|
|
# scripts/), falling back to the system python3 when the venv is absent.
|
|
_resolve_python3() {
|
|
if [[ -n "${VPHONE_PYTHON:-}" ]]; then
|
|
echo "$VPHONE_PYTHON"
|
|
return
|
|
fi
|
|
local venv_py="${SCRIPT_DIR:h}/.venv/bin/python3"
|
|
if [[ -x "$venv_py" ]]; then
|
|
echo "$venv_py"
|
|
else
|
|
command -v python3 || true
|
|
fi
|
|
}
|
|
PYTHON3="$(_resolve_python3)"
|
|
|
|
# ════════════════════════════════════════════════════════════════
|
|
# Step 1: Run base CFW install (skip halt — we continue with JB phases)
|
|
# ════════════════════════════════════════════════════════════════
|
|
echo "[*] cfw_install_exp.sh — Installing CFW + JB extensions + EXP experimental patches..."
|
|
echo ""
|
|
|
|
# ────────────────────────────────────────────────────────────────────
|
|
# Pre-step: patch hv_vmm_present user-mode consumers in the SystemOS
|
|
# Cryptex's DSC chunks BEFORE running cfw_install.sh.
|
|
#
|
|
# cfw_install.sh decrypts the SystemOS Cryptex AEA into
|
|
# $TEMP_DIR/CryptexSystemOS.dmg and reuses it on subsequent runs. We
|
|
# pre-create that decrypted DMG here, mount it, patch the DSC chunks
|
|
# in place, unmount, and let cfw_install.sh pick up the cached
|
|
# (already-patched) DMG. cfw_install.sh itself is unmodified — this
|
|
# is the EXP variant's device-like user-mode patching, kept out of
|
|
# the JB install path so JB remains unaffected.
|
|
# ────────────────────────────────────────────────────────────────────
|
|
VM_DIR_ABS="$(cd "${VM_DIR:-.}" && pwd)"
|
|
JB_TEMP_DIR="$VM_DIR_ABS/.cfw_temp"
|
|
JB_SYSOS_DMG="$JB_TEMP_DIR/CryptexSystemOS.dmg"
|
|
JB_MNT_SYSOS="$JB_TEMP_DIR/mnt_sysos_hv_vmm"
|
|
mkdir -p "$JB_TEMP_DIR"
|
|
|
|
# Find the restore directory (same logic as cfw_install.sh)
|
|
JB_RESTORE_DIR=""
|
|
for d in "$VM_DIR_ABS"/iPhone*_Restore; do
|
|
[[ -d "$d" ]] && { JB_RESTORE_DIR="$d"; break; }
|
|
done
|
|
|
|
if [[ -z "$JB_RESTORE_DIR" ]]; then
|
|
echo "[!] hv_vmm DSC patch: no restore directory found, skipping"
|
|
elif [[ ! -f "$JB_SYSOS_DMG" ]]; then
|
|
# Not yet decrypted — decrypt to the cache location cfw_install.sh expects.
|
|
echo "[*] hv_vmm DSC patch: decrypting SystemOS into cache..."
|
|
JB_CRYPTEX_SYSOS=$("$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" cryptex-paths "$JB_RESTORE_DIR/iPhone-BuildManifest.plist" | head -1)
|
|
JB_AEA_KEY=$(ipsw fw aea --key "$JB_RESTORE_DIR/$JB_CRYPTEX_SYSOS")
|
|
aea decrypt -i "$JB_RESTORE_DIR/$JB_CRYPTEX_SYSOS" -o "$JB_SYSOS_DMG" -key-value "$JB_AEA_KEY"
|
|
fi
|
|
|
|
if [[ -f "$JB_SYSOS_DMG" ]]; then
|
|
# Mount, patch chunks (hv_vmm + camera, same cryptex), unmount.
|
|
# Idempotent: re-running on an already-patched DMG is a no-op
|
|
# (hv_vmm patcher detects already-patched cstrings; camera patcher
|
|
# refuses pre-patched function entries unless --force is passed).
|
|
echo "[*] DSC patches: mounting cached SystemOS DMG..."
|
|
mkdir -p "$JB_MNT_SYSOS"
|
|
sudo ${SUDO_ASKPASS:+-A} hdiutil detach "$JB_MNT_SYSOS" -force 2>/dev/null || true
|
|
sudo ${SUDO_ASKPASS:+-A} hdiutil attach -mountpoint "$JB_MNT_SYSOS" "$JB_SYSOS_DMG" -nobrowse -owners off
|
|
|
|
JB_DSC_CHUNKS_DIR="$JB_MNT_SYSOS/System/Library/Caches/com.apple.dyld"
|
|
JB_DSC_HEADER="$JB_DSC_CHUNKS_DIR/dyld_shared_cache_arm64e"
|
|
if [[ -d "$JB_DSC_CHUNKS_DIR" ]]; then
|
|
echo "[*] hv_vmm DSC patch: patching chunks under $JB_DSC_CHUNKS_DIR..."
|
|
"$SCRIPT_DIR/patch_hv_vmm_userland.sh" dsc "$JB_DSC_CHUNKS_DIR"
|
|
echo "[+] hv_vmm DSC patch: chunks patched"
|
|
|
|
# Camera DSC patches: (1) short-circuit the NeutrinoCore
|
|
# _NUStyleTransfer*Processor methods so Camera.app's style-
|
|
# thumbnail pipeline doesn't crash on this VM build, and (2)
|
|
# force +[AVCaptureDevice authorizationStatusForMediaType:] =
|
|
# Authorized so apps don't bail at the TCC gate. Both are
|
|
# resolved per-build via `ipsw dyld symaddr` and verified by
|
|
# pacibsp prologue match — version-portable as long as the
|
|
# named ObjC symbols continue to exist. Combined with the
|
|
# /product/camera DT node added at fw_patch time, this lets
|
|
# Camera.app's icon show on Home Screen and the app launch
|
|
# without immediately bailing.
|
|
if [[ -f "$JB_DSC_HEADER" ]]; then
|
|
echo "[*] camera DSC patch: patching chunks under $JB_DSC_CHUNKS_DIR..."
|
|
if "$SCRIPT_DIR/patch_camera_userland.sh" dsc "$JB_DSC_CHUNKS_DIR" "$JB_DSC_HEADER"; then
|
|
echo "[+] camera DSC patch: chunks patched"
|
|
else
|
|
echo "[!] camera DSC patch: failed (likely build-version mismatch); continuing"
|
|
fi
|
|
else
|
|
echo "[-] camera DSC patch: $JB_DSC_HEADER not found, skipping"
|
|
fi
|
|
else
|
|
echo "[-] DSC patches: $JB_DSC_CHUNKS_DIR not found, skipping"
|
|
fi
|
|
|
|
sudo ${SUDO_ASKPASS:+-A} hdiutil detach "$JB_MNT_SYSOS" -force
|
|
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.
|
|
zsh "$SCRIPT_DIR/cfw_install.sh" "$VM_DIR"
|
|
|
|
# ════════════════════════════════════════════════════════════════
|
|
# Step 2: JB-specific phases
|
|
# ════════════════════════════════════════════════════════════════
|
|
|
|
# Resolve absolute paths (same as base script)
|
|
VM_DIR="$(cd "${VM_DIR}" && pwd)"
|
|
|
|
# ── Configuration ───────────────────────────────────────────────
|
|
CFW_INPUT="cfw_input"
|
|
CFW_JB_INPUT="cfw_jb_input"
|
|
CFW_JB_ARCHIVE="cfw_jb_input.tar.zst"
|
|
TEMP_DIR="$VM_DIR/.cfw_temp"
|
|
DISABLE_LAUNCHD_HOOK="${DISABLE_LAUNCHD_HOOK:-0}"
|
|
|
|
# ── Helpers ─────────────────────────────────────────────────────
|
|
die() {
|
|
echo "[-] $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
check_prerequisites() {
|
|
local missing=()
|
|
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
|
|
}
|
|
|
|
ldid_sign() {
|
|
local file="$1" bundle_id="${2:-}"
|
|
local args=(-S -M "-K$VM_DIR/$CFW_INPUT/signcert.p12")
|
|
[[ -n "$bundle_id" ]] && args+=("-I$bundle_id")
|
|
ldid "${args[@]}" "$file"
|
|
}
|
|
|
|
ldid_sign_ent() {
|
|
local file="$1" entitlements_plist="$2" bundle_id="${3:-}"
|
|
local args=("-S$entitlements_plist" "-K$VM_DIR/$CFW_INPUT/signcert.p12")
|
|
[[ -n "$bundle_id" ]] && args+=("-I$bundle_id")
|
|
ldid "${args[@]}" "$file"
|
|
}
|
|
|
|
build_tweakloader() {
|
|
local src="$SCRIPT_DIR/tweakloader/TweakLoader.m"
|
|
local out="$TEMP_DIR/TweakLoader.dylib"
|
|
local sdk cc
|
|
|
|
[[ -f "$src" ]] || die "Missing tweak loader source at $src"
|
|
|
|
sdk="$(xcrun --sdk iphoneos --show-sdk-path)"
|
|
cc="$(xcrun --sdk iphoneos -f clang)"
|
|
|
|
"$cc" -isysroot "$sdk" \
|
|
-arch arm64 -arch arm64e \
|
|
-miphoneos-version-min=15.0 \
|
|
-dynamiclib \
|
|
-fobjc-arc -O3 \
|
|
-framework Foundation \
|
|
-o "$out" \
|
|
"$src"
|
|
|
|
ldid_sign "$out"
|
|
echo "$out"
|
|
}
|
|
|
|
# Build vpregister — registers JB apps via the containerized LaunchServices API on
|
|
# iOS 27, where -[LSApplicationWorkspace registerApplicationDictionary:] (uicache -a)
|
|
# is a deprecated no-op stub. Needs the lsd embedded-reg gate patch
|
|
# (cfw_patch_lsd_embedded_reg, applied by cfw_install.sh). Deployed to /cores and
|
|
# invoked by vphone_jb_setup.sh at first boot.
|
|
build_vpregister() {
|
|
local src="$SCRIPT_DIR/vpregister/vpregister.m"
|
|
local out="$TEMP_DIR/vpregister"
|
|
local sdk cc
|
|
|
|
[[ -f "$src" ]] || die "Missing vpregister source at $src"
|
|
|
|
sdk="$(xcrun --sdk iphoneos --show-sdk-path)"
|
|
cc="$(xcrun --sdk iphoneos -f clang)"
|
|
|
|
"$cc" -isysroot "$sdk" \
|
|
-arch arm64e \
|
|
-miphoneos-version-min=15.0 \
|
|
-fobjc-arc -Os \
|
|
-framework Foundation \
|
|
-Wl,-undefined,dynamic_lookup \
|
|
-o "$out" \
|
|
"$src"
|
|
|
|
ldid_sign_ent "$out" "$SCRIPT_DIR/vphoned/entitlements.plist"
|
|
echo "$out"
|
|
}
|
|
|
|
# Builds the libvcamcaptured.dylib injected into /usr/libexec/cameracaptured
|
|
# 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"
|
|
local out="$TEMP_DIR/libvcamcaptured.dylib"
|
|
local sdk cc
|
|
|
|
[[ -f "$src" ]] || die "Missing libvcamcaptured source at $src"
|
|
|
|
sdk="$(xcrun --sdk iphoneos --show-sdk-path)"
|
|
cc="$(xcrun --sdk iphoneos -f clang)"
|
|
|
|
"$cc" -isysroot "$sdk" \
|
|
-arch arm64e \
|
|
-miphoneos-version-min=15.0 \
|
|
-dynamiclib \
|
|
-fobjc-arc -Os \
|
|
-install_name /var/jb/usr/lib/libvcamcaptured.dylib \
|
|
-framework CoreMedia \
|
|
-framework CoreVideo \
|
|
-framework Foundation \
|
|
-o "$out" \
|
|
"$src"
|
|
|
|
ldid_sign "$out"
|
|
echo "$out"
|
|
}
|
|
|
|
# Builds the libcamfix.dylib substrate plugin. Loaded into every process
|
|
# that links AVFoundation via TweakLoader's Filter.Frameworks key —
|
|
# Camera.app, third-party apps, anywhere the
|
|
# documented capture interface is used. Implements the photo-delivery
|
|
# path through CAMCaptureEngine + the preview/state guards that keep
|
|
# the viewfinder live on the virtual camera.
|
|
build_libcamfix() {
|
|
local src="$SCRIPT_DIR/camfix/libcamfix.m"
|
|
local out="$TEMP_DIR/libcamfix.dylib"
|
|
local sdk cc
|
|
|
|
[[ -f "$src" ]] || die "Missing libcamfix source at $src"
|
|
|
|
sdk="$(xcrun --sdk iphoneos --show-sdk-path)"
|
|
cc="$(xcrun --sdk iphoneos -f clang)"
|
|
|
|
"$cc" -isysroot "$sdk" \
|
|
-arch arm64e \
|
|
-miphoneos-version-min=15.0 \
|
|
-dynamiclib \
|
|
-fobjc-arc -Os \
|
|
-install_name /var/jb/Library/MobileSubstrate/DynamicLibraries/libcamfix.dylib \
|
|
-framework AVFoundation \
|
|
-framework CoreImage \
|
|
-framework CoreGraphics \
|
|
-framework CoreMedia \
|
|
-framework CoreVideo \
|
|
-framework Foundation \
|
|
-framework ImageIO \
|
|
-framework IOSurface \
|
|
-framework MobileCoreServices \
|
|
-framework Photos \
|
|
-framework QuartzCore \
|
|
-framework UIKit \
|
|
-o "$out" \
|
|
"$src"
|
|
|
|
ldid_sign "$out"
|
|
echo "$out"
|
|
}
|
|
|
|
get_boot_manifest_hash() {
|
|
/bin/ls $MNT5 2>/dev/null | awk 'length($0)==96{print; exit}'
|
|
}
|
|
|
|
# ── Setup JB input resources ──────────────────────────────────
|
|
setup_cfw_jb_input() {
|
|
[[ -d "$VM_DIR/$CFW_JB_INPUT" ]] && return
|
|
local archive
|
|
for search_dir in "$SCRIPT_DIR/resources" "$SCRIPT_DIR" "$VM_DIR"; do
|
|
archive="$search_dir/$CFW_JB_ARCHIVE"
|
|
if [[ -f "$archive" ]]; then
|
|
echo " Extracting $CFW_JB_ARCHIVE..."
|
|
tar --zstd -xf "$archive" -C "$VM_DIR"
|
|
return
|
|
fi
|
|
done
|
|
die "JB mode: neither $CFW_JB_INPUT/ nor $CFW_JB_ARCHIVE found"
|
|
}
|
|
|
|
# ── Apply dev overlay (replace rpcserver_ios in iosbinpack64) ──
|
|
apply_dev_overlay() {
|
|
local dev_bin
|
|
for search_dir in "$SCRIPT_DIR/resources/cfw_dev" "$SCRIPT_DIR/cfw_dev"; do
|
|
dev_bin="$search_dir/rpcserver_ios"
|
|
if [[ -f "$dev_bin" ]]; then
|
|
echo " Applying dev overlay (rpcserver_ios)..."
|
|
local iosbinpack="$VM_DIR/$CFW_INPUT/jb/iosbinpack64.tar"
|
|
local tmpdir="$VM_DIR/.iosbinpack_tmp"
|
|
mkdir -p "$tmpdir"
|
|
tar -xf "$iosbinpack" -C "$tmpdir"
|
|
cp "$dev_bin" "$tmpdir/iosbinpack64/usr/local/bin/rpcserver_ios"
|
|
(cd "$tmpdir" && tar -cf "$iosbinpack" iosbinpack64)
|
|
rm -rf "$tmpdir"
|
|
return
|
|
fi
|
|
done
|
|
die "Dev overlay not found (cfw_dev/rpcserver_ios)"
|
|
}
|
|
|
|
# The VM's Disk.img is attached on the host by cfw_install_host.sh; its APFS
|
|
# volumes are mounted here and every file is placed with plain cp/chmod/etc.
|
|
# (the VM is off — nothing runs "on the device").
|
|
: "${CFW_HOST_CONTAINER:?CFW_HOST_CONTAINER unset — run via cfw_install_host.sh}"
|
|
HOST_MNT="${CFW_HOST_MNT:-/private/tmp/cfwhost}"
|
|
MNT1="$HOST_MNT/mnt1" # disk1s1 (System / rootfs)
|
|
MNT3="$HOST_MNT/mnt3" # disk1s3
|
|
MNT5="$HOST_MNT/mnt5" # disk1s5 (per-boot-manifest OS dir / procursus bootstrap)
|
|
TAR="$(command -v gtar 2>/dev/null || echo /opt/homebrew/bin/gtar)" # macOS bsdtar lacks GNU tar flags
|
|
mkdir -p "$HOST_MNT"
|
|
|
|
# Mount an APFS volume of the attached image container at a host mount point.
|
|
mount_vol() { # mount_vol <slice, e.g. s1> <mountpoint> [opts]
|
|
local dev="/dev/${CFW_HOST_CONTAINER}$1" mnt="$2" opts="${3:-rw}"
|
|
/bin/mkdir -p "$mnt"
|
|
/sbin/mount | /usr/bin/grep -q " on $mnt " && return 0
|
|
/sbin/mount_apfs -o "$opts" "$dev" "$mnt" 2>/dev/null || true
|
|
/sbin/mount | /usr/bin/grep -q " on $mnt " || die "mount failed: $dev -> $mnt"
|
|
}
|
|
|
|
# ── Check JB prerequisites ────────────────────────────────────
|
|
command -v zstd >/dev/null 2>&1 || die "'zstd' not found (required for JB bootstrap phase)"
|
|
|
|
setup_cfw_jb_input
|
|
JB_INPUT_DIR="$VM_DIR/$CFW_JB_INPUT"
|
|
echo ""
|
|
echo "[+] JB input resources: $JB_INPUT_DIR"
|
|
check_prerequisites
|
|
|
|
mkdir -p "$TEMP_DIR"
|
|
|
|
# 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 ! [[ -e "$MNT1/sbin/launchd.bak" ]]; then
|
|
echo " Creating backup..."
|
|
/bin/cp $MNT1/sbin/launchd $MNT1/sbin/launchd.bak
|
|
fi
|
|
|
|
cp "$MNT1/sbin/launchd.bak" "$TEMP_DIR/launchd"
|
|
|
|
# Extract original entitlements before patching (must preserve for spawn permissions)
|
|
echo " Extracting original entitlements..."
|
|
ldid -e "$TEMP_DIR/launchd" > "$TEMP_DIR/launchd.entitlements" 2>/dev/null || true
|
|
if [[ -s "$TEMP_DIR/launchd.entitlements" ]]; then
|
|
echo " [+] Preserved launchd entitlements"
|
|
else
|
|
echo " [!] No entitlements found on original launchd"
|
|
fi
|
|
|
|
# Injecting launchdhook into pid 1 is on by default. This boot-critical hook
|
|
# path has produced boot-analysis failures; set DISABLE_LAUNCHD_HOOK=1 to keep
|
|
# BaseBin deployed without loading /b from launchd.
|
|
if [[ "$DISABLE_LAUNCHD_HOOK" == "1" ]]; then
|
|
echo " [*] Skipping launchdhook dylib injection (DISABLE_LAUNCHD_HOOK=1)"
|
|
elif [[ -d "$JB_INPUT_DIR/basebin" ]]; then
|
|
echo " Injecting weak dylib load for /b (short launchdhook alias)..."
|
|
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" inject-dylib "$TEMP_DIR/launchd" "/b"
|
|
else
|
|
echo " [!] BaseBin is missing; skipping launchdhook injection"
|
|
fi
|
|
|
|
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-launchd-jetsam "$TEMP_DIR/launchd"
|
|
|
|
# Re-sign with original entitlements to avoid "operation not permitted" on spawn
|
|
if [[ -s "$TEMP_DIR/launchd.entitlements" ]]; then
|
|
ldid -S"$TEMP_DIR/launchd.entitlements" -M "-K$VM_DIR/$CFW_INPUT/signcert.p12" "$TEMP_DIR/launchd"
|
|
else
|
|
ldid_sign "$TEMP_DIR/launchd"
|
|
fi
|
|
cp -R "$TEMP_DIR/launchd" "$MNT1/sbin/launchd"
|
|
/bin/chmod 0755 $MNT1/sbin/launchd
|
|
|
|
echo " [+] launchd patched"
|
|
|
|
# ═══════════ JB-2 INSTALL IOSBINPACK64 ════════════════════════
|
|
echo ""
|
|
echo "[JB-2] Installing iosbinpack64..."
|
|
|
|
apply_dev_overlay
|
|
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"
|
|
|
|
# ═══════════ JB-3 PATCH debugserver entitlements ════
|
|
echo ""
|
|
echo "[JB-3] Patching debugserver entitlements..."
|
|
|
|
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"
|
|
cp -R "$TEMP_DIR/debugserver" "$MNT1/usr/libexec/debugserver"
|
|
/bin/chmod 0755 $MNT1/usr/libexec/debugserver
|
|
|
|
echo " [+] debugserver entitlements patched"
|
|
|
|
|
|
# ═══════════ JB-3b CAMPO SANDBOX FIX (iOS 27 only) ════════════
|
|
# Grant Campo the backboard/frontboard mach-lookups the 26.4 temporary-sandbox denies (see 0_binary_patch_comparison.md #14).
|
|
# 27-gated on the mounted rootfs SystemVersion.plist: 26.x userlands don't need it and Campo.app exists there too.
|
|
CAMPO_BIN="$MNT1/Applications/Campo.app/Campo"
|
|
CAMPO_BASE_IOS=$(/usr/bin/plutil -extract ProductVersion raw -o - "$MNT1/System/Library/CoreServices/SystemVersion.plist" 2>/dev/null || true)
|
|
case "$CAMPO_BASE_IOS" in
|
|
27.*)
|
|
if [[ -f "$CAMPO_BIN" ]]; then
|
|
echo ""
|
|
echo "[JB-3b] Granting Campo backboard/frontboard mach-lookup exceptions (iOS $CAMPO_BASE_IOS)..."
|
|
cp "$CAMPO_BIN" "$TEMP_DIR/Campo"
|
|
ldid -e "$TEMP_DIR/Campo" > "$TEMP_DIR/Campo.entitlements" 2>/dev/null || true
|
|
if [[ -s "$TEMP_DIR/Campo.entitlements" ]]; then
|
|
"$PYTHON3" "$SCRIPT_DIR/patchers/campo_mach_lookup_exceptions.py" "$TEMP_DIR/Campo.entitlements"
|
|
ldid_sign_ent "$TEMP_DIR/Campo" "$TEMP_DIR/Campo.entitlements"
|
|
cp -R "$TEMP_DIR/Campo" "$CAMPO_BIN"
|
|
/bin/chmod 0755 "$CAMPO_BIN"
|
|
echo " [+] Campo re-signed with backboard/frontboard mach-lookup exceptions"
|
|
else
|
|
echo " [!] Could not read Campo entitlements; skipping Campo sandbox fix"
|
|
fi
|
|
else
|
|
echo "[JB-3b] Campo.app not present in this OS image; skipping Campo sandbox fix"
|
|
fi
|
|
;;
|
|
*)
|
|
echo "[JB-3b] skip Campo sandbox fix (base iOS ${CAMPO_BASE_IOS:-unknown} — 27-only)"
|
|
;;
|
|
esac
|
|
|
|
|
|
# ═══════════ EXP-JB-3.5 PATCH watchdogd hv_vmm_present cache ══
|
|
#
|
|
# Background: the kernel-side OID rename (KernelEXPPatchHvVmmRename)
|
|
# makes sysctlbyname("kern.hv_vmm_present", ...) return ENOENT on this
|
|
# image. watchdogd caches that answer at startup and uses it to decide
|
|
# whether to look for the IOWatchdog kext. The unpatched flow takes
|
|
# the "not on a VM" branch on ENOENT, fails to find the kext (it
|
|
# doesn't exist on the VM), calls _os_crash -> brk #1, and launchd's
|
|
# `_PanicOnCrash` knob in com.apple.watchdogd.plist escalates the
|
|
# resulting SIGTRAP to a kernel panic.
|
|
#
|
|
# Patch shape: two-instruction surgical edit at every site in
|
|
# watchdogd that has the canonical
|
|
# adrp/add(kern.hv_vmm_present) -> bl _sysctlbyname -> cbnz w0,skip
|
|
# -> cset wN,ne -> strb wN,[global]
|
|
# shape. The edit forces the cached byte to 1 regardless of the
|
|
# sysctl result, so the downstream branch at +0x58e0 takes watchdogd's
|
|
# pre-existing "detected virtual machine environment" clean-exit path.
|
|
# The patcher also recomputes the affected CodeDirectory slot hashes
|
|
# (cfw_macho_codesign) so TXM still accepts the modified pages on
|
|
# demand-page-in. We deliberately do NOT re-sign with ldid — the
|
|
# Apple-issued code-signing identifier ("com.apple.watchdogd") must be
|
|
# preserved for launchd boot-task identity validation.
|
|
echo ""
|
|
echo "[EXP-JB-3.5] Patching watchdogd hv_vmm_present cache..."
|
|
|
|
cp "$MNT1/usr/libexec/watchdogd" "$TEMP_DIR/watchdogd"
|
|
"$SCRIPT_DIR/patch_hv_vmm_userland.sh" watchdogd "$TEMP_DIR/watchdogd"
|
|
cp -R "$TEMP_DIR/watchdogd" "$MNT1/usr/libexec/watchdogd"
|
|
/bin/chmod 0755 $MNT1/usr/libexec/watchdogd
|
|
|
|
echo " [+] watchdogd patched"
|
|
|
|
|
|
# ═══════════ JB-4 INSTALL PROCURSUS BOOTSTRAP ══════════════════
|
|
echo ""
|
|
echo "[JB-4] Installing procursus bootstrap..."
|
|
|
|
mount_vol s5 "$MNT5"
|
|
BOOT_HASH="$(get_boot_manifest_hash)"
|
|
[[ -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"
|
|
SILEO_DEB="$JB_INPUT_DIR/jb/org.coolstar.sileo_2.5.1_iphoneos-arm64.deb"
|
|
[[ -f "$BOOTSTRAP_ZST" ]] || die "Missing $BOOTSTRAP_ZST"
|
|
|
|
BOOTSTRAP_TAR="$TEMP_DIR/bootstrap-iphoneos-arm64.tar"
|
|
zstd -d -f "$BOOTSTRAP_ZST" -o "$BOOTSTRAP_TAR"
|
|
|
|
cp -R "$BOOTSTRAP_TAR" "$MNT5/$BOOT_HASH/bootstrap-iphoneos-arm64.tar"
|
|
if [[ -f "$SILEO_DEB" ]]; then
|
|
cp -R "$SILEO_DEB" "$MNT5/$BOOT_HASH/org.coolstar.sileo_2.5.1_iphoneos-arm64.deb"
|
|
fi
|
|
|
|
# ── Extra debs: download from manifest, then stage the whole cache ──────
|
|
echo " Fetching extra debs..."
|
|
zsh "$SCRIPT_DIR/fetch_debs.sh" || true
|
|
DEBS_CACHE="${VPHONE_DEBS_DIR:-${SCRIPT_DIR:h}/debs}"
|
|
DEBS_DEST="$MNT5/$BOOT_HASH/debs"
|
|
/bin/rm -rf "$DEBS_DEST"
|
|
deb_count=0
|
|
for deb in "$DEBS_CACHE"/*.deb(N); do
|
|
(( deb_count == 0 )) && /bin/mkdir -p "$DEBS_DEST"
|
|
cp -R "$deb" "$DEBS_DEST/"
|
|
deb_count=$((deb_count + 1))
|
|
done
|
|
if (( deb_count > 0 )); then
|
|
echo " [+] Staged $deb_count extra deb(s) for first-boot install"
|
|
else
|
|
echo " [=] No extra debs to stage"
|
|
fi
|
|
|
|
JB_DIR_NAME="jb-vphone"
|
|
/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 at install time).
|
|
|
|
echo " [+] procursus bootstrap installed"
|
|
|
|
# ═══════════ JB-4 DEPLOY BASEBIN HOOKS ═════════════════════════
|
|
BASEBIN_DIR="$JB_INPUT_DIR/basebin"
|
|
|
|
if [[ -d "$BASEBIN_DIR" ]]; then
|
|
echo ""
|
|
echo "[JB-4] Deploying BaseBin hooks to /cores/..."
|
|
|
|
# Clean previous dylibs before re-uploading
|
|
echo " Cleaning old /cores/ dylibs..."
|
|
/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
|
|
[[ -f "$dylib" ]] || continue
|
|
dylib_name="$(basename "$dylib")"
|
|
echo " Installing $dylib_name..."
|
|
ldid_sign "$dylib"
|
|
cp -R "$dylib" "$MNT1/cores/$dylib_name"
|
|
/bin/chmod 0755 $MNT1/cores/$dylib_name
|
|
done
|
|
|
|
# Short alias for launchdhook (header space is tight)
|
|
if [[ -f "$BASEBIN_DIR/launchdhook.dylib" ]]; then
|
|
echo " Installing short launchdhook alias at /b..."
|
|
cp "$BASEBIN_DIR/launchdhook.dylib" "$TEMP_DIR/b"
|
|
ldid_sign "$TEMP_DIR/b"
|
|
/bin/rm -f $MNT1/b
|
|
cp -R "$TEMP_DIR/b" "$MNT1/b"
|
|
/bin/chmod 0755 $MNT1/b
|
|
fi
|
|
|
|
echo " [+] BaseBin hooks deployed"
|
|
fi
|
|
|
|
# ═══════════ JB-4 INSTALL TWEAKLOADER ════════════════════════════
|
|
echo ""
|
|
echo "[JB-4] Building and installing TweakLoader..."
|
|
|
|
TWEAKLOADER_OUT="$(build_tweakloader)"
|
|
/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"
|
|
|
|
# ═══════════ JB-4.1 INSTALL libvcamcaptured ═════════════════════════
|
|
# Inject dylib loaded into /usr/libexec/cameracaptured (via TweakLoader
|
|
# allowlist in TweakLoader.m's kVPhoneAllowedDaemonPaths). Hosts the
|
|
# synthetic FigCaptureSource + shm-frame reader paired with vphoned's
|
|
# vsock 1338 listener on the guest side.
|
|
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"
|
|
/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
|
|
cp -R "$LIBVCAM_PLIST" "$LIBVCAM_DIR/libvcamcaptured.plist"
|
|
/bin/chmod 0644 $LIBVCAM_DIR/libvcamcaptured.plist
|
|
fi
|
|
echo " [+] libvcamcaptured installed to procursus/Library/MobileSubstrate/DynamicLibraries/"
|
|
|
|
# ═══════════ JB-4.2 INSTALL libcamfix ═══════════════════════════════
|
|
# Substrate plugin loaded by TweakLoader into every process that links
|
|
# AVFoundation. The companion plist's Filter.Frameworks = ["AVFoundation"]
|
|
# tells TweakLoader to defer the dlopen until AVFoundation actually
|
|
# appears in the loaded image list (via _dyld_register_func_for_add_image),
|
|
# so processes that don't use AVF don't pay any cost.
|
|
echo ""
|
|
echo "[JB-4.2] Building and installing libcamfix..."
|
|
LIBCAMFIX_OUT="$(build_libcamfix)"
|
|
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
|
|
cp -R "$LIBCAMFIX_PLIST" "$LIBVCAM_DIR/libcamfix.plist"
|
|
/bin/chmod 0644 $LIBVCAM_DIR/libcamfix.plist
|
|
fi
|
|
echo " [+] libcamfix installed to procursus/Library/MobileSubstrate/DynamicLibraries/"
|
|
|
|
# ═══════════ JB-5 DEPLOY FIRST-BOOT SETUP ══════════════════════
|
|
echo ""
|
|
echo "[JB-5] Deploying first-boot setup..."
|
|
|
|
# Deploy first-boot JB setup script + LaunchDaemon
|
|
SETUP_SCRIPT="$SCRIPT_DIR/vphone_jb_setup.sh"
|
|
SETUP_PLIST="$SCRIPT_DIR/vphone_jb_setup.plist"
|
|
if [[ -f "$SETUP_SCRIPT" ]]; then
|
|
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
|
|
# vpregister: registers JB apps via the containerized LS API at first boot (uicache -a's
|
|
# registerApplicationDictionary is a deprecated no-op on iOS 27). Deployed ONLY on a 27
|
|
# base — it needs the 27-only lsd embedded-reg gate, and on 26.x/18.x uicache registers
|
|
# apps normally. Its /cores presence IS the runtime gate in vphone_jb_setup.sh (that
|
|
# script must NOT version-check on guest sw_vers — the hybrid guest does not reliably
|
|
# report the 27 userland version at first boot). Version read from the mounted rootfs
|
|
# SystemVersion.plist, the same source cfw_install.sh gates its 27 patches on.
|
|
JB_BASE_IOS=$(/usr/bin/plutil -extract ProductVersion raw -o - "$MNT1/System/Library/CoreServices/SystemVersion.plist" 2>/dev/null || true)
|
|
case "$JB_BASE_IOS" in
|
|
27.*)
|
|
VPREGISTER="$(build_vpregister)"
|
|
if [[ -f "$VPREGISTER" ]]; then
|
|
cp -R "$VPREGISTER" "$MNT1/cores/vpregister"
|
|
/bin/chmod 0755 $MNT1/cores/vpregister
|
|
echo " [+] vpregister -> /cores/ (iOS $JB_BASE_IOS)"
|
|
fi
|
|
;;
|
|
*)
|
|
echo " [skip] vpregister (base iOS ${JB_BASE_IOS:-unknown} — 27-only; uicache registers apps on older bases)"
|
|
;;
|
|
esac
|
|
if [[ -f "$SETUP_PLIST" ]]; then
|
|
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..."
|
|
cp "$MNT1/System/Library/xpc/launchd.plist" "$TEMP_DIR/launchd.plist"
|
|
"$PYTHON3" -c "
|
|
import plistlib, sys
|
|
with open(sys.argv[1], 'rb') as f:
|
|
target = plistlib.load(f)
|
|
with open(sys.argv[2], 'rb') as f:
|
|
daemon = plistlib.load(f)
|
|
target.setdefault('LaunchDaemons', {})['/System/Library/LaunchDaemons/com.vphone.jb-setup.plist'] = daemon
|
|
with open(sys.argv[1], 'wb') as f:
|
|
plistlib.dump(target, f, sort_keys=False)
|
|
" "$TEMP_DIR/launchd.plist" "$SETUP_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
|
|
|
|
# ═══════════ EXP-JB-6 POST-RESTORE DT IDENTITY REWRITE ════════
|
|
#
|
|
# Apply the three restore-unsafe DT property edits that broke earlier
|
|
# attempts when applied at fw_patch time:
|
|
# root/model iPhone99,11 -> iPhone17,3
|
|
# root/target-type VPHONE600 -> D47
|
|
# root/compatible reordered to [D47AP, VPHONE600AP, AppleVirtualPlatformARM]
|
|
#
|
|
# These are restore-time-fatal (restored_external / iBoot's restore mode
|
|
# cross-checks model+target-type against the BuildManifest's signed
|
|
# identity) but NOT boot-time-fatal — the existing iBSS/iBEC/LLB
|
|
# 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 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).
|
|
echo ""
|
|
echo "[EXP-JB-6] Post-restore DT identity rewrite..."
|
|
|
|
if [[ -z "$BOOT_HASH" ]]; then
|
|
BOOT_HASH="$(get_boot_manifest_hash)"
|
|
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_LOCAL="$TEMP_DIR/devicetree.img4"
|
|
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"
|
|
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"
|
|
fi
|
|
fi
|
|
|
|
# ═══════════ EXP-JB-7 BUILD-VERSION REWRITE (SystemVersion.plist) ══
|
|
#
|
|
# OPT-IN. Only runs when SPOOF_BUILD is set in the environment (e.g.
|
|
# `make setup_machine JB=1 SPOOF_BUILD=23F77` or `make cfw_install_jb
|
|
# SPOOF_BUILD=23F77`). When SPOOF_BUILD is unset/empty the step is
|
|
# skipped entirely and the build identifier stays at whatever the IPSW
|
|
# shipped.
|
|
#
|
|
# What it does (when enabled): flips ProductBuildVersion in the two
|
|
# SystemVersion.plist files iOS reads for "Build" display and
|
|
# MGCopyAnswer("BuildVersion"):
|
|
# /System/Library/CoreServices/SystemVersion.plist (rootfs)
|
|
# /private/preboot/Cryptexes/OS/System/Library/CoreServices/SystemVersion.plist (Cryptex)
|
|
#
|
|
# 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
|
|
#
|
|
# After this, Settings -> About -> Build, MG BuildVersion key, and every
|
|
# framework that reads SystemVersion.plist see the new identifier.
|
|
# `sysctl kern.osversion` still reports the kernel image's own build
|
|
# (e.g. 23B78 from the PCC vphone600/vresearch101 kernel) — that comes
|
|
# from a kernel global populated at boot from boot args, not from this
|
|
# plist.
|
|
if [[ -n "${SPOOF_BUILD:-}" ]]; then
|
|
echo ""
|
|
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"
|
|
do
|
|
if [[ -e "$jb7_remote" ]]; then
|
|
jb7_local="$TEMP_DIR/$(echo "$jb7_remote" | tr '/' '_').plist"
|
|
cp "$jb7_remote" "$jb7_local"
|
|
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw_patch_build_version.py" \
|
|
"$jb7_local" "$SPOOF_BUILD"
|
|
cp -R "$jb7_local" "$jb7_remote"
|
|
else
|
|
echo " [-] $jb7_remote not found, skipping"
|
|
fi
|
|
done
|
|
else
|
|
echo ""
|
|
echo "[EXP-JB-7] Skipped — SPOOF_BUILD not set (pass SPOOF_BUILD=<id> to enable)"
|
|
fi
|
|
|
|
# ═══════════ CLEANUP ═════════════════════════════════════════
|
|
echo ""
|
|
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..."
|
|
rm -rf "$TEMP_DIR"
|
|
|
|
echo ""
|
|
echo "[+] CFW + JB + EXP installation complete!"
|
|
echo " Boot to apply changes."
|
|
echo " After boot, SSH will be available on port 22222 (password: alpine)"
|