Virtualization.framework Keyboard Event Pipeline
Reverse engineering findings for the keyboard event pipeline in Apple's Virtualization.framework (macOS 26.2, version 259.3.3.0.0). Documents how keyboard events flow from the macOS host to the virtual iPhone guest.
Event Flow Architecture
There are two pipelines for sending keyboard events to the VM:
Pipeline 1: _VZKeyEvent -> sendKeyEvents: (Standard Keys)
Pipeline 2: _processHIDReports (Raw HID Reports)
_VZKeyEvent Structure
From IDA + LLDB inspection:
Initializer: _VZKeyEvent(type: Int64, keyCode: UInt16)
_VZKeyboard Object Layout
From LLDB memory dump:
Lookup Tables in sendKeyEvents:
Two tables indexed by Apple VK keyCode (0x00-0xB2, 179 entries x 8 bytes each):
Table 1 (validity flags): All valid entries = 0x0000000100000000 (bit 32 set).
Invalid entries = 0.
Table 2 (intermediate indices): Maps Apple VK codes to internal indices (0x00-0x72).
The tables are OR'd: combined = table1[vk] | table2[vk]. Bit 32 check validates
the entry. The lower 32 bits of combined become the intermediate index.
Sample Table 2 Entries
Invalid VK Codes (both tables = 0, silently dropped)
0x48 (Volume Up), 0x49 (Volume Down), 0x4A (Mute), and many others.
Packed Event Format (std::vector<uint64_t>)
Each element in the vector sent to sendKeyboardEvents:keyboardID::
sendKeyboardEventsHIDReport Switch Statement
For type-2 keyboards, the intermediate index is mapped to
IOHIDEventCreateKeyboardEvent(page, usage) via a large switch.
Standard Keyboard Entries (HID Page 7)
Consumer / System Entries (Non-Standard Pages)
Home/Menu (Consumer page 0x0C, usage 0x40) has NO intermediate index. It cannot be sent through Pipeline 1 at all.
_processHIDReports Parameter Format
From IDA decompilation of
VZVirtualMachine._processHIDReports:forDevice:deviceType: at 0x2301b2310.
The void * parameter is a pointer to std::vector<std::span
The function iterates spans in the vector:
deviceType: 0 = keyboard, 1 = pointing device
device: device identifier (uint32_t, matches _VZKeyboard._deviceIdentifier)
Crash Analysis: Why Raw Bytes Crashed
Passing raw [0x40, 0x00] as the void* parameter:
- Function reads bytes as vector struct: begin = 0x0040 (first 8 bytes), end = garbage
- Dereferences begin as span pointer -> reads from address ~0x0040
- Gets garbage data_ptr (0x700420e) and garbage length (0x300000020 = 12GB)
xpc_data_create(0x700420e, 0x300000020)-> EXC_BAD_ACCESS in memcpy
The three-level indirection (vector -> span -> bytes) must be constructed correctly or the framework will dereference invalid pointers.
Swift Implementation Notes
Accessing _VZKeyboard
Constructing std::vector<uint64_t> for sendKeyboardEvents
Constructing vector<span> for _processHIDReports
Source Files
- Class dumps:
/Users/qaq/Documents/GitHub/super-tart-vphone-private/Virtualization_26.2-class-dump/ - IDA database: dyld_shared_cache_arm64e with Virtualization.framework
Key Functions Analyzed
What Never Reaches the Guest (2026-10-02)
Measured on macOS 27.0.1 (Virtualization 259), against the two lookup tables
-[_VZKeyboard sendKeyEvents:] reads (179 entries each, at adrp+add in
the method's prologue; read from the shared cache, whose slide is one per
boot) and the report descriptor of VZUSBKeyboardConfiguration, found in
com.apple.Virtualization.VirtualMachine by its 05 01 09 06 A1 01 prefix:
A boot-protocol keyboard. Of the 179 virtual key codes, 115 have an index; the rest are dropped before any report is built:
VPhoneApplication.sendEvent(_:) sends these through vphoned's input.hid
(VPhoneGuestKeyMap), since nothing the virtual keyboard emits can carry
them. Everything else — letters, digits, punctuation, Return, Tab, Space,
Delete, Forward Delete, Caps Lock, all eight modifiers, F1–F20, arrows,
Home / End / Page Up / Page Down, the keypad and ISO § — goes through the
virtual keyboard unchanged.
On an iPadOS 26.6.2 guest with English and Simplified Pinyin enabled, measured by injecting each usage through vphoned in Notes:
- page 7
E0+2C(⌃Space) switches the input source; - vendor
FF/03(🌐) switches the input source; - page 7
39(Caps Lock) types capitals in Pinyin rather than switching — iPadOS's default for Chinese input.
Esc: VPhoneApplication turns Esc into the back gesture on an iPhone guest,
which has no Esc key. On an iPad guest Esc goes through as Esc, where it
cancels a composition, a menu or a sheet.
Host shortcuts still apply before the guest sees anything: with
AppleFnUsageType = 1 (System Settings › Keyboard › "Press 🌐 key to: Change
Input Source") the Mac switches its own input source on the same fn press,
and a ⌃Space or ⌘Space bound to Spotlight or input-source switching on the
host is the host's unless the VM view's capturesSystemKeys takes it first.
While a VM window is key, VPhoneHostHotKeys pushes
kHIHotKeyModeAllDisabledExceptUniversalAccess so the guest gets them; that
only takes effect when the responsible app is trusted for Accessibility, and
lapses when vphone-vm is deactivated or exits. The mode does not cover the
input-source hot keys: ⌃Space and ⌃⌥Space stay with the Mac, and neither the
mode nor SkyLight's per-connection SLSSetSymbolicHotKeyEnabledForConnection
lets Space reach vphone-vm (only Control's flags change arrives). Only switching
the hot key off system-wide does, which can leave the Mac without it after a
crash, so it is not done. Switch the guest's input source with 🌐 or Device ›
Switch Guest Input Source.