Kernel: 环境配置
- 简易版环境:pwncollege/pwnkernel: Kernel development & exploitation practice environment. (github.com)
- 高级版环境:cirosantilli/linux-kernel-module-cheat: The perfect emulation setup to study and develop the Linux kernel v5.4.3, kernel modules, QEMU, gem5 and x86_64, ARMv7 and ARMv8 userland and baremetal assembly, ANSI C, C++ and POSIX. (github.com)
gdb 可以连接到 qemu 提供的调试端口,对 qemu 中的整个系统进行调试,在用户空间和内核中下地址都可以!(target remote :1234。不能用 ni)
如果要在内核中下断点的话,获取符号地址有两种方式:
- 如果有 root 权限,可以打开 /proc/kallsyms
- 如果有系统镜像且没有打开 ASLR,可以直接 objdump 或 nm 等。
自制环境搭建(使用 buildroot)
buildroot 是一个制作嵌入式 linux 环境的工具,会首先编译出一个目标环境的工具链,然后用这个工具链交叉编译出一个内核+小型文件系统出来。
使用 buildroot 的总体流程如下:
# 首先需要下载 buildroot: http://buildroot.org/downloads/
make list-defconfigs # 列出一些预定义(predefined)的配置
make qemu_x86_64_defconfig # 使用预定义的 QEMU x86_64 配置(这一步会覆盖用户自定义的配置,所以请小心!)
make menuconfig # buildroot 自己的配置项
make linux-menuconfig # linux 的配置项
make busybox-menuconfig # busybox 的配置项
make -j$(nproc)
其中,一个 kernel pwn 环境需要修改的地方包括:
- buildroot 配置
Filesystem Image -> cpio the root filesystemFilesystem images -> ext2/3/4 root file system -> choose ext3
- linux 配置
General Settings -> Initial RAM filesystem and RAM disk (initramfs/initrd) supportKernel Hacking -> Kernel debuggingKernel Hacking -> Compile-time checks and compiler options -> Debug information -> Rely on the toolchain's implicit default DWARF version
- busybox 配置
Shells -> cttyhackRunit Utilities -> setuidgid
编译完以后,把 <PATH_TO_BUILDROOT>/output/images/* 和 <PATH_TO_BUILDROOT>/output/linux-<VERSION>/vmlinux 拿出来。
附带一个启动脚本:
#!/bin/sh
#
# run.sh
# Copyright (C) 2025 y2 <cameudis@gmail.com>
# decompress rootfs.cpio
if [ ! -d rootfs ]; then
mkdir rootfs && cd rootfs
if file "../rootfs.cpio" | grep -q 'gzip compressed'; then
cp ../rootfs.cpio ./rootfs.cpio.gz
gunzip ./rootfs.cpio.gz
else
cp ../rootfs.cpio .
fi
cpio -idmv < rootfs.cpio
rm rootfs.cpio
cd ..
fi
# compile exp
gcc -masm=intel --static -fno-pie -o exp ./exp.c
mv exp rootfs
# repack rootfs.cpio
cd rootfs
find . -print0 \
| cpio --null -ov --owner root:root --format=newc \
| gzip -9 > ../rootfs.cpio
cd ..
# start vm
qemu-system-x86_64 \
-m 256M \
-cpu kvm64,+smep,+smap \
-smp cores=2,threads=2 \
-kernel ./bzImage \
-initrd ./rootfs.cpio \
-nographic \
-monitor /dev/null \
-snapshot \
-append "console=ttyS0 rdinit=/init nokaslr pti=on quiet oops=panic panic=1" \
-no-reboot -s
再附带一个 CTF /init 脚本:
#!/bin/sh
chown -R 0:0 /
mount -t tmpfs tmpfs /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
# echo 1 > /proc/sys/kernel/dmesg_restrict
# echo 1 > /proc/sys/kernel/kptr_restrict
chown 0:0 /flag
chmod 400 /flag
chmod 777 /tmp
# insmod kgadget.ko
# chmod 777 /dev/kgadget
#setsid cttyhack setuidgid 1000 sh
setsid cttyhack setuidgid 0 sh
poweroff -d 0 -f
内核版本指定
- buildroot 配置
Toolchain -> Custom kernel headers series -> choose xxxKernel -> Kernel version
内核修改(patch)
buildroot 每次重新 build 会重新拉下来一个指定版本的 linux 源码进行编译,因此如果想要对内核源码进行修改,需要手动制作 patch 文件并在 buildroot 中指定。
- 方便起见,最好先整一个带 git 版本控制的完整 linux 源码(
git clone https://mirror.sjtu.edu.cn/git/linux.git)。 - 切换到目标版本
git checkout xxx - 进行修改
- 将修改提交为一个 commit
git format-patch -1 HEAD将修改导出为一个 patch 文件- 将 patch 文件保存下来,并修改 buildroot 配置
Kernel -> Custom kernel patches,将其修改为 patch 文件的绝对路径或其目录(后者可用于加载多个 patch)
内核模块
如果需要编译内核模块,需要在 buildroot 的 package 目录下新建如下结构:
pwn_kmod
├── Config.in
├── pwn_kmod.mk
└── src
├── Makefile
└── pwn.c
Config.in:
config BR2_PACKAGE_PWN_KMOD
bool "pwn_kmod (out-of-tree)"
depends on BR2_LINUX_KERNEL
help
Kernel module with a controlled bug for practice.
pwn_kmod.mk:
PWN_KMOD_VERSION = 1.0
PWN_KMOD_SITE = package/pwn_kmod/src
PWN_KMOD_SITE_METHOD = local
PWN_KMOD_LICENSE = MIT
define PWN_KMOD_BUILD_CMDS
$(LINUX_MAKE_ENV) $(MAKE) -C $(LINUX_DIR) \
M=$(@D) \
ARCH=$(KERNEL_ARCH) \
CROSS_COMPILE="$(KERNEL_CROSS)" \
HOSTCC="$(HOSTCC)" \
modules
endef
define PWN_KMOD_INSTALL_TARGET_CMDS
$(INSTALL) -D -m 0644 $(@D)/pwn.ko $(TARGET_DIR)/lib/modules/pwn.ko
endef
$(eval $(generic-package))
src/Makefile:
obj-m := pwn.o
src/pwn.c:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#define DEV "pwn"
static int __init p_init(void){
pr_info("pwn loaded\n");
return 0;
}
static void __exit p_exit(void){
pr_info("pwn unloaded\n");
}
module_init(p_init);
module_exit(p_exit);
MODULE_LICENSE("GPL");
最后,还需要在 package/Config.in 中添加一行 source "package/pwn_kmod/Config.in"。
然后,编译后的 kmod 文件就会位于 /lib/modules/ 目录下,可以在 /init 里自行加载。
注意,有时候编译内核模块 package 时,还没有编译好 linux,会导致编译失败。由于我懒得研究怎么添加依赖,所以遇到这个错直接 make linux 就行。
Buildroot 使用技巧
因为 buildroot 一个主要用途是异架构环境配置,他会 build 一整套编译工具链,耗时非常长。所以,当修改了某个部分的配置想要重新编译时,千万不要执行 make clean:
- 当想要完全重新 build 一个包 from scratch,最好的做法是删除
output/build下对应的目录(或者使用make <package>-dirclean指令),然后重新make。 - 当想要重新编译,可以执行
make <package>-rebuild。比如说仅仅通过make linux-menuconfig修改了 linux 的配置时,就可以通过make linux-rebuild重新构建 linux。 - 具体细节可以参考 The Buildroot user manual。
另外,如果移动了 buildroot 目录,似乎需要完全重新编译,否则会有奇怪的问题!
更多参考: