Skip to main content

Overview

Code signing is a critical security mechanism in iOS that ensures:
  • Authenticity: The app comes from a known, trusted source
  • Integrity: The app hasn’t been modified since it was signed
  • Authorization: The app has permission to run on the device
Every iOS app must be code-signed to execute on a device, whether it’s for development, ad-hoc distribution, or App Store release.
Modifying any part of a signed app bundle invalidates the signature, preventing the app from launching.

Code Signature Components

iOS code signatures consist of several interconnected components:
1

Digital Certificate

An Apple-issued certificate that identifies the developer or organization.
2

Provisioning Profile

A file containing entitlements, certificates, and device IDs (for non-App Store builds).
3

Entitlements

Permissions and capabilities the app is allowed to use.
4

Code Signature Blob

Cryptographic hashes of all executable code and resources.
┌─────────────────────────────────────────┐
│          iOS App Bundle                 │
├─────────────────────────────────────────┤
│  MyApp (Mach-O)                         │
│  ├─ LC_CODE_SIGNATURE → CodeDirectory   │ ← Hashes of code
│  └─ Signature                           │ ← Cryptographic signature
├─────────────────────────────────────────┤
│  _CodeSignature/CodeResources           │ ← Hashes of all resources
├─────────────────────────────────────────┤
│  embedded.mobileprovision               │ ← Entitlements & certificates
├─────────────────────────────────────────┤
│  Info.plist, Assets, Frameworks, etc.   │
└─────────────────────────────────────────┘

Certificate Types

Apple issues different types of certificates for different purposes:
iOS App Development
  • Used for debugging and testing on registered devices
  • Limited to 100 devices per account per year
  • Requires device UDIDs in provisioning profile
  • Cannot be distributed outside registered devices
# Verify development certificate
security find-identity -v -p codesigning

Code Signature Structure

The code signature is embedded in the Mach-O binary’s __LINKEDIT segment and referenced by the LC_CODE_SIGNATURE load command.

CodeDirectory

The CodeDirectory contains:
SHA-1 or SHA-256 hashes of every page (4KB) of executable code.
# View code hashes
codesign -d -vvvv MyApp 2>&1 | grep "CandidateCDHash"
These hashes ensure the executable code hasn’t been modified.
Hashes of special data like:
  • Info.plist (slot -1)
  • Requirements (slot -2)
  • Resource directory (slot -3)
  • Entitlements (slot -5)
  • DER entitlements (slot -7)
# Extract and view special slots
codesign -d --entitlements :- MyApp
The unique bundle identifier for the application.
codesign -d -r- MyApp
# Output: identifier "com.company.appname"
Code signature flags indicating properties like:
  • adhoc: Signed locally without certificate
  • hard: Kill process if signature is invalid
  • runtime: Hardened runtime enabled
  • library-validation: Only load Apple or same-team-signed libraries
codesign -d -vvvv MyApp 2>&1 | grep "CodeDirectory"

CodeResources

The _CodeSignature/CodeResources file is an XML plist containing SHA hashes of all files in the app bundle:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "...">
<plist version="1.0">
<dict>
    <key>files</key>
    <dict>
        <key>Assets.car</key>
        <data>5oqY8JG9h8...</data>  ← SHA-1 hash
        <key>Info.plist</key>
        <data>7kPl2QW3x9...</data>
    </dict>
    <key>files2</key>
    <dict>
        <key>Assets.car</key>
        <dict>
            <key>hash2</key>
            <data>9mKp4RT7y2...</data>  ← SHA-256 hash
        </dict>
    </dict>
</dict>
</plist>
The presence of both files (SHA-1) and files2 (SHA-256) ensures compatibility with older iOS versions.

Provisioning Profiles

Provisioning profiles connect certificates, app IDs, entitlements, and devices.

Profile Structure

A .mobileprovision file is a CMS (Cryptographic Message Syntax) signed plist:
# Extract plist from provisioning profile
security cms -D -i embedded.mobileprovision > profile.plist

Key Profile Properties

AppIDName

Human-readable app ID name

ApplicationIdentifierPrefix

Team ID prefix for the app identifier

DeveloperCertificates

Array of DER-encoded certificates allowed to sign the app

Entitlements

Dictionary of entitlements granted to the app

ProvisionedDevices

Array of device UDIDs (development/ad-hoc only)

ExpirationDate

When the provisioning profile expires

TeamIdentifier

The developer team ID

UUID

Unique identifier for this profile

Profile Example

<dict>
    <key>AppIDName</key>
    <string>My Application</string>
    <key>ApplicationIdentifierPrefix</key>
    <array>
        <string>A1B2C3D4E5</string>
    </array>
    <key>DeveloperCertificates</key>
    <array>
        <data>MIIFmTCCBIG...</data>
    </array>
    <key>Entitlements</key>
    <dict>
        <key>application-identifier</key>
        <string>A1B2C3D4E5.com.company.appname</string>
        <key>get-task-allow</key>
        <true/>
    </dict>
    <key>ExpirationDate</key>
    <date>2026-12-31T23:59:59Z</date>
    <key>ProvisionedDevices</key>
    <array>
        <string>00008030-001234567890001E</string>
    </array>
</dict>

Verification Process

When iOS launches an app, it verifies the code signature in multiple steps:
1

Certificate Chain Validation

Verify the signing certificate chains to a trusted Apple root certificate.
# View certificate chain
codesign -dv --verbose=4 MyApp 2>&1 | grep "Authority"
2

Provisioning Profile Check

For non-App Store apps:
  • Check profile hasn’t expired
  • Verify device UDID is in profile (dev/ad-hoc)
  • Ensure certificate in profile matches signing certificate
3

Code Hash Verification

Verify hashes of all code pages match those in CodeDirectory.
# Verify signature
codesign -v -vvvv MyApp
# Output: MyApp: valid on disk
# Output: MyApp: satisfies its Designated Requirement
4

Resource Verification

Verify hashes of all resources match CodeResources file.
5

Entitlements Validation

Ensure requested entitlements are granted in provisioning profile and not security-critical without proper authorization.
Verification happens both at launch time and continuously during execution. If a memory page is modified, the kernel will detect the hash mismatch.

Inspecting Code Signatures

Using codesign

# Display basic signature info
codesign -dv MyApp

# Output:
Executable=/path/to/MyApp
Identifier=com.company.appname
Format=app bundle with Mach-O thin (arm64)
CodeDirectory v=20500 size=12345 flags=0x0(none) hashes=123+5 location=embedded

Using jtool2

# Advanced code signature analysis
jtool2 --sig MyApp

# Display CodeDirectory
jtool2 --sig --ent MyApp

# Extract embedded provisioning profile
jtool2 --prov MyApp

Using security command

# Extract provisioning profile
security cms -D -i embedded.mobileprovision

# Verify certificate
security verify-cert -c certificate.cer

# List available signing identities
security find-identity -v -p codesigning

Re-signing Applications

Re-signing apps requires:
  • A valid certificate and provisioning profile
  • All embedded frameworks must also be re-signed
  • Original entitlements must be compatible with your profile

Re-signing Steps

1

Extract entitlements

codesign -d --entitlements entitlements.xml OriginalApp
2

Sign embedded frameworks

codesign -f -s "iPhone Developer: Name (ID)" \
  MyApp.app/Frameworks/Framework.framework
3

Replace provisioning profile

cp new.mobileprovision MyApp.app/embedded.mobileprovision
4

Sign the app bundle

codesign -f -s "iPhone Developer: Name (ID)" \
  --entitlements entitlements.xml \
  MyApp.app
5

Verify the signature

codesign -v -vvvv MyApp.app

Automated Re-signing Tools

ios-app-signer

GUI tool for macOS to re-sign IPA files with drag-and-drop interface.

zsign

Command-line tool for re-signing IPAs without Xcode or macOS.

ldid

Lightweight tool for ad-hoc signing (jailbreak environments).

Xcode (xcodebuild)

Official Apple tool for signing during build process.

Security Implications

Bypassing Code Signing

On jailbroken devices:
  • Code signature verification is disabled
  • Apps can be modified and run without valid signatures
  • Unsigned binaries can execute
  • Common on older iOS versions and security research devices

Detecting Tampering

Apps can implement runtime integrity checks:
// Check if running with valid signature
func isCodeSigningValid() -> Bool {
    var status = SecStaticCodeCheckValidityWithErrors(
        code, 
        SecCSFlags(rawValue: kSecCSCheckAllArchitectures),
        nil,
        nil
    )
    return status == errSecSuccess
}

// Detect debugger attachment (get-task-allow)
func isDebuggerAttached() -> Bool {
    var info = kinfo_proc()
    var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()]
    var size = MemoryLayout<kinfo_proc>.stride
    sysctl(&mib, 4, &info, &size, nil, 0)
    return (info.kp_proc.p_flag & P_TRACED) != 0
}

Practical Examples

Example 1: Analyzing Example IPA Signatures

# Extract IPA
unzip ~/workspace/source/ObfuscatedAppExamples/NoTampering.ipa -d /tmp/notamper/
cd /tmp/notamper/Payload/*.app/

# Check code signature
codesign -dv NoTampering

# Extract entitlements
codesign -d --entitlements :- NoTampering

# Verify signature validity
codesign -v -vvvv NoTampering

# Check provisioning profile
security cms -D -i embedded.mobileprovision | plutil -p -

Example 2: Comparing Debug vs Release Signatures

# Debug build typically has:
# - get-task-allow entitlement (allows debugging)
# - Development certificate
# - Device UDIDs in profile

# Release build has:
# - No get-task-allow
# - Distribution certificate  
# - No device restrictions (App Store)

Next Steps

Entitlements

Learn about app permissions and capabilities.

Mach-O Format

Understand where signatures are stored in binaries.

IPA Files

Learn how to extract and inspect signed apps.

Runtime Analysis

Analyze apps at runtime despite code signing.

Build docs developers (and LLMs) love