Advertisement

Thread Rating:
  • 3 Vote(s) - 5 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How to Escape SandBox And Get Root on iOS 12.x once you've got tfp0
#1
Information 
So you wanna build a Jailbreak and there is a tfp0 kernel exploit released (probably by either Sparkey or by Google Project Zero if I know this community well). The tfp0 is basically task_for_pid(0) so the task port for PID 0, which is the kernel_task or the XNU kernel itself. Once you've got tfp0, things are pretty simple because if you possess the Kernel Task Port, you have access to vm_read and vm_write to the Kernel Virtual Memory which means that you can apply various patches to yourself (your process representation in the kernel), or to other processes.

Of course, Apple thought about this and starting with iOS 9 things have changed quite a bit with the advent of KPP or Kernel Patch Protection. With A10 (iPhone 7, 7 Plus), Apple took it one step further after KPP was bypassed in iOS 9 and 10. They introduced KTRR (Kernel Text Read-Only Region), a hardware solution which to this date was only bypassed once, back in the iOS 10 days. KPP and KTRR are very different in the implementation. One is software, another is hardware, and they work in different ways. Siguza has very well writ explanations on how these work in his blog, but it suffices to know that both KTRR and KPP prevent you from patching the Kernel (well, Apple tried... in reality, they only protect the __TEXT region (the code itself) and the constants). Since variable data cannot be protected, it has since been abused to heck and beyond in all post iOS 10 Jailbreaks, the so-called KPPLess paradigm which is not really a KPP bypass, but a KPP compliance. KPP/KTRR don't want us to mess with the constants and the code, and we don't because we don't even have to, at least for now.

iOS is basically a mobile fork of macOS which grew to have its own particularities. macOS is basically FreeBSD + Unix + Apple's own shenanigans, so you will see many similarities with other Unix-based systems. One of these is the fact that each process that runs on the device has a PID (process ID) and a representation somewhere in the kernel. That representation holds everything from your permissions (or lack of thereof) to your PID, your Entitlements (to make AMFI happy) and other bits and pieces which make up the process structure.

So the plan is simple: If you have Kernel Read / Write privileges, you can poke around the kernel to find basically yourself (your app's representation in the Kernel). Once you find that, given the right offsets, you can modify the data to grant yourself new entitlements (they govern what you can and what you can't as an App on iOS), escape yourself from the SandBox, get to be owned by root (root:wheel) rather than mobile which is far more limited, etc.
(Or you can just say freak it and get the kernel credentials and replace yours with the kernel's, but not only that can result in weird bugs due to increased reference counters and other weird glitches, but it's also a bit dangerous).

So, the first thing we wanna do after we've integrated the tfp0 exploit with our Jailbreak Xcode project is to add the proper offsets. These offsets basically represent how far from a specific base address we should expect to find an object in the memory.
The following analogy should clear what offsets are once and for all:
Imagine a street. The street has a number, let's say street 0xFFFFFFFFFFa14eba. Now, there are multiple houses on that street, but we want to find Joe's house. We know that Joe lives at the house 401 so 401 is the offset because from the base address (the start of the street) we need to go 401 positions up (houses) before we find what we need. The same way in the memory we can find things by knowing their offsets relative to a base address.

Problem with these offsets is that they change from a version to another and even from a device to another, so iOS 11's offsets will not work on iOS 12. They may, however, in some cases work from a minor version to another, for example from 12.0 to 12.1.2.

The following structure contains the offsets for iOS 12.x firmware:
 
Code:
uint32_t _kstruct_offsets_12_0[] = {
    0xb, // KSTRUCT_OFFSET_TASK_LCK_MTX_TYPE
    0x10, // KSTRUCT_OFFSET_TASK_REF_COUNT
    0x14, // KSTRUCT_OFFSET_TASK_ACTIVE
    0x20, // KSTRUCT_OFFSET_TASK_VM_MAP
    0x28, // KSTRUCT_OFFSET_TASK_NEXT
    0x30, // KSTRUCT_OFFSET_TASK_PREV
    0x300, // KSTRUCT_OFFSET_TASK_ITK_SPACE
#if __arm64e__
    0x368, // KSTRUCT_OFFSET_TASK_BSD_INFO
#else
    0x358, // KSTRUCT_OFFSET_TASK_BSD_INFO
#endif
#if __arm64e__
    0x3a8, // KSTRUCT_OFFSET_TASK_ALL_IMAGE_INFO_ADDR
#else
    0x398, // KSTRUCT_OFFSET_TASK_ALL_IMAGE_INFO_ADDR
#endif
#if __arm64e__
    0x3b0, // KSTRUCT_OFFSET_TASK_ALL_IMAGE_INFO_SIZE
#else
    0x3a0, // KSTRUCT_OFFSET_TASK_ALL_IMAGE_INFO_SIZE
#endif
#if __arm64e__
    0x400, // KSTRUCT_OFFSET_TASK_TFLAGS
#else
    0x390, // KSTRUCT_OFFSET_TASK_TFLAGS
#endif
    
    0x0, // KSTRUCT_OFFSET_IPC_PORT_IO_BITS
    0x4, // KSTRUCT_OFFSET_IPC_PORT_IO_REFERENCES
    0x40, // KSTRUCT_OFFSET_IPC_PORT_IKMQ_BASE
    0x50, // KSTRUCT_OFFSET_IPC_PORT_MSG_COUNT
    0x60, // KSTRUCT_OFFSET_IPC_PORT_IP_RECEIVER
    0x68, // KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT
    0x88, // KSTRUCT_OFFSET_IPC_PORT_IP_PREMSG
    0x90, // KSTRUCT_OFFSET_IPC_PORT_IP_CONTEXT
    0xa0, // KSTRUCT_OFFSET_IPC_PORT_IP_SRIGHTS
    
    0x60, // KSTRUCT_OFFSET_PROC_PID
    0x108, // KSTRUCT_OFFSET_PROC_P_FD
    0x10, // KSTRUCT_OFFSET_PROC_TASK
    0xf8, // KSTRUCT_OFFSET_PROC_UCRED
    0x8, // KSTRUCT_OFFSET_PROC_P_LIST
    0x290, // KSTRUCT_OFFSET_PROC_P_CSFLAGS
    
    0x0, // KSTRUCT_OFFSET_FILEDESC_FD_OFILES
    
    0x8, // KSTRUCT_OFFSET_FILEPROC_F_FGLOB
    
    0x38, // KSTRUCT_OFFSET_FILEGLOB_FG_DATA
    
    0x10, // KSTRUCT_OFFSET_SOCKET_SO_PCB
    
    0x10, // KSTRUCT_OFFSET_PIPE_BUFFER
    
    0x14, // KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE_SIZE
    0x20, // KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE
    
    0xd8, // KSTRUCT_OFFSET_VNODE_V_MOUNT
    0x78, // KSTRUCT_OFFSET_VNODE_VU_SPECINFO
    0x0, // KSTRUCT_OFFSET_VNODE_V_LOCK
    0xe0, // KSTRUCT_OFFSET_VNODE_V_DATA
    
    0x10, // KSTRUCT_OFFSET_SPECINFO_SI_FLAGS
    
    0x70, // KSTRUCT_OFFSET_MOUNT_MNT_FLAG
    0x8f8, // KSTRUCT_OFFSET_MOUNT_MNT_DATA
    
    0x10, // KSTRUCT_OFFSET_HOST_SPECIAL
    
    0x18, // KSTRUCT_OFFSET_UCRED_CR_UID
    0x78, // KSTRUCT_OFFSET_UCRED_CR_LABEL
    
    0x18, // KSTRUCT_SIZE_IPC_ENTRY
    
    0x6c, // KFREE_ADDR_OFFSET
};

 
// proc_t
unsigned off_p_pid = 0x60;
unsigned off_task = 0x10;
unsigned off_p_uid = 0x28;
unsigned off_p_gid = 0x2C;
unsigned off_p_ruid = 0x30;
unsigned off_p_rgid = 0x34;
unsigned off_p_ucred = 0xF8;
unsigned off_p_csflags = 0x290;
unsigned off_p_comm = 0x250;
unsigned off_p_textvp = 0x230;
unsigned off_p_textoff = 0x238;
unsigned off_p_cputype = 0x2A8;
unsigned off_p_cpu_subtype = 0x2AC;
 
// task_t
unsigned off_itk_self = 0xD8;
unsigned off_itk_sself = 0xE8;
unsigned off_itk_bootstrap = 0x2B8;
unsigned off_itk_space = 0x300;
 
// ipc_port_t
unsigned off_ip_mscount = 0x9C;
unsigned off_ip_srights = 0xA0;
unsigned off_ip_kobject = 0x68;
 
// ucred
unsigned off_ucred_cr_uid = 0x18;
unsigned off_ucred_cr_ruid = 0x1c;
unsigned off_ucred_cr_svuid = 0x20;
unsigned off_ucred_cr_ngroups = 0x24;
unsigned off_ucred_cr_groups = 0x28;
unsigned off_ucred_cr_rgid = 0x68;
unsigned off_ucred_cr_svgid = 0x6c;
unsigned off_ucred_cr_label = 0x78;
 
// vnode
unsigned off_v_type = 0x70;
unsigned off_v_id = 0x74;
unsigned off_v_ubcinfo = 0x78;
unsigned off_v_flags = 0x54;
unsigned off_v_mount = 0xD8; // vnode::v_mount
unsigned off_v_specinfo = 0x78; // vnode::v_specinfo
 
// ubc_info
unsigned off_ubcinfo_csblobs = 0x50; // ubc_info::csblobs
 
// cs_blob
unsigned off_csb_cputype = 0x8;
unsigned off_csb_flags = 0x12;
unsigned off_csb_base_offset = 0x16;
unsigned off_csb_entitlements_offset = 0x90;
unsigned off_csb_signer_type = 0xA0;
unsigned off_csb_platform_binary = 0xA8;
unsigned off_csb_platform_path = 0xAC;
unsigned off_csb_cd = 0x80;
 
// task
unsigned off_t_flags = 0x3A0;
 
// mount
unsigned off_specflags = 0x10;
unsigned off_mnt_flag = 0x70;
unsigned off_mnt_data = 0x8F8;
 
 
unsigned off_special = 2 * sizeof(long);
unsigned off_ipc_space_is_table = 0x20;
 
unsigned off_amfi_slot = 0x8;
unsigned off_sandbox_slot = 0x10;
 
_Bool offs_init() {
    if (SYSTEM_VERSION_BETWEEN_OR_EQUAL_TO(@"12.0", @"13.0") && !SYSTEM_VERSION_EQUAL_TO(@"13.0")) {
        off_p_pid = 0x60;
        off_task = 0x10;
        off_p_uid = 0x28;
        off_p_gid = 0x2C;
        off_p_ruid = 0x30;
        off_p_rgid = 0x34;
        off_p_ucred = 0xF8;
        off_p_csflags = 0x290;
        off_p_comm = 0x250;
        off_p_textvp = 0x230;
        off_p_textoff = 0x238;
        off_p_cputype = 0x2A8;
        off_p_cpu_subtype = 0x2AC;
        off_itk_space = 0x300;
        off_csb_platform_binary = 0xA8;
        off_csb_platform_path = 0xAC;
    } else {
        ERROR("iOS version unsupported.");
        return false;
    }
    return true;
}

Alright, we have the offsets. Now what?
Having tfp0 + offsets means that we can find ourselves, which we need to do if we want to escalate our privileges on iOS. So, in order to find ourselves, we have to read the Kernel memory until we find our PID.
The kernel stores a proc structure for every single process in the memory, here's what that structure looks like:
Code:
struct proc {
LIST_ENTRY(proc) p_list; /* List of all processes. */
 
pid_t p_pid; /* Process identifier. (static)*/
void * task; /* corresponding task (static)*/
struct proc * p_pptr; /* Pointer to parent process.(LL) */
pid_t p_ppid; /* process's parent pid number */
pid_t p_pgrpid; /* process group id of the process (LL)*/
uid_t p_uid;
gid_t p_gid;
uid_t p_ruid;
gid_t p_rgid;
uid_t p_svuid;
gid_t p_svgid;
uint64_t p_uniqueid; /* process unique ID - incremented on fork/spawn/vfork, remains same across exec. */
uint64_t p_puniqueid; /* parent's unique ID - set on fork/spawn/vfork, doesn't change if reparented. */
 
lck_mtx_t p_mlock; /* mutex lock for proc */
 
char p_stat; /* S* process status. (PL)*/
char p_shutdownstate;
char p_kdebug; /* P_KDEBUG eq (CC)*/ 
char p_btrace; /* P_BTRACE eq (CC)*/
 
LIST_ENTRY(proc) p_pglist; /* List of processes in pgrp.(PGL) */
LIST_ENTRY(proc) p_sibling; /* List of sibling processes. (LL)*/
LIST_HEAD(, proc) p_children; /* Pointer to list of children. (LL)*/
TAILQ_HEAD( , uthread) p_uthlist; /* List of uthreads  (PL) */
 
LIST_ENTRY(proc) p_hash; /* Hash chain. (LL)*/
TAILQ_HEAD( ,eventqelt) p_evlist; /* (PL) */
 
#if CONFIG_PERSONAS
struct persona  *p_persona;
LIST_ENTRY(proc) p_persona_list;
#endif
 
lck_mtx_t p_fdmlock; /* proc lock to protect fdesc */
lck_mtx_t p_ucred_mlock; /* mutex lock to protect p_ucred */
 
/* substructures: */
kauth_cred_t p_ucred; /* Process owner's identity. (PUCL) */ !!!
struct filedesc *p_fd; /* Ptr to open files structure. (PFDL) */
struct pstats *p_stats; /* Accounting/statistics (PL). */
struct plimit *p_limit; /* Process limits.(PL) */
 
struct sigacts *p_sigacts; /* Signal actions, state (PL) */
int p_siglist; /* signals captured back from threads */
lck_spin_t p_slock; /* spin lock for itimer/profil protection */
 
#define p_rlimit p_limit->pl_rlimit
 
struct plimit *p_olimit; /* old process limits  - not inherited by child  (PL) */
unsigned int p_flag; /* P_* flags. (atomic bit ops) */
unsigned int p_lflag; /* local flags  (PL) */
unsigned int p_listflag; /* list flags (LL) */
unsigned int p_ladvflag; /* local adv flags (atomic) */
int p_refcount; /* number of outstanding users(LL) */
int p_childrencnt; /* children holding ref on parent (LL) */
int p_parentref; /* children lookup ref on parent (LL) */
 
pid_t p_oppid; /* Save parent pid during ptrace. XXX */
u_int p_xstat; /* Exit status for wait; also stop signal. */
uint8_t p_xhighbits; /* Stores the top byte of exit status to avoid truncation*/
 
#ifdef _PROC_HAS_SCHEDINFO_
/* may need cleanup, not used */
u_int p_estcpu; /* Time averaged value of p_cpticks.(used by aio and proc_comapre) */
fixpt_t p_pctcpu; /* %cpu for this process during p_swtime (used by aio)*/
u_int p_slptime; /* used by proc_compare */
#endif /* _PROC_HAS_SCHEDINFO_ */
 
struct itimerval p_realtimer; /* Alarm timer. (PSL) */
struct timeval p_rtime; /* Real time.(PSL)  */
struct itimerval p_vtimer_user; /* Virtual timers.(PSL)  */
struct itimerval p_vtimer_prof; /* (PSL) */
 
struct timeval p_rlim_cpu; /* Remaining rlim cpu value.(PSL) */
int p_debugger; /*  NU 1: can exec set-bit programs if suser */
boolean_t sigwait; /* indication to suspend (PL) */
void *sigwait_thread; /* 'thread' holding sigwait(PL)  */
void *exit_thread; /* Which thread is exiting(PL)  */
int p_vforkcnt; /* number of outstanding vforks(PL)  */
    void *  p_vforkact;     /* activation running this vfork proc)(static)  */
int p_fpdrainwait; /* (PFDL) */
pid_t p_contproc; /* last PID to send us a SIGCONT (PL) */
 
/* Following fields are info from SIGCHLD (PL) */
pid_t si_pid; /* (PL) */
u_int si_status; /* (PL) */
u_int si_code; /* (PL) */
uid_t si_uid; /* (PL) */
 
void * vm_shm; /* (SYSV SHM Lock) for sysV shared memory */
 
#if CONFIG_DTRACE
user_addr_t p_dtrace_argv; /* (write once, read only after that) */
user_addr_t p_dtrace_envp; /* (write once, read only after that) */
lck_mtx_t p_dtrace_sprlock; /* sun proc lock emulation */
int p_dtrace_probes; /* (PL) are there probes for this proc? */
u_int p_dtrace_count; /* (sprlock) number of DTrace tracepoints */
        uint8_t                         p_dtrace_stop;                  /* indicates a DTrace-desired stop */
struct dtrace_ptss_page* p_dtrace_ptss_pages; /* (sprlock) list of user ptss pages */
struct dtrace_ptss_page_entry* p_dtrace_ptss_free_list; /* (atomic) list of individual ptss entries */
struct dtrace_helpers* p_dtrace_helpers; /* (dtrace_lock) DTrace per-proc private */
struct dof_ioctl_data* p_dtrace_lazy_dofs; /* (sprlock) unloaded dof_helper_t's */
#endif /* CONFIG_DTRACE */
 
/* XXXXXXXXXXXXX BCOPY'ed on fork XXXXXXXXXXXXXXXX */
/* The following fields are all copied upon creation in fork. */
#define p_startcopy p_argslen
 
u_int p_argslen; /* Length of process arguments. */
int  p_argc; /* saved argc for sysctl_procargs() */
user_addr_t user_stack; /* where user stack was allocated */
struct vnode *p_textvp; /* Vnode of executable. */
off_t p_textoff; /* offset in executable vnode */
 
sigset_t p_sigmask; /* DEPRECATED */
sigset_t p_sigignore; /* Signals being ignored. (PL) */
sigset_t p_sigcatch; /* Signals being caught by user.(PL)  */
 
u_char p_priority; /* (NU) Process priority. */
u_char p_resv0; /* (NU) User-priority based on p_cpu and p_nice. */
char p_nice; /* Process "nice" value.(PL) */
u_char p_resv1; /* (NU) User-priority based on p_cpu and p_nice. */
 
// types currently in sys/param.h
command_t   p_comm;
proc_name_t p_name; /* can be changed by the process */
 
 
struct pgrp *p_pgrp; /* Pointer to process group. (LL) */
uint32_t p_csflags; /* flags for codesign (PL) */
uint32_t p_pcaction; /* action  for process control on starvation */
uint8_t p_uuid[16]; /* from LC_UUID load command */
 
/* 
* CPU type and subtype of binary slice executed in
* this process.  Protected by proc lock.
*/
cpu_type_t p_cputype;
cpu_subtype_t p_cpusubtype;
 
/* End area that is copied on creation. */
/* XXXXXXXXXXXXX End of BCOPY'ed on fork (AIOLOCK)XXXXXXXXXXXXXXXX */
#define p_endcopy p_aio_total_count
int p_aio_total_count; /* all allocated AIO requests for this proc */
int p_aio_active_count; /* all unfinished AIO requests for this proc */
TAILQ_HEAD( , aio_workq_entry ) p_aio_activeq; /* active async IO requests */
TAILQ_HEAD( , aio_workq_entry ) p_aio_doneq; /* completed async IO requests */
 
struct klist p_klist;  /* knote list (PL ?)*/
 
struct rusage_superset *p_ru; /* Exit information. (PL) */
int p_sigwaitcnt;
thread_t p_signalholder;
thread_t p_transholder;
 
/* DEPRECATE following field  */
u_short p_acflag; /* Accounting flags. */
volatile u_short p_vfs_iopolicy; /* VFS iopolicy flags. (atomic bit ops) */
 
user_addr_t p_threadstart; /* pthread start fn */
user_addr_t p_wqthread; /* pthread workqueue fn */
int p_pthsize; /* pthread size */
uint32_t p_pth_tsd_offset; /* offset from pthread_t to TSD for new threads */
user_addr_t p_stack_addr_hint; /* stack allocation hint for wq threads */
void * p_wqptr; /* workq ptr */
 
struct  timeval p_start;        /* starting time */
void * p_rcall;
int p_ractive;
int p_idversion; /* version of process identity */
void * p_pthhash; /* pthread waitqueue hash */
volatile uint64_t was_throttled __attribute__((aligned(8))); /* Counter for number of throttled I/Os */
volatile uint64_t did_throttle __attribute__((aligned(8)));  /* Counter for number of I/Os this proc throttled */
 
#if DIAGNOSTIC
unsigned int p_fdlock_pc[4];
unsigned int p_fdunlock_pc[4];
#if SIGNAL_DEBUG
unsigned int lockpc[8];
unsigned int unlockpc[8];
#endif /* SIGNAL_DEBUG */
#endif /* DIAGNOSTIC */
uint64_t p_dispatchqueue_offset;
uint64_t p_dispatchqueue_serialno_offset;
uint64_t p_return_to_kernel_offset;
uint64_t p_mach_thread_self_offset;
#if VM_PRESSURE_EVENTS
struct timeval vm_pressure_last_notify_tstamp;
#endif
 
#if CONFIG_MEMORYSTATUS
/* Fields protected by proc list lock */
TAILQ_ENTRY(proc) p_memstat_list;               /* priority bucket link */
uint32_t          p_memstat_state;              /* state */
int32_t           p_memstat_effectivepriority;  /* priority after transaction state accounted for */
int32_t           p_memstat_requestedpriority;  /* active priority */
uint32_t          p_memstat_dirty;              /* dirty state */
uint64_t          p_memstat_userdata;           /* user state */
uint64_t          p_memstat_idledeadline;       /* time at which process became clean */
uint64_t          p_memstat_idle_start;         /* abstime process transitions into the idle band */
uint64_t   p_memstat_idle_delta;         /* abstime delta spent in idle band */
int32_t           p_memstat_memlimit;           /* cached memory limit, toggles between active and inactive limits */
int32_t           p_memstat_memlimit_active; /* memory limit enforced when process is in active jetsam state */
int32_t           p_memstat_memlimit_inactive; /* memory limit enforced when process is in inactive jetsam state */
#if CONFIG_FREEZE
uint32_t          p_memstat_suspendedfootprint; /* footprint at time of suspensions */
#endif /* CONFIG_FREEZE */
#endif /* CONFIG_MEMORYSTATUS */
 
/* cached proc-specific data required for corpse inspection */
pid_t             p_responsible_pid; /* pid resonsible for this process */
_Atomic uint32_t  p_user_faults; /* count the number of user faults generated */
 
struct os_reason     *p_exit_reason;
};

Here is the code from my Osiris Jailbreak for iOS 12:
 
Code:
uint64_t findOurselves(){
    static uint64_t self = 0;
    if (!self) {
        self = ReadKernel64(current_task + OFFSET(task, bsd_info));
        printf("[i] Found Ourselves at 0x%llx\n", self);
    }
    return self;
}

A simple function which returns an uint64_t (an address / a pointer) to ourselves in the kernel.
The ReadKernel64(...) function is part of the exploit, the memory read primitive and it's actually a wrapper around rk64_via_tfp0() which is a wrapper around another function which is a wrapper around another until we get to mach_vm_read_overwrite() which is part of the iOS kernel. 

The current_task is exported as part of the exploit in kernel_memory.h as such:
 
Code:
/*
* current_task
*
* Description:
* The address of the current task in kernel memory.
*/
 
extern uint64_t current_task;

The OFFSET(task, bsd_info) part is a macro defined in parameters.h on Brandon Azad's exploit:
 
Code:
// Generate the name for an offset.
 
#define OFFSET(base_, object_) _##base_##__##object_##__offset_

If everything goes fine and we have proper Kernel Read privileges and we have the correct offsets, we should now have our address which is our representation inside the Kernel. Let the games begin!
At this point, getting ROOT and escaping the iOS Sandbox is ridiculously simple. Here's the code from my Osiris Jailbreak for iOS 12.
 
Code:
int elevatePrivsAndShaiHulud(){
    if (!shouldUseMachSwap) {
        printf("[i] Preparing to elevate own privileges!\n");
        uint64_t selfProc = findOurselves();
        uint64_t creds = kernel_read64(selfProc + off_p_ucred);
        
        // GID
        kernel_write32(selfProc + off_p_gid, 0);
        kernel_write32(selfProc + off_p_rgid, 0);
        kernel_write32(creds + off_ucred_cr_rgid, 0);
        kernel_write32(creds + off_ucred_cr_svgid, 0);
        printf("[i] STILL HERE!!!!\n");
        
        // UID
        creds = kernel_read64(selfProc + off_p_ucred);
        kernel_write32(selfProc + off_p_uid, 0);
        kernel_write32(selfProc + off_p_ruid, 0);
        kernel_write32(creds + off_ucred_cr_uid, 0);
        kernel_write32(creds + off_ucred_cr_ruid, 0);
        kernel_write32(creds + off_ucred_cr_svuid, 0);
        printf("[i] Set UID = 0\n");
        
        // ShaiHulud
        creds = kernel_read64(selfProc + off_p_ucred);
        uint64_t cr_label = kernel_read64(creds + off_ucred_cr_label);
        kernel_write64(cr_label + off_sandbox_slot, 0);
 
    }
    if (geteuid() == 0) {
        FILE * testfile = fopen("/var/mobile/OsirisJailbreak", "w");
        if (!testfile) {
            printf("[i] We failed! Still Sandboxed\n");
            return -2; // Root, but sandboxed :/
        }else {
            printf("[i] Nuked SandBox, FREEEEEEEEE!!!!!!\n");
            printf("[+] Wrote file OsirisJailbreak to /var/mobile/OsirisJailbreak successfully!\n");
            return 0; // FREE!!!!
        }
    } else {
        return -1; // Not even root :(
    }
    return 0;
}

Bit of a bigger function, so let's break it point by point.
The "if (!shouldUseMachSwap) { ... }" should be ignored. It's there because Osiris Jailbreak uses two exploits, Brandon Azad's and Sparkey's. Brandon's requires that I escape the Sandbox myself, while Sparkey's escapes the sandbox for me, so no need to run the function in that case.

Immediately after that, we find ourselves using the above-mentioned function, then we do a kernel read to grab our credentials from our process representation in the kernel, which happens to be at selfProc + off_p_ucred. So, selfProc is the address returned by the findOurselves() function and it serves as our base address. Our process' representation in the kernel starts there. The off_p_ucred is an offset which as the value 0xF8 as you can see on the offsets code. So base address + 0xF8 = the address of our ucred structure.

After that, you can see that I labeled a block "GID" and another one "UID". GID stands for Group Identifier and UID for User Identified. By default, our app belongs like any other AppStore app to mobile, a less privileged user on iOS with UID 501. We want root because it has way more privileges, that would be UID 0. For the group, we want "wheel" so again, GID 0, but we're listed as mobile (501) already in the kernel. No problem, these are not constants so we can do a simple Kernel Write to those offsets inside our structure to change our GID and UID to 0, so we do:
 
Code:
 
 // GID
        kernel_write32(selfProc + off_p_gid, 0);
        kernel_write32(selfProc + off_p_rgid, 0);
        kernel_write32(creds + off_ucred_cr_rgid, 0);
        kernel_write32(creds + off_ucred_cr_svgid, 0);
        printf("[i] STILL HERE!!!!\n");
        
        // UID
        creds = kernel_read64(selfProc + off_p_ucred);
        kernel_write32(selfProc + off_p_uid, 0);
        kernel_write32(selfProc + off_p_ruid, 0);
        kernel_write32(creds + off_ucred_cr_uid, 0);
        kernel_write32(creds + off_ucred_cr_ruid, 0);
        kernel_write32(creds + off_ucred_cr_svuid, 0);
        printf("[i] Set UID = 0\n");

The kernel_write32(...) function is part of the exploit. One of the Kernel Write primitives. The off_p_uid, off_p_ruid, off_p_gid,  off_p_rgid, off_ucred_cr_uid, off_ucred_cr_ruid and off_ucred_cr_svuid are all offsets from the above-mentioned huge offsets list. We have to set 0 to all these for the desired effect. Once we wrote 0, bam! we're "root:wheel" and not mobile (501) anymore.

The next thing we do is to nuke the Sandbox. By default, we're sandboxed like any third-party iOS app. This means that we can ONLY write to our App's own folders, and we cannot do much. We want full system access so it's time to leave the sand and the box for a better landscape.

In order to nuke the Sandbox, all we need to do is to find again our process' representation in the kernel, use the offsets to locate the cr_label through a kernel_read64(...), then to the cr_label address we add the off_sandbox_slot offset which is 0x10 on iOS 12, and then at the address we obtain we just have to write 0. We do that like this on Osiris Jailbreak:
 
Code:
 
 // ShaiHulud
        creds = kernel_read64(selfProc + off_p_ucred);
        uint64_t cr_label = kernel_read64(creds + off_ucred_cr_label);
        kernel_write64(cr_label + off_sandbox_slot, 0);

Now we should be outta Sandbox and root, but we should do a check before we continue, just in case so we check if we are root through "if (geteuid() == 0) {...} ". If we are root, we proceed to create a new file in a path we should not be allowed to if we're still sandboxed. For this, the "/var/mobile/" suffices. We try to create there an empty file called "OsirisJailbreak". If the file is created, we're clearly out of the sandbox and root, so we suck sid (succeed), else, we failed hard - probably wrong offsets or bad read / write primitives or permissions.
Here's the code from Osiris Jailbreak for performing the check:
Code:
 
if (geteuid() == 0) {
        FILE * testfile = fopen("/var/mobile/OsirisJailbreak", "w");
        if (!testfile) {
            printf("[i] We failed! Still Sandboxed\n");
            return -2; // Root, but sandboxed :/
        }else {
            printf("[i] Nuked SandBox, FREEEEEEEEE!!!!!!\n");
            printf("[+] Wrote file OsirisJailbreak to /var/mobile/OsirisJailbreak successfully!\n");
            return 0; // FREE!!!!
        }
    } else {
        return -1; // Not even root :(
    }
    return 0;

If everything went fine, we should return 0 ("FREE!!!!").

And that is all, that's how you get root and how you escape Sandbox while building your own jailbreak on iOS 12 once you have tfp0. Smile

I hope you enjoyed this article.
All the best, GeoSn0w (@FCE365).
Reply
#2
(08-10-2019, 07:09 PM)GeoSn0w Wrote: So you wanna build a Jailbreak and there is a tfp0 kernel exploit released (probably by either Sparkey or by Google Project Zero if I know this community well). The tfp0 is basically task_for_pid(0) so the task port for PID 0, which is the kernel_task or the XNU kernel itself. Once you've got tfp0, things are pretty simple because if you possess the Kernel Task Port, you have access to vm_read and vm_write to the Kernel Virtual Memory which means that you can apply various patches to yourself (your process representation in the kernel), or to other processes.

Of course, Apple thought about this and starting with iOS 9 things have changed quite a bit with the advent of KPP or Kernel Patch Protection. With A10 (iPhone 7, 7 Plus), Apple took it one step further after KPP was bypassed in iOS 9 and 10. They introduced KTRR (Kernel Text Read-Only Region), a hardware solution which to this date was only bypassed once, back in the iOS 10 days. KPP and KTRR are very different in the implementation. One is software, another is hardware, and they work in different ways. Siguza has very well writ explanations on how these work in his blog, but it suffices to know that both KTRR and KPP prevent you from patching the Kernel (well, Apple tried... in reality, they only protect the __TEXT region (the code itself) and the constants). Since variable data cannot be protected, it has since been abused to heck and beyond in all post iOS 10 Jailbreaks, the so-called KPPLess paradigm which is not really a KPP bypass, but a KPP compliance. KPP/KTRR don't want us to mess with the constants and the code, and we don't because we don't even have to, at least for now.

iOS is basically a mobile fork of macOS which grew to have its own particularities. macOS is basically FreeBSD + Unix + Apple's own shenanigans, so you will see many similarities with other Unix-based systems. One of these is the fact that each process that runs on the device has a PID (process ID) and a representation somewhere in the kernel. That representation holds everything from your permissions (or lack of thereof) to your PID, your Entitlements (to make AMFI happy) and other bits and pieces which make up the process structure.

So the plan is simple: If you have Kernel Read / Write privileges, you can poke around the kernel to find basically yourself (your app's representation in the Kernel). Once you find that, given the right offsets, you can modify the data to grant yourself new entitlements (they govern what you can and what you can't as an App on iOS), escape yourself from the SandBox, get to be owned by root (root:wheel) rather than mobile which is far more limited, etc.
(Or you can just say freak it and get the kernel credentials and replace yours with the kernel's, but not only that can result in weird bugs due to increased reference counters and other weird glitches, but it's also a bit dangerous).

So, the first thing we wanna do after we've integrated the tfp0 exploit with our Jailbreak Xcode project is to add the proper offsets. These offsets basically represent how far from a specific base address we should expect to find an object in the memory.
The following analogy should clear what offsets are once and for all:
Imagine a street. The street has a number, let's say street 0xFFFFFFFFFFa14eba. Now, there are multiple houses on that street, but we want to find Joe's house. We know that Joe lives at the house 401 so 401 is the offset because from the base address (the start of the street) we need to go 401 positions up (houses) before we find what we need. The same way in the memory we can find things by knowing their offsets relative to a base address.

Problem with these offsets is that they change from a version to another and even from a device to another, so iOS 11's offsets will not work on iOS 12. They may, however, in some cases work from a minor version to another, for example from 12.0 to 12.1.2.

The following structure contains the offsets for iOS 12.x firmware:
 
Code:
uint32_t _kstruct_offsets_12_0[] = {
    0xb, // KSTRUCT_OFFSET_TASK_LCK_MTX_TYPE
    0x10, // KSTRUCT_OFFSET_TASK_REF_COUNT
    0x14, // KSTRUCT_OFFSET_TASK_ACTIVE
    0x20, // KSTRUCT_OFFSET_TASK_VM_MAP
    0x28, // KSTRUCT_OFFSET_TASK_NEXT
    0x30, // KSTRUCT_OFFSET_TASK_PREV
    0x300, // KSTRUCT_OFFSET_TASK_ITK_SPACE
#if __arm64e__
    0x368, // KSTRUCT_OFFSET_TASK_BSD_INFO
#else
    0x358, // KSTRUCT_OFFSET_TASK_BSD_INFO
#endif
#if __arm64e__
    0x3a8, // KSTRUCT_OFFSET_TASK_ALL_IMAGE_INFO_ADDR
#else
    0x398, // KSTRUCT_OFFSET_TASK_ALL_IMAGE_INFO_ADDR
#endif
#if __arm64e__
    0x3b0, // KSTRUCT_OFFSET_TASK_ALL_IMAGE_INFO_SIZE
#else
    0x3a0, // KSTRUCT_OFFSET_TASK_ALL_IMAGE_INFO_SIZE
#endif
#if __arm64e__
    0x400, // KSTRUCT_OFFSET_TASK_TFLAGS
#else
    0x390, // KSTRUCT_OFFSET_TASK_TFLAGS
#endif
    
    0x0, // KSTRUCT_OFFSET_IPC_PORT_IO_BITS
    0x4, // KSTRUCT_OFFSET_IPC_PORT_IO_REFERENCES
    0x40, // KSTRUCT_OFFSET_IPC_PORT_IKMQ_BASE
    0x50, // KSTRUCT_OFFSET_IPC_PORT_MSG_COUNT
    0x60, // KSTRUCT_OFFSET_IPC_PORT_IP_RECEIVER
    0x68, // KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT
    0x88, // KSTRUCT_OFFSET_IPC_PORT_IP_PREMSG
    0x90, // KSTRUCT_OFFSET_IPC_PORT_IP_CONTEXT
    0xa0, // KSTRUCT_OFFSET_IPC_PORT_IP_SRIGHTS
    
    0x60, // KSTRUCT_OFFSET_PROC_PID
    0x108, // KSTRUCT_OFFSET_PROC_P_FD
    0x10, // KSTRUCT_OFFSET_PROC_TASK
    0xf8, // KSTRUCT_OFFSET_PROC_UCRED
    0x8, // KSTRUCT_OFFSET_PROC_P_LIST
    0x290, // KSTRUCT_OFFSET_PROC_P_CSFLAGS
    
    0x0, // KSTRUCT_OFFSET_FILEDESC_FD_OFILES
    
    0x8, // KSTRUCT_OFFSET_FILEPROC_F_FGLOB
    
    0x38, // KSTRUCT_OFFSET_FILEGLOB_FG_DATA
    
    0x10, // KSTRUCT_OFFSET_SOCKET_SO_PCB
    
    0x10, // KSTRUCT_OFFSET_PIPE_BUFFER
    
    0x14, // KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE_SIZE
    0x20, // KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE
    
    0xd8, // KSTRUCT_OFFSET_VNODE_V_MOUNT
    0x78, // KSTRUCT_OFFSET_VNODE_VU_SPECINFO
    0x0, // KSTRUCT_OFFSET_VNODE_V_LOCK
    0xe0, // KSTRUCT_OFFSET_VNODE_V_DATA
    
    0x10, // KSTRUCT_OFFSET_SPECINFO_SI_FLAGS
    
    0x70, // KSTRUCT_OFFSET_MOUNT_MNT_FLAG
    0x8f8, // KSTRUCT_OFFSET_MOUNT_MNT_DATA
    
    0x10, // KSTRUCT_OFFSET_HOST_SPECIAL
    
    0x18, // KSTRUCT_OFFSET_UCRED_CR_UID
    0x78, // KSTRUCT_OFFSET_UCRED_CR_LABEL
    
    0x18, // KSTRUCT_SIZE_IPC_ENTRY
    
    0x6c, // KFREE_ADDR_OFFSET
};

 
// proc_t
unsigned off_p_pid = 0x60;
unsigned off_task = 0x10;
unsigned off_p_uid = 0x28;
unsigned off_p_gid = 0x2C;
unsigned off_p_ruid = 0x30;
unsigned off_p_rgid = 0x34;
unsigned off_p_ucred = 0xF8;
unsigned off_p_csflags = 0x290;
unsigned off_p_comm = 0x250;
unsigned off_p_textvp = 0x230;
unsigned off_p_textoff = 0x238;
unsigned off_p_cputype = 0x2A8;
unsigned off_p_cpu_subtype = 0x2AC;
 
// task_t
unsigned off_itk_self = 0xD8;
unsigned off_itk_sself = 0xE8;
unsigned off_itk_bootstrap = 0x2B8;
unsigned off_itk_space = 0x300;
 
// ipc_port_t
unsigned off_ip_mscount = 0x9C;
unsigned off_ip_srights = 0xA0;
unsigned off_ip_kobject = 0x68;
 
// ucred
unsigned off_ucred_cr_uid = 0x18;
unsigned off_ucred_cr_ruid = 0x1c;
unsigned off_ucred_cr_svuid = 0x20;
unsigned off_ucred_cr_ngroups = 0x24;
unsigned off_ucred_cr_groups = 0x28;
unsigned off_ucred_cr_rgid = 0x68;
unsigned off_ucred_cr_svgid = 0x6c;
unsigned off_ucred_cr_label = 0x78;
 
// vnode
unsigned off_v_type = 0x70;
unsigned off_v_id = 0x74;
unsigned off_v_ubcinfo = 0x78;
unsigned off_v_flags = 0x54;
unsigned off_v_mount = 0xD8; // vnode::v_mount
unsigned off_v_specinfo = 0x78; // vnode::v_specinfo
 
// ubc_info
unsigned off_ubcinfo_csblobs = 0x50; // ubc_info::csblobs
 
// cs_blob
unsigned off_csb_cputype = 0x8;
unsigned off_csb_flags = 0x12;
unsigned off_csb_base_offset = 0x16;
unsigned off_csb_entitlements_offset = 0x90;
unsigned off_csb_signer_type = 0xA0;
unsigned off_csb_platform_binary = 0xA8;
unsigned off_csb_platform_path = 0xAC;
unsigned off_csb_cd = 0x80;
 
// task
unsigned off_t_flags = 0x3A0;
 
// mount
unsigned off_specflags = 0x10;
unsigned off_mnt_flag = 0x70;
unsigned off_mnt_data = 0x8F8;
 
 
unsigned off_special = 2 * sizeof(long);
unsigned off_ipc_space_is_table = 0x20;
 
unsigned off_amfi_slot = 0x8;
unsigned off_sandbox_slot = 0x10;
 
_Bool offs_init() {
    if (SYSTEM_VERSION_BETWEEN_OR_EQUAL_TO(@"12.0", @"13.0") && !SYSTEM_VERSION_EQUAL_TO(@"13.0")) {
        off_p_pid = 0x60;
        off_task = 0x10;
        off_p_uid = 0x28;
        off_p_gid = 0x2C;
        off_p_ruid = 0x30;
        off_p_rgid = 0x34;
        off_p_ucred = 0xF8;
        off_p_csflags = 0x290;
        off_p_comm = 0x250;
        off_p_textvp = 0x230;
        off_p_textoff = 0x238;
        off_p_cputype = 0x2A8;
        off_p_cpu_subtype = 0x2AC;
        off_itk_space = 0x300;
        off_csb_platform_binary = 0xA8;
        off_csb_platform_path = 0xAC;
    } else {
        ERROR("iOS version unsupported.");
        return false;
    }
    return true;
}

Alright, we have the offsets. Now what?
Having tfp0 + offsets means that we can find ourselves, which we need to do if we want to escalate our privileges on iOS. So, in order to find ourselves, we have to read the Kernel memory until we find our PID.
The kernel stores a proc structure for every single process in the memory, here's what that structure looks like:
Code:
struct proc {
LIST_ENTRY(proc) p_list; /* List of all processes. */
 
pid_t p_pid; /* Process identifier. (static)*/
void * task; /* corresponding task (static)*/
struct proc * p_pptr; /* Pointer to parent process.(LL) */
pid_t p_ppid; /* process's parent pid number */
pid_t p_pgrpid; /* process group id of the process (LL)*/
uid_t p_uid;
gid_t p_gid;
uid_t p_ruid;
gid_t p_rgid;
uid_t p_svuid;
gid_t p_svgid;
uint64_t p_uniqueid; /* process unique ID - incremented on fork/spawn/vfork, remains same across exec. */
uint64_t p_puniqueid; /* parent's unique ID - set on fork/spawn/vfork, doesn't change if reparented. */
 
lck_mtx_t p_mlock; /* mutex lock for proc */
 
char p_stat; /* S* process status. (PL)*/
char p_shutdownstate;
char p_kdebug; /* P_KDEBUG eq (CC)*/ 
char p_btrace; /* P_BTRACE eq (CC)*/
 
LIST_ENTRY(proc) p_pglist; /* List of processes in pgrp.(PGL) */
LIST_ENTRY(proc) p_sibling; /* List of sibling processes. (LL)*/
LIST_HEAD(, proc) p_children; /* Pointer to list of children. (LL)*/
TAILQ_HEAD( , uthread) p_uthlist; /* List of uthreads  (PL) */
 
LIST_ENTRY(proc) p_hash; /* Hash chain. (LL)*/
TAILQ_HEAD( ,eventqelt) p_evlist; /* (PL) */
 
#if CONFIG_PERSONAS
struct persona  *p_persona;
LIST_ENTRY(proc) p_persona_list;
#endif
 
lck_mtx_t p_fdmlock; /* proc lock to protect fdesc */
lck_mtx_t p_ucred_mlock; /* mutex lock to protect p_ucred */
 
/* substructures: */
kauth_cred_t p_ucred; /* Process owner's identity. (PUCL) */ !!!
struct filedesc *p_fd; /* Ptr to open files structure. (PFDL) */
struct pstats *p_stats; /* Accounting/statistics (PL). */
struct plimit *p_limit; /* Process limits.(PL) */
 
struct sigacts *p_sigacts; /* Signal actions, state (PL) */
int p_siglist; /* signals captured back from threads */
lck_spin_t p_slock; /* spin lock for itimer/profil protection */
 
#define p_rlimit p_limit->pl_rlimit
 
struct plimit *p_olimit; /* old process limits  - not inherited by child  (PL) */
unsigned int p_flag; /* P_* flags. (atomic bit ops) */
unsigned int p_lflag; /* local flags  (PL) */
unsigned int p_listflag; /* list flags (LL) */
unsigned int p_ladvflag; /* local adv flags (atomic) */
int p_refcount; /* number of outstanding users(LL) */
int p_childrencnt; /* children holding ref on parent (LL) */
int p_parentref; /* children lookup ref on parent (LL) */
 
pid_t p_oppid; /* Save parent pid during ptrace. XXX */
u_int p_xstat; /* Exit status for wait; also stop signal. */
uint8_t p_xhighbits; /* Stores the top byte of exit status to avoid truncation*/
 
#ifdef _PROC_HAS_SCHEDINFO_
/* may need cleanup, not used */
u_int p_estcpu; /* Time averaged value of p_cpticks.(used by aio and proc_comapre) */
fixpt_t p_pctcpu; /* %cpu for this process during p_swtime (used by aio)*/
u_int p_slptime; /* used by proc_compare */
#endif /* _PROC_HAS_SCHEDINFO_ */
 
struct itimerval p_realtimer; /* Alarm timer. (PSL) */
struct timeval p_rtime; /* Real time.(PSL)  */
struct itimerval p_vtimer_user; /* Virtual timers.(PSL)  */
struct itimerval p_vtimer_prof; /* (PSL) */
 
struct timeval p_rlim_cpu; /* Remaining rlim cpu value.(PSL) */
int p_debugger; /*  NU 1: can exec set-bit programs if suser */
boolean_t sigwait; /* indication to suspend (PL) */
void *sigwait_thread; /* 'thread' holding sigwait(PL)  */
void *exit_thread; /* Which thread is exiting(PL)  */
int p_vforkcnt; /* number of outstanding vforks(PL)  */
    void *  p_vforkact;     /* activation running this vfork proc)(static)  */
int p_fpdrainwait; /* (PFDL) */
pid_t p_contproc; /* last PID to send us a SIGCONT (PL) */
 
/* Following fields are info from SIGCHLD (PL) */
pid_t si_pid; /* (PL) */
u_int si_status; /* (PL) */
u_int si_code; /* (PL) */
uid_t si_uid; /* (PL) */
 
void * vm_shm; /* (SYSV SHM Lock) for sysV shared memory */
 
#if CONFIG_DTRACE
user_addr_t p_dtrace_argv; /* (write once, read only after that) */
user_addr_t p_dtrace_envp; /* (write once, read only after that) */
lck_mtx_t p_dtrace_sprlock; /* sun proc lock emulation */
int p_dtrace_probes; /* (PL) are there probes for this proc? */
u_int p_dtrace_count; /* (sprlock) number of DTrace tracepoints */
        uint8_t                         p_dtrace_stop;                  /* indicates a DTrace-desired stop */
struct dtrace_ptss_page* p_dtrace_ptss_pages; /* (sprlock) list of user ptss pages */
struct dtrace_ptss_page_entry* p_dtrace_ptss_free_list; /* (atomic) list of individual ptss entries */
struct dtrace_helpers* p_dtrace_helpers; /* (dtrace_lock) DTrace per-proc private */
struct dof_ioctl_data* p_dtrace_lazy_dofs; /* (sprlock) unloaded dof_helper_t's */
#endif /* CONFIG_DTRACE */
 
/* XXXXXXXXXXXXX BCOPY'ed on fork XXXXXXXXXXXXXXXX */
/* The following fields are all copied upon creation in fork. */
#define p_startcopy p_argslen
 
u_int p_argslen; /* Length of process arguments. */
int  p_argc; /* saved argc for sysctl_procargs() */
user_addr_t user_stack; /* where user stack was allocated */
struct vnode *p_textvp; /* Vnode of executable. */
off_t p_textoff; /* offset in executable vnode */
 
sigset_t p_sigmask; /* DEPRECATED */
sigset_t p_sigignore; /* Signals being ignored. (PL) */
sigset_t p_sigcatch; /* Signals being caught by user.(PL)  */
 
u_char p_priority; /* (NU) Process priority. */
u_char p_resv0; /* (NU) User-priority based on p_cpu and p_nice. */
char p_nice; /* Process "nice" value.(PL) */
u_char p_resv1; /* (NU) User-priority based on p_cpu and p_nice. */
 
// types currently in sys/param.h
command_t   p_comm;
proc_name_t p_name; /* can be changed by the process */
 
 
struct pgrp *p_pgrp; /* Pointer to process group. (LL) */
uint32_t p_csflags; /* flags for codesign (PL) */
uint32_t p_pcaction; /* action  for process control on starvation */
uint8_t p_uuid[16]; /* from LC_UUID load command */
 
/* 
* CPU type and subtype of binary slice executed in
* this process.  Protected by proc lock.
*/
cpu_type_t p_cputype;
cpu_subtype_t p_cpusubtype;
 
/* End area that is copied on creation. */
/* XXXXXXXXXXXXX End of BCOPY'ed on fork (AIOLOCK)XXXXXXXXXXXXXXXX */
#define p_endcopy p_aio_total_count
int p_aio_total_count; /* all allocated AIO requests for this proc */
int p_aio_active_count; /* all unfinished AIO requests for this proc */
TAILQ_HEAD( , aio_workq_entry ) p_aio_activeq; /* active async IO requests */
TAILQ_HEAD( , aio_workq_entry ) p_aio_doneq; /* completed async IO requests */
 
struct klist p_klist;  /* knote list (PL ?)*/
 
struct rusage_superset *p_ru; /* Exit information. (PL) */
int p_sigwaitcnt;
thread_t p_signalholder;
thread_t p_transholder;
 
/* DEPRECATE following field  */
u_short p_acflag; /* Accounting flags. */
volatile u_short p_vfs_iopolicy; /* VFS iopolicy flags. (atomic bit ops) */
 
user_addr_t p_threadstart; /* pthread start fn */
user_addr_t p_wqthread; /* pthread workqueue fn */
int p_pthsize; /* pthread size */
uint32_t p_pth_tsd_offset; /* offset from pthread_t to TSD for new threads */
user_addr_t p_stack_addr_hint; /* stack allocation hint for wq threads */
void * p_wqptr; /* workq ptr */
 
struct  timeval p_start;        /* starting time */
void * p_rcall;
int p_ractive;
int p_idversion; /* version of process identity */
void * p_pthhash; /* pthread waitqueue hash */
volatile uint64_t was_throttled __attribute__((aligned(8))); /* Counter for number of throttled I/Os */
volatile uint64_t did_throttle __attribute__((aligned(8)));  /* Counter for number of I/Os this proc throttled */
 
#if DIAGNOSTIC
unsigned int p_fdlock_pc[4];
unsigned int p_fdunlock_pc[4];
#if SIGNAL_DEBUG
unsigned int lockpc[8];
unsigned int unlockpc[8];
#endif /* SIGNAL_DEBUG */
#endif /* DIAGNOSTIC */
uint64_t p_dispatchqueue_offset;
uint64_t p_dispatchqueue_serialno_offset;
uint64_t p_return_to_kernel_offset;
uint64_t p_mach_thread_self_offset;
#if VM_PRESSURE_EVENTS
struct timeval vm_pressure_last_notify_tstamp;
#endif
 
#if CONFIG_MEMORYSTATUS
/* Fields protected by proc list lock */
TAILQ_ENTRY(proc) p_memstat_list;               /* priority bucket link */
uint32_t          p_memstat_state;              /* state */
int32_t           p_memstat_effectivepriority;  /* priority after transaction state accounted for */
int32_t           p_memstat_requestedpriority;  /* active priority */
uint32_t          p_memstat_dirty;              /* dirty state */
uint64_t          p_memstat_userdata;           /* user state */
uint64_t          p_memstat_idledeadline;       /* time at which process became clean */
uint64_t          p_memstat_idle_start;         /* abstime process transitions into the idle band */
uint64_t   p_memstat_idle_delta;         /* abstime delta spent in idle band */
int32_t           p_memstat_memlimit;           /* cached memory limit, toggles between active and inactive limits */
int32_t           p_memstat_memlimit_active; /* memory limit enforced when process is in active jetsam state */
int32_t           p_memstat_memlimit_inactive; /* memory limit enforced when process is in inactive jetsam state */
#if CONFIG_FREEZE
uint32_t          p_memstat_suspendedfootprint; /* footprint at time of suspensions */
#endif /* CONFIG_FREEZE */
#endif /* CONFIG_MEMORYSTATUS */
 
/* cached proc-specific data required for corpse inspection */
pid_t             p_responsible_pid; /* pid resonsible for this process */
_Atomic uint32_t  p_user_faults; /* count the number of user faults generated */
 
struct os_reason     *p_exit_reason;
};

Here is the code from my Osiris Jailbreak for iOS 12:
 
Code:
uint64_t findOurselves(){
    static uint64_t self = 0;
    if (!self) {
        self = ReadKernel64(current_task + OFFSET(task, bsd_info));
        printf("[i] Found Ourselves at 0x%llx\n", self);
    }
    return self;
}

A simple function which returns an uint64_t (an address / a pointer) to ourselves in the kernel.
The ReadKernel64(...) function is part of the exploit, the memory read primitive and it's actually a wrapper around rk64_via_tfp0() which is a wrapper around another function which is a wrapper around another until we get to mach_vm_read_overwrite() which is part of the iOS kernel. 

The current_task is exported as part of the exploit in kernel_memory.h as such:
 
Code:
/*
* current_task
*
* Description:
* The address of the current task in kernel memory.
*/
 
extern uint64_t current_task;

The OFFSET(task, bsd_info) part is a macro defined in parameters.h on Brandon Azad's exploit:
 
Code:
// Generate the name for an offset.
 
#define OFFSET(base_, object_) _##base_##__##object_##__offset_

If everything goes fine and we have proper Kernel Read privileges and we have the correct offsets, we should now have our address which is our representation inside the Kernel. Let the games begin!
At this point, getting ROOT and escaping the iOS Sandbox is ridiculously simple. Here's the code from my Osiris Jailbreak for iOS 12.
 
Code:
int elevatePrivsAndShaiHulud(){
    if (!shouldUseMachSwap) {
        printf("[i] Preparing to elevate own privileges!\n");
        uint64_t selfProc = findOurselves();
        uint64_t creds = kernel_read64(selfProc + off_p_ucred);
        
        // GID
        kernel_write32(selfProc + off_p_gid, 0);
        kernel_write32(selfProc + off_p_rgid, 0);
        kernel_write32(creds + off_ucred_cr_rgid, 0);
        kernel_write32(creds + off_ucred_cr_svgid, 0);
        printf("[i] STILL HERE!!!!\n");
        
        // UID
        creds = kernel_read64(selfProc + off_p_ucred);
        kernel_write32(selfProc + off_p_uid, 0);
        kernel_write32(selfProc + off_p_ruid, 0);
        kernel_write32(creds + off_ucred_cr_uid, 0);
        kernel_write32(creds + off_ucred_cr_ruid, 0);
        kernel_write32(creds + off_ucred_cr_svuid, 0);
        printf("[i] Set UID = 0\n");
        
        // ShaiHulud
        creds = kernel_read64(selfProc + off_p_ucred);
        uint64_t cr_label = kernel_read64(creds + off_ucred_cr_label);
        kernel_write64(cr_label + off_sandbox_slot, 0);
 
    }
    if (geteuid() == 0) {
        FILE * testfile = fopen("/var/mobile/OsirisJailbreak", "w");
        if (!testfile) {
            printf("[i] We failed! Still Sandboxed\n");
            return -2; // Root, but sandboxed :/
        }else {
            printf("[i] Nuked SandBox, FREEEEEEEEE!!!!!!\n");
            printf("[+] Wrote file OsirisJailbreak to /var/mobile/OsirisJailbreak successfully!\n");
            return 0; // FREE!!!!
        }
    } else {
        return -1; // Not even root :(
    }
    return 0;
}

Bit of a bigger function, so let's break it point by point.
The "if (!shouldUseMachSwap) { ... }" should be ignored. It's there because Osiris Jailbreak uses two exploits, Brandon Azad's and Sparkey's. Brandon's requires that I escape the Sandbox myself, while Sparkey's escapes the sandbox for me, so no need to run the function in that case.

Immediately after that, we find ourselves using the above-mentioned function, then we do a kernel read to grab our credentials from our process representation in the kernel, which happens to be at selfProc + off_p_ucred. So, selfProc is the address returned by the findOurselves() function and it serves as our base address. Our process' representation in the kernel starts there. The off_p_ucred is an offset which as the value 0xF8 as you can see on the offsets code. So base address + 0xF8 = the address of our ucred structure.

After that, you can see that I labeled a block "GID" and another one "UID". GID stands for Group Identifier and UID for User Identified. By default, our app belongs like any other AppStore app to mobile, a less privileged user on iOS with UID 501. We want root because it has way more privileges, that would be UID 0. For the group, we want "wheel" so again, GID 0, but we're listed as mobile (501) already in the kernel. No problem, these are not constants so we can do a simple Kernel Write to those offsets inside our structure to change our GID and UID to 0, so we do:
 
Code:
 
 // GID
        kernel_write32(selfProc + off_p_gid, 0);
        kernel_write32(selfProc + off_p_rgid, 0);
        kernel_write32(creds + off_ucred_cr_rgid, 0);
        kernel_write32(creds + off_ucred_cr_svgid, 0);
        printf("[i] STILL HERE!!!!\n");
        
        // UID
        creds = kernel_read64(selfProc + off_p_ucred);
        kernel_write32(selfProc + off_p_uid, 0);
        kernel_write32(selfProc + off_p_ruid, 0);
        kernel_write32(creds + off_ucred_cr_uid, 0);
        kernel_write32(creds + off_ucred_cr_ruid, 0);
        kernel_write32(creds + off_ucred_cr_svuid, 0);
        printf("[i] Set UID = 0\n");

The kernel_write32(...) function is part of the exploit. One of the Kernel Write primitives. The off_p_uid, off_p_ruid, off_p_gid,  off_p_rgid, off_ucred_cr_uid, off_ucred_cr_ruid and off_ucred_cr_svuid are all offsets from the above-mentioned huge offsets list. We have to set 0 to all these for the desired effect. Once we wrote 0, bam! we're "root:wheel" and not mobile (501) anymore.

The next thing we do is to nuke the Sandbox. By default, we're sandboxed like any third-party iOS app. This means that we can ONLY write to our App's own folders, and we cannot do much. We want full system access so it's time to leave the sand and the box for a better landscape.

In order to nuke the Sandbox, all we need to do is to find again our process' representation in the kernel, use the offsets to locate the cr_label through a kernel_read64(...), then to the cr_label address we add the off_sandbox_slot offset which is 0x10 on iOS 12, and then at the address we obtain we just have to write 0. We do that like this on Osiris Jailbreak:
 
Code:
 
 // ShaiHulud
        creds = kernel_read64(selfProc + off_p_ucred);
        uint64_t cr_label = kernel_read64(creds + off_ucred_cr_label);
        kernel_write64(cr_label + off_sandbox_slot, 0);

Now we should be outta Sandbox and root, but we should do a check before we continue, just in case so we check if we are root through "if (geteuid() == 0) {...} ". If we are root, we proceed to create a new file in a path we should not be allowed to if we're still sandboxed. For this, the "/var/mobile/" suffices. We try to create there an empty file called "OsirisJailbreak". If the file is created, we're clearly out of the sandbox and root, so we suck sid (succeed), else, we failed hard - probably wrong offsets or bad read / write primitives or permissions.
Here's the code from Osiris Jailbreak for performing the check:
Code:
 
if (geteuid() == 0) {
        FILE * testfile = fopen("/var/mobile/OsirisJailbreak", "w");
        if (!testfile) {
            printf("[i] We failed! Still Sandboxed\n");
            return -2; // Root, but sandboxed :/
        }else {
            printf("[i] Nuked SandBox, FREEEEEEEEE!!!!!!\n");
            printf("[+] Wrote file OsirisJailbreak to /var/mobile/OsirisJailbreak successfully!\n");
            return 0; // FREE!!!!
        }
    } else {
        return -1; // Not even root :(
    }
    return 0;

If everything went fine, we should return 0 ("FREE!!!!").

And that is all, that's how you get root and how you escape Sandbox while building your own jailbreak on iOS 12 once you have tfp0. Smile

I hope you enjoyed this article.
All the best, GeoSn0w (@FCE365).

Do you know of any patchfinder other than patchfinder64(which is currently half broken on ios 13) that works on ios 13?
Reply
#3
The one from Unc0ver, but it is private.
Reply
#4
Great write up now the key question is.....
Where do i get the equipment and software to start accessing a ios to make a jailbreak?
I mean how does google project and unc0ver do it?
Reply
#5
(08-10-2019, 07:09 PM)GeoSn0w Wrote: So you wanna build a Jailbreak and there is a tfp0 kernel exploit released (probably by either Sparkey or by Google Project Zero if I know this community well). The tfp0 is basically task_for_pid(0) so the task port for PID 0, which is the kernel_task or the XNU kernel itself. Once you've got tfp0, things are pretty simple because if you possess the Kernel Task Port, you have access to vm_read and vm_write to the Kernel Virtual Memory which means that you can apply various patches to yourself (your process representation in the kernel), or to other processes.

Of course, Apple thought about this and starting with iOS 9 things have changed quite a bit with the advent of KPP or Kernel Patch Protection. With A10 (iPhone 7, 7 Plus), Apple took it one step further after KPP was bypassed in iOS 9 and 10. They introduced KTRR (Kernel Text Read-Only Region), a hardware solution which to this date was only bypassed once, back in the iOS 10 days. KPP and KTRR are very different in the implementation. One is software, another is hardware, and they work in different ways. Siguza has very well writ explanations on how these work in his blog, but it suffices to know that both KTRR and KPP prevent you from patching the Kernel (well, Apple tried... in reality, they only protect the __TEXT region (the code itself) and the constants). Since variable data cannot be protected, it has since been abused to heck and beyond in all post iOS 10 Jailbreaks, the so-called KPPLess paradigm which is not really a KPP bypass, but a KPP compliance. KPP/KTRR don't want us to mess with the constants and the code, and we don't because we don't even have to, at least for now.

iOS is basically a mobile fork of macOS which grew to have its own particularities. macOS is basically FreeBSD + Unix + Apple's own shenanigans, so you will see many similarities with other Unix-based systems. One of these is the fact that each process that runs on the device has a PID (process ID) and a representation somewhere in the kernel. That representation holds everything from your permissions (or lack of thereof) to your PID, your Entitlements (to make AMFI happy) and other bits and pieces which make up the process structure.

So the plan is simple: If you have Kernel Read / Write privileges, you can poke around the kernel to find basically yourself (your app's representation in the Kernel). Once you find that, given the right offsets, you can modify the data to grant yourself new entitlements (they govern what you can and what you can't as an App on iOS), escape yourself from the SandBox, get to be owned by root (root:wheel) rather than mobile which is far more limited, etc.
(Or you can just say freak it and get the kernel credentials and replace yours with the kernel's, but not only that can result in weird bugs due to increased reference counters and other weird glitches, but it's also a bit dangerous).

So, the first thing we wanna do after we've integrated the tfp0 exploit with our Jailbreak Xcode project is to add the proper offsets. These offsets basically represent how far from a specific base address we should expect to find an object in the memory.
The following analogy should clear what offsets are once and for all:
Imagine a street. The street has a number, let's say street 0xFFFFFFFFFFa14eba. Now, there are multiple houses on that street, but we want to find Joe's house. We know that Joe lives at the house 401 so 401 is the offset because from the base address (the start of the street) we need to go 401 positions up (houses) before we find what we need. The same way in the memory we can find things by knowing their offsets relative to a base address.

Problem with these offsets is that they change from a version to another and even from a device to another, so iOS 11's offsets will not work on iOS 12. They may, however, in some cases work from a minor version to another, for example from 12.0 to 12.1.2.

The following structure contains the offsets for iOS 12.x firmware:
 
Code:
uint32_t _kstruct_offsets_12_0[] = {
    0xb, // KSTRUCT_OFFSET_TASK_LCK_MTX_TYPE
    0x10, // KSTRUCT_OFFSET_TASK_REF_COUNT
    0x14, // KSTRUCT_OFFSET_TASK_ACTIVE
    0x20, // KSTRUCT_OFFSET_TASK_VM_MAP
    0x28, // KSTRUCT_OFFSET_TASK_NEXT
    0x30, // KSTRUCT_OFFSET_TASK_PREV
    0x300, // KSTRUCT_OFFSET_TASK_ITK_SPACE
#if __arm64e__
    0x368, // KSTRUCT_OFFSET_TASK_BSD_INFO
#else
    0x358, // KSTRUCT_OFFSET_TASK_BSD_INFO
#endif
#if __arm64e__
    0x3a8, // KSTRUCT_OFFSET_TASK_ALL_IMAGE_INFO_ADDR
#else
    0x398, // KSTRUCT_OFFSET_TASK_ALL_IMAGE_INFO_ADDR
#endif
#if __arm64e__
    0x3b0, // KSTRUCT_OFFSET_TASK_ALL_IMAGE_INFO_SIZE
#else
    0x3a0, // KSTRUCT_OFFSET_TASK_ALL_IMAGE_INFO_SIZE
#endif
#if __arm64e__
    0x400, // KSTRUCT_OFFSET_TASK_TFLAGS
#else
    0x390, // KSTRUCT_OFFSET_TASK_TFLAGS
#endif
    
    0x0, // KSTRUCT_OFFSET_IPC_PORT_IO_BITS
    0x4, // KSTRUCT_OFFSET_IPC_PORT_IO_REFERENCES
    0x40, // KSTRUCT_OFFSET_IPC_PORT_IKMQ_BASE
    0x50, // KSTRUCT_OFFSET_IPC_PORT_MSG_COUNT
    0x60, // KSTRUCT_OFFSET_IPC_PORT_IP_RECEIVER
    0x68, // KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT
    0x88, // KSTRUCT_OFFSET_IPC_PORT_IP_PREMSG
    0x90, // KSTRUCT_OFFSET_IPC_PORT_IP_CONTEXT
    0xa0, // KSTRUCT_OFFSET_IPC_PORT_IP_SRIGHTS
    
    0x60, // KSTRUCT_OFFSET_PROC_PID
    0x108, // KSTRUCT_OFFSET_PROC_P_FD
    0x10, // KSTRUCT_OFFSET_PROC_TASK
    0xf8, // KSTRUCT_OFFSET_PROC_UCRED
    0x8, // KSTRUCT_OFFSET_PROC_P_LIST
    0x290, // KSTRUCT_OFFSET_PROC_P_CSFLAGS
    
    0x0, // KSTRUCT_OFFSET_FILEDESC_FD_OFILES
    
    0x8, // KSTRUCT_OFFSET_FILEPROC_F_FGLOB
    
    0x38, // KSTRUCT_OFFSET_FILEGLOB_FG_DATA
    
    0x10, // KSTRUCT_OFFSET_SOCKET_SO_PCB
    
    0x10, // KSTRUCT_OFFSET_PIPE_BUFFER
    
    0x14, // KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE_SIZE
    0x20, // KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE
    
    0xd8, // KSTRUCT_OFFSET_VNODE_V_MOUNT
    0x78, // KSTRUCT_OFFSET_VNODE_VU_SPECINFO
    0x0, // KSTRUCT_OFFSET_VNODE_V_LOCK
    0xe0, // KSTRUCT_OFFSET_VNODE_V_DATA
    
    0x10, // KSTRUCT_OFFSET_SPECINFO_SI_FLAGS
    
    0x70, // KSTRUCT_OFFSET_MOUNT_MNT_FLAG
    0x8f8, // KSTRUCT_OFFSET_MOUNT_MNT_DATA
    
    0x10, // KSTRUCT_OFFSET_HOST_SPECIAL
    
    0x18, // KSTRUCT_OFFSET_UCRED_CR_UID
    0x78, // KSTRUCT_OFFSET_UCRED_CR_LABEL
    
    0x18, // KSTRUCT_SIZE_IPC_ENTRY
    
    0x6c, // KFREE_ADDR_OFFSET
};

 
// proc_t
unsigned off_p_pid = 0x60;
unsigned off_task = 0x10;
unsigned off_p_uid = 0x28;
unsigned off_p_gid = 0x2C;
unsigned off_p_ruid = 0x30;
unsigned off_p_rgid = 0x34;
unsigned off_p_ucred = 0xF8;
unsigned off_p_csflags = 0x290;
unsigned off_p_comm = 0x250;
unsigned off_p_textvp = 0x230;
unsigned off_p_textoff = 0x238;
unsigned off_p_cputype = 0x2A8;
unsigned off_p_cpu_subtype = 0x2AC;
 
// task_t
unsigned off_itk_self = 0xD8;
unsigned off_itk_sself = 0xE8;
unsigned off_itk_bootstrap = 0x2B8;
unsigned off_itk_space = 0x300;
 
// ipc_port_t
unsigned off_ip_mscount = 0x9C;
unsigned off_ip_srights = 0xA0;
unsigned off_ip_kobject = 0x68;
 
// ucred
unsigned off_ucred_cr_uid = 0x18;
unsigned off_ucred_cr_ruid = 0x1c;
unsigned off_ucred_cr_svuid = 0x20;
unsigned off_ucred_cr_ngroups = 0x24;
unsigned off_ucred_cr_groups = 0x28;
unsigned off_ucred_cr_rgid = 0x68;
unsigned off_ucred_cr_svgid = 0x6c;
unsigned off_ucred_cr_label = 0x78;
 
// vnode
unsigned off_v_type = 0x70;
unsigned off_v_id = 0x74;
unsigned off_v_ubcinfo = 0x78;
unsigned off_v_flags = 0x54;
unsigned off_v_mount = 0xD8; // vnode::v_mount
unsigned off_v_specinfo = 0x78; // vnode::v_specinfo
 
// ubc_info
unsigned off_ubcinfo_csblobs = 0x50; // ubc_info::csblobs
 
// cs_blob
unsigned off_csb_cputype = 0x8;
unsigned off_csb_flags = 0x12;
unsigned off_csb_base_offset = 0x16;
unsigned off_csb_entitlements_offset = 0x90;
unsigned off_csb_signer_type = 0xA0;
unsigned off_csb_platform_binary = 0xA8;
unsigned off_csb_platform_path = 0xAC;
unsigned off_csb_cd = 0x80;
 
// task
unsigned off_t_flags = 0x3A0;
 
// mount
unsigned off_specflags = 0x10;
unsigned off_mnt_flag = 0x70;
unsigned off_mnt_data = 0x8F8;
 
 
unsigned off_special = 2 * sizeof(long);
unsigned off_ipc_space_is_table = 0x20;
 
unsigned off_amfi_slot = 0x8;
unsigned off_sandbox_slot = 0x10;
 
_Bool offs_init() {
    if (SYSTEM_VERSION_BETWEEN_OR_EQUAL_TO(@"12.0", @"13.0") && !SYSTEM_VERSION_EQUAL_TO(@"13.0")) {
        off_p_pid = 0x60;
        off_task = 0x10;
        off_p_uid = 0x28;
        off_p_gid = 0x2C;
        off_p_ruid = 0x30;
        off_p_rgid = 0x34;
        off_p_ucred = 0xF8;
        off_p_csflags = 0x290;
        off_p_comm = 0x250;
        off_p_textvp = 0x230;
        off_p_textoff = 0x238;
        off_p_cputype = 0x2A8;
        off_p_cpu_subtype = 0x2AC;
        off_itk_space = 0x300;
        off_csb_platform_binary = 0xA8;
        off_csb_platform_path = 0xAC;
    } else {
        ERROR("iOS version unsupported.");
        return false;
    }
    return true;
}

Alright, we have the offsets. Now what?
Having tfp0 + offsets means that we can find ourselves, which we need to do if we want to escalate our privileges on iOS. So, in order to find ourselves, we have to read the Kernel memory until we find our PID.
The kernel stores a proc structure for every single process in the memory, here's what that structure looks like:
Code:
struct proc {
LIST_ENTRY(proc) p_list; /* List of all processes. */
 
pid_t p_pid; /* Process identifier. (static)*/
void * task; /* corresponding task (static)*/
struct proc * p_pptr; /* Pointer to parent process.(LL) */
pid_t p_ppid; /* process's parent pid number */
pid_t p_pgrpid; /* process group id of the process (LL)*/
uid_t p_uid;
gid_t p_gid;
uid_t p_ruid;
gid_t p_rgid;
uid_t p_svuid;
gid_t p_svgid;
uint64_t p_uniqueid; /* process unique ID - incremented on fork/spawn/vfork, remains same across exec. */
uint64_t p_puniqueid; /* parent's unique ID - set on fork/spawn/vfork, doesn't change if reparented. */
 
lck_mtx_t p_mlock; /* mutex lock for proc */
 
char p_stat; /* S* process status. (PL)*/
char p_shutdownstate;
char p_kdebug; /* P_KDEBUG eq (CC)*/ 
char p_btrace; /* P_BTRACE eq (CC)*/
 
LIST_ENTRY(proc) p_pglist; /* List of processes in pgrp.(PGL) */
LIST_ENTRY(proc) p_sibling; /* List of sibling processes. (LL)*/
LIST_HEAD(, proc) p_children; /* Pointer to list of children. (LL)*/
TAILQ_HEAD( , uthread) p_uthlist; /* List of uthreads  (PL) */
 
LIST_ENTRY(proc) p_hash; /* Hash chain. (LL)*/
TAILQ_HEAD( ,eventqelt) p_evlist; /* (PL) */
 
#if CONFIG_PERSONAS
struct persona  *p_persona;
LIST_ENTRY(proc) p_persona_list;
#endif
 
lck_mtx_t p_fdmlock; /* proc lock to protect fdesc */
lck_mtx_t p_ucred_mlock; /* mutex lock to protect p_ucred */
 
/* substructures: */
kauth_cred_t p_ucred; /* Process owner's identity. (PUCL) */ !!!
struct filedesc *p_fd; /* Ptr to open files structure. (PFDL) */
struct pstats *p_stats; /* Accounting/statistics (PL). */
struct plimit *p_limit; /* Process limits.(PL) */
 
struct sigacts *p_sigacts; /* Signal actions, state (PL) */
int p_siglist; /* signals captured back from threads */
lck_spin_t p_slock; /* spin lock for itimer/profil protection */
 
#define p_rlimit p_limit->pl_rlimit
 
struct plimit *p_olimit; /* old process limits  - not inherited by child  (PL) */
unsigned int p_flag; /* P_* flags. (atomic bit ops) */
unsigned int p_lflag; /* local flags  (PL) */
unsigned int p_listflag; /* list flags (LL) */
unsigned int p_ladvflag; /* local adv flags (atomic) */
int p_refcount; /* number of outstanding users(LL) */
int p_childrencnt; /* children holding ref on parent (LL) */
int p_parentref; /* children lookup ref on parent (LL) */
 
pid_t p_oppid; /* Save parent pid during ptrace. XXX */
u_int p_xstat; /* Exit status for wait; also stop signal. */
uint8_t p_xhighbits; /* Stores the top byte of exit status to avoid truncation*/
 
#ifdef _PROC_HAS_SCHEDINFO_
/* may need cleanup, not used */
u_int p_estcpu; /* Time averaged value of p_cpticks.(used by aio and proc_comapre) */
fixpt_t p_pctcpu; /* %cpu for this process during p_swtime (used by aio)*/
u_int p_slptime; /* used by proc_compare */
#endif /* _PROC_HAS_SCHEDINFO_ */
 
struct itimerval p_realtimer; /* Alarm timer. (PSL) */
struct timeval p_rtime; /* Real time.(PSL)  */
struct itimerval p_vtimer_user; /* Virtual timers.(PSL)  */
struct itimerval p_vtimer_prof; /* (PSL) */
 
struct timeval p_rlim_cpu; /* Remaining rlim cpu value.(PSL) */
int p_debugger; /*  NU 1: can exec set-bit programs if suser */
boolean_t sigwait; /* indication to suspend (PL) */
void *sigwait_thread; /* 'thread' holding sigwait(PL)  */
void *exit_thread; /* Which thread is exiting(PL)  */
int p_vforkcnt; /* number of outstanding vforks(PL)  */
    void *  p_vforkact;     /* activation running this vfork proc)(static)  */
int p_fpdrainwait; /* (PFDL) */
pid_t p_contproc; /* last PID to send us a SIGCONT (PL) */
 
/* Following fields are info from SIGCHLD (PL) */
pid_t si_pid; /* (PL) */
u_int si_status; /* (PL) */
u_int si_code; /* (PL) */
uid_t si_uid; /* (PL) */
 
void * vm_shm; /* (SYSV SHM Lock) for sysV shared memory */
 
#if CONFIG_DTRACE
user_addr_t p_dtrace_argv; /* (write once, read only after that) */
user_addr_t p_dtrace_envp; /* (write once, read only after that) */
lck_mtx_t p_dtrace_sprlock; /* sun proc lock emulation */
int p_dtrace_probes; /* (PL) are there probes for this proc? */
u_int p_dtrace_count; /* (sprlock) number of DTrace tracepoints */
        uint8_t                         p_dtrace_stop;                  /* indicates a DTrace-desired stop */
struct dtrace_ptss_page* p_dtrace_ptss_pages; /* (sprlock) list of user ptss pages */
struct dtrace_ptss_page_entry* p_dtrace_ptss_free_list; /* (atomic) list of individual ptss entries */
struct dtrace_helpers* p_dtrace_helpers; /* (dtrace_lock) DTrace per-proc private */
struct dof_ioctl_data* p_dtrace_lazy_dofs; /* (sprlock) unloaded dof_helper_t's */
#endif /* CONFIG_DTRACE */
 
/* XXXXXXXXXXXXX BCOPY'ed on fork XXXXXXXXXXXXXXXX */
/* The following fields are all copied upon creation in fork. */
#define p_startcopy p_argslen
 
u_int p_argslen; /* Length of process arguments. */
int  p_argc; /* saved argc for sysctl_procargs() */
user_addr_t user_stack; /* where user stack was allocated */
struct vnode *p_textvp; /* Vnode of executable. */
off_t p_textoff; /* offset in executable vnode */
 
sigset_t p_sigmask; /* DEPRECATED */
sigset_t p_sigignore; /* Signals being ignored. (PL) */
sigset_t p_sigcatch; /* Signals being caught by user.(PL)  */
 
u_char p_priority; /* (NU) Process priority. */
u_char p_resv0; /* (NU) User-priority based on p_cpu and p_nice. */
char p_nice; /* Process "nice" value.(PL) */
u_char p_resv1; /* (NU) User-priority based on p_cpu and p_nice. */
 
// types currently in sys/param.h
command_t   p_comm;
proc_name_t p_name; /* can be changed by the process */
 
 
struct pgrp *p_pgrp; /* Pointer to process group. (LL) */
uint32_t p_csflags; /* flags for codesign (PL) */
uint32_t p_pcaction; /* action  for process control on starvation */
uint8_t p_uuid[16]; /* from LC_UUID load command */
 
/* 
* CPU type and subtype of binary slice executed in
* this process.  Protected by proc lock.
*/
cpu_type_t p_cputype;
cpu_subtype_t p_cpusubtype;
 
/* End area that is copied on creation. */
/* XXXXXXXXXXXXX End of BCOPY'ed on fork (AIOLOCK)XXXXXXXXXXXXXXXX */
#define p_endcopy p_aio_total_count
int p_aio_total_count; /* all allocated AIO requests for this proc */
int p_aio_active_count; /* all unfinished AIO requests for this proc */
TAILQ_HEAD( , aio_workq_entry ) p_aio_activeq; /* active async IO requests */
TAILQ_HEAD( , aio_workq_entry ) p_aio_doneq; /* completed async IO requests */
 
struct klist p_klist;  /* knote list (PL ?)*/
 
struct rusage_superset *p_ru; /* Exit information. (PL) */
int p_sigwaitcnt;
thread_t p_signalholder;
thread_t p_transholder;
 
/* DEPRECATE following field  */
u_short p_acflag; /* Accounting flags. */
volatile u_short p_vfs_iopolicy; /* VFS iopolicy flags. (atomic bit ops) */
 
user_addr_t p_threadstart; /* pthread start fn */
user_addr_t p_wqthread; /* pthread workqueue fn */
int p_pthsize; /* pthread size */
uint32_t p_pth_tsd_offset; /* offset from pthread_t to TSD for new threads */
user_addr_t p_stack_addr_hint; /* stack allocation hint for wq threads */
void * p_wqptr; /* workq ptr */
 
struct  timeval p_start;        /* starting time */
void * p_rcall;
int p_ractive;
int p_idversion; /* version of process identity */
void * p_pthhash; /* pthread waitqueue hash */
volatile uint64_t was_throttled __attribute__((aligned(8))); /* Counter for number of throttled I/Os */
volatile uint64_t did_throttle __attribute__((aligned(8)));  /* Counter for number of I/Os this proc throttled */
 
#if DIAGNOSTIC
unsigned int p_fdlock_pc[4];
unsigned int p_fdunlock_pc[4];
#if SIGNAL_DEBUG
unsigned int lockpc[8];
unsigned int unlockpc[8];
#endif /* SIGNAL_DEBUG */
#endif /* DIAGNOSTIC */
uint64_t p_dispatchqueue_offset;
uint64_t p_dispatchqueue_serialno_offset;
uint64_t p_return_to_kernel_offset;
uint64_t p_mach_thread_self_offset;
#if VM_PRESSURE_EVENTS
struct timeval vm_pressure_last_notify_tstamp;
#endif
 
#if CONFIG_MEMORYSTATUS
/* Fields protected by proc list lock */
TAILQ_ENTRY(proc) p_memstat_list;               /* priority bucket link */
uint32_t          p_memstat_state;              /* state */
int32_t           p_memstat_effectivepriority;  /* priority after transaction state accounted for */
int32_t           p_memstat_requestedpriority;  /* active priority */
uint32_t          p_memstat_dirty;              /* dirty state */
uint64_t          p_memstat_userdata;           /* user state */
uint64_t          p_memstat_idledeadline;       /* time at which process became clean */
uint64_t          p_memstat_idle_start;         /* abstime process transitions into the idle band */
uint64_t   p_memstat_idle_delta;         /* abstime delta spent in idle band */
int32_t           p_memstat_memlimit;           /* cached memory limit, toggles between active and inactive limits */
int32_t           p_memstat_memlimit_active; /* memory limit enforced when process is in active jetsam state */
int32_t           p_memstat_memlimit_inactive; /* memory limit enforced when process is in inactive jetsam state */
#if CONFIG_FREEZE
uint32_t          p_memstat_suspendedfootprint; /* footprint at time of suspensions */
#endif /* CONFIG_FREEZE */
#endif /* CONFIG_MEMORYSTATUS */
 
/* cached proc-specific data required for corpse inspection */
pid_t             p_responsible_pid; /* pid resonsible for this process */
_Atomic uint32_t  p_user_faults; /* count the number of user faults generated */
 
struct os_reason     *p_exit_reason;
};

Here is the code from my Osiris Jailbreak for iOS 12:
 
Code:
uint64_t findOurselves(){
    static uint64_t self = 0;
    if (!self) {
        self = ReadKernel64(current_task + OFFSET(task, bsd_info));
        printf("[i] Found Ourselves at 0x%llx\n", self);
    }
    return self;
}

A simple function which returns an uint64_t (an address / a pointer) to ourselves in the kernel.
The ReadKernel64(...) function is part of the exploit, the memory read primitive and it's actually a wrapper around rk64_via_tfp0() which is a wrapper around another function which is a wrapper around another until we get to mach_vm_read_overwrite() which is part of the iOS kernel. 

The current_task is exported as part of the exploit in kernel_memory.h as such:
 
Code:
/*
* current_task
*
* Description:
* The address of the current task in kernel memory.
*/
 
extern uint64_t current_task;

The OFFSET(task, bsd_info) part is a macro defined in parameters.h on Brandon Azad's exploit:
 
Code:
// Generate the name for an offset.
 
#define OFFSET(base_, object_) _##base_##__##object_##__offset_

If everything goes fine and we have proper Kernel Read privileges and we have the correct offsets, we should now have our address which is our representation inside the Kernel. Let the games begin!
At this point, getting ROOT and escaping the iOS Sandbox is ridiculously simple. Here's the code from my Osiris Jailbreak for iOS 12.
 
Code:
int elevatePrivsAndShaiHulud(){
    if (!shouldUseMachSwap) {
        printf("[i] Preparing to elevate own privileges!\n");
        uint64_t selfProc = findOurselves();
        uint64_t creds = kernel_read64(selfProc + off_p_ucred);
        
        // GID
        kernel_write32(selfProc + off_p_gid, 0);
        kernel_write32(selfProc + off_p_rgid, 0);
        kernel_write32(creds + off_ucred_cr_rgid, 0);
        kernel_write32(creds + off_ucred_cr_svgid, 0);
        printf("[i] STILL HERE!!!!\n");
        
        // UID
        creds = kernel_read64(selfProc + off_p_ucred);
        kernel_write32(selfProc + off_p_uid, 0);
        kernel_write32(selfProc + off_p_ruid, 0);
        kernel_write32(creds + off_ucred_cr_uid, 0);
        kernel_write32(creds + off_ucred_cr_ruid, 0);
        kernel_write32(creds + off_ucred_cr_svuid, 0);
        printf("[i] Set UID = 0\n");
        
        // ShaiHulud
        creds = kernel_read64(selfProc + off_p_ucred);
        uint64_t cr_label = kernel_read64(creds + off_ucred_cr_label);
        kernel_write64(cr_label + off_sandbox_slot, 0);
 
    }
    if (geteuid() == 0) {
        FILE * testfile = fopen("/var/mobile/OsirisJailbreak", "w");
        if (!testfile) {
            printf("[i] We failed! Still Sandboxed\n");
            return -2; // Root, but sandboxed :/
        }else {
            printf("[i] Nuked SandBox, FREEEEEEEEE!!!!!!\n");
            printf("[+] Wrote file OsirisJailbreak to /var/mobile/OsirisJailbreak successfully!\n");
            return 0; // FREE!!!!
        }
    } else {
        return -1; // Not even root :(
    }
    return 0;
}

Bit of a bigger function, so let's break it point by point.
The "if (!shouldUseMachSwap) { ... }" should be ignored. It's there because Osiris Jailbreak uses two exploits, Brandon Azad's and Sparkey's. Brandon's requires that I escape the Sandbox myself, while Sparkey's escapes the sandbox for me, so no need to run the function in that case.

Immediately after that, we find ourselves using the above-mentioned function, then we do a kernel read to grab our credentials from our process representation in the kernel, which happens to be at selfProc + off_p_ucred. So, selfProc is the address returned by the findOurselves() function and it serves as our base address. Our process' representation in the kernel starts there. The off_p_ucred is an offset which as the value 0xF8 as you can see on the offsets code. So base address + 0xF8 = the address of our ucred structure.

After that, you can see that I labeled a block "GID" and another one "UID". GID stands for Group Identifier and UID for User Identified. By default, our app belongs like any other AppStore app to mobile, a less privileged user on iOS with UID 501. We want root because it has way more privileges, that would be UID 0. For the group, we want "wheel" so again, GID 0, but we're listed as mobile (501) already in the kernel. No problem, these are not constants so we can do a simple Kernel Write to those offsets inside our structure to change our GID and UID to 0, so we do:
 
Code:
 
 // GID
        kernel_write32(selfProc + off_p_gid, 0);
        kernel_write32(selfProc + off_p_rgid, 0);
        kernel_write32(creds + off_ucred_cr_rgid, 0);
        kernel_write32(creds + off_ucred_cr_svgid, 0);
        printf("[i] STILL HERE!!!!\n");
        
        // UID
        creds = kernel_read64(selfProc + off_p_ucred);
        kernel_write32(selfProc + off_p_uid, 0);
        kernel_write32(selfProc + off_p_ruid, 0);
        kernel_write32(creds + off_ucred_cr_uid, 0);
        kernel_write32(creds + off_ucred_cr_ruid, 0);
        kernel_write32(creds + off_ucred_cr_svuid, 0);
        printf("[i] Set UID = 0\n");

The kernel_write32(...) function is part of the exploit. One of the Kernel Write primitives. The off_p_uid, off_p_ruid, off_p_gid,  off_p_rgid, off_ucred_cr_uid, off_ucred_cr_ruid and off_ucred_cr_svuid are all offsets from the above-mentioned huge offsets list. We have to set 0 to all these for the desired effect. Once we wrote 0, bam! we're "root:wheel" and not mobile (501) anymore.

The next thing we do is to nuke the Sandbox. By default, we're sandboxed like any third-party iOS app. This means that we can ONLY write to our App's own folders, and we cannot do much. We want full system access so it's time to leave the sand and the box for a better landscape.

In order to nuke the Sandbox, all we need to do is to find again our process' representation in the kernel, use the offsets to locate the cr_label through a kernel_read64(...), then to the cr_label address we add the off_sandbox_slot offset which is 0x10 on iOS 12, and then at the address we obtain we just have to write 0. We do that like this on Osiris Jailbreak:
 
Code:
 
 // ShaiHulud
        creds = kernel_read64(selfProc + off_p_ucred);
        uint64_t cr_label = kernel_read64(creds + off_ucred_cr_label);
        kernel_write64(cr_label + off_sandbox_slot, 0);

Now we should be outta Sandbox and root, but we should do a check before we continue, just in case so we check if we are root through "if (geteuid() == 0) {...} ". If we are root, we proceed to create a new file in a path we should not be allowed to if we're still sandboxed. For this, the "/var/mobile/" suffices. We try to create there an empty file called "OsirisJailbreak". If the file is created, we're clearly out of the sandbox and root, so we suck sid (succeed), else, we failed hard - probably wrong offsets or bad read / write primitives or permissions.
Here's the code from Osiris Jailbreak for performing the check:
Code:
 
if (geteuid() == 0) {
        FILE * testfile = fopen("/var/mobile/OsirisJailbreak", "w");
        if (!testfile) {
            printf("[i] We failed! Still Sandboxed\n");
            return -2; // Root, but sandboxed :/
        }else {
            printf("[i] Nuked SandBox, FREEEEEEEEE!!!!!!\n");
            printf("[+] Wrote file OsirisJailbreak to /var/mobile/OsirisJailbreak successfully!\n");
            return 0; // FREE!!!!
        }
    } else {
        return -1; // Not even root :(
    }
    return 0;

If everything went fine, we should return 0 ("FREE!!!!").

And that is all, that's how you get root and how you escape Sandbox while building your own jailbreak on iOS 12 once you have tfp0. Smile

I hope you enjoyed this article.
All the best, GeoSn0w (@FCE365).

Thank you for this! Was really useful for me, and I understood everything except how you printf on iOS since there’s no shell and how do I start writing this all
Reply
#6
Is their anyway I could learn with a YouTube video I’m still waiting for you to upload episode 2 for it thanks
Reply
#7
(03-24-2020, 01:21 AM)DragonTechRoyale4k Wrote:
(08-10-2019, 07:09 PM)GeoSn0w Wrote: So you wanna build a Jailbreak and there is a tfp0 kernel exploit released (probably by either Sparkey or by Google Project Zero if I know this community well). The tfp0 is basically task_for_pid(0) so the task port for PID 0, which is the kernel_task or the XNU kernel itself. Once you've got tfp0, things are pretty simple because if you possess the Kernel Task Port, you have access to vm_read and vm_write to the Kernel Virtual Memory which means that you can apply various patches to yourself (your process representation in the kernel), or to other processes.

Of course, Apple thought about this and starting with iOS 9 things have changed quite a bit with the advent of KPP or Kernel Patch Protection. With A10 (iPhone 7, 7 Plus), Apple took it one step further after KPP was bypassed in iOS 9 and 10. They introduced KTRR (Kernel Text Read-Only Region), a hardware solution which to this date was only bypassed once, back in the iOS 10 days. KPP and KTRR are very different in the implementation. One is software, another is hardware, and they work in different ways. Siguza has very well writ explanations on how these work in his blog, but it suffices to know that both KTRR and KPP prevent you from patching the Kernel (well, Apple tried... in reality, they only protect the __TEXT region (the code itself) and the constants). Since variable data cannot be protected, it has since been abused to heck and beyond in all post iOS 10 Jailbreaks, the so-called KPPLess paradigm which is not really a KPP bypass, but a KPP compliance. KPP/KTRR don't want us to mess with the constants and the code, and we don't because we don't even have to, at least for now.

iOS is basically a mobile fork of macOS which grew to have its own particularities. macOS is basically FreeBSD + Unix + Apple's own shenanigans, so you will see many similarities with other Unix-based systems. One of these is the fact that each process that runs on the device has a PID (process ID) and a representation somewhere in the kernel. That representation holds everything from your permissions (or lack of thereof) to your PID, your Entitlements (to make AMFI happy) and other bits and pieces which make up the process structure.

So the plan is simple: If you have Kernel Read / Write privileges, you can poke around the kernel to find basically yourself (your app's representation in the Kernel). Once you find that, given the right offsets, you can modify the data to grant yourself new entitlements (they govern what you can and what you can't as an App on iOS), escape yourself from the SandBox, get to be owned by root (root:wheel) rather than mobile which is far more limited, etc.
(Or you can just say freak it and get the kernel credentials and replace yours with the kernel's, but not only that can result in weird bugs due to increased reference counters and other weird glitches, but it's also a bit dangerous).

So, the first thing we wanna do after we've integrated the tfp0 exploit with our Jailbreak Xcode project is to add the proper offsets. These offsets basically represent how far from a specific base address we should expect to find an object in the memory.
The following analogy should clear what offsets are once and for all:
Imagine a street. The street has a number, let's say street 0xFFFFFFFFFFa14eba. Now, there are multiple houses on that street, but we want to find Joe's house. We know that Joe lives at the house 401 so 401 is the offset because from the base address (the start of the street) we need to go 401 positions up (houses) before we find what we need. The same way in the memory we can find things by knowing their offsets relative to a base address.

Problem with these offsets is that they change from a version to another and even from a device to another, so iOS 11's offsets will not work on iOS 12. They may, however, in some cases work from a minor version to another, for example from 12.0 to 12.1.2.

The following structure contains the offsets for iOS 12.x firmware:
 
Code:
uint32_t _kstruct_offsets_12_0[] = {
    0xb, // KSTRUCT_OFFSET_TASK_LCK_MTX_TYPE
    0x10, // KSTRUCT_OFFSET_TASK_REF_COUNT
    0x14, // KSTRUCT_OFFSET_TASK_ACTIVE
    0x20, // KSTRUCT_OFFSET_TASK_VM_MAP
    0x28, // KSTRUCT_OFFSET_TASK_NEXT
    0x30, // KSTRUCT_OFFSET_TASK_PREV
    0x300, // KSTRUCT_OFFSET_TASK_ITK_SPACE
#if __arm64e__
    0x368, // KSTRUCT_OFFSET_TASK_BSD_INFO
#else
    0x358, // KSTRUCT_OFFSET_TASK_BSD_INFO
#endif
#if __arm64e__
    0x3a8, // KSTRUCT_OFFSET_TASK_ALL_IMAGE_INFO_ADDR
#else
    0x398, // KSTRUCT_OFFSET_TASK_ALL_IMAGE_INFO_ADDR
#endif
#if __arm64e__
    0x3b0, // KSTRUCT_OFFSET_TASK_ALL_IMAGE_INFO_SIZE
#else
    0x3a0, // KSTRUCT_OFFSET_TASK_ALL_IMAGE_INFO_SIZE
#endif
#if __arm64e__
    0x400, // KSTRUCT_OFFSET_TASK_TFLAGS
#else
    0x390, // KSTRUCT_OFFSET_TASK_TFLAGS
#endif
    
    0x0, // KSTRUCT_OFFSET_IPC_PORT_IO_BITS
    0x4, // KSTRUCT_OFFSET_IPC_PORT_IO_REFERENCES
    0x40, // KSTRUCT_OFFSET_IPC_PORT_IKMQ_BASE
    0x50, // KSTRUCT_OFFSET_IPC_PORT_MSG_COUNT
    0x60, // KSTRUCT_OFFSET_IPC_PORT_IP_RECEIVER
    0x68, // KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT
    0x88, // KSTRUCT_OFFSET_IPC_PORT_IP_PREMSG
    0x90, // KSTRUCT_OFFSET_IPC_PORT_IP_CONTEXT
    0xa0, // KSTRUCT_OFFSET_IPC_PORT_IP_SRIGHTS
    
    0x60, // KSTRUCT_OFFSET_PROC_PID
    0x108, // KSTRUCT_OFFSET_PROC_P_FD
    0x10, // KSTRUCT_OFFSET_PROC_TASK
    0xf8, // KSTRUCT_OFFSET_PROC_UCRED
    0x8, // KSTRUCT_OFFSET_PROC_P_LIST
    0x290, // KSTRUCT_OFFSET_PROC_P_CSFLAGS
    
    0x0, // KSTRUCT_OFFSET_FILEDESC_FD_OFILES
    
    0x8, // KSTRUCT_OFFSET_FILEPROC_F_FGLOB
    
    0x38, // KSTRUCT_OFFSET_FILEGLOB_FG_DATA
    
    0x10, // KSTRUCT_OFFSET_SOCKET_SO_PCB
    
    0x10, // KSTRUCT_OFFSET_PIPE_BUFFER
    
    0x14, // KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE_SIZE
    0x20, // KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE
    
    0xd8, // KSTRUCT_OFFSET_VNODE_V_MOUNT
    0x78, // KSTRUCT_OFFSET_VNODE_VU_SPECINFO
    0x0, // KSTRUCT_OFFSET_VNODE_V_LOCK
    0xe0, // KSTRUCT_OFFSET_VNODE_V_DATA
    
    0x10, // KSTRUCT_OFFSET_SPECINFO_SI_FLAGS
    
    0x70, // KSTRUCT_OFFSET_MOUNT_MNT_FLAG
    0x8f8, // KSTRUCT_OFFSET_MOUNT_MNT_DATA
    
    0x10, // KSTRUCT_OFFSET_HOST_SPECIAL
    
    0x18, // KSTRUCT_OFFSET_UCRED_CR_UID
    0x78, // KSTRUCT_OFFSET_UCRED_CR_LABEL
    
    0x18, // KSTRUCT_SIZE_IPC_ENTRY
    
    0x6c, // KFREE_ADDR_OFFSET
};

 
// proc_t
unsigned off_p_pid = 0x60;
unsigned off_task = 0x10;
unsigned off_p_uid = 0x28;
unsigned off_p_gid = 0x2C;
unsigned off_p_ruid = 0x30;
unsigned off_p_rgid = 0x34;
unsigned off_p_ucred = 0xF8;
unsigned off_p_csflags = 0x290;
unsigned off_p_comm = 0x250;
unsigned off_p_textvp = 0x230;
unsigned off_p_textoff = 0x238;
unsigned off_p_cputype = 0x2A8;
unsigned off_p_cpu_subtype = 0x2AC;
 
// task_t
unsigned off_itk_self = 0xD8;
unsigned off_itk_sself = 0xE8;
unsigned off_itk_bootstrap = 0x2B8;
unsigned off_itk_space = 0x300;
 
// ipc_port_t
unsigned off_ip_mscount = 0x9C;
unsigned off_ip_srights = 0xA0;
unsigned off_ip_kobject = 0x68;
 
// ucred
unsigned off_ucred_cr_uid = 0x18;
unsigned off_ucred_cr_ruid = 0x1c;
unsigned off_ucred_cr_svuid = 0x20;
unsigned off_ucred_cr_ngroups = 0x24;
unsigned off_ucred_cr_groups = 0x28;
unsigned off_ucred_cr_rgid = 0x68;
unsigned off_ucred_cr_svgid = 0x6c;
unsigned off_ucred_cr_label = 0x78;
 
// vnode
unsigned off_v_type = 0x70;
unsigned off_v_id = 0x74;
unsigned off_v_ubcinfo = 0x78;
unsigned off_v_flags = 0x54;
unsigned off_v_mount = 0xD8; // vnode::v_mount
unsigned off_v_specinfo = 0x78; // vnode::v_specinfo
 
// ubc_info
unsigned off_ubcinfo_csblobs = 0x50; // ubc_info::csblobs
 
// cs_blob
unsigned off_csb_cputype = 0x8;
unsigned off_csb_flags = 0x12;
unsigned off_csb_base_offset = 0x16;
unsigned off_csb_entitlements_offset = 0x90;
unsigned off_csb_signer_type = 0xA0;
unsigned off_csb_platform_binary = 0xA8;
unsigned off_csb_platform_path = 0xAC;
unsigned off_csb_cd = 0x80;
 
// task
unsigned off_t_flags = 0x3A0;
 
// mount
unsigned off_specflags = 0x10;
unsigned off_mnt_flag = 0x70;
unsigned off_mnt_data = 0x8F8;
 
 
unsigned off_special = 2 * sizeof(long);
unsigned off_ipc_space_is_table = 0x20;
 
unsigned off_amfi_slot = 0x8;
unsigned off_sandbox_slot = 0x10;
 
_Bool offs_init() {
    if (SYSTEM_VERSION_BETWEEN_OR_EQUAL_TO(@"12.0", @"13.0") && !SYSTEM_VERSION_EQUAL_TO(@"13.0")) {
        off_p_pid = 0x60;
        off_task = 0x10;
        off_p_uid = 0x28;
        off_p_gid = 0x2C;
        off_p_ruid = 0x30;
        off_p_rgid = 0x34;
        off_p_ucred = 0xF8;
        off_p_csflags = 0x290;
        off_p_comm = 0x250;
        off_p_textvp = 0x230;
        off_p_textoff = 0x238;
        off_p_cputype = 0x2A8;
        off_p_cpu_subtype = 0x2AC;
        off_itk_space = 0x300;
        off_csb_platform_binary = 0xA8;
        off_csb_platform_path = 0xAC;
    } else {
        ERROR("iOS version unsupported.");
        return false;
    }
    return true;
}

Alright, we have the offsets. Now what?
Having tfp0 + offsets means that we can find ourselves, which we need to do if we want to escalate our privileges on iOS. So, in order to find ourselves, we have to read the Kernel memory until we find our PID.
The kernel stores a proc structure for every single process in the memory, here's what that structure looks like:
Code:
struct proc {
LIST_ENTRY(proc) p_list; /* List of all processes. */
 
pid_t p_pid; /* Process identifier. (static)*/
void * task; /* corresponding task (static)*/
struct proc * p_pptr; /* Pointer to parent process.(LL) */
pid_t p_ppid; /* process's parent pid number */
pid_t p_pgrpid; /* process group id of the process (LL)*/
uid_t p_uid;
gid_t p_gid;
uid_t p_ruid;
gid_t p_rgid;
uid_t p_svuid;
gid_t p_svgid;
uint64_t p_uniqueid; /* process unique ID - incremented on fork/spawn/vfork, remains same across exec. */
uint64_t p_puniqueid; /* parent's unique ID - set on fork/spawn/vfork, doesn't change if reparented. */
 
lck_mtx_t p_mlock; /* mutex lock for proc */
 
char p_stat; /* S* process status. (PL)*/
char p_shutdownstate;
char p_kdebug; /* P_KDEBUG eq (CC)*/ 
char p_btrace; /* P_BTRACE eq (CC)*/
 
LIST_ENTRY(proc) p_pglist; /* List of processes in pgrp.(PGL) */
LIST_ENTRY(proc) p_sibling; /* List of sibling processes. (LL)*/
LIST_HEAD(, proc) p_children; /* Pointer to list of children. (LL)*/
TAILQ_HEAD( , uthread) p_uthlist; /* List of uthreads  (PL) */
 
LIST_ENTRY(proc) p_hash; /* Hash chain. (LL)*/
TAILQ_HEAD( ,eventqelt) p_evlist; /* (PL) */
 
#if CONFIG_PERSONAS
struct persona  *p_persona;
LIST_ENTRY(proc) p_persona_list;
#endif
 
lck_mtx_t p_fdmlock; /* proc lock to protect fdesc */
lck_mtx_t p_ucred_mlock; /* mutex lock to protect p_ucred */
 
/* substructures: */
kauth_cred_t p_ucred; /* Process owner's identity. (PUCL) */ !!!
struct filedesc *p_fd; /* Ptr to open files structure. (PFDL) */
struct pstats *p_stats; /* Accounting/statistics (PL). */
struct plimit *p_limit; /* Process limits.(PL) */
 
struct sigacts *p_sigacts; /* Signal actions, state (PL) */
int p_siglist; /* signals captured back from threads */
lck_spin_t p_slock; /* spin lock for itimer/profil protection */
 
#define p_rlimit p_limit->pl_rlimit
 
struct plimit *p_olimit; /* old process limits  - not inherited by child  (PL) */
unsigned int p_flag; /* P_* flags. (atomic bit ops) */
unsigned int p_lflag; /* local flags  (PL) */
unsigned int p_listflag; /* list flags (LL) */
unsigned int p_ladvflag; /* local adv flags (atomic) */
int p_refcount; /* number of outstanding users(LL) */
int p_childrencnt; /* children holding ref on parent (LL) */
int p_parentref; /* children lookup ref on parent (LL) */
 
pid_t p_oppid; /* Save parent pid during ptrace. XXX */
u_int p_xstat; /* Exit status for wait; also stop signal. */
uint8_t p_xhighbits; /* Stores the top byte of exit status to avoid truncation*/
 
#ifdef _PROC_HAS_SCHEDINFO_
/* may need cleanup, not used */
u_int p_estcpu; /* Time averaged value of p_cpticks.(used by aio and proc_comapre) */
fixpt_t p_pctcpu; /* %cpu for this process during p_swtime (used by aio)*/
u_int p_slptime; /* used by proc_compare */
#endif /* _PROC_HAS_SCHEDINFO_ */
 
struct itimerval p_realtimer; /* Alarm timer. (PSL) */
struct timeval p_rtime; /* Real time.(PSL)  */
struct itimerval p_vtimer_user; /* Virtual timers.(PSL)  */
struct itimerval p_vtimer_prof; /* (PSL) */
 
struct timeval p_rlim_cpu; /* Remaining rlim cpu value.(PSL) */
int p_debugger; /*  NU 1: can exec set-bit programs if suser */
boolean_t sigwait; /* indication to suspend (PL) */
void *sigwait_thread; /* 'thread' holding sigwait(PL)  */
void *exit_thread; /* Which thread is exiting(PL)  */
int p_vforkcnt; /* number of outstanding vforks(PL)  */
    void *  p_vforkact;     /* activation running this vfork proc)(static)  */
int p_fpdrainwait; /* (PFDL) */
pid_t p_contproc; /* last PID to send us a SIGCONT (PL) */
 
/* Following fields are info from SIGCHLD (PL) */
pid_t si_pid; /* (PL) */
u_int si_status; /* (PL) */
u_int si_code; /* (PL) */
uid_t si_uid; /* (PL) */
 
void * vm_shm; /* (SYSV SHM Lock) for sysV shared memory */
 
#if CONFIG_DTRACE
user_addr_t p_dtrace_argv; /* (write once, read only after that) */
user_addr_t p_dtrace_envp; /* (write once, read only after that) */
lck_mtx_t p_dtrace_sprlock; /* sun proc lock emulation */
int p_dtrace_probes; /* (PL) are there probes for this proc? */
u_int p_dtrace_count; /* (sprlock) number of DTrace tracepoints */
        uint8_t                         p_dtrace_stop;                  /* indicates a DTrace-desired stop */
struct dtrace_ptss_page* p_dtrace_ptss_pages; /* (sprlock) list of user ptss pages */
struct dtrace_ptss_page_entry* p_dtrace_ptss_free_list; /* (atomic) list of individual ptss entries */
struct dtrace_helpers* p_dtrace_helpers; /* (dtrace_lock) DTrace per-proc private */
struct dof_ioctl_data* p_dtrace_lazy_dofs; /* (sprlock) unloaded dof_helper_t's */
#endif /* CONFIG_DTRACE */
 
/* XXXXXXXXXXXXX BCOPY'ed on fork XXXXXXXXXXXXXXXX */
/* The following fields are all copied upon creation in fork. */
#define p_startcopy p_argslen
 
u_int p_argslen; /* Length of process arguments. */
int  p_argc; /* saved argc for sysctl_procargs() */
user_addr_t user_stack; /* where user stack was allocated */
struct vnode *p_textvp; /* Vnode of executable. */
off_t p_textoff; /* offset in executable vnode */
 
sigset_t p_sigmask; /* DEPRECATED */
sigset_t p_sigignore; /* Signals being ignored. (PL) */
sigset_t p_sigcatch; /* Signals being caught by user.(PL)  */
 
u_char p_priority; /* (NU) Process priority. */
u_char p_resv0; /* (NU) User-priority based on p_cpu and p_nice. */
char p_nice; /* Process "nice" value.(PL) */
u_char p_resv1; /* (NU) User-priority based on p_cpu and p_nice. */
 
// types currently in sys/param.h
command_t   p_comm;
proc_name_t p_name; /* can be changed by the process */
 
 
struct pgrp *p_pgrp; /* Pointer to process group. (LL) */
uint32_t p_csflags; /* flags for codesign (PL) */
uint32_t p_pcaction; /* action  for process control on starvation */
uint8_t p_uuid[16]; /* from LC_UUID load command */
 
/* 
* CPU type and subtype of binary slice executed in
* this process.  Protected by proc lock.
*/
cpu_type_t p_cputype;
cpu_subtype_t p_cpusubtype;
 
/* End area that is copied on creation. */
/* XXXXXXXXXXXXX End of BCOPY'ed on fork (AIOLOCK)XXXXXXXXXXXXXXXX */
#define p_endcopy p_aio_total_count
int p_aio_total_count; /* all allocated AIO requests for this proc */
int p_aio_active_count; /* all unfinished AIO requests for this proc */
TAILQ_HEAD( , aio_workq_entry ) p_aio_activeq; /* active async IO requests */
TAILQ_HEAD( , aio_workq_entry ) p_aio_doneq; /* completed async IO requests */
 
struct klist p_klist;  /* knote list (PL ?)*/
 
struct rusage_superset *p_ru; /* Exit information. (PL) */
int p_sigwaitcnt;
thread_t p_signalholder;
thread_t p_transholder;
 
/* DEPRECATE following field  */
u_short p_acflag; /* Accounting flags. */
volatile u_short p_vfs_iopolicy; /* VFS iopolicy flags. (atomic bit ops) */
 
user_addr_t p_threadstart; /* pthread start fn */
user_addr_t p_wqthread; /* pthread workqueue fn */
int p_pthsize; /* pthread size */
uint32_t p_pth_tsd_offset; /* offset from pthread_t to TSD for new threads */
user_addr_t p_stack_addr_hint; /* stack allocation hint for wq threads */
void * p_wqptr; /* workq ptr */
 
struct  timeval p_start;        /* starting time */
void * p_rcall;
int p_ractive;
int p_idversion; /* version of process identity */
void * p_pthhash; /* pthread waitqueue hash */
volatile uint64_t was_throttled __attribute__((aligned(8))); /* Counter for number of throttled I/Os */
volatile uint64_t did_throttle __attribute__((aligned(8)));  /* Counter for number of I/Os this proc throttled */
 
#if DIAGNOSTIC
unsigned int p_fdlock_pc[4];
unsigned int p_fdunlock_pc[4];
#if SIGNAL_DEBUG
unsigned int lockpc[8];
unsigned int unlockpc[8];
#endif /* SIGNAL_DEBUG */
#endif /* DIAGNOSTIC */
uint64_t p_dispatchqueue_offset;
uint64_t p_dispatchqueue_serialno_offset;
uint64_t p_return_to_kernel_offset;
uint64_t p_mach_thread_self_offset;
#if VM_PRESSURE_EVENTS
struct timeval vm_pressure_last_notify_tstamp;
#endif
 
#if CONFIG_MEMORYSTATUS
/* Fields protected by proc list lock */
TAILQ_ENTRY(proc) p_memstat_list;               /* priority bucket link */
uint32_t          p_memstat_state;              /* state */
int32_t           p_memstat_effectivepriority;  /* priority after transaction state accounted for */
int32_t           p_memstat_requestedpriority;  /* active priority */
uint32_t          p_memstat_dirty;              /* dirty state */
uint64_t          p_memstat_userdata;           /* user state */
uint64_t          p_memstat_idledeadline;       /* time at which process became clean */
uint64_t          p_memstat_idle_start;         /* abstime process transitions into the idle band */
uint64_t   p_memstat_idle_delta;         /* abstime delta spent in idle band */
int32_t           p_memstat_memlimit;           /* cached memory limit, toggles between active and inactive limits */
int32_t           p_memstat_memlimit_active; /* memory limit enforced when process is in active jetsam state */
int32_t           p_memstat_memlimit_inactive; /* memory limit enforced when process is in inactive jetsam state */
#if CONFIG_FREEZE
uint32_t          p_memstat_suspendedfootprint; /* footprint at time of suspensions */
#endif /* CONFIG_FREEZE */
#endif /* CONFIG_MEMORYSTATUS */
 
/* cached proc-specific data required for corpse inspection */
pid_t             p_responsible_pid; /* pid resonsible for this process */
_Atomic uint32_t  p_user_faults; /* count the number of user faults generated */
 
struct os_reason     *p_exit_reason;
};

Here is the code from my Osiris Jailbreak for iOS 12:
 
Code:
uint64_t findOurselves(){
    static uint64_t self = 0;
    if (!self) {
        self = ReadKernel64(current_task + OFFSET(task, bsd_info));
        printf("[i] Found Ourselves at 0x%llx\n", self);
    }
    return self;
}

A simple function which returns an uint64_t (an address / a pointer) to ourselves in the kernel.
The ReadKernel64(...) function is part of the exploit, the memory read primitive and it's actually a wrapper around rk64_via_tfp0() which is a wrapper around another function which is a wrapper around another until we get to mach_vm_read_overwrite() which is part of the iOS kernel. 

The current_task is exported as part of the exploit in kernel_memory.h as such:
 
Code:
/*
* current_task
*
* Description:
* The address of the current task in kernel memory.
*/
 
extern uint64_t current_task;

The OFFSET(task, bsd_info) part is a macro defined in parameters.h on Brandon Azad's exploit:
 
Code:
// Generate the name for an offset.
 
#define OFFSET(base_, object_) _##base_##__##object_##__offset_

If everything goes fine and we have proper Kernel Read privileges and we have the correct offsets, we should now have our address which is our representation inside the Kernel. Let the games begin!
At this point, getting ROOT and escaping the iOS Sandbox is ridiculously simple. Here's the code from my Osiris Jailbreak for iOS 12.
 
Code:
int elevatePrivsAndShaiHulud(){
    if (!shouldUseMachSwap) {
        printf("[i] Preparing to elevate own privileges!\n");
        uint64_t selfProc = findOurselves();
        uint64_t creds = kernel_read64(selfProc + off_p_ucred);
        
        // GID
        kernel_write32(selfProc + off_p_gid, 0);
        kernel_write32(selfProc + off_p_rgid, 0);
        kernel_write32(creds + off_ucred_cr_rgid, 0);
        kernel_write32(creds + off_ucred_cr_svgid, 0);
        printf("[i] STILL HERE!!!!\n");
        
        // UID
        creds = kernel_read64(selfProc + off_p_ucred);
        kernel_write32(selfProc + off_p_uid, 0);
        kernel_write32(selfProc + off_p_ruid, 0);
        kernel_write32(creds + off_ucred_cr_uid, 0);
        kernel_write32(creds + off_ucred_cr_ruid, 0);
        kernel_write32(creds + off_ucred_cr_svuid, 0);
        printf("[i] Set UID = 0\n");
        
        // ShaiHulud
        creds = kernel_read64(selfProc + off_p_ucred);
        uint64_t cr_label = kernel_read64(creds + off_ucred_cr_label);
        kernel_write64(cr_label + off_sandbox_slot, 0);
 
    }
    if (geteuid() == 0) {
        FILE * testfile = fopen("/var/mobile/OsirisJailbreak", "w");
        if (!testfile) {
            printf("[i] We failed! Still Sandboxed\n");
            return -2; // Root, but sandboxed :/
        }else {
            printf("[i] Nuked SandBox, FREEEEEEEEE!!!!!!\n");
            printf("[+] Wrote file OsirisJailbreak to /var/mobile/OsirisJailbreak successfully!\n");
            return 0; // FREE!!!!
        }
    } else {
        return -1; // Not even root :(
    }
    return 0;
}

Bit of a bigger function, so let's break it point by point.
The "if (!shouldUseMachSwap) { ... }" should be ignored. It's there because Osiris Jailbreak uses two exploits, Brandon Azad's and Sparkey's. Brandon's requires that I escape the Sandbox myself, while Sparkey's escapes the sandbox for me, so no need to run the function in that case.

Immediately after that, we find ourselves using the above-mentioned function, then we do a kernel read to grab our credentials from our process representation in the kernel, which happens to be at selfProc + off_p_ucred. So, selfProc is the address returned by the findOurselves() function and it serves as our base address. Our process' representation in the kernel starts there. The off_p_ucred is an offset which as the value 0xF8 as you can see on the offsets code. So base address + 0xF8 = the address of our ucred structure.

After that, you can see that I labeled a block "GID" and another one "UID". GID stands for Group Identifier and UID for User Identified. By default, our app belongs like any other AppStore app to mobile, a less privileged user on iOS with UID 501. We want root because it has way more privileges, that would be UID 0. For the group, we want "wheel" so again, GID 0, but we're listed as mobile (501) already in the kernel. No problem, these are not constants so we can do a simple Kernel Write to those offsets inside our structure to change our GID and UID to 0, so we do:
 
Code:
 
 // GID
        kernel_write32(selfProc + off_p_gid, 0);
        kernel_write32(selfProc + off_p_rgid, 0);
        kernel_write32(creds + off_ucred_cr_rgid, 0);
        kernel_write32(creds + off_ucred_cr_svgid, 0);
        printf("[i] STILL HERE!!!!\n");
        
        // UID
        creds = kernel_read64(selfProc + off_p_ucred);
        kernel_write32(selfProc + off_p_uid, 0);
        kernel_write32(selfProc + off_p_ruid, 0);
        kernel_write32(creds + off_ucred_cr_uid, 0);
        kernel_write32(creds + off_ucred_cr_ruid, 0);
        kernel_write32(creds + off_ucred_cr_svuid, 0);
        printf("[i] Set UID = 0\n");

The kernel_write32(...) function is part of the exploit. One of the Kernel Write primitives. The off_p_uid, off_p_ruid, off_p_gid,  off_p_rgid, off_ucred_cr_uid, off_ucred_cr_ruid and off_ucred_cr_svuid are all offsets from the above-mentioned huge offsets list. We have to set 0 to all these for the desired effect. Once we wrote 0, bam! we're "root:wheel" and not mobile (501) anymore.

The next thing we do is to nuke the Sandbox. By default, we're sandboxed like any third-party iOS app. This means that we can ONLY write to our App's own folders, and we cannot do much. We want full system access so it's time to leave the sand and the box for a better landscape.

In order to nuke the Sandbox, all we need to do is to find again our process' representation in the kernel, use the offsets to locate the cr_label through a kernel_read64(...), then to the cr_label address we add the off_sandbox_slot offset which is 0x10 on iOS 12, and then at the address we obtain we just have to write 0. We do that like this on Osiris Jailbreak:
 
Code:
 
 // ShaiHulud
        creds = kernel_read64(selfProc + off_p_ucred);
        uint64_t cr_label = kernel_read64(creds + off_ucred_cr_label);
        kernel_write64(cr_label + off_sandbox_slot, 0);

Now we should be outta Sandbox and root, but we should do a check before we continue, just in case so we check if we are root through "if (geteuid() == 0) {...} ". If we are root, we proceed to create a new file in a path we should not be allowed to if we're still sandboxed. For this, the "/var/mobile/" suffices. We try to create there an empty file called "OsirisJailbreak". If the file is created, we're clearly out of the sandbox and root, so we suck sid (succeed), else, we failed hard - probably wrong offsets or bad read / write primitives or permissions.
Here's the code from Osiris Jailbreak for performing the check:
Code:
 
if (geteuid() == 0) {
        FILE * testfile = fopen("/var/mobile/OsirisJailbreak", "w");
        if (!testfile) {
            printf("[i] We failed! Still Sandboxed\n");
            return -2; // Root, but sandboxed :/
        }else {
            printf("[i] Nuked SandBox, FREEEEEEEEE!!!!!!\n");
            printf("[+] Wrote file OsirisJailbreak to /var/mobile/OsirisJailbreak successfully!\n");
            return 0; // FREE!!!!
        }
    } else {
        return -1; // Not even root :(
    }
    return 0;

If everything went fine, we should return 0 ("FREE!!!!").

And that is all, that's how you get root and how you escape Sandbox while building your own jailbreak on iOS 12 once you have tfp0. Smile

I hope you enjoyed this article.
All the best, GeoSn0w (@FCE365).

Thank you for this! Was really useful for me, and I understood everything except how you printf on iOS since there’s no shell and how do I start writing this all

The printf() lines are meant for debugging in Xcode since Xcode has a console output for running apps. You can, however, pipe the log messages to a UIView that contains a multi-line Text Field in your App.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
Video How To Build An iOS 13 / 12 JAILBREAK From Scratch: Getting Started With tfp0 Exploits (E1) GeoSn0w 1 3,864 03-29-2020, 11:10 PM
Last Post: YoXpertguyZ

Forum Jump:


Users browsing this thread: 1 Guest(s)

About Us
    Welcome to the Jailbreak Central Forum! Here you can get the latest iOS Jailbreak News from iDevice Central, ask your jailbreak questions and request help, and find the best iOS modding tools for downgrade, CFW iCloud Bypass, Jailbreak and so on. :-)