抓住被恶意隐藏的Linux内核模块

前面的文章介绍了一种相对彻底的隐藏Linux内核模块的方法:
https://blog.csdn.net/dog250/article/details/106023941

总结下来就是:

  • 摘除list,使得/proc/modules中不可见。
  • 摘除kobject,使得/sys/modules/中不可见。
  • 摘除depend on,使得依赖模块的/sys/modules/$(depended)/holder/中不可见。
  • 摘除vmap area list/rbtree,使得/proc/vmallocinfo中不可见。

然而还是有办法逮到它,本文描述一种与之对抗的方法。

无论如何,只要黑客没有hook掉module_alloc,那么所有的模块的内核均在以下范围内:

#define MODULES_VADDR    _AC(0xffffffffa0000000, UL)
#define MODULES_END      _AC(0xffffffffff000000, UL)

参见/proc/vmallocinfo文件,所有类似下面的区间,均是一个模块的地址空间:

0xffffffffa037d000-0xffffffffa0382000   20480 module_alloc_update_bounds+0x14/0x70 pages=4 vmalloc N0=4
0xffffffffa0382000-0xffffffffa0387000   20480 module_alloc_update_bounds+0x14/0x70 pages=4 vmalloc N0=4
0xffffffffa0387000-0xffffffffa038c000   20480 module_alloc_update_bounds+0x14/0x70 pages=4 vmalloc N0=4

这些区间其实是由vmap_area结构体组成的链表维护的。

虽然黑客将自己的模块从该链表中摘除,但是其vmap_area在内存分配的时候,依然使用了kmalloc-128这个预制的kmem cache slab,我们了解slab分配的行为,即 在连续的page中分配object。 其首page的objects字段表示从该page开始,page连续的object的数量。

虽然链表摘除了,跑了和尚跑不了庙,page还在!

同时,我们注意到module结构体的module_core字段,其实就是自指到该vmap_area的va_start:

// kernel/module.c ---> move_module:
ptr = module_alloc(...);
mod->module_core = ptr;

有了这些线索,那就来吧,我把下面的代码生成的模块叫做 缉拿模块

#include <linux/module.h>
#include <linux/kallsyms.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>

// 遍历从一个page开始的连续object
#define for_each_object(__p, __s, __addr, __objects) \
	for (__p = (__addr); __p < (__addr) + (__objects) * (__s);\
			__p += (__s))
#define MODULES_VADDR    _AC(0xffffffffa0000000, UL)
#define MODULES_END      _AC(0xffffffffff000000, UL)

// 试着解码module结构体
void get_module(unsigned long *ptr, unsigned long size)
{
	int i;
	unsigned long *base;

	base = ptr;
	size -= PAGE_SIZE;

	for (i = 0; i < size/sizeof(unsigned long); i++) {
		// 找到module的module_core字段的自指。
		// 注意,宁可多,不可少,所以这里不在于准,而在于狠。
		if (*base == (unsigned long)ptr) {
			struct module *mod = container_of(base, struct module, module_core);
			printk("------dubious module base:%llx  mod:%p name:[%s]\n", *base, mod, mod->name);
		}
		base ++;
	}
}

// 编译已知的包含vmap_area连续page中的对象
void list_area(unsigned int size, struct page *page)
{
	void *addr;
	void *va;

	addr = page_address(page);
	for_each_object(va, size, addr, page->objects) {
		struct vmap_area *va1 = (struct vmap_area *)va;
		struct vm_struct *vm = va1->vm;
		// 忽略尚未初始化的对象或者已经释放的野对象
		if ((va1->va_start >= MODULES_VADDR && va1->va_start <= MODULES_END) &&
			(va1->va_end >= MODULES_VADDR && va1->va_end <= MODULES_END) &&
			(va1->va_start < va1->va_end) && vm) {
			printk("vmap_area: %llx -  %llx   %d\n", va1->va_start, va1->va_end, vm->nr_pages);
			get_module((unsigned long *)(va1->va_start), va1->va_end - va1->va_start);
		}
	}
}

static void __init walk_vm_area(void)
{
	struct vmap_area *va, *vtmp;
	struct list_head *_vmap_area_list;
	struct page *page, *prev = NULL;

	_vmap_area_list = (struct list_head *)kallsyms_lookup_name("vmap_area_list");

	list_for_each_entry_safe(va, vtmp, _vmap_area_list, list) {
		page = virt_to_page(va);
		if (page != prev && page->objects != 32767) {
			// sizeof(struct vmap_area)向上的kmalloc slab就是kmalloc-128
			list_area(/*sizeof(struct vmap_area)*/128, page);
		}
	}
	// 这里遗漏了per cpu的slub,待补充!
}

static int __init findmod_init(void)
{
	walk_vm_area();

	return -1;
}

module_init(findmod_init);
MODULE_LICENSE("GPL");

将上述代码编译成findmod.ko,加载之。下面我来解释以下缉拿代码的输出,看看它是如何导出系统中所有的模块的:

...
# bridge 模块的内存段
[  366.721103] vmap_area: ffffffffa0343000 -  ffffffffa0362000   30
# 虽然也是自指,但貌似垃圾数据,无意义。
[  366.721108] ------dubious module base:ffffffffa0343000   mod:ffffffffa0355f80  name:[]
# 虽然也是自指,但貌似垃圾数据,无意义。
[  366.721110] ------dubious module base:ffffffffa0343000   mod:ffffffffa0357b88  name:[pass-vlan-input-dev]
# bridge是一个合理的模块名字,该行大大大概率属于有效模块的module_core字段解析而成,container_of即可decode到module结构体。
[  366.721111] ------dubious module base:ffffffffa0343000   mod:ffffffffa035a1c0  name:[bridge]
# 虽然也是自指,但貌似垃圾数据,无意义。
[  366.721112] ------dubious module base:ffffffffa0343000   mod:ffffffffa035af30  name:[]
# stp 模块的内存段
[  366.721114] vmap_area: ffffffffa033e000 -  ffffffffa0343000   4
# 虽然也是自指,但貌似垃圾数据,无意义。
[  366.721115] ------dubious module base:ffffffffa033e000   mod:ffffffffa033eef8  name:[]
# 虽然也是自指,但貌似垃圾数据,无意义。
[  366.721116] ------dubious module base:ffffffffa033e000   mod:ffffffffa033ef58  name:[]
# stp是一个合理的模块名字,该行大大大概率属于有效模块的module_core字段解析而成,container_of即可decode到module结构体。
[  366.721117] ------dubious module base:ffffffffa033e000   mod:ffffffffa03400e0  name:[stp]
# 虽然也是自指,但貌似垃圾数据,无意义。
[  366.721118] ------dubious module base:ffffffffa033e000   mod:ffffffffa0341020  name:[4\xffffffa0\xffffffff\xffffffff\xffffffff\xffffffff]
# ip_set 模块的内存段
[  366.721119] vmap_area: ffffffffa0376000 -  ffffffffa0380000   9
# 虽然也是自指,但貌似垃圾数据,无意义。
[  366.721121] ------dubious module base:ffffffffa0376000   mod:ffffffffa037a248  name:[ip_set_destroy_set]
# 虽然也是自指,但貌似垃圾数据,无意义。
[  366.721123] ------dubious module base:ffffffffa0376000   mod:ffffffffa037bc28  name:[est]
# ip_set是一个合理的模块名字,该行大大大概率属于有效模块的module_core字段解析而成,container_of即可decode到module结构体。
[  366.721124] ------dubious module base:ffffffffa0376000   mod:ffffffffa037c4c0  name:[ip_set]
# 虽然也是自指,但貌似垃圾数据,无意义。
[  366.721125] ------dubious module base:ffffffffa0376000   mod:ffffffffa037cee8  name:[]
...

为什么会有那么多垃圾数据呢?我们发现每个vmalloc段都是在第三次自指中指向module,我们要不要把前两次和第四次的自指去掉呢?

没必要,这里的目的是要缉拿隐藏模块,当然是不求准,但求狠咯,只要是疑似信息,都应该无一遗漏输出!

接下来,我们用之前文章的方法隐藏一个模块:

[root@localhost test]# lsmod |grep undep
[root@localhost test]# echo $?
1
[root@localhost test]# lsmod |grep nf_conntrack
nf_conntrack          105737  1  # 只有依赖,并不知道是undep在依赖。

用我的新方法找找看:

[root@localhost test]# insmod ./findmod.ko
insmod: ERROR: could not insert module ./findmod.ko: Operation not permitted
[root@localhost test]# dmesg |grep undep
[   56.664671] ------dubious module base:ffffffffa0125000   mod:ffffffffa0127000  name:[undep]

成功缉拿归案!

接下来就可以深入detect这个0xffffffffa0125000地址开始的内存了,此外,0xffffffffa0127000就是undep隐藏模块结构体的地址!

如果你真的去尝试运行上面的隐藏模块缉拿代码,就会发现它偶尔(概率还是比较大的)会导致宕机,也就是说加载缉拿模块的时候,会宕机,这是为什么呢?

很正常,在杀毒领域,打架是必须的。互搏的结局经常是玉石俱焚。在前文我那个undep隐藏模块中,在 摘除vmalloc关系链,使得/proc/vmallocinfo中不可见 这一步,有以下的代码:

// rbtree中摘除,无法通过rbtree找到
rb_erase(&va->rb_node, _vmap_area_root);

这句代码其实效果很牛X。

已经分配的vmap_area是通过rbtree关联的,既然rbtree中找不到了,那么在缉拿模块本身载入内存分配它自己的时候,它就有可能将undep隐藏模块的内存当做是free,从而抢占掉它的内存,导致踩内存或者缉拿模块覆盖掉隐藏模块!

牛X的地方在于,这对于undep来讲是一种 自毁机制 !只要有缉拿模块来抓它,它就让缉拿模块本身大概率覆盖掉它,让缉拿模块自己咬到自己的舌头。

这就体现了左右互搏的残酷性和背后的精彩。

当然了,由于两个模块都是我自己写的,我当然一下子就知道问题出在哪里,如果攻防双方素不相识,那才是有趣的事情!

有人可能会有疑问,我这么攻防折腾有意义吗?没意义,但有意思。


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

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

抵扣说明:

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

余额充值