Kernel Pwn 基础

环境

CISCN2017-babydriver为例

busybox文件系统

简单的文件系统,提供基本用户环境,一些常用命令和工具

https://www.busybox.net/

make menuconfig

将busybox编译为静态链接的文件

image-20230808234139406

make -j6
make install

编译其他架构busybox

make -j4 CROSS_COMPILE = 其他架构交叉编译器

在编译好的文件夹下(默认为_install)

mkdir -p  proc sys dev etc/init.d

写下init 脚本 chmod +x init

#!/bin/sh
echo "INIT SCRIPT"
mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
mount -t debugfs none /sys/kernel/debug
mount -t tmpfs none /tmp
echo -e "Boot took $(cut -d' ' -f1 /proc/uptime) seconds"
insmod /babydriver.ko
setsid /bin/cttyhack setuidgid 0 /bin/sh

打包脚本

pack.sh

#!/bin/sh

sudo cp -r rootfs rootfs_tmp
sudo cp init rootfs_tmp/
sudo cp babydriver.ko rootfs_tmp/

sudo chmod +x rootfs_tmp/
sudo chmod g-w -R rootfs_tmp/
sudo chmod o-w -R rootfs_tmp/
sudo chown -R root rootfs_tmp/
sudo chgrp -R root rootfs_tmp/
sudo chmod u+s rootfs_tmp/bin/busybox

cd rootfs_tmp
sudo mkdir -p proc sys dev etc/init.d
find . | cpio -o --format=newc > ../rootfs.img
cd ../
sudo rm -rf ./rootfs_tmp

解包

cpio -idmv < rootfs.img

内核

自己编译内核

https://www.kernel.org/

https://mirrors.tuna.tsinghua.edu.cn/kernel/

https://cdn.kernel.org/pub/linux/kernel/

不要在共享目录下来解压kernel,不然会有些符号链接的问题

make help
make menuconfig
make -j4 bzImage

跨平台

ARCH=平台 CROSS_COMPILE= 其他架构交叉编译器 make -j4 bzImage

一般按照题目给的内核文件bzImage编译指定版本内核

grxer@Ubuntu22 ~/kernel-env> file ./bzImage
./bzImage: Linux kernel x86 boot executable bzImage, version 4.4.72 (atum@ubuntu) #1 SMP Thu Jun 15 19:52:50 PDT 2017, RO-rootFS, swap_dev 0X6, Normal VGA

https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.4.72.tar.gz

确定一下gcc版本,防止一些不必要的问题

grxer@Ubuntu22 ~/kernel-env> strings ./bzImage | grep 'gcc'
4.4.72 (atum@ubuntu) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4) ) #1 SMP Thu Jun 15 19:52:50 PDT 2017

自己编译时遇到的问题

grxer@Ubuntu16 /m/h/s/k/linux-4.4.42> make menuconfig
HOSTCC scripts/kconfig/mconf.o
In file included from scripts/kconfig/mconf.c:23:0:
scripts/kconfig/lxdialog/dialog.h:38:20: fatal error: curses.h: No such file or directory
compilation terminated.
scripts/Makefile.host:108: recipe for target 'scripts/kconfig/mconf.o' failed
make[1]: *** [scripts/kconfig/mconf.o] Error 1
Makefile:542: recipe for target 'menuconfig' failed
make: *** [menuconfig] Error 2
解决
sudo apt-get install libncurses5-dev
scripts/sign-file.c:23:30: fatal error: openssl/opensslv.h: No such file or directory
compilation terminated.
scripts/Makefile.host:91: recipe for target 'scripts/sign-file' failed
make[1]: *** [scripts/sign-file] Error 1
make[1]: *** Waiting for unfinished jobs....
HOSTCC arch/x86/tools/relocs_common.o
HOSTLD arch/x86/tools/relocs
Makefile:556: recipe for target 'scripts' failed
make: *** [scripts] Error 2
make: *** Waiting for unfinished jobs....
解决
grxer@Ubuntu16 /m/h/s/k/linux-4.4.42> sudo apt-get install libssl-dev

编译成功

Setup is 17404 bytes (padded to 17408 bytes).
System is 6820 kB
CRC df8a19f
Kernel: arch/x86/boot/bzImage is ready (#1)

两个比较重要的文件就是

  • bzImage:arch/x86/boot/bzImage
  • vmlinux:用来调试

内核镜像详细介绍: https://gohalo.me/post/kernel-compile.html

下载现有内核镜像

sudo apt search linux-image- 
sudo apt download xxxxx
dpkg -X xxxx extract
./boot/xxx-generic即为bzImage内核镜像文件

系统内核镜像

/boot/目录下

grxer@Ubuntu22 ~> ll -a /boot
total 177M
drwxr-xr-x 4 root root 4.0K 7月 12 09:06 .
drwxr-xr-x 20 root root 4.0K 7月 28 15:44 ..
-rw-r--r-- 1 root root 264K 6月 7 22:23 config-5.19.0-45-generic
-rw-r--r-- 1 root root 264K 6月 21 22:38 config-5.19.0-46-generic
drwx------ 3 root root 4.0K 1月 1 1970 efi
drwxr-xr-x 6 root root 4.0K 7月 12 09:05 grub
lrwxrwxrwx 1 root root 28 7月 12 09:03 initrd.img -> initrd.img-5.19.0-46-generic
-rw-r--r-- 1 root root 70M 7月 12 09:04 initrd.img-5.19.0-45-generic
-rw-r--r-- 1 root root 70M 7月 12 09:04 initrd.img-5.19.0-46-generic
lrwxrwxrwx 1 root root 28 7月 12 09:03 initrd.img.old -> initrd.img-5.19.0-45-generic
-rw-r--r-- 1 root root 179K 2月 7 2022 memtest86+.bin
-rw-r--r-- 1 root root 181K 2月 7 2022 memtest86+.elf
-rw-r--r-- 1 root root 181K 2月 7 2022 memtest86+_multiboot.bin
-rw------- 1 root root 6.2M 6月 7 22:23 System.map-5.19.0-45-generic
-rw------- 1 root root 6.2M 6月 21 22:38 System.map-5.19.0-46-generic
lrwxrwxrwx 1 root root 25 7月 12 09:03 vmlinuz -> vmlinuz-5.19.0-46-generic
-rw------- 1 root root 12M 6月 7 22:23 vmlinuz-5.19.0-45-generic
-rw------- 1 root root 12M 6月 21 22:43 vmlinuz-5.19.0-46-generic
lrwxrwxrwx 1 root root 25 7月 12 09:03 vmlinuz.old -> vmlinuz-5.19.0-45-generic
grxer@Ubuntu22 ~> uname -r
5.19.0-46-generic
grxer@Ubuntu22 ~> sudo file /boot/vmlinuz-5.19.0-46-generic
[sudo] password for grxer:
/boot//vmlinuz-5.19.0-46-generic: Linux kernel x86 boot executable bzImage, version 5.19.0-46-generic (buildd@lcy02-amd64-025) #47~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Wed Jun 21 15:35:31 UTC 2, RO-rootFS, swap_dev 0XB, Normal VGA

vmlinuz-5.19.0-46-generic 内核

initrd.img-5.19.0-46-generic 引导文件

启动内核

start.sh

#!/bin/sh
qemu-system-x86_64 \
-m 64M \
-nographic \
-kernel ./bzImage \
-initrd ./rootfs.img \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 kaslr" \
-smp cores=2,threads=1 \
-cpu kvm64
  • -m:虚拟机内存大小
  • -nographic:以非图形界面模式运行虚拟机
  • -kernel:指定内核镜像文件
  • initrd:指定初始RAM磁盘镜像文件
  • append:设置内核启动附加参数
  • smp:设置虚拟机的处理器拓扑,此处使用 2 个核心和 1 个线程
  • cpu:设置CPU安全选项

调试内核

启动脚本里关掉aslr

-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 nokaslr" \

开启调试接口

启动参数中添加  -gdb dev,如-gdb tcp::1234 可简写为-s 如果希望 qemu 启动后立即挂起,额外添加-S参数
~ # lsmod
babydriver 16384 0 - Live 0xffffffffc0000000 (OE)
~ # find /sys/| grep babydriver
/sys/module/babydriver
/sys/module/babydriver/srcversion
/sys/module/babydriver/notes
/sys/module/babydriver/notes/.note.gnu.build-id
/sys/module/babydriver/taint
/sys/module/babydriver/initstate
/sys/module/babydriver/coresize
/sys/module/babydriver/sections
/sys/module/babydriver/sections/.bss
/sys/module/babydriver/sections/.init.text
/sys/module/babydriver/sections/.data
/sys/module/babydriver/sections/.text
/sys/module/babydriver/sections/__mcount_loc
/sys/module/babydriver/sections/.strtab
/sys/module/babydriver/sections/.symtab
/sys/module/babydriver/sections/.gnu.linkonce.this_module
/sys/module/babydriver/sections/.rodata.str1.1
/sys/module/babydriver/sections/.note.gnu.build-id
/sys/module/babydriver/sections/.exit.text
/sys/module/babydriver/refcnt
/sys/module/babydriver/uevent
/sys/module/babydriver/holders
/sys/module/babydriver/initsize
~ # cat /sys/module/babydriver/sections/.text
0xffffffffc0000000
~ $ cat /sys/module/babydriver/sections/.bss
0xffffffffc0002440
~ $ cat /sys/module/babydriver/sections/.data
0xffffffffc0002000

gdb.sh

#!/bin/sh
sudo gdb -q \
-ex "file vmlinux"\
-ex "add-symbol-file babydriver.ko 0xffffffffc0000000 -s .data 0xffffffffc0002000 -s .bss 0xffffffffc0002440"\
-ex "target remote localhost:1234"\
-ex "b babyopen"

Loadable Kernel Modules(LKMs)

Linux Kernle采用的是宏内核架构,一切的系统服务都需要由内核来提供,效率较高,但是缺乏可扩展性与可维护性,然后就有了可装载内核模块Loadable Kernel Modules,简称LKMs,LKMs可以提供新的系统调用或其他服务,驱动等,LKMs是ELF文件格式,运行在内核

微内核架构就相对安全,宏内核里一个设备失控,危机整个内核,微内核只能危机提供该系统服务的进程

jyy老师有讲到哇: https://jyywiki.cn/OS/2022/slides/21.slides.html#/2

  • lsmod:列出现有的LKMs
  • insmod:装载新的LKM(需要root)
  • rmmod:从内核中移除LKM(需要root)、
  • modprobe: 添加或删除模块,modprobe 在加载模块时会查找依赖关系

一般ctf就是分析这个文件,了解一下程序的编写

在之前下载的源码文件夹下

mkdir testdrive & cd testdrive

kotest.c

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static int ko_test_init(void) {
printk("This is a test ko!\n");
return 0;
}
static void ko_test_exit(void) { printk("Bye Bye~\n"); }
module_init(ko_test_init);//模块入口函数 加载时执行
module_exit(ko_test_exit);//模块出口函数 卸载时执行

Makefile

obj-m += ko_test.o

KDIR =/home/grxer/kernel-env/linux-4.4.72/

all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
rm -rf *.o *.ko *.mod.* *.symvers *.order
  • obj-m: 指定了编译的结果应当为可装载内核模块,指定要声称哪些模块
  • KDIR 用来标识内核源码目录,提供驱动编译所需环境
  • -C 表示进入到指定的内核目录
  • M 指定驱动源码的环境,使 Makefile 在构建模块之前返回到 驱动源码 目录,并在该目录中生成驱动模块

make

grxer@Ubuntu16 ~/k/l/testdrive> make
make -C /home/grxer/kernel-env/linux-4.4.72/ M=/home/grxer/kernel-env/linux-4.4.72/testdrive modules
make[1]: Entering directory '/home/grxer/kernel-env/linux-4.4.72'
CC [M] /home/grxer/kernel-env/linux-4.4.72/testdrive/ko_test.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/grxer/kernel-env/linux-4.4.72/testdrive/ko_test.mod.o
LD [M] /home/grxer/kernel-env/linux-4.4.72/testdrive/ko_test.ko
make[1]: Leaving directory '/home/grxer/kernel-env/linux-4.4.72'
grxer@Ubuntu16 ~/k/l/testdrive> ls
ko_test.c* ko_test.mod.c ko_test.o modules.order
ko_test.ko ko_test.mod.o Makefile* Module.symvers

这里的gcc尽量还是之前确定的那个gcc

ko_test.ko复制到文件系统根目录,然后在init装载新的LKM,重新打包

insmod /ko_test.ko

开机后dmesg就可以看到了

image-20230814231519956

杂项字符设备

注册:int misc_register(struct miscdevice *misc)
释放:int misc_deregister(struct miscdevice *misc)

misc_register注册杂项字符设备,该类设备使用同一个主设备号10,会自动创建设备节点,即设备文件。无需mknod指令创建设备文件。因为misc_register()会调用class_device_creat或者device_creat().

struct miscdevice  {
int minor;//次设备号,一般使用 MISC_DYNAMIC_MINOR 宏来使用动态分配的次设备号
const char *name;//字符设备的名称
struct file_operations *fops;//指向字符设备操作函数集的指针
struct list_head list;
struct device *dev;
struct class_device *class;
char devfs_name[64];
};

file_operations结构体成员基本上都是函数指针,可以通过修改其中的函数指针来达到重写某个函数的目的,如果对这个驱动调用某个其中的函数,就会调用结构体中的函数指针

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
static ssize_t misc_ioctl(struct file* file, unsigned int request, size_t arg) {
switch (request)
{
case 0x666:
printk("0x666\n");
break;
case 0x888:
printk("0x888\n");
break;
default:
return -1;
break;
}
return 0;
}
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = misc_ioctl,
};
static struct miscdevice test_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "testmisc",
.fops = &dev_fops,
};

MODULE_LICENSE("Dual BSD/GPL");
static int __init ko_test_init(void) {
printk("This is a test ko!\n");
int err = misc_register(&test_misc);
if (err) {
printk("envctrl: Unable to get misc minor %d/n",test_misc.minor);
}
return 0;
}
static void __exit ko_test_exit(void) {
printk("Bye Bye~\n");
misc_deregister(&test_misc);
}
module_init(ko_test_init);
module_exit(ko_test_exit);

重写了驱动的ioctl函数

32位的APP运行在64位的kernel上,调用的是compat_ioctl

64位的用户程序运行在64位的kernel上,调用的是unlocked_ioctl,如果是32位的APP运行在32位的kernel上,调用的也是unlocked_ioctl

jyy老师有讲哇: https://jyywiki.cn/OS/2022/slides/25.slides.html#/2/4

用户态测试程序

#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
int fd=open("/dev/testmisc",O_RDONLY);
if(-1==fd){
perror("open error");
return -1;
}
int res=ioctl(fd, 0x666);
printf("res:%d\n",res);
res=ioctl(fd, 0x888);
printf("res:%d\n",res);
}

内核防护

KASLR

kernel address space layout randomize 内核空间地址随机化

qemu开启smep -append ”kaslr“
关闭 -append ”nokaslr“

FGKASLR

FGKASLR 在KASLR基地址随机化的基础上,在加载时刻,以函数粒度重新排布内核代码

STACK PROTECTOR

类似于用户态程序的 canary,x86 架构下 Canary 实现的特点是同一个 task 共享 Canary

内核中的 canary 的值通常取自 gs 段寄存器某个固定偏移处的值

SMEP/SMAP

image-20230811154518221

Supervisor Mode Execution Protection 管理模式执行保护 内核态不可执行用户态的代码

ARM里叫做PXN

CR4寄存器中的第20位来标记

qemu开启smep     -cpu qemu64-v1,smep

Supervisor Mode Access Prevention 管理模式访问保护 内核态不可访问用户态的数据

ARM里叫做PAN

CR4寄存器中的第21位来标记

qemu开启smep     -cpu qemu64-v1,smap

KPTI

image-20230811165305116

Kernel Page Table Isolation 用户态不可看到内核态的页表;内核态不可执行用户态的代码

KPTI的发明也修复了Meltdown漏洞

  • 内核态中的页表包括用户空间内存的页表和内核空间内存的页表, x86_64 的 PTI 机制中,内核态的用户空间内存映射部分被全部标记为不可执行
  • 用户态的页表只包括用户空间内存的页表以及必要的内核空间内存的页表,如用于处理系统调用、中断等信息的内存。
qemu开启smep -append ”kpti=1“
关闭 -append ”nopti“

Linux 4.15 中引入了 KPTI 机制,并且该机制被反向移植到了 Linux 4.14.11,4.9.75,4.4.110

访问控制

dmesg_restrict

/proc/sys/kernel/dmesg_restrict

该选项用于控制是否可以使用 dmesg 来查看内核日志

  • 0 没有任何限制
  • 1 只有具有 CAP_SYSLOG 权限的用户才可以通过 dmesg 命令来查看内核日志。

kptr_restrict

/proc/sys/kernel/kptr_restrict

该选项用于控制在输出内核地址时施加的限制,如通过/proc

  • 0:默认情况下,没有任何限制。
  • 1:使用 %pK 输出的内核指针地址将被替换为 0,除非用户具有 CAP_SYSLOG 特权,并且 group id 和真正的 id 相等。
  • 2:使用 %pK 输出的内核指针都将被替换为 0 ,即与权限无关。