ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

CTF-Wiki Linux 内核驱动编译实战:从 LKM 源码到可装载内核模块的完整构建流程

CTF-Wiki Linux 内核驱动编译实战:从 LKM 源码到可装载内核模块的完整构建流程 文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载导读在 Linux Kernel Pwn 的题目中漏洞往往隐藏在内核模块Loadable Kernel ModuleLKM中而调试与利用的第一步就是能够亲手把一个内核驱动编译为.ko文件并成功装载进目标内核。本文基于 ctf-wiki 仓库中「编译内核驱动」一章build-kernel-module.md展开完整讲解驱动源码、Makefile 的编写方法以及obj-m、KDIR、-C、M等关键参数的含义并结合仓库中 QEMU 模拟环境、内核下载与编译 与 Kbuild 构建系统 等章节带你走通「编写驱动 → 编译成.ko→ 装入 rootfs → 在 QEMU 中 insmod 加载并验证」的完整链路。读完本文你将掌握内核模块的标准编译姿势并能将其复用到任意 CTF 内核题目的环境搭建中。为什么内核 pwn 要先学会编译驱动模块在 基础知识点 一节中已经说明Linux 内核采用宏内核monolithic kernel架构为弥补可扩展性与可维护性不足引入了**可装载内核模块LKM**机制。LKM 可以像积木一样被随时装载进内核、从内核中卸载常见的 LKM 包括设备驱动、文件系统驱动和各类内核扩展模块。大多数 CTF 中的 kernel 漏洞就出现在 LKM 中——出题人会给你一个存在漏洞的驱动再配上一个包含bzImage、rootfs.cpio、boot.sh的启动环境。因此编译并加载一个「测试驱动」是搭建内核调试环境的必经步骤它既能验证你的内核源码与编译工具链是否可用也能验证 rootfs 与 QEMU 启动脚本是否正常为后续分析真实漏洞驱动打下基础。ctf-wiki 的 environment 目录 正是围绕「搭建内核调试分析环境」这一目标组织的本文是其中的核心一环。编写一个最小的内核驱动模块要编译一个驱动首先需要一份驱动源码。仓库给出的示例驱动ko_test代码结构非常精简只包含模块的初始化函数与退出函数#include linux/init.h #include linux/module.h #include linux/kernel.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);逐个解读这份源码中的要素#include linux/init.h与#include linux/module.h前者提供module_init()/module_exit()宏与__init/__exit等标记后者提供内核模块框架所需的基本声明#include linux/kernel.h则提供printk等内核态函数原型。MODULE_LICENSE(Dual BSD/GPL)声明模块许可证。不声明或声明了非 GPL 兼容许可证的模块在加载时内核会将其标记为「污染内核」taints kernel并限制部分仅对 GPL 模块开放的符号EXPORT_SYMBOL_GPL的使用。ko_test_init()模块被装载时自动执行的初始化函数。它调用printk输出一条日志并返回 0表示初始化成功。ko_test_exit()模块被卸载时自动执行的退出函数。module_init(ko_test_init);与module_exit(ko_test_exit);这两个宏把上面的两个函数注册为内核模块的入口与出口。与用户态printf不同printk输出的内容不一定直接显示到终端但一定会写入内核环形缓冲区可通过dmesg查看——这一点在后续验证模块加载时会用到。仓库中更完整的版本简体中文版 LKM 开发文档还展示了带__init/__exit标记、MODULE_AUTHOR声明以及 Kbuild 组织方式的进阶写法可作为扩展阅读。编写驱动 Makefile驱动源码写好后还需要一个 Makefile 来驱动内核的模块构建系统。仓库给出的 Makefile 如下obj-m ko_test.o KDIR /home/iromise/dev/kernel/linux-5.4.98/ all: $(MAKE) -C $(KDIR) M$(PWD) modules clean: rm -rf *.o *.ko *.mod.* *.symvers *.order仓库原文对其中三个核心要素做了说明这里结合内核构建系统的行为进一步展开obj-m ko_test.oobj-m用来指定要编译成模块的目标列表。表示向该列表追加项ko_test.o是源码ko_test.c对应的中间目标文件内核的构建系统会最终把它链接成ko_test.ko。与之相对的是obj-y它表示把目标**编译进内核镜像vmlinux**而不是独立模块。如果你有多个源文件比如a.c、b.c共同构成模块ko_test可以写成ko_test-objs : a.o b.o的形式再由obj-m ko_test.o引用。KDIR标识内核源码目录为驱动编译提供内核头文件、模块构建脚本与规则。仓库示例硬编码为/home/iromise/dev/kernel/linux-5.4.98/对应前文内核下载与编译章节中下载的 5.4.98 内核源码。在通用 Linux 发行版上更常见的做法是利用内核头文件包软链接KDIR /lib/modules/$(shell uname -r)/build这样编译出的模块与当前运行内核版本严格匹配。需要注意的是编译内核模块要求内核源码目录已经配置过生成了 .config并至少执行过一次模块编译准备否则会缺少Module.symvers、include/generated/等构建产物若使用自编译内核编译前需先在内核源码目录执行make modules预处理。$(MAKE) -C $(KDIR) M$(PWD) modules这是整条命令的核心三个要素缺一不可-C $(KDIR)让 make 先进入内核源码目录以该目录下的顶层 Makefile 作为构建入口。M$(PWD)注意M并不是 Makefile 的常规选项而是内核根目录下 Makefile 中使用的变量。它的作用是让内核构建系统在构造模块之前返回到M所指向的目录即驱动源码所在目录并在该目录中生成驱动模块。这解释了为什么在驱动目录里敲一条make最终.ko文件却产生在驱动源码目录下。modules指定执行内核顶层 Makefile 中的modules目标也就是执行内核模块的编译行为。clean目标用于清理编译产物*.o、*.ko、*.mod.*、*.symvers、*.order在重新编译前执行可以避免旧产物干扰。仓库对应的简体中文版文档中给出了等价但更通用化的写法并额外声明了.PHONY: clean伪目标值得对照参考见 Kbuild 构建系统。执行编译从 .c 到 .ko写好源码与 Makefile 后在驱动源码目录下直接运行make由于all是 Makefile 中的第一个目标直接执行make默认就会运行它。整个编译流程实际会经历进入KDIR指定的内核目录 → 读取其顶层 Makefile → 根据M$(PWD)回到驱动目录 → 由obj-m ko_test.o驱动编译ko_test.c→ 链接生成ko_test.ko。编译完成后目录中会出现以下关键产物ko_test.ko最终的内核模块文件。LKMs 与用户态可执行程序同样采用 ELF 格式因此可以直接用 IDA、Ghidra 等逆向工具打开分析参考 基础知识点 中的 LKM 章节。ko_test.o单个目标文件。ko_test.mod.o、ko_test.mod.c模块元数据相关文件记录了模块依赖、许可证、入口点等信息。Module.symvers、modules.order内核构建系统生成的符号版本与构建顺序信息。如果编译报错优先检查三处KDIR路径是否指向真实存在且已配置的内核源码obj-m中的目标名是否与源文件名一致ko_test.o对应ko_test.c内核源码目录是否已经过make modules_prepare或完整编译。装载、验证与卸载内核模块模块编译成功后可以在目标环境中用insmod直接装载insmod /ko_test.ko装载后模块的初始化函数ko_test_init()会被调用其printk输出进入内核缓冲区可用dmesg查看。仓库在 QEMU 模拟环境 的「加载驱动」小节中给出了实际运行效果[ 2.019440] ko_test: loading out-of-tree module taints kernel. [ 2.020847] ko_test: module verification failed: signature and/or required key missing - tainting kernel [ 2.025423] This is a test ko!两行taints kernel警告是正常的前者表示这是树外out-of-tree模块后者表示模块缺少内核签名验证所需的密钥。第三行This is a test ko!正是ko_test_init()中printk的输出说明模块已成功加载并执行了初始化代码。与之配套的常用操作命令还包括见 基础知识点rmmod ko_test从内核卸载模块触发ko_test_exit()本示例会输出Bye Bye~。lsmod列出当前已加载的模块确认驱动是否装载成功。modprobe自动处理模块依赖关系的装载/卸载工具。在 QEMU 内核调试环境中加载驱动在真实 CTF 场景中驱动通常是随内核一起在 QEMU 中启动的。完整的实践流程如下详细步骤见 QEMU 模拟环境编译内核与构建 rootfs按 内核下载与编译 编译出bzImage位于内核源码arch/x86/boot/bzImage与vmlinux并用 busybox 制作rootfs.img。把驱动放进文件系统将编译好的ko_test.ko拷贝到 busybox 的_install目录下再重新用find . | cpio -o --formatnewc ../rootfs.img打包。修改 init 启动脚本在 init 脚本中追加insmod /ko_test.ko使内核启动后自动装载驱动#!/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 insmod /ko_test.ko echo -e Boot took $(cut -d -f1 /proc/uptime) seconds setsid /bin/cttyhack setuidgid 1000 /bin/sh poweroff -f启动并验证用qemu-system-x86_64加上-kernel ./bzImage -initrd ./rootfs.img -append root/dev/ram rw consolettyS0 oopspanic panic1 kaslr -smp cores2,threads1 -cpu kvm64启动后进入 shell 执行dmesg即可看到上节所述的加载日志。此外System.map 使用指南 还补充了内核调试中的符号处理技巧当vmlinux被 stripped 时可用编译生成的System.map每行记录「符号地址 / 符号类型 / 符号名」通过脚本批量导入 IDA恢复符号信息后再结合add-symbol-file ./your_module.ko addr_of_ko加载驱动模块的符号展开后续的漏洞分析。这与你编译出的.ko文件直接相关——.ko的符号表正是分析驱动内部逻辑的第一手资料。进阶使用 Kbuild 组织模块源码仓库的简体中文版 LKM 开发文档 展示了更工程化的组织方式将源码放在src/子目录并编写src/Kbuild文件与驱动根目录的通用 Makefile 分离# src/Kbuild MODULE_NAME ? a3kmod obj-m $(MODULE_NAME).o ccflags-y -I$(src)/include $(MODULE_NAME)-y main.oobj-m指定要编译的模块列表含义与本文第三部分一致。ccflags-y编译选项这里用-I$(src)/include引入自定义头文件目录。$(MODULE_NAME)-y指明$(MODULE_NAME).o依赖的源文件main.o对应main.c多个源文件时逐行列出。根目录 Makefile 则只负责转发构建请求并优先使用发行版内核头文件软链接作为KDIRA3KMOD_ROOT_DIR$(shell pwd) A3KMOD_SRC_DIR$(A3KMOD_ROOT_DIR)/src LINUX_KERNEL_SRC/lib/modules/$(shell uname -r)/build all: $(MAKE) -C $(LINUX_KERNEL_SRC) M$(A3KMOD_SRC_DIR) modules clean: $(MAKE) -C $(LINUX_KERNEL_SRC) M$(A3KMOD_SRC_DIR) clean .PHONY: clean对比两种组织方式可以看出obj-m、-C、M这几个要素是内核模块构建的通用骨架KDIR 的指定则可根据环境灵活选择「自编译内核源码路径」或「/lib/modules/$(uname -r)/build」。理解了这一层你在面对任何 CTF 内核题目时都能快速为题目提供的驱动源码搭起编译环境产出可供调试的.ko文件。总结从一份十几行的ko_test.c和一个四行核心的 Makefile 出发本文完整走通了 Linux 内核驱动的编译全流程obj-m声明模块目标、KDIR指定内核源码、-C与M驱动内核构建系统在驱动目录产出.ko随后通过insmod/dmesg验证加载、rmmod验证卸载并进一步把驱动打包进 busybox rootfs、在 QEMU 启动脚本中自动加载最终衔接System.map与符号导入完成可调试的内核环境闭环。这套流程是 ctf-wiki 内核调试环境搭建系列environment 目录的关键一步也是后续阅读内核堆利用、ROP、提权等高级章节前必须掌握的技能。赞分享文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载相关推荐CTF-Wiki 内核 Pwn 环境搭建一Linux 内核源码下载、签名校验与编译CTF Wiki 内核 Pwn 环境搭建一Linux 内核源码下载、签名校验与编译 内核漏洞利用与调试Kernel Pwn的第一步是获得一份「可调试文档网络安全教程终极Linux内核模块编程指南从零构建完整设备驱动项目终极Linux内核模块编程指南从零构建完整设备驱动项目 Linux内核模块编程是深入理解操作系统底层工作原理的关键途径。本指南基于最新5.x和6.x内核版本文档教程操作系统驱动开发KernelSU 内核构建指南从 GKI 内核源码同步到集成编译的完整流程KernelSU 内核构建指南从 GKI 内核源码同步到集成编译的完整流程 导读 本文基于 KernelSU 官方文档 how to build.md htt操作系统驱动开发上一篇终极指南如何参与Scoop社区活动——线上线下meetup全攻略下一篇claude-seo 的 GBP Categories Prompt 实战指南用 FLOW 模型产出可执行的本地点位交付物创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表