# How Kernel Patches Are Anchored in vphone-cli: String-Based Binary Patching Explained

> Discover how vphone-cli anchors kernel patches using string-based binary patching. Learn to reliably locate target instructions by scanning for string references instead of fixed memory offsets.

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

---

**Kernel patches in vphone-cli are anchored to immutable hard-coded strings within the kernel binary, allowing the tool to reliably locate target instructions across different iOS versions by scanning for string references rather than using fixed memory offsets.**

The **Lakr233/vphone-cli** project implements a robust kernel patching system for iOS firmware by anchoring modifications to unique string literals embedded in the kernel binary. Unlike traditional patchers that rely on brittle static offsets, vphone-cli uses a multi-stage analysis pipeline to locate target code through data references. This approach ensures patches remain functional across kernel rebase operations and minor code changes between iOS releases.

## The Six-Stage Anchor Resolution Workflow

vphone-cli applies **kernel patches** through a systematic six-step process that bridges from immutable data anchors to executable code modifications. Each patch definition in `sources/FirmwarePatcher/Kernel/Patches/` implements this workflow.

### Step 1: Locating the Anchor String

The patching process begins by locating a unique, hard-coded string within the kernel's binary buffer. This string serves as the immutable anchor that survives rebasing and version changes.

```swift
guard let strOff = buffer.findString("rootvp not authenticated after mounting") else { 
    return false 
}

```

*Source:* [`sources/FirmwarePatcher/Kernel/Patches/KernelPatchBsdInit.swift`](https://github.com/Lakr233/vphone-cli/blob/main/sources/FirmwarePatcher/Kernel/Patches/KernelPatchBsdInit.swift) (lines 63-66)

The `findString` method, implemented in [`BinaryBuffer.swift`](https://github.com/Lakr233/vphone-cli/blob/main/BinaryBuffer.swift), returns the file offset of the literal string within the kernel image. If the anchor string is absent, the patch aborts gracefully.

### Step 2: Resolving ADRP/ADD Instruction Pairs

After locating the string offset, the patcher scans the `__text` section for **ADRP** (Address of Page) and **ADD** instruction pairs that reference the anchor string. These instruction pairs load the string's address into a register at runtime.

```swift
let refs = findStringRefs(strOff)          // returns [(adrpOff, addOff), …]

```

*Source:* [`KernelPatchBsdInit.swift`](https://github.com/Lakr233/vphone-cli/blob/main/KernelPatchBsdInit.swift) (lines 69-71)

The `findStringRefs` function, defined in [`KernelPatcherBase.swift`](https://github.com/Lakr233/vphone-cli/blob/main/KernelPatcherBase.swift), returns a list of tuples containing the offsets of the ADRP and corresponding ADD instructions that reference the anchor.

### Step 3: Forward Scanning for Functional Blocks

From each ADD instruction offset, the patcher performs a forward scan (typically up to `0x40` bytes) to locate functional blocks protected by the anchor. This scan searches for **BL** (Branch with Link) or **BLR** (Branch with Link to Register) instructions targeting known functions like `_panic`.

```swift
// forward-scan up to 0x40 bytes for BL _panic
while scan <= fwdLimit {
    if let blTarget = decodeBranchTarget(from: scan), blTarget == panicAddr {
        // Found the error path entry point
        break
    }
    scan += 4  // Advance by instruction size
}

```

*Source:* [`KernelPatchBsdInit.swift`](https://github.com/Lakr233/vphone-cli/blob/main/KernelPatchBsdInit.swift) (lines 78-95)

This phase identifies the error-handling code path that the targeted conditional branch guards.

### Step 4: Back-Scanning for Conditional Branches

Working backward from the ADRP instruction, the patcher locates the **conditional branch** that gates access to the error block identified in Step 3. The back-scan uses the Capstone disassembler to decode each instruction and filters for conditional branch mnemonics.

```swift
guard let insn = disasm.disassembleOne(in: buffer.original, at: back) else { continue }
guard condBranchMnemonics.contains(insn.mnemonic) else { continue }

```

*Source:* [`KernelPatchBsdInit.swift`](https://github.com/Lakr233/vphone-cli/blob/main/KernelPatchBsdInit.swift) (lines 100-116)

Supported conditional branch types include `b.eq`, `cbz`, `tbz`, and other ARM64 conditional variants defined in the patcher's mnemonic whitelist.

### Step 5: Validating Branch Targets

Before modification, the patcher validates that the discovered conditional branch actually targets the error block. The target address is extracted from Capstone's operand string via `decodeBranchTargetFromInsn` and checked against the previously identified error block boundaries (`errLo` and `errHi`).

```swift
guard let branchTarget = decodeBranchTargetFromInsn(insn) else { continue }
guard branchTarget >= errLo, branchTarget < errHi else { continue }

```

*Source:* [`KernelPatchBsdInit.swift`](https://github.com/Lakr233/vphone-cli/blob/main/KernelPatchBsdInit.swift) (lines 118-125)

This validation prevents false positives where an unrelated conditional branch might be located near the anchor reference.

### Step 6: Emitting the Patch

Once validated, the patcher replaces the conditional branch with a **NOP** (No Operation) instruction, effectively removing the guard check. The `emit` function records the modification with metadata for verification.

```swift
emit(
    back, 
    ARM64.nop, 
    patchID: "kernel.bsd_init_rootvp",
    virtualAddress: va,
    description: "NOP conditional branch guarding rootvp panic"
)

```

*Source:* [`KernelPatchBsdInit.swift`](https://github.com/Lakr233/vphone-cli/blob/main/KernelPatchBsdInit.swift) (lines 126-134)

The `emit` function, centralized in [`KernelPatcher.swift`](https://github.com/Lakr233/vphone-cli/blob/main/KernelPatcher.swift), handles deduplication, logging, and patch serialization.

## Key Architectural Components

The **vphone-cli** anchoring system relies on several specialized components that work together to provide version-independent patching:

- **BinaryBuffer.swift** – Contains the `findString` method that locates anchor strings in the raw kernel binary. This provides the stable entry point that survives kernel rebasing.

- **KernelPatcherBase.swift** – Implements `findStringRefs`, which bridges from data offsets (strings) to code locations by identifying ADRP+ADD instruction pairs in the `__text` section.

- **ARM64Disassembler.swift** – Wraps the Capstone disassembly engine to decode ARM64 instructions and extract branch targets. This enables architecture-agnostic analysis of conditional branches.

- **KernelPatcher.swift** – Provides the core `emit` function that records patches and assigns unique patch IDs for later verification and debugging.

- **Patch Definition Files** – Individual Swift files in `sources/FirmwarePatcher/Kernel/Patches/` encapsulate specific patches. Examples include:
  - [`KernelPatchBsdInit.swift`](https://github.com/Lakr233/vphone-cli/blob/main/KernelPatchBsdInit.swift) – Removes the rootvp authentication check using the anchor *"rootvp not authenticated after mounting"*
  - [`KernelPatchExcGuard.swift`](https://github.com/Lakr233/vphone-cli/blob/main/KernelPatchExcGuard.swift) – Disables thread guard violations using the anchor *"thread_guard_violation"*
  - [`KernelPatchApfsSnapshot.swift`](https://github.com/Lakr233/vphone-cli/blob/main/KernelPatchApfsSnapshot.swift) – Modifies APFS snapshot handling using related string anchors

## Implementation Example: Thread Guard Patch

The [`KernelPatchExcGuard.swift`](https://github.com/Lakr233/vphone-cli/blob/main/KernelPatchExcGuard.swift) file demonstrates the same anchoring pattern applied to a different kernel subsystem. This patch locates the `thread_guard_violation` string and follows identical resolution steps to disable the guard mechanism.

```swift
// Example from KernelPatchExcGuard.swift
guard let strOff = buffer.findString("thread_guard_violation") else { return false }
let refs = findStringRefs(strOff)               // locate ADRP/ADD pairs
// ... forward-scan for the guard call, back-scan for the conditional branch,
// then NOP the branch to allow continued execution

```

*Source:* [`KernelPatchExcGuard.swift`](https://github.com/Lakr233/vphone-cli/blob/main/KernelPatchExcGuard.swift) (lines 30-40)

The `decodeBranchTargetFromInsn` helper function parses Capstone's operand strings to extract hexadecimal branch targets:

```swift
private func decodeBranchTargetFromInsn(_ insn: Instruction) -> Int? {
    let tokens = insn.operandString.split(separator: ",")
        .map { $0.trimmingCharacters(in: .whitespaces) }
    guard let last = tokens.last else { return nil }
    let hex = last.hasPrefix("#") ? String(last.dropFirst()) : last
    return hex.hasPrefix("0x") ? Int(hex.dropFirst(2), radix: 16) : Int(hex)
}

```

*Source:* [`KernelPatchBsdInit.swift`](https://github.com/Lakr233/vphone-cli/blob/main/KernelPatchBsdInit.swift) (lines 45-65)

## Summary

vphone-cli implements a **string-based anchoring system** for kernel patches that provides several critical advantages over offset-based patching:

- **Version independence** – Patches survive kernel rebasing and minor code changes across iOS releases
- **Reliability** – Hard-coded strings provide unique, immutable anchors that are unlikely to collide
- **Maintainability** – Each patch is self-contained in its own file with a clear anchor definition
- **Safety** – Multi-stage validation (forward scanning, back-scanning, and target verification) prevents incorrect modifications

The architecture separates concerns between binary searching (`BinaryBuffer`), reference resolution (`KernelPatcherBase`), disassembly (`ARM64Disassembler`), and patch application (`KernelPatcher`).

## Frequently Asked Questions

### How does vphone-cli find the specific instruction to patch without knowing the kernel version?

vphone-cli uses **hard-coded strings** as immutable anchors. Since these strings remain constant across kernel versions, the tool can always locate them using `buffer.findString()`. From there, it follows code references (ADRP/ADD pairs) to find the specific instructions that reference the string, rather than relying on fixed memory addresses that change between builds.

### What ARM64 instructions does vphone-cli look for when resolving string references?

The patcher specifically searches for **ADRP** (Address of Page) paired with **ADD** instructions. On ARM64, this instruction sequence is the standard compiler pattern for loading the address of a string literal into a register. By scanning the `__text` section for these pairs that reference the anchor string's offset, vphone-cli reliably locates all code paths that use that string.

### Why does vphone-cli use both forward and backward scanning from the anchor location?

**Forward scanning** identifies the functional block protected by the anchor (such as a call to `_panic`), establishing the boundaries of the error path. **Backward scanning** locates the conditional branch (like `b.eq` or `cbz`) that gates entry to that error path. This bidirectional approach ensures the patcher modifies the correct control-flow instruction—the one that decides whether to enter the error block—not just any nearby branch.

### What happens if the anchor string appears multiple times in the kernel?

The `findStringRefs` function returns **all** (ADRP, ADD) pairs that reference the anchor string. The patching logic then iterates through each reference, performing the forward/backward scan and validation for every occurrence. This ensures patches are applied consistently across all code paths that check the anchored condition, even if the kernel contains multiple functions referencing the same string literal.