This doc details all the steps to build and run the emulator.
You will need a macOS system for some of the preparation steps.
git clone https://github.com/TrungNguyen1909/qemu-t8030-tools
pip3 install pyasn1
brew install libtasn1 meson ninja pixman jtool2 jq coreutils
sudo apt update
sudo apt install -y git libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev libtasn1-dev ninja-build build-essential cmake
Get jtool2 from the jtool2's official website.
There is a jtool2.ELF64
inside the package.
For Linux, follow the step below to install lzfse.
For macOS, you can install lzfse from Homebrew.
However, there are some problems building when Homebrew uses /opt
prefix.
If you have issues regarding LZFSE, try installing it from sources like below.
git clone https://github.com/lzfse/lzfse
cd lzfse
mkdir build; cd build
cmake ..
make
sudo make install
cd ..
git clone https://github.com/TrungNguyen1909/qemu-t8030
cd qemu-tt8030
mkdir build; cd build
../configure --target-list=aarch64-softmmu,x86_64-softmmu --disable-capstone --enable-lzfse
make -j$(nproc)
Download and unzip iPhone11,8,iPhone12,1_14.0_18A5351d_Restore.ipsw
wget https://updates.cdn-apple.com/2020SummerSeed/fullrestores/001-35886/5FE9BE2E-17F8-41C8-96BB-B76E2B225888/iPhone11,8,iPhone12,1_14.0_18A5351d_Restore.ipsw
mkdir iphone; cd iphone
unzip ../iPhone11,8,iPhone12,1_14.0_18A5351d_Restore.ipsw
export STRAP_URL=$(curl https://assets.checkra.in/loader/config.json | jq -r ".core_bootstrap_tar")
wget $STRAP_URL
mkdir strap
tar xf strap.tar.lzma -C strap
These steps are only needed if you want to add your own binaries to the ramdisk.
Note that for all the below steps might need to be run on macOS.
python3 qemu-t8030-tools/bootstrap_scripts/asn1rdskdecode.py 038-44087-125.dmg 038-44087-125.dmg.out
# resize
hdiutil resize -size 512M -imagekey diskimage-class=CRawDiskImage 038-44087-125.dmg.out
# mount
hdiutil attach -imagekey diskimage-class=CRawDiskImage 038-44087-125.dmg.out
# enable ownership
sudo diskutil enableownership /Volumes/AzulSeed18A5351d.arm64eUpdateRamDisk
# Copy system binaries
sudo rsync -av strap/ /Volumes/AzulSeed18A5351d.arm64eUpdateRamDisk
# LaunchDaemons
sudo rm /Volumes/AzulSeed18A5351d.arm64eUpdateRamDisk/System/Library/LaunchDaemons/*
sudo cp qemu-t8030/setup-ios/bash.plist /Volumes/AzulSeed18A5351d.arm64eUpdateRamDisk/System/Library/LaunchDaemons/
# unmount
hdiutil detach /Volumes/AzulSeed18A5351d.arm64eUpdateRamDisk
This step is no longer needed as we now patch AMFI
python3 qemu-t8030-tools/bootstrap_scripts/asn1trustcachedecode.py Firmware/038-44087-125.dmg.trustcache Firmware/038-44087-125.dmg.trustcache.out
python3 qemu-t8030-tools/bootstrap_scripts/dump_trustcache.py Firmware/038-44087-125.dmg.trustcache.out | grep cdhash | cut -d' ' -f2 > tchashes
for filename in $(find strap/ -type f); do jtool2 --sig $filename 2>/dev/null; done | grep CDHash | cut -d' ' -f6 | cut -c 1-40 >> ./tchashes
python3 qemu-t8030-tools/bootstrap_scripts/create_trustcache.py tchashes static_tc
This step is temporary as this is not enough to create a usable system. As such, some userspace daemons might fail to start.
To create a usable system, we need to do a restore; However, we haven't found a way to modify the virtual disk after restore yet.
So if you want to run your own binaries, either use the ramdisk environment, or follow the steps below.
hdiutil convert 038-44337-083.dmg -format UDRW -tgtimagekey diskimage-class=CRawDiskImage -o disk.1
mv disk.1.dmg disk.1
hdiutil attach -imagekey diskimage-class=CRawDiskImage disk.1
# enable ownership
sudo diskutil enableownership /Volumes/AzulSeed18A5351d.N104N841DeveloperOS
# mount with RW
mount -urw /Volumes/AzulSeed18A5351d.N104N841DeveloperOS
sudo newfs_apfs -v Preboot -o role=b -e -A disk3
sudo newfs_apfs -v Data -o role=d -e -A disk3
sudo mkdir -p /Volumes/AzulSeed18A5351d.N104N841DeveloperOS/private/preboot/000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000/usr/standalone/firmware
sudo mkdir -p /Volumes/AzulSeed18A5351d.N104N841DeveloperOS/private/var/hardware/FactoryData/System/Library/Caches/com.apple.factorydata
sudo rsync -av strap/ /Volumes/AzulSeed18A5351d.N104N841DeveloperOS
This step is no longer needed as we now patch AMFI
python3 qemu-t8030-tools/bootstrap_scripts/asn1trustcachedecode.py Firmware/038-44337-083.dmg.trustcache Firmware/038-44337-083.dmg.trustcache.out
python3 qemu-t8030-tools/bootstrap_scripts/dump_trustcache.py Firmware/038-44337-083.dmg.trustcache.out | grep cdhash | cut -d' ' -f2 > tchashes
for filename in $(find strap/ -type f); do jtool2 --sig $filename 2>/dev/null; done | grep CDHash | cut -d' ' -f6 | cut -c 1-40 >> ./tchashes
python3 qemu-t8030-tools/bootstrap_scripts/create_trustcache.py tchashes static_tc
Either use setup-ios/launchd.plist
, or customize it from iOS firmware as follows.
- Copy
/Volumes/AzulSeed18A5351d.N104N841DeveloperOS/System/Library/xpc/launchd.plist
to somewhere else to work with. - Convert to xml1 format:
plutil -convert xml1 /path/to/launchd.plist
- Use Xcode or your preferred xml editor
- Remove all entries in
LaunchDaemons
(may be optional) - Add an entry for bash in
LaunchDaemons
- Remove all entries in
<key>/System/Library/LaunchDaemons/bash.plist</key>
<dict>
<key>EnablePressuredExit</key>
<false/>
<key>Label</key>
<string>com.apple.bash</string>
<key>POSIXSpawnType</key>
<string>Interactive</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StandardErrorPath</key>
<string>/dev/console</string>
<key>StandardInPath</key>
<string>/dev/console</string>
<key>StandardOutPath</key>
<string>/dev/console</string>
<key>Umask</key>
<integer>0</integer>
<key>UserName</key>
<string>root</string>
</dict>
- Copy back
sudo cp /path/to/launchd.plist /Volumes/AzulSeed18A5351d.N104N841DeveloperOS/System/Library/xpc/launchd.plist
hdiutil detach /Volumes/AzulSeed18A5351d.N104N841DeveloperOS
./qemu-t8030/build/qemu-img create -f qcow2 nvme.1.qcow2 128G
./qemu-t8030/build/qemu-img create -f raw nvme.2 8M
./qemu-t8030/build/qemu-img create -f raw nvme.3 128K
./qemu-t8030/build/qemu-img create -f raw nvme.4 8K
./qemu-t8030/build/qemu-img create -f raw nvram 8K
./qemu-t8030/build/qemu-img create -f raw nvme.6 4K
./qemu-t8030/build/qemu-img create -f raw nvme.7 1M
Don't forget that -snapshot
can be used to prevent filesystem corruptions on reset.
-smp
can be set up to 6 CPUs.
This will put the device into Restore mode on the first run and boot to NAND after restore completed
qemu-t8030/build/qemu-system-aarch64 -s -M t8030,trustcache-filename=Firmware/038-44135-124.dmg.trustcache,ticket-filename=root_ticket.der \
-kernel kernelcache.research.iphone12b \
-dtb Firmware/all_flash/DeviceTree.n104ap.im4p \
-append "debug=0x14e kextlog=0xffff serial=3 -v" \
-initrd 038-44135-124.dmg \
-cpu max -smp 2 \
-m 4G -serial mon:stdio \
-drive file=nvme.1.qcow2,format=qcow2,if=none,id=drive.1 \
-device nvme-ns,drive=drive.1,bus=nvme-bus.0,nsid=1,nstype=1,logical_block_size=4096,physical_block_size=4096 \
-drive file=nvme.2,format=raw,if=none,id=drive.2 \
-device nvme-ns,drive=drive.2,bus=nvme-bus.0,nsid=2,nstype=2,logical_block_size=4096,physical_block_size=4096 \
-drive file=nvme.3,format=raw,if=none,id=drive.3 \
-device nvme-ns,drive=drive.3,bus=nvme-bus.0,nsid=3,nstype=3,logical_block_size=4096,physical_block_size=4096 \
-drive file=nvme.4,format=raw,if=none,id=drive.4 \
-device nvme-ns,drive=drive.4,bus=nvme-bus.0,nsid=4,nstype=4,logical_block_size=4096,physical_block_size=4096 \
-drive file=nvram,if=none,format=raw,id=nvram \
-device apple-nvram,drive=nvram,bus=nvme-bus.0,nsid=5,nstype=5,id=nvram,logical_block_size=4096,physical_block_size=4096 \
-drive file=nvme.6,format=raw,if=none,id=drive.6 \
-device nvme-ns,drive=drive.6,bus=nvme-bus.0,nsid=6,nstype=6,logical_block_size=4096,physical_block_size=4096 \
-drive file=nvme.7,format=raw,if=none,id=drive.7 \
-device nvme-ns,drive=drive.7,bus=nvme-bus.0,nsid=7,nstype=8,logical_block_size=4096,physical_block_size=4096 \
-monitor telnet:127.0.0.1:1235,server,nowait
Remove rd=md0
boot args from the below commands if you want to boot from NAND instead of ramdisk.
qemu-t8030/build/qemu-system-aarch64 -s -M t8030,trustcache-filename=static_tc,boot-mode=manual \
-kernel kernelcache.research.iphone12b \
-dtb Firmware/all_flash/DeviceTree.n104ap.im4p \
-append "debug=0x14e kextlog=0xffff serial=3 -v rd=md0 wdt=-1" \
-initrd 038-44087-125.dmg.out \
-cpu max -smp 1 \
-m 4G -serial mon:stdio \
-drive file=disk.1,format=raw,if=none,id=drive.1 \
-device nvme-ns,drive=drive.1,bus=nvme-bus.0,nsid=1,nstype=1,logical_block_size=4096,physical_block_size=4096 \
-drive file=nvram,if=none,format=raw,id=nvram \
-device apple-nvram,drive=nvram,bus=nvme-bus.0,nsid=5,nstype=5,id=nvram,logical_block_size=4096,physical_block_size=4096
Connect to the monitor at localhost:1235
and use the q
command, the shutdown
command is not yet supported.
As such, to avoid filesystem corruptions on the NAND, run this command after the wdog: reset system
(will appear when iOS reboots) and before the NAND mounts.
This requires another Linux VM to connect to an iOS VM.
Note that the USB-over-TCP Protocol will run on unix socket at /tmp/usbqemu
by default.
You can use any QEMU Linux VM. Example below uses Arch Linux installer ISO
./qemu-system-x86_64 -cdrom archlinux-2021.06.01-x86_64.iso -boot order=d -m 1024 -vga virtio -cpu qemu64 -device usb-ehci,id=ehci -device usb-tcp-remote,bus=ehci.0
Start an iOS QEMU instance, which will automatically connect to unix socket /tmp/usbqemu
.
From inside the Linux VM, you can access to the iOS VM over USB like a real device.
To restore iOS, you need a working Linux installation on QEMU. I use Arch Linux for this purpose. The installation guide can be found on their official guide
Here is my QEMU command to run the Linux VM:
./qemu-t8030/build/qemu-system-x86_64 -boot order=c -m 1024 -vga none -device virtio-vga,xres=640,yres=480 -cpu qemu64 -usb -device usb-ehci,id=ehci -device usb-tcp-remote,bus=ehci.0 -drive file=arch.qcow2 -monitor telnet:127.0.0.1:1236,server,nowait
First boot the Linux VM, then install usbmuxd
if it hasn't been installed.
DO NOT install idevicerestore
from your package manager.
Because you will need scp/sftp to transfer the ipsw, and also for convenince, below is my way of setting up SSH.
Run this command on the Linux VM
ssh -fN -R 10222:localhost:22 <host-user-name>@<host-ip-address>
and enter your HOST user password.
And then run this command on your host machine to connect to it
ssh root@localhost -p 10222
now enter your VM user password.
If you have an SSH server (i.e openssh-server) installed on the Linux VM, you will now have a shell on it.
To copy any file from your host to the VM:
scp /path/to/file scp://root@localhost:10222/
For idevicerestore
we need to clone and patch it
Run these commands on the Linux VM
git clone https://github.com/libimobiledevice/idevicerestore.git
cd idevicerestore
git apply /path/to/qemu-t8030-tools/libimobiledevice_patches/idevicerestore.patch
./autogen.sh
make
sudo make install
If the iOS version you are trying to restore is still signed, it is okay to use tsschecker to fetch the SHSH2 blobs. Then extract the data from the APImg4Ticket
field of the blob (plist) and save it at root_ticket.der
Otherwise, you can use my script to do that.
python3 qemu-t8030-tools/bootstrap_scripts/create_apticket.py n104ap BuildManifest.plist ticket.shsh2 root_ticket.der
A sample ticket is also provided in the bootstrap_scripts
folder for your ease.
DO NOT modify the root_ticket.der
until you restore again. It is required even after the restore completed.
With the root_ticket.der
and the ipsw inside the Linux VM. Start up the iOS emulator using the command from Auto boot
Then when you saw something like could not receive message
, run the following command in the Linux VM to start the restore process.
idevicerestore -P -d --erase --restore-mode -i 0x1122334455667788 iPhone11,8,iPhone12,1_14.0_18A5351d_Restore.ipsw -T root_ticket.der
After you type YES
to the prompt, the restore will start.
DO NOT let your computer sleep during this process.
If the restore completed successfully, the iOS VM will automatically reboot to NAND, otherwise, it will reboot to the ramdisk and attempt to restore again.
xcrun -sdk iphoneos clang -arch arm64 -mcpu=apple-a13 -o hello hello.c
Then sign the binary
codesign -f -s - hello
# attach image
hdiutil attach -imagekey diskimage-class=CRawDiskImage disk.1
# enable ownership
sudo diskutil enableownership /Volumes/AzulSeed18A5351d.N104N841DeveloperOS
# mount with RW
mount -urw /Volumes/AzulSeed18A5351d.N104N841DeveloperOS
Then copy the signed binary to image
sudo cp hello /Volumes/AzulSeed18A5351d.N104N841DeveloperOS/bin
Also copy the binary to the local strap
directory
cp hello strap/bin
This step is no longer needed as we now patch AMFI
# dump trustcache from firmware
python3 qemu-t8030-tools/bootstrap_scripts/dump_trustcache.py Firmware/038-44337-083.dmg.trustcache.out | grep cdhash | cut -d' ' -f2 > tchashes
# update trustcache with new binaries from strap
for filename in $(find strap/ -type f); do jtool2 --sig $filename 2>/dev/null; done | grep CDHash | cut -d' ' -f6 | cut -c 1-40 >> ./tchashes
# re-serialize updated trustcache
python3 qemu-t8030-tools/bootstrap_scripts/create_trustcache.py tchashes static_tc
Finally, unmount the firmware image - now with new binary inserted
hdiutil detach /Volumes/AzulSeed18A5351d.N104N841DeveloperOS