# How Dopamine Achieves Semi-Untethered Jailbreak Persistence: A Deep Dive into the Userspace Reboot Strategy

> Discover how Dopamine achieves semi-untethered jailbreak persistence via a userspace reboot strategy. Learn about its userspace restart and boomerang helper process.

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

---

**Dopamine achieves semi-untethered jailbreak persistence through a userspace-restart mechanism that automatically re-injects its launchd hook dylib across launchd restarts while preserving kernel exploit primitives via a helper process called boomerang.**

The Dopamine jailbreak for iOS maintains persistence without requiring a full exploit chain on every boot. Instead of targeting the kernel boot process, it exploits the fact that `launchd`—the system bootstrap daemon—can be restarted without rebooting the entire device. This article examines how the [opa334/Dopamine](https://github.com/opa334/Dopamine) repository implements this semi-untethered persistence model through environment variable inheritance, dylib injection, and primitive recovery.

## The Userspace Reboot Foundation

A **userspace reboot** restarts `launchd` and all user processes while keeping the kernel running. This preserves kernel memory allocations—including the jailbreak's critical exploit primitives—while refreshing the userspace environment. Dopamine exploits this behavior by ensuring its code automatically reloads whenever `launchd` restarts.

The key insight from the Dopamine source code is that environment variables set in the dying `launchd` are inherited by the replacement process. By strategically setting `DYLD_INSERT_LIBRARIES` before triggering a userspace reboot, Dopamine forces the new `launchd` to load its hook automatically.

## The Boomerang Helper: Preserving Exploit Primitives

Before any userspace reboot occurs, Dopamine establishes a **boomerang** helper process that outlives the current `launchd` instance and preserves the jailbreak's kernel capabilities.

### Spawning and Stashing Primitives

In [`BaseBin/launchdhook/src/boomerang.c`](https://github.com/opa334/Dopamine/blob/main/BaseBin/launchdhook/src/boomerang.c), the `boomerang_stashPrimitives()` function launches the boomerang binary and waits for it to request primitives via XPC:

```c
void boomerang_stashPrimitives(void) {
    // Launch boomerang, wait for it to request primitives, then store them
    posix_spawn(&boomerangPid, JBROOT_PATH("/basebin/boomerang"), NULL, &attr, NULL, NULL);
    dispatch_semaphore_wait(boomerangDone, DISPATCH_TIME_FOREVER);
    // Record PID so we can clean up later
    snprintf(pidBuf, 10, "%d", boomerangPid);
    setenv("BOOMERANG_PID", pidBuf, 1);
}

```

The boomerang server in [`BaseBin/libjailbreak/src/jbserver_boomerang.c`](https://github.com/opa334/Dopamine/blob/main/BaseBin/libjailbreak/src/jbserver_boomerang.c) exposes critical primitives—including **physrw** (physical memory read/write) and **thread signing** capabilities—through XPC messages. This allows the new `launchd` instance to recover kernel access without re-exploiting the system.

### The Boomerang Binary

The standalone helper at [`BaseBin/boomerang/src/main.c`](https://github.com/opa334/Dopamine/blob/main/BaseBin/boomerang/src/main.c) connects to the XPC server, receives the stashed primitives, and maintains them in memory. Because it runs as a separate process with its own lifetime, it survives the `launchd` transition that would otherwise destroy in-process state.

## Environment Variable Injection Strategy

The core persistence mechanism resides in `BaseBin/launchdhook/src/main.m`, where the initializer configures three critical environment variables before any userspace reboot:

### DYLD_INSERT_LIBRARIES: Automatic Dylib Loading

```objc
setenv("DYLD_INSERT_LIBRARIES", JBROOT_PATH("/basebin/launchdhook.dylib"), 1);

```

This variable instructs `dyld` to automatically load Dopamine's launch hook into every process—including the replacement `launchd`. The jailbreak dylib is therefore **automatically re-injected** without user intervention.

### DOPAMINE_INITIALIZED: State Detection

```objc
setenv("DOPAMINE_INITIALIZED", "1", 1);

```

This flag enables the initializer to distinguish between:
- **First boot**: Full exploit chain execution required
- **Userspace reboot continuation**: Primitive recovery only, skipping re-exploitation

On startup, the code checks this variable as shown in `BaseBin/launchdhook/src/main.m#L28-L33`:

```objc
NSString *v = @(getenv("DOPAMINE_INITIALIZED"));
if (v) {
    // Resuming after userspace reboot
    gInEarlyBoot = false;
}

```

### LAUNCHD_UUID: Boot Session Tracking

```objc
setenv("LAUNCHD_UUID", [NSUUID UUID].UUIDString.UTF8String, 1);

```

A fresh UUID is generated for each `launchd` instance, enabling precise lifecycle tracking and debugging across multiple userspace reboots.

## Primitive Recovery After Reboot

When `DOPAMINE_INITIALIZED` is detected, the initializer follows a streamlined path designed for speed and reliability:

```objc
if (getenv("DOPAMINE_INITIALIZED")) {
    BOOL firstLoad = false;
    // Re-connect to boomerang, retrieve primitives, and re-initialize
    int err = boomerang_recoverPrimitives(firstLoad, true);
    if (err != 0) {
        // Failure: must fail closed for security
        abort();
    }
}

```

The `boomerang_recoverPrimitives()` function in [`BaseBin/launchdhook/src/boomerang.c`](https://github.com/opa334/Dopamine/blob/main/BaseBin/launchdhook/src/boomerang.c) handles:
1. Connecting to the surviving boomerang process using the PID from `BOOMERANG_PID`
2. Retrieving the stashed kernel primitives via XPC
3. Re-initializing the jailbreak's kernel interface
4. Optionally terminating the boomerang helper (when `shouldEndBoomerang` is true)

This recovery path is significantly faster than the initial exploit chain and requires no user interaction.

## Hidden Jailbreak State Handling

Dopamine supports a **hidden jailbreak** mode where the root filesystem is mounted normally to evade detection. After a userspace reboot, the initializer in `BaseBin/launchdhook/src/main.m#L68-L76` handles remounting:

```objc
if (DOPAMINE_IS_HIDDEN) {
    // Remount fake library after userspace reboot
    jbctl_earlyboot(..., "mount", NULL);
    // Then unmount to restore hidden state
    jbctl_earlyboot(..., "unmount", NULL);
}

```

This ensures the jailbreak remains functional but concealed across persistence events.

## Triggering the Persistence Cycle

The userspace reboot itself is triggered by `BaseBin/watchdoghook/src/main.m` using the `reboot3()` system call with the `RB2_USERREBOOT` flag:

```c
reboot3(RB2_USERREBOOT);  // Userspace-only restart, kernel continues running

```

This single call initiates the entire persistence cycle: `launchd` terminates, a new instance starts with `DYLD_INSERT_LIBRARIES` set, the hook loads, detects `DOPAMINE_INITIALIZED`, and recovers primitives from boomerang.

## Why This Model Is "Semi-Untethered"

Dopamine's persistence model earns the **semi-untethered** designation because:

- **Survives userspace reboots**: Unlimited `launchd` restarts with automatic re-injection
- **Survives resprings**: SpringBoard crashes and restarts trigger the same mechanism
- **Fails on full reboot**: Device power-off or kernel panic clears all kernel memory, requiring re-exploitation via the Dopamine app

This tradeoff balances convenience against security: the jailbreak persists through normal operational interruptions but does not survive conditions that would compromise system integrity.

## Summary

- **Boomerang helper** ([`BaseBin/boomerang/src/main.c`](https://github.com/opa334/Dopamine/blob/main/BaseBin/boomerang/src/main.c)): Preserves kernel primitives across `launchd` restarts via XPC server in [`BaseBin/libjailbreak/src/jbserver_boomerang.c`](https://github.com/opa334/Dopamine/blob/main/BaseBin/libjailbreak/src/jbserver_boomerang.c)

- **Environment injection**: `DYLD_INSERT_LIBRARIES` forces automatic dylib loading; `DOPAMINE_INITIALIZED` enables state detection; `LAUNCHD_UUID` tracks boot sessions

- **Primitive recovery**: `boomerang_recoverPrimitives()` in [`BaseBin/launchdhook/src/boomerang.c`](https://github.com/opa334/Dopamine/blob/main/BaseBin/launchdhook/src/boomerang.c) reconnects to the helper and restores kernel access without re-exploitation

- **Userspace reboot trigger**: `BaseBin/watchdoghook/src/main.m` calls `reboot3(RB2_USERREBOOT)` to initiate the persistence cycle

- **Semi-untethered limitation**: Full device reboots terminate the jailbreak, requiring manual re-activation through the Dopamine application

## Frequently Asked Questions

### What is a userspace reboot on iOS?

A **userspace reboot** restarts the system bootstrap daemon (`launchd`) and all user processes while preserving the kernel and its memory state. Unlike a full device reboot, no hardware reinitialization occurs. Dopamine exploits this behavior to refresh the userspace environment while retaining its kernel exploit primitives, enabling automatic jailbreak restoration without user intervention.

### How does Dopamine differ from fully untethered jailbreaks?

**Fully untethered** jailbreaks persist across complete device reboots by modifying the kernel boot chain or using hardware exploits. Dopamine is **semi-untethered** because its persistence mechanism relies on kernel memory remaining intact. When the device fully reboots, all kernel state is lost and the user must re-run the Dopamine app to restore the jailbreak. This design avoids permanent system modifications that could reduce security or stability.

### What happens if the boomerang helper fails during primitive recovery?

If `boomerang_recoverPrimitives()` returns a non-zero error code, the initializer immediately calls `abort()` as shown in `BaseBin/launchdhook/src/main.m#L35-L37`. This **fail-closed** design prevents partial jailbreak states that could destabilize the system. The user must then manually re-run Dopamine to re-establish the full exploit chain and jailbreak state.

### Can the Dopamine persistence mechanism be detected by apps?

The persistence mechanism itself operates at the `launchd` level before normal app execution, making direct detection difficult. However, indicators such as the presence of `launchdhook.dylib` in memory, modified environment variables, or jailbreak-specific file paths may be detectable. Dopamine's **hidden jailbreak** mode attempts to minimize these artifacts by remounting the root filesystem normally and disabling visible jailbreak features.