How Dopamine Handles Trustcache Injection and Code Signing Bypass on iOS Jailbreaks

Dopamine uses a dual-layer approach combining kernel trustcache injection with ad-hoc code signing via F_ADDSIGS to execute unsigned binaries on iOS 15-17+.

Running unsigned code on modern iOS requires defeating multiple layers of Apple's code signing enforcement. The opa334/Dopamine jailbreak solves this through two complementary mechanisms implemented in libjailbreak: trustcache injection that registers binary hashes directly with the kernel, and a code signing bypass that attaches minimal ad-hoc signatures while patching enforcement flags. This article examines both approaches with direct reference to the source implementation.

Trustcache Injection: Making Binaries Appear Trusted

The trustcache injection mechanism creates a custom kernel-resident trustcache (jb_trustcache) and populates it with CDHashes of target binaries. Once a binary's hash exists in this cache, the kernel treats it as trusted regardless of its actual signature status.

Creating the Jailbreak Trustcache

In BaseBin/libjailbreak/src/trustcache.c, Dopamine initializes empty trustcache structures with version metadata and UUID generation:

void _trustcache_file_init(trustcache_file_v1 *file) {
    memset(file, 0, sizeof(*file));
    file->version = 1;
    uuid_generate(file->uuid);
}

The kernel-side allocation happens in _jb_trustcache_grow(), which reserves 16KB of kernel memory via kalloc() and marks the structure with JB_MAGIC:

uint64_t _jb_trustcache_grow(void) {
    if (kalloc(&jbTcKern, 0x4000) != 0) return 0;
    // ... populate trustcache structure ...
    jbTc->magic = JB_MAGIC;
}

Inserting into the Kernel's Linked List

The trustcache_list_insert() function stitches the new trustcache into the kernel's existing chain by updating head pointers directly:

int trustcache_list_insert(uint64_t tcToInsert) {
    uint64_t previousStartTC = _trustcache_list_get_start();
    kwrite64(tcToInsert + koffsetof(trustcache, nextptr), previousStartTC);
    _trustcache_list_set_start(tcToInsert);
}

Adding CDHash Entries

The public API jb_trustcache_add_entries() (declared in trustcache.h) copies hash entries, sorts them, and writes back to kernel memory:

int jb_trustcache_add_entries(struct trustcache_entry_v1 *entries, uint32_t entryCount) {
    // Find free slot, grow if needed, copy entries, sort, write to kernel
}

Each trustcache_entry_v1 contains a 20-byte CDHash that identifies a specific binary. When the kernel's cs_enforcement logic checks a launching binary, it searches all trustcaches in the linked list—finding a match in jb_trustcache causes verification to succeed.

Code Signing Bypass: Ad-Hoc Signatures and Enforcement Patches

While trustcache injection handles kernel-level trust, the code signing bypass addresses userspace signature requirements and file-system level checks through ad-hoc signing.

Parsing Mach-O Signatures

BaseBin/libjailbreak/src/signatures.c provides macho_parse_code_signature() to extract existing CDHashes from Mach-O binaries:

bool macho_parse_code_signature(MachO *macho, cdhash_t cdhashOut) {
    CS_SuperBlob *superblob = macho_read_code_signature(macho);
    // ... parse signature blob ...
}

Generating Ad-Hoc Signatures

For unsigned binaries, code_signature_calculate_adhoc_cdhash() creates a minimal valid signature structure:

bool code_signature_calculate_adhoc_cdhash(CS_SuperBlob *superblob, cdhash_t cdhashOut) {
    // Compute CDHash for ad-hoc signed blob
}

Attaching Signatures via F_ADDSIGS

The critical step uses the fcntl() system call with F_ADDSIGS to attach the signature directly to a file descriptor:

int fd_attach_signature(int fd, fsignatures_t *signature) {
    lseek(fd, signature->fs_file_start, SEEK_SET);
    return fcntl(fd, F_ADDSIGS, signature);
}

This operation modifies the running kernel's view of the file without changing on-disk contents, allowing immediate execution.

Patching cs_enforcement Flags

Dopamine clears the cs_enforcement bit (defined in BaseBin/libjailbreak/src/kernel.h) for target processes:

struct {
    unsigned int cs_enforcement:1;   // code-signing enforcement
    unsigned int cs_debugged:1;      // code-signed but debugged
} ...;

Disabling this flag suppresses library validation, enabling unsigned dylib loading in addition to binary execution.

Practical Implementation Examples

Injecting a Binary into Trustcache

This pattern from trustcache.h usage adds a single binary's hash:

#include "trustcache.h"
#include "info.h"

cdhash_t hash;
if (info_get_cdhash(targetPath, hash) == 0) {
    trustcache_entry_v1 entry = { .hash = {0} };
    memcpy(entry.hash, hash, sizeof(cdhash_t));
    jb_trustcache_add_entries(&entry, 1);
}

Attaching Ad-Hoc Signatures

For binaries requiring immediate signature attachment:

#include "signatures.h"
#include <fcntl.h>

int fd = open("/var/jb/basebin/mytool", O_RDWR);
if (fd >= 0) {
    fsignatures_t sig = {0};
    // Populate sig with signature data via signatures.c helpers
    fd_attach_signature(fd, &sig);
    close(fd);
}

Command-Line Interface

The jbctl utility (in BaseBin/jbctl/src/main.m) exposes trustcache operations:

  • jbctl trustcache create – allocates new trustcache
  • jbctl trustcache clear – removes all entries

The upload.sh script in BaseBin/systemhook/ triggers jbctl rebuild_trustcache for deployment workflows.

Key Source Files and Their Roles

File Purpose
BaseBin/libjailbreak/src/trustcache.c Trustcache creation, growth, and linked-list management
BaseBin/libjailbreak/src/trustcache.h Public API: jb_trustcache_add_entries, jb_trustcache_clear
BaseBin/libjailbreak/src/signatures.c Mach-O parsing, ad-hoc signature generation, fd_attach_signature
BaseBin/libjailbreak/src/signatures.h fsignatures_t structure and helper prototypes
BaseBin/libjailbreak/src/codesign.c / .h CDHash calculation and kernel thread signing
BaseBin/libjailbreak/src/kernel.h cs_enforcement flag definitions
BaseBin/jbctl/src/main.m CLI interface for trustcache operations
BaseBin/systemhook/upload.sh Deployment automation triggering trustcache rebuilds

Summary

  • Trustcache injection creates kernel-resident jb_trustcache structures containing binary CDHashes, inserted directly into the kernel's trustcache linked list via trustcache_list_insert().

  • Code signing bypass generates minimal ad-hoc signatures and attaches them using fcntl(F_ADDSIGS) through fd_attach_signature(), while cs_enforcement flags disable further validation.

  • Both mechanisms operate in BaseBin/libjailbreak/ with clear separation: trustcache code in trustcache.c, signature handling in signatures.c, and kernel flag definitions in kernel.h.

  • The public API surface in trustcache.h and signatures.h enables jailbreak tools to register arbitrary binaries for execution without Apple signatures.

Frequently Asked Questions

What is a trustcache in iOS security?

A trustcache is a kernel data structure containing cryptographic hashes (CDHashes) of trusted code. iOS maintains multiple trustcaches—some from Apple, others added by the system— and checks them during code signature validation. Dopamine's jb_trustcache is a dynamically-created cache that jailbreak tools can populate to whitelist their own binaries.

How does F_ADDSIGS differ from normal code signing?

F_ADDSIGS is a private fcntl() command that attaches code signatures to open file descriptors in-memory, without writing to the underlying file. Unlike standard code signing which requires Apple-issued certificates, F_ADDSIGS accepts ad-hoc signatures—self-signed blobs that satisfy kernel signature parsers but carry no cryptographic trust chain. Dopamine uses this in signatures.c to make unsigned binaries appear signed to the system.

Why does Dopamine use both trustcache injection and ad-hoc signing?

The two mechanisms address different enforcement layers. Trustcache injection bypasses the kernel's cs_enforcement trust evaluation by pre-registering hashes. Ad-hoc signing satisfies file-system and Mach-O loader checks that require a signature blob to be present. Using both ensures compatibility across iOS 15-17+ where enforcement behaviors vary between kernel versions and security configurations.

Can trustcache entries persist across reboots?

No. The jb_trustcache structures exist only in kernel memory allocated via kalloc(). After reboot, the jailbreak must re-run jb_trustcache_grow() and repopulate entries. The upload.sh script and jbctl commands automate this re-initialization during jailbreak startup.

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 →