I wanted a jailbroken iOS environment I could tear down and rebuild in minutes, without keeping a spare iPhone frozen on some ancient firmware just so a jailbreak tool still works on it. That's the usual price of doing iOS security research: you either babysit an old device forever or you buy a Corellium seat. So when I found vphone-cli, a command-line tool that boots an actual iPhone firmware image as a virtual machine using Apple's own Virtualization.framework, I cleared my Saturday and gave it a shot.
It did not go smoothly on the first try, and that's really the reason I'm writing this. The README is thorough, but two spots took me longer than they should have: getting macOS to actually let the tool run at all, and a dead-silent SSH failure after first boot that has nothing to do with networking and everything to do with a missing key file. Both are documented if you read closely enough, but I didn't read closely enough the first time, so here's the version of this walkthrough I wish I'd had.
Worth saying up front: this only works on Apple Silicon Macs running macOS 15 (Sequoia) or later, because the tool depends on a paravirtualization mode the README calls PV=3, and it involves deliberately relaxing macOS's own code-signing protections. That's not a decision to make on your daily-driver machine without thinking it through. And the thing that ended up mattering most for whether any of this booted cleanly wasn't my iOS version or which firmware variant I picked — it was whether AMFI and SIP were configured correctly before I ever ran make build.
What vphone-cli Actually Boots
The framing that took me a minute to get straight: this isn't emulation, and it isn't the iOS Simulator with a different skin. vphone-cli takes real iPhone firmware, patches parts of the boot chain, and hands it to Virtualization.framework the same way Apple's own Private Cloud Compute research VM infrastructure does internally. The README is explicit that the manifest format and tooling are derived from that PCC research VM stack, just repointed at a consumer iOS build instead of a server one.
What you get out of the other end is a genuine iOS 26 kernel and filesystem running as a guest OS, not a simulated one. That matters if you care about kernel-level behavior, real jailbreak tooling, or anything that the Simulator fundamentally can't fake because it isn't running Apple's actual kernel. The tradeoff is that "real firmware" also means real firmware signing, real SEP-adjacent restrictions, and a genuinely fiddly restore process — you're not just launching a prebuilt disk image.
vphone-cli ships five firmware patch variants, and picking the wrong one for your use case is an easy way to waste an afternoon re-flashing:
| Variant | Boot Chain Patches | CFW Phases | What It's For |
|---|---|---|---|
| Patchless | 4 | 2 | Minimal bypass, closest to stock behavior |
| Regular | 42 | 10 | Standard unsigned-code workflow |
| Development | 53 | 12 | Adds TXM entitlement/debug bypasses |
| Jailbreak | 113 | 14 | Full bypass, ships Sileo + TrollStore |
| Experimental | 141 | 18 | Jailbreak plus patches that hide VM identity from some Apple services |
I started on Regular to make sure the whole pipeline worked end to end before committing to a full jailbreak rebuild, and I'd recommend the same order of operations to anyone trying this for the first time.
The AMFI Wall: Getting Past "zsh: killed"
The first real obstacle wasn't a vphone-cli bug at all — it was macOS refusing to even start the binary. vphone-cli needs private Virtualization.framework entitlements and the ability to run unsigned or ad-hoc-signed binaries, and by default AMFI (Apple Mobile File Integrity) and SIP exist specifically to stop exactly that.
The symptom is unglamorous. You run the tool, and the shell just reports that it died, with no further explanation:
zsh: killed ./vphone-cli
That message means AMFI's debug restrictions killed the process before it got anywhere near your code. The README lays out two ways to fix it, and the difference between them is really a question of how much of your Mac's default security posture you're willing to give up for this. Option one is the blunt instrument: boot into Recovery and fully disable SIP, then restart and set a permissive boot-arg from a normal Terminal session:
# in Recovery Terminal
csrutil disable
csrutil allow-research-guests enable
# after restarting into macOS
sudo nvram boot-args="amfi_get_out_of_my_way=1 -v"
# then restart once more
That works, and it's what I used for my first attempt because I wanted the fastest possible path to a booted VM. But it turns off AMFI checks globally, for every binary on the machine, not just vphone-cli — which is a much bigger blast radius than I actually needed. Option two is narrower: keep SIP enabled except for debug restrictions, then use a third-party tool like amfidont or amfree to allowlist vphone-cli specifically by its code-signature hash, so the rest of the system stays locked down. The repo even ships a Makefile target, make amfidont_allow_vphone, that computes the current CDHash and registers it for you.
If this machine is a dedicated research box, option one is fine and honestly less to think about. If it's the same laptop you do client work on, option two is the one I'd actually reach for — it's a few more steps, but it doesn't leave AMFI wide open system-wide after you're done experimenting.
Picking a Firmware Variant for What You're Actually Doing
Once the AMFI wall is out of the way, the next decision that actually affects your workflow is which of those five variants to patch and restore. It's tempting to jump straight to Jailbreak because that's the interesting one, but the patch count table above is a rough proxy for how much of iOS's security model you're tearing out, and more isn't automatically better for every use case.
If you're testing an app's own logic — UI flows, networking, background behavior — Regular or Development gets you a real device without needing Sileo or a jailbreak detection bypass at all. Jailbreak is what you want if your actual work involves Frida, Burp interception, or filesystem-level poking around, since that's where Sileo and TrollStore become available automatically on first boot via a LaunchDaemon the README calls out by name.
I'd treat Experimental as a targeted tool for a specific problem — an app that refuses to run because it's detected a virtual machine — rather than a default choice. It's also the newest and most heavily patched variant, which in practice means it's the one most likely to need a rebuild when Apple ships firmware that shifts binary offsets around.
The Two-Terminal DFU Restore Dance
Restoring the patched firmware onto the VM is where vphone-cli stops feeling like a normal build tool and starts feeling like actual device restore tooling, because that's exactly what it's driving under the hood. It uses pymobiledevice3 as the restore backend, and it genuinely wants two terminal windows open at once, one of which just sits there holding a DFU-mode boot alive while the other does the real work.
The sequence itself is short once you know it, but getting the terminal choreography wrong is the easiest way to end up restoring against a VM that already dropped out of DFU:
- In terminal one, run
make boot_dfuand leave it running — this is what keeps the VM parked in DFU mode. - In terminal two, run
make restore_get_shshto fetch the SHSH blob needed to sign the restore. - Still in terminal two, run
make restore(ormake restore_offlineif you've already cached the blob and want to decrypt AEA images locally instead of pulling from Apple).
The part that isn't obvious until you've done it once: installing the custom firmware afterward means repeating this same dance a second time, because you need a fresh DFU boot to push a signed SSH ramdisk before cfw_install can run. It's not a bug, it's just how DFU-based restore has always worked on real hardware — vphone-cli isn't skipping any of the steps a real jailbreak tool would need, which is honestly reassuring from a "does this actually behave like a real device" standpoint.
The Dropbear SSH Gotcha That Looks Like a Network Problem
This is the one that actually cost me the most time, because every symptom pointed at the wrong layer. After first boot on the Regular variant, I forwarded the SSH port through usbmux, tried to connect, and got an instant connection reset — no handshake, no banner, nothing. My first instinct was to blame the port forward, then the firewall, then usbmux itself.
None of that was it. On the Regular and Development variants, first boot drops you into a direct console rather than starting SSH automatically, and the README is specific about why: the SSH daemon on this image is dropbear, and dropbear will happily accept a TCP connection and then immediately close it if it has no host keys generated yet. It's not refusing the connection — it's accepting it and then having nothing to complete the handshake with. From the client side those two failure modes look identical.
The fix is to generate the keys once from the console before you ever try to SSH in:
export PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/bin/X11:/usr/games:/iosbinpack64/usr/local/sbin:/iosbinpack64/usr/local/bin:/iosbinpack64/usr/sbin:/iosbinpack64/usr/bin:/iosbinpack64/sbin:/iosbinpack64/bin'
mkdir -p /var/dropbear
cp /iosbinpack64/etc/profile /var/profile
cp /iosbinpack64/etc/motd /var/motd
dropbearkey -t rsa -f /var/dropbear/dropbear_rsa_host_key
dropbearkey -t ecdsa -f /var/dropbear/dropbear_ecdsa_host_key
shutdown -h now
Run that once from the console, shut down, and boot again — dropbear will have keys to complete the handshake with and SSH starts working normally on subsequent boots. It's a one-time setup cost, but it's easy to burn twenty minutes debugging port forwards before you realize the problem is two files that were never created.
Connecting to the VM Once It's Actually Running
After that first-boot setup, day-to-day use is a lot more pleasant than the restore process makes it look. Every subsequent boot is just make boot, followed by opening usbmux forward tunnels in a separate terminal for whichever services you need — dropbear SSH, an OpenSSH server if you installed one from Sileo, VNC for the actual screen, and an RPC socket for programmatic control.
Which port you connect to and with which credentials depends on the variant and what you've installed on top of it:
- SSH on the jailbreak variant: connect as
mobile@127.0.0.1on your forwarded port, passwordalpine, once you've installed openssh-server from Sileo. - SSH on Regular/Development: connect as
root@127.0.0.1, samealpinepassword, using the dropbear keys you generated above. - VNC: point any VNC client at
vnc://127.0.0.1:5901for the actual device screen. - RPC: the
rpc-projectclient can attach on port 5910 for lower-level remote control.
The VNC connection ends up mattering more than I expected, mostly because of one specific setup screen that has no obvious button: the "Press home to continue" prompt that shows up during initial iOS setup on hardware with no physical home button. Right-clicking anywhere on the VNC window — a two-finger click if you're on a trackpad — simulates that button press and gets you unstuck.
Where This Breaks Down
None of this is magic, and there are a few hard limits worth knowing about before you invest a weekend in it. The one that will bite you fastest if you're doing this inside a CI runner or a cloud Mac: nested virtualization doesn't work. If the host macOS is itself running inside an Apple virtual machine, Virtualization.framework refuses to boot a nested guest and vphone-cli fails fast with a hardware-availability error before it even tries.
The other gotcha that has nothing to do with the tool itself and everything to do with iOS setup: don't select Japan or the European Union as your region during initial setup. Both enforce extra regulatory checks — sideloading disclosures, camera shutter requirements — that a virtual machine can't satisfy, and the practical result is that system apps like the App Store silently refuse to install. Picking the United States or basically any other region sidesteps the whole problem.
Beyond troubleshooting, the tool exposes a host control socket at vm/vphone.sock for programmatic interaction — screenshots, touch injection, swipe gestures, hardware key presses, clipboard access — with every action returning a compact screenshot inline. That's clearly built for automated end-to-end testing rather than manual poking, and projects like vphone-mcp wrap that socket as an MCP server so you can drive the virtual phone from Claude Code or Claude Desktop instead of a Makefile.
Was It Worth the Weekend
Yes, but not because it was painless — it wasn't, and I don't think anyone should expect a tool patching real signed iPhone firmware to be a one-command experience. What I got out of it was a jailbroken iOS 26 environment I can snapshot, break, and restore from a backup in a few minutes with make vm_backup and make vm_switch, instead of babysitting a physical device that could get bricked by one careless update. For anyone doing app security work, Frida instrumentation, or jailbreak-adjacent research, that tradeoff — a few hours of setup friction against never touching a soldering iron or a hardware DFU cable again — is an easy one to make. Just budget the extra hour for AMFI and the dropbear keys, because the README covers both, but it's easy to skim past them the first time through.
Comments
No comments yet — be the first to share your thoughts.