Linux kernel pwn基础

主要记录一下学习linux kernel pwn遇到的命令以及相关函数、工具使用,之前没有接触过,有必要记录一下。

命令

模块相关

1
2
3
insmod:加载指定模块
rmmod:卸载指定模块
lsmod:查看已经加载的模块

启动qemu

1
2
3
4
32位启动:
qemu-system-i386 -kernel linux-2.6.32.1/arch/i386/boot/bzImage -initrd busybox-1.28.2/rootfs.img -append "root=/dev/ram rdinit=/sbin/init"
64位启动:
qemu-system-x86_64 -kernel linux-2.6.32.1/arch/x86/boot/bzImage -initrd busybox-1.28.2/rootfs.img -append "root=/dev/ram rdinit=/sbin/init"

ctf kernel pwn文件组成

1
2
3
4
5
6
rootfs.cpio:文件系统镜像
boot.sh:kernel启动的脚本,一般是qemu启动,可以根据参数查看题目的保护情况
bzImage:压缩后的内核文件
vmlinux:静态编译,未压缩的内核文件,可以在里面找ROP
init文件:在rootfs.cpio文件解压可以看到,记录了系统初始化时的操作,一般在文件里insmod一个内核模块.ko文件,通常是有漏洞的文件
.ko文件:需要拖到IDA里面分析找漏洞的文件,可以根据init文件的路径去rootfs.cpio里面找

查看系统的slab信息:

1
cat /proc/slabinfo

获取模块加载基址或直接lsmod,找到对应的module

1
cat /sys/module/模块名称/sections/.text

打包文件系统

1
2
#!/bin/sh
find . | cpio -o --format=newc > ../rootfs.cpio

获取内核基址,常用在已经泄露了函数地址之后再减去偏移得到内核基址,但这个偏移怎么确定呢,通过startup_64这个函数:

1
2
3
4
/ # cat /proc/kallsyms | grep startup_64
ffffffff81000000 T startup_64 //kernel_base
ffffffff81000030 T secondary_startup_64
ffffffff810001f0 T __startup_64

在gdb中获取cred在task_struct中的偏移:

1
pwndbg> p &(*(struct task_struct *)0).cred

找到结构体cred中uid的偏移(其他同理):

1
pwndbg> p &(*(struct cred *)0).uid

函数

提权

1
commit_creds(prepare_kernel_cred(0))

查看函数地址,在/proc/kallsyms中查看

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

1

常用函数

1
2
3
4
5
kmalloc(),申请内存,类似于用户态的malloc()
kfree():释放内存,类似于用户态的free()
printk():输出函数,类似于用户态的printf()
copy_to_user(void __user *to, const void *from, unsigned long n) //将内核空间的数据拷贝用户空间
copy_from_user(void *to, const void __user *from, unsigned long n) //将用户控件的数据拷贝到内核空间

ioctl函数,对设备的I/O通道进行管理,cmd是用户程序对设备发出的控制命令,handle是打开涉笔返回的文件描述符。

1
int ioctl(int handle, int cmd,[int *argdx, int argcx]);

kptr_restrict控制/proc/kallsyms是否显示symbols的地址,通常会在init文件中给出限制:

1
echo 1 > /proc/sys/kernel/kptr_restrict

dmesg_restrict限制非特权用户读取dmesg信息,无法访问内核打印的消息,通常会在init文件中给出限制:

1
echo 1 > /proc/sys/kernel/dmesg_restrict

/proc文件系统是一个虚拟文件系统,可以在/proc中动态创建虚拟文件,通过对虚拟文件的读写与实现与内核的通信。可以使用以下函数创建虚拟文件:

1
2
3
4
5
//第三个参数是文件在/proc中的位置,默认为/proc
struct proc_dir_entry *create_proc_entry( const char *name, mode_t mode, struct proc_dir_entry *parent );

//
static inline struct proc_dir_entry *proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent,const struct file_operations *proc_fops)

删除虚拟文件使用以下函数:

1
void remove_proc_entry( const char *name, struct proc_dir_entry *parent );

工具

题目通常会给bzImage,如果没有vmlinux,可以使用extract-vmlinux提取,使用方法如下:

1
ubuntu@ubuntu:~/kernel/pwn$ ./extract-vmlinux ./bzImage > vmlinux

在ubuntu14.04上安装pwntools:

1
2
3
4
sudo apt-get install -y python-dev python-pip libffi-dev libssl-dev
sudo pip install -U setuptools
sudo pip install cryptography==2.4.2
sudo pip install pwntools

寻找rop的另外一个工具:

1
https://github.com/sashs/Ropper

因为通常要找的rop较多,而且寻找的速度较慢,因此可以使用该工具找到所有的rop,重定向到文件,然后再去找自己需要的。使用方法:

1
ropper --file ./vmlinux --nocolor > ropgadget

gdb调试

首先修改init文件,添加以下命令,以便可以获取core.ko的代码段的基址。这样内核启动时就是root权限,当然这是为了调试方便,真正执行exp可以去掉这条命令。

1
setsid /bin/cttyhack setuidgid 0 /bin/sh

然后重新打包文件系统,运行start.sh起内核,在qemu中查找core.ko的.text段的地址:

1
2
/ # cat /sys/module/core/sections/.text
0xffffffffc0205000

在另外一个terminal中启动gdb:

1
gdb ./vmlinux -q

然后添加core.ko的符号表,加载了符号表之后就可以直接对函数名下断点了。

1
2
3
4
gdb-peda$ add-symbol-file ./core.ko 0xffffffffc0205000
add symbol table from file "./core.ko" at
.text_addr = 0xffffffffc0205000
Reading symbols from ./core.ko...(no debugging symbols found)...done.

然后运行以下命令连接qemu进行调试:

1
target remote localhost:1234

4

参考

https://ctf-wiki.github.io/ctf-wiki/pwn/linux/kernel/basic_knowledge-zh/
https://www.cnblogs.com/pcat/p/5451780.html
https://blog.csdn.net/tiantao2012/article/details/78864757
https://pengcc.iteye.com/blog/914092