linux不同本间驱动加载方法.docx
- 文档编号:28515017
- 上传时间:2023-07-18
- 格式:DOCX
- 页数:26
- 大小:23.80KB
linux不同本间驱动加载方法.docx
《linux不同本间驱动加载方法.docx》由会员分享,可在线阅读,更多相关《linux不同本间驱动加载方法.docx(26页珍藏版)》请在冰豆网上搜索。
linux不同本间驱动加载方法
WebZine
∙Introduction----------byroot
∙Flashsky访谈----------byflashsky
∙Struts2框架安全缺陷----------bykxlzx
∙重谈IP欺骗技术----------bypapaya
∙Fuzz客户端存储对象,寻找clientddos----------bywoyigui
∙应用软件缺陷利用的一点心得(Webkit篇)----------bywushi
∙BypassingLinuxkernelmoduleversioncheck----------bywzt
∙ACS-ActiveContentSignatures----------byEduardoVelaNava
∙卡巴虚拟机启发式查毒的绕过方法----------bydangdang
BypassingLinuxkernelmoduleversioncheck
Bywzt
1、为什么要突破模块验证
2、内核是怎么实现的
3、怎样去突破
4、总结
5、参考
6、附录
1、为什么要突破模块验证
Linux内核版本很多,升级很快,2个小内核版本中内核函数的定义可能都不一样,为了确保不一致的驱动程序导致kerneloops,
开发者加入了模块验证机制。
它在加载内核模块的时候对模块进行校验,如果模块与主机的一些环境不一致,就会加载不成功。
看下面一个例子,它简单的输出当期系统中的模块列表:
#include
#include
#include
#include
#include
#include
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wzt");
structmodule*m=&__this_module;
intprint_module_test(void)
{
structmodule*mod;
list_for_each_entry(mod,&m->list,list){
printk("%s\n",mod->name);
}
returnNULL;
}
staticintlist_print_init(void)
{
printk("loadlist_printmodule.\n");
print_module_test();
return0;
}
staticvoidlist_print_exit(void)
{
printk("unloadlist_printmodule.\n");
}
module_init(list_print_init);
module_exit(list_print_exit);
我们在centos5.3环境中编译一下:
[root@localhostlist]#uname-a
Linuxlocalhost.localdomain2.6.18-128.el5#1SMPWedJan2110:
44:
23EST2009i686i686i386GNU/Linux
然后拷贝到另一台主机centos5.1xen上:
[root@localhost~]#uname-a
Linuxlocalhost.localdomain2.6.18-53.el5xen#1SMPMonNov1203:
26:
12EST2007i686i686i386GNU/Linux
用insmod加载:
[root@localhost~]#insmodlist.ko
insmod:
errorinserting'list.ko':
-1Invalidmoduleformat
报错了,在看下dmesg的信息:
[root@localhost~]#dmesg|tail-n1
list:
disagreesaboutversionofsymbolstruct_module
先不管这是什么,总之我们的模块在另一台2.6.18的主机中加载失败。
通常的做法是要在主机中对源代码进行编译,
然后才能加载成功,但是如果主机中缺少内核编译环境的话,我们的rootkit就不能编译,也不能安装在主机之中,
这是多么尴尬的事情:
)。
没错,这就是linuxkernel开发的特点,你别指望像windows驱动一样,编译一个驱动,
然后可以满世界去装^_^.一些rootkit开发者抛弃了lkm类型rk的开发,转而去打kmem,mem的注意,像sk,
moodnt这样的rk大家都喜欢,可以在用户层下动态patch内核,不需要编译环境,wget下来,install即可。
但是它也有很多缺点,比如很不稳定,而且在2.6.x后内核已经取消了kmem这个设备,mem文件也做了映射和读写的
限制。
rk开发者没法继续sk的神话了。
反过来,如果我们的lkm后门不需要编译环境,也可以达到直接insmod的目的,
这是件多么美好的事情,而且lkm后门更加稳定,还不用像sk在内核中添加了很多自己的数据结构。
2、内核是怎么实现的
我们去看看内核在加载模块的时候都干了什么,或许我们可以发现点bug,然后做点手脚,欺骗过去:
)
grep下dmesg里的关键字,看看它在哪个文件中:
[root@localhostlinux-2.6.18]#grep-r-i'disagreesabout'kernel/
kernel/module.c:
printk("%s:
disagreesaboutversionofsymbol%s\n",
2.6.18/kernel/module.c:
insmod调用了sys_init_module这个系统调用,然后进入load_module这个主函数,它解析elf格式的ko文件,然后加载
到内核中:
/*Allocateandloadthemodule:
notethatsizeofsection0isalways
zero,andwerelyonthisforoptionalsections.*/
staticstructmodule*load_module(void__user*umod,
unsignedlonglen,
constchar__user*uargs)
{
...
if(!
check_modstruct_version(sechdrs,versindex,mod)){
err=-ENOEXEC;
gotofree_hdr;
}
modmagic=get_modinfo(sechdrs,infoindex,"vermagic");
/*Thisisallowed:
modprobe--forcewillinvalidateit.*/
if(!
modmagic){
add_taint(TAINT_FORCED_MODULE);
printk(KERN_WARNING"%s:
noversionmagic,taintingkernel.\n",
mod->name);
}elseif(!
same_magic(modmagic,vermagic)){
printk(KERN_ERR"%s:
versionmagic'%s'shouldbe'%s'\n",
mod->name,modmagic,vermagic);
err=-ENOEXEC;
gotofree_hdr;
}
...
}
check_modstruct_version就是用来计算模块符号的一些crc值,不相同就会出现我们在dmesg里看到的
“disagreesaboutversionofsymbol”信息。
get_modinfo取得了内核本身的vermagic值,然后用same_magic
函数和内核的vermagic去比较,不同也会使内核加载失败。
所以在这里,我们看到内核对模块验证的时候采用了
2层验证的方法:
模块crc值和vermagic检查。
继续跟踪check_modstruct_version,现在的内核默认的都开启了CONFIG_MODVERSIONS,如果没有指定这个选项,
函数为空,我们的目的是要在As,Centos下安装模块,redhat不是吃干饭的,当然开了MODVERSIONS选项。
staticinlineintcheck_modstruct_version(Elf_Shdr*sechdrs,
unsignedintversindex,
structmodule*mod)
{
constunsignedlong*crc;
structmodule*owner;
if(!
__find_symbol("struct_module",&owner,&crc,1))
BUG();
returncheck_version(sechdrs,versindex,"struct_module",mod,
crc);
}
__find_symbol找到了struct_module这个符号的crc值,然后调用check_version去校验:
staticintcheck_version(Elf_Shdr*sechdrs,
unsignedintversindex,
constchar*symname,
structmodule*mod,
constunsignedlong*crc)
{
unsignedinti,num_versions;
structmodversion_info*versions;
/*Exportingmoduledidn'tsupplycrcs?
OK,we'realreadytainted.*/
if(!
crc)
return1;
versions=(void*)sechdrs[versindex].sh_addr;
num_versions=sechdrs[versindex].sh_size
/sizeof(structmodversion_info);
for(i=0;i if(strcmp(versions[i].name,symname)! =0) continue; if(versions[i].crc==*crc) return1; printk("%s: disagreesaboutversionofsymbol%s\n", mod->name,symname); DEBUGP("Foundchecksum%lXvsmodule%lX\n", *crc,versions[i].crc); return0; } /*Notinmodule'sversiontable.OK,butthattaintsthekernel.*/ if(! (tainted&TAINT_FORCED_MODULE)){ printk("%s: noversionfor\"%s\"found: kerneltainted.\n", mod->name,symname); add_taint(TAINT_FORCED_MODULE); } return1; } 它搜寻elf的versions小节,循环遍历数组中的每个符号表,找到struct_module这个符号,然后去比较crc的值。 现在有个疑问,versions小节是怎么链接到模块的elf文件中去的呢? 在看下编译后的生成文件,有一个list.mod.c [root@localhostlist]#catlist.mod.c #include #include #include MODULE_INFO(vermagic,VERMAGIC_STRING); structmodule__this_module __attribute__((section(".gnu.linkonce.this_module")))={ .name=KBUILD_MODNAME, .init=init_module, #ifdefCONFIG_MODULE_UNLOAD .exit=cleanup_module, #endif }; staticconststructmodversion_info____versions[] __attribute_used__ __attribute__((section("__versions")))={ {0x89e24b9c,"struct_module"}, {0x1b7d4074,"printk"}, }; staticconstchar__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@localhostlist]#modinfolist.ko filename: list.ko author: wzt license: GPL srcversion: 26DB52D8A56205333D414B9 depends: vermagic: 2.6.18-128.el5SMPmod_unload686REGPARM4KSTACKSgcc-4.1 这里可以看到vermagic跟内核版本,smp,gcc版本,内核堆栈大小都有关。 /*Firstpartiskernelversion,whichweignore.*/ staticinlineintsame_magic(constchar*amagic,constchar*bmagic) { amagic+=strcspn(amagic,""); bmagic+=strcspn(bmagic,""); returnstrcmp(amagic,bmagic)==0; } same_magic忽略了对内核版本的判断,直接比较后面的值。 3、怎样去突破 知道了内核是怎么实现的了,下面开始想办法绕过这些验证: ) 3.1怎么突破crc验证: 在仔细看下代码: for(i=0;i if(strcmp(versions[i].name,symname)! =0) continue; if(versions[i].crc==*crc) return1; printk("%s: disagreesaboutversionofsymbol%s\n", mod->name,symname); DEBUGP("Foundchecksum%lXvsmodule%lX\n", *crc,versions[i].crc); return0; } /*Notinmodule'sversiontable.OK,butthattaintsthekernel.*/ if(! (tainted&TAINT_FORCED_MODULE)){ printk("%s: noversionfor\"%s\"found: kerneltainted.\n", mod->name,symname); add_taint(TAINT_FORCED_MODULE); } return1; 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: $(wildcardvmlinux)$(modules: .ko=.o)FORCE $(callcmd,modpost) 我们用一个很土的方法,就是在编译模块的时候,modpost生成.mod.c文件后,暂停下编译,sleep30秒吧,我们用 这个时间去改写下.mod.c,把struct_module换个名字。 PHONY+=__modpost __modpost: $(wildcardvmlinux)$(modules: .ko=.o)FORCE $(callcmd,modpost) @sleep30 随便将struct_module改个名: [root@localhostlist]#catlist.mod.c #include #include #include MODULE_INFO(vermagic,VERMAGIC_STRING); structmodule__this_module __attribute__((section(".gnu.linkonce.this_module")))={ .name=KBUILD_MODNAME, .init=init_module, #ifdefCONFIG_MODULE_UNLOAD .exit=cleanup_module, #endif }; staticconststructmodversion_info____versions[] __attribute_used__ __attribute__((section("__versions")))={ {0x89e24b9c,"stauct_module"}, {0x1b7d4074,"printk"}, }; staticconstchar__module_depends[] __attribute_used__ __attribute__((section(".modinfo")))= "depends="; MODULE_INFO(srcversion,"26DB52D8A56205333D414B9"); 我们是在centos5.3下编译的,然后拷贝到centos5.1下,在执行下insmod看下: [root@localhost~]#insmodlist.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版本中 才得到修正。 我们可以用这种方法在redhat主机中任意安装模块了。 那么怎样绕过在2.6.31以后的内核呢? 看下它是怎么修补的: for(i=0;i if(strcmp(versions[i].name,symname)! =0) continue; if(versions[i].crc==*crc) return1; DEBUGP("Foundchecksum%lXvsmodule%lX\n", *crc,versions[i].crc); gotobad_version; } printk(KERN_WARNING"%s: nosymbolversionfor%s\n", mod->name,symname); return0; bad_version: printk("%s: disagreesaboutversionofsymbol%s\n", mod->name,symname); return0; 如果没找到struct_module也会返回0,这样我们就必须将struct_module的值改为正确后,才能继续安装。 如何找到模块符号的crc值呢? 我们可以去找目标主机中那些已被系统加载的模块的crc值,如ext3文件系统 的模块,自己写个程序去解析elf文件,就可以得到某些符号的crc值了。 还有没有更简单的方法呢? 去/boot目录下看看,symvers-2.6.18-128.el5.gz貌似和crc有关,gunzip解压后看看: [root@localhostboot]#grep'struct_module'symvers-2.6.18-128.el5 0x89e24b9cstruct_modulevmlinuxEXPORT_SYMBOL 原来内核中所有符号的crc值都保存在这个文件中。 如何改写struct_module的值呢,可以用上面那个土方法, 或者自己写程序去解析elf文件,然后改写其值。 本文最
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- linux 不同 驱动 加载 方法
![提示](https://static.bdocx.com/images/bang_tan.gif)