How to Access Private Virtualization APIs Using Dynamic in vPhone-CLI

vPhone-CLI uses the Dynamic Swift package to instantiate and manipulate private Objective-C classes from Apple’s Virtualization.framework by leveraging runtime reflection via NSClassFromString and performSelector: bridges.

The vphone-cli project demonstrates how to bypass the public Swift SDK and interact with undocumented Virtualization APIs on macOS. By integrating the third-party Dynamic library, the CLI tool unlocks hidden functionality—such as multi-touch input, synthetic key events, and hardware acceleration—that Apple does not expose in standard headers. This approach relies on a consistent three-step pattern to create objects, access properties, and invoke methods without compile-time bindings.

Understanding the Dynamic Runtime Bridge

Dynamic is a Swift package that provides a thin runtime bridge for pure-Swift code to manipulate Objective-C classes and members unavailable in the public SDK. It operates by dynamically resolving class names at runtime using NSClassFromString and forwarding method calls through performSelector:. This mechanism allows developers to instantiate private Apple framework classes—such as those in Virtualization.framework and BiometricKit—without importing private headers or using Objective-C++ bridging.

The library exposes two core interfaces: factory methods prefixed with an underscore (e.g., Dynamic._VZTouch) to create instances, and wrapper initialization (Dynamic(<obj>)) to interact with existing objects. When wrapped, objects gain property accessors (.asObject, .asUInt64, .asInt) and method proxies that translate Swift types to Objective-C expectations automatically.

The Three-Step Pattern for Private API Access

All private Virtualization API interactions in vPhone-CLI follow a predictable workflow. Mastering this pattern allows you to replicate the technique for other undocumented Apple frameworks.

Step 1: Instantiate Private Classes

To create an instance of a private class, call the factory method named after the Objective-C class prefixed with an underscore. Dynamic uses NSClassFromString internally to locate the class and performs standard alloc/init sequences.

// Create a private VZTouch instance
let touch = Dynamic._VZTouch(
    locationX: x, locationY: y,
    pressure: pressure, phase: .began
)

// Create a private VZKeyEvent for keyboard simulation
let keyEvent = Dynamic._VZKeyEvent(type: 0, keyCode: UInt16(0x38))

Step 2: Access Properties with Type Converters

After obtaining an object, wrap it with Dynamic(<obj>) to access properties that lack public getters or setters. The wrapper provides type-specific accessors that marshal values between Swift and Objective-C runtimes.

// Wrap an existing machine identifier to read a private property
if let ecid = Dynamic(machineIdentifier)._ECID.asUInt64 {
    print("ECID = \(ecid)")
}

// Access the BiometricKit manager delegate property
guard let mgr = Dynamic.BiometricKit.manager().asObject else { return }

Step 3: Invoke Private Methods

Dynamic exposes Objective-C selectors as Swift methods on the wrapper object. These calls are forwarded using performSelector: under the hood, enabling invocation of private methods without static linking or header imports.

// Send touch events to a virtual device
let touchEvent = Dynamic._VZMultiTouchEvent(touches: [touchObj])
Dynamic(device).sendMultiTouchEvents([touchEvent] as NSArray)

// Send synthesized keystrokes to the virtual keyboard
Dynamic(keyboard).sendKeyEvents([keyEvent.asAnyObject] as NSArray)

Real-World Implementation in vPhone-CLI

The vPhone-CLI source code demonstrates this pattern across multiple subsystems. Examining these concrete implementations reveals how to apply the technique consistently.

Handling Touch Input in VPhoneVirtualMachineView.swift

In sources/vphone-cli/VPhoneVirtualMachineView.swift (lines 270–287), the CLI constructs multi-touch events for the virtual machine by instantiating private VZTouch and VZMultiTouchEvent classes, then dispatching them via the private sendMultiTouchEvents: selector.

let touch = Dynamic._VZTouch(
    locationX: x, locationY: y,
    pressure: pressure, phase: .began
)
let touchObj = touch.asObject
let eventObj = Dynamic._VZMultiTouchEvent(touches: [touchObj]).asObject
Dynamic(device).sendMultiTouchEvents([eventObj] as NSArray)

Simulating Keyboard Events in VPhoneKeyHelper.swift

The sources/vphone-cli/VPhoneKeyHelper.swift file (lines 121–136) uses Dynamic to construct VZKeyEvent objects with arbitrary key codes, allowing the CLI to inject synthetic keystrokes—including modifier keys and power button events—into the guest OS.

if let obj = Dynamic._VZKeyEvent(type: 0, keyCode: UInt16(0x38)).asAnyObject {
    events.append(obj)
}
Dynamic(keyboard).sendKeyEvents(events as NSArray)

Accessing BiometricKit in VPhoneTouchIDMonitor.swift

At lines 171–197 in sources/vphone-cli/VPhoneTouchIDMonitor.swift, the code accesses the private BiometricKit framework to monitor Touch ID state. It retrieves the framework’s singleton manager, sets a delegate, and enables background fingerprint detection using Dynamic’s property and method proxies.

guard let mgr = Dynamic.BiometricKit.manager().asObject else { return }
Dynamic(mgr).setDelegate(delegate)
Dynamic(mgr).enableBackgroundFdet(true)

Configuring VM Accelerators in VPhoneVirtualMachine.swift

The sources/vphone-cli/VPhoneVirtualMachine.swift file (lines 224–228) assembles hardware acceleration by initializing multiple private device configuration classes—VZMacVideoToolboxDeviceConfiguration, VZMacNeuralEngineDeviceConfiguration, and VZMacScalerAcceleratorDeviceConfiguration—then attaches them via the private _setAcceleratorDevices: method.

let obj1 = Dynamic._VZMacVideoToolboxDeviceConfiguration().asObject
let obj2 = Dynamic._VZMacNeuralEngineDeviceConfiguration().asObject
let obj3 = Dynamic._VZMacScalerAcceleratorDeviceConfiguration().asObject
Dynamic(config)._setAcceleratorDevices([obj1, obj2, obj3])

Summary

Accessing private Virtualization APIs requires a systematic approach to runtime introspection. The vPhone-CLI implementation demonstrates that the Dynamic library provides a reliable bridge between Swift and undocumented Objective-C frameworks.

  • Dynamic exposes private classes through underscore-prefixed factory methods that resolve via NSClassFromString.
  • Property access requires wrapping objects with Dynamic() and using type-specific accessors like .asObject or .asUInt64.
  • Method invocation relies on performSelector: forwarding, allowing calls to private selectors such as sendMultiTouchEvents: and _setAcceleratorDevices:.
  • Key implementation files include VPhoneVirtualMachineView.swift for input handling, VPhoneKeyHelper.swift for keyboard injection, and VPhoneVirtualMachine.swift for hardware configuration.

Frequently Asked Questions

What is the Dynamic Swift package and how does it work?

Dynamic is a third-party Swift package that creates a runtime bridge to Objective-C. It uses NSClassFromString to locate private classes by name and performSelector: to forward method calls, allowing Swift code to instantiate and manipulate Objective-C objects that are not linked at compile time.

How do I handle type conversion when accessing private properties?

Use the wrapper’s convenience accessors provided by Dynamic. After wrapping an object with Dynamic(<obj>), access properties using .asObject, .asUInt64, .asInt, or .asAnyObject depending on the expected Objective-C type. These accessors marshal the runtime value into the appropriate Swift type automatically.

Can Dynamic be used with frameworks other than Virtualization?

Yes. The technique applies to any Objective-C framework, as demonstrated in VPhoneTouchIDMonitor.swift where Dynamic accesses the private BiometricKit framework. The same pattern—factory methods for instantiation, property wrappers for fields, and method proxies for selectors—works across macOS and iOS private APIs.

Is using private Virtualization APIs safe for production applications?

No. Private APIs are undocumented, unsupported, and subject to change or removal in future macOS updates. Apps using these techniques may break with system updates, fail App Store review, or cause instability. This approach is intended for research, debugging, and internal tooling only.

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 →