How vphone-cli Handles EXC_GUARD Patching on iOS 18 Bases

vphone-cli disables EXC_GUARD enforcement by patching thread_guard_violation to return immediately via an ARM64 RET instruction, with automatic activation on iOS 18 (I8) kernels to prevent fatal Mach-port-guard exceptions from terminating the virtual machine.

The vphone-cli repository provides kernel patching utilities for iOS virtualization research. When running virtualized iOS instances, EXC_GUARD patching becomes critical on modern kernels to bypass the Mach-port guard mechanism that would otherwise crash the system upon guard violations.

Understanding EXC_GUARD and iOS 18 Requirements

EXC_GUARD is a Mach exception type that enforces security policies on port usage. In iOS 18 kernels (referred to as "I8" bases in the codebase), these guard violations are unconditionally fatal, meaning any process touching a guarded port triggers an immediate EXC_GUARD exception that terminates execution.

For research VMs running unmodified third-party binaries, this behavior is prohibitive. The vphone-cli toolchain solves this by surgically disabling the violation delivery mechanism inside the kernel image before boot.

How the EXC_GUARD Patch Works

The implementation in sources/FirmwarePatcher/Kernel/Patches/KernelPatchExcGuard.swift performs a multi-stage binary analysis to locate and neutralize the guard violation handler.

Locating the Anchor String

The patch begins by searching the kernel image for the unique string constant:

"com.apple.security.only-one-exception-port"

This string resides within set_exception_behavior_allowed() and serves as the entry point for resolving the surrounding code graph.

Traversing the Call Chain

From the anchor, the patcher resolves the ADRP+ADD instruction pair that references the string, then performs a controlled scan of the function to find a BL (branch-with-link) call to set_exception_behavior_violation. The target function is identified by its PACIBSP prologue followed by a TBZ/TBNZ conditional check.

Inside set_exception_behavior_violation, the scanner locates the subsequent BL that reaches thread_guard_violation—the ultimate target for patching.

Injecting the Return Instruction

The first instruction of thread_guard_violation is replaced with an ARM64 return instruction:

emit(inner, ARM64.ret, patchID: "kernel.thread_guard_violation",
     virtualAddress: va,
     description: "PACIBSP→RET (disable guard violation delivery)")

This RET forces the function to exit immediately, bypassing the code that logs the violation and triggers the AST (Asynchronous Software Trap) responsible for delivering the EXC_GUARD exception to user space.

When vphone-cli Applies the EXC_GUARD Patch

The patching logic supports three activation modes controlled by KernelPatcher.swift and the CLI interface.

Automatic Application on iOS 18 (I8) Bases

For iOS 18 kernels, the patch is always applied regardless of user preferences. The FirmwarePipeline checks variant.isI8Base and forces the patch because these kernels make guard violations fatal for any application touching a guarded port. This automatic inclusion guarantees VM compatibility with unmodified binaries.

The decision logic appears in sources/FirmwarePatcher/Kernel/KernelPatcher.swift:

if options.forceExcGuard || variant.isI8Base {
    patchExcGuardBehavior() // 26
}

Manual Override via CLI

Users can force the patch on non-iOS 18 bases using the --force-exc-guard flag defined in sources/vphone-cli/VPhoneCLI.swift:

@Flag(name: .customLong("force-exc-guard"),
      help: "Force-enable the EXC_GUARD (Mach port guard) disable patch …")
var forceExcGuard: Bool = false

This flag propagates through FirmwarePipeline to KernelPatcher, enabling the patch for older kernels or specialized testing scenarios like third-party crash-reporting integrations.

Developer Variant Builds

The dev variant (KernelPatcher.dev) automatically includes the EXC_GUARD patch without requiring explicit flags, streamlining development workflows where repeated manual invocation would be cumbersome.

Implementation Details and Usage Examples

Patching via Command Line

To patch a VM directory with automatic iOS 18 detection:


# Patch a VM directory - EXC_GUARD applies automatically for iOS 18 bases

vphone-cli patch-firmware -d /path/to/vm/restore \
    --variant exp

# Force the patch on older bases (optional)

vphone-cli patch-firmware -d /path/to/vm/restore \
    --variant regular \
    --force-exc-guard

Programmatic Swift Integration

Invoke the pipeline directly in Swift applications:

import VPhoneCore

let pipeline = FirmwarePipeline(
    vmDirectory: URL(fileURLWithPath: "/tmp/vm/Restore"),
    variant: .experimental,
    verbose: true,
    forceExcGuard: true,  // Explicit enable
    enableFrida: false
)

let appliedPatches = try pipeline.patchAll()
print("Applied \(appliedPatches.count) patches, including EXC_GUARD")

Direct Kernel Patcher Access

For custom scripting bypassing the automatic logic:

let kernelPatcher = KernelPatcher(
    kernelData: kernelBuffer,
    options: KernelPatchOptions(forceExcGuard: true)
)
let success = kernelPatcher.patchExcGuardBehavior()
print("EXC_GUARD disabled:", success)

Summary

  • Automatic protection: iOS 18 (I8) bases automatically receive the EXC_GUARD patch to prevent fatal Mach-port-guard exceptions.
  • Surgical patching: The patch replaces the first instruction of thread_guard_violation with RET, found via anchor string "com.apple.security.only-one-exception-port" and call-chain analysis.
  • Flexible activation: Available through automatic detection, --force-exc-guard CLI flag, or dev variant builds.
  • Key files: KernelPatchExcGuard.swift contains the patch logic, while KernelPatcher.swift and VPhoneCLI.swift manage activation conditions.

Frequently Asked Questions

What happens if I don't apply the EXC_GUARD patch on iOS 18?

Without the patch, any process in the VM that violates a Mach-port guard policy triggers an immediate EXC_GUARD exception. On iOS 18 kernels, this exception is fatal and terminates the process (or potentially the entire VM), making it impossible to run arbitrary third-party binaries that haven't been explicitly hardened against port-guard violations.

How does vphone-cli find the right function to patch?

The patcher uses a signature-based approach starting with the unique kernel string "com.apple.security.only-one-exception-port". It resolves the referencing instructions to locate set_exception_behavior_allowed(), then follows the BL calls through set_exception_behavior_violation to reach thread_guard_violation, where it inserts the return instruction.

Can I use this patch on iOS versions earlier than 18?

Yes. While iOS 18 requires the patch unconditionally, you can apply it to earlier versions using the --force-exc-guard CLI flag or by setting forceExcGuard: true in the KernelPatchOptions when constructing the FirmwarePipeline programmatically.

Is the patch reversible once applied to the kernel image?

The patch modifies the kernel binary in memory during the patching process. Since vphone-cli operates on a copy of the firmware (typically in a VM directory), the original kernel remains unmodified. To "reverse" the patch, simply regenerate the VM from the original firmware image without applying the patch.

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 →