kernel pwn 环境搭建

编译生成内核镜像

先下载一个内核源码 :https://mirrors.edge.kernel.org/pub/linux/kernel/
我下载的是linux-4.9.tar.gz
然后安装一些依赖

1
2
sudo apt-get update
sudo apt-get install git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc

解压源码后进入内核目录,输入命令:sudo make menuconfig进入内核编译设置

进入kernel hacking,检查保证勾选了以下选项(其实默认都是勾选的,只是进去看一眼而已)

  • Kernel debugging
  • Compile-time checks and compiler options —> Compile the kernel with debug info和Compile the kernel with frame pointers
  • KGDB: kernel debugger

保存并退出

sudo make bzImage生成 bzImage,过程挺久的,完成后在boot下生成了一个bzImage文件

这样,就算编译成功了这样我们就从/arch/x86/boot得到了 bzImage文件

下载内核镜像

根据版本号搜索 sudo apt search linux-image-xxx

然后下载 sudo apt download linux-image-4.10.0-1004-gcp,解压

安装busybox

这里下载,解压后进入目录,make menuconfig,同样会进入图形界面,在Settings 上勾选Build static binary (no shared libs),然后保存退出

编译,执行make install,根目录下会生成一个_install文件夹,进入文件夹配置

1
2
3
4
5
6
cd _install
mkdir proc
mkdir sys
touch init
touch packet
chmod +x init

编辑init 文件,用于内核初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/sh
echo "{==DBG==} INIT SCRIPT"
mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t debugfs none /sys/kernel/debug
mount -t tmpfs none /tmp
#mount指令 挂载某个分区到某个文件,这样就将分区与文件建立联系从而访问文件时就可以访问分区。
# insmod /xxx.ko # 加载模块
# insmod /hello.ko # 加载hello.ko模块
mdev -s
# We need this to find /dev/sda later
echo -e "{==DBG==} Boot took $(cut -d' ' -f1 /proc/uptime) seconds"
setsid /bin/cttyhack setuidgid 1000 /bin/sh #normal user
# exec /bin/sh #root

几个常见指令

  • insmod: 指定模块加载到内核中
  • rmmod: 从内核中卸载指定模块
  • lsmod: 列出已经加载的模块

在packet 中写入,用于将FileSystem 打包成映像

1
2
3
#!/bin/sh
echo "Generate rootfs.img"
find . | cpio -o --format=newc > ./rootfs.img

运行 packet 将得到 rootfs.img文件

运行内核

这里使用qemu进行运行,qemu 有多种运行模式,常见的有User-mode emulationSystem emulation两种

安装 qemu

1
2
3
4
安装QEMU的依赖库
sudo apt-get install git libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev
sudo apt-get install qemu
测试输入qemu + tab 如果有很多实例回显,就说明安装成功

接着写一个shell脚本boot.sh用来启动qemu,运行内核

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/sh
qemu-system-x86_64 \
-m 64M \
-kernel ./bzImage \
-initrd ./rootfs.img \
-append "root=/dev/ram rw oops=panic panic=1 kalsr" \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-monitor /dev/null \
-smp cores=2,threads=1 \
-cpu kvm64,+smep \
#-S 启动gdb调试
#-gdb tcp::1234 等待gdb调试

将前面生成的bzImage,rootfs.img一起放到_install文件夹下,运行boot.sh

这里解释一下shell命令中参数的意义

1
2
3
4
5
6
7
8
9
-m 是指定RMA大小(默认384)
-kernel 是指定的内核镜像,这里是我们编译的镜像路径,也可以是我们下载好的镜像,如./vmlinuz-4.10.0-1004-gcp
-initrd 设置刚刚利用 busybox 创建的 rootfs.img,作为内核启动的文件系统
-append 附加选项,指定no kaslr 可以关闭随机偏移
--nographic和console=ttyS0一起使用,启动的界面就变成了当前终端
-s 相当于-gdb tcp::1234的简写,可以直接通过主机的gdb远程连接
-monitor 配置用户械的网络 // 将监视器重定向主机设备 /dev/null
-smp 用于生明所以可能用的cps,ie,socket cores threads = maxcputs.
-cpu 设置cpu的安全选项

到这里也说明了为什么kernel pwn 题目一般都会给出这3个文件.sh ,bzImage,rootfs.cpio,分别是启动脚本,kernel镜像以及文件系统映像

一般来说kernel pwn 里面,漏洞通常是出现在ko文件,也就是模块文件,驱动 文件中,而kernel pwn 的最终目标一般是提权,拿到root才能读取flag

添加syscall

在内核源码下添加一个目录:mysyscall,在mysyscall文件夹下添加一个mysyscall.c 一个Makefile

1
2
3
4
mkdir mysyscall
cd mysyscall
touch mysyscall.c
touch Makefile

编辑Makefile:

1
obj-y=mysyscall.o

编辑mysyscall.c

1
2
3
4
5
#include <linux/kernel.h>
asmlinkage long sys_mysyscall(void){
printk("{==== kernel ====}this is my syscall!\n");
return 0;
}

接着编辑源码根目录下的Makefile,添加mysyscall/

然后编辑include/linux/syscall.h,在末尾添加函数原型

1
asmlinkage long sys_mysyscall(void);

最后编辑arch/x86/entry/syscalls/syscall_32.tblarch/x86/entry/syscalls/syscall_64.tbl,添加系统 调用号

1
2
3
4
//syscall_32.tbl
2333 i386 mysyscall sys_mysyscall
//syscall_64.tbl
2333 common mysyscall sys_mysyscall

到这里,syscall就添加完了,如果调用了2333号就会输出 一句{==== kernel ====}this is my syscall!,不过要重新编译一次内核才能生效,make bzImage重新得到bzImage文件

编译ko

在kernel 源码目录下创建一个新的文件夹

1
2
3
4
mkdir test_ko
cd test_ko
touch hello.c
touch Makefile

编辑hello.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//hello.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cred.h>
MODULE_LICENSE("Dual BSD/GPL");
struct cred c1;
static int hello_init(void)
{
printk("================");
printk("<1> Hello world!\n");
printk("size of cred : %d \n",sizeof(c1));
printk("================");
return 0;
}
static void hello_exit(void)
{
printk("<1> Bye, cruel world\n");
}
module_init(hello_init);
module_exit(hello_exit);

编辑Makefile,(写Makefile的时候注意要使用Tab而不是空格)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
obj-m := hello.o

KERNELDR := ~/linux-4.9

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

然后make 命令编译出一个hello.ko文件

将编译好的hello.ko放到busybox的_install目录下,再写一个demo来调用前面添加的syscall,将demo也放到_install目录下

1
2
3
4
5
6
7
//demo  
//gcc demo.c -static -o demo
#include <unistd.h>
int main(void){
syscall(2333);
return 0;
}

注意这里我们需要加载自己编译的驱动 hello.ko,所以需要在init 文件中加入一句insmod /hello.ko然后再打包./packet

接着./boot.sh启动可以看到我们的ko模块和syscall都生效了

0%