mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-04-05 13:09:06 +08:00
252 lines
8.5 KiB
Markdown
252 lines
8.5 KiB
Markdown
# 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)
|
|
|
|
```
|
|
_VZKeyEvent(type, keyCode)
|
|
-> _VZKeyboard.sendKeyEvents:(NSArray<_VZKeyEvent>)
|
|
-> table lookup: keyCode -> intermediate index
|
|
-> pack: uint64_t = (index << 32) | is_key_down
|
|
-> std::vector<uint64_t>
|
|
-> [if type==2] sendKeyboardEventsHIDReport:keyboardID: (switch -> IOHIDEvent -> HID reports)
|
|
-> [fallback] eventSender.sendKeyboardEvents:keyboardID: (VzCore C++ layer)
|
|
```
|
|
|
|
### Pipeline 2: \_processHIDReports (Raw HID Reports)
|
|
|
|
```
|
|
Raw HID report bytes
|
|
-> std::span<const unsigned char>{data_ptr, length}
|
|
-> std::vector<std::span<...>>{begin, end, cap}
|
|
-> VZVirtualMachine._processHIDReports:forDevice:deviceType:
|
|
-> XpcEncoder::encode_data(span) -> xpc_data_create
|
|
-> XPC to VMM process
|
|
```
|
|
|
|
---
|
|
|
|
## \_VZKeyEvent Structure
|
|
|
|
From IDA + LLDB inspection:
|
|
|
|
```c
|
|
struct _VZKeyEvent { // sizeof = 0x18
|
|
uint8_t isa[8]; // offset 0x00 -- ObjC isa pointer
|
|
uint16_t _keyCode; // offset 0x08 -- Apple VK code (0x00-0xB2)
|
|
uint8_t _pad[6]; // offset 0x0A -- padding
|
|
int64_t _type; // offset 0x10 -- 0 = keyDown, 1 = keyUp
|
|
};
|
|
```
|
|
|
|
Initializer: `_VZKeyEvent(type: Int64, keyCode: UInt16)`
|
|
|
|
---
|
|
|
|
## \_VZKeyboard Object Layout
|
|
|
|
From LLDB memory dump:
|
|
|
|
```
|
|
+0x00: isa
|
|
+0x08: _eventSender (weak, id<_VZHIDAdditions, _VZKeyboardEventSender>)
|
|
+0x10: _deviceIdentifier (uint32_t) -- value 1 for first keyboard
|
|
+0x18: type (int64_t) -- 0 for USB keyboard, 2 for type that tries HIDReport first
|
|
```
|
|
|
|
---
|
|
|
|
## 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
|
|
|
|
| Apple VK | Key | Table2 (Index) | HID Page | HID Usage |
|
|
| -------- | ------- | -------------- | -------- | --------- |
|
|
| 0x00 | A | 0x00 | 7 | 0x04 |
|
|
| 0x01 | S | 0x12 | 7 | 0x16 |
|
|
| 0x24 | Return | 0x24 | 7 | 0x28 |
|
|
| 0x31 | Space | 0x29 | 7 | 0x2C |
|
|
| 0x35 | Escape | 0x25 | 7 | 0x29 |
|
|
| 0x38 | Shift | 0x51 | 7 | 0xE1 |
|
|
| 0x37 | Command | 0x53 | 7 | 0xE3 |
|
|
|
|
### 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:`:
|
|
|
|
```
|
|
bits 63:32 = intermediate_index (from table2, lower 32 bits of combined)
|
|
bits 31:1 = 0
|
|
bit 0 = is_key_down (1 = down, 0 = up)
|
|
```
|
|
|
|
---
|
|
|
|
## 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)
|
|
|
|
| Index | HID Page | HID Usage | Meaning |
|
|
| --------- | -------- | --------- | -------------------- |
|
|
| 0x00-0x19 | 7 | 4-29 | Letters a-z |
|
|
| 0x1A-0x23 | 7 | 30-39 | Digits 1-0 |
|
|
| 0x24 | 7 | 40 | Return |
|
|
| 0x25 | 7 | 41 | Escape |
|
|
| 0x29 | 7 | 44 | Space |
|
|
| 0x48-0x4B | 7 | 79-82 | Arrow keys |
|
|
| 0x50-0x53 | 7 | 224-227 | L-Ctrl/Shift/Alt/Cmd |
|
|
|
|
### Consumer / System Entries (Non-Standard Pages)
|
|
|
|
| Index | HID Page | HID Usage | Meaning |
|
|
| ----- | -------- | --------- | ------------------------ |
|
|
| 0x6E | **12** | 671 | **Consumer Volume Down** |
|
|
| 0x6F | **12** | 674 | **Consumer Volume Up** |
|
|
| 0x70 | **12** | 207 | **Consumer Play/Pause** |
|
|
| 0x71 | **12** | 545 | **Consumer Snapshot** |
|
|
| 0x72 | **1** | 155 | **Generic Desktop Wake** |
|
|
|
|
**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<const unsigned char>>**:
|
|
|
|
```
|
|
Level 3 (outermost): std::vector (24 bytes, passed by pointer)
|
|
+0x00: __begin_ (pointer to span array)
|
|
+0x08: __end_ (pointer past last span)
|
|
+0x10: __end_cap_ (capacity pointer)
|
|
|
|
Level 2: std::span (16 bytes per element in the array)
|
|
+0x00: data_ptr (const unsigned char *)
|
|
+0x08: length (size_t)
|
|
|
|
Level 1 (innermost): raw HID report bytes
|
|
```
|
|
|
|
The function iterates spans in the vector:
|
|
|
|
```c
|
|
begin = *vec; // vec->__begin_
|
|
end = *(vec + 1); // vec->__end_
|
|
for (span = begin; span != end; span += 16) {
|
|
data_ptr = *(uint64_t*)span;
|
|
length = *(uint64_t*)(span + 8);
|
|
encoder.encode_data(data_ptr, length); // -> xpc_data_create
|
|
}
|
|
```
|
|
|
|
**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:
|
|
|
|
1. Function reads bytes as vector struct: begin = 0x0040 (first 8 bytes), end = garbage
|
|
2. Dereferences begin as span pointer -> reads from address ~0x0040
|
|
3. Gets garbage data_ptr (0x700420e) and garbage length (0x300000020 = 12GB)
|
|
4. `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
|
|
|
|
```swift
|
|
// Get keyboards array
|
|
let arr = Dynamic(vm)._keyboards.asObject as? NSArray
|
|
let keyboard = arr?.object(at: 0) as AnyObject
|
|
|
|
// _deviceIdentifier is an ivar, not a property -- use KVC
|
|
(keyboard as? NSObject)?.value(forKey: "_deviceIdentifier") as? UInt32
|
|
```
|
|
|
|
### Constructing std::vector<uint64_t> for sendKeyboardEvents
|
|
|
|
```swift
|
|
let data = UnsafeMutablePointer<UInt64>.allocate(capacity: 1)
|
|
data.pointee = (index << 32) | (isKeyDown ? 1 : 0)
|
|
var vec = (data, data.advanced(by: 1), data.advanced(by: 1))
|
|
withUnsafeMutablePointer(to: &vec) { vecPtr in
|
|
Dynamic(vm).sendKeyboardEvents(UnsafeMutableRawPointer(vecPtr), keyboardID: deviceId)
|
|
}
|
|
```
|
|
|
|
### Constructing vector<span<unsigned char>> for \_processHIDReports
|
|
|
|
```swift
|
|
let reportPtr = UnsafeMutablePointer<UInt8>.allocate(capacity: N)
|
|
// fill report bytes...
|
|
|
|
let spanPtr = UnsafeMutablePointer<Int>.allocate(capacity: 2)
|
|
spanPtr[0] = Int(bitPattern: reportPtr) // data pointer
|
|
spanPtr[1] = N // length
|
|
|
|
let vecPtr = UnsafeMutablePointer<Int>.allocate(capacity: 3)
|
|
vecPtr[0] = Int(bitPattern: UnsafeRawPointer(spanPtr)) // begin
|
|
vecPtr[1] = Int(bitPattern: UnsafeRawPointer(spanPtr).advanced(by: 16)) // end
|
|
vecPtr[2] = vecPtr[1] // cap
|
|
|
|
Dynamic(vm)._processHIDReports(UnsafeRawPointer(vecPtr), forDevice: deviceId, deviceType: 0)
|
|
```
|
|
|
|
---
|
|
|
|
## 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
|
|
|
|
| Function | Address |
|
|
| ------------------------------------------------------------------------------- | ----------- |
|
|
| `-[_VZKeyboard sendKeyEvents:]` | 0x2301b2f54 |
|
|
| `-[_VZKeyboard sendKeyboardEventsHIDReport:keyboardID:]` | 0x2301b3230 |
|
|
| `-[VZVirtualMachine(_VZHIDAdditions) _processHIDReports:forDevice:deviceType:]` | 0x2301b2310 |
|
|
| `-[VZVirtualMachineView _sendKeyEventsToVirtualMachine:]` | -- |
|
|
| `-[_VZHIDEventMonitor getHIDReportsFromHIDEvent:]` | 0x2301b2af0 |
|