Inferno for iPhone
An emulated iPhone 11, running as an app on a real iPhone.
This is a SwiftUI front end for Inferno — ChefKiss's QEMU fork that emulates Apple's T8030 (A13) platform well enough to boot iOS 14. The emulator is built as a library and loaded into the app, so there is no helper process, no server, and nothing to connect to: the machine runs inside the app that draws it.
Inferno itself is by Visual Ehrmanntraut and the Inferno
team at ChefKiss. This repository is the iOS app around it, plus
the changes to the emulator that iOS made necessary. It is unofficial, and not affiliated with
or endorsed by ChefKiss.
Found a bug, or have an idea? Open an issue, in English or Russian. Anything goes — see Issues and ideas.
What works
- The guest's screen, drawn from the emulator's own framebuffer memory — no VNC, no encoding,
- Device buttons — side button, volume, home.
- Internet in the guest, with no companion VM. The app is its own USB host: it switches the
apt works.
- File transfer both ways, over a spare NVMe namespace the guest reads as raw bytes — megabytes
- Apps installed with one tap. Install an .ipa in the guest unpacks it on the phone, carries
- An app catalogue of things an iOS 14 guest can actually run: DolphiniOS, Taurine, iSH and
- A package manager on the phone. Cydia repositories — bingner's, BigBoss, Chariz, Havoc and
.deb goes into the guest. It knows what is
installed, and can reinstall or remove it.
- Cydia that works. A restored image breaks it three ways at once — the root is read-only, dpkg
cydo never becomes root. The app gives Cydia a root helper in its place, and repairs the part a
guest reboot undoes on every start.
- The phone's battery in the guest — the charge, and the bolt while it charges — or a figure of
- A network in the guest's status bar: Wi-Fi or cellular as the phone has it, or whatever you
- The phone's time zone in the guest. The guest's clock is right, but its image comes with a
- An agent in the guest that takes the app's requests over the NVMe namespace instead of the
- A shell into the guest, waiting for the bootstrap's bash rather than giving up on a slow boot.
- A real terminal, not a log view: eighty columns, colours, cursor movement.
neofetchdraws
apt redraws its progress line in place.
- English and Russian, following the system's language unless told otherwise — the emulator's
What does not
- Speed. There is no KVM on iOS and there never will be — everything is translated. Boot takes
- Only 3 GiB. iOS kills a process at exactly that, entitlement or no entitlement, so the guest
- Only three panel sizes — 828×1792, 752×1336 and 640×1136. A row of the frame has to be a
- No App Store apps. Their binaries are encrypted with FairPlay, which only the real device that
- No dependency resolution. The package manager installs what you pick and shows dpkg's
.zst indexes do not open: iOS has nothing
to decompress them with.
- No sound yet. There is an experimental switch: the guest builds its speaker device, but the
- The guest sometimes drops its own network after using it — a known iOS behaviour, worked
What you need
A jailbroken guest image. Not distributed here, and not optional. Follow ChefKiss's guides to build one, then apply the jailbreak bootstrap:
The bootstrap is required, not a nicety. Without bash on the guest's console there is no file
transfer, no shell, and no way to bring the network up from inside. Half of what this app does
talks to that shell.
A device that can run the app with JIT. The translator has to make memory executable, which iOS does not allow on its own. StikDebug provides it.
About 9 GB free for the guest image.
Installing
- Build the guest image by ChefKiss's guide, and apply the jailbreak patches.
- Get
Inferno.ipafrom Releases, or build
StikDebug also needs LocalDevVPN, which is on the App Store. It is not a VPN in the usual sense — it is what lets the debugger reach the device over its own loopback.
- Set StikDebug up: turn LocalDevVPN on, and put your pairing file into StikDebug (iLoader can
- Assign the script, then launch Inferno through StikDebug, so it starts with JIT. Before
legacy.js.
Then launch it, and check that the app's folder has appeared in Files → On My iPhone → Inferno.
- Copy the guest images in. Put
AppleSEPROM-Cebu-B1and theInfernoDatafolder into that
AppleSEPROM-Cebu-B1
InfernoData/root.qcow2 (or root — the raw image)
InfernoData/firmware
InfernoData/syscfg
InfernoData/ctrl_bits
InfernoData/nvram
InfernoData/effaceable
InfernoData/panic_log
InfernoData/sep_nvram
InfernoData/sep_ssc
InfernoData/root_ticket.der
InfernoData/sep-firmware.n104.RELEASE.new.img4
InfernoData/Restore/kernelcache.release.iphone12b
InfernoData/Restore/Firmware/038-44135-124.dmg.trustcache
InfernoData/Restore/Firmware/all_flash/DeviceTree.n104ap.im4p
> Take root.qcow2, not the raw root. The raw image is nominally 34 GB holding about 9 GB
> of data, and it relies on the file being sparse — which copying to a phone loses. The qcow2 is
> its real size however you move it.
- Relaunch the app through StikDebug, open the menu and start the machine.
Using it
The button in the corner opens everything: the view (screen or terminal), starting and stopping the machine, patches and packages, files, device buttons. Drag it wherever you like.
Network. On by default. If the guest never takes an address, the menu has *Bring the network up
in the guest*, which runs ipconfig set en0 DHCP on its console — the same thing the stock guide
tells you to type by hand.
Files. Send a file to the guest puts it where the guest's own Files app will find it.
Fetch a file from the guest takes a path and saves it into the app's Guest folder, visible in
Files on the host phone. Install an .ipa in the guest installs an app from a file on the phone.
Apps and packages are under Patches. App catalogue and Package manager download on the
phone and install into the guest; Install a .deb into the guest takes a package you already have.
Restart SpringBoard is what Cydia calls a respring. Repair the package manager is the slow half
of fixing Cydia, needed once per image — firmware.sh and configuring the bootstrap's packages —
and can take minutes. The quick half runs by itself on every start, once the guest's shell is up;
Settings → Machine can turn that off.
The shell. The terminal's Shell tab opens a shell over the guest's console, with its output marked so the kernel's cannot be mistaken for it, and waits for the bootstrap's bash to come up — on a phone that can be minutes. When the kernel log is too noisy to share the console with, *Over the network* opens it over a socket instead.
The guest's battery, status bar and time zone are in Settings: the battery follows the phone or holds a figure you set, the status bar shows the phone's network, one you make up, or is left alone, and the time zone follows the phone unless you turn that off to pick one in the guest's own Settings.
Settings hold everything else: cores, memory, translator, screen, terminal, network, language, diagnostics, and the credits.
Building
You need a Mac with Xcode — this is built with Xcode 27.0 (27A5237l) and the iOS 27 SDK; older versions have not been tried — and the emulator's dependencies built for arm64 iOS into one prefix: glib, pixman, libslirp, libucontext, lzfse, libpng, gmp, nettle and libtasn1.
The emulator lives in its own repository — a fork of Inferno carrying the changes iOS needed: the USB-NCM host, the built-in display, the coalesced UART, the address-space memory patch, the extra NVMe namespace in the device tree. Clone it beside this one:
git clone -b ios https://github.com/MakrSas/Inferno.git inferno-src
Meson cross-files hold absolute paths — the SDK, the toolchain, wherever the dependencies were built — so this one is generated rather than committed:
PREFIX=path/to/built/deps ./make-cross-file.sh
Then build the emulator as a shared library for iOS:
meson setup build/inferno inferno-src \
--cross-file=cross-ios-arm64.txt \
-Dbuildtype=release -Dprefix=$PWD/prefix \
-Dshared_lib=true -Db_staticpic=true -Dwerror=false \
-Dkvm=disabled -Dhvf=disabled -Dwhpx=disabled \
-Dcocoa=disabled -Dgtk=disabled -Dsdl=disabled -Dcurses=disabled \
-Dcoreaudio=disabled -Dcurl=disabled -Dlibssh=disabled -Dbzip2=disabled \
-Dvnc=enabled -Dvnc_jpeg=disabled -Dvnc_sasl=disabled \
-Dtools=disabled -Dcoroutine_backend=ucontext
ninja -C build/inferno libqemu-aarch64-softmmu.dylib
-Dshared_lib=true is what makes it a library rather than a program; ucontext replaces the
coroutine backend because iOS has no usable sigaltstack. VNC stays enabled — the app draws from
the framebuffer directly, but the VNC path is still there as a fallback in the settings.
Then the app:
cd app && ./build.sh
build.sh compiles the SwiftUI front end with swiftc, bundles the emulator library, renders the
app icon with actool, ad-hoc signs everything and produces Inferno.ipa. Besides Xcode's command
line tools it needs QEMU's keymaps — brew install qemu provides them, or point INFERNO_KEYMAPS
at a copy. There is no Xcode project.
It also wants ldid (brew install ldid), to sign the small programs the app carries into the
guest with the entitlements they need. Without it the app still builds, but leaves them out — and
with them the fast file channel, the agent and the status bar.
Without a Mac — GitHub Actions
.github/workflows/build-ipa.yml does all of the above on
GitHub's own macOS runners, so a fork can produce an installable (unsigned) Inferno.ipa without
anyone owning a Mac. It runs only when started by hand from the Actions tab (workflow_dispatch);
the .ipa is attached to the run as an artifact. Release builds are made on a Mac, not here.
The nine iOS dependencies are built from source by
scripts/build-ios-deps.sh (which also works locally:
PREFIX=$PWD/prefix scripts/build-ios-deps.sh) and the resulting prefix/ is cached, keyed on that
script and the runner's Xcode, so only the first run pays the ~20-minute cost. The workflow still
does not touch any guest image, firmware or Apple key — those are yours to build.
The runner is macos-26, so actool there compiles Inferno.icon (Icon Composer, Xcode 26+)
directly — the built .ipa gets the real Liquid Glass icon, not a placeholder. app/build.sh
still probes actool before using it and falls back to app/Resources/Assets.xcassets — a flat
PNG rendering of the same artwork — if it ever runs on an older Xcode.
Issues and ideas
Open an issue for anything at all:
- Something broke — a crash, a hang, a black screen, a guest that will not boot or will not get
- Something could be better — anything awkward, slow, confusing, or missing from the menu.
- Something new — a feature you would like to see, even a half-formed one.
emulator.log — and emulator.prev.log, the run before it, in case you restarted the app to see
what went wrong — and guest-console.log in its folder in Files; attach them if you can.
Pull requests are welcome too.
Helping out
Most of what was learned here is written down, because most of it was expensive to learn. The notes are in Russian, the language the work was done in.
TODO.md— everything still open, with the reasoning behind each item and what was
FINDINGS.md— how the SEP panic was actually diagnosed. Worth reading not for
TROUBLESHOOTING.md— the traps, and there are many. The console has no
"\r\n" is one
Character in Swift, and its asciiValue is a line feed.
ANDROID-PORT.md— what the same app on Android would take. Android is the
HAPTICS-NOTES.md— passing the guest's vibration through to the real
The test stand
Trying something against the guest on the phone costs a boot of several minutes. macos-repro.sh
runs the same machine natively on a Mac against a qcow2 overlay, so a hypothesis costs a minute and
leaves the real image untouched:
INFERNO_SRC=path/to/fork INFERNO_STAGE=path/to/images \
./macos-repro.sh mytest fresh on "some marker" 120
The tools
netlab/ holds what the USB and network work was built out of, in Python, small enough to read:
tcpusb.py speaks the emulator's URB protocol, ncm.py brings a CDC-NCM link up by hand,
lockdown.py and pair.py do usbmux and pairing, slice.py and splice.py take an APFS container
out of a disk image and put it back. guestfs.py moves files over the console alone, no network.
install-bootstrap.sh needs sudo and rewrites somebody's guest image. Read it before running it.
Credits and licence
Inferno is by Visual Ehrmanntraut and the Inferno team at ChefKiss. QEMU is the work of very many people. This app stands entirely on both — if it is useful to you, consider supporting ChefKiss.
This project is unofficial and is not affiliated with or endorsed by ChefKiss.
The app is under GPL-3.0; see LICENSE. The emulator keeps Inferno's terms: GPL-3.0
as a whole, with ChefKiss's own code under AGPL-3.0, and the fork's changes under the same.
Inferno's boot splash artwork belongs to ChefKiss and is not covered by those licences, so builds from here leave it out, and the screen stays dark until the guest draws.
No Apple firmware, image or key is distributed here, and none ever will be.