前面的文章介紹了很多種隱藏進程,隱藏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被管控。到底要怎樣呢??很簡單!
偷頁面!!
這是下文的內容。
浙江溫州皮鞋溼,下雨進水不會胖。