How Dopamine Manipulates Process Credentials (kauth_cred Operations) in iOS Jailbreak
Dopamine manipulates process credentials by reading kauth_cred structures from kernel memory, adjusting reference counts through atomic operations, and swapping ucred pointers between processes while handling iOS version differences between proc and proc_ro layouts.
The libjailbreak component in opa334/Dopamine provides a complete abstraction layer for kauth_cred operations. This subsystem enables elevated privilege operations by working directly with the kernel's credential structures that control UID/GID identity, group memberships, and privilege flags.
Reading Process Credentials Across iOS Versions
Dopamine's credential manipulation begins with safely reading a process's ucred pointer. In BaseBin/libjailbreak/src/kernel.c, the proc_ucred() function handles semantic differences between iOS versions:
uint64_t proc_ucred(uint64_t proc) {
if (gSystemInfo.kernelStruct.proc_ro.exists) {
uint64_t proc_ro = kread_ptr(proc + koffsetof(proc, proc_ro));
return kread_ptr(proc_ro + koffsetof(proc_ro, ucred));
} else {
return kread_ptr(proc + koffsetof(proc, ucred));
}
}
This abstraction resolves a critical kernel structure change: iOS ≤ 15 stores ucred directly in the proc structure, while iOS ≥ 16 moves it to a read-only sub-structure proc_ro. The function uses offset tables defined in info.h and info.c to remain portable across kernel builds.
Reference Count Management for kauth_cred
Credential objects in XNU use reference counting for memory safety. Dopamine implements four helpers in kernel.c that mirror the kernel's own kauth_cred API:
| Helper | Purpose |
|---|---|
kauth_cred_ref() |
Atomically increments strong reference count |
kauth_cred_unref() |
Atomically decrements strong reference count |
kauth_cred_hold() |
Increments weak reference count |
kauth_cred_drop() |
Decrements weak reference count |
These functions detect the kernel's credential layout at runtime via gSystemInfo.kernelStruct.ucred_rw.exists. When ucred_rw is present, operations target the weak_ref field; otherwise they use the classic ref field in ucred. All operations use kaccess_mapped with atomic fetch-add/subtract to prevent race conditions.
Swapping Credentials Between Processes
The proc_copy_ucred() function in BaseBin/libjailbreak/src/util.c performs safe credential handoffs:
void proc_copy_ucred(uint64_t procCopyFrom, uint64_t procCopyTo) {
uint64_t ucredToCopy = proc_ucred(procCopyFrom);
uint64_t origUcred = proc_ucred(procCopyTo);
// Release the old credential of the target process
kauth_cred_drop(origUcred);
kauth_cred_unref(origUcred);
// Retain the new credential
kauth_cred_ref(ucredToCopy);
kauth_cred_hold(ucredToCopy);
// Finally write the new ucred pointer
proc_ucred_update(procCopyTo, ucredToCopy);
}
The operation follows strict ordering: drop destination references, reference source credential, then write the new pointer via proc_ucred_update(). This prevents use-after-free and maintains kernel invariants.
Root Credential Stealing via XPC
For privilege escalation scenarios, jbclient_root_steal_ucred() in BaseBin/libjailbreak/src/jbclient_xpc.c provides an inter-process mechanism. The function sends an XPC message containing a target ucred address and optionally receives the original credential in exchange. The underlying swap still uses the reference-counted helpers above, ensuring the root credential's lifetime is properly managed even when transferred across process boundaries.
Practical Usage Examples
Spawning a binary with root credentials:
uid_t uid = 0; // root UID
gid_t gid = 0; // root GID
gid_t ruid = 0, rgid = 0;
gid_t groups[NGROUPS_MAX] = {0};
int ret = target_proc_with_ucred("/usr/bin/someBinary", uid, gid,
ruid, rgid, groups);
if (ret == 0) {
/* Child now runs with the root credential */
}
Replacing the current process's credential:
uint64_t myProc = proc_find(getpid());
uint64_t otherProc = proc_find(otherPid);
proc_copy_ucred(otherProc, myProc); // now myProc adopts otherProc's credentials
Key Implementation Files
BaseBin/libjailbreak/src/kernel.c—kauth_cred_*helpers andproc_ucred()accessorBaseBin/libjailbreak/src/util.c—proc_copy_ucred(),proc_ucred_update(),target_proc_with_ucred()BaseBin/libjailbreak/src/jbclient_xpc.c— XPC interface forjbclient_root_steal_ucred()BaseBin/libjailbreak/src/info.h/info.c— Structure offsets forucred,ucred_rw,proc,proc_roBaseBin/libjailbreak/src/kernel.h— Declarations for credential manipulation API
Summary
- Version abstraction:
proc_ucred()transparently handlesprocvs.proc_rolayout differences between iOS 15 and iOS 16+ - Memory safety: Four reference count helpers (
ref/unref/hold/drop) maintain kernel invariants using atomic operations - Safe swapping:
proc_copy_ucred()implements ordered release-retain-write to prevent credential leaks or dangling pointers - Privilege elevation: XPC-based
jbclient_root_steal_ucred()enables cross-process credential transfer for root operations
Frequently Asked Questions
What is kauth_cred in the XNU kernel?
The kauth_cred structure (commonly referenced as ucred) is the kernel's canonical representation of process identity. It contains the real and effective UID/GID, supplementary groups, privilege flags, and audit information. Multiple processes can share the same credential object, which is why reference counting is essential for memory management.
Why does Dopamine need to manipulate process credentials directly?
iOS prohibits standard privilege escalation APIs in sandboxed or non-root contexts. Dopamine bypasses these restrictions by directly modifying kernel structures through read/write primitives, allowing arbitrary processes to assume root identity without traditional BSD syscalls that would be blocked by MAC policies.
How does Dopamine avoid kernel panics during credential swapping?
The implementation enforces three safety properties: atomic reference count operations prevent use-after-free, ordered release-before-retain ensures the destination credential remains valid until replaced, and version-detected offsets guarantee correct structure field access across different kernel builds.
What is the difference between strong and weak credential references?
Strong references (ref/unref) participate in garbage collection—when the count reaches zero, the credential is freed. Weak references (hold/drop) allow caching and temporary borrowing without preventing deallocation. Dopamine manipulates both to match kernel conventions and ensure credential objects are valid throughout operation lifetimes.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →