Code signing is the mechanism that ensures only code trusted by Apple (or developers given a certificate by Apple) is allowed to run on iOS. Entitlements are the capability-based security system tied to the code signature.


Why This Matters

  • Jailbreaks must bypass code signing to run unsigned code
  • Entitlements determine permissions – many exploit chains require entitlement manipulation
  • Trust Cache injection is an essential step in modern jailbreaks
  • TrollStore works thanks to a CoreTrust bypass – understanding the mechanism = understanding the bug

Code Signing Architecture on iOS

Layer 1: AMFI (AppleMobileFileIntegrity)

AMFI is a kernel extension (kext) that checks code signatures before allowing execution:

  • Every page of code loaded into memory is hash-verified
  • The amfid process (userspace daemon) assists AMFI with complex checks
  • AMFI decides: is this binary allowed to run, and with which entitlements

Verification flow:

exec() system call
  └─→ XNU kernel calls AMFI
       β”œβ”€β†’ Check Trust Cache (kernel-loaded list of trusted cd_hashes)
       β”‚     └─→ Found? β†’ ALLOW (no further checks needed)
       β”‚
       β”œβ”€β†’ Send to amfid (userspace) for full validation
       β”‚     β”œβ”€β†’ Verify certificate chain (up to Apple Root CA)
       β”‚     β”œβ”€β†’ Verify CMS signature
       β”‚     β”œβ”€β†’ Verify Code Directory hashes
       β”‚     └─→ Return result to kernel
       β”‚
       └─→ Decision: allow/deny execution + which entitlements to grant

Layer 2: CoreTrust (iOS 12+)

CoreTrust is a kernel extension that performs certificate validation in the kernel, instead of relying on amfid:

  • Runs in KTRR-protected memory, making it harder to patch
  • Validates the certificate chain for App Store apps
  • If CoreTrust approves, the binary runs with unfiltered entitlements

Why Apple added CoreTrust:

  • Before iOS 12: amfid ran in userspace, so jailbreaks hooked amfid to bypass code signing
  • CoreTrust moved validation into the kernel, making it impossible to hook from userspace

Layer 3: TXM (iOS 17+, A15+)

Trusted Execution Monitor replaces both AMFI and CoreTrust:

  • Runs at EL2 (hypervisor level) – higher than the kernel
  • A kernel exploit alone is not sufficient to bypass TXM

Code Directory & cd_hash

Code Directory

struct CodeDirectory {
    uint32_t magic;        // CSMAGIC_CODEDIRECTORY (0xFADE0C02)
    uint32_t length;
    uint32_t version;
    uint32_t flags;
    uint32_t hashOffset;   // Offset to hash array
    uint32_t identOffset;  // Offset to identifier string
    uint32_t nSpecialSlots; // Number of special slots (before hash array)
    uint32_t nCodeSlots;   // Number of code page hashes
    uint64_t codeLimit;    // Limit of code pages
    uint8_t  hashSize;     // Hash output size (20 for SHA-1, 32 for SHA-256)
    uint8_t  hashType;     // Hash algorithm
    uint8_t  platform;     // Platform identifier
    uint8_t  pageSize;     // log2(page size in bytes), usually 12 (4KB) or 14 (16KB)
    // ... followed by hash slots
};

cd_hash = hash of the Code Directory itself. This is the unique identifier for each signed binary.

Special Slots:

Slot Content
-1 Info.plist hash
-2 Requirements hash
-3 Resource Directory hash
-4 Application Specific (unused)
-5 Entitlements hash
-6 DER Entitlements hash
-7 Launch Constraints hash

Code Slots: Hash of each page (typically 16KB on iOS) of binary code. When a page is loaded into memory, AMFI verifies this hash.


Entitlements

Entitlements are XML/DER plists embedded in the code signature that define capabilities:

Important Entitlements

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN">
<plist version="1.0">
<dict>
    <!-- Allows debugging (get-task-allow β†’ tfp) -->
    <key>get-task-allow</key>
    <true/>

    <!-- Run without sandbox -->
    <key>com.apple.private.security.no-sandbox</key>
    <true/>

    <!-- Platform binary status (Apple binaries) -->
    <key>platform-application</key>
    <true/>

    <!-- Task-for-pid β€” read/write another process's memory -->
    <key>task_for_pid-allow</key>
    <true/>

    <!-- Root access -->
    <key>com.apple.private.persona-mgmt</key>
    <true/>

    <!-- Keychain access groups -->
    <key>keychain-access-groups</key>
    <array>
        <string>com.apple.token</string>
    </array>

    <!-- Sandbox container -->
    <key>com.apple.security.app-sandbox</key>
    <true/>

    <!-- Mach port lookup permissions -->
    <key>com.apple.security.exception.mach-lookup.global-name</key>
    <array>
        <string>com.apple.backboardd</string>
    </array>
</dict>
</plist>

Entitlement Categories

Category Example Who can use
Public com.apple.security.app-sandbox Any developer
Private com.apple.private.security.* Apple binaries only
Restricted task_for_pid-allow Platform binaries with the correct profile only
Managed com.apple.developer.kernel.* Requires Apple approval

Platform binaries = binaries signed by Apple with an Apple-internal certificate. They can use private entitlements that third-party apps cannot.


Trust Cache

The Trust Cache is a list of cd_hashes automatically trusted by AMFI, without requiring full certificate validation.

Trust Cache Types

  1. Static Trust Cache: Embedded in firmware, contains hashes of all system binaries
  2. Loadable Trust Cache: Can be loaded at runtime (requires a special entitlement)
  3. Engineering Trust Cache: For development/debug builds

Trust Cache Structure

struct trust_cache_entry_v2 {
    uint8_t cd_hash[20];     // SHA-1 cd_hash
    uint8_t hash_type;       // 1 = SHA-1, 2 = SHA-256
    uint8_t flags;
    uint16_t constraint_category;
};

struct trust_cache_v2 {
    uint32_t version;        // 2
    uuid_t uuid;
    uint32_t entry_count;
    struct trust_cache_entry_v2 entries[];  // sorted for binary search
};

Trust Cache Injection (Jailbreak technique)

Modern jailbreaks inject trust cache entries so AMFI trusts unsigned binaries:

  1. Gain kernel read/write
  2. Find the loadedTrustCaches linked list in the kernel
  3. Allocate kernel memory for a new trust cache
  4. Add cd_hashes of jailbreak binaries
  5. Link into loadedTrustCaches

Known Bypass Techniques

1. amfid Hook (Pre-iOS 12)

  • Jailbreak injects code into the amfid process
  • Hooks the validation function to always return β€œvalid”
  • CoreTrust fixed this by moving validation into the kernel

2. TOCTOU Attack

  • Change the cd_hash while AMFI is evaluating but hasn’t finished
  • Race condition between β€œcheck hash” and β€œuse hash”

3. CoreTrust Multiple Signer Bug (TrollStore)

  • Binary has multiple signers
  • CoreTrust evaluates the last signer but applies policy from the first signer
  • Allows a self-signed binary to have App Store policy flags
  • This is the basis for TrollStore – permanently install any IPA

4. CoreTrust Root Certificate Bug

  • Craft a certificate chain that tricks CoreTrust into accepting a custom root CA
  • Allows signing a binary with any entitlements

5. Trust Cache Injection (Runtime)

  • After gaining kernel r/w, inject entries into the loaded trust caches
  • Binaries with a matching cd_hash will bypass all signature checks

Practice

# Extract entitlements from a system binary
codesign -d --entitlements :- /usr/libexec/mobileassetd

# View code signature details
codesign -dvvv /usr/bin/ls

# On a jailbroken device, dump the trust cache
# (requires a kernel r/w tool)

# Using jtool2
jtool2 --ent /path/to/binary
jtool2 --sig /path/to/binary

Resources