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.
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:
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:
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/batterylow1@3x~iphone.im4p
inflating: Firmware/all_flash/batterycharging0@3x~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/batteryfull@3x~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/applelogo@3x~iphone.im4p
inflating: Firmware/all_flash/recoverymode@1920~iphone-lightning.im4p
creating: Firmware/dfu/
inflating: Firmware/dfu/iBSS.n56.RELEASE.im4p.plist
inflating: Firmware/all_flash/glyphplugin@1920~iphone-lightning.im4p
inflating: Firmware/all_flash/batterylow0@3x~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/batterycharging1@3x~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-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.
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:
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.