01 March 2019

Completely Bypassing Codesigning on Modern iOS

By Dynastic

iOS prevents the execution of unsigned binaries, and in iOS 12, CoreTrust enforces this even further, becoming a significant obstacle for jailbreaks. In this post, we will detail a practical attack against both AMFI and CoreTrust, utilising a time of check to time of use (TOCTOU) attack.

This is a follow-up to our previous research post on CoreTrust, CoreTrust: an overview.

Heads up: This is developer-oriented research for those with advanced knowledge of programming, code signing techniques, attack vectors, security research, and jailbreak development. While we have attempted to thoroughly explain terms used, a background in jailbreaking is recommended.

Background

When a binary is spawned, iOS ensures that it has a valid code signature from Apple before it is executed. This is stored in the vnode of the binary, in a field called cs_blob. Among other things, the cs_blob stores a hash of the binary in a field called csb_cdhash.

AppleMobileFileIntegrity (AMFI) is responsible for ensuring the validity of the signature and the entitlements.

Overview

Early on in the codesigning validation process, a function called _vnode_check_signature is invoked in AMFI—this is where the bulk of AMFI’s logic lies, and is where all of AMFI’s checks for a binary originate from. From here, the signature and entitlements are parsed. Any error that occurs here is fatal and prevents the binary from being launched. CoreTrust validation occurs here too (we recommend reading our previous post CoreTrust: an overview to understand more about what CoreTrust does).

AMFI has a feature known as the TrustCache, which is simply a list of cd_hashes that are automatically trusted by AMFI. Xcode utilises this functionality to make Xcode’s debugging features work, and modern jailbreaks use this to load their main payloads and run them with special privileges.

Early in the _vnode_check_signature flow, the cd_hash of the vnode is checked against the list of hashes in the loadedTrustCaches.

The check in AMFI to see if a hash is in a loaded TrustCache.
The check in AMFI to see if a hash is in a loaded TrustCache.

This happens very early in the flow, before CoreTrust is called and before any other additional checks happen. Therefore, if the hash can be changed to a trusted hash whilst AMFI is evaluating it, but returned to normal after, then the codesigning flow can be completely bypassed.

The attack

Now that we know about about the issue, we can work on attacking it. It is important to remember that the cd_hash of the binary must match the hash of the binary when dyld checks it, so the cd_hash of the binary must be swapped back before that happens.

To achieve this, these steps have to be performed on the launch of any process:

  1. Lookup the vnode of the binary, attaching a valid cs_blob to it. This can be from another vnode, providing it will pass codesigning validation.
  2. Once AMFI has finished, the cs_blob of the vnode has to be restored to one which matches the binary. This can be achieved by crafting a blob, a technique used by Meridian.
  3. The binary has now been launched successfully! 🎉

Whilst we mentioned the TrustCache approach, provided that the cs_blob that is swapped has a valid CMS blob (to pass CoreTrust validation), this approach will still work. It is also possible to replace only the cd_hash, with that of one in the TrustCache, providing this is changed back in step 3.

Note: There are many variations possible to this attack. For example, to avoid TOCTOU’ing completely, the binary could be added to the TrustCache and then removed after the AMFI flow has completed. The differences between these attacks are minimal and it should be trivial to switch implementations; the most important fact is that your solution is stable, and reliable.

Obtaining the necessary hooks (process launch and pre-dyld) is an exercise left to the reader. Some modern jailbreaks already have the required userland hooks, so this technique is perfect for such tools.

Pratical usage

We envision that this bypass can be used in a jailbreak as a proper bypass to the CoreTrust mitigation. Additionally, it serves as a cleaner codesign bypass. Most modern jailbreaks achieve this by hijacking a daemon, which would no longer be necessary with this technique. Overall, this could increase stability and user experience when using such tools.

We plan on releasing a POC attack utilising this bug soon. This post will be updated with a link to that when it is ready.

Thanks

Many thanks to @iBSparkes, for helping out with the implementation of the attack, and for technical proofreading. His Twitter is full of interesting content similar to this.

We hope that this research is useful to you. If it is, and you use it in a project, please include a reference to this post; hopefully someone else can also benefit from it. For that reason, please consider open-sourcing any code which implements this technique. We also ask that you credit Dynastic and @iBSparkes, as hard work has gone into this writeup and research.

Updates

6th April, 2019: article has been revised to include clarified steps for POC bypass.


This article was brought to you by Dynastic.

« Back to posts