Chapter 10

Jailbreaking

If you followed all the examples in this book, you most probably have done your experiments and also your own research on a jailbroken iPhone. You have that in common with a large number of people, because nearly all iPhone security research is performed on jailbroken devices. However, for the majority of people, including the security community and iPhone security researchers, the inner workings of a jailbreak are completely unknown. Many people think of jailbreaks as black boxes that work — like magic — after they click a jailbreak button in their tool of choice. This is often because knowing the inner workings of a jailbreak is not required for the development of things they are working on, for example userland exploits.

But if you've ever wondered how the jailbreaking process works internally, this chapter will answer a lot of your questions.

After a short introduction of the different jailbreak types, we use the redsn0w jailbreak as an example, guiding you step by step through the jailbreak process happening on your device. This chapter also introduces you to the inner workings of the kernel patches applied by the jailbreak, so that you can learn which of these patches are actually required and which are optional.

Why Jailbreak?

People jailbreak their iOS devices for many reasons. Some of them want an open platform for which they can develop software, others like the idea of having total control over their devices, some require jailbreaks to install software like ultrasn0w to bypass cellular carrier locks, and some use jailbreaks to pirate iPhone applications.

Security researchers, on the other hand, are normally motivated to jailbreak their own iOS devices for other reasons. The fact that normal iPhones are locked down tightly and do not allow the execution of unsigned code is a big roadblock when it comes to evaluating the security of a system, or trying to discover security vulnerabilities within it.

Even with an iOS development account from Apple, code running on the iPhone is limited, due to the sandbox and other restrictions. For example, processes are not even allowed to execute other processes or to fork. Also, the sandbox stops researchers from tampering with other applications' files, and attaching a debugger to MobileSafari to debug it is simply not possible.

Although it is possible to detect the names of running processes from within a normal iPhone application, a user has no way to stop suspicious processes from running or to analyze what they are doing. Just remember the incident with GPS movement profiles that were stored on every iPhone due to a bug. This problem, which is also known as “locationgate,” would never have been found without the availability of a jailbreak.

Most importantly, the majority of the research that led to this book would not have been possible without the availability of public jailbreaks. You may be surprised to find that the majority of iPhone security researchers leave the whole work of jailbreaking to groups like the iPhone Dev Team or the Chronic Dev Team, and are merely users of their tools. However, jailbreaking iOS devices gets harder and harder with every new hardware and software revision, and therefore it is important for more people from the security community to help out the jailbreaking teams. We hope the rest of this chapter raises your appetite to participate in the development of jailbreaks in the future.

Jailbreak Types

Although people have been able to jailbreak their iPhones for many years across most of the different iOS versions, not all of these jailbreaks have offered the same set of features. The major reason for this is that the quality of a jailbreak depends — in large part — on the security vulnerabilities that can be found and used to break the restrictions enforced by the device. Naturally, vulnerabilities exploited once by a jailbreak will be known to Apple and usually fixed as soon as possible in the next revision of iOS. Therefore, nearly every new version of iOS requires a new set of vulnerabilities to jailbreak the device. However, sometimes vulnerabilities reside in the hardware and cannot be fixed by Apple with a simple software upgrade. They require a new set of hardware, which will take Apple a longer time to fix, because it requires releasing the next revision of iPhones or iPads.

Jailbreak Persistence

Depending on the vulnerabilities used for jailbreaking, the effects of a jailbreak might be persistent, or they might disappear the moment a device is switched off and on again. To describe these two kinds of jailbreaks, the jailbreak community coined the two terms tethered jailbreak and untethered jailbreak.

Tethered Jailbreaks

A tethered jailbreak is a jailbreak that disappears when a device is restarted. The jailbroken device requires some form of re-jailbreak after every reboot. This usually means it has to be connected to a computer, every time it is switched off and on again. Because of the USB cable required for this procedure, the use of the term tethered makes sense. However, the term is also used if the re-jailbreak does not require a USB connection, but does require a visit of a certain website or execution of a certain application.

If the vulnerability exploited is in some privileged code, a tethered jailbreak could consist of only a single vulnerability being exploited. An example for this is the limera1n bootrom exploit that is currently used for most of the iOS 4 and 5 jailbreaks. Another example would be an exploit against a vulnerability in the USB kernel driver of iOS. However, no such vulnerability or exploit is currently public.

If no such vulnerability or exploit is available, initial entry into the device might be accomplished through a vulnerability in an application with fewer privileges, such as MobileSafari. However, this alone would not be considered a jailbreak, because without an additional kernel exploit, it is not possible to disable all the security features.

So a tethered jailbreak consists of one exploit against privileged code, or one exploit against unprivileged code combined with another privilege escalation exploit.

Untethered Jailbreaks

Untethered jailbreak is the term coined for capitalizing on a persistent vulnerability that will not disappear by rebooting the device. It is untethered because it does not require a re-jailbreak each time the device is rebooted. It is, therefore, the better form of a jailbreak.

Naturally, an untethered jailbreak is much harder to accomplish because it requires vulnerabilities in very specific places in the bootchain. In the past, this was possible because very powerful vulnerabilities in the hardware were found that allowed for exploiting the device very early in the boot chain. But these vulnerabilities are now gone, and no vulnerabilities of the same quality seem to be on the horizon.

Because of this, untethered jailbreaks are often a combination of some form of tethered jailbreak used in conjunction with additional exploits that allow persisting on the device. The initial tethered jailbreak is then used to install the additional exploits on the root filesystem of the device. At least two additional exploits are required, because first arbitrary unsigned code must be executed and then privileges must be escalated to be able to patch the kernel.

The exact actions required to jailbreak a device completely will become obvious once you read through the following sections, which introduce you to the full picture.

Exploit Type

The location of a vulnerability impacts your access level to the device. Some allow low-level hardware access; others allow limited permissions inside the sandbox.

Bootrom Level

Bootrom-level vulnerabilities are the most powerful vulnerabilities from the point of view of a jailbreaker. The bootrom is contained inside the hardware of the iPhone and vulnerabilities in there cannot be fixed by pushing a software update. Instead, the vulnerabilities can be fixed only within the next hardware revision. In the case of the limera1n vulnerability, Apple did not produce new revisions of iPad 1 or iPhone 4, although the vulnerability was known long before the A5 devices, iPad 2 and iPhone 4S, hit the market.

Bootrom-level vulnerabilities are not only the most powerful because they cannot be fixed. They are also powerful because they allow you to replace or patch every piece of the whole bootchain, including the kernel's boot arguments. Also, because the exploit occurs very early in the bootchain, the exploit payload will have full access to the hardware. For example, it is possible to use the GID key of the AES hardware accelerator to decrypt IMG3 files, which allows decrypting new iOS updates.

iBoot Level

Vulnerabilities inside iBoot are nearly as powerful as vulnerabilities inside the bootrom when it comes to the features they can provide. These vulnerabilities have the downside that iBoot is not baked into the hardware and therefore they can be fixed by a simple software upgrade.

Aside from this, iBoot is still early enough in the bootchain that boot arguments can be given to the kernel, the kernel can be patched, or the hardware can be used directly to perform GID key AES operations.

Userland Level

Userland jailbreaks like JBME3 (http://jailbreakme.com) are based completely on vulnerabilities in userland processes. These processes run either with the permissions of the root user, if they are system processes; or with the permissions of a lesser privileged user like the mobile user, in case they are user applications. In both cases at least two exploits are required to jailbreak the device. The first exploit has to achieve arbitrary code execution, whereas the second exploit has to escalate privileges in a way that the kernel-based security restrictions are disabled.

In previous versions of iOS, code signing could be disabled from user space as long the exploited process was running as root. Nowadays, kernel memory corruption or kernel code execution is required to disable the code-signing enforcement.

Compared to bootrom and iBoot-level vulnerabilities, userland vulnerabilities are less powerful, because even if kernel code execution is possible, certain hardware features like the GID key of the AES accelerator are not accessible anymore. Also, userland vulnerabilities are easier for Apple to fix and remote userland vulnerabilities are often fixed very quickly by Apple, because they can also be used for drive by iPhone infection malware.

Understanding the Jailbreaking Process

This section looks at the inner workings of the redsn0w jailbreaking tool. It was developed by the iPhone Dev Team and you can download it from their site at http://blog.iphone-dev.org/. It is the most popular tool available right now for jailbreaking pre-A5 devices, because it supports the majority of iOS versions, is very easy to use, seems to be the most stable jailbreak, and comes for both Windows and OS X.

With redsn0w, jailbreaking is nothing more than clicking a few buttons and setting your iPhone into DFU (Device Firmware Upgrade) mode. It's easy enough that even novice users are tempted to jailbreak their iPhones. Figure 10.1 shows the welcoming screen of redsn0w.

Figure 10.1 redsn0w startup screen

10.1

After you click the Jailbreak button, redsn0w walks you through setting your iPhone into DFU mode and then, depending on the device you have attached, offers you a few different jailbreak features that you can select from. You simply select your choice (for example, multitasking gestures), click the Next button, and wait for redsn0w to do its work.

Although this is a very simple process from a user's point of view, many things are happening under the hood and no one really knows about them except for a few in the jailbreak community. After you read through the following sections, you will be one of those who know all about the inner workings of redsn0w.

All the information in the following sections has been extracted, with the permission of the author, from a decompiled version of the redsn0w jailbreak. Because A5 devices like the iPad 2 or the iPhone 4S do not have a publicly known bootrom vulnerability, any jailbreak of these devices must be userland level. However, this simply means the first two steps, exploiting the bootrom and booting a ramdisk, must be replaced by something like an exploit in MobileSafari and a kernel vulnerability. The rest of the jailbreaking process works the same.

Exploiting the Bootrom

The jailbreaking process starts with redsn0w using the limera1n DFU bootrom exploit to execute code at the highest privilege level possible. The vulnerability exploited is a heap-based buffer overflow in the USB DFU stack of the bootrom in pre-A5 devices. We will not discuss the specifics of this vulnerability here. If you are interested in this vulnerability, you can find a number of descriptions and source code to exploit it in various places like THEiPHONEWiKi: http://theiphonewiki.com/wiki/index.php?title=Limera1n_Exploit.

For our purposes, the only thing you need to know is that this exploit is used to patch the signature verification inside the bootrom code, which allows you to boot arbitrary ramdisks and patched versions of Low-Level-Bootloader (LLB), iBoot, and the kernel. Source code that performs exactly these actions was released by the Chronic Dev Team on GitHub (https://github.com/Chronic-Dev/syringe). If you want to write your own jailbreaking tool from the ground up, this is a good place to start, because the source code of redsn0w is not publicly available.

Booting the Ramdisk

redsn0w uses the limera1n exploit to boot the system using a patched kernel and a custom-prepared ramdisk. The kernel is patched with a number of jailbreak patches to allow the execution of unsigned code. However, it does not contain all the kernel patches you normally find in an untethered jailbroken system. The ramdisk is custom built on every execution, because depending on the switches a user sets while performing the jailbreak, different files will be created in the root directory of the ramdisk. The presence of these files is later detected by the jailbreak executable on the ramdisk, which decides what features of redsn0w should be activated. For example, the presence of a file called /noUntetherHacks will skip the installation of untethering exploits.

When the ramdisk is booted, the kernel executes the included /sbin/launchd binary from the ramdisk, which contains a small stub that initializes the jailbreak. This binary first mounts the root filesystem and the data partition into the system. Both will be mounted as readable and writable because of the required modifications. Eventually, an executable called jailbreak will take over and perform all of the following steps.

Jailbreaking the Filesystem

By default, the filesystem of an iPhone is split into two partitions. The first partition is the root filesystem, which contains the iOS operating system files and the set of standard applications like MobileMail or MobileSafari. In earlier iOS versions, the root filesystem was approximately the size of the files on the partition, with not much free space left. Nowadays the root filesystem is around 1 GB in size and has around 200 MB of free space left, although it is not supposed to be modified and therefore is mounted read-only by default. The rest of the device's storage space is allocated to the second partition, the data partition, which is mounted as readable and writable into the directory /private/var. This is configured by the /etc/fstab file on the root filesystem:

/dev/disk0s1 / hfs ro 0 1
/dev/disk0s2 /private/var hfs rw,nosuid,nodev 0 2

As you can see, the mount configuration for the data partition contains the flags nodev and nosuid. The nodev flag ensures that device nodes that might exist on the writable data partition, due to a filesystem-level attack, will be ignored. The nosuid flag tells the kernel to ignore the suid bit on executables within the data partition. The suid bit is used to mark executables that need to run as root, or generally as a different user than the one executing it. Both these flags are, therefore, an additional small line of defense inside iOS against privilege escalation exploits.

This default configuration is a problem for all jailbreaks, no matter whether bootrom-level or userland, because they usually require making modifications to the root filesystem, for example to survive reboots or add additional daemons and services. The first action of each jailbreak after acquiring root permissions is, therefore, to (re-)mount the root filesystem as readable and writable. To persist this change across reboots, the next step is to replace the system's /etc/fstab file with something like this:

/dev/disk0s1 / hfs rw 0 1
/dev/disk0s2 /private/var hfs rw 0 2

This new filesystem configuration loads the root filesystem as readable and writable and removes the nosuid and nodev flags from the mount configuration of the second partition.

Installing the Untethering Exploit

Every time a new version of iOS comes out, previously known vulnerabilities are closed. Therefore, there is a limited time window during which redsn0w can jailbreak new firmware on old devices, but cannot install an untethering exploit.

Once a new untethering exploit is available, redsn0w gets modified by its author to install it. And because every new set of exploits is different, they always require different installation steps.

But, although the actual untether installation is different, it usually comes down to just renaming or moving some files on the root filesystem and then copying some additional files onto it. When you decompile the current version of redsn0w, you can see that it supports installing untethers for most of the iOS versions between 4.2.1 and 5.0.1, and see exactly what files are required for each untether.

Installing the AFC2 Service

The Apple File Connection (AFC) is a file transfer service that runs on every iPhone and allows you to access files within the media directory /var/mobile/Media of the iPhone via USB. This service is provided by the lockdownd daemon and is named com.apple.afc. However, lockdownd only provides access to the service, its actual implementation is within the afcd daemon. It can be accessed from a Mac through the MobileDevice.framework or through the iTunesMobileDevice.dll on a Windows PC.

A second lockdownd service is powered by afcd. It is registered with the name com.apple.crashreportcopymobile. It is used to copy the CrashReporter reports from the device to the computer, and it is limited to providing read and write access to the /var/mobile/Library/Logs/CrashReporter directory and its subdirectories only.

Because both these services run with the permissions of the mobile user only and are locked into specific directories, they are too limited to be useful to jailbreakers. Therefore, redsn0w and several other earlier jailbreaking tools register an additional service with lockdownd called com.apple.afc2. This service uses the afcd daemon to provide read and write access to the whole filesystem with root permissions, which is a quite dangerous feature of jailbreaks that the majority of users do not know about. It basically means that attaching a jailbroken iPhone without a passcode, or in an unlocked state, to a USB power station or another person's computer gives the other side read and write access to the whole filesystem without user interaction. They can steal all your data or add rootkits.

The com.apple.afc2 service is installed by changing the lockdownd configuration within the /System/Library/Lockdown/Services.plist file. It is a normal .plist file and therefore can be modified with the standard tools or API for .plist files. In case of redsn0w the new service is installed by adding the following lines to the file:

<key>com.apple.afc2</key>
<dict>
   <key>AllowUnactivatedService</key>
   <true />
   <key>Label</key>
   <string>com.apple.afc2</string>
   <key>ProgramArguments</key>
   <array>
      <string>/usr/libexec/afcd</string>
      <string>--lockdown</string>
      <string>-d</string>
      <string>/</string>
   </array>
</dict>

Because the filesystem jailbreak and the new AFC2 service are provided by simple configuration changes and do not require unsigned binaries to be executed, they both work after reboot, even if a device has no untethered jailbreak available.

Installing Base Utilities

Apple does not ship the iPhone with a UNIX shell, so it is no surprise that the /bin and /usr/bin directories on the root filesystem are nearly empty and not filled with all the executable binaries you expect to find in these directories. In fact, the latest version of iOS 5.0.1 ships with only five preinstalled executables in these directories:

  • /bin/launchctl
  • /usr/bin/awd_ice3
  • /usr/bin/DumpBasebandCrash
  • /usr/bin/powerlog
  • /usr/bin/simulatecrash

Because of this, jailbreak utilities like redsn0w usually install a set of base utilities in these directories that implement basic features, which make the installation of the files of the jailbreak easier. The following list of tools was extracted from the jailbreak binary on the redsn0w ramdisk. It shows the list of base utilities installed by redsn0w. These tools are also used within the jailbreak binary itself, for example to decompress tar archives or to change the content of .plist files.

  • /bin/mv
  • /bin/cp
  • /bin/tar
  • /bin/gzip
  • /bin/gunzip
  • /usr/sbin/nvram
  • /usr/bin/codesign_allocate
  • /usr/bin/ldid
  • /usr/bin/plutil

Aside from these files, some additional libraries and files are installed that are useful only in the context of the jailbreak and not for the user of a UNIX shell. Therefore, we do not list them. One interesting thing here is that the current stock iOS firmware already comes with a /usr/sbin/nvram binary that is overwritten by redsn0w.

Application Stashing

When applications are installed from the Apple App Store, they are installed inside the directory /var/mobile/Applications, which resides on the big data partition of the iPhone. Therefore, the number of applications that can be installed depends on the amount of free space available on the data partition. This is usually in gigabytes and therefore not really a limitation.

For jailbreak applications installed through Cydia, which is the jailbreaker's equivalent to the Apple App Store, this is different. These applications, like Cydia itself and all the built-in binaries, are installed in the /Applications directory, which is on the root filesystem. As mentioned before, the size of the root filesystem depends on the firmware version, its size, and the device type. Usually, it is between 1 GB and 1.5 GB in size, with about 200 MB of free space, which does not leave much space for installable applications.

In addition, wallpapers and ringtones are also stored on the root filesystem in the directories /Library/Wallpaper and /Library/Ringtones. Therefore, every wallpaper or ringtone that is installed through Cydia will eat up the already limited space for applications.

To solve this problem, the various jailbreaks implement the so called application stashing. The idea is to create a new directory on the data partition of the iPhone called /var/stash and move a number of directories that are normally located on the root filesystem into this directory. The original directories are then replaced by symbolic links to the new location.

The following list shows the directories that are currently stashed away into the /var/stash directory:

  • /Applications
  • /Library/Ringtones
  • /Library/Wallpaper
  • /usr/include
  • /usr/lib/pam
  • /usr/libexec
  • /usr/share

However, not all jailbreaking tools or versions of these tools perform the application stashing. If this is the case, it will be detected and made up for by Cydia, on its first invocation. This is the long “Reorganizing Filesystem” step in Cydia.

Bundle Installation

The next step in the jailbreak installation process is the installation of the application bundles. Depending on the tool used, this is either a custom bundle created by an advanced user, or the Cydia bundle, which is usually shipped by default with the jailbreak. For example, the bundles accepted by redsn0w are simple tar archives that can optionally be packed with gzip. They are unpacked with the previously installed base utilities, so that the jailbreak does not require code for archive unpacking.

The bundle installation loops through each of the bundles contained on the ramdisk and unpacks one after another. During unpacking, tar is told to preserve UNIX permissions, which allows you to have bundles with the suid root bit set. Cydia requires this, because without root permissions, it cannot install new applications. It is interesting to note that due to some Apple trickery, GUI applications may not have the suid bit set on their main binary. Cydia works around by having a shell script called Cydia that will then call the suid root main binary, which is called MobileCydia.

However, the installation of application bundles is not finished after they are unpacked into the /Applications directory. Instead, all installed applications have to be registered in a special systemwide installation cache that is stored in the file /var/mobile/Library/Caches/com.apple.mobile.installation.plist. This file is a normal .plist file with the following format:

<plist version="1.0">
<dict>
   <key>LastDevDirStat</key>
   <integer>…</integer>
   <key>Metadata</key>
   <dict>…</dict>
   <key>System</key>
   <dict>
      <key>com.apple.xxx</key>
      <dict>…</dict>
   </dict>
   <key>User</key>
   <dict>
      <key>someuserapp</key>
      <dict>…</dict>
   </dict>
</dict>
</plist>

The cache contains a timestamp, some meta data, and information about all system and user applications. System applications are all those inside the main /Applications directory and user applications are those downloaded from the Apple App Store inside /var/mobile/Applications. Therefore, all application bundles have to be registered inside the System cache entry. Within redsn0w, this is done by reading the application's Info.plist file and using the information contained to create a new cache entry. First, the CFBundleIdentifier key is read and used as a new key for the cache. Then a new key called ApplicationType with the value System is added to the dictionary inside the Info.plist file. Finally, the new content of the whole dictionary is copied into the cache.

Post-Installation Process

After everything is installed, redsn0w invokes the sync() system call to ensure that everything is written to the disk. Then, the root filesystem is remounted as read-only again, which ensures that all write buffers are synced onto the disk. The data partition, which is mounted to the /var directory, is then unmounted. In case of a mount operation failure, the process is repeated until it is successful or a number of retries is exceeded.

The jailbreak is then finished by rebooting the system with the reboot() system call. In case of a tethered jailbreak, the device then reboots into a non-jailbroken state, unless one of the installed bundles tampered with one of the files required for booting. redsn0w is then required to reboot the device tethered in a jailbroken state.

In the case of a fully untethered jailbreak, the device reboots into a jailbroken state, because the installed untether exploits some application during the boot process and then uses an additional kernel exploit to execute code inside the kernel. You learn more about this kernel payload in the next section.

Executing Kernel Payloads and Patches

The previous chapter about kernel exploitation did not discuss kernel-level payloads and instead postponed the topic to this chapter. The reason for this is that the executing kernel payload is the actual break-the-jail part within a jailbreak, and, therefore, the most important part of it. Because of this we believe the topic to be better suited for this chapter.

Although each kernel exploit and each payload is different, you can distinguish four common components of kernel-level payloads used for jailbreaks:

  • Kernel state reparation
  • Privilege escalation
  • Kernel patching
  • Clean return

The following sections describe each of these points in detail.

Kernel State Reparation

Although different types of kernel vulnerabilities exist, the execution of arbitrary code inside the kernel is usually the result of some kernel-level function pointer being overwritten. Depending on the vulnerability type, this overwritten function pointer might be the only corruption in kernel memory. However, quite often this is not the case. Vulnerability types like stack or heap buffer overflows usually cause larger corrupted areas. Especially in the case of a heap buffer overflow that attacks heap meta data structures, the kernel heap might be in an unstable state after exploitation. This results in a kernel panic sooner or later.

It is therefore very important that every kernel exploit fixes the memory or state corruption it caused. This should start with restoring the overwritten function pointer to the value it had before the corruption. However, in the general case this is not enough. For heap exploits the kernel reparation might be a very complex task, because it means the attacked heap meta data needs to be repaired. Depending on the methods used for kernel heap massage, this can also require scanning the kernel memory for leaked heap memory blocks that need to be freed again to ensure that the kernel does not run out of memory.

In the case of stack data corruptions, whether the kernel stack needs to be fixed or not depends on the specific vulnerability. A stack buffer overflow inside a system call doesn't need to be fixed, because it is possible to leave the kernel thread with an exception, without causing a kernel panic.

Privilege Escalation

Because all applications on the iPhone run as lesser privileged users like mobile, _wireless, _mdsnresponder or _securityd the kernel exploit payload executed after exploiting one of the applications usually escalates the privileges of the running process to those of the root user. Without this step, operations like remounting the root filesystem for write access, or modifying files that are owned by the root user, would not be possible. Both of these are required for the initial jailbreak installation. Kernel exploits that are used only for untethering after a reboot are usually already executed as the root user and therefore do not require this step.

From within the kernel, it very easy to escalate the privileges of the currently running process. All that is required is modifying the credentials attached to its proc_t structure. This structure is defined as struct proc within the file /bsd/sys/proc_internal.h of the XNU source code. Depending on how the kernel exploit payload was started, you have different ways to get a pointer to the proc_t structure of the current process. In many previous public iOS kernel exploits, different kernel vulnerabilities are used to overwrite the address of a system call handler inside the system call table. The kernel exploit payload is then triggered by calling the overwritten system call. In this case, it is trivial to get access to the proc_t structure, because it is supplied to the system call handler as its first parameter!

A more generic way to get the address of the proc_t structure is to call the kernel function current_proc(), which retrieves the address of the structure. This function is an exported symbol of the kernel and therefore very easy to find. Because the original kernel exploit can determine the exact kernel version used, it can hard-code the address of this function into the kernel exploit, because there is no address randomization inside the kernel.

A third option to retrieve the address of the proc_t structure is to use the kernel address information leak through the sysctl interface. This technique was first documented by noir (www.phrack.org/issues.html?issue=60&id=06) against the OpenBSD kernel and later used by nemo (www.phrack.org/issues.html?issue=64&id=11) for the XNU kernel. This information leak allows userspace processes to retrieve the kernel address of the proc_t structure of a process through a simple sysctl() system call.

After the address of the process's proc_t structure is retrieved, its p_ucred member is used to modify the attached ucred structure. This element can be accessed through the proc_ucred() function, or accessed directly. The disassembly reveals that the offset of the p_ucred field inside the structure is 0x84 in current versions of iOS:

_proc_ucred:
LDR.W           R0, [R0,#0x84]
BX              LR

The definition of the struct ucred is located in the file /bsd/sys/ucred.h. Among other things it contains the different user and group IDs of the identity owning the process:

struct ucred {
    TAILQ_ENTRY(ucred)  cr_link; /* never modify this without
 KAUTH_CRED_HASH_LOCK */
    u_long  cr_ref;         /* reference count */
   
struct posix_cred {
    /*
     * The credential hash depends on everything from this point on
     * (see kauth_cred_get_hashkey)
     */
    uid_t   cr_uid;         /* effective user id */
    uid_t   cr_ruid;        /* real user id */
    uid_t   cr_svuid;       /* saved user id */
    short   cr_ngroups;     /* number of groups in advisory list */
    gid_t   cr_groups[NGROUPS]; /* advisory group list */
    gid_t   cr_rgid;        /* real group id */
    gid_t   cr_svgid;       /* saved group id */
    uid_t   cr_gmuid;       /* UID for group membership purposes */
    int cr_flags;       /* flags on credential */
} cr_posix;
    struct label    *cr_label;  /* MAC label */
    /*
     * NOTE: If anything else (besides the flags)
     * added after the label, you must change
     * kauth_cred_find().
     */
    struct au_session cr_audit;     /* user auditing data */
};

To escalate the privileges of the identity owning the process, the cr_uid field, which is located at offset 0x0c, can be set to 0. The offset is 0x0c and not 0x08 as you might expect, because a TAILQ_ENTRY is eight bytes wide. Of course, the other elements can also be patched. However, once the uid is set to zero the userspace process can use system calls to change its permissions.

Kernel Patching

The most important part of the kernel-level payload is to apply the kernel-level patches to the kernel code and data to actually disable the security features, so that unsigned code can be executed and the device is jailbroken. Throughout the years, the different jailbreaking groups have all developed their own sets of patches, therefore most jailbreaks come with different kernel patches, which sometimes results in different features. The most popular set of kernel patches was developed by comex and is available in his github datautils0 repository (https://github.com/comex/datautils0). It is widely used by not only comex's own http://jailbreakme.com, but also as a reference by many of those doing research into the iOS kernel. However, it is unlikely that these patches in this particular GitHub repository, will be ported to future kernel versions, because comex took an internship at Apple and most probably had to sign contracts that stop him from working on future iPhone jailbreaks.

Nevertheless, the following sections introduce you to these patches and explain the idea behind them, which will enable you to produce your own set of kernel patches for future versions of iOS.

security.mac.proc_enforce

The sysctl variable security.mac.proc_enforce controls whether MAC policies are enforced on process operations. When disabled, various process policy checks and limitations are switched off. For example, limitations exist on the fork(), setpriority(), kill() and wait() system calls. In addition to that, this variable controls whether the digital signature of code-signing blobs is validated. When disabled, it is possible to execute binaries that have code-signing blobs that have been signed with a wrong key.

In iOS prior to 4.3 this was used as a shortcut in untethering exploits that were running as root user. They could disable this variable via the sysctl() system call, which allowed them to execute a binary containing the kernel exploit. It was not necessary to write the whole kernel exploit using return-oriented programming as required today. To stop this attack, Apple made the sysctl variable read only in iOS 4.3.

From within the kernel payload, disabling the variable is not a big problem, because you can just assign the value 0 to it. The only work required is to determine the address of the variable in memory. A potential solution is to scan the —sysctl_set segment of the kernel for the definition of the sysctl variable and its address. Because this variable is within the data segment of the kernel, it is always at a static address.

cs_enforcement_disable (kernel)

The source code of the page fault handler, which is contained in the file /osfmk/vm/vm_fault.c, contains a variable called cs_enforcement_disable that controls whether or not code signing is enforced by the page fault handler. In the iOS kernel this variable is initialized to 0 by default, which enables the enforcement. Setting it to a non-zero value, on the other hand, disables the enforcement.

When you look at the code you will see that this variable is used only two times and both uses are within the vm_fault_enter() function. The following code is the first location that uses this variable and the code comment explains in detail what is happening in this piece of code:

    /* If the map is switched, and is switch-protected, we must protect
     * some pages from being write-faulted: immutable pages because by
     * definition they may not be written, and executable pages because
     * that would provide a way to inject unsigned code.
     * If the page is immutable, we can simply return. However, we can't
     * immediately determine whether a page is executable anywhere. But,
     * we can disconnect it everywhere and remove the executable
     * protection from the current map.
     * We do that below right before we do the
     * PMAP_ENTER.
     */
    if(!cs_enforcement_disable && map_is_switched &&
       map_is_switch_protected && page_immutable(m, prot) &&
       (prot & VM_PROT_WRITE))
    {
        return KERN_CODESIGN_ERROR;
    }

As you can see in the code, if the cs_enforcement_disable flag is set, the other condition checks are skipped. The same is true for the code immediately following that checks whether a page is unsigned but wants to be executable:

if (m->cs_tainted ||
        (( !cs_enforcement_disable && !cs_bypass ) &&
         (/* The page is unsigned and wants to be executable */
          (!m->cs_validated && (prot & VM_PROT_EXECUTE))  ||
          /* ... */
          (page_immutable(m, prot) && ((prot & VM_PROT_WRITE) || m->wpmapped))
          ))
        )
    {

In both cases all protection is disabled when the cs_enforcement_disable variable is set. Considering that the variable is initialized to 0 and is not written to at all, we are lucky that it is not optimized away by the compiler. Therefore it can be patched by the jailbreak, after it has been located inside the kernel binary. For iOS 5, comex has chosen to no longer patch the variable, but to patch the code checking it. Patching the code directly is also the way to go if the variable is no longer used in a future version of iOS.

The kernel patch generator from datautils0 finds this check by searching for the byte pattern:

df f8 88 33 1d ee 90 0f a2 6a 1b 68 00 2b

This disassembles to:

80045730   LDR.W   R3, =dword_802DE330
80045734   MRC     p15, 0, R0,c13,c0, 4
80045738   LDR     R2, [R4,#0x28]
8004573A   LDR     R3, [R3]
8004573C   CMP     R3, #0

You can see here that the cs_enforcement_disable variable is located at the address 0x802DE330, its value is loaded into the R3 register, and then compared against 0. The easiest way to patch this is to load the value 1 into the R3 register instead of dereferencing it. This is enough to patch both uses of the variable in vm_fault_enter(), because the compiler has generated code that does not reload the variable and instead uses the register cached copy of it.

cs_enforcement_disable (AMFI)

The Apple Mobile File Integrity (AMFI) kernel module, discussed in Chapter 4, checks for the presence of several arguments. One of these is cs_enforcement_disable. If it is set, this variable influences how the AMFI_vnode_check_exec() policy handler works. As you can see in the decompiled version of the policy check, it stops AMFI from setting the CS_HARD and CS_KILL flags inside the process's code-signing flags:

int AMFI_vnode_check_exec(kauth_cred_t cred, struct vnode *vp, struct label
 *label, struct label *execlabel, struct componentname *cnp, u_int *csflags)
{
  if ( !cs_enforcement_disable )
  {
    if ( !csflags )
      Assert(
        "/SourceCache/AppleMobileFileIntegrity/AppleMobileFileIntegrity-
79/AppleMobileFileIntegrity.cpp",
        872,
        "csflags");
    *csflags |= CS_HARD|CS_KILL;
  }
  return 0;
}

If the CS_HARD and CS_KILL flags are not set, the code signing is effectively disabled. It is, however, unclear why the current jailbreaks patch this variable, because the mac_vnode_check_exec() policy check, which is used inside the execve() and posix_spawn() system calls, is already disabled by the proc_enforce patch, as you can see in the following code:

int mac_vnode_check_exec(vfs_context_t ctx, struct vnode *vp,
    struct image_params *imgp)
{
    kauth_cred_t cred;
    int error;

    if (!mac_vnode_enforce || !mac_proc_enforce)
        return (0);

    cred = vfs_context_ucred(ctx);
    MAC_CHECK(vnode_check_exec, cred, vp, vp->v_label,
          (imgp != NULL) ? imgp->ip_execlabelp : NULL,
          (imgp != NULL) ? &imgp->ip_ndp->ni_cnd : NULL,
          (imgp != NULL) ? &imgp->ip_csflags : NULL);
    return (error);
}

If the proc_enforce flag is set to 0, which is done in most public jailbreaks, the AMFI policy check is not executed at all. Instead, the check returns success. Hence, this patch is useful only if the proc_enforce flag is not touched, which in some non-public jailbreaks we know of, is the case.

PE_i_can_has_debugger

The iOS kernel exports a function called PE_i_can_has_debugger(). It is used in various places throughout the kernel and several kernel extensions to determine whether debugging is allowed. For example, the KDP kernel debugger cannot be used without this function returning true. Because this function is not available within the XNU source code, you can read its decompilation here:

int PE_i_can_has_debugger(int *pFlag)
{
  int v1; // r1@3

  if ( pFlag )
  {
    if ( debug_enable )
      v1 = debug_boot_arg;
    else
      v1 = 0;
    *pFlag = v1;
  }
  return debug_enable;
}

In jailbreaks before iOS 4.3 this function was patched so that it always returned true. This seemed to work until we tried to use the KDP kernel debugger. Setting the debug boot argument resulted in kernel panics in some of the iOS kernel extensions, because just returning true did not completely emulate the original function. This is why most current jailbreaks no longer patch the code of the function, but instead patch the debug_enable variable in memory. To determine the address of this variable, it is necessary to analyze the code of the PE_i_can_has_debugger() function. Because this variable is within an uninitialized data segment of the kernel, this patch can be performed only at run time. To find the code that initializes this variable during boot, you should search for the string debug-enabled. It will lead you directly to the code that copies the value into the variable.

vm_map_enter

When memory is mapped into the address space of a process, the kernel function vm_map_enter() is called to allocate a range in the virtual address map. You can trigger this function, for example, by using the mmap() system call. In the context of a jailbreak, this function is interesting because it enforces the rule that mapped memory cannot be writable and executable at the same time. The following code enforces this rule. If you want to see the full code of the function, have a look into the file /osfmk/vm/vm_map.c. As you can see in the code, the VM_PROT_EXECUTE flag is cleared in case the VM_PROT_WRITE flag is also set:

kern_return_t vm_map_enter(
    vm_map_t        map,
    vm_map_offset_t     *address,   /* IN/OUT */
    vm_map_size_t       size,
    vm_map_offset_t     mask,
    int         flags,
    vm_object_t     object,
    vm_object_offset_t  offset,
    boolean_t       needs_copy,
    vm_prot_t       cur_protection,
    vm_prot_t       max_protection,
    vm_inherit_t        inheritance)
{
    ...
    if (cur_protection & VM_PROT_WRITE){
        if ((cur_protection & VM_PROT_EXECUTE) && !(flags &
VM_FLAGS_MAP_JIT)){
            printf("EMBEDDED: %s curprot cannot be write+execute.
turning off execute
", _PRETTY_FUNCTION_);
            cur_protection &= ∼VM_PROT_EXECUTE;
        }
    }

As you saw in Chapter 4, there is an exception to the rule for so-called JIT (just-in-time) mappings. This is a special type of memory area that is allowed to be writable and executable at the same time, which is required for the JIT JavaScript compiler inside MobileSafari. An application can make use of this exception only one time and only if it has the dynamic code-signing entitlement.

So far this is true only for MobileSafari. All other applications cannot have self-modifying code, dynamic code generators, or JIT compilers, with the exception of the dynamic code-signing vulnerability found by Charlie Miller, which is discussed in Chapter 4. For a full jailbreak, this is an unwanted limitation, because it disallows runtime patching of applications, which is required for the popular MobileSubstrate. Additionally, a number of emulators, which are available for jailbroken iPhones, require self-modifying code.

To find the best way to patch this check you should have a look at the iOS kernel binary. Though there is no symbol for the vm_map_enter() function, it is very easy to find the function by looking for strings containing vm_map_enter. A look at the ARM assembly of the check shows that multiple different one-byte patches exist to kill the check. For example, the AND.W R0, R1, #6 can be changed into AND.W R0, R1, #8; or the BIC.W R0, R0, #4 can be changed into BIC.W R0, R0, #0:

800497C6   LDR      R1, [R7,#cur_protection]
800497C8   AND.W    R0, R4, #0x80000
800497CC   STR      R0, [SP,#0xB8+var_54]
800497CE   STR      R1, [SP,#0xB8+var_78]
800497D0   AND.W    R0, R1, #6
800497D4   CMP      R0, #6
800497D6   ITT EQ
800497D8   LDREQ    R0, [SP,#0xB8+var_54]
800497DA   CMPEQ    R0, #0
800497DC   BNE      loc_800497F0
800497DE   LDR.W    R1, =aKern_return_
800497E2   MOVS     R0, #0
800497E4   BL       sub_8001D608
800497E8   LDR      R0, [R7,#cur_protection]
800497EA   BIC.W    R0, R0, #4
800497EE   STR      R0, [SP,#0xB8+var_78]

For people who jailbreak their iPhones just for the purpose of security research or to have shell access, this patch is not required. It is actually counterproductive to have this limitation patched, because the phone behaves less like a default iPhone.

vm_map_protect

When the protection on mapped memory is changed, the kernel function vm_map_protect() is called. You can trigger this, for example, by using the mprotect() system call. Similar to the vm_map_enter() function, it does not allow changing the protection to writable and executable at the same time. The following code enforces this rule. You can also find the full code of this function in the file /osfmk/vm/vm_map.c, if you want to look at it in more detail. As you can see in the code, the VM_PROT_EXECUTE flag is again cleared in case the VM_PROT_WRITE flag is also set:

kern_return_t vm_map_protect(
    register vm_map_t   map,
    register vm_map_offset_t    start,
    register vm_map_offset_t    end,
    register vm_prot_t  new_prot,
    register boolean_t  set_max)
{
    . . .
#if CONFIG_EMBEDDED
        if (new_prot & VM_PROT_WRITE) {
            if ((new_prot & VM_PROT_EXECUTE) && !(current->used_for_jit)) {
                printf(„EMBEDDED: %s can't have both write and exec at the
same time
", _FUNCTION_);
                new_prot &= ∼VM_PROT_EXECUTE;
            }
        }
#endif

Again you can see that an exception is made only for memory ranges that are used for JIT, which can be created only by applications with the dynamic code-signing entitlement. No other applications can use mprotect() to make a memory area writable and executable at the same time. The standard jailbreaks therefore patch this check, to allow applications to make previously allocated memory writable and executable.

To patch this function it first has to be found. Although there is no kernel symbol pointing to it, there is a reference to the string vm_map_protect within the function, which makes it easy to find. A look at the ARM disassembly shows you that, again, two alternative one-byte patches can be applied to remove the security check. The AND.W R1, R6, #6 can be changed into AND.W R1, R6, #8; or the BIC.W R6, R6, #4 can be changed into BIC.W R6, R6, #0:

8004A950   AND.W     R1, R6, #6
8004A954   CMP       R1, #6
8004A956   IT EQ
8004A958   TSTEQ.W   R0, #0x40000000
8004A95C   BNE       loc_8004A96A
8004A95E   BIC.W     R6, R6, #4

Because of this patch, jailbreaking weakens the memory protection of the iOS device. We suggest applying this patch only if the user of the jailbreak wants to run applications that require self-modifying code. The problem with these patches is that they disable the non-executable memory restrictions, so that remote attacks against iPhone applications do not need to be implemented in 100 percent ROP. Instead, these attacks (or malware) just need a short ROP stub that uses mprotect() to make the injected code executable.

AMFI Binary Trust Cache

The AMFI kernel module is responsible for validating the digital signature on code-signing blobs. It registers several MAC policy handlers like the vnode_check_signature hook, which is called every time a new code-signing blob is added to the kernel. The AMFI handler validates the signature against the certificate from Apple. However, the validation is bypassed if the amfi_get_out_of_my_way or the amfi_allow_any_signature boot-arguments are set, which is only possible with a bootrom- or iBoot-based jailbreak. But the validation is also skipped if the SHA1 hash of the code-signing blob is found within a built-in list of more than 2200 known hashes, which is called the AMFI binary trust. The trust cache lookup is implemented in a single function that is patched by comex to always return success. This makes AMFI believe that every signature is within this cache and therefore trusted, which effectively disables the digital signature on the code-signing blobs.

You can find the address of this function by looking up the AMFI vnode_check_signature MAC policy handler in the AMFI MAC policy table and searching for the first function call inside. An alternative way to find the function is to search for the following byte pattern in the kernel binary:

f0 b5 03 af 2d e9 00 05 04 46 .. .. 14 f8 01 0b 4f f0 13 0c

This code is then overwritten with a function that just returns true, which will help in bypassing the digital signature. Further research into this kernel patch will show you that it is not required at all. When you look into the code for mac_vnode_check_signature, which is defined in /security/mac_vfs.c, you can see that the AMFI handler is already completely disabled by the previous proc_enforce patch:

int mac_vnode_check_signature(struct vnode *vp, unsigned char *sha1, void *
signature, size_t size)
{
    int error;
   
    if (!mac_vnode_enforce || !mac_proc_enforce)
        return (0);
   
    MAC_CHECK(vnode_check_signature, vp, vp->v_label, sha1, signature, size);
    return (error);
}

If the mac_proc_enforce flag is disabled, the AMFI vnode_check_signature check is not called. The same is true for all the other MAC policy handlers that make use of the AMFI binary trust cache.

Task_for_pid 0

Although this patch is not necessary for the majority of jailbreakers, we document it here because it involves a mach trap and therefore allows us to introduce you to a strategy for finding the mach_trap_table within the iOS kernel binary.

The function task_for_pid() is a mach trap that returns the task port for another process, named by its process ID. This is limited to processes of the same user ID, unless the process requesting the task port is privileged. In earlier versions of Mac OS X, it is possible to get the task port of the kernel process by asking for the task port of process 0. This technique was used by Mac OS X rootkits, because it allowed userspace processes to read and write arbitrary kernel memory.

This might be the reason why task_for_pid() was changed to no longer allow access to the task port of process ID 0, as you can see in the following code that was taken from the file /bsd/vm/vm_unix.c of the XNU source code:

kern_return_t task_for_pid(struct task_for_pid_args *args)
{
    mach_port_name_t    target_tport = args->target_tport;
    int         pid = args->pid;
    user_addr_t     task_addr = args->t;
    proc_t          p = PROC_NULL;
    task_t          t1 = TASK_NULL;
    mach_port_name_t    tret = MACH_PORT_NULL;
    ipc_port_t      tfpport;
    void * sright;
    int error = 0;

    AUDIT_MACH_SYSCALL_ENTER(AUE_TASKFORPID);
    AUDIT_ARG(pid, pid);
    AUDIT_ARG(mach_port1, target_tport);

    /* Always check if pid == 0 */
    if (pid == 0) {
        (void ) copyout((char *)&t1, task_addr, sizeof(mach_port_name_t));
        AUDIT_MACH_SYSCALL_EXIT(KERN_FAILURE);
        return(KERN_FAILURE);
    }

As you can see, now there is an explicit check for the process ID zero and if it is specified, an error code is returned. comex patches this check by changing the conditional jump generated by the if statement into an unconditional jump. The address to patch is found by a pattern search for the following byte string:

91 e8 01 04 d1 f8 08 80 00 21 02 91 ba f1 00 0f 01 91

An alternative way to find the place to patch is to look up the address of the task_for_pid() function in the mach trap table. However, the symbol mach_trap_table, which is defined in the file /osfmk/kern/syscall_sw.c, is not exported, and therefore the table requires some extra work to be found. When you look at the definition of the table it looks like this:

mach_trap_t mach_trap_table[MACH_TRAP_TABLE_COUNT] = {
/* 0 */     MACH_TRAP(kern_invalid, 0, NULL, NULL),
/* 1 */     MACH_TRAP(kern_invalid, 0, NULL, NULL),
/* 2 */     MACH_TRAP(kern_invalid, 0, NULL, NULL),
. . .
/* 26 */    MACH_TRAP(mach_reply_port, 0, NULL, NULL),
/* 27 */    MACH_TRAP(thread_self_trap, 0, NULL, NULL),
/* 28 */    MACH_TRAP(task_self_trap, 0, NULL, NULL),
. . .
/* 45 */    MACH_TRAP(task_for_pid, 3, munge_www, munge_ddd),

As you can see, the table starts with a number of invalid kernel traps. This fact can be used to detect the address of the mach_trap_table in memory. The table defined in the public XNU source code shows the first 26 mach traps as invalid. However, when you look at the iOS kernel you will find that only the first 10 mach traps are invalid.

Unfortunately, the function kern_invalid() is also not exported and therefore it has to be found first. This is not a problem, because as you can see in the following code, it references a very revealing string:

kern_return_t kern_invalid(_unused struct kern_invalid_args *args)
{
       if (kern_invalid_debug) Debugger("kern_invalid mach trap");
       return(KERN_INVALID_ARGUMENT);
}

Because the referenced string is used only once throughout the code, the only cross reference to this string is from within the kern_invalid() function. With the help of this address, the mach_trap_table can be found by searching for a repeating pattern of four bytes filled with 0, followed by four bytes filled with the address of the function. However, in the current iOS kernel, the address of kern_invalid() is not really required to find the table, because the repeated pattern of zero followed by the same pointer is good enough to find the table.

Sandbox Patches

The last kernel patch from comex's set of kernel patches changes the behavior of the sandbox. Without this patch, certain applications like MobileSafari and MobileMail will not work on jailbroken iPhones. The reason for this is that the /Applications directory is moved to the /var/stash/Applications directory, which leads to sandbox violations. A surprise is that only those two applications are affected as far as we know. All the other built-in applications seem to work flawlessly without the sandbox patch.

The patch itself consists of two parts: The first part overwrites the beginning of the sb_evaluate() function with a hook, and the second part is new code that gets written into an unused area inside the kernel. For more information about this function, review Chapter 5. The patch changes the behavior of the sandbox evaluation to handle access to certain directories differently.

Before we describe the new evaluation functionality, we have to find a method to locate the sb_evaluate() function inside the kernel code, because there is no symbol available. One possibility would be to search for the table of mac policy handlers inside the Sandbox kernel extension. Several of the mac policy handlers make use of the sb_evaluate() function. For current iOS kernels, it is easier to search for the string bad opcode. It is used only within your function of interest, and once you find its data reference you just have to find the beginning of the function in which it is used.

With the address of the sb_evaluate() function located, you can put a hook into it and let it jump to one of the unused kernel areas, where you put the rest of the code. We already discussed how to find these unused areas in Chapter 9. You can find the source code of the evaluation hook inside the datautils0 GitHub repository from comex, but we discuss it here, piece by piece. The overall idea of this code is to exclude files outside of /private/var/mobile and files inside /private/var/mobile/Library/Preferences from the sandbox check. The code starts by checking if the supplied vnode is 0. If this is the case, the hook ignores this call and just passes execution to the original handler:

start:
    push {r0-r4, lr}
    sub sp, #0x44
    ldr r4, [r3, #0x14]
    cmp r4, #0
    beq actually_eval

The next piece of the code calls the vn_getpath() function to retrieve the path for the supplied vnode. If this function returns an error, the error ENOSPC is ignored; all other errors result in the execution being passed to the original handler:

    ldr r3, vn_getpath
    mov r1, sp
    movs r0, #0x40
    add r2, sp, #0x40
    str r0, [r2]
    mov r0, r4
    blx r3
    cmp r0, #28
    beq enospc
    cmp r0, #0
    bne actually_eval

If no error was returned or there was not enough space to get the full pathname, the returned pathname is compared against the string /private/var/mobile. If the pathname does not match, access is allowed:

enospc:
    # that error's okay...
    mov r0, sp
    adr r1, var_mobile ; # "/private/var/mobile"
    movs r2, #19 ;# len(var_mobile)
    ldr r3, memcmp
    blx r3
    cmp r0, #0
    bne allow

If the pathname matches, it is compared against /private/var/mobile/Library/Preferences/com.apple next. If it matches, the original sb_evaluate() function is called:

    mov r0, sp
    adr r1, pref_com_apple
    ; # "/private/var/mobile/Library/Preferences/com.apple"
    movs r2, #49 ;# len(preferences_com_apple)
    ldr r3, memcmp
    blx r3
    cmp r0, #0
    beq actually_eval

The next check just tests whether the pathname is within /private/var/mobile/Library/Preferences. If it is, access is allowed; otherwise, the original handler is called:

    mov r0, sp
    adr r1, preferences ;# "/private/var/mobile/Library/Preferences"
    movs r2, #39 ;# len(preferences)
    ldr r3, memcmp
    blx r3
    cmp r0, #0
    bne actually_eval

The code to allow access writes this information back into the supplied data structure, which is documented in more detail in Chapter 5.

allow:
    # it's not in /var/mobile but we have a path, let it through
    add sp, #0x44
    pop {r0}
    movs r1, #0
    str r1, [r0]
    movs r1, #0x18
    strb r1, [r0, #4]
    pop {r1-r4, pc}

The rest of the code just passes execution back to the original function. We will not discuss it here, because it is just standard API interception technique.

Clearing the Caches

Applying the previous kernel patches is straightforward because the whole kernel image is in readable, writable, and executable memory. Therefore, the kernel-level payload can write the patches over the original code, without the need to change memory permissions. The only complication when patching the kernel is that the CPU instruction and data caches have to be cleared, because otherwise the modifications that result from the jailbreak might not be immediately active.

The iOS kernel exports two functions for this purpose that the exploit payload should call every time it patches kernel code or data directly. To clear the instruction cache, the invalidate_icache() function needs to be called. It requires three parameters. The first parameter is the address of the memory area to invalidate, the second parameter is the length of this area, and the third parameter should be 0.

The function to clear the data cache is called flush_dcache() and is called with the same three parameters.

Clean Return

After privileges have been escalated and security features have been patched out of the kernel, the only thing left is to leave the kernel space in a clean way that will not destabilize the kernel or result in an immediate crash. Normally this just requires restoring the general-purpose CPU registers to the values before the kernel payload was called and then returning to the saved program counter. In the case of a kernel stack buffer overflow, this might not be possible because the actual values on the stack have been overwritten by the buffer overflow. If this happens, it might be possible to return to one of the previous stack frames that were not destroyed.

An alternative way to exit the kernel is to call the kernel function thread_exception_return(). You need to find this function by pattern scanning or by scanning for its cross-references because there is no symbol for it in the kernel. It is used inside the kernel to recover from exceptional situations that require execution to end the current kernel thread when unwinding the stack frames is not possible. It is, therefore, possible to use it to leave the kernel from an exploit payload. However, whenever possible, the kernel should be left by returning to the right stack frames, because otherwise it is not guaranteed that the kernel is left in a stable state.

Summary

In this chapter we have given an insight into jailbreaking, something considered a black box for the majority of people. We have introduced you to the reasoning behind using jailbroken phones, instead of factory phones or development iPhones, for security research. We have discussed the assets and drawbacks of different types of jailbreaks.

We analyzed the inner workings of the redsn0w jailbreak and walked you through each step of the jailbreaking process. This should have made clear the differences between jailbroken iPhones and factory phones from a usability and security point of view.

We also documented the kernel patches applied by jailbreaks, and for each of them we discussed the reasoning behind them, how to find the address to patch, and in what way to patch it. With this knowledge, it should be possible for you to port the patches to future iOS versions, without having to rely on the jailbreak community.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset