Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
Expand Down
28 changes: 24 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ help:
@echo " make amfidont_allow_vphone Start amfidont for the signed vphone-cli binary"
@echo " make boot_host_preflight Diagnose whether host can launch signed PV=3 binary"
@echo " make boot Boot VM (reads from config.plist)"
@echo " make boot_less Boot VM in vphoned patchless compatibility"
@echo " make boot_dfu Boot VM in DFU mode (reads from config.plist)"
@echo ""
@echo "Firmware pipeline:"
Expand All @@ -86,6 +87,8 @@ help:
@echo " IPHONE_SOURCE= URL or local path to iPhone IPSW"
@echo " CLOUDOS_SOURCE= URL or local path to cloudOS IPSW"
@echo " make fw_patch Patch boot chain with Swift pipeline (regular variant)"
@echo " make fw_patch_less Patch boot chain with Swift pipeline (less patches)"
@echo " DEBUG=1 Add debug tooling (SRD branding, developer mode, debugserver, SSH)"
@echo " make fw_patch_dev Patch boot chain with Swift pipeline (dev mode TXM patches)"
@echo " make fw_patch_jb Patch boot chain with Swift pipeline (dev + JB extensions)"
@echo ""
Expand All @@ -111,15 +114,21 @@ help:
.PHONY: setup_machine setup_tools

setup_machine:
@if [ "$(filter 1 true yes YES TRUE,$(JB))" != "" ] && [ "$(filter 1 true yes YES TRUE,$(DEV))" != "" ]; then \
echo "Error: JB=1 and DEV=1 are mutually exclusive"; \
@if count=0; \
[ -n "$(filter 1 true yes YES TRUE,$(JB))" ] && count=$$((count+1)); \
[ -n "$(filter 1 true yes YES TRUE,$(DEV))" ] && count=$$((count+1)); \
[ -n "$(filter 1 true yes YES TRUE,$(LESS))" ] && count=$$((count+1)); \
[ $$count -gt 1 ]; then \
echo "Error: JB=1, DEV=1, and LESS=1 are mutually exclusive"; \
exit 1; \
fi
SUDO_PASSWORD="$(SUDO_PASSWORD)" \
NONE_INTERACTIVE="$(NONE_INTERACTIVE)" \
zsh $(SCRIPTS)/setup_machine.sh \
$(if $(filter 1 true yes YES TRUE,$(JB)),--jb,) \
$(if $(filter 1 true yes YES TRUE,$(DEV)),--dev,) \
$(if $(filter 1 true yes YES TRUE,$(LESS)),--less,) \
$(if $(filter 1 true yes YES TRUE,$(DEBUG)),--debug,) \
$(if $(filter 1 true yes YES TRUE,$(SKIP_PROJECT_SETUP)),--skip-project-setup,)

setup_tools:
Expand Down Expand Up @@ -193,7 +202,7 @@ vphoned:
# VM management
# ═══════════════════════════════════════════════════════════════════

.PHONY: vm_new vm_backup vm_restore vm_switch vm_list amfidont_allow_vphone boot_host_preflight boot boot_dfu boot_binary_check
.PHONY: vm_new vm_backup vm_restore vm_switch vm_list amfidont_allow_vphone boot_host_preflight boot boot_less boot_dfu boot_binary_check

vm_new:
CPU="$(CPU)" MEMORY="$(MEMORY)" \
Expand Down Expand Up @@ -262,6 +271,10 @@ boot: bundle vphoned boot_binary_check
cd $(VM_DIR) && "$(CURDIR)/$(BUNDLE_BIN)" \
--config ./config.plist

boot_less: bundle vphoned boot_binary_check
cd $(VM_DIR) && "$(CURDIR)/$(BUNDLE_BIN)" \
--config ./config.plist --variant less

boot_dfu: build boot_binary_check
cd $(VM_DIR) && "$(CURDIR)/$(BINARY)" \
--config ./config.plist \
Expand All @@ -271,14 +284,21 @@ boot_dfu: build boot_binary_check
# Firmware pipeline
# ═══════════════════════════════════════════════════════════════════

.PHONY: fw_prepare fw_patch fw_patch_dev fw_patch_jb
.PHONY: fw_prepare fw_patch fw_patch_less fw_patch_dev fw_patch_jb

fw_prepare:
cd $(VM_DIR) && bash "$(CURDIR)/$(SCRIPTS)/fw_prepare.sh"

fw_patch: patcher_build
"$(CURDIR)/$(PATCHER_BINARY)" patch-firmware --vm-directory "$(CURDIR)/$(VM_DIR)" --variant regular

fw_patch_less: patcher_build
@sh -c 'if [ "$$(id -u)" -ne 0 ]; then \
echo "fw_patch_less must be run via sudo" >&2; \
exit 1; \
fi; \
"$(CURDIR)/$(PATCHER_BINARY)" patch-firmware --vm-directory "$(CURDIR)/$(VM_DIR)" --variant less $(if $(filter 1 true yes YES TRUE,$(DEBUG)),--debug,)'

fw_patch_dev: patcher_build
"$(CURDIR)/$(PATCHER_BINARY)" patch-firmware --vm-directory "$(CURDIR)/$(VM_DIR)" --variant dev

Expand Down
22 changes: 14 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ Boot a virtual iPhone (iOS 26) via Apple's Virtualization.framework using PCC re

## Firmware Variants

Three patch variants are available with increasing levels of security bypass:
Four patch variants are available with increasing levels of security bypass:

| Variant | Boot Chain | CFW | Make Targets |
| --------------- | :---------: | :-------: | ---------------------------------- |
| **Regular** | 41 patches | 10 phases | `fw_patch` + `cfw_install` |
| **Development** | 52 patches | 12 phases | `fw_patch_dev` + `cfw_install_dev` |
| **Jailbreak** | 112 patches | 14 phases | `fw_patch_jb` + `cfw_install_jb` |
| Variant | Boot Chain | CFW | Make Targets |
| --------------- | :---------: | :-------: | ----------------------------------- |
| **Patchless** | 3 patches | 2 phases | `fw_patch_less` (root) + `boot_less`|
| **Regular** | 41 patches | 10 phases | `fw_patch` + `cfw_install` |
| **Development** | 52 patches | 12 phases | `fw_patch_dev` + `cfw_install_dev` |
| **Jailbreak** | 112 patches | 14 phases | `fw_patch_jb` + `cfw_install_jb` |

> JB finalization (symlinks, Sileo, apt, TrollStore) runs automatically on first boot via `/cores/vphone_jb_setup.sh` LaunchDaemon. Monitor progress: `/var/log/vphone_jb_setup.log`.

Expand All @@ -35,6 +36,9 @@ See [research/0_binary_patch_comparison.md](./research/0_binary_patch_comparison

**Configure SIP/AMFI** — required for private Virtualization.framework entitlements and unsigned binary workflows.

The Patchless variant depends on an "untrusted" arm64e binary. You have to allow this by setting the host boot args "-arm64e_preview_abi" (and "amfi_get_out_of_my_way=1").


Boot into Recovery (long press power button), open Terminal, then choose one setup path:

- **Option 1: Fully disable SIP + AMFI boot-arg (most permissive)**
Expand Down Expand Up @@ -87,7 +91,7 @@ Boot into Recovery (long press power button), open Terminal, then choose one set
**Install dependencies:**

```bash
brew install aria2 ideviceinstaller wget gnu-tar openssl@3 ldid-procursus sshpass keystone autoconf automake pkg-config libtool cmake
brew install aria2 ideviceinstaller wget gnu-tar openssl@3 ldid-procursus sshpass keystone autoconf automake pkg-config libtool cmake ipsw dropbear
```

`scripts/fw_prepare.sh` prefers `aria2c` for faster multi-connection downloads and falls back to `curl` or `wget` when needed.
Expand All @@ -102,7 +106,8 @@ git clone --recurse-submodules https://github.com/Lakr233/vphone-cli.git

```bash
make setup_machine # full automation through "First Boot" (includes restore/ramdisk/CFW)
# options: NONE_INTERACTIVE=1 SUDO_PASSWORD=...
# options: NONE_INTERACTIVE=1 SUDO_PASSWORD=...
# LESS=1 for patchless variant (- AMFI, SSV, Img4, TXM bypasses)
# DEV=1 for dev variant (+ TXM entitlement/debug bypasses)
# JB=1 for jailbreak variant (+ full security bypass)
```
Expand All @@ -116,6 +121,7 @@ make vm_new # create VM directory with manifest (config.plist)
# options: CPU=8 MEMORY=8192 DISK_SIZE=64
make fw_prepare # download IPSWs, extract, merge, generate manifest
make fw_patch # patch boot chain (regular variant)
# or: sudo make fw_patch_less # patchless variant (- AMFI, SSV, Img4, TXM bypasses)
# or: make fw_patch_dev # dev variant (+ TXM entitlement/debug bypasses)
# or: make fw_patch_jb # jailbreak variant (+ full security bypass)
```
Expand Down
10 changes: 7 additions & 3 deletions scripts/boot_host_preflight.sh
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,17 @@ RESEARCH_GUEST_STATUS="$(
# Single install or already got a direct answer
echo "$_out"
else
# Multiple installs: try to auto-select "Macintosh HD"
_num=$(echo "$_out" | awk '/Macintosh HD/ { match($0, /[0-9]+/); if (RSTART > 0) { print substr($0, RSTART, RLENGTH); exit } }')
# Multiple installs: try to auto-select the currently booted volume.
_boot_vol=$(diskutil info / 2>/dev/null | awk -F':[[:space:]]+' '/Volume Name/ {print $2; exit}' || true)
_num=""
if [[ -n "$_boot_vol" ]]; then
_num=$(echo "$_out" | awk -v vol="$_boot_vol" 'index($0, vol) { match($0, /[0-9]+/); if (RSTART > 0) { print substr($0, RSTART, RLENGTH); exit } }')
fi
if [[ -n "$_num" ]]; then
_result=$(printf '%s\n' "$_num" | csrutil allow-research-guests status 2>/dev/null \
| grep -o 'Allow Research Guests status:.*' || true)
if [[ -n "$_result" ]]; then
echo "(auto-selected: Macintosh HD) $_result"
echo "(auto-selected: ${_boot_vol}) $_result"
exit 0
fi
fi
Expand Down
125 changes: 84 additions & 41 deletions scripts/setup_machine.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ NONE_INTERACTIVE_RAW="${NONE_INTERACTIVE:-0}"
NONE_INTERACTIVE=0
JB_MODE=0
DEV_MODE=0
LESS_MODE=0
DEBUG_MODE=0
SKIP_PROJECT_SETUP=0

die() {
Expand Down Expand Up @@ -723,6 +725,19 @@ run_make() {
make "$@"
}

run_make_sudo() {
local label="$1"
shift

echo ""
echo "=== ${label} ==="
if [[ -n "${SUDO_PASSWORD:-}" ]]; then
sudo -A -- make "$@"
else
sudo -- make "$@"
fi
}

start_boot_dfu() {
mkdir -p "$LOG_DIR"

Expand Down Expand Up @@ -932,16 +947,24 @@ parse_args() {
--dev)
DEV_MODE=1
;;
--less)
LESS_MODE=1
;;
--debug)
DEBUG_MODE=1
;;
--skip-project-setup)
SKIP_PROJECT_SETUP=1
;;
-h|--help)
cat <<'EOF'
Usage: setup_machine.sh [--jb] [--dev] [--skip-project-setup]
Usage: setup_machine.sh [--jb] [--dev] [--less] [--debug] [--skip-project-setup]

Options:
--jb Use jailbreak firmware patching + jailbreak CFW install.
--dev Use dev firmware patching + dev CFW install.
--less Use patchless firmware patching + CFW install.
--debug Add debug tooling to less variant (SRD, devmode, debugserver, SSH).
--skip-project-setup Skip setup_tools/build stage.

Environment:
Expand All @@ -968,8 +991,8 @@ main() {
local cfw_install_target="cfw_install"
local mode_label="base"

if [[ "$JB_MODE" -eq 1 && "$DEV_MODE" -eq 1 ]]; then
die "--jb and --dev are mutually exclusive"
if (( JB_MODE + DEV_MODE + LESS_MODE > 1 )); then
die "--jb, --dev, and --less are mutually exclusive"
fi

if [[ "$JB_MODE" -eq 1 ]]; then
Expand All @@ -980,6 +1003,11 @@ main() {
fw_patch_target="fw_patch_dev"
cfw_install_target="cfw_install_dev"
mode_label="dev"
elif [[ "$LESS_MODE" -eq 1 ]]; then
fw_patch_target="fw_patch_less"
cfw_install_target=""
mode_label="less"
[[ "$DEBUG_MODE" -eq 1 ]] && mode_label="less+debug"
fi

echo "[*] setup_machine mode: ${mode_label}, project_setup=$([[ "$SKIP_PROJECT_SETUP" -eq 1 ]] && echo "skip" || echo "run"), non_interactive=${NONE_INTERACTIVE}"
Expand All @@ -1003,7 +1031,15 @@ main() {

run_make "Firmware prep" vm_new
run_make "Firmware prep" fw_prepare
run_make "Firmware patch" "$fw_patch_target"
if [[ "$LESS_MODE" -eq 0 ]]; then
run_make "Firmware patch" "$fw_patch_target"
else
if [[ "$DEBUG_MODE" -eq 1 ]]; then
run_make_sudo "Firmware patch" "$fw_patch_target" "DEBUG=1"
else
run_make_sudo "Firmware patch" "$fw_patch_target"
fi
fi

echo ""
echo "=== Restore phase ==="
Expand All @@ -1014,50 +1050,53 @@ main() {
run_make "Restore" restore RESTORE_UDID="$DEVICE_UDID" RESTORE_ECID="0x$DEVICE_ECID"
wait_for_post_restore_reboot
stop_boot_dfu
echo "[*] Waiting ${POST_KILL_SETTLE_DELAY}s for cleanup before ramdisk stage..."
sleep "$POST_KILL_SETTLE_DELAY"

echo ""
echo "=== Ramdisk + CFW phase ==="
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
if [[ "$LESS_MODE" -eq 0 ]]; then
echo "[*] Waiting ${POST_KILL_SETTLE_DELAY}s for cleanup before ramdisk stage..."
sleep "$POST_KILL_SETTLE_DELAY"

echo ""
echo "=== Ramdisk + CFW phase ==="
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
wait_for_ramdisk_ssh

run_make "CFW install" "$cfw_install_target" SSH_PORT="$RAMDISK_SSH_PORT"
stop_boot_dfu
stop_iproxy
run_make "CFW install" "$cfw_install_target" SSH_PORT="$RAMDISK_SSH_PORT"
stop_boot_dfu
stop_iproxy

echo ""
echo "=== First boot ==="
if [[ "$NONE_INTERACTIVE" -eq 0 ]]; then
read -r "?[*] press Enter to start VM, after the VM has finished booting, press Enter again to finish last stage"
else
echo "[*] NONE_INTERACTIVE=1: auto-starting first boot"
fi
echo ""
echo "=== First boot ==="
if [[ "$NONE_INTERACTIVE" -eq 0 ]]; then
read -r "?[*] press Enter to start VM, after the VM has finished booting, press Enter again to finish last stage"
else
echo "[*] NONE_INTERACTIVE=1: auto-starting first boot"
fi

start_first_boot
start_first_boot

if [[ "$NONE_INTERACTIVE" -eq 0 ]]; then
read -r "?[*] Press Enter once the VM is fully booted"
else
wait_for_first_boot_prompt_auto
fi
send_first_boot_commands
if [[ "$NONE_INTERACTIVE" -eq 0 ]]; then
read -r "?[*] Press Enter once the VM is fully booted"
else
wait_for_first_boot_prompt_auto
fi
send_first_boot_commands

echo "[*] Commands sent. Waiting for VM shutdown..."
wait "$BOOT_PID"
BOOT_PID=""
echo "[*] Commands sent. Waiting for VM shutdown..."
wait "$BOOT_PID"
BOOT_PID=""

exec {BOOT_FIFO_FD}>&- || true
BOOT_FIFO_FD=""
rm -f "$BOOT_FIFO" || true
BOOT_FIFO=""
exec {BOOT_FIFO_FD}>&- || true
BOOT_FIFO_FD=""
rm -f "$BOOT_FIFO" || true
BOOT_FIFO=""
fi

if [[ "$JB_MODE" -eq 1 ]]; then
echo ""
Expand All @@ -1072,7 +1111,11 @@ main() {
echo "Setup completed."

echo "=== Boot analysis ==="
run_boot_analysis
if [[ "$LESS_MODE" -eq 0 ]]; then
run_boot_analysis
else
run_make "Start VM" boot_less
fi
}

main "$@"
2 changes: 1 addition & 1 deletion scripts/setup_tools.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ ensure_repo_submodule() {

echo "[1/5] Checking brew packages..."

BREW_PACKAGES=(aria2 gnu-tar openssl@3 ldid-procursus sshpass)
BREW_PACKAGES=(aria2 gnu-tar openssl@3 ldid-procursus sshpass dropbear)
BREW_MISSING=()

for pkg in "${BREW_PACKAGES[@]}"; do
Expand Down
6 changes: 4 additions & 2 deletions scripts/vphoned/vphoned.m
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,9 @@ int main(int argc, char *argv[]) {
// Bootstrap: if running from install path and a cached update exists, exec
// it
const char *selfPath = self_executable_path();
NSLog(@"vphoned: starting (pid=%d, path=%s)", getpid(), selfPath ?: "?");

#if !LESS
if (selfPath && strcmp(selfPath, INSTALL_PATH) == 0 &&
access(CACHE_PATH, X_OK) == 0) {
NSLog(@"vphoned: found cached binary at %s, exec'ing", CACHE_PATH);
Expand All @@ -421,8 +424,7 @@ int main(int argc, char *argv[]) {
strerror(errno));
unlink(CACHE_PATH);
}

NSLog(@"vphoned: starting (pid=%d, path=%s)", getpid(), selfPath ?: "?");
#endif

if (!vp_hid_load())
return 1;
Expand Down
Loading