突破linux内核模块验证
来源:互联网 发布:卖家加入淘宝客的条件 编辑:程序博客网 时间:2024/05/02 04:39
本篇文章是讲内核安全方面的, 不感兴趣的同学可绕过, 文章写的比较仓促, 有问题的话, 欢迎指出。
Bypassing Linux kernel module version check
By wzt
1、 为什么要突破模块验证
2、 内核是怎么实现的
3、 怎样去突破
4、 总结
5、 参考
6、 附录
1、 为什么要突破模块验证
Linux内核版本很多,升级很快,2个小内核版本中内核函数的定义可能都不一样,为了确保不一致的驱动程序导致kernel oops,
开发者加入了模块验证机制。它在加载内核模块的时候对模块进行校验, 如果模块与主机的一些环境不一致,就会加载不成功。
看下面一个例子,它简单的输出当期系统中的模块列表:
我们在centos5.3环境中编译一下:
然后拷贝到另一台主机centos5.1xen上:
用insmod加载:
报错了,在看下dmesg的信息:
先不管这是什么, 总之我们的模块在另一台2.6.18的主机中加载失败。 通常的做法是要在主机中对源代码进行编译,
然后才能加载成功, 但是如果主机中缺少内核编译环境的话, 我们的rootkit就不能编译, 也不能安装在主机之中,
这是多么尴尬的事情。 没错, 这就是linux kernel开发的特点, 你别指望像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里的关键字, 看看它在哪个文件中:
2.6.18/kernel/module.c:
insmod调用了sys_init_module这个系统调用, 然后进入load_module这个主函数,它解析elf格式的ko文件,然后加载
到内核中:
check_modstruct_version就是用来计算模块符号的一些crc值,不相同就会出现我们在dmesg里看到的
“disagrees about version of symbol”信息。 get_modinfo取得了内核本身的vermagic值,然后用same_magic
函数和内核的vermagic去比较,不同也会使内核加载失败。 所以在这里,我们看到内核对模块验证的时候采用了
2层验证的方法:模块crc值和vermagic检查。
继续跟踪check_modstruct_version, 现在的内核默认的都开启了CONFIG_MODVERSIONS, 如果没有指定这个选项,
函数为空,我们的目的是要在As, Centos下安装模块,redhat不是吃干饭的, 当然开了MODVERSIONS选项。
__find_symbol找到了struct_module这个符号的crc值,然后调用check_version去校验:
它搜寻elf的versions小节, 循环遍历数组中的每个符号表,找到struct_module这个符号,然后去比较crc的值。
现在有个疑问, versions小节是怎么链接到模块的elf文件中去的呢? 在看下编译后的生成文件, 有一个list.mod.c
这个文件是模块在编译的时候,调用了linux-2.6.18/scripts/modpost这个文件生成的。
里面增加了2个小节.gnu.linkonce.this_module和__versions。 __versions小节的内容就是
一些字符串和值组成的数组,check_version就是解析这个小节去做验证。 这里还有一个
MODULE_INFO宏用来生成模块的magic字符串,这个在以后的vermagic中要做验证。
先看下vermagic的格式:
这里可以看到vermagic跟内核版本,smp,gcc版本,内核堆栈大小都有关。
same_magic忽略了对内核版本的判断, 直接比较后面的值。
3、怎样去突破
知道了内核是怎么实现的了, 下面开始想办法绕过这些验证:)
3.1 怎么突破crc验证:
在仔细看下代码:
check_version在循环中只是在寻找struct_module符号, 如果没找到呢? 它会直接返回1! 没错, 这是一个
逻辑bug,在正常情况下,module必会有一个struct_module的符号, 这是modpost生成的。如果我们修改elf文件,
把struct_module这个符号改名,岂不是就可以绕过crc验证了吗? 先做个实验看下:
.mod.c是由modpost这个工具生成的, 它在linux-2.6.18/scripts/Makefile.modpost文件中被调用, 去看下:
我们用一个很土的方法, 就是在编译模块的时候,modpost生成.mod.c文件后, 暂停下编译,sleep 30秒吧,我们用
这个时间去改写下.mod.c, 把struct_module换个名字。
随便将struct_module改个名:
我们是在centos5.3下编译的, 然后拷贝到centos5.1下, 在执行下insmod看下:
成功了! 这跟我们预期的一样, 我们用这个逻辑bug绕过了模块的crc验证! 这个bug直到2.6.31版本中
才得到修正。 我们可以用这种方法在redhat主机中任意安装模块了。 那么怎样绕过在2.6.31以后的内核呢?
看下它是怎么修补的:
如果没找到struct_module也会返回0, 这样我们就必须将struct_module的值改为正确后, 才能继续安装。
如何找到模块符号的crc值呢? 我们可以去找目标主机中那些已被系统加载的模块的crc值,如ext3文件系统
的模块, 自己写个程序去解析elf文件, 就可以得到某些符号的crc值了。
还有没有更简单的方法呢?去/boot目录下看看,symvers-2.6.18-128.el5.gz貌似和crc有关,gunzip解压后看看:
原来内核中所有符号的crc值都保存在这个文件中。如何改写struct_module的值呢,可以用上面那个土方法,
或者自己写程序去解析elf文件, 然后改写其值。本文最后附上一个小程序用来修改elf的符号和crc值。
3.2 如何突破vermagic验证:
如果我们用list.mod.c中的做法, 用MODULE_INFO宏来生成一个与目标主机相同的vermagic呢? 答案是
否定的,gcc链接的时候会把modinfo小节链接在最后,加载模块的时候还是会读取第一个.modinfo小节。
我们可以用上面那种很土的方法, 先用modinfo命令得到目标主机中某个模块的信息:
然后在用那个很土的方面, 将.mod.c中vermagic值进行修改。还有一种直接修改elf文件的方法,附录在本文后面。
4、总结
前面有一点没有提到, 就是某些内核版本的相同接口的函数代码可能已经变化, 这样在使用这项技术的时候,
最好在同一个大内核版本使用。你也可能感觉要想跨平台安装模块有些麻烦, 这里还有2个方法, 作为一个专业
搞渗透的人来说,他会自己在本地装很多发行版本的linux,特别是root掉一台主机后,会在本地装一个一模一样
的发行版本,smp、kernel stack size、gcc version都一样。在本地机器装上开发环境,这样编译出来的模块
也是可以直接装到目标主机上的,但这很麻烦,因为linux有太多的发行版本了:), 另一个方法就是自己
装一个linux,编译下内核,然后将build后的开发包集成到自己的后门里, 压缩后大概几m。 然后传到主机
去解压,编译。庆幸的是,现在大多数主机中都有内核开发环境, 直接去主机编译就ok了。
5、参考
【1】 linux kernel source code
http://www.kernel.org
【2】 module injection in 2.6 kernel - Coolq
http://www.nsfocus.net/index.php ... o=view&mid=2533
[ 本帖最后由 W.Z.T 于 2009-12-25 11:00 编辑 ]
Bypassing Linux kernel module version check
By wzt
1、 为什么要突破模块验证
2、 内核是怎么实现的
3、 怎样去突破
4、 总结
5、 参考
6、 附录
1、 为什么要突破模块验证
Linux内核版本很多,升级很快,2个小内核版本中内核函数的定义可能都不一样,为了确保不一致的驱动程序导致kernel oops,
开发者加入了模块验证机制。它在加载内核模块的时候对模块进行校验, 如果模块与主机的一些环境不一致,就会加载不成功。
看下面一个例子,它简单的输出当期系统中的模块列表:
我们在centos5.3环境中编译一下:
然后拷贝到另一台主机centos5.1xen上:
用insmod加载:
报错了,在看下dmesg的信息:
先不管这是什么, 总之我们的模块在另一台2.6.18的主机中加载失败。 通常的做法是要在主机中对源代码进行编译,
然后才能加载成功, 但是如果主机中缺少内核编译环境的话, 我们的rootkit就不能编译, 也不能安装在主机之中,
这是多么尴尬的事情。 没错, 这就是linux kernel开发的特点, 你别指望像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里的关键字, 看看它在哪个文件中:
2.6.18/kernel/module.c:
insmod调用了sys_init_module这个系统调用, 然后进入load_module这个主函数,它解析elf格式的ko文件,然后加载
到内核中:
check_modstruct_version就是用来计算模块符号的一些crc值,不相同就会出现我们在dmesg里看到的
“disagrees about version of symbol”信息。 get_modinfo取得了内核本身的vermagic值,然后用same_magic
函数和内核的vermagic去比较,不同也会使内核加载失败。 所以在这里,我们看到内核对模块验证的时候采用了
2层验证的方法:模块crc值和vermagic检查。
继续跟踪check_modstruct_version, 现在的内核默认的都开启了CONFIG_MODVERSIONS, 如果没有指定这个选项,
函数为空,我们的目的是要在As, Centos下安装模块,redhat不是吃干饭的, 当然开了MODVERSIONS选项。
__find_symbol找到了struct_module这个符号的crc值,然后调用check_version去校验:
它搜寻elf的versions小节, 循环遍历数组中的每个符号表,找到struct_module这个符号,然后去比较crc的值。
现在有个疑问, versions小节是怎么链接到模块的elf文件中去的呢? 在看下编译后的生成文件, 有一个list.mod.c
这个文件是模块在编译的时候,调用了linux-2.6.18/scripts/modpost这个文件生成的。
里面增加了2个小节.gnu.linkonce.this_module和__versions。 __versions小节的内容就是
一些字符串和值组成的数组,check_version就是解析这个小节去做验证。 这里还有一个
MODULE_INFO宏用来生成模块的magic字符串,这个在以后的vermagic中要做验证。
先看下vermagic的格式:
这里可以看到vermagic跟内核版本,smp,gcc版本,内核堆栈大小都有关。
same_magic忽略了对内核版本的判断, 直接比较后面的值。
3、怎样去突破
知道了内核是怎么实现的了, 下面开始想办法绕过这些验证:)
3.1 怎么突破crc验证:
在仔细看下代码:
check_version在循环中只是在寻找struct_module符号, 如果没找到呢? 它会直接返回1! 没错, 这是一个
逻辑bug,在正常情况下,module必会有一个struct_module的符号, 这是modpost生成的。如果我们修改elf文件,
把struct_module这个符号改名,岂不是就可以绕过crc验证了吗? 先做个实验看下:
.mod.c是由modpost这个工具生成的, 它在linux-2.6.18/scripts/Makefile.modpost文件中被调用, 去看下:
我们用一个很土的方法, 就是在编译模块的时候,modpost生成.mod.c文件后, 暂停下编译,sleep 30秒吧,我们用
这个时间去改写下.mod.c, 把struct_module换个名字。
随便将struct_module改个名:
我们是在centos5.3下编译的, 然后拷贝到centos5.1下, 在执行下insmod看下:
成功了! 这跟我们预期的一样, 我们用这个逻辑bug绕过了模块的crc验证! 这个bug直到2.6.31版本中
才得到修正。 我们可以用这种方法在redhat主机中任意安装模块了。 那么怎样绕过在2.6.31以后的内核呢?
看下它是怎么修补的:
如果没找到struct_module也会返回0, 这样我们就必须将struct_module的值改为正确后, 才能继续安装。
如何找到模块符号的crc值呢? 我们可以去找目标主机中那些已被系统加载的模块的crc值,如ext3文件系统
的模块, 自己写个程序去解析elf文件, 就可以得到某些符号的crc值了。
还有没有更简单的方法呢?去/boot目录下看看,symvers-2.6.18-128.el5.gz貌似和crc有关,gunzip解压后看看:
原来内核中所有符号的crc值都保存在这个文件中。如何改写struct_module的值呢,可以用上面那个土方法,
或者自己写程序去解析elf文件, 然后改写其值。本文最后附上一个小程序用来修改elf的符号和crc值。
3.2 如何突破vermagic验证:
如果我们用list.mod.c中的做法, 用MODULE_INFO宏来生成一个与目标主机相同的vermagic呢? 答案是
否定的,gcc链接的时候会把modinfo小节链接在最后,加载模块的时候还是会读取第一个.modinfo小节。
我们可以用上面那种很土的方法, 先用modinfo命令得到目标主机中某个模块的信息:
然后在用那个很土的方面, 将.mod.c中vermagic值进行修改。还有一种直接修改elf文件的方法,附录在本文后面。
4、总结
前面有一点没有提到, 就是某些内核版本的相同接口的函数代码可能已经变化, 这样在使用这项技术的时候,
最好在同一个大内核版本使用。你也可能感觉要想跨平台安装模块有些麻烦, 这里还有2个方法, 作为一个专业
搞渗透的人来说,他会自己在本地装很多发行版本的linux,特别是root掉一台主机后,会在本地装一个一模一样
的发行版本,smp、kernel stack size、gcc version都一样。在本地机器装上开发环境,这样编译出来的模块
也是可以直接装到目标主机上的,但这很麻烦,因为linux有太多的发行版本了:), 另一个方法就是自己
装一个linux,编译下内核,然后将build后的开发包集成到自己的后门里, 压缩后大概几m。 然后传到主机
去解压,编译。庆幸的是,现在大多数主机中都有内核开发环境, 直接去主机编译就ok了。
5、参考
【1】 linux kernel source code
http://www.kernel.org
【2】 module injection in 2.6 kernel - Coolq
http://www.nsfocus.net/index.php ... o=view&mid=2533
[ 本帖最后由 W.Z.T 于 2009-12-25 11:00 编辑 ]
- 突破linux内核模块验证
- 突破linux内核模块验证
- 突破linux内核模块验证
- 突破Linux内核模块校验机制
- 突破Linux内核模块校验机制
- 突破Linux内核模块校验机制
- 单独编译Linux内核中的某一模块(验证可行!)
- Linux内核模块:模块参数
- Linux 内核模块
- linux内核模块加载
- Linux内核模块
- Linux内核模块编程
- Linux内核模块编程
- Linux内核模块使用指南
- linux内核模块解析
- Linux内核驱动模块
- linux内核模块编译
- linux 添加内核模块
- 出现bad interpreter:No such file or directory的原因+ 编译信息重定向问题
- VC 修改窗口属性 GetWindowLong SetWindowLong
- 设计模式4 - 构建者模式Builder Design Pattern
- 动态区间最大值最小值区间和查询,支持区间设置,线段树
- LIBPCAP
- 突破linux内核模块验证
- C# String.Format格式说明
- 一, 监控缓冲区的命中率
- NEFU要崛起——第1场 A - Theatre Square
- 安装VirtualBox虚拟网卡并任意修改其物理地址(MAC)
- PhotoShop与css3的结缘,自动生成css3样式的ps插件css3ps
- 每天学点shell [第一天]
- oracle 10g DATA PUMP 的REMAP_SCHEMA和REMAP_TABLESPACE的功能
- 【小蒙淘金】12月3日金评-日内震荡反弹为主