How to Analyze the Entitlements Database Across Different Firmware Versions

Use the ipsw ent command to extract code-signing entitlements from IPSW files into a SQLite or PostgreSQL database, then query across versions or use ipsw diff --ent to compare entitlement changes between specific firmware builds.

The blacktop/ipsw open-source tool provides a complete workflow to analyze the entitlements database across different firmware versions. By parsing Mach-O binaries inside IPSW files and normalizing entitlement data into a relational schema, security researchers can track how Apple’s code-signing permissions evolve between iOS releases.

Extracting and Storing Entitlements

Architecture

The extraction pipeline consists of four main components that transform raw IPSW data into a queryable database:

Component Role Source File
ent command Parses Mach-O binaries inside an IPSW, extracts normal or DER-encoded entitlements, and builds a map of file → plist XML. [cmd/ipsw/cmd/ent.go](https://github.com/blacktop/ipsw/blob/master/cmd/ipsw/cmd/ent.go)
Entitlement parser Decodes the Apple DER wrapper when a binary only contains EntitlementsDER via the DerDecode function. [internal/codesign/entitlements/entitlements.go](https://github.com/blacktop/ipsw/blob/master/internal/codesign/entitlements/entitlements.go)
Database service Stores parsed entitlements in a relational schema that de-duplicates keys, values, and file paths for fast search. [internal/commands/ent/database.go](https://github.com/blacktop/ipsw/blob/master/internal/commands/ent/database.go)
Data model Defines tables entitlement_keys, entitlement_values, paths, entitlements, and ipsws. [internal/model/ent.go](https://github.com/blacktop/ipsw/blob/master/internal/model/ent.go)

The extraction flow follows this sequence:

  1. Open the IPSW and iterate over AppOS, SystemOS, and other partitions.
  2. Open each Mach-O (fat or thin) and check for CodeSignature.
  3. Read .Entitlements (plain XML) or .EntitlementsDER (DER-encoded).
  4. Decode DER if needed using DerDecode.
  5. Store the XML string in a map keyed by the relative file path.

After the map is built, the DatabaseService bulk-inserts data using storeEntitlementsBulk, which groups inserts into a single transaction per IPSW to populate:

  • Unique keys (entitlement_keys)
  • Unique values (entitlement_values with a short hash for deduplication)
  • Paths (paths) representing the file within the IPSW
  • Entitlement rows linking the above to a specific IPSW (ipsws table)

CLI Example – Build a SQLite Database

Create a fresh SQLite database from multiple IPSW files:


# Create a database from one or more IPSWs (any number of builds)

ipsw ent \
    --sqlite entitlements.db \
    --ipsw iPhone16,1_18.2_22C150_Restore.ipsw \
    iPhone16,1_18.3_22D68_Restore.ipsw
  • --sqlite specifies the local SQLite file path.
  • --ipsw accepts a glob or list of IPSW paths; each file is processed sequentially.

For PostgreSQL deployments (useful for Supabase or shared analysis), replace --sqlite with --pg-* flags; the same relational schema applies.

Querying the Entitlements Database

Architecture

The search API resides in internal/commands/ent/operations.go. It constructs GORM queries based on the EntitlementQuery struct defined in internal/model/ent.go.

You can filter on:

Field Meaning
Version / Build iOS version or build number of the IPSW
Device Device identifier (e.g., iPhone16,1)
KeyPattern SQL LIKE pattern for entitlement keys
ValuePattern Pattern for the stringified value
FilePath Relative file path inside the IPSW

CLI Examples

Search for specific entitlements across all imported firmware versions:


# Find every file that grants the "com.apple.private.set-launch-type.internal" key

ipsw ent \
    --sqlite entitlements.db \
    --key com.apple.private.set-launch-type.internal

Search for entitlement values containing specific strings:


# Search for all entitlements containing the string "LockdownMode"

ipsw ent \
    --sqlite entitlements.db \
    --value LockdownMode

Restrict searches to specific iOS versions:


# Show only entitlements from iOS 18.2 matching "sandbox"

ipsw ent \
    --sqlite entitlements.db \
    --version 18.2 \
    --key sandbox

Extract file paths for scripting:


# Output only file paths (useful for piping to other tools)

ipsw ent \
    --sqlite entitlements.db \
    --key platform-application \
    --file-only

The command outputs a table mapping IPSW version → file → key → value. For raw data processing, query the SQLite database directly:

SELECT i.version, p.path, k.key, v.value
FROM entitlements AS e
JOIN ipsws AS i   ON e.ipsw_id = i.id
JOIN paths AS p   ON e.path_id = p.id
JOIN entitlement_keys AS k ON e.key_id = k.id
JOIN entitlement_values AS v ON e.value_id = v.id
WHERE k.key LIKE '%sandbox%';

Diffing Entitlements Between Firmware Versions

Architecture

The diff engine lives in pkg/diff. When invoked with the --ent flag, the routine:

  1. Extracts entitlements from both IPSWs using the same code path as the ent command.
  2. Normalizes them into a map of file → plistXML.
  3. Compares the two maps using generic diff utilities to generate a structured diff object.
  4. Renders output as Markdown, JSON, or HTML via templates in pkg/diff/format.go.

The relevant struct field is Entitlements, rendered under the "🔑 Entitlements" section in the final output.

CLI Example – Visual Diff

Generate a Markdown diff between two iOS versions:


# Diff entitlements between iOS 18.2 and 18.3, output as Markdown

ipsw diff \
    iPhone16,1_18.2_22C150_Restore.ipsw \
    iPhone16,1_18.3_22D68_Restore.ipsw \
    --ent \
    --markdown \
    --output ./diffs

This produces a diff.md file containing a collapsible section with side-by-side entitlement comparisons and highlighted changes.

For quick console inspection, omit the output flags:

ipsw diff \
    old.ipsw new.ipsw \
    --ent

Programmatic Diff (Go)

Embed entitlement comparison in a Go application using the public API:

import (
    "github.com/blacktop/ipsw/pkg/diff"
)

cfg := &diff.Config{
    IpswOld:      "/path/to/old.ipsw",
    IpswNew:      "/path/to/new.ipsw",
    Entitlements: true, // enable entitlements diff
}
d := diff.New(cfg)

if err := d.Diff(); err != nil {
    // handle error
}
fmt.Println(d.String()) // console output
// Or d.Markdown()/d.ToJSON()/d.ToHTML() for rendered formats.

See [pkg/diff/diff.go](https://github.com/blacktop/ipsw/blob/master/pkg/diff/diff.go) for the complete API implementation.

Complete Workflow Example


# 0. Prepare a folder with the IPSWs you want to compare

mkdir -p ~/ipsws
cp *.ipsw ~/ipsws/

# 1. Build a SQLite DB containing all builds

ipsw ent --sqlite ~/ipsws/ent.db --ipsw ~/ipsws/*.ipsw

# 2. Quick search – "Which builds have the com.apple.private.set-launch-type.internal key?"

ipsw ent --sqlite ~/ipsws/ent.db --key com.apple.private.set-launch-type.internal

# 3. Diff two specific builds (e.g., 18.2 vs 18.3)

ipsw diff \
    ~/ipsws/iPhone16,1_18.2_22C150_Restore.ipsw \
    ~/ipsws/iPhone16,1_18.3_22D68_Restore.ipsw \
    --ent \
    --markdown \
    --output ~/ipsws/diffs

This workflow yields a searchable database (ent.db) spanning all imported firmware versions and a Markdown diff (diff.md) highlighting entitlement changes between specific releases.

Key Source Files

File Description
[internal/commands/ent/ent.go](https://github.com/blacktop/ipsw/blob/master/internal/commands/ent/ent.go) High-level entry point for the ent command – orchestrates extraction and DB storage.
[internal/codesign/entitlements/entitlements.go](https://github.com/blacktop/ipsw/blob/master/internal/codesign/entitlements/entitlements.go) Handles DER-encoded entitlements via DerDecode.
[internal/commands/ent/database.go](https://github.com/blacktop/ipsw/blob/master/internal/commands/ent/database.go) Bulk insertion logic for keys, values, paths, and entitlements.
[internal/model/ent.go](https://github.com/blacktop/ipsw/blob/master/internal/model/ent.go) GORM data model defining tables and query structures.
[cmd/ipsw/cmd/ent.go](https://github.com/blacktop/ipsw/blob/master/cmd/ipsw/cmd/ent.go) CLI wiring – flag handling and user-facing command implementation.
[cmd/ipsw/cmd/diff.go](https://github.com/blacktop/ipsw/blob/master/cmd/ipsw/cmd/diff.go) CLI entry point for the diff command; enables --ent flag.
[pkg/diff/diff.go](https://github.com/blacktop/ipsw/blob/master/pkg/diff/diff.go) Core diff engine – builds diff objects for kernel, kexts, Mach-Os, and entitlements.
[pkg/diff/format.go](https://github.com/blacktop/ipsw/blob/master/pkg/diff/format.go) Markdown/JSON/HTML rendering templates, including the “🔑 Entitlements” section.

Tips and Gotchas

  • DER vs. XML – Some older binaries contain only EntitlementsDER. The ent command automatically falls back to DerDecode in internal/codesign/entitlements/entitlements.go, so manual handling is unnecessary.

  • Large IPSW collections – Use the bulk insert path (storeEntitlementsBulk) for speed; it groups inserts into a single transaction per IPSW rather than individual row commits.

  • Version filtering – The Version field in the ipsws table is derived from the BuildManifest inside the IPSW. Queries using --version translate to WHERE i.version = 'X.Y'.

  • Cross-database queries – If you store data in PostgreSQL (via --pg-* flags), the same CLI flags and SQL schema apply, enabling shared analysis across teams.

Summary

  • Extract entitlements from one or more IPSWs using ipsw ent --sqlite dbfile --ipsw … to populate a relational database.
  • Query the database for specific keys, values, or file paths across all stored firmware versions.
  • Compare entitlement changes between two specific builds using ipsw diff old.ipsw new.ipsw --ent.
  • Leverage the normalized schema in internal/model/ent.go for custom SQL analysis or export to downstream security tools.

Frequently Asked Questions

How do I handle DER-encoded entitlements in older firmware?

The ipsw ent command automatically detects and decodes DER-encoded entitlements using the DerDecode function in internal/codesign/entitlements/entitlements.go. You do not need to specify any special flags; the tool transparently handles both XML and DER formats during extraction.

Can I use PostgreSQL instead of SQLite for team collaboration?

Yes. Instead of --sqlite, supply the PostgreSQL connection flags (--pg-host, --pg-port, --pg-user, --pg-pass, --pg-db). The same schema defined in internal/model/ent.go is used for both backends, allowing multiple analysts to query the same entitlements database concurrently.

What is the performance impact of processing many IPSW files?

The tool uses storeEntitlementsBulk in internal/commands/ent/database.go to group inserts into a single transaction per IPSW. This bulk insertion approach minimizes SQLite lock contention and PostgreSQL round-trips, making it feasible to process hundreds of firmware builds into a single database efficiently.

How can I export entitlement diffs for reporting?

When using ipsw diff --ent, add the --markdown, --json, or --html flags to generate structured output. The templates in pkg/diff/format.go render the "🔑 Entitlements" section containing side-by-side comparisons. Specify --output ./diffs to write the results to disk for inclusion in security audit reports.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →