环境搭建

Linux Kernel Archive下载对应版本的内核源码

wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.11.tar.xz
tar -xvf linux-5.11.tar.xz
cd linux-5.11.1/
make menuconfig
勾选 * Kernel hacking —> Kernel debugging - Kernel hacking —> Compile-time checks and compiler options —> Compile the kernel with debug info - Kernel hacking —> Generic Kernel Debugging Instruments –> KGDB: kernel debugger 编译
make -j$(nproc) bzImage
arch/x86/boot/目录下提取到bzImage,为压缩后的内核文件 再下载busybox构建文件系统,在busybox.net下载版本
wget https://busybox.net/downloads/busybox-1.33.0.tar.bz2
tar -jxvf busybox-1.33.0.tar.bz2
cd busybox-1.33.0/
make menuconfig
勾选 Settings —> Build static file (no shared libs)
make install
cd _install
mkdir -pv {bin,tmp,sbin,etc,proc,sys,home,lib64,lib/x86_64-linux-gnu,usr/{bin,sbin}}
touch etc/inittab
mkdir etc/init.d
touch etc/init.d/rcS
chmod +x ./etc/init.d/rcS
配置gedit etc/inittab
::sysinit:/etc/init.d/rcS
::askfirst:/bin/ash
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init
配置etc/init.d/rcS
sudo cat <<EOF > etc/init.d/rcS
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
mount -t tmpfs tmpfs /tmp
mkdir /dev/pts
mount -t devpts devpts /dev/pts

echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
setsid cttyhack setuidgid 1000 sh

poweroff -d 0 -f
EOF
配置用户组
echo "root:x:0:0:root:/root:/bin/sh" > etc/passwd
echo "ctf:x:1000:1000:ctf:/home/ctf:/bin/sh" >> etc/passwd
echo "root:x:0:" > etc/group
echo "ctf:x:1000:" >> etc/group
echo "none /dev/pts devpts gid=5,mode=620 0 0" > etc/fstab
打包文件系统
find . | cpio -o --format=newc > ../rootfs.cpio
如果需要添加文件可以解包文件系统再打包
cpio -idv < ./rootfs.cpio
qemu运行内核 bzImagerootfs.cpio放到同一个目录下,然后编写sh脚本
#!/bin/sh
qemu-system-x86_64 \
-m 128M \
-kernel ./bzImage \
-initrd ./rootfs.cpio \
-monitor /dev/null \
-append "root=/dev/ram rdinit=/sbin/init console=ttyS0 oops=panic panic=1 loglevel=3 quiet kaslr" \
-cpu kvm64,+smep \
-smp cores=2,threads=1 \
-nographic \
-s

-m:虚拟机内存大小 -kernel:内存镜像路径 -initrd:磁盘镜像路径 -append:附加参数选项 nokalsr:关闭内核地址随机化,方便我们进行调试 rdinit:指定初始启动进程,/sbin/init进程会默认以 /etc/init.d/rcS 作为启动脚本 loglevel=3quiet:不输出log console=ttyS0:指定终端为/dev/ttyS0,这样一启动就能进入终端界面 -monitor:将监视器重定向到主机设备/dev/null,这里重定向至null主要是防止CTF中被人给偷了qemu拿flag -cpu:设置CPU安全选项,在这里开启了smep保护(smep保护就不能采用ret2usr手法了) -s:相当于-gdb tcp::1234的简写(也可以直接这么写),后续我们可以通过gdb连接本地端口进行调试

机制

KASLR

和普通用户态的ASLR差不多,都是基地址+偏移

在未开启 KASLR 保护机制时 * 内核代码段的基址为 0xffffffff81000000  * 直接映射区域的基址为 0xffff888000000000

FGKASLR

KASLR的plus版本,以函数粒度重新排布内核代码 原来不同的函数会在.text一个节上,现在不同的函数在不同的节上

ksymtab

kernel_symbol结构体其记录了函数的偏移、函数名的偏移以及命名空间的偏移 在使用fgkalsr编译后函数重定向通过此结构体

struct kernel_symbol {
int value_offset; // 函数的偏移量
int name_offset; // 符号名称的偏移量
int namespace_offset; // 符号命名空间的偏移量
};
利用kernel_symbol结构体存储的偏移就能找到具体函数的内存地址 比如
cat /proc/kallsyms | grep commit_creds
有时候内核符号表不会记录ksymtab的偏移 __start___ksymtab__stop___ksymtab 被记录在each_symbol_section函数中 只需要
cat /proc/kallsyms | grep each_symbols_section
> addr_A

x/10i arrd_A
> ...
> mov rbx,addr_B
> ...

x/10gx addr_B
> addr_C

x/10wx addr_C
> neg_offset

x/10i addr_B + neg_offset - 0x100000000
> addr_offset_function
### STACK PROTECTOR 类似于canary,用以检测是否发生内核堆栈溢出,通常取自 gs 段寄存器某个固定偏移处的值

SMAP/SMEP

指管理模式访问保护和管理模式执行保护 用来防止内核态访问/执行用户态数据,完全将内核空间与用户空间隔离 绕过的两种方式: 篡改CR4寄存器->ret2usr:CR4寄存器的第20位标识SMEP开关(0关,1开),利用kernel ROP篡改CR4,然后完成ret2usr。 不过现在都是KPTI的内核,内核页面的用户地址没有执行权限ret2usr已经过时

ret2dir:简单说,把用户地址的数据映射到内核地址空间上。利用内核线性映射区对物理空间地址的完整映射,可以找到用户空间的数据,但是地址在内核空间上,利用内核地址访问用户的数据

KPTI

内核页表隔离,内核空间与用户空间使用两组不同的页表集