Apple has been fighting for a secure iPhone since 2007, when the first jailbreaks came out about two weeks after the phone was released. Since then, they’ve gotten quite good at keeping the jailbreak community on the defensive side of this cat and mouse game, and hardened their OS to an impressive degree. Nonetheless, as we see every release, there are still vulnerabilities and tomhackery to be had. Among the most notable recent exploits, iOS 9 was patched for a WebKit memory corruption vulnerability that was used to deploy the Trident / Pegasus surveillance kit on selected nation state targets, and Google Project Zero recently announced plans to release a jailbreak for iOS 10.1 after submitting an impressive number of critical vulnerabilities to Apple (props to Ian Beer, who should be promoted to wizard).
I’ve been thinking about ways to harden iOS against jailbreaks, and came up with three recommendations that would up the game considerably for attackers. Two of them involve leveraging the Secure Enclave, and one is an OS hardening technique.
Security is about increasing the cost and time it takes to penetrate a target. These recommendations are designed to do just that: They’d greatly frustrate and upset current ongoing jailbreak and malware efforts.
Frustrating Delivery and Persistence using MAC Policy Framework
The MAC Policy Framework (macf) is a kernel-level access control framework originally written into TrustedBSD, and made its way into the xnu kernel, used by iOS and macOS. It’s used for sandboxing, SIP, and other security functions. The MAC (mandatory access control) framework provides granular controls over many aspects of the file system, processes, memory, sockets, and other operations performed within the running OS. It’s also a component of the kernel I’ve spent a lot of time researching for Little Flocker.
Rooting iOS often requires getting kernel execution privileges, in which in most cases all bets are off – you can, of course, patch out macf hooks. But getting that delicious kernel execution can be especially tricky if you’re depending on an exploit chain that needs to perform tasks blocked by macf, before you get your kernel code off the ground. It also forces an attacker to increase the size and complexity of their payload in order to successfully disable it, all which take time and increase cost. Should an attacker still succeed in gaining kernel execution, digging up and patching out macf will leave sandboxes and a number of other iOS features broken, which jailbreaks want to leave intact. In short, it would require a much more complex and intricate attack to whack macf without screwing up the rest of the operating system.
For example, consider a kernel level exploit that requires an exploit chain that needs to write to the root file system, inject code into other processes (task_for_pid), abuse shared memory, or perform other tasks that can be stopped with a macf policy. If you can prevent that task_for_pid from ever happening, then that exploit chain might not be able to get off the ground to make the rest possible. Should the attack succeed in spite of this added security, you’ve now forced the attacker to go digging pretty deep in the kernel, find the right memory addresses to patch out macf, and invest a lot of time to be sure their jailbreak doesn’t completely break application sandboxing or other features, in order to become persistent. In other words, it takes a lot of work to break macf without also breaking the operating system. Adding some code to sandboxd to test macf would also be extra gravy; if macf is compromised and that causes sandboxd to completely break, the user is going to notice it and perhaps find their phone unusable (which is what you’d want if a device is compromised).
Apple understands that if you can keep an exploit chain from getting off the ground, you can frustrate attempts to gain kernel execution. For example, Apple mounts the root partition as read-only; it’s trivial to undo this, as is demonstrated by any jailbreak. All you need is root – not even kernel. But what about macf? Using the MAC Policy Framework can prevent any writes to the root file system at a kernel level, and can even prevent it from being mounted as read-write except by a software update. MAC is so well written that even once you’re in the kernel, opening a file (vnode_open) still invokes macf hooks; you’ll have to go in and patch all of those hooks out first in order to disable it. This means that lower down on your exploit chain, your root user won’t be able to gain persistence without first performing surgery in the kernel (and likely breaking the rest of the OS).
But wait, macf can do a heck of a lot more than just file control. Using macf, you can prevent unauthorized kexts from loading, you can prevent processes (like cycript and mobile substrate) from attaching to other processes (task_for_pid has hooks into macf), prevent what kernel driver providers a process can talk to; you can even prevent signals, IPC, sockets, and a lot more that could be used to exploit the OS… there’s a whole lot you can do to frustrate an exploit chain before it even gets off the ground by adding some carefully crafted macf policies into iOS that operate on the entire system, and not just inside the sandbox.
Apple has yet to take full advantage of what macf can do to defend against an exploit chain, but it could greatly frustrate an attack. Care would have to be taken, of course, to ensure that mechanisms like OTA software updates could still perform writes to the file system, Xcode could still attach to a pid when in developer mode, and other such tasks; this is trivial to do with macf.
Leveraging the SEP for Executable Page Validation
The Secure Enclave (SEP) has a DMA path to perform very high speed cryptography operations for the main processor. It’s also responsible for unwrapping class keys and performing a number of other critical operations that are needed in order to read user data. Leveraging the SEP’s cryptographic capabilities could be used to ensure that the state of executable memory has not been tampered with after boot.
The rootfs partition is read only and remains read only for the life of the operating system (that is, until it’s upgraded). Background tasks and other types of third party software don’t load until after the device has booted, and usually also authenticated. That means that somewhere in the boot process is a predictable machine state that is unique to the version of iOS running on it, at least as far as executable pages are concerned.
Whenever a new version of iOS is loaded onto the device, the update process could set a flag in the SEP (after the update is authenticated with Apple) so that on next reboot, the SEP will take a measurement of all the executable memory pages at a specific time when the state of the machine can be reliably reproduced. This snapshot would be taken likely after the OS has booted but before the user interface is presented. These measurements could include a series of hashes of each page marked executable in memory, or possibly other types of measurements that could be optimized. These measurements get stored in the SEP until the software is updated again or until the device is wiped. There are a few details to work around, such as ASLR, however should be quite feasible to maintain a cryptographic hash of executable memory pages, possibly even of contiguous pages.
Every time iOS boots after this, the same measurements are taken of all executable pages in memory. If a root kit has been made persistent, the pages should not match and the SEP could refuse to unlock class keys, which would leave the user at a “Connect to iTunes” screen or similar.
This technique may not work on some tethered jailbreaks that actively exploit the OS post-boot, but nobody really cares about those much anyway; the user is aware of them, root kits or malware can’t leverage those without the user’s knowledge, and the user is effectively crippling their phone to use a tethered jailbreak. It does, however, protect against code that gets executed or altered while the system is booting, including detecting kernel patches made in memory. An attacker would have to execute their exploit after the measurements are taken in order for the code to go unnoticed by the SEP.
Encrypt the Root Partition and Leverage the SEP’s File Keys
One final concept that takes control out of the hands of the kernel is to rely on the SEP to prevent files from being created on the root partition by encrypting the root partition with a quasi-like class key in a way that the SEP could refuse to wrap new file keys for files on the root partition. Presently, the file system’s keys are all stored in effaceable storage however if rooffs’ keys were treated as a kind of class key inside the SEP, the SEP could refuse to process any new file keys for that specific class short of a system update. Even should an exploit such as Trident be able to exploit the kernel, it theoretically shouldn’t be able to gain persistence in this scenario without also exploiting the Secure Enclave, as it couldn’t create any new files on the file system; it may also be possible to prevent file writes in the same way, with some changes.
Creating files on the root file system are typically needed by jailbreaks, and even Pegasus, to add launchd manifests, substrate libraries, or other such content into places where they will be recognized and loaded. Preventing this using the Secure Enclave would allow the root file system to remain in a pristine state, preventing unauthorized launch items, library injection, and so on.
The SEP is one of Apple’s most powerful weapons. Two of these three solutions recommend leveraging it as a means to enforce a predictable memory state and root file system. A third recommendation could lead to a more hardened operating system where an exploit chain could potentially become frustrated, and/or require a much more elaborate kernel attack in order to succeed.
These are things only Apple could implement, and from what is known about Apple as a company, they’re driven to tighten the security of their products.