目的
把進來的漏洞堵上,防止其他人進入系統,尤其是阻止有可能威脅rootkit的代碼。如Anit-rootkit,這個有些難度,我們先實現了基本的控制內核模塊的加載。
例子:小偷潛入了銀行,要保證偷偷在銀行這段時間不會被警察給揪出來。
通知鏈機制
在實現我們如何控制內核模塊加載前,我們先簡要介紹一下linux的通知鏈機制。
大多數的內核子系統都是相互獨立的,因此有可能某個子系統對其他子系統產生的事件感興趣。爲了滿足這個需求,linux提供了通知鏈的機制,使得某個子系統在發生某個事件時通知其他的子系統。
通知鏈的數據結構如下:
struct notifier_block
{
int (*notifier_call)(struct notifier_block *self, unsigned long, void *);
struct notifier_block *next;
int priority;
};
所以這個數據結構是一個函數鏈表,每個節點都註冊了一個函數,當某個事件發生時鏈表上的所有節點對應的函數就會被執行。
另外通知這個事件使所運行的函數由被通知方決定,就是說被通知方可以在鏈表上註冊一個通知函數,在發生某個事件的時候,這些函數就會得到執行,從而通知到被通知方。
我們這裏的事件是加載模塊。所以現在我們來分析一下內核中加載模塊的代碼。首先是init_module,在init_module下函數會返回一個load_module函數,所以我們跟進到load_module函數,會發現有好多流程,比如檢查簽名、prepare_coming_module、使模塊與sysfs發生聯繫、執行模塊入口函數等等。我們關注的是prepare_coming_module,因爲這裏將會調用註冊的通知處理函數。然後我們繼續跟進到prepare_coming_module,會發現它有個blocking_notifier_call_chain函數傳入了MODULE_STATE_COMING參數,還有通知鏈表module_notify_list。它會調用通知鏈中的通知處理函數。這裏相當於內核告訴模塊通知鏈的通知處理函數一個信:MODULE_STATE_COMING,即一個模塊準備好了,同時把這個模塊傳遞給處理函數。繼續跟進,會發現有這樣一行代碼說明在這裏調用我們的通知處理函數。
ret = notifier_call_chain(&nh->head, val, v, nr_to_call,nr_calls);
所以,加載模塊這個事件使用了linux通知鏈的機制,即模塊加載時會執行通知鏈表上的通知處理函數。於是我們就可以編寫一個通知處理函數,當其他模塊完成加載,開始初始化前的狀態爲module_state_coming時,我們篡改他的入口函數和出口函數,達到了其他模塊僞加載的目的。
實現
- 實現通知處理函數
- 加鎖
- 當模塊狀態爲MODULE_STATE_COMING,我們篡改其入口函數與出口函數。
- 解鎖
- 註冊入口函數和註銷出口函數
加鎖的目的是爲了保證篡改成功。
//保存中斷狀態加鎖。
spin_lock_irqsave(&module_notifier_spinlock, flags);
switch (module->state) {
case MODULE_STATE_COMING:
printk("Replacing init and exit functions: %s.\n",
module->name);
// 篡改模塊的初始函數與退出函數。
module->init = fake_init;
module->exit = fake_exit;
break;
default:
break;
}
// 恢復中斷狀態解鎖。
spin_unlock_irqrestore(&module_notifier_spinlock, flags);
功能展示
guard目錄下是阻止其他內核模塊的加載,lamb目錄下是一個簡單的模塊。
正常情況下lamb目錄下模塊的加載是這樣的。
輸入:
結果:
而當加載了module_block模塊後,簡單模塊被其阻止加載了。
輸入:
實驗結果:
參考: