前面咱們介紹瞭如何修改/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命令得到的系統調用表地址一致。
編譯好內核模塊後,運行咱們的測試程序(測試程序與方法一所用相同),所得結果以下: