环境 以CISCN2017-babydriver 为例
简单的文件系统,提供基本用户环境,一些常用命令和工具
https://www.busybox.net/
将busybox编译为静态链接的文件
编译其他架构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
解包
内核 自己编译内核 https://www.kernel.org/
https://mirrors.tuna.tsinghua.edu.cn/kernel/
https://cdn.kernel.org/pub/linux/kernel/
不要在共享目录下来解压kernel,不然会有些符号链接的问题
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)
vmlinuz-5.19.0-46-generic 内核
initrd.img-5.19.0-46-generic 引导文件
启动内核 start.sh
#!/bin/sh qemu-system-x86_64 \ -m 64 M \ -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,重新打包
开机后dmesg就可以看到了
注册: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; 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
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
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 ,即与权限无关。