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

> Learn how Dopamine achieves trustcache injection and code signing bypass on iOS 15-17+. Discover its dual-layer approach for executing unsigned binaries.

- Repository: [Lars Fröder/Dopamine](https://github.com/opa334/Dopamine)
- Tags: internals
- Published: 2026-08-12

---

**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](https://github.com/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`](https://github.com/opa334/Dopamine/blob/main/BaseBin/libjailbreak/src/trustcache.c), Dopamine initializes empty trustcache structures with version metadata and UUID generation:

```c
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`:

```c
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:

```c
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`](https://github.com/opa334/Dopamine/blob/main/trustcache.h)) copies hash entries, sorts them, and writes back to kernel memory:

```c
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`](https://github.com/opa334/Dopamine/blob/main/BaseBin/libjailbreak/src/signatures.c) provides `macho_parse_code_signature()` to extract existing CDHashes from Mach-O binaries:

```c
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:

```c
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:

```c
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`](https://github.com/opa334/Dopamine/blob/main/BaseBin/libjailbreak/src/kernel.h)) for target processes:

```c
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`](https://github.com/opa334/Dopamine/blob/main/trustcache.h) usage adds a single binary's hash:

```c
#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:

```c
#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`](https://github.com/opa334/Dopamine/blob/main/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`](https://github.com/opa334/Dopamine/blob/main/BaseBin/libjailbreak/src/trustcache.c) | Trustcache creation, growth, and linked-list management |
| [`BaseBin/libjailbreak/src/trustcache.h`](https://github.com/opa334/Dopamine/blob/main/BaseBin/libjailbreak/src/trustcache.h) | Public API: `jb_trustcache_add_entries`, `jb_trustcache_clear` |
| [`BaseBin/libjailbreak/src/signatures.c`](https://github.com/opa334/Dopamine/blob/main/BaseBin/libjailbreak/src/signatures.c) | Mach-O parsing, ad-hoc signature generation, `fd_attach_signature` |
| [`BaseBin/libjailbreak/src/signatures.h`](https://github.com/opa334/Dopamine/blob/main/BaseBin/libjailbreak/src/signatures.h) | `fsignatures_t` structure and helper prototypes |
| [`BaseBin/libjailbreak/src/codesign.c`](https://github.com/opa334/Dopamine/blob/main/BaseBin/libjailbreak/src/codesign.c) / `.h` | CDHash calculation and kernel thread signing |
| [`BaseBin/libjailbreak/src/kernel.h`](https://github.com/opa334/Dopamine/blob/main/BaseBin/libjailbreak/src/kernel.h) | `cs_enforcement` flag definitions |
| `BaseBin/jbctl/src/main.m` | CLI interface for trustcache operations |
| [`BaseBin/systemhook/upload.sh`](https://github.com/opa334/Dopamine/blob/main/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`](https://github.com/opa334/Dopamine/blob/main/trustcache.c), signature handling in [`signatures.c`](https://github.com/opa334/Dopamine/blob/main/signatures.c), and kernel flag definitions in [`kernel.h`](https://github.com/opa334/Dopamine/blob/main/kernel.h).

- The public API surface in [`trustcache.h`](https://github.com/opa334/Dopamine/blob/main/trustcache.h) and [`signatures.h`](https://github.com/opa334/Dopamine/blob/main/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`](https://github.com/opa334/Dopamine/blob/main/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`](https://github.com/opa334/Dopamine/blob/main/upload.sh) script and `jbctl` commands automate this re-initialization during jailbreak startup.