• 绕过kernel模块版本校验检测


    kernel module version check bypass
    1、 举例说明
    2、 内核是怎么实现的
    3、 怎样去突破
    4、 总结
    
    1、 举例说明
    Linux内核版本很多,升级很快,2个小内核版本中内核函数的定义可能都不一样,为了确保不一致的驱动程序导致kernel oops, 
    开发者加入了模块验证机制。它在加载内核模块的时候对模块进行校验, 如果模块与主机的一些环境不一致,就会加载不成功。
    看下面一个例子,它简单的输出当期系统中的模块列表:
    -------------------------------------------------------------------
    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/version.h>
    #include <linux/string.h>
    #include <linux/list.h>
    
    MODULE_LICENSE("GPL");
    struct module *m = THIS_MODULE;
    
    int print_module_test(void)
    {
        struct module *mod;
        list_for_each_entry(mod, &m->list, list) {
            printk("%s
    ", mod->name); 
        }
        return NULL;
    }
    
    static int list_print_init(void)
    {
        printk("load list_print module.
    ");
        print_module_test();
        return 0;
    }
    static void list_print_exit(void)
    {
        printk("unload list_print module.
    ");
    }
    module_init(list_print_init);
    module_exit(list_print_exit);复制代码
    我们在Asianux3.0环境中编译一下:
    [root@localhost list]# uname -a
    Linux localhost.localdomain 2.6.18-8.10AX #1 SMP Wed Jan 21 10:44:23 EST 2007 i686 i686 i386 GNU/Linux
    然后拷贝到另一台主机上Asianux3.0sp2:
    [root@localhost list]#uname -a
    Linux localhost.localdomain 2.6.18-128.7AXS3 #1 SMP Wed Jan 21 10:44:23 EST 2007 i686 i686 i386 GNU/Linux
    用insmod加载:
    [root@localhost ~]# insmod list.ko
    insmod: error inserting 'list.ko': -1 Invalid module format
    报错了,在看下dmesg的信息:
    [root@localhost ~]# dmesg|tail -n 1
    list: disagrees about version of symbol struct_module
    
    先不管这是什么, 总之我们的模块在另一台2.6.18的主机中加载失败。 通常的做法是要在主机中对源代码进行编译,
    然后才能加载成功, 但是如果主机中缺少内核编译环境的话, 我们的模块就不能编译, 也不能安装在主机之中,这是多么尴尬的事情。 
    没错, 这就是linux kernel开发的特点。
    
    
    2、内核是怎么实现的
    我们去看看内核在加载模块的时候都干了什么。
    grep下dmesg里的关键字, 看看它在哪个文件中:
    [root@localhost linux-2.6.18]# grep -r -i 'disagrees about' kernel/
    kernel/module.c:                printk("%s: disagrees about version of symbol %s
    ",
    
    
    2.6.18/kernel/module.c:
    insmod调用了sys_init_module这个系统调用, 然后进入load_module这个主函数,它解析elf格式的ko文件,然后加载
    到内核中:
    
    /* Allocate and load the module: note that size of section 0 is always
       zero, and we rely on this for optional sections. */
    static struct module *load_module(void __user *umod, unsigned long len, const char __user *uargs)
    {
        ...
        if (!check_modstruct_version(sechdrs, versindex, mod)) {
            err = -ENOEXEC;
            goto free_hdr;
        }
    
        modmagic = get_modinfo(sechdrs, infoindex, "vermagic");
        /* This is allowed: modprobe --force will invalidate it. */
        if (!modmagic) {
            add_taint_module(mod,TAINT_FORCED_MODULE);
            printk(KERN_WARNING "%s: no version magic, tainting kernel.
    ",
                    mod->name);
        } else if (!same_magic(modmagic, vermagic)) {
            printk(KERN_ERR "%s: version magic '%s' should be '%s'
    ", mod->name, modmagic, vermagic);
            err = -ENOEXEC;
            goto free_hdr;
        }
        ...
    }
    check_modstruct_version就是用来计算模块符号的一些crc值,不相同就会出现我们在dmesg里看到的“disagrees about version of symbol”信息。
    get_modinfo取得了内核本身的vermagic值,然后用same_magic函数和内核的vermagic去比较,不同也会使内核加载失败。
    所以在这里,我们看到内核对模块验证的时候采用了2层验证的方法:模块crc值和vermagic检查。
    
    继续跟踪check_modstruct_version, 现在的内核默认的都开启了CONFIG_MODVERSIONS, 如果没有指定这个选项,
    函数为空,我们的目的是要在Asianux 3.0sp2下安装模块,系统当然开了MODVERSIONS选项。
    static inline int check_modstruct_version(Elf_Shdr *sechdrs, unsigned int versindex, struct module *mod)
    {
        const unsigned long *crc;
        struct module *owner;
    
        if (!__find_symbol("struct_module", &owner, &crc, 1))
            BUG();
    
        return check_version(sechdrs, versindex, "struct_module", mod, crc);
    }
    __find_symbol找到了struct_module这个符号的crc值,然后调用check_version去校验:
    
    static int check_version(Elf_Shdr *sechdrs, unsigned int versindex, const char *symname, struct module *mod, const unsigned long *crc)
    {
        unsigned int i, num_versions;
        struct modversion_info *versions;
    
        /* Exporting module didn't supply crcs? OK, we're already tainted. */
        if (!crc)
            return 1;
    
        versions = (void *) sechdrs[versindex].sh_addr;
        num_versions = sechdrs[versindex].sh_size / sizeof(struct modversion_info);
    
        for (i = 0; i < num_versions; i++) {
            if (strcmp(versions[i].name, symname) != 0)
                continue;
    
            if (versions[i].crc == *crc)
                return 1;
    
            printk("%s: disagrees about version of symbol %s
    ", mod->name, symname);
            DEBUGP("Found checksum %lX vs module %lX
    ", *crc, versions[i].crc);
            return 0;
        }
    
        /* Not in module's version table. OK, but that taints the kernel. */
    
        if (!(tainted & TAINT_FORCED_MODULE)) {
            printk("%s: no version for "%s" found: kernel tainted.
    ", mod->name, symname);
            add_taint(TAINT_FORCED_MODULE);
        }
        return 1;
    }
    
    它搜寻elf的versions小节, 循环遍历数组中的每个符号表,找到struct_module这个符号,然后去比较crc的值。
    现在有个疑问, versions小节是怎么链接到模块的elf文件中去的呢?  在看下编译后的生成文件, 有一个list.mod.c
    
    [root@localhost list]# cat list.mod.c
    #include <linux/module.h>
    #include <linux/vermagic.h>
    #include <linux/compiler.h>
    
    MODULE_INFO(vermagic, VERMAGIC_STRING);
    
    struct module __this_module
    __attribute__((section(".gnu.linkonce.this_module"))) = {
        .name = KBUILD_MODNAME,
        .init = init_module,
    #ifdef CONFIG_MODULE_UNLOAD
        .exit = cleanup_module,
    #endif
    };
    
    static const struct modversion_info ____versions[]
    __attribute_used__
    __attribute__((section("__versions"))) = {
        { 0x89e24b9c, "struct_module" },
        { 0x1b7d4074, "printk" },
    };
    
    static const char __module_depends[]
    __attribute_used__
    __attribute__((section(".modinfo"))) =
    "depends=";
    
    MODULE_INFO(srcversion, "26DB52D8A56205333D414B9");
    这个文件是模块在编译的时候,调用了linux-2.6.18/scripts/modpost这个文件生成的。
    里面增加了2个小节.gnu.linkonce.this_module和__versions。 __versions小节的内容就是一些字符串和值组成的数组,
    check_version就是解析这个小节去做验证。 这里还有一个MODULE_INFO宏用来生成模块的magic字符串,这个在以后的vermagic中要做验证。
    
    先看下vermagic的格式:
    [root@localhost list]# modinfo list.ko
    filename: list.ko
    license: GPL
    srcversion: 26DB52D8A56205333D414B9
    depends: 
    vermagic: 2.6.18-128.el5 SMP mod_unload 686 REGPARM 4KSTACKS gcc-4.1复制代码
    这里可以看到vermagic跟内核版本,smp,gcc版本,内核堆栈大小都有关。
    
    3、怎样去突破
    知道了内核是怎么实现的了, 下面开始想办法绕过这些验证。
    3.1 怎么突破crc验证:
    在仔细看下代码:
    for (i = 0; i < num_versions; i++) {
        if (strcmp(versions[i].name, symname) != 0)
            continue;
    
        if (versions[i].crc == *crc)
            return 1;
    
        printk("%s: disagrees about version of symbol %s
    ", mod->name, symname);
        DEBUGP("Found checksum %lX vs module %lX
    ", *crc, versions[i].crc);
        return 0;
    }
    /* Not in module's version table. OK, but that taints the kernel. */
    if (!(tainted & TAINT_FORCED_MODULE)) {
        printk("%s: no version for "%s" found: kernel tainted.
    ",
                mod->name, symname);
        add_taint(TAINT_FORCED_MODULE);
    }
    return 1;
    
    check_version在循环中只是在寻找struct_module符号, 如果没找到呢? 它会直接返回1!  没错, 这是一个
    逻辑bug,在正常情况下,module必会有一个struct_module的符号, 这是modpost生成的。如果我们修改elf文件,
    把struct_module这个符号改名,岂不是就可以绕过crc验证了吗? 先做个实验看下:
    .mod.c是由modpost这个工具生成的, 它在linux-2.6.18/scripts/Makefile.modpost文件中被调用, 去看下:
    PHONY += __modpost
        __modpost: $(wildcard vmlinux) $(modules:.ko=.o) FORCE
    $(call cmd,modpost)
        @sleep 30           //  add line
    
    我们用一个很土的方法, 就是在编译模块的时候,modpost生成.mod.c文件后, 暂停下编译,sleep 30秒吧,我们用
    这个时间去改写下.mod.c, 把struct_module换个名字。
    
    随便将struct_module改个名:
    
    [root@localhost list]# cat list.mod.c
    ...
    static const struct modversion_info ____versions[]
    __attribute_used__
    __attribute__((section("__versions"))) = {
        { 0x89e24b9c, "stauct_module" },  //modify line
        { 0x1b7d4074, "printk" },
    };
    ...
    我们是在AS3下编译的, 然后拷贝到AS3SP1下, 在执行下insmod看下:
    [root@localhost ~]# insmod list.ko
    [root@localhost ~]# dmesg|tail
    ata_piix
    libata
    sd_mod
    scsi_mod
    ext3
    jbd
    ehci_hcd
    ohci_hcd
    uhci_hcd
    成功了! 这跟我们预期的一样, 我们用这个逻辑bug绕过了模块的crc验证! 这个bug直到2.6.31版本中才得到修正。 
    我们可以用这种方法在Asianux or redhat主机中任意安装模块了。 那么怎样绕过在2.6.31以后的内核呢?
    看下它是怎么修补的:
    
    for (i = 0; i < num_versions; i++) {
        if (strcmp(versions[i].name, symname) != 0)
            continue;
    
        if (versions[i].crc == *crc)
            return 1;
        DEBUGP("Found checksum %lX vs module %lX
    ", *crc, versions[i].crc);
        goto bad_version;
    }
    
    printk(KERN_WARNING "%s: no symbol version for %s
    ", mod->name, symname);
    return 0;
    
    bad_version:
    printk("%s: disagrees about version of symbol %s
    ", mod->name, symname);
    return 0;
    
    如果没找到struct_module也会返回0, 这样我们就必须将struct_module的值改为正确后, 才能继续安装。
    如何找到模块符号的crc值呢? 我们可以去找目标主机中那些已被系统加载的模块的crc值,如ext3文件系统的模块,
    或者自己写个程序去解析elf文件, 就可以得到某些符号的crc值了。 
    还有没有更简单的方法呢?去/boot目录下看看,symvers-2.6.18-128.7AXS3.gz貌似和crc有关,gunzip解压后看看:
    [root@localhost boot]# grep 'struct_module' symvers-2.6.18-128.7AXS3
    0x89e24b9c struct_module vmlinux EXPORT_SYMBOL复制代码
    原来内核中所有符号的crc值都保存在这个文件中。如何改写struct_module的值呢,可以用上面那个土方法,
    或者自己写程序去解析elf文件, 然后改写其值。
    
    3.2 如何突破vermagic验证:
    如果我们用list.mod.c中的做法, 用MODULE_INFO宏来生成一个与目标主机相同的vermagic呢? 答案是
    否定的,gcc链接的时候会把modinfo小节链接在最后,加载模块的时候还是会读取第一个.modinfo小节。
    我们可以用上面那种很土的方法, 先用modinfo命令得到目标主机中某个模块的信息:
    [root@localhost list]# modinfo /lib/modules/2.6.18-128.7AXS3/kernel/fs/ext3/ext3.ko
    filename: /lib/modules/2.6.18-128.7AXS3/kernel/fs/ext3/ext3.ko
    license: GPL
    description: Second Extended Filesystem with journaling extensions
    author: Remy Card, Stephen Tweedie, Andrew Morton, Andreas Dilger, Theodore Ts'o and others
    srcversion: B048AC103E5034604A721C5
    depends: jbd
    vermagic: 2.6.18-128.7AXS3 SMP mod_unload 686 REGPARM 4KSTACKS gcc-4.1
    然后在用那个很土的方面, 将.mod.c中vermagic值进行修改。
    ...
    MODULE_INFO(vermagic, "2.6.18-128.7AXS3 SMP mod_unload 686 REGPARM 4KSTACKS gcc-4.1");  //modify line
    ...
    4、总结
    前面有一点没有提到, 就是某些内核版本的相同接口的函数代码可能已经变化, 这样在使用这项技术的时候, 
    最好在同一个大内核版本使用。本人在ubuntu 9.04 上编译然后放入AXS3SP1中#insmod list.ko模块插入没有问题,
    但是没有打印出任何信息,这就是kernel版本差别大的结果。
  • 相关阅读:
    Future和Callable的使用
    Tiny Jpeg Decoder (JPEG解码程序) 源代码分析 1:解码文件头
    jQuery 表格排序插件 Tablesorter 使用
    jQuery 表单验证插件 jQuery Validation Engine 使用
    jQuery 文本编辑器插件 HtmlBox 使用
    开源视频质量评价工具: IQA
    hql 语法与详细解释
    MYSQL常用命令
    C++发送HTTP请求获取网页HTML代码
    编译运行Red5源代码
  • 原文地址:https://www.cnblogs.com/super-king/p/3296347.html
Copyright © 2020-2023  润新知