前面的文章介绍了很多种隐藏进程,隐藏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被管控。到底要怎样呢??很简单!
偷页面!!
这是下文的内容。
浙江温州皮鞋湿,下雨进水不会胖。
来源:oschina
链接:https://my.oschina.net/u/4279909/blog/4285828