CRUX下實現進程隱藏(2)

前面咱們介紹瞭如何修改/proc目錄讀取函數的方法實現進程隱藏。這篇博文將介紹另外一種方法——linux

劫持系統調用實現進程隱藏。數組

其基本原理是:加載一個內核模塊(LKM),經過劫持系統調用sys_getdents()來針對進程文件進行適當的過濾,從而達到隱藏進程文件的目的。安全

因爲實驗方法一時,已經修改過了proc_pid_readdir函數,這會影響方法二的實現,所以必須把方法一中所作的修改恢復。打開proc_pid_readdir所在的源文件fs/proc/base.c架構

在其中加入DEBUG_PROC,表示採用方法一,這裏我註釋掉了,表示這時使用方法二。ide

把該函數中對進程hide字段的判斷加入宏定義中便可。以後從新編譯內核,編譯後執行測試程序,已經沒法隱藏進程。函數

下面開始介紹第二種方法:oop

① 分析讀取proc文件系統時所執行的系統調用:測試

在linux下,咱們可使用strace命令來跟蹤一個用戶程序在執行過程當中所使用的系統調用。spa

在linux終端命令行輸入:strace ps aux 2>out.txt操作系統

能夠查詢ps命令所執行的系統調用,結果以下:

......

open("/proc/meminfo", O_RDONLY)         = 4

lseek(4, 0, SEEK_SET)                   = 0

read(4, "MemTotal:         495788 kB\nMemF"..., 2047) = 1114

stat("/proc/self/task", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0

openat(AT_FDCWD, "/proc", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 5

mmap(NULL, 135168, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5330b72000

mmap(NULL, 135168, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5330b51000

getdents(5, /* 140 entries */, 32768)   = 3696

stat("/proc/1", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0

open("/proc/1/stat", O_RDONLY)          = 6

read(6, "1 (init) S 0 1 1 0 -1 4202752 22"..., 4095) = 326

close(6)                               

......

其中,咱們看到ps命令執行過程當中調用了getdents這個函數,而ps命令正是使

用這個函數來讀取/proc目錄下的進程文件的。其對應的系統調用是sys_getdents(),其函數原型以下:

int sys_getdents(unsigned int fd, struct linux_dirent64 __user *dirp, unsigned int count)

其中,fd爲指向目錄文件的文件描述符,該函數根據fd所指向的目錄文件讀取相應dirent結構,並放入dirp中,其中count爲dirp中返回的數據量,正確時該函數返回值爲填充到dirp的字節數。

而咱們要作的就是代替原先的系統調用,使用本身定義的系統調用hacked_getdents(),加上咱們本身的判斷語句就能實現對進程文件的過濾。Linux中,替換系統調用有兩種方法:一是直接修改原先的系統調用;二是編寫一個內核模塊(LKM),經過得到系統調用表的地址來替換原先的系統調用。

由於前者須要從新編譯內核,比較費時。內核模塊是一些可讓操做系統內核在須要時載入和執行的代碼,這一樣意味着它能夠在不須要時由操做系統卸載。它們擴展了操做系統內核的功能卻不須要從新編譯內核,所以內核模塊機制的好處是:既避免了內核的臃腫不堪,又極大程度地提升了內核的可擴展性。因此這裏我採用了內核模塊的方法。

一個內核模塊應該至少包含兩個函數:初始化函數(init)和清理函數(cleanup)。初始函數負責完成一些初始化的工做,在內核模塊被insmod裝載時調用;而清理函數則負責幹一些收尾清理的工做,在內核模塊被rmmod卸載時被執行。

模塊初始化流程以下:

模塊清理流程以下:

③ 得到系統調用表地址

系統調用表(sys_call_table)是用來存放內核中各個系統調用處理函數入口地址的一個數組,所以只要得到系統調用表的地址,咱們就能夠修改系統調用函數,甚至替換原先的系統調用函數。在這一節,咱們要作的事就是找到sys_getdents()的入口地址,並用hacked_getdents()的地址進行替換。

出於安全方面的考慮,Linux從2.4.18內核之後就再也不導出系統調用表,所以要修改系統調用,必須從軟件上獲取系統調用表的地址。因爲不一樣CPU架構的系統獲取系統調用表的方式不同,這裏以我本身的系統(x86_64)爲例,系統調用表地址獲取流程以下:

說明:

a)因爲內核的頁標記爲只讀,嘗試用函數去寫這個區域的內存,會產生一個內核oops。這種保護能夠很簡單的被規避,可經過設置cr0寄存器的WP位爲0,禁止寫保護CPU。

b)Linux x86_64有兩套調用模式:Long模式和兼容模式,分別對應有兩套調用表:sys_call_table,ia32_sys_call_table。實驗中採用Long模式的系統調用表。

使用grep命令從系統映射表中獲取sys_call_table的地址,其中紅框中標出的是系統調用表的地址,能夠看到,系統調用表的地址是ffffffff81a00180。

c)Long方式使用syscall,MSR寄存器地址爲0xc0000082,宏MSR_LSTAR來表明. 使用rdmsrl指令獲取system_call的代碼段起始地址,再經過system_call獲取sys_call_table特徵碼。x86_64下獲取sys_call_table與x86特徵碼不一樣,是"\xff\x14\xc5"。

④ 劫持系統調用

在得到系統調用表的地址後,咱們就能夠對原先的系統調用進行替換了。系統調用

sys_getdents()的入口地址保存在數組sys_call_table以__NR_getdents爲偏移的位置上。咱們定義一個函數指針orig_getdents,用於存放原始的sys_getdents()系統調用,其定義以下:

asmlinkage long (*orig_getdents)(unsigned int, struct linux_dirent64 __user *, unsigned int);

替換過程以下:

/* 保存原始系統調用 */
orig_getdents = sys_call_table[__NR_getdents];
/* 替換原始系統調用 */
sys_call_table[__NR_getdents] = hacked_getdents;
恢復系統調用的代碼以下:
/* 恢復原始系統調用 */
sys_call_table[__NR_getdents] = orig_getdents ;

編寫完內核模塊後,創建Makefile:

接着編譯、連接:

使用insmod hook.ko掛載模塊,rmmod hook卸載模塊

測試結果:

將寫好的內核模塊加載入內核,能夠看到,內核模塊得到的系統調用表地址與使用grep命令得到的系統調用表地址一致。

編譯好內核模塊後,運行咱們的測試程序(測試程序與方法一所用相同),所得結果以下:

相關文章
相關標籤/搜索