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被管控。到底要怎樣呢??很簡單!

偷頁面!!

這是下文的內容。


浙江溫州皮鞋溼,下雨進水不會胖。

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