What a Jailbreak Binary Tells You Without Running It
A static analysis of the Dopamine jailbreak using Ghidra. 6,660 strings and a handful of class names are enough to reconstruct the full 4-stage attack chain — kernel exploit, filesystem remount, sandbox escape, and persistence — without executing a single instruction.
Most security analysis focuses on what code does when it runs.
Static analysis asks a different question: what does the code say before you run it?
This post is a walkthrough of the Dopamine jailbreak binary using Ghidra. No device. No debugger. No execution. Just a 2.5MB Mach-O and what it reveals about itself.
The Binary
Dopamine is an arm64 Mach-O executable — the standard format for every iOS app and system binary. The file is 2,612,288 bytes. It contains 6,660 embedded strings. Of those, 610 are security-relevant.
That number matters. Strings survive stripping. Even when a binary has no visible symbols, C strings and Objective-C selectors remain in __TEXT/__cstring and __DATA/__objc_selrefs. This is the first thing to pull.
Binary: Dopamine
Format: Mach-O 64-bit (arm64)
Size: 2,612,288 bytes
Total strings: 6,660
Security-relevant: 610
Reading the Architecture from Class Names
Objective-C metadata is almost never stripped. Class names, method selectors, and ivar names all live in __DATA/__objc_classlist and survive the build process intact.
From a single pass, the class hierarchy tells you the entire design:
DOExploitManager — selects and runs kernel exploits
DOExploit — wraps a single exploit with lifecycle methods
DOBootstrapper — manages filesystem setup and package extraction
DOEnvironmentManager — handles sandbox escape and privilege elevation
DOJailbreaker — orchestrates the full chain
DOJailbreakButton — UI layer (confirms this is a user-facing app, not a daemon)
The naming convention (DO prefix = Dopamine Object) is consistent. The separation of concerns is clean. This is not obfuscated malware — it's a release-quality iOS application that happens to escalate kernel privileges.
The Four Stages
Stage 1: Kernel Exploitation
The entry point is DOExploitManager. Its method list reveals a priority-based exploit selection system:
+[DOExploitManager sharedManager]
-[DOExploitManager _loadAvailableExploits]
-[DOExploitManager availableExploitsForType:]
-[DOExploitManager preferredKernelExploit]
-[DOExploitManager selectedKernelExploit]
-[DOExploitManager _findPreferredExploitForType:]
And PAC/PPL bypass variants, required on newer hardware:
-[DOExploitManager preferredPACBypass]
-[DOExploitManager preferredPPLBypass]
The DOExploit class wraps each individual exploit with a consistent interface:
-[DOExploit isSupported] — checks iOS version compatibility
-[DOExploit priority] — used for selection ordering
-[DOExploit run] — executes the exploit
-[DOExploit cleanup] — handles cleanup on failure
This is a plugin architecture. The manager loads whatever exploits are bundled, scores them by support and priority, and runs the best one. The string "kfd" in __TEXT/__cstring identifies one of the bundled kernel exploits (kfd = kernel file descriptor exploit, patched in iOS 16.7.1+).
Stage 2: Filesystem Access
Kernel privileges alone aren't enough — you need writable filesystem access to install anything persistent. DOBootstrapper handles this:
-[DOBootstrapper ensurePrivatePrebootIsWritable]
-[DOBootstrapper isPrivatePrebootMountedWritable]
-[DOBootstrapper remountPrivatePrebootWritable:]
/private/preboot is where iOS stores boot-critical data. On a stock device it's read-only. With kernel r/w primitives from Stage 1, you can remount it writable — which is exactly what this method chain does.
The bootstrap archive format is visible in the strings:
%@/bootstrap_%@.tar.zst
zstd-compressed tar archive, extracted to /private/preboot. The full download-and-extract pipeline is spelled out in the method list:
-[DOBootstrapper bootstrapURL]
-[DOBootstrapper URLSession:downloadTask:didFinishDownloadingToURL:]
-[DOBootstrapper decompressZstd:toTar:]
-[DOBootstrapper extractTar:toPath:]
-[DOBootstrapper fixupPathPermissions]
-[DOBootstrapper finalizeBootstrap]
Stage 3: Sandbox Escape
The sandbox is iOS's primary process isolation mechanism. Breaking out of it requires private entitlements — capabilities Apple reserves for system processes.
These strings appear in the binary verbatim:
%com.apple.private.security.no-sandbox
*com.apple.private.mobileinstall.allowedSPI0e
!com.apple.security.network.client
The no-sandbox entitlement disables the app sandbox entirely. The mobileinstall entitlement grants access to private installation APIs. Together, they let the process write anywhere and install system-level packages.
DOEnvironmentManager owns this stage:
-[DOEnvironmentManager runUnsandboxed:]
-[DOEnvironmentManager runAsRoot:]
-[DOEnvironmentManager setPrivatePrebootProtected:]
-[DOEnvironmentManager isPACBypassRequired]
-[DOEnvironmentManager isPPLBypassRequired]
The PAC/PPL checks matter on A12+ hardware. Pointer Authentication Code (PAC) and Page Protection Layer (PPL) are hardware mitigations that make kernel exploitation significantly harder. The binary checks whether these bypasses are needed, which tells you it was built to target both older (A11 and before) and newer hardware.
Stage 4: Persistence
Installation is what separates a temporary exploit from a jailbreak. DOBootstrapper handles persistence through daemon patching:
-[DOBootstrapper patchBasebinDaemonPlist:]
-[DOBootstrapper patchBasebinDaemonPlists]
-[DOBootstrapper installPackageManagers]
-[DOBootstrapper installPackage:]
-[DOBootstrapper createSymlinkAtPath:toPath:createIntermediateDirectories:]
The mechanism: launchd reads .plist files from system directories at boot. By patching these plists to reference bootstrap executables in /private/preboot, the jailbreak code runs automatically on every reboot — before most security checks.
The Full Chain
App launches (sandboxed, no special privileges)
│
▼
DOExploitManager
selects kfd exploit
────────────────────── kernel r/w achieved
│
▼
DOBootstrapper
remounts /private/preboot writable
downloads bootstrap_%@.tar.zst
────────────────────── filesystem access
│
▼
DOEnvironmentManager
requests no-sandbox + mobileinstall entitlements
────────────────────── sandbox escape
│
▼
DOBootstrapper
patches basebin daemon plists
installs package manager
────────────────────── persistent across reboot
Each stage gates the next. The exploit gives you kernel access; kernel access lets you remount the filesystem; a writable filesystem lets you inject entitlements; entitlements let you patch system daemons; patched daemons give you persistence.
What This Means for Detection
If you're building fraud detection or device attestation, a jailbroken device changes your trust model fundamentally. The binary tells you exactly what artifacts to look for:
Filesystem artifacts:
- Files under
/private/preboot/that shouldn't exist on a stock device bootstrap_*.tar.zstin temp directories- Modified daemon plists in
/var/lib/basebin/
Process artifacts:
- Processes running outside their expected sandbox
- Injected tweaks (dylibs loaded by non-system processes)
Entitlement artifacts:
- Apps claiming
no-sandboxthat aren't system binaries mobileinstallentitlements on user-installed apps
The jailbreak is not subtle. It leaves a trail at every stage. The question is whether your detection runs before the attacker has a chance to hide it.
On Obfuscation
Dopamine is an open-source project. It makes no attempt to hide its behavior. Commercial tools that do the same thing — or that try to bypass jailbreak detection — will be more obfuscated: stripped symbols, XOR'd strings, split class names.
The analysis technique stays the same. You're just doing more reconstruction work: recovering constants from arithmetic patterns, reassembling split strings at xref points, identifying class boundaries from vtable layout.
Static analysis doesn't require the binary to cooperate. It just requires time.
Tools used: Ghidra 12.0.4, custom Python Mach-O parser. Binary: Dopamine.app extracted from Dopamine.ipa.
Stay in the loop
New posts on mobile security, jailbreaks, and iOS reverse engineering.