Code Signing & Entitlements
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
amfidprocess (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
- Static Trust Cache: Embedded in firmware, contains hashes of all system binaries
- Loadable Trust Cache: Can be loaded at runtime (requires a special entitlement)
- 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:
- Gain kernel read/write
- Find the
loadedTrustCacheslinked list in the kernel - Allocate kernel memory for a new trust cache
- Add cd_hashes of jailbreak binaries
- 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
- Apple β Security of Runtime Process
- Dynastic Research β CoreTrust Overview
- Dynastic Research β Bypassing Codesigning
- Alfie CG β Getting Untethered Code Execution
- TrollStore β How it works
- Apple
<Security/SecCode.h>and<security/codesign.h>headers