<--

Running iOS in QEMU to an interactive bash shell (1): tutorial

By Jonathan Afek (@JonathanAfek)
June 17, 2019
*
*

While wanting to do some iOS security research and inspired by the work done by zhuowei, I decided to try and get this emulation project further along the boot process. The goal was to get the system to boot without having to patch the kernel beforehand or during the boot process, have new modules that extend QEMU’s capabilities to execute arm64 XNU systems and, get an interactive bash shell. This post is the first post in a 2-post series, in which I will present instructions for executing iOS on QEMU and launching an interactive bash shell. In the second post, I will detail some of the research that was required in order to get there. For this project, the iOS version and device that were chosen are iOS 12.1 and iPhone 6s Plus, because this specific iOS 12 image comes with a lot of symbols exported in the kernel image compared to other iOS kernel images that are usually stripped of most symbols. This presented some more challenges, because it is a non-KTRR device that uses a secure monitor image, and that required changes in the stages of the solution already done by zhuowei. Another change is that I wanted this feature to be in external modules that could later be extended and used to create modules for other iOS devices and versions, instead of having the code inside the core QEMU code.

Current Status

The project is now available at qemu-aleph-git with the required scripts at qemu-scripts-aleph-git. The current status allows booting to user mode with a read only mounted ram disk, to which new executables and launchd items can be added (before boot), and those can use the dyld cache from the main disk image copied to the ram disk and communicate with the user via the emulated UART channel. Here is a demonstration of running an interactive bash shell with this project:

iOS bash shell in QEMU

This lets you execute whatever user mode process you want as root, with whatever entitlements you choose, and debug the process and/or the kernel with a kernel debugger:

iOS QEMU gdb kernel debugger

Current Limitations

  1. There is a long suspension where nothing happens for a few seconds before mounting the ram disk.
  2. It only works with a ram disk image which is mounted as read only, and is limited to 2GB in size.
  3. We can only communicate with the guest iOS over UART, and no other communication channels are currently available.
  4. No devices emulation: screen, touch, wifi, BT or anything else.
  5. Only a single emulated CPU is currently supported.

Tutorial

To start the process we first need to prepare a kernel image, a secure monitor image, a device tree, a static trust cache, and ram disk images. To get the images we first need to get the update file from Apple: iOS 12.1 update file. This is actually a zip file which we can extract:

Downloads jonathanafek$ unzip iPhone_5.5_12.1_16B92_Restore.ipsw
Archive:  iPhone_5.5_12.1_16B92_Restore.ipsw
   creating: Firmware/
  inflating: Restore.plist           
   creating: Firmware/usr/
   creating: Firmware/usr/local/
  inflating: BuildManifest.plist     
  inflating: Firmware/Mav10-7.21.00.Release.plist  
   creating: Firmware/all_flash/
  inflating: Firmware/all_flash/DeviceTree.n66ap.im4p.plist  
  inflating: Firmware/all_flash/LLB.n56.RELEASE.im4p  
  inflating: Firmware/all_flash/[email protected]~iphone.im4p  
  inflating: Firmware/all_flash/[email protected]~iphone.im4p  
  inflating: Firmware/all_flash/LLB.n66.RELEASE.im4p  
  inflating: Firmware/all_flash/sep-firmware.n56.RELEASE.im4p.plist  
  inflating: Firmware/all_flash/iBoot.n56.RELEASE.im4p.plist  
  inflating: Firmware/all_flash/[email protected]~iphone.im4p  
  inflating: Firmware/all_flash/iBoot.n66m.RELEASE.im4p  
  inflating: Firmware/all_flash/iBoot.n56.RELEASE.im4p  
  inflating: Firmware/all_flash/DeviceTree.n66ap.im4p  
  inflating: Firmware/all_flash/sep-firmware.n66m.RELEASE.im4p.plist  
  inflating: Firmware/all_flash/[email protected]~iphone.im4p  
  inflating: Firmware/all_flash/[email protected]~iphone-lightning.im4p  
   creating: Firmware/dfu/
  inflating: Firmware/dfu/iBSS.n56.RELEASE.im4p.plist  
  inflating: Firmware/all_flash/[email protected]~iphone-lightning.im4p  
  inflating: Firmware/all_flash/[email protected]~iphone.im4p  
  inflating: Firmware/dfu/iBEC.n66m.RELEASE.im4p.plist  
  inflating: Firmware/dfu/iBSS.n66.RELEASE.im4p  
  inflating: Firmware/048-32459-105.dmg.trustcache  
  inflating: Firmware/dfu/iBSS.n66m.RELEASE.im4p  
  inflating: Firmware/dfu/iBEC.n56.RELEASE.im4p.plist  
  inflating: Firmware/all_flash/sep-firmware.n56.RELEASE.im4p  
  inflating: Firmware/Mav13-5.21.00.Release.bbfw  
  inflating: Firmware/all_flash/sep-firmware.n66m.RELEASE.im4p  
  inflating: Firmware/all_flash/LLB.n66m.RELEASE.im4p.plist  
  inflating: Firmware/all_flash/iBoot.n66.RELEASE.im4p.plist  
  inflating: Firmware/dfu/iBSS.n56.RELEASE.im4p  
  inflating: Firmware/all_flash/DeviceTree.n66map.im4p.plist  
  inflating: Firmware/all_flash/DeviceTree.n56ap.im4p.plist  
  inflating: Firmware/all_flash/LLB.n66.RELEASE.im4p.plist  
   creating: Firmware/AOP/
  inflating: Firmware/AOP/aopfw-s8000aop.im4p  
  inflating: Firmware/dfu/iBEC.n56.RELEASE.im4p  
  inflating: Firmware/all_flash/LLB.n66m.RELEASE.im4p  
  inflating: Firmware/all_flash/iBoot.n66.RELEASE.im4p  
  inflating: Firmware/all_flash/sep-firmware.n66.RELEASE.im4p  
  inflating: Firmware/048-31952-103.dmg.trustcache  
  inflating: Firmware/all_flash/sep-firmware.n66.RELEASE.im4p.plist  
  inflating: Firmware/dfu/iBSS.n66.RELEASE.im4p.plist  
  inflating: Firmware/all_flash/DeviceTree.n66map.im4p  
  inflating: Firmware/dfu/iBSS.n66m.RELEASE.im4p.plist  
  inflating: Firmware/all_flash/[email protected]~iphone.im4p  
  inflating: Firmware/all_flash/iBoot.n66m.RELEASE.im4p.plist  
  inflating: 048-32651-104.dmg       
  inflating: Firmware/all_flash/LLB.n56.RELEASE.im4p.plist  
  inflating: Firmware/dfu/iBEC.n66.RELEASE.im4p  
  inflating: Firmware/dfu/iBEC.n66.RELEASE.im4p.plist  
  inflating: Firmware/dfu/iBEC.n66m.RELEASE.im4p  
  inflating: kernelcache.release.iphone7  
  inflating: Firmware/048-32651-104.dmg.trustcache  
  inflating: Firmware/Mav13-5.21.00.Release.plist  
  inflating: Firmware/all_flash/DeviceTree.n56ap.im4p  
  inflating: Firmware/Mav10-7.21.00.Release.bbfw  
  inflating: 048-32459-105.dmg       
  inflating: kernelcache.release.n66  
 extracting: 048-31952-103.dmg       

Next, we need to clone the supporting scripts repository:

Downloads jonathanafek$ git clone [email protected]:alephsecurity/xnu-qemu-arm64-scripts.git
Cloning into 'xnu-qemu-arm64-scripts'...
remote: Enumerating objects: 16, done.
remote: Counting objects: 100% (16/16), done.
remote: Compressing objects: 100% (11/11), done.
remote: Total 16 (delta 4), reused 16 (delta 4), pack-reused 0
Receiving objects: 100% (16/16), 5.16 KiB | 5.16 MiB/s, done.
Resolving deltas: 100% (4/4), done.

And extract the ASN1 encoded kernel image:

Downloads jonathanafek$ python xnu-qemu-arm64-scripts/asn1kerneldecode.py kernelcache.release.n66 kernelcache.release.n66.asn1decoded

This decoded image now includes the compressed kernel and the secure monitor image. To extract both of them:

Downloads jonathanafek$ python xnu-qemu-arm64-scripts/decompress_lzss.py kernelcache.release.n66.asn1decoded kernelcache.release.n66.out
Downloads jonathanafek$ python xnu-qemu-arm64-scripts/kernelcompressedextractmonitor.py kernelcache.release.n66.asn1decoded securemonitor.out

Now let’s prepare a device tree which we can boot with (more details about the device tree in the second post). First, extract it from the ASN1 encoded file:

Downloads jonathanafek$ python xnu-qemu-arm64-scripts/asn1dtredecode.py Firmware/all_flash/DeviceTree.n66ap.im4p Firmware/all_flash/DeviceTree.n66ap.im4p.out

Then, parse it and modify it to make our kernel boot on QEMU:

Downloads jonathanafek$ python xnu-qemu-arm64-scripts/read_device_tree.py Firmware/all_flash/DeviceTree.n66ap.im4p.out Firmware/all_flash/DeviceTree.n66ap.im4p.out.mod

Now we have to set up the ram disk. First, ASN1 decode it:

Downloads jonathanafek$ python xnu-qemu-arm64-scripts/asn1rdskdecode.py ./048-32651-104.dmg ./048-32651-104.dmg.out

Next, resize it so it has room for the dynamic loader cache file (needed by bash and other executables), mount it, and force usage of file permissions on it:

Downloads jonathanafek$ hdiutil resize -size 1.5G -imagekey diskimage-class=CRawDiskImage 048-32651-104.dmg.out
Downloads jonathanafek$ hdiutil attach -imagekey diskimage-class=CRawDiskImage 048-32651-104.dmg.out
Downloads jonathanafek$ sudo diskutil enableownership /Volumes/PeaceB16B92.arm64UpdateRamDisk/

Now let’s mount the regular update disk image by double clicking on it: 048-31952-103.dmg

Create a directory for the dynamic loader cache in the ram disk, copy the cache from the update image and chown it to root:

Downloads jonathanafek$ sudo mkdir -p /Volumes/PeaceB16B92.arm64UpdateRamDisk/System/Library/Caches/com.apple.dyld/
Downloads jonathanafek$ sudo cp /Volumes/PeaceB16B92.N56N66OS/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64 /Volumes/PeaceB16B92.arm64UpdateRamDisk/System/Library/Caches/com.apple.dyld/
Downloads jonathanafek$ sudo chown root /Volumes/PeaceB16B92.arm64UpdateRamDisk/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64

Get precompiled user mode tools for iOS, including bash, from rootlessJB and/or iOSBinaries. Alternatively, compile your own iOS console binaries as described here.

Downloads jonathanafek$ git clone https://github.com/jakeajames/rootlessJB
Cloning into 'rootlessJB'...
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 253 (delta 2), reused 0 (delta 0), pack-reused 247
Receiving objects: 100% (253/253), 7.83 MiB | 3.03 MiB/s, done.
Resolving deltas: 100% (73/73), done.
Downloads jonathanafek$ cd rootlessJB/rootlessJB/bootstrap/tars/
tars jonathanafek$ tar xvf iosbinpack.tar
tars jonathanafek$ sudo cp -R iosbinpack64 /Volumes/PeaceB16B92.arm64UpdateRamDisk/
tars jonathanafek$ cd -

Configure launchd to not execute any services:

Downloads jonathanafek$ sudo rm /Volumes/PeaceB16B92.arm64UpdateRamDisk/System/Library/LaunchDaemons/*

And now, configure it to launch the interactive bash shell by creating a new file under /Volumes/PeaceB16B92.arm64UpdateRamDisk/System/Library/LaunchDaemons/com.apple.bash.plist with the following contents:

<?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>EnablePressuredExit</key>
        <false/>
        <key>Label</key>
        <string>com.apple.bash</string>
        <key>POSIXSpawnType</key>
        <string>Interactive</string>
        <key>ProgramArguments</key>
        <array>
                <string>/iosbinpack64/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>
</plist>

As a side note, you can always convert the binary plist files that you find natively in iOS images to text xml format and back to binary format with:

Downloads jonathanafek$ plutil -convert xml1 file.plist
Downloads jonathanafek$ vim file.plist
Downloads jonathanafek$ plutil -convert binary1 file.plist

For launch daemon, iOS accepts both xml and binary plist files.

Since the new binaries are signed, but not by Apple, they need to be trusted by the static trust cache that we will create. To do this, we need to get jtool (also available via Homebrew: brew cask install jtool). Once we have the tool, we have to run it on every binary we wish to be trusted, extract the first 40 characters of its CDHash, and put it in a new file named tchashes. A sample execution of jtool looks like this:

Downloads jonathanafek$ jtool --sig --ent /Volumes/PeaceB16B92.arm64UpdateRamDisk/iosbinpack64/bin/bash
Blob at offset: 1308032 (10912 bytes) is an embedded signature
Code Directory (10566 bytes)
                Version:     20001
                Flags:       none
                CodeLimit:   0x13f580
                Identifier:  /Users/jakejames/Desktop/jelbreks/multi_path/multi_path/iosbinpack64/bin/bash (0x58)
                CDHash:      7ad4d4c517938b6fdc0f5241cd300d17fbb52418b1a188e357148f8369bacad1 (computed)
                # of Hashes: 320 code + 5 special
                Hashes @326 size: 32 Type: SHA-256
 Empty requirement set (12 bytes)
Entitlements (279 bytes) :
--
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>platform-application</key>
    <true/>
    <key>com.apple.private.security.container-required</key>
    <false/>
</dict>
</plist>

In the above case, we need to write down 7ad4d4c517938b6fdc0f5241cd300d17fbb52418 in tchashes. For convenience, the following command will extract the correct part of the hash from each of the binaries we put in the image:

Downloads jonathanafek$ for filename in $(find /Volumes/PeaceB16B92.arm64UpdateRamDisk/iosbinpack64 -type f); do jtool --sig --ent $filename 2&>/dev/null; done | grep CDHash | cut -d' ' -f6 | cut -c 1-40
ebe945ddbb4dbeb1ee9624e6ba1932d2ec61cfde
7ad4d4c517938b6fdc0f5241cd300d17fbb52418
0cf1b00e3bf76ab51c56da7ca888e89359f1d1c4
c9c1e21c3f3593c99f4e7c91c64d7f3106ad29ce
522dda7f40fe6aa6e2038bc66c9cb31660a43429
dc040d340f1fcfb493394e77d9944aa164e23ca3
f975cd0eec230299d1b8d9b0e3b54ae7cf660d92
728be7f7a78f400742e887f7ac93306145f822c0
4f4ca5aa3e506d145f344d59504630b85ddefffc
0d274c72cefbff705db0ed0fda29fb6f4cacf4c9
ebcf9073fd59db7c59a5212b0824faf1d7b30e39
cf784ea216e6b49f66a3cc81aeceaf7ac39b71d7
9d625c7eaadc8fd3eb57d9facca294b1a5afab8a
90c02c153e636cac74ca09e7e3dc89c0508a1393
59cba1c5ce169d4cd454d43e3a3c6fa824cf2764
9ff1194d135e979a632033ec2df63ba0cfe4682a
d11b49576e0f6645c4c9f234497f51219173dce8
7a01f3e7bcda18b26297c3936c9e256ddf8f9fe3
b7fd47df9b6652f2810cc789d5903a082af2570d
68a32f0a35bbb23f4f272ca99186521618c08d21
e04fa65a33c4b69d2338688ee72ea13d624a4255
b400373e16a7f82fa56d318038ec7b4b28e2593f
65859385e11b910de3841e53a833ab4c4b855282
2eae1b42c4f6bb95e3226aff8cb93a539c0a6263
c305e094747ba274f37e3063b826a5e41e5e2549
41620d4632bf6f071388033f8cf267123df16489
3bf1f6c49e3bcd775041864085893bf9b1ab3870
bb2d9c166635fc693e99355e84984aa61692c6f3
3bb79fd3568c3620a2bd7bad004ab759bec4e331
7c60ae6060d7bf2772c6b4b0c04b605c4e62a7a7
b904a692d548c3323621c17212121aca0c733088
6fe1d88bcbdd97d273533d695c04279f8ddf5e32
4165a869f1b35bdff90b74116499c1c210f27ddb
414ebc5e48c94d60b2018e4c83a323426bc0ac74
62b2b303c31e5fc9d5210b736d8d632eee28d24f
871e0ea84b71cd01e45e261542e9b2dd08fb81ab
0912c647e222bd04f05b837a8286519bd8ae2393
bd6d7d7f51b639da99e0581096534273b4f040ed
27ed9a3b21392bc459619293a6b36fe2c3b8ddac
e92565cbfdb0bd41d069384689ffae715e61b216
164fc2d96f9decd643ac33fc279b2078e51f5c88
3e0529b705d666af4f25c8c18fc7992f6934cf6f
176f273cb276085052519054d042508dc8d562b4
18762f5c54d935759f02248b032576bdc93be260
22d2f02d3be49da4819534553ad5ac37c0ace28c
e76bf6e8e84b656ee61b1ff10b38eab23607ae82
84bbc455477d6737f738b649c5afd3d4a069abee
57fe14db863b48f19cdec3c884c5dfad1bff6a12
e6ee59194bd768c3e3cc140009b6a729c7700a11
f1c25d5ac4e3924deaa3418a9ba309e15c09f502
e962bfddead7da46f23b6f4dc448df085e946940
26d34ca63bc69c8e81c15672258f3b8cbaf4ba4c
7fc69d2fc1f57ca555b07d6de51c82f74915c6bd
85f3c5263835d90b776886f92e8536ceb2f46036
0f1214d8a6138f170c2654a6f81c40586fbebaac
dc995e91bc0b67c52b969c91c1d68b09bbf94ec2
5d46a9681b4a3cc84a69083288e76aa969ec3a43
3c0db01f7aaf0a5b935dfcc51f6b2534013795ad
8422f07e41b2951e4138b88e013eab5773ae52f7
f9c4cca6b141064b7ae97131ff3969386d624718
259733b48f2f4fa88ba4f2e5f519bd40a6a3750d
8e06a919d28c3c0376b1207981d70b3bda99b6bc
68cd528c435b417c6f0022a132d459fc25d6e039
d176fa07a7ea5bfe88b9d2d703f3c65b4298b2e6
30f3d6e1d00614a0a9e8e8a3d4f31b8c68066091
698587325d71b9d51c22ae26e0c2de8ca70f6dc8
ccf27e4d7b62f1f839cfb9d70340efd1a2b77532
928a02f17cef27a5528ae055a467a18528f2aff5
4d24ada94fa70d27a684867541266f264261ce36
ab3e7808ee41f4536ece24091d1f166c5f0e9b63
e492332b87adc07406503ca857b6f3e2a3f0625d
d121b2de1778563183087238c4675316176f159d
12fe31a31132f7c0bab2857c0b3ac3c71cdb9dae
d6bc5428d129dd76695519b9b7f201daa9eb87de
685660477e1f851a90ace593670e5288d2168a24
94a493c2909f8b563e0076956bec7a1941455ed3
13c2e0251ba0469f2e1ec3d61da61c664822c791
e6332fc916f9b06f4987ecbaa23bbf4fa374c68f
1f6f82bcc994a4559d891d3a9e187268632da0b9
f864bd7891b9a0970f3ea05f13f7769289e62803
ba84abbeb198b91cbefec678096c8fd17387657d
d537ff6ab7d2bf38b0f18e964ad3525f2761b535
1acf88c15c1a08b3387b62969a34a95196632932
345d3b92a7f8a11c0872ec9ec439b5a6a2ada104
067b54e23cd6bc5b007113929dc4e2d2868228b6
11794790670afe1b651ed838362bb955e1503706
973674b1cf5f51119fa655ad2393df3dee9f44cc
c59738382faa4b7f803359d0c92dd53d6479ffb8
e3285e8252c44404675876ae0104f02cdc36574c
41c139fa86a3e67d49566d11a7d1d14fe375b564
b52692291cc4d9c9f09bc0ba650904d889674218
65713ffe304718b3b6a8b710b7db0467e52ca5aa
f2e77f5600970036ffdd5a06067491c5799a2ebd
cb08034d4647f2cc921b62ea648a76b5635fcc13
a9fc0262a6925ec1c18b0bf627c04c60fa5b5ecf
3736f93cc5f88d138f58016fdce2c3c3af979c43
183cd29cea8ba53f6e5d28d87e37b0cc603106c6
cd0281c8fa808c3f0f0b74db8c262a6997f52d03
e3016edd7acfa4d24d2eacec4918f3018d9d2449
ddd943f2a4192b3eabbb0580c64ff23ea7c31387
e3285e8252c44404675876ae0104f02cdc36574c
41c139fa86a3e67d49566d11a7d1d14fe375b564
b52692291cc4d9c9f09bc0ba650904d889674218
8af0e498ca73e05155f10fe7c26cfbdd9762ff24
73657606cb288c85f909da3ec4b92d7f8819ae79
918a3cf30a9c9d6ee2872c670421e528883221ae
dcf5eeaefc7ec3e7a0166676f6ee564761f78bc6
994ada738587ba622bfe36b987e9bfa246ff3858
d6f9c9107eb6dc237040d18debd4244c3e4c1320
f0e0c6a7e5c4545bac0d9ebf7811997f5c7076ad
38a790a40cca659fb8a0942ba140aa07309a17aa
070472831955773d78c9f33aff696c0a67b06bda
4ca98aac5e3b9174beaa2e4175e33fdcddee6866
44bd100692ded0637a763d324490db7435216f8c
a28a364092033230a6045fd288cb503aedbdd072
bbbe8ea84bdc4f3004398895ee58979a55b744c0
a09ee84582821397aa68d81350ed07b9902d09cb
8f8f612996a91e4fb26deacf2c88b8eda42da7a2
504d7c5b0a0e72a3dc5177ec571f591f3dae2ade
c0b0dea10a283f9d904bad52c53e20b129ae278c
5b089432710347242dfb6ccfdfea6fc523d9fe60
40af3f97ae3dc743f638c82f4ed78bce13687c83
7b3d463b62ce306c86d88e7ec0e52964c073c223
580eb965a96782a1fd005bd8a27100abca8430e1
330efc667ea608575d863b10a41a73e49f31d1c6
5827c3ef16144d298fd04342fc7041dd3b20d35e
f9bce1706a98b2492750aaa977806549f7d010f7
eeeaeb163512c31c6462f41c6bc3b6a228224bee
2ae51c0fac8b5656ec91693e7f9846a9c4af8069
92c89c47a734cad1a36756155ea3043e406ae565
be0e71c532033d79d519951f0450cdca44f835c3
feff0ce891c71c69f581b19a70b30ffd4c407205
8b0f3f0c620f008d4b85b7aff69933d3aae6098e
296124c76c9f0201480678a012a1df2e6835c521
a1876907ad59843dc5ed1390c78c88698504b9d8
e3190fc3865f02092ab6725b25c485ea5c143e3b
8bbd9944ebc23ce2001a4837732ba082c040d0f4
6408ed0d9df71e7bdde2faa985e5c07911a43503
ca2b47f582135e00a9720215cc09881dd9b49b85
e7e478f2e7f9715d9b540c9f8d12993c83ece0c1
25ac265b51c484680decaf8903b0b3c12c5ff81c
5a37eb16c2eaba8dcb55d9edb3ba98a0ee09afd0

The above output should be saved in tchashes, and then we can create the static trust cache blob:

Downloads jonathanafek$ python xnu-qemu-arm64-scripts/create_trustcache.py tchashes static_tc

Now is a good time to unmount both volumes. We now have all the images and files prepared. Let’s get the modified QEMU code (more detailed info on the work done in QEMU will be in the second post in the series):

Downloads jonathanafek$ git clone [email protected]:alephsecurity/xnu-qemu-arm64.git
Cloning into 'xnu-qemu-arm64'...
remote: Enumerating objects: 377340, done.
remote: Total 377340 (delta 0), reused 0 (delta 0), pack-reused 377340
Receiving objects: 100% (377340/377340), 187.68 MiB | 5.32 MiB/s, done.
Resolving deltas: 100% (304400/304400), done.
Checking out files: 100% (6324/6324), done.

and compile it:

Downloads jonathanafek$ cd xnu-qemu-arm64
xnu-qemu-arm64 jonathanafek$ ./configure --target-list=aarch64-softmmu --disable-capstone
Install prefix    /usr/local
BIOS directory    /usr/local/share/qemu
firmware path     /usr/local/share/qemu-firmware
binary directory  /usr/local/bin
library directory /usr/local/lib
module directory  /usr/local/lib/qemu
libexec directory /usr/local/libexec
include directory /usr/local/include
config directory  /usr/local/etc
local state directory   /usr/local/var
Manual directory  /usr/local/share/man
ELF interp prefix /usr/gnemul/qemu-%M
Source path       /Users/jonathanafek/Downloads/xnu-qemu-arm64
GIT binary        git
GIT submodules    ui/keycodemapdb dtc
C compiler        cc
Host C compiler   cc
C++ compiler      c++
Objective-C compiler clang
ARFLAGS           rv
CFLAGS            -O2 -g
QEMU_CFLAGS       -I/opt/local/include/pixman-1 -I$(SRC_PATH)/dtc/libfdt -D_REENTRANT -I/opt/local/include/glib-2.0 -I/opt/local/lib/glib-2.0/include -I/opt/local/include -m64 -mcx16 -DOS_OBJECT_USE_OBJC=0 -arch x86_64 -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -Wstrict-prototypes -Wredundant-decls -Wall -Wundef -Wwrite-strings -Wmissing-prototypes -fno-strict-aliasing -fno-common -fwrapv  -Wno-error=address-of-packed-member -Wno-string-plus-int -Wno-initializer-overrides -Wexpansion-to-defined -Wendif-labels -Wno-shift-negative-value -Wno-missing-include-dirs -Wempty-body -Wnested-externs -Wformat-security -Wformat-y2k -Winit-self -Wignored-qualifiers -Wold-style-definition -Wtype-limits -fstack-protector-strong -I/opt/local/include -I/opt/local/include/p11-kit-1 -I/opt/local/include  -I/opt/local/include/libpng16 -I/opt/local/include
LDFLAGS           -framework Hypervisor -m64 -framework CoreFoundation -framework IOKit -arch x86_64 -g
QEMU_LDFLAGS      -L$(BUILD_DIR)/dtc/libfdt
make              make
install           install
python            python -B
smbd              /usr/sbin/smbd
module support    no
host CPU          x86_64
host big endian   no
target list       aarch64-softmmu
gprof enabled     no
sparse enabled    no
strip binaries    yes
profiler          no
static build      no
Cocoa support     yes
SDL support       no
GTK support       no
GTK GL support    no
VTE support       no
TLS priority      NORMAL
GNUTLS support    yes
GNUTLS rnd        yes
libgcrypt         no
libgcrypt kdf     no
nettle            yes (3.4.1)
nettle kdf        yes
libtasn1          yes
curses support    yes
virgl support     no
curl support      yes
mingw32 support   no
Audio drivers     coreaudio
Block whitelist (rw)
Block whitelist (ro)
VirtFS support    no
Multipath support no
VNC support       yes
VNC SASL support  yes
VNC JPEG support  no
VNC PNG support   yes
xen support       no
brlapi support    no
bluez  support    no
Documentation     yes
PIE               no
vde support       no
netmap support    no
Linux AIO support no
ATTR/XATTR support no
Install blobs     yes
KVM support       no
HAX support       yes
HVF support       yes
WHPX support      no
TCG support       yes
TCG debug enabled no
TCG interpreter   no
malloc trim support no
RDMA support      no
fdt support       git
membarrier        no
preadv support    no
fdatasync         no
madvise           yes
posix_madvise     yes
posix_memalign    yes
libcap-ng support no
vhost-net support no
vhost-crypto support no
vhost-scsi support no
vhost-vsock support no
vhost-user support yes
Trace backends    log
spice support     no
rbd support       no
xfsctl support    no
smartcard support no
libusb            no
usb net redir     no
OpenGL support    no
OpenGL dmabufs    no
libiscsi support  no
libnfs support    no
build guest agent yes
QGA VSS support   no
QGA w32 disk info no
QGA MSI support   no
seccomp support   no
coroutine backend sigaltstack
coroutine pool    yes
debug stack usage no
mutex debugging   no
crypto afalg      no
GlusterFS support no
gcov              gcov
gcov enabled      no
TPM support       yes
libssh2 support   no
TPM passthrough   no
TPM emulator      yes
QOM debugging     yes
Live block migration yes
lzo support       no
snappy support    no
bzip2 support     yes
NUMA host support no
libxml2           yes
tcmalloc support  no
jemalloc support  no
avx2 optimization no
replication support yes
VxHS block device no
capstone          no
docker            no

xnu-qemu-arm64 jonathanafek$ make -j16
xnu-qemu-arm64 jonathanafek$ cd -

And all there’s left to do is execute:

Downloads jonathanafek$ ./xnu-qemu-arm64/aarch64-softmmu/qemu-system-aarch64 -M iPhone6splus-n66-s8000,kernel-filename=kernelcache.release.n66.out,dtb-filename=Firmware/all_flash/DeviceTree.n66ap.im4p.out.mod,secmon-filename=securemonitor.out,ramdisk-filename=048-32651-104.dmg.out,tc-filename=static_tc,kern-cmd-args="debug=0x8 kextlog=0xfff cpus=1 rd=md0 serial=2" -cpu max -m 6G -serial mon:stdio
iBoot version:
corecrypto_kext_start called
FIPSPOST_KEXT [38130750] fipspost_post:156: PASSED: (6 ms) - fipspost_post_integrity
FIPSPOST_KEXT [38201250] fipspost_post:162: PASSED: (2 ms) - fipspost_post_hmac
FIPSPOST_KEXT [38233562] fipspost_post:163: PASSED: (0 ms) - fipspost_post_aes_ecb
FIPSPOST_KEXT [38275375] fipspost_post:164: PASSED: (1 ms) - fipspost_post_aes_cbc
FIPSPOST_KEXT [41967250] fipspost_post:165: PASSED: (153 ms) - fipspost_post_rsa_sig
FIPSPOST_KEXT [44373250] fipspost_post:166: PASSED: (99 ms) - fipspost_post_ecdsa
FIPSPOST_KEXT [44832437] fipspost_post:167: PASSED: (18 ms) - fipspost_post_ecdh
FIPSPOST_KEXT [44861312] fipspost_post:168: PASSED: (0 ms) - fipspost_post_drbg_ctr
FIPSPOST_KEXT [44922625] fipspost_post:169: PASSED: (2 ms) - fipspost_post_aes_ccm
FIPSPOST_KEXT [44994250] fipspost_post:171: PASSED: (2 ms) - fipspost_post_aes_gcm
FIPSPOST_KEXT [45042125] fipspost_post:172: PASSED: (1 ms) - fipspost_post_aes_xts
FIPSPOST_KEXT [45109687] fipspost_post:173: PASSED: (2 ms) - fipspost_post_tdes_cbc
FIPSPOST_KEXT [45167062] fipspost_post:174: PASSED: (1 ms) - fipspost_post_drbg_hmac
FIPSPOST_KEXT [45178250] fipspost_post:197: all tests PASSED (300 ms)
Darwin Image4 Validation Extension Version 1.0.0: Tue Oct 16 21:46:27 PDT 2018; root:AppleImage4-1.200.18~1853/AppleImage4/RELEASE_ARM64
AppleS8000IO::start: chip-revision: A0
AppleS8000IO::start: this: <ptr>, TCC virt addr: <ptr>, TCC phys addr: 0x202240000
AUC[<ptr>]::init(<ptr>)
AUC[<ptr>]::probe(<ptr>, <ptr>)
AppleCredentialManager: init: called, instance = <ptr>.
ACMRM: init: called, ACMDRM_ENABLED=YES, ACMDRM_STATE_PUBLISHING_ENABLED=YES, ACMDRM_KEYBAG_OBSERVING_ENABLED=YES.
ACMRM: _loadRestrictedModeForceEnable: restricted mode force-enabled = 0 .
ACMRM-A: init: called, .
ACMRM-A: _loadAnalyticsCollectionPeriod: analytics collection period = 86400 .
ACMRM: _loadStandardModeTimeout: standard mode timeout = 259200 .
ACMRM-A: notifyStandardModeTimeoutChanged: called, value = 259200 (modified = YES).
ACMRM: _loadGracePeriodTimeout: device lock timeout = 3600 .
ACMRM-A: notifyGracePeriodTimeoutChanged: called, value = 3600 (modified = YES).
AppleCredentialManager: init: returning, result = true, instance = <ptr>.
AUC[<ptr>]::start(<ptr>)
virtual bool AppleARMLightEmUp::start(IOService *): starting...
AppleKeyStore starting (BUILT: Oct 17 2018 20:34:07)
AppleSEPKeyStore::start: _sep_enabled = 1
AppleCredentialManager: start: called, instance = <ptr>.
ACMRM: _publishIOResource: AppleUSBRestrictedModeTimeout = 259200.
AppleCredentialManager: start: initializing power management, instance = <ptr>.
AppleCredentialManager: start: started, instance = <ptr>.
AppleCredentialManager: start: returning, result = true, instance = <ptr>.
AppleARMPE::getGMTTimeOfDay can not provide time of day: RTC did not show up
: apfs_module_start:1277: load: com.apple.filesystems.apfs, v748.220.3, 748.220.3, 2018/10/16
com.apple.AppleFSCompressionTypeZlib kmod start
IOSurfaceRoot::installMemoryRegions()
IOSurface disallowing global lookups
apfs_sysctl_register:911: done registering sysctls.
com.apple.AppleFSCompressionTypeZlib load succeeded
L2TP domain init
L2TP domain init complete
PPTP domain init
BSD root: md0, major 2, minor 0
apfs_vfsop_mountroot:1468: apfs: mountroot called!
apfs_vfsop_mount:1231: unable to root from devvp <ptr> (root_device): 2
apfs_vfsop_mountroot:1472: apfs: mountroot failed, error: 2
hfs: mounted PeaceB16B92.arm64UpdateRamDisk on device b(2, 0)
: : Darwin Bootstrapper Version 6.0.0: Tue Oct 16 22:26:06 PDT 2018; root:libxpc_executables-1336.220.5~209/launchd/RELEASE_ARM64
boot-args = debug=0x8 kextlog=0xfff cpus=1 rd=md0 serial=2
Thu Jan  1 00:01:05 1970 localhost com.apple.xpc.launchd[1] <Notice>: Restore environment starting.
Thu Jan  1 00:01:05 1970 localhost com.apple.xpc.launchd[1] <Notice>: Early boot complete. Continuing system boot.
Thu Jan  1 00:01:06 1970 localhost com.apple.xpc.launchd[1] (com.apple.xpc.launchd.domain.system) <Error>: Could not read path: path = /AppleInternal/Library/LaunchDaemons, error = 2: No such file or directory
Thu Jan  1 00:01:06 1970 localhost com.apple.xpc.launchd[1] (com.apple.xpc.launchd.domain.system) <Error>: Could not read path: path = /System/Library/NanoLaunchDaemons, error = 2: No such file or directory
Thu Jan  1 00:01:06 1970 localhost com.apple.xpc.launchd[1] (com.apple.xpc.launchd.domain.system) <Error>: Failed to bootstrap path: path = /System/Library/NanoLaunchDaemons, error = 2: No such file or directory
bash-4.4# export PATH=$PATH:/iosbinpack64/usr/bin:/iosbinpack64/bin:/iosbinpack64/usr/sbin:/iosbinpack64/sbin
bash-4.4# id
uid=0(root) gid=0(wheel) groups=0(wheel),1(daemon),2(kmem),3(sys),4(tty),5(operator),8(procview),9(procmod),20(staff),29(certusers),80(admin)
bash-4.4# pwd
/
bash-4.4# ls -la
total 18
drwxr-xr-x  17 root    wheel  748 Jun 10  2019 .
drwxr-xr-x  17 root    wheel  748 Jun 10  2019 ..
-rw-r--r--   1 root    wheel    0 Oct 20  2018 .Trashes
drwx------   2 mobile  staff  170 Jun 10  2019 .fseventsd
drwxr-xr-x   4 root    wheel  136 Oct 20  2018 System
drwxr-xr-x   2 root    wheel  272 Oct 20  2018 bin
dr-xr-xr-x   3 root    wheel  660 Jan  1 00:01 dev
lrwxr-xr-x   1 root    wheel   11 Oct 20  2018 etc -> private/etc
drwxr-xr-x   7 root    wheel  374 Jun 10  2019 iosbinpack64
drwxr-xr-x   2 root    wheel   68 Oct 20  2018 mnt1
drwxr-xr-x   2 root    wheel   68 Oct 20  2018 mnt2
drwxr-xr-x   2 root    wheel   68 Oct 20  2018 mnt3
drwxr-xr-x   2 root    wheel   68 Oct 20  2018 mnt4
drwxr-xr-x   2 root    wheel   68 Oct 20  2018 mnt5
drwxr-xr-x   2 root    wheel   68 Oct 20  2018 mnt6
drwxr-xr-x   2 root    wheel   68 Oct 20  2018 mnt7
drwxr-xr-x   4 root    wheel  136 Oct 20  2018 private
drwxr-xr-x   2 root    wheel  510 Oct 20  2018 sbin
drwxr-xr-x   9 root    wheel  306 Oct 20  2018 usr
lrwxr-xr-x   1 root    admin   11 Oct 20  2018 var -> private/var
bash-4.4# 

And we have an interactive bash shell! :)

Note that the last flag (-serial mon:stdio) will forward all shell combinations (such as Ctrl+C) to the shell. To shut down QEMU, close its (empty) window.

To get a kernel debugger, -S -s should be added to the QEMU command line, and then it’s possible to execute target remote :1234 in a gdb console which supports this architecture. More details on how to get this gdb and perform this can be found in here. You can also get the relevant gdb on OSX with mac ports, while adding the multiarch and python27 options to the gdb port.

Future Improvements

  1. Make this boot much faster, without a long suspension before mounting the ram disk.
  2. Add support to emulate the iOS as a USB device and communicate over usbmuxd. This will enable us to connect over SSH and therefore copy files using scp, have a more robust terminal, conduct security research for network protocols, use gdbserver to debug user mode applications and more.
  3. Add support for emulated physical storage to work with a r/w mounted disk, that won’t be a ram disk and will not be limited to 2GB.
  4. Add support for devices such as screen, touch, wifi, BT, etc…
  5. Add support for more iDevices and iOS versions.

Fun Feature

User applications get loaded at different addresses every boot because of ASLR, and can share virtual addresses with one another, so using a regular breakpoint on a static virtual address in gdb can be challenging, when debugging user mode applications. Therefore, I added another fun feature to help debug user mode applications in this kernel debugger. When QEMU encounters the HLT aarch64 instruction, it breaks in gdb just as if it was a gdb breakpoint, so all you have to do to debug user mode applications in the kernel debugger is to patch the application with an HLT instruction, using ghidra, for example: HLT patch in ghidra 1 HLT patch in ghidra 2 HLT patch in ghidra 3

and then sign it using jtool with any required entitlements:

Downloads jonathanafek$ ./jtool/jtool --sign --ent ent.xml --inplace bin

After that, you need to add the new CDHash to the tchashes file, and recreate the static trust cache.

Following that, gdb will break when it encounters the HLT instruction in the user mode application so we can debug the application in the kernel debugger: HLT break in gdb

Conclusion

If you have gotten this far, and are interested in learning about some of the research and some of the implementation details, then carry on and read the second blog post in the series, as well as the code, comments and commit messages. The second blog post in the series will be available soon, stay tuned.

Please let me know if you have any ideas, suggestions, or other comments below.