绕过kernel模块版本校验检测
kernel module version check bypass
、 举例说明
、 内核是怎么实现的
、 怎样去突破
、 总结 、 举例说明
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\n", mod->name);
}
return NULL;
} static int list_print_init(void)
{
printk("load list_print module.\n");
print_module_test();
return ;
}
static void list_print_exit(void)
{
printk("unload list_print module.\n");
}
module_init(list_print_init);
module_exit(list_print_exit);复制代码
我们在Asianux3.0环境中编译一下:
[root@localhost list]# uname -a
Linux localhost.localdomain 2.6.-.10AX # SMP Wed Jan :: EST i686 i686 i386 GNU/Linux
然后拷贝到另一台主机上Asianux3.0sp2:
[root@localhost list]#uname -a
Linux localhost.localdomain 2.6.-.7AXS3 # SMP Wed Jan :: EST i686 i686 i386 GNU/Linux
用insmod加载:
[root@localhost ~]# insmod list.ko
insmod: error inserting 'list.ko': - Invalid module format
报错了,在看下dmesg的信息:
[root@localhost ~]# dmesg|tail -n
list: disagrees about version of symbol struct_module 先不管这是什么, 总之我们的模块在另一台2..18的主机中加载失败。 通常的做法是要在主机中对源代码进行编译,
然后才能加载成功, 但是如果主机中缺少内核编译环境的话, 我们的模块就不能编译, 也不能安装在主机之中,这是多么尴尬的事情。
没错, 这就是linux kernel开发的特点。 、内核是怎么实现的
我们去看看内核在加载模块的时候都干了什么。
grep下dmesg里的关键字, 看看它在哪个文件中:
[root@localhost linux-2.6.]# grep -r -i 'disagrees about' kernel/
kernel/module.c: printk("%s: disagrees about version of symbol %s\n", 2.6./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.\n",
mod->name);
} else if (!same_magic(modmagic, vermagic)) {
printk(KERN_ERR "%s: version magic '%s' should be '%s'\n", 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 .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, ))
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 ; versions = (void *) sechdrs[versindex].sh_addr;
num_versions = sechdrs[versindex].sh_size / sizeof(struct modversion_info); for (i = ; i < num_versions; i++) {
if (strcmp(versions[i].name, symname) != )
continue; if (versions[i].crc == *crc)
return ; printk("%s: disagrees about version of symbol %s\n", mod->name, symname);
DEBUGP("Found checksum %lX vs module %lX\n", *crc, versions[i].crc);
return ;
} /* 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.\n", mod->name, symname);
add_taint(TAINT_FORCED_MODULE);
}
return ;
} 它搜寻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./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.-.el5 SMP mod_unload REGPARM 4KSTACKS gcc-.1复制代码
这里可以看到vermagic跟内核版本,smp,gcc版本,内核堆栈大小都有关。 、怎样去突破
知道了内核是怎么实现的了, 下面开始想办法绕过这些验证。
3.1 怎么突破crc验证:
在仔细看下代码:
for (i = ; i < num_versions; i++) {
if (strcmp(versions[i].name, symname) != )
continue; if (versions[i].crc == *crc)
return ; printk("%s: disagrees about version of symbol %s\n", mod->name, symname);
DEBUGP("Found checksum %lX vs module %lX\n", *crc, versions[i].crc);
return ;
}
/* 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.\n",
mod->name, symname);
add_taint(TAINT_FORCED_MODULE);
}
return ; check_version在循环中只是在寻找struct_module符号, 如果没找到呢? 它会直接返回1! 没错, 这是一个
逻辑bug,在正常情况下,module必会有一个struct_module的符号, 这是modpost生成的。如果我们修改elf文件,
把struct_module这个符号改名,岂不是就可以绕过crc验证了吗? 先做个实验看下:
.mod.c是由modpost这个工具生成的, 它在linux-2.6./scripts/Makefile.modpost文件中被调用, 去看下:
PHONY += __modpost
__modpost: $(wildcard vmlinux) $(modules:.ko=.o) FORCE
$(call cmd,modpost)
@sleep // 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..31版本中才得到修正。
我们可以用这种方法在Asianux or redhat主机中任意安装模块了。 那么怎样绕过在2..31以后的内核呢?
看下它是怎么修补的: for (i = ; i < num_versions; i++) {
if (strcmp(versions[i].name, symname) != )
continue; if (versions[i].crc == *crc)
return ;
DEBUGP("Found checksum %lX vs module %lX\n", *crc, versions[i].crc);
goto bad_version;
} printk(KERN_WARNING "%s: no symbol version for %s\n", mod->name, symname);
return ; bad_version:
printk("%s: disagrees about version of symbol %s\n", mod->name, symname);
return ; 如果没找到struct_module也会返回0, 这样我们就必须将struct_module的值改为正确后, 才能继续安装。
如何找到模块符号的crc值呢? 我们可以去找目标主机中那些已被系统加载的模块的crc值,如ext3文件系统的模块,
或者自己写个程序去解析elf文件, 就可以得到某些符号的crc值了。
还有没有更简单的方法呢?去/boot目录下看看,symvers-2.6.-.7AXS3.gz貌似和crc有关,gunzip解压后看看:
[root@localhost boot]# grep 'struct_module' symvers-2.6.-.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.-.7AXS3/kernel/fs/ext3/ext3.ko
filename: /lib/modules/2.6.-.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.-.7AXS3 SMP mod_unload 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
...
、总结
前面有一点没有提到, 就是某些内核版本的相同接口的函数代码可能已经变化, 这样在使用这项技术的时候,
最好在同一个大内核版本使用。本人在ubuntu 9.04 上编译然后放入AXS3SP1中#insmod list.ko模块插入没有问题,
但是没有打印出任何信息,这就是kernel版本差别大的结果。
绕过kernel模块版本校验检测的更多相关文章
- kernel 模块与简单 hello 模块
Kernel 模块与简单 hello 模块 kernel 模块的简介 Linux 内核进行扩展时,例如编写驱动程序.netfilter功能等,最方便的方式是通过编写模块,然后加载到内核中.由于 ker ...
- linux kernel 模块多文件编译
/*************************************************************************** * linux kernel 模块多文件编译 ...
- 时间转换,django的时间设置,re模块简单校验密码和手机号
时间转换和密码,手机的re模块简单校验 import re,time def check_userinfo(request): pwd = request.POST.get("pwd&quo ...
- mark LINUX_6.8 python_2.6.6 setup版本升级 python 2.7.9 安装 pip 临时使用国内镜像源库 指定模块版本 删除指定模块
简单但却又经常需要使用 网上 贴子也很多 也经常用 所以 做个mark 吧: 1首先下载python2.7.9 源tar包 源码安装 可利用linux自带下载工具wget下载,如下所示: ...
- Go依赖模块版本之Module避坑使用详解
前提 对于Go的版本管理主要用过 glide,下面介绍 Go 1.11 之后官方支持的版本管理工具 mod. 关于 mod 官方给出了三个命令 go help mod.go help modules. ...
- Linux kernel模块管理相关详解
Linux内核模块化设计 1. Linux内核设计:单内核.模块化(动态装载和卸载) (1) Linux:单内核设计,但充分借鉴了微内核体系的设计的优点:为内核引入了模块化机制:(2) 内核的组成部分 ...
- npm 安装指定模块版本
npm list 查看具体模块 如: npm list @antv/g6 如需要安装指定的模块和版本 保存时 - --save-dev 是你开发时候依赖的东西,--save 是你发布之后还 ...
- weblogic 反序列化补丁绕过漏洞的一个批量检测shell脚本(CVE-2017-3248 )
~ 以下内容,仅供学习参考 ~ weblogic 反序列化补丁绕过漏洞已经出了两个月了,balabala ~~~ 废话不说,拿到该漏洞的利用工具weblogic.jar,但只能一个个检测ip和端口,效 ...
- [Kernel]内核版本添加字符和内核版本'+'解决
转自:http://blog.csdn.net/adaptiver/article/details/7225980 之前每次由于git仓库编译出来每次都带有'+', 导致都需要使用git archiv ...
随机推荐
- EPP3怎么安装SVN(EclipsePHP Studio 3.0)
如果你和我一样,喜欢用eclipse,你想用用他来开发PHP,那么EPP是一个不错的选择(个人觉得,中文版的有点不习惯) 我们一般都用svn来进行版本控制和代码共享,但是用epp3的时候会遇到这么一个 ...
- ExtJs owner.componentLayoutCounter问题解
owner.componentLayoutCounter问题解:listeners : { render : function(grid) ...
- HTML招聘简历解析
使用 jsoup 对 HTML 文档进行解析和操作 Jsoup解析html简历与dom4j解析xml是一个道理:首先必须知道html的格式,不知道格式,无法解析.根据格式,再将需要的内容通过下面的方法 ...
- NSMutableAttributedString(富文本)的简单使用
#import "ViewController.h" @interface ViewController () @end @implementation ViewControlle ...
- 基于SMB共享文件夹的上传于下载
需要用到的jar包 http://pan.baidu.com/s/1skQFk77 1.首先在一台电脑上设置共享文件夹 ----上传下载的方法类 package com.strongit.tool ...
- 14_Request对象
[HttpServletRequest简介] HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,开发人员通过 ...
- Move can only be called on an active agent that has been placed a Navmesh的解决办法
这是虽然将场景物体添加了Nav Mesh,但是没有进行Bake(烘焙)导致的. 在网上查了 很多久,都没解决这个error,最后发现是因为在unity4.6以下版本中,Bake是Navigation下 ...
- 九度OJ 1480 最大上升子序列和 -- 动态规划
题目地址:http://ac.jobdu.com/problem.php?pid=1480 题目描述: 一个数的序列bi,当b1 < b2 < ... < bS的时候,我们称这个序列 ...
- [转]优化PHP程序的方法
1. If a method c++an be static, declare it static. Speed improvement is by a factor of 4. 如果一个方法可静态化 ...
- Sublime Text 中使用Git插件连接GitHub
sublime Text的另一个强大之处在于它提供了非常丰富的插件,可以帮助程序员来适合大多数语言的开发.这些插件通过它自己的Package Controll(包管理)组件来安装,非常方便.一般常用的 ...