CFW Kernel Patches and CFW Installation Phases for Different iOS Variants in vphone-cli

CFW kernel patches modify the iOS kernel binary during the firmware build process, while CFW installation phases copy binaries into the VM bundle after restore; both are gated by the variant flag (regular, dev, jb, exp, or less), which determines which Swift patchers run and which shell scripts execute.

The open-source tool vphone-cli constructs Custom Firmware (CFW) for virtual iPhones by orchestrating kernel patchers written in Swift and installation scripts written in Bash. Understanding the distinction between these two layers—and how they vary across iOS firmware variants—is critical for developers debugging boot failures or extending the toolchain with new patches.

Architecture: Kernel Patches vs Installation Phases

The vphone-cli pipeline separates concerns into two distinct layers that both respect the user-selected variant.

Kernel patches are Swift classes that mutate the kernel binary before the VM boots. According to the Lakr233/vphone-cli source code, these live under sources/FirmwarePatcher/Kernel/ and include base patches, jailbreak extensions, and experimental modifications.

Installation phases are shell scripts that run after the restore step completes. These scripts, located in scripts/, mount the VM’s Disk.img, copy patched binaries, flip APFS snapshots, and apply variant-specific file sets. The orchestration logic resides in sources/vphone-cli/VPhoneCreateOrchestrator.swift, which constructs the ordered component list based on the --variant argument.

Kernel Patch Differences by Variant

The FirmwarePipeline.swift file dispatches patchers conditionally. The following breakdown shows which kernel classes execute for each iOS variant.

Regular Variant

KernelPatcher – Applies base patches including APFS snapshot modifications, launch-constraint relaxations, dyld-policy adjustments, and sandbox hooks. This is the minimal set required to boot a clean iOS image in the virtual environment. Source: sources/FirmwarePatcher/Kernel/KernelPatcher.swift.

Dev Variant

KernelPatcher with applyExcGuard = true – Executes the same base patches as regular, but additionally disables EXC-GUARD (Mach-port guard) checks. This enables developer-mode debugging by relaxing certain security validations. The flag is passed during the KernelPatcher initialization in the pipeline.

JB (Jailbreak) Variant

KernelPatcher → KernelJBPatcher – Chains the base patcher with KernelJBPatcher.swift, which adds jailbreak-specific extensions such as KernelJBPatchVmMapDelete and KernelJBPatchThreadSetState. This layer rewrites sandbox hooks and patches iOS-specific kexts to remove restrictions. The pipeline adds KernelJBPatcher only when the variant is jb or exp.

EXP (Experimental) Variant

KernelPatcher → KernelJBPatcher → KernelEXPPatcher – Applies all JB patches, then invokes KernelEXPPatcher.swift for research-only changes. These include KernelEXPPatchHvVmmRename (renaming hv_vmm), extra Dynamic Shared Cache (DSC) tweaks, and device-tree modifications. This variant is intended for experimental hardware virtualization research.

Less (Patch-Less) Variant

No kernel patcher invoked – The pipeline skips all Swift patchers when the variant is less. This produces an unmodified kernel used solely for testing raw restore flows without any binary alteration.

The dispatch logic in FirmwarePipeline.swift implements these conditionals:

// sources/FirmwarePatcher/Pipeline/FirmwarePipeline.swift
if variant != .less {
    // add KernelPatcher
}
if variant == .jb || variant == .exp {
    // add KernelJBPatcher
}
if variant == .exp {
    // add KernelEXPPatcher
}

CFW Installation Phase Differences by Variant

After kernel patching, the installation pipeline runs shell scripts that vary by variant. The base installer handles phases 1-7, while extended variants append additional phases.

Base Installation (Phases 1-7)

Script: scripts/cfw_install.sh

Executes for all variants (regular, dev, jb, exp, less). This script extracts the cfw_input/ tarball, cryptographically signs binaries, and copies them into the staging area. It represents the common foundation upon which variant-specific phases are layered.

Jailbreak Phase (Phase 8)

Script: scripts/cfw_install_jb.sh

Executes only for jb and exp variants. This phase applies the jb/ directory contents to the disk image, installs additional kexts, and runs ldid with jailbreak entitlements. It bridges the gap between kernel-level patches and user-space jailbreak tools.

Experimental Phases (Phases 9-13)

Script: scripts/cfw_install_exp.sh

Executes only for exp. These steps apply the hv_vmm DSC patch, additional camera DSC patches, watchdogd modifications, device-tree (DT) alterations, and build-version opt-ins. These changes are experimental and may destabilize the VM if used outside research contexts.

Host-Mount Finalization

Script: scripts/cfw_install_host.sh

Executes for all variants except where explicitly skipped. After the VM powers off, this script mounts the VM’s Disk.img on the host, copies the fully patched binaries into place, and flips the APFS snapshot offline. It is the final synchronization step before the VM boots with CFW.

Patch-Less Flow Adjustments

For the less variant, the orchestrator sets CFW_SKIP_HALT=1 to prevent the final halt behavior, and skips the JB/EXP-specific installer scripts entirely. Only the raw restore image is used, with no host-mount file injection beyond the base copy.

The variant-aware orchestration in VPhoneCreateOrchestrator.swift controls script selection:

// sources/vphone-cli/VPhoneCreateOrchestrator.swift
if variantOption == .jb || variantOption == .exp {
    // Run base CFW install then JB/EXP phases
}
if isLess {
    args += ["--variant", "less"]
}

Complete Execution Flows by Variant

The following tables summarize which kernel patchers and installation scripts execute for each variant.

Regular:

Dev:

JB:

EXP:

Less:

Practical Code Examples

Selecting a Variant via CLI


# Regular CFW with base patches only

make boot --variant regular

# Dev variant with EXC-GUARD patches

make boot --variant dev

# Jailbreak variant with kernel hooks and JB installer phases

make boot --variant jb

# Experimental variant with full patch chain

make boot --variant exp

# Patch-less variant for raw restore testing

make boot --variant less

These commands invoke VPhoneCreateOrchestrator, which selects the appropriate FirmwarePipeline components and installer scripts.

Adding a Custom Experimental Patch

To extend the EXP variant with a new kernel patch, create a struct conforming to KernelPatch in sources/FirmwarePatcher/Kernel/:

public struct KernelEXPPatchCustomSandbox: KernelPatch {
    public static let name = "CustomSandboxBypass"
    
    public func apply(to buffer: inout PatchBuffer) -> Bool {
        // Locate specific instruction pattern
        guard let hit = buffer.find(mnemonic: "mov", operandContains: "0xdead") else {
            return false
        }
        // Replace with NOPs
        buffer.replace(at: hit.offset, with: .nop)
        return true
    }
}

Register the patch in KernelEXPPatcher.swift and ensure FirmwarePipeline.swift includes it when variant == .exp.

Manual Host-Mount Installation

For debugging or manual CFW application after VM shutdown:

scripts/cfw_install_host.sh --variant exp /path/to/VM.bundle

This mounts Disk.img, synchronizes patched binaries, and prepares the APFS snapshot for the next boot.

Summary

  • Kernel patches are Swift-based binary modifications applied before boot; installation phases are Bash scripts that stage files after restore.
  • The regular variant uses only KernelPatcher and base installer scripts, while dev adds EXC-GUARD patches without extra installation steps.
  • jb chains KernelJBPatcher and runs cfw_install_jb.sh for jailbreak-specific file injection.
  • exp adds KernelEXPPatcher and runs cfw_install_exp.sh for experimental hardware virtualization research.
  • less skips all kernel patchers and extra installer phases, using only the raw restore flow.
  • Variant selection logic is centralized in FirmwarePipeline.swift (kernel dispatch) and VPhoneCreateOrchestrator.swift (script orchestration).

Frequently Asked Questions

What distinguishes the JB variant from the EXP variant?

The JB variant applies jailbreak kernel patches and installer phases sufficient for standard jailbreak functionality. The EXP variant includes all JB patches plus KernelEXPPatcher (adding hv_vmm renames and experimental DSC tweaks) and runs additional installer phases 9-13 from cfw_install_exp.sh for research purposes.

How does the 'less' variant affect the installation pipeline?

The less variant skips all Swift kernel patchers entirely and sets CFW_SKIP_HALT=1 to bypass the final halt behavior. It executes only the base cfw_install.sh and cfw_install_host.sh without the JB or EXP-specific file injection phases, making it suitable for testing raw restore images.

Can I run installation phases without applying kernel patches?

No. According to the implementation in VPhoneCreateOrchestrator.swift, the installation scripts are invoked as part of the CFW creation workflow. While the less variant skips kernel patches, the installation phases still run to copy the (unpatched) base image. There is no variant that runs JB/EXP installer phases without their corresponding kernel patches.

Where is the variant selection logic implemented?

Variant selection is split across two key files. Kernel patch dispatch is handled in sources/FirmwarePatcher/Pipeline/FirmwarePipeline.swift, which conditionally adds KernelJBPatcher and KernelEXPPatcher. Installer script selection is implemented in sources/vphone-cli/VPhoneCreateOrchestrator.swift, which constructs shell command arguments based on the --variant flag passed by the user.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →