在Linux系统中ptrace进程跟踪是调试、分析程序的核心能力——比如我们常用的GDB调试器就是靠ptrace系统调用来实现断点调试、查看进程内存、单步执行等功能。但凡事有两面性恶意程序也可能通过ptrace跟踪系统中的关键进程窃取数据或篡改执行逻辑。今天我们就通过一个极简的Linux内核模块聊聊如何从内核层面识别并终止所有存在ptrace跟踪关系的进程对跟踪者与被跟踪者同时拆解背后的内核编程思路和核心知识点。一、先搞懂ptrace跟踪关系的内核本质要写终止ptrace跟踪的模块首先得知道内核里怎么记录“谁在跟踪谁”。Linux内核中每个进程都对应一个叫task_struct的结构体可以理解成进程的“身份证”里面藏着所有和进程相关的信息。其中有个关键的链表字段ptraced——只要进程A是跟踪者tracer那么所有被A跟踪的进程tracee都会被挂到A的ptraced链表上。简单说想找谁是跟踪者只要看它的ptraced链表有没有内容就行想找被跟踪的进程直接遍历这个链表就能全部揪出来。这也是我们这个模块的核心判断依据。二、模块核心功能简单直接的“终止逻辑”这个内核模块的目标没有花里胡哨的设计核心就四件事模块加载后周期性扫描系统中所有进程逐个检查进程的ptraced链表识别出“跟踪者”进程先给所有被跟踪进程发送无法拦截的SIGKILL信号必杀信号终止被跟踪者再终止跟踪者进程模块卸载时安全停止扫描并清理占用的内核资源。整个逻辑“简单粗暴”但胜在高效——毕竟内核层操作不需要经过用户态的权限校验能直接对进程下“终止指令”。三、代码拆解我们把核心代码拆成几个关键部分用普通人能懂的话解释不堆砌专业术语1. 基础准备宏定义与头文件#definepr_fmt(fmt)KBUILD_MODNAME: fmt#includelinux/list.h#includelinux/module.h#includelinux/sched.h#includelinux/sched/signal.h#includelinux/workqueue.hpr_fmt宏让内核打印日志时自动带上模块名比如日志会显示模块名: Loaded!方便我们定位日志来源头文件list.h用来操作内核链表比如ptraced链表sched.h用来访问进程的task_struct结构体workqueue.h用来实现“周期性扫描”的功能。2. 核心工具函数杀进程判断跟踪者1kill_task给进程发“致命信号”staticvoidkill_task(structtask_struct*task){send_sig(SIGKILL,task,1);}send_sig是内核里发送信号的函数这里发的是SIGKILL信号9——这是Linux里最“狠”的信号进程无法忽略、无法捕获收到就只能终止。参数里的1表示“向整个进程组发送”确保彻底杀死进程不留残留。2is_tracer判断是不是“跟踪者”staticboolis_tracer(structlist_head*children){structlist_head*list;list_for_each(list,children){structtask_struct*tasklist_entry(list,structtask_struct,ptrace_entry);if(task)returntrue;}returnfalse;}这个函数接收一个链表也就是进程的ptraced链表用list_for_each遍历链表如果链表里有元素说明有被跟踪的进程就返回“是跟踪者”true否则返回false。这里的list_entry是内核链表的核心宏——简单说就是“从链表节点找到对应的进程信息task_struct”是内核链表操作的必学知识点。3kill_tracee杀死所有被跟踪者staticvoidkill_tracee(structlist_head*children){structlist_head*list;list_for_each(list,children){structtask_struct*task_ptracedlist_entry(list,structtask_struct,ptrace_entry);pr_info(ptracee - comm: %s, pid: %d, gid: %d, ptrace: %d\n,task_ptraced-comm,task_ptraced-pid,task_ptraced-tgid,task_ptraced-ptrace);kill_task(task_ptraced);}}遍历跟踪者的ptraced链表逐个取出被跟踪进程先打印进程名、PID等信息方便排查再调用kill_task发SIGKILL终止它。3. 核心扫描逻辑check函数staticvoidcheck(void){structtask_struct*task;for_each_process(task){if(!is_tracer(task-ptraced))continue;kill_tracee(task-ptraced);kill_task(task);/* Kill the tracer once all tracees are killed */}}for_each_process是内核提供的宏能遍历系统中所有进程。对每个进程先用is_tracer判断是不是跟踪者如果是先杀所有被它跟踪的进程kill_tracee最后再杀这个跟踪者本身。4. 周期性扫描工作队列的妙用模块需要“一直扫描”但内核里不能用死循环会占满CPU所以用了“延迟工作队列”实现周期性执行staticvoidperiodic_routine(structwork_struct*ws){if(likely(loaded))check();queue_delayed_work(wq,dont_trace_task,JIFFIES_DELAY);}periodic_routine是每次要执行的函数先看模块是否还加载loaded为true如果是就执行check扫描进程然后把自己重新加入工作队列延迟1个内核节拍JIFFIES_DELAY1约10ms后再次执行——这样就实现了“循环扫描”既不卡CPU又能实时监控。5. 模块的加载与卸载生命周期管理1加载函数dont_trace_initstaticint__initdont_trace_init(void){wqcreate_workqueue(...);queue_delayed_work(wq,dont_trace_task,JIFFIES_DELAY);loadedtrue;pr_info(Loaded!\n);return0;}模块加载时创建工作队列 → 把扫描任务加入队列1个节拍后首次执行 → 标记模块已加载。2卸载函数dont_trace_exitstaticvoid__exitdont_trace_exit(void){loadedfalse;cancel_delayed_work(dont_trace_task);flush_workqueue(wq);destroy_workqueue(wq);pr_info(Unloaded.\n);}模块卸载时先标记“不再扫描” → 取消还没执行的扫描任务 → 等正在执行的任务完成 → 销毁工作队列释放资源——这是内核模块的通用规范避免内存泄漏或任务残留。四、设计思路与执行流程我们用流程图把模块的核心逻辑梳理清楚一看就懂否是否是模块加载创建工作队列提交首次延迟扫描任务10ms后执行重新提交延迟任务等待下一次扫描模块是否已加载终止本次任务遍历系统所有进程进程的ptraced链表非空遍历ptraced链表杀死所有被跟踪进程杀死该跟踪者进程模块卸载标记模块未加载取消未执行的扫描任务等待正在执行的任务完成销毁工作队列释放资源模块卸载完成If you need the complete source code, please add the WeChat number (c17865354792)这个模块的设计思路其实是内核编程的通用思路找对数据结构依托task_struct的ptraced链表精准定位跟踪关系——内核编程的核心就是“找对数据结构问题就解决了一半”异步周期性执行用工作队列实现非阻塞的循环扫描既保证实时性又不占用过多CPU资源安全的资源管理加载时创建资源卸载时彻底清理遵循“谁创建谁销毁”的原则。五、核心知识点总结从这个模块学到什么这个小模块虽然代码量少但覆盖了Linux内核编程的三大核心领域是入门内核开发的绝佳案例1. 进程管理领域task_structLinux内核描述进程的“核心结构体”包含PID、进程名、链表、状态等所有进程信息for_each_process遍历系统所有进程的基础宏是进程监控、进程管理类模块的必备工具内核态信号发送send_sig比用户态的kill系统调用权限更高能直接对所有进程发送信号。2. 内核链表领域list_headLinux内核的“通用链表”几乎所有内核数据结构比如进程链表、文件链表都基于它实现list_for_each/list_entry遍历链表、转换链表节点的核心宏——掌握这两个就能操作内核中90%的链表结构。3. 工作队列领域工作队列是内核实现“异步任务”的核心机制适合处理需要延迟、周期性执行的任务模块卸载时必须用flush_workqueue等待任务完成这是避免内核资源泄漏的关键。六、模块测试运行完整步骤制造ptrace跟踪场景测试靶场先开两个终端手动用gdb做ptrace跟踪模拟调试/跟踪场景终端1启动一个常驻进程被跟踪者# 启动sleep进程休眠300秒sleep300记一下这个sleep的PID比如后续用ps查。终端2用gdb附加sleep进程跟踪者# 查sleep的PIDpsaux|grepsleep# gdb附加该PID替换为实际sleep的PIDgdb -p 你的sleep进程PIDgdb成功attach后就建立了ptrace跟踪关系gdb是跟踪者(tracer)sleep是被跟踪者(tracee)。加载模块验证查杀效果1. 加载内核模块回到第三个root终端加载模块insmod dont_trace.ko加载成功无报错查看内核日志确认加载dmesg-w日志会输出模块名: Loaded!2. 观察查杀结果加载后模块会立即周期性扫描瞬间发生两件事终端2的gdb进程直接被杀死退回命令行终端1的sleep进程也被杀死进程退出dmesg会打印被杀死的跟踪者/被跟踪者的进程名、PID等日志。3. 验证无残留# 查gdb和sleep是否还存在psaux|grep-Egdb|sleep正常情况两个进程都已消失说明模块生效。卸载模块测试完成必做测试结束后安全卸载模块避免持续查杀rmmod dont_trace查看dmesg日志会输出模块名: Unloaded.模块停止扫描、资源释放完成。七、最后说两句这个模块虽然功能简单但它是“从0到1”理解内核进程管理的好例子。你可以基于它扩展更多功能比如只终止特定PID的跟踪者、记录跟踪行为日志、或者把“SIGKILL”换成“SIGSTOP”暂停进程而非终止。内核编程的核心从来不是写复杂的代码而是“理解内核的设计逻辑和数据结构”——只要找对了task_struct里的关键字段再配合内核提供的通用工具链表、工作队列就能写出简洁又有效的内核模块。总结该模块的核心是利用task_struct的ptraced链表识别跟踪者通过send_sig发送SIGKILL终止跟踪者和被跟踪者采用工作队列实现周期性扫描遵循内核模块生命周期规范确保安全卸载模块覆盖了进程管理、内核链表、工作队列三大内核编程核心领域是入门内核开发的优质实战案例。Welcome to follow WeChat official account【程序猿编码】