dump出Linux内核所有的slab对象缉拿内核Rootkit

前面的文章介绍了很多种隐藏进程,隐藏TCP连接,隐藏内核模块的方法,总结起来和大多数网上介绍Rootkit的文章种介绍的方法不同点在于:

  • 网上大多数文章均是hook procfs来达到隐藏对象的目的。
  • 我的方法则是直接将对象从链表等数据结构中摘除。

无疑,我的方法更简单,因为很显然,hook procfs需要修改大量关于VFS API调用相关的代码。

但是,跑了和尚跑不了庙,虽然进程,TCP连接,内核模块等摘了链表,它还是在slab中啊!我们只需要dump特定的slab对象,就能找到它们:

  • dump出名字为TCP的kmem cache所有slab对象,就能找到隐藏的TCP连接。
  • dump出名字为task_struct的kmem cache所有slab对象,就能找到隐藏的进程。

然而,内核并没有提供dump所有slab对象的方法。

我们知道,/proc/slabinfo里面的信息完全不足够,这里面仅仅是伴随着slab对象的分配和释放,Linux内核记录的一些统计信息,那么,我们必须自己实现所有slab的dump操作。

来吧,让我实现它。

slab位于buddy系统的上层,从buddy系统拿page,所以我们需要从page上做文章。

让人恼火的是,作为slab的默认实现的slub kmem_cache,竟然没有字段保存自己保有了那些page! 一旦对象被分配了,它就和kmem_cache的管理脱钩了,直到释放时,它才重新回到kmem_cache的freelist中。

然而,不幸中总有万幸,虽然kmem_cache没有所有page的引用,但是page中有反向引用啊:

  • page的slab_cache字段记录着自己所属的kmem_cache。

方案就是,扫描所有的page,按照其kmem_cache字段归类汇总即可。代码如下:

#include <linux/module.h>
#include <linux/kallsyms.h>
#include <net/inet_sock.h>

struct list_head *_slab_caches;
struct list_head __slab_caches;

struct kmemcache_stat {
	struct list_head list;
	struct kmem_cache *slab;
	unsigned long cnt;
	void (*print_obj)(struct kmem_cache *, void *);
	void *priv;
};

// 遍历连续的n个包含连续obj的pages
#define for_each_object(__p, __s, __addr, __objects) \
	for (__p = (__addr); __p < (__addr) + (__objects) * (__s)->size;\
		 __p += (__s)->size)

// task_struct的打印回调函数
void print_task(struct kmem_cache *s, void *p)
{
	struct task_struct *p1 = (struct task_struct *)p;
	printk("##### %s   %d   \n", p1->comm, p1->pid);
}

// tcp socket的打印回调函数
void print_tcp(struct kmem_cache *s, void *p)
{
	struct inet_sock *sk = (struct inet_sock *)p;
	printk("##### %08X->%08X %d %d  \n",
						sk->inet_daddr,
						sk->inet_rcv_saddr,
						ntohs(sk->inet_dport),
						sk->inet_num);
}

// mm_struct的打印回调函数
void print_mm(struct kmem_cache *s, void *p)
{
	struct mm_struct *mm = (struct mm_struct *)p;
	printk("##### owner:[%s] %d   PGD:%lx  \n",
						mm->owner?mm->owner->comm:"[null]",
						mm->owner?mm->owner->pid:-1,
						(unsigned long)mm->pgd);
}

// vm_area_struct的打印回调函数
void print_vm(struct kmem_cache *s, void *p)
{
	struct vm_area_struct *vma = (struct vm_area_struct *)p;
	if (vma->vm_mm && vma->vm_mm->owner) {
		printk("##### VMA owner:[%s] %d   PGD:%lx  \n",
						vma->vm_mm->owner->comm,
						vma->vm_mm->owner->pid,
						(unsigned long)vma->vm_mm->pgd);
	}
}

void print_object(struct kmemcache_stat *ment, void *p)
{
	ment->cnt ++;
	if (ment->print_obj) {
		ment->print_obj(ment->slab, p);
	}
}

unsigned long show_objects(struct page *page)
{
	struct kmem_cache *s = page->slab_cache;
	struct kmemcache_stat *entry;
	void *p, *addr;
	int found = 0;

	list_for_each_entry (entry, &__slab_caches, list) {
		if (entry->slab == s) {
			found = 1;
			break;
		}
	}

	if (!found) {
		return 1;
	}
	addr = page_address(page);
	for_each_object (p, s, addr, page->objects) {
		print_object(entry, p);
	}
	// 越过n个pages
	return (PAGE_ALIGN((unsigned long)p) - (unsigned long)addr)/PAGE_SIZE;

}

static void slab_scan(void)
{
	int i = 0;

	for_each_online_node(i) {
		unsigned long spfn, epfn, pfn;

		spfn= node_start_pfn(i);
		epfn = node_end_pfn(i);
		for (pfn = spfn; pfn < epfn;) {
			struct page *page = pfn_to_page(pfn);

			if(!PageSlab(page)) {
				pfn ++;
				continue;
			}
			pfn += show_objects(page);
		}
	}
}

static int __init dump_slab_obj_init(void)
{
	struct kmem_cache *entry;
	struct kmemcache_stat *ment, *tmp;
	unsigned long total = 0;

	_slab_caches = (struct list_head *)kallsyms_lookup_name("slab_caches");
	if (!_slab_caches) {
		return -1;
	}

	INIT_LIST_HEAD(&__slab_caches);
	list_for_each_entry (entry, _slab_caches, list) {
		struct kmemcache_stat *stat;
		stat = kmalloc(sizeof(struct kmemcache_stat), GFP_KERNEL);
		stat->cnt = 0;
		stat->print_obj = NULL;
		INIT_LIST_HEAD(&stat->list);
		list_add(&stat->list, &__slab_caches);
		stat->slab = entry;
		if (!strcmp(entry->name, "task_struct")) {
			stat->print_obj = print_task;
		}
		if (!strcmp(entry->name, "TCP")) {
			stat->print_obj = print_tcp;
		}
		if (!strcmp(entry->name, "mm_struct")) {
			stat->print_obj = print_mm;
		}
		if (!strcmp(entry->name, "vm_area_struct")) {
			stat->print_obj = print_vm;
		}
	}
	slab_scan();

	list_for_each_entry_safe (ment, tmp, &__slab_caches, list) {
		printk("[%s]  %lu\n", ment->slab->name, ment->cnt);
		total += ment->cnt;
		list_del(&ment->list);
		kfree(ment);
	}
	printk("total objs:  %lu\n", total);

	return -1;
}

module_init(dump_slab_obj_init);
MODULE_LICENSE("GPL");

代码非常简单,我来给出一个输出:

 ##### owner:[sshd] 1841   PGD:ffff88003be3d000
 ##### owner:[[null]] -1   PGD:ffff88003c110000
 ##### owner:[bash] 1843   PGD:ffff880000098000
 ##### owner:[sshd] 1745   PGD:ffff880035aac000
 ##### owner:[agetty] 664   PGD:ffff88003c321000
 ##### owner:[dhclient] 1793   PGD:ffff88003c3fd000
 ##### owner:[[null]] -1   PGD:ffff880035aa2000
 ##### owner:[[null]] -1   PGD:ffff88003bd06000
...
 ##### gmain   2260
 ##### tuned   1248
 ##### VMA owner:[firewalld] 658   PGD:ffff88003b7f6000
 ##### VMA owner:[firewalld] 658   PGD:ffff88003b7f6000
 ##### VMA owner:[firewalld] 658   PGD:ffff88003b7f6000
 ##### VMA owner:[firewalld] 658   PGD:ffff88003b7f6000
...	
 ##### 0138A8C0->6E38A8C0 62006 22
 ##### 0138A8C0->6E38A8C0 62011 22
...
 total objs:  2367564

是的,你没有看错,我是故意展示mm_struct,vm_area_struct以及TCP的:

  • 你隐藏了task_struct,甚至不在task的kmem_cache里分配task_struct,但mm_struct/vm_area_struct出卖了你的task。
  • 你隐藏了TCP,从ehash中摘除了它,然而它还在TCP socket的slab中。

Linux内核的所有数据结构编织成了一张大网,这张网彼此关联着。 只要你揪住一个角,其它的东西就全部出来了。

每个task,对于现代操作系统Linux,无论是内核线程还是普通用户进程必然有它的PGD,找到了PGD,就能dump出这个task的地址空间数据。而我们知道,PGD位于task的mm_struct字段mm中,而mm_struct的owner则直接指向了task本身,如果我们在slab中找到了mm_struct,就可以定位到task了,无论它藏在哪里!

如果你害怕有人将fork过程的copy_process给hook了,详见:
https://blog.csdn.net/dog250/article/details/105939822
也就是说mm_struct对象以及task_struct对象本身均不在slab中分配,那么好办,随便找个它的vm_area_struct对象呗,然后从它的vm_mm字段定位mm_struct,进而再定位到task。我们知道,vm_area_struct的分配是随着task的运行自动进行的,很难hook它的分配过程,所以,从vm_area_struct来按图索骥揪出隐藏的task,绝对是一个屡试不爽的好办法。

进水了,进水了。

只要你使用了slab,那么一定可以被按图索骥顺藤摸瓜,获得便利和高效的代价就是被系统管控。 即便我在前面的文章中使用kmalloc的匿名slab分配了task_struct,那依然是个slab对象啊。

如果你想让你的Rootkit藏得更深,那势必要想办法彻底脱离这些内核数据结构的管理,如何做呢?直接调用alloc_pages吗?非也! alloc_pages依然是受控的,获取的页面依然会被加入各种list被管控。到底要怎样呢??很简单!

偷页面!!

这是下文的内容。


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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章