zoukankan      html  css  js  c++  java
  • 绕过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版本差别大的结果。
  • 相关阅读:
    XML(学习笔记)
    css样式学习笔记
    Request(对象)
    sql一些错误修改的总结
    转载(如何学习C#)
    sql server(学习笔记2 W3Cschool)
    sql sqrver(学习笔记1 W3Cschool)
    关于 flutter开发碰到的各种问题,有的已经解决有的一直没解决或者用其他方法替代
    关于 Flutter IOS build It appears that your application still contains the default signing identifier.
    关于 flutter本地化问题 The getter 'pasteButtonLabel' was called on null
  • 原文地址:https://www.cnblogs.com/super-king/p/3296347.html
Copyright © 2011-2022 走看看