Introduction

Code-signing is an integral component of iOS security - it is used to ensure that all code that is run on the device originates from a trusted source. It involves ensuring that every byte of executable code inside a binary is what it was when the binary was originally signed. Without code-signing, it would be trivial to modify binaries to do whatever you want - and Apple would lose complete control over the security of their devices.

At first glance, code-signing seems like a fairly complex system. However, once you break it down into its individual components, it is actually quite simple. In this blog post, I will be going over the structure of the code-signature for ad-hoc signed binaries (which aren’t actually signed with a certificate, but still contain all the required blobs), and how each of the blobs in the code-signature work.

Feel free to have a (signed) MachO on hand, as well as a hex editor, in order to follow along a bit easier!

The superblob

MachOs (executable files on Apple platforms) have a set of “load commands” that describe the structure of the file. These load commands tell iOS what is needed to run the binary - such as required libraries, the entry point, and the code signature. The LC_CODE_SIGNATURE load command is often the final load command in a MachO, and it points to the start of the code signature. However, the code signature is not just a single blob of data - it is actually a collection of multiple data blobs, each with their own purpose. This collection of blobs is known as the superblob.

If you want to avoid the hassle of finding the superblob using the load commands of a MachO, you can simple search for the 0xFADE0CC0 byte sequence in the MachO. This is the magic number of the superblob, and it is always located at the start of the superblob.

The structure of the superblob

The superblob structure goes as follows:

struct CS_SuperBlob {
    uint32_t magic; // 0xFADE0CC0
    uint32_t length; // Length of the superblob
    uint32_t count; // Number of blobs in the superblob
    CS_BlobIndex blobs[count]; // Offsets to each blob
};

The CS_BlobIndex structure is defined as follows:

struct CS_BlobIndex {
    uint32_t type; // Type of the blob
    uint32_t offset; // Offset of the blob
};

The type field of the CS_BlobIndex structure is a four-byte magic number that identifies the type of the blob. The offset field is the offset of the blob from the start of the superblob. The length field of the superblob is the length of the superblob, including the superblob header.

So with all this information from the superblob, it’s fairly easy to segment the entire code signature up into it’s individual components. However, we still need to know what each of these blobs actually do, and we’ll start with the most important one - the code directory.

The code directory

The code directory is arguably the most important part of the code signature. It contains a list of hashes of every single page of executable code in the binary, as well as a hash of the entitlements and requirements blobs (which we will get to later). The code directory blob acts as a sort of ‘manifest’ of hashes, such that iOS can verify the binary is completely unmodified just by hashing the code directory.

There can be multiple code directories in a code signature, but they must have different hash types. All App Store binaries have two code directories - one containing SHA1 hashes, and one containing SHA256 hashes. The SHA256 code directory is more secure, but the SHA1 code directory is still used anyway.

The structure of the code directory

The code directory is structured as follows:

struct CS_CodeDirectory {
    uint32_t magic;
    uint32_t length;
    uint32_t version;
    uint32_t flags;
    uint32_t hashOffset;
    uint32_t identOffset;
    uint32_t nSpecialSlots;
    uint32_t nCodeSlots;
    uint32_t codeLimit;
    uint8_t hashSize;
    uint8_t hashType;
    uint8_t spare1;
    uint8_t pageSize;
    uint32_t spare2;
    uint32_t scatterOffset;
    uint32_t teamOffset;
};

The magic for the CodeDirectory is always 0xFADE0C02, and as with every blob in the code signature, the magic is always followed by the length. There is a lot of offsets defined in this structure, but we will only be looking into the important ones.

Special slots

The nSpecialSlots field of the code directory defines the number of special slots in the code directory. These special slots are used to store hashes of data that isn’t part of the executable code. The special slots are as follows:

  • -1 - The hash of the Info.plist file
  • -2 - The hash of the requirements blob
  • -3 - The hash of _CodeSignature/CodeResources
  • -4 - An app-specific hash (often goes unused)
  • -5 - The hash of the entitlements blob
  • -6 - The hash of a disk image (if applicable)
  • -7 - The hash of the DER-encoded entitlements blob
  • -8 -> -11 - The hashes of various launch constraints for the process and related processes (if applicable)

Code slots

The nCodeSlots field of the code directory defines the number of code slots in the code directory. These code slots are used to store hashes of the executable code in the binary, which is calculated by using pageSize as the number of bytes to hash in one go, and then creating a long list of hashes of each page of executable code in the binary and appending this to the code directory.

So with a hash of every page of executable code in the binary, and a hash of every other important blob in the code signature, iOS can take a hash of the code directory (which is often called the ‘CDHash’ of a binary) to be representative of the entire code signature.

Bonus: trustcache

If you’ve ever heard the term ‘trustcache’ in the context of iOS security, this is what it is referring to. The trustcache is a list of CDHashes that are automatically approved by the kernel, without even running any code-signing checks (except for ensuring that the hashes are actually correct). These binaries can have any entitlement they choose, and don’t even need to contain a signature - binaries without a signature (but with all the other required blobs) are known as ‘ad-hoc’ signed binaries.

The entitlements blobs

The entitlements blobs are used to store the entitlements of the binary. There are two entitlements blobs in the code signature - one is a DER-encoded blob, and the other is a XML blob. The XML blob has a magic of 0xFADE7171, and the DER blob has a magic of 0xFADE7172.

Entitlements are a set of key-value pairs that define what the binary is allowed to do. For example, the com.apple.private.security.no-sandbox entitlement allows the binary to run without sandbox restrictions. The com.apple.private.persona-mgmt entitlement allows a binary to spawn another binary under the root user (or any user, for that matter). Here is an example of XML entitlements, taken from TrollStore:

XML entitlements

The requirements blob

The requirements blob is used to store the requirements of the binary. Requirements are a set of rules that must be met in order for the binary to be allowed to run. The requirements blob has a magic of 0xFADE0C01. Below is a set of requirements, parsed using Jonathan Levin’s extremely useful jtool2:

Requirement Set (124 bytes) with 1 requirement:
    0: Designated Requirement (@20, 92 bytes): Ident(CocoaTop)
    AND Cert field [subject.CN] = ''
    AND (Cert Generic[1] = WWD Relations CA)

As far as I can tell, requirements are not respected for App Store apps (and hence apps which are signed with a CoreTrust bypass), but they are respected for developer-signed apps. If the app’s code signature does not meet the requirements, the app will not be allowed to run.

CodeResources

By now, you may be wondering - how are the other resources in the bundle verified to be original and genuine? The answer is the CodeResources file. This file contains a list of hashes of every file in the bundle, and is used to verify that the files the same as they were when the bundle was signed. The CodeResources file is located at _CodeSignature/CodeResources in the bundle, and is hashed as part of the code directory.

If you take a look at the CodeResources file, you will see that it is a regular XML file. It containes Base64-encoded hashes of every other file in the bundle, as well as extra properties such as whether the resource is optional, or its weight (its priority over other resources).

CodeResources

Conclusion

Code-signing is a very important part of iOS security, and it is important to understand how it works. The various types of blobs discussed in this blog post (CodeDirectory, XML entitlements, DER entitlements, requirements and CodeResources) make up the ad-hoc code signature. This allows iOS to calculate a CDHash for the binary, even if it isn’t actually signed. Thus, all binaries on iOS that are in the trustcache are actually ad-hoc signed binaries, and not signed with a certificate. Binaries are identified by their CDHash, and not their signature, so it doesn’t matter if the binary is signed or not - as long as the CDHash is in the trustcache, the binary will be allowed to run.

In my next blog post, I will be going over the structure of a signed code signature, and how it differs from an ad-hoc code signature. I will also be going over how to create a valid, signed code signature for a MachO binary without any existing code-signing tools. Stay tuned!