#!/bin/zsh
set -euo pipefail
[[ -n "${_VPHONE_PATH:-}" ]] && export PATH="$_VPHONE_PATH"
VM_DIR="${1:-.}"
SCRIPT_DIR="${0:a:h}"
VM_DIR="$(cd "$VM_DIR" && pwd)"
_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)"
CFW_INPUT="cfw_input"
CFW_ARCHIVE="cfw_input.tar.zst"
TEMP_DIR="$VM_DIR/.cfw_temp"
die() {
echo "[-] $*" >&2
exit 1
}
check_prerequisites() {
local missing=()
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
}
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" ent="$2" bundle_id="${3:-}"
local args=("-S$ent" -M "-K$VM_DIR/$CFW_INPUT/signcert.p12")
[[ -n "$bundle_id" ]] && args+=("-I$bundle_id")
ldid "${args[@]}" "$file"
}
host_hdiutil() {
local rc
[[ -n "${SUDO_ASKPASS:-}" ]] && { sudo -A hdiutil "$@"; return; }
hdiutil "$@" && return 0
rc=$?
if sudo -n true 2>/dev/null; then
sudo hdiutil "$@"
return
fi
return "$rc"
}
safe_detach() {
local mnt="$1"
if mount | grep -Fq " on $mnt "; then
host_hdiutil detach -force "$mnt" 2>/dev/null || true
fi
}
assert_mount_under_vm() {
local mnt="$1" label="${2:-mountpoint}"
local abs_vm abs_mnt
abs_vm="$(cd "$VM_DIR" && pwd -P)"
abs_mnt="$(cd "$mnt" && pwd -P)"
case "$abs_mnt/" in
"$abs_vm/"*) ;;
*) die "Unsafe ${label}: ${abs_mnt} (must be inside ${abs_vm})" ;;
esac
}
find_restore_dir() {
for dir in "$VM_DIR"/iPhone*_Restore; do
[[ -f "$dir/BuildManifest.plist" ]] && echo "$dir" && return
done
die "No restore directory found in $VM_DIR"
}
setup_cfw_input() {
[[ -d "$VM_DIR/$CFW_INPUT" ]] && return
local archive
for search_dir in "$SCRIPT_DIR/resources" "$SCRIPT_DIR" "$VM_DIR"; do
archive="$search_dir/$CFW_ARCHIVE"
if [[ -f "$archive" ]]; then
echo " Extracting $CFW_ARCHIVE..."
"$TAR" --zstd --warning=no-unknown-keyword -xf "$archive" -C "$VM_DIR"
return
fi
done
die "Neither $CFW_INPUT/ nor $CFW_ARCHIVE found"
}
check_prereqs() {
command -v ipsw >/dev/null 2>&1 || die "'ipsw' not found. Install: brew install blacktop/tap/ipsw"
command -v aea >/dev/null 2>&1 || die "'aea' not found (requires macOS 12+)"
[[ -x "$PYTHON3" ]] || die "python3 not found (tried: $PYTHON3). Run: make setup_venv"
echo "[*] Python: $PYTHON3 ($("$PYTHON3" --version 2>&1))"
local py_err
py_err="$("$PYTHON3" -c "import capstone, keystone" 2>&1)" || {
die "Missing Python deps (using $PYTHON3).\n Error: ${py_err}\n Fix: source ${SCRIPT_DIR:h}/.venv/bin/activate && pip install capstone keystone-engine\n Or: make setup_venv"
}
}
cleanup_on_exit() {
safe_detach "$TEMP_DIR/mnt_sysos" 2>/dev/null || true
safe_detach "$TEMP_DIR/mnt_appos" 2>/dev/null || true
}
trap cleanup_on_exit EXIT
: "${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"
MNT3="$HOST_MNT/mnt3"
TAR="$(command -v gtar 2>/dev/null || echo /opt/homebrew/bin/gtar)"
mkdir -p "$HOST_MNT"
mount_vol() {
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"
}
echo "[*] cfw_install.sh — Installing CFW on vphone..."
check_prereqs
RESTORE_DIR=$(find_restore_dir)
echo "[+] Restore directory: $RESTORE_DIR"
setup_cfw_input
INPUT_DIR="$VM_DIR/$CFW_INPUT"
echo "[+] Input resources: $INPUT_DIR"
check_prerequisites
mkdir -p "$TEMP_DIR"
echo ""
echo "[*] Parsing iPhone BuildManifest for Cryptex paths..."
CRYPTEX_PATHS=$("$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" cryptex-paths "$RESTORE_DIR/iPhone-BuildManifest.plist")
CRYPTEX_SYSOS=$(echo "$CRYPTEX_PATHS" | head -1)
CRYPTEX_APPOS=$(echo "$CRYPTEX_PATHS" | tail -1)
echo " SystemOS: $CRYPTEX_SYSOS"
echo " AppOS: $CRYPTEX_APPOS"
echo ""
echo "[1/7] Installing Cryptex (SystemOS + AppOS)..."
echo " Mounting rootfs rw..."
mount_vol s1 "$MNT1"
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"
/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
SYSOS_DMG="$TEMP_DIR/CryptexSystemOS.dmg"
APPOS_DMG="$TEMP_DIR/CryptexAppOS.dmg"
MNT_SYSOS="$TEMP_DIR/mnt_sysos"
MNT_APPOS="$TEMP_DIR/mnt_appos"
if [[ ! -f "$SYSOS_DMG" ]]; then
echo " Extracting AEA key..."
AEA_KEY=$(ipsw fw aea --key "$RESTORE_DIR/$CRYPTEX_SYSOS")
echo " key: $AEA_KEY"
echo " Decrypting SystemOS..."
aea decrypt -i "$RESTORE_DIR/$CRYPTEX_SYSOS" -o "$SYSOS_DMG" -key-value "$AEA_KEY"
else
echo " Using cached SystemOS DMG"
fi
if [[ ! -f "$APPOS_DMG" ]]; then
cp "$RESTORE_DIR/$CRYPTEX_APPOS" "$APPOS_DMG"
else
echo " Using cached AppOS DMG"
fi
safe_detach "$MNT_SYSOS"
safe_detach "$MNT_APPOS"
mkdir -p "$MNT_SYSOS" "$MNT_APPOS"
assert_mount_under_vm "$MNT_SYSOS" "SystemOS mountpoint"
assert_mount_under_vm "$MNT_APPOS" "AppOS mountpoint"
echo " Mounting SystemOS..."
host_hdiutil attach -mountpoint "$MNT_SYSOS" "$SYSOS_DMG" -nobrowse -owners off \
|| die "Failed to mount SystemOS DMG. Run 'sudo -v' in a terminal and retry if hdiutil needs administrator privileges."
echo " Mounting AppOS..."
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."
/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
echo " Copying Cryptexes..."
cp -R "$MNT_SYSOS/." "$MNT1/System/Cryptexes/OS"
cp -R "$MNT_APPOS/." "$MNT1/System/Cryptexes/App"
echo " Creating dyld symlinks..."
/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 " Unmounting Cryptex DMGs..."
safe_detach "$MNT_SYSOS"
safe_detach "$MNT_APPOS"
echo " [+] Cryptex installed"
fi
IOS_VERSION=$(/usr/bin/plutil -extract ProductVersion raw -o - "$MNT1/System/Library/CoreServices/SystemVersion.plist" 2>/dev/null || true)
DSC_DIR="$MNT1/System/Cryptexes/OS/System/Library/Caches/com.apple.dyld"
IOMFB_TARGET=""
case "$IOS_VERSION" in
26.0*|18.*) IOMFB_TARGET=0x560 ;;
esac
if [[ -n "$IOMFB_TARGET" ]]; then
echo " [*] Patching IOMobileFramebuffer SwapEnd payload size (iOS $IOS_VERSION -> $IOMFB_TARGET)..."
[[ -d "$DSC_DIR" ]] || die "dyld cache dir missing: $DSC_DIR"
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-iomfb-swapend "$DSC_DIR" --target-size "$IOMFB_TARGET"
fi
case "$IOS_VERSION" in
27.*)
echo " [*] Forcing IOMobileFramebuffer present onto the kern (method-5) path (iOS $IOS_VERSION)..."
[[ -d "$DSC_DIR" ]] || die "dyld cache dir missing: $DSC_DIR"
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-iomfb-force-kern "$DSC_DIR"
;;
esac
DSC_DIR="$MNT1/System/Cryptexes/OS/System/Library/Caches/com.apple.dyld"
FORCE_DSC_MAXSLIDE="${FORCE_DSC_MAXSLIDE:-0}"
case "$IOS_VERSION" in
27.*)
if [[ -d "$DSC_DIR" ]]; then
echo " [*] Checking dyld cache maxSlide vs kernel shared region..."
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-dsc-maxslide "$DSC_DIR"
echo " [*] Patching lsd embedded-registration gate (iOS 27 app registration)..."
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-lsd-embedded-reg "$DSC_DIR"
echo " [*] Patching libxpc LWCR self-check (iOS 27 daemon crash-loop)..."
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-xpc-lwcr "$DSC_DIR"
echo " [*] Patching os_lockdown_mode_enabled (missing MAC sysctl -> launchd abort)..."
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-lockdown-mode "$DSC_DIR"
fi
;;
*)
if [[ "$FORCE_DSC_MAXSLIDE" == "1" && -d "$DSC_DIR" ]]; then
echo " [*] Forcing dyld cache maxSlide=0 (opt-in FORCE_DSC_MAXSLIDE=1; base iOS ${IOS_VERSION:-unknown})..."
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-dsc-maxslide "$DSC_DIR" --force
fi
;;
esac
echo ""
echo "[2/7] Patching seputil..."
if ! [[ -e "$MNT1/usr/libexec/seputil.bak" ]]; then
echo " Creating backup..."
/bin/cp $MNT1/usr/libexec/seputil $MNT1/usr/libexec/seputil.bak
fi
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"
cp -R "$TEMP_DIR/seputil" "$MNT1/usr/libexec/seputil"
/bin/chmod 0755 $MNT1/usr/libexec/seputil
case "$IOS_VERSION" in
27.*)
echo " Patching diskimagesiod (DDI auto-mount, iOS $IOS_VERSION)..."
if ! [[ -e "$MNT1/usr/libexec/diskimagesiod.bak" ]]; then
/bin/cp "$MNT1/usr/libexec/diskimagesiod" "$MNT1/usr/libexec/diskimagesiod.bak"
fi
ldid -e "$MNT1/usr/libexec/diskimagesiod.bak" > "$TEMP_DIR/diskimagesiod.ent.plist"
cp "$MNT1/usr/libexec/diskimagesiod.bak" "$TEMP_DIR/diskimagesiod"
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-diskimagesiod "$TEMP_DIR/diskimagesiod"
ldid_sign_ent "$TEMP_DIR/diskimagesiod" "$TEMP_DIR/diskimagesiod.ent.plist" "com.apple.diskimagesiod"
cp -R "$TEMP_DIR/diskimagesiod" "$MNT1/usr/libexec/diskimagesiod"
/bin/chmod 0755 "$MNT1/usr/libexec/diskimagesiod"
;;
esac
echo " Renaming gigalocker..."
mount_vol s3 "$MNT3"
mv "$MNT3"/*.gl(N) "$MNT3/AA.gl" 2>/dev/null || true
echo " [+] seputil patched"
echo ""
echo "[3/7] Installing AppleParavirtGPUMetalIOGPUFamily..."
cp -R "$INPUT_DIR/custom/AppleParavirtGPUMetalIOGPUFamily.tar" "$MNT1"
"$TAR" --preserve-permissions --no-overwrite-dir --warning=no-unknown-keyword \
-xf $MNT1/AppleParavirtGPUMetalIOGPUFamily.tar -C $MNT1
BUNDLE="$MNT1/System/Library/Extensions/AppleParavirtGPUMetalIOGPUFamily.bundle"
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"
echo ""
echo "[4/7] Installing iosbinpack64..."
cp -R "$INPUT_DIR/jb/iosbinpack64.tar" "$MNT1"
"$TAR" --preserve-permissions --no-overwrite-dir --warning=no-unknown-keyword \
-xf $MNT1/iosbinpack64.tar -C $MNT1
/bin/rm -f $MNT1/iosbinpack64.tar
/bin/mkdir -p $MNT3/dropbear
echo " [+] iosbinpack64 installed"
echo ""
echo "[5/7] Patching launchd_cache_loader..."
if ! [[ -e "$MNT1/usr/libexec/launchd_cache_loader.bak" ]]; then
echo " Creating backup..."
/bin/cp $MNT1/usr/libexec/launchd_cache_loader $MNT1/usr/libexec/launchd_cache_loader.bak
fi
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"
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"
echo ""
echo "[6/7] Patching mobileactivationd..."
if ! [[ -e "$MNT1/usr/libexec/mobileactivationd.bak" ]]; then
echo " Creating backup..."
/bin/cp $MNT1/usr/libexec/mobileactivationd $MNT1/usr/libexec/mobileactivationd.bak
fi
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"
cp -R "$TEMP_DIR/mobileactivationd" "$MNT1/usr/libexec/mobileactivationd"
/bin/chmod 0755 $MNT1/usr/libexec/mobileactivationd
echo " [+] mobileactivationd patched"
echo ""
echo "[7/7] Installing LaunchDaemons..."
VPHONED_SRC="$SCRIPT_DIR/vphoned"
VPHONED_BIN="$VPHONED_SRC/vphoned"
VPHONED_SRCS=("$VPHONED_SRC"/*.m)
needs_vphoned_build=0
if [[ ! -f "$VPHONED_BIN" ]]; then
needs_vphoned_build=1
else
for src in "${VPHONED_SRCS[@]}"; do
if [[ "$src" -nt "$VPHONED_BIN" ]]; then
needs_vphoned_build=1
break
fi
done
fi
if [[ "$needs_vphoned_build" == "1" ]]; then
echo " Building vphoned for arm64..."
xcrun -sdk iphoneos clang -arch arm64 -Os -fobjc-arc \
-I"$VPHONED_SRC" \
-I"$VPHONED_SRC/vendor/libarchive" \
-o "$VPHONED_BIN" "${VPHONED_SRCS[@]}" \
-larchive \
-lsqlite3 \
-framework Foundation \
-framework Security \
-framework CoreServices
fi
cp "$VPHONED_BIN" "$TEMP_DIR/vphoned"
ldid \
-S"$VPHONED_SRC/entitlements.plist" \
-M "-K$VM_DIR/$CFW_INPUT/signcert.p12" \
"$TEMP_DIR/vphoned"
cp -R "$TEMP_DIR/vphoned" "$MNT1/usr/bin/vphoned"
/bin/chmod 0755 $MNT1/usr/bin/vphoned
cp "$TEMP_DIR/vphoned" "$VM_DIR/.vphoned.signed"
echo " [+] vphoned installed (signed copy at .vphoned.signed)"
for plist in bash.plist dropbear.plist trollvnc.plist rpcserver_ios.plist; do
plist_src="$INPUT_DIR/jb/LaunchDaemons/$plist"
if [[ "$plist" == "dropbear.plist" ]]; then
plist_src="$TEMP_DIR/dropbear.plist"
cp "$INPUT_DIR/jb/LaunchDaemons/dropbear.plist" "$plist_src"
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-dropbear-plist "$plist_src"
fi
cp -R "$plist_src" "$MNT1/System/Library/LaunchDaemons/"
/bin/chmod 0644 $MNT1/System/Library/LaunchDaemons/$plist
done
cp -R "$VPHONED_SRC/vphoned.plist" "$MNT1/System/Library/LaunchDaemons/"
/bin/chmod 0644 $MNT1/System/Library/LaunchDaemons/vphoned.plist
echo " Patching launchd.plist..."
if ! [[ -e "$MNT1/System/Library/xpc/launchd.plist.bak" ]]; then
echo " Creating backup..."
/bin/cp $MNT1/System/Library/xpc/launchd.plist $MNT1/System/Library/xpc/launchd.plist.bak
fi
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"
cp -R "$TEMP_DIR/launchd.plist" "$MNT1/System/Library/xpc/launchd.plist"
/bin/chmod 0644 $MNT1/System/Library/xpc/launchd.plist
echo " [+] LaunchDaemons installed"
echo ""
echo "[*] Unmounting image volumes..."
/sbin/umount $MNT1 2>/dev/null || true
/sbin/umount $MNT3 2>/dev/null || true
echo "[*] Cleaning up temp..."
rm -rf "$TEMP_DIR"
echo ""
echo "[+] CFW installation complete!"
echo " Boot to apply changes."
echo " After boot, SSH will be available on port 22222 (password: alpine)"