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

Edit (July 2020): This project has greatly evolved since its first release. Now, the kernel is patched to bypass the Secure Monitor and the Core Trust mechanisms. We decided to leave this blog post unchanged for educational purposes. To see the current status of the project, please visit our GitHub repository.

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:

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:

Current Limitations

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

Tutorial

To start the process we first need to prepare a kernel 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:

Next, we need to clone the supporting scripts repository:

Downloads jonathanafek$ git clone [email protected]:alephsecurity/xnu-qemu-arm64-tools.git Cloning into 'xnu-qemu-arm64-tools'... 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$ python3 xnu-qemu-arm64-tools/bootstrap_scripts/asn1kerneldecode.py kernelcache.release.n66 kernelcache.release.n66.asn1decoded

This decoded image now includes the lzss compressed kernel. You can use this code to decompress it or use this translated code with python2.

Downloads jonathanafek$ python2 xnu-qemu-arm64-tools/bootstrap_scripts/decompress_lzss.py kernelcache.release.n66.asn1decoded kernelcache.release.n66.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$ python3 xnu-qemu-arm64-tools/bootstrap_scripts/asn1dtredecode.py Firmware/all_flash/DeviceTree.n66ap.im4p Firmware/all_flash/DeviceTree.n66ap.im4p.out

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

Downloads jonathanafek$ python3 xnu-qemu-arm64-tools/bootstrap_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 048-31952-103.dmg :

Downloads jonathanafek$ hdiutil attach ./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/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$ touch ./tchashes 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 >> ./tchashes

Create the static trust cache blob:

Downloads jonathanafek$ python3 xnu-qemu-arm64-tools/bootstrap_scripts/create_trustcache.py tchashes static_tc

Now is a good time to unmount both volumes.

Downloads jonathanafek$ hdiutil detach /Volumes/PeaceB16B92.arm64UpdateRamDisk Downloads jonathanafek$ hdiutil detach /Volumes/PeaceB16B92.N56N66OS

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 --disable-pie --disable-slirp 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 tests/fp/berkeley-testfloat-3 tests/fp/berkeley-softfloat-3 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 python3 -B (3.7.6) slirp support no 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 SDL image support no GTK support no GTK GL support no VTE support no TLS priority NORMAL GNUTLS support yes libgcrypt no nettle yes (3.4.1) XTS no libtasn1 yes PAM yes iconv support yes curses support no 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 yes VNC PNG support yes xen support no brlapi support no bluez support no Documentation no PIE no vde support yes 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 PVRDMA 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 yes vhost-crypto support yes vhost-scsi support no vhost-vsock support no vhost-user support yes vhost-user-fs support yes Trace backends log spice support no rbd support no xfsctl support no smartcard support no libusb yes 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 libssh support no QOM debugging yes Live block migration yes lzo support no snappy support no bzip2 support yes lzfse support no NUMA host support no libxml2 yes tcmalloc support no jemalloc support no avx2 optimization no replication support yes VxHS block device no bochs support yes cloop support yes dmg support yes qcow v1 support yes vdi support yes vvfat support yes qed support yes parallels support yes sheepdog support yes capstone no libpmem support no libudev no default devices yes plugin support no cross containers 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,ramdisk-filename=048-32651-104.dmg.out,tc-filename=static_tc,kern-cmd-args="debug=0x8 kextlog=0xfff cpus=1 rd=md0 serial=2",xnu-ramfb=off -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

Make this boot much faster, without a long suspension before mounting the ram disk. 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. 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. Add support for devices such as screen, touch, wifi, BT, etc… 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:

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:

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.