How KernelEXPPatchHvVmmRename Evades VM-Detection Services in vPhone-CLI

KernelEXPPatchHvVmmRename evades VM-detection by performing single-byte surgical edits that rename the hv_vmm_present sysctl OID from hv_vmm_present to Xv_vmm_present, causing trusted kernel services to read the valid VM value (1) while blacklisted callers receive ENOENT and conclude they are not in a virtual machine.

The EXP variant of vPhone-CLI ships with a sophisticated kernel-level countermeasure designed to bypass virtualization detection in iOS kernels. According to the Lakr233/vphone-cli source code, the KernelEXPPatchHvVmmRename patch manipulates the kern.hv_vmm_present sysctl at the binary level to create a split-view reality: critical system components like AMFI and the sandbox see a valid VM presence, while targeted detection services believe they are running on bare metal.

The Two-Part Byte-Level Patching Strategy

The patch works through two coordinated single-byte modifications inside the kernelcache binary. Both changes flip the character h (hex 0x68) to X (hex 0x58) at specific offsets, creating a renamed sysctl OID that remains internally accessible but externally invisible.

Part A: OID Rename (The String Mutation)

The first modification targets the NUL-delimited C-string that defines the sysctl's OID name. The patch locates the sequence \0hv_vmm_present\0 and changes the h to X, resulting in \0Xv_vmm_present\0.

This rename has a critical effect: after the patch, direct lookups for "kern.hv_vmm_present" return ENOENT (No such file or directory), while lookups for "kern.Xv_vmm_present" resolve to the real OID with an integer value of 1. As implemented in KernelEXPPatchHvVmmRename.swift, this is the foundation of the evasion technique.

Part B: Internal Caller Mangling (Synchronizing Trusted Components)

The second modification updates every internal kernel reference to ensure legitimate services remain synchronized with the renamed OID. The patch scans for two specific patterns:

  • The full C-string \0kern.hv_vmm_present\0
  • The sandbox token kern.hv_vmm_present\x0f

For each occurrence, the patch flips the fifth byte (h → X), transforming the strings to kern.Xv_vmm_present. This ensures that kernel code, AMFI, and the sandbox profile verifier query the renamed OID and receive the truthful value 1, preventing VM detection panics. Blacklisted userland callers that use the original name receive ENOENT, triggering their "not-in-VM" defensive logic.

Implementation Details in KernelEXPPatchHvVmmRename.swift

The core algorithm lives in sources/FirmwarePatcher/Kernel/EXPPatches/KernelEXPPatchHvVmmRename.swift. The implementation is idempotent—re-running the patch detects already-patched bytes and skips them.

The Swift code performs byte-level searches using Data buffers:

// Part A: Rename the OID name itself
let needle = Data([0x00, 0x68, 0x76, 0x5f, 0x76, 0x6d, 0x6d, 0x5f, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x00])
if let offset = buffer.findAll(needle).first {
    emit(offset + 1, Data([0x58]), comment: "OID: h -> X")
}

// Part B: Mangle internal callers
let sites = [
    (needle: "\0kern.hv_vmm_present\0".data(using: .utf8)!, delta: 6, label: "cstr"),
    (needle: "kern.hv_vmm_present\x0f".data(using: .utf8)!, delta: 5, label: "tlv")
]

for (needle, delta, label) in sites {
    for offset in buffer.findAll(needle) {
        let mangle = offset + delta
        guard buffer.data[mangle] == 0x68 else { continue }
        emit(mangle, Data([0x58]), comment: "\(label): h -> X")
    }
}

The hv_vmm_present_xref.json file in the repository's research directory provides the offset maps for each kernel binary, ensuring the patch locates all occurrences without false positives.

Practical Code Examples

Applying the Patch

Invoke the patch through the KernelEXPPatcher coordinator class:

let expPatcher = KernelEXPPatcher(kernelcache: kernelBuffer)
if expPatcher.patchHvVmmRename() {
    print("[EXP] hv_vmm_present patch applied")
} else {
    print("[EXP] Patch already present or buffer not found")
}

Querying the Sysctl Before Patching

On an unpatched virtual machine, querying the original name fails:

var value: Int32 = 0
var size = MemoryLayout.size(ofValue: value)
let rc = sysctlbyname("kern.hv_vmm_present", &value, &size, nil, 0)
// rc == ENOENT (2) - AMFI interprets this as "not a VM" and may panic

Querying the Sysctl After Patching

After applying KernelEXPPatchHvVmmRename, trusted components query the renamed OID:

var value: Int32 = 0
var size = MemoryLayout.size(ofValue: value)
let rc = sysctlbyname("kern.Xv_vmm_present", &value, &size, nil, 0)
// rc == 0, value == 1 - AMFI sees VM presence as valid and continues safely

Summary

  • KernelEXPPatchHvVmmRename performs single-byte surgical edits (0x68 → 0x58) to rename the hv_vmm_present sysctl OID to Xv_vmm_present.
  • The patch creates a split-view environment where ENOENT is returned to blacklisted callers while trusted kernel services receive the valid VM value 1.
  • Implementation spans KernelEXPPatchHvVmmRename.swift for the kernelcache and cfw_patch_hv_vmm_dsc.py for userland DSC dylibs.
  • The technique is idempotent and relies on the hv_vmm_present_xref.json research data to locate all string occurrences.
  • This evasion prevents AMFI and sandbox panics while allowing VM-detection services to conclude they are running on physical hardware.

Frequently Asked Questions

How does flipping a single byte from 'h' to 'X' evade detection?

The byte flip changes the sysctl's registered name from hv_vmm_present to Xv_vmm_present without altering its data structure. When blacklisted services query the original name "kern.hv_vmm_present", the kernel returns ENOENT because that OID no longer exists. Meanwhile, internal kernel components query "kern.Xv_vmm_present" and receive value 1, confirming the VM is present. This architectural split allows the system to lie to specific callers while maintaining internal consistency.

Is KernelEXPPatchHvVmmRename safe to run multiple times?

Yes. According to the source code in KernelEXPPatchHvVmmRename.swift, the patch is idempotent. During each run, the algorithm checks that the target byte equals 0x68 (the original 'h') before writing 0x58 ('X'). If the byte is already 0x58, the patch skips that location, preventing double-patching or corruption of the kernelcache binary.

Does this patch affect userland applications or only the kernel?

The patch primarily targets the kernelcache, but the evasion strategy extends to userland through scripts/patchers/cfw_patch_hv_vmm_dsc.py. This companion script performs identical byte flips in the Dynamic Shared Cache (DSC) dylibs, ensuring that system libraries in user space maintain the same split-view behavior as the kernel. Without this coordination, userland detection services could still identify the VM environment.

Which specific services are blacklisted versus whitelisted by this technique?

The whitelist includes critical system components that require VM awareness to function correctly: AMFI (Apple Mobile File Integrity) for its "No PMGR?" checks, the sandbox profile verifier, and graphics acceleration kexts. Blacklisted callers typically include sign-in libraries, DRM verification tools, and jailbreak detection frameworks that query kern.hv_vmm_present to trigger defensive logic when a VM is detected.

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 →