Modern Jailbreaks' Post-Exploitation


In iOS all running code, including applications, must be signed by a certificate issued by Apple. The intended way for a developer or a company to deliver an App is through the App Store. This enables Apple to perform checks on the App and ensure it is compliant with their policy. To regain control of your device and be able to execute whatever is desired you'll have to jailbreak it.



The main purpose of a jailbreak is to enable arbitrary App and binaries execution on an iOS (and derivative) device. Before the release of iOS 9 on 64-bit devices, there was no mechanism to check kernel code integrity, meaning a kernel exploit giving read/write access would effectively be the last hurdle before patching the kernel security mechanisms. To circumvent patching, KPP (Kernel Patch Protection) has been introduced, starting with the iPhone 5s on iOS 9. It is using the EL3 CPU feature to perform the checks. However, a bypass has been found by Lucas Todesco and was used in the Yalu102 jailbreak. Since the release of the iPhone 7, all the iPhones use KTRR (same role as KPP but relies on hardware to enforce no write access), rendering kernel code patching not possible anymore. No public bypass has been found for the moment, which means modern jailbreak post-exploitation is less straightforward than before because they can only patch writable segments such as __DATA.


Historically, jailbreaks were either tethered (needs to be connected to the computer to be jailbroken, it has no persistence) or untethered (still jailbroken after iOS is restarted). Modern jailbreaks are "semi-tethered": that means each time your device is powered on, you'll have to launch an App on your phone to jailbreak it. Thus, the favored way to distribute a jailbreak is to package it as an App. To be deployed on the phone it needs to be signed by either a free or paid developer account's certificate. This is easily done with Cydia Impactor: an application that signs (the developer account credentials are needed) and sideloads an App on the phone. After trusting the developer on the phone, the App can be launched. Keep in mind that such an App is valid for a limited amount of time (due to the certificate used to sign it), after, it needs to be signed and sent again.

Stages of modern jailbreaks

Most security checks are performed in kernel land, therefore, a jailbreak has to exploit the kernel.

Kernel exploitation

Usually, the first step is to exploit a kernel component such as an IOKit driver or the Mach layer and leverage the vulnerability to obtain an arbitrary read and write access to kernel memory. The best way to manipulate kernel memory is through the kernel task, an object which is used to abstract and manipulate the components of a mach task such as threads and virtual memory. Once this access to the kernel is provided, the post-exploitation stage can begin.


In order to obtain a fully functional jailbreak, the following steps are performed:

  • Gain root privileges.
  • Bypass Sandbox restrictions.
  • Read and write access to the root filesystem.
  • Bypass code signing.

Due to KTRR, the jailbreak cannot simply patch the kernel code to bypass the security mechanisms, but rather needs to patch kernel writable structures and manipulate some key user land components. Some jailbreaks may perform additional steps to ease the life of the end user and provide additional features and components.

LiberiOS and Electra

LiberiOS and Electra are two major jailbreaks for iOS 11.1.2. The first one released, LiberiOS uses the QiLin framework (they're made by the same author) while Electra doesn't. Both jailbreaks use Ian Beer's async_wake exploit to obtain tfp0, the kernel task. After the exploitation, they start by locating the kernel base to deduce the slide from ASLR. Electra also defines a macro named KCALL to call kernel functions using the technic found in the v0rtex exploit. From now on, LiberiOS and Electra use their kernel read/write functions (based on tfp0) to overcome the security mechanisms.

Root privileges

LiberiOS gains root privileges by calling the function rootifyMe from QiLin. The first step is to look for its own process structure in kernel memory. Once found, the credentials structure (offset 0x100 in the process structure) address is retrieved. Then the UID and GID of our process structure are set to 0 by overwriting the effective, real and saved user id located at the offset 0x18. The code signing flags are also set to CS_SIGNED | CS_ENTITLEMENTS_VALIDATED | CS_GET_TASK_ALLOW | CS_VALID.

_setuidProcessAtAddr ; called by rootifyMe

LDR X8, [SP,#0x110+self_ucred]
ADD X0, X8, #0x18 ; self_ucred + 0x18 (destination)
LDR X8, [SP,#0x110+var_D0] ; local buffer filled with 0 (source)
LDR X1, [SP,#0x110+var_C8] ; 12 (size)
MOV X2, X8
BL _writeKernelMemory

Electra loop over the process list and keeps the address of some of them. The ones of interests here are the kernel and Electra itself. After using the KCALL macro to copy the kernel credentials location, it replaces its own MAC label (offset 0x78 in the credential structure) by the one from the kernel credentials. MACF (Mandatory Access Control Framework) is the framework used in iOS to hook system calls or mach traps and perform various checks (described by MAC policies) to allow or refuse the operation. The MAC label is a struct used by those policies as a storage place and thus define the rights of a process for mechanisms based on MACF (for example the sandbox). The way the UID and GID are set to 0 is the same as LiberiOS except Electra also makes a call to setuid(0).

uint64_t kern_ucred = 0;
KCALL(find_copyout(), kern_proc+0x100, &kern_ucred, sizeof(kern_ucred), 0, 0, 0, 0);

uint64_t self_ucred = 0;
KCALL(find_copyout(), our_proc+0x100, &self_ucred, sizeof(self_ucred), 0, 0, 0, 0);

KCALL(find_bcopy(), kern_ucred + 0x78, self_ucred + 0x78, sizeof(uint64_t), 0, 0, 0, 0);
KCALL(find_bzero(), self_ucred + 0x18, 12, 0, 0, 0, 0, 0);


Bypass the Sandbox mechanism

The following structure is the MAC label located from the offset 0x78 of the credential structure:

struct label {
        int    l_flags;
        union {
                void    *l_ptr;
                long     l_long;
        } l_perpolicy[MAC_MAX_SLOTS];

Each l_perpolicy "slot" is used by a particular MACF module, the first one being AMFI and the second one the sandbox. LiberiOS calls ShaiHulud2ProcessAtAddr to put 0 in its second label l_perpolicy[1]. Being the label used by the sandbox (processed in the function sb_evaluate), this move will neutralize it while keeping the label used by AMFI (Apple Mobile File Integrity) l_perpolicy[0] untouched (it's more precise and prevent useful entitlement loss).

MOV             X1, #8 ; size
ADD             X8, SP, #0x50+zero
STR             XZR, [SP,#0x50+zero]
LDR             X0, [SP,#0x50+self_mac_label_ptr]
LDR             X0, [X0] ; self_mac_label
ADD             X0, X0, #0x10 ; self_mac_label->l_perpolicy[1] (destination)
MOV             X2, X8  ; pointer to 0 (source)
BL              _writeKernelMemory ;

By replacing the MAC label inside the process credentials with those of the kernel, Electra has already bypassed the sandbox mechanism.

Gain write access on the root filesystem

Bypassing the sandbox allows calls to the mount function. To update the root vnode flags, the flag MNT_ROOTFS has to be disabled otherwise the MACF hook would prevent it. So both Electra and LiberiOS call mount without MNT_ROOTFS and MNT_RDONLY (Electra also flip off the MNT_NOSUID flag), and after that, manually put back the MNT_ROOTFS flag. Now there is full read/write access on the root filesystem of the phone.

Bypass code signing

The code signature check is performed by the kernel extension AMFI and its daemon amfid.

For LiberiOS, two things are needed before bypassing code signing. Firstly, it adds the TF_PLATFORM flag to its task flags, which is necessary to use task_for_pid on a daemon (only a platform binary can obtain the task port of another platform binary). Then it will borrow entitlements from a process which possess task_for_pid-allow and entitlements to enable calls to task_for_pid. This is done respectively by the plateformizeMe and borrowEntitlementsFromDonor functions. The last one spawn /usr/bin/sysdiagnose to borrow his entitlements and simply replace LiberiOS MAC label pointer with the sysdiagnose one:

ADRP            X8, #[email protected] ; "/usr/bin/sysdiagnose"
ADD             X0, X8, #[email protected] ; "/usr/bin/sysdiagnose"
ADRP            X8, #[email protected] ; "-u"
ADD             X1, X8, #[email protected] ; "-u"
BL              _borrowEntitlementsFromDonor

Finally, LiberiOS calls castrateAmfid. This function will register a new exception handler for amfid with setExceptionHandlerForTask. By overwriting the imported function pointer MISValidateSignatureAndCopyInfo in amfid with 0x454D41524542494C (an invalid address), calls to MISValidateSignatureAndCopyInfo will be redirected to the custom handler. By always returning the boolean 0 (ok) the handler will effectively bypass code signing.

Bypassing the signature mechanism is also possible by adding the correct information inside the Trust Cache of the kernel. The Trust Cache is an array of hash used to verify ad hoc binaries. Electra adds the binaries inject_criticald, amfid_payload.dylib and pspawn_payload.dylib to the Trust Cache located in writable memory (a cached version of the original Trust Cache) with the function inject_trusts:

inject_trusts(3, (const char **)&(const char*[]){
// Don't forget to update number in beginning


Then, it spawns the daemon inject_criticald, that injects amfid_payload.dylib into amfid. By rebinding the MISValidateSignatureAndCopyInfo symbol from /usr/lib/libmis.dylib with fake_MISValidateSignatureAndCopyInfo the signature validation is performed by Electra's substitute function:

void rebind_mis(void) {
    void *libmis = dlopen("/usr/lib/libmis.dylib", RTLD_NOW); //Force binding now
    old_MISValidateSignatureAndCopyInfo = dlsym(libmis, "MISValidateSignatureAndCopyInfo");
    struct rebinding rebindings[] = {
        {"MISValidateSignatureAndCopyInfo", (void *)fake_MISValidateSignatureAndCopyInfo, (void **)&old_MISValidateSignatureAndCopyInfo_broken},
rebind_symbols(rebindings, 1);


The function behaves as LiberiOS handler by returning 0.

Filesystem modification

LiberiOS creates a new directory /jb to add utility binaries and the amfidebilitate daemon. The role of amfidebilitate is to keep amfid "castrated" at all times because if amfid is killed and relaunched it has to be patched again. LiberiOS and Electra give an SSH access to the phone with Dropbear. Electra adds a new daemon jailbreakd meant to provide access to several useful functions to leverage the powered gained with the jailbreak. They also provide a shell script to completely remove the jailbreak from the filesystem. If tweaks are enabled in the UI Electra will install additional components that won't be covered here.


Despite the fact that they're both sharing the same goal, LiberiOS and Electra achieve some post-exploitation steps in different ways: for example, the code signing bypass is done by either injection or exception handler registration. And judging from the content installed they target different kinds of users. A security researcher often needs a few tools to easily work on the phone, like an SSH access and common binaries. In this case, LiberiOS is plenty enough because both are provided (Dropbear for SSH and several binaries are installed). Fewer things also means that the impact on the system will be lower. However, the additional features offered by Electra, such as the inclusion of Cydia, jailbreakd and tweaks related components (making it more like a toolkit) is welcomed by common users and developers of tweaks.



  • Fred Raynal for the topic of the blog post and the help provided
  • Marion Videau for her help through the redaction
  • Jonathan Levin for his answer to my question
  • Nicolas, Richard and Benoît for their feedback
  • Quarkslab colleagues for proofreading this article

Article Link: