# How vphone-cli Handles EXC_GUARD Patching on iOS 18 Bases

> Discover how vphone-cli handles EXC_GUARD patching on iOS 18 bases by patching thread_guard_violation to prevent fatal VM termination. Learn about its automatic activation.

- Repository: [Lakr/vphone-cli](https://github.com/Lakr233/vphone-cli)
- Tags: internals
- Published: 2026-08-31

---

**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`](https://github.com/Lakr233/vphone-cli/blob/main/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:

```swift
"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:

```swift
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`](https://github.com/Lakr233/vphone-cli/blob/main/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`](https://github.com/Lakr233/vphone-cli/blob/main/sources/FirmwarePatcher/Kernel/KernelPatcher.swift):

```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`](https://github.com/Lakr233/vphone-cli/blob/main/sources/vphone-cli/VPhoneCLI.swift):

```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:

```bash

# 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:

```swift
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:

```swift
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`](https://github.com/Lakr233/vphone-cli/blob/main/KernelPatchExcGuard.swift) contains the patch logic, while [`KernelPatcher.swift`](https://github.com/Lakr233/vphone-cli/blob/main/KernelPatcher.swift) and [`VPhoneCLI.swift`](https://github.com/Lakr233/vphone-cli/blob/main/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.