對于這個系列文章,我的規劃如下:這一系列文章的重點集中在介紹linux rootkit中最討論最多也是最受歡迎的一種:loadable kernel module rootkit(LKM rootkit)。
首先介紹最基礎的lkm模塊的編寫與加載以及如何讓lsmod命令無法發現我們的模塊(也就是本文的內容),然后是介紹lkm rootkit中最重要的技術,系統調用掛鉤,我將會給大家介紹三種不同的系統調用掛鉤技術,以便于在不同的場景中選擇最恰當的一種。接下來便是系統實戰,使用我們之前的知識來進一步完善我們的rootkit,包括如何隱藏進程,隱藏端口,徹底隱藏lkm,以及如何向現有的系統LKM模塊注射我們的代碼來改造成我們自己的lkm模塊。
LKM(可加載內核模塊)
LKM的全稱為Loadable Kernel Modules,中文名為可加載內核模塊,主要作用是用來擴展linux的內核功能。LKM的優點在于可以動態地加載到內存中,無須重新編譯內核。由于LKM具有這樣的特點,所以它經常被用于一些設備的驅動程序,例如聲卡,網卡等等。當然因為其優點,也經常被駭客用于rootkit技術當中。
1.基本的LKM的編寫
下面是一個最基本的LKM的實現,接下來我會對這個例子進行講解
/*lkm.c*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
static int lkm_init(void)
{
printk("Arciryas:module loaded
");
return 0;
}
static void lkm_exit(void)
{
printk("Arciryas:module removed
");
}
module_init(lkm_init);
module_exit(lkm_exit);
這個程序并不是很復雜:其中我們的lkm_init()是初始化函數,在該模塊被加載時,這個函數被內核執行,有點構造函數的感覺;與之相對應的,lkm_init()是清除函數,當模塊被卸載時,內核將執行該函數,有點類似析構函數的感覺,注意,如果一個模塊未定義清除函數,則內核不允許卸載該模塊。
為什么我們的初始化與清除函數中,使用的是printk()函數,而并非是我們熟悉的printf()函數呢?注意下我們這個程序包含的頭文件,在LKM中,是無法依賴于我們平時使用的C庫的,模塊僅僅被鏈接到內核,只可以調用內核所導出的函數,不存在可鏈接的函數庫。這是內核編程與我們平時應用程序編程的不同之一。printk()函數將內容紀錄在系統日志文件里,當然我們也可以用printk()將信息輸出至控制臺:
printk(KERN_ALERT "output messages");
其中KERN_ALERT指定了消息的優先級。
module_init和module_exit是內核的特殊宏,我們需要利用這兩個特殊宏告訴內核,我們所定義的初始化函數和清除函數分別是什么。
代碼的描述就到這里,接下來我們需要對我們的LKM程序進行編譯,下面是編譯所需的Makefile:
obj-m := lkm.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
接下來我們鍵入make命令開始編譯,除去編譯的中間產物外,我們僅僅需要的是lkm.ko。
裝載LKM我們需要insmod命令。鍵入
insmod lkm.ko
回車,這時你會發現什么都沒有發生,沒有關系,這是因為我們并沒有對于我們的消息指定KERN_ALERT優先級,此時printk將消息傳輸到了系統日志syslog中,我們可以在/var/log/messages中查看,當然,在不同的發行版以及不同的syslog配置中,該文件的路徑不同。
我們可以cat /var/log/messages或者利用dmesg命令查看printk輸出的消息,如下圖所示:
為了方便起見我只顯示了最后一條信息,也就是我們LKM中初始化函數所輸出的信息。
我們再輸入lsmod命令查看我們的模塊。lsmod命令的作用是顯示已載入系統的模塊。如下圖:
其中lkm當然是我們的模塊名稱,676則代表的是模塊大小,0表示模塊的被使用次數。有興趣的同學可以自己試試lsmod命令查看下系統所加載的其他模塊。
OK,現在我們可以對我們的LKM進行卸載了,卸載LKM的命令是rmmod。鍵入
rmmod lkm.ko
后,我們再查看下系統日志:
可以看出清除函數中的信息也成功輸出,這時再試試lsmod命令,你會發現我們的模塊在其中不復存在了。
2.從lsmod命令中隱藏我們的模塊
現在有個小問題,如果我們既不想讓dmesg也不想讓lsmod這兩個命令察覺到我們的模塊呢?對于rootkit來說,隱蔽性是非常重要的,一個lsmod命令就可以讓我們的lkm遁形,這顯然談不上隱蔽。對于dmesg命令,我們只要刪除掉printk()函數就好,這個函數所起的僅僅是示范作用。但是如何讓lsmod命令無法顯示我們的模塊呢。
在這里我簡單介紹下lsmod原理,以便于讀者理解之后我是如何在lsmod命令中隱藏我的模塊的。lsmod命令是通過/proc/modules來獲取當前系統模塊信息的。而/proc/modules中的當前系統模塊信息是內核利用struct modules結構體的表頭遍歷內核模塊鏈表、從所有模塊的struct module結構體中獲取模塊的相關信息來得到的。結構體struct module在內核中代表一個內核模塊。通過insmod(實際執行init_module系統調用)把自己編寫的內核模塊插入內核時,模塊便與一個 struct module結構體相關聯,并成為內核的一部分,所有的內核模塊都被維護在一個全局鏈表中,鏈表頭是一個全局變量struct module *modules。任何一個新創建的模塊,都會被加入到這個鏈表的頭部,通過modules->next即可引用到。為了讓我們的模塊在lsmod命令中的輸出里消失掉,我們需要在這個鏈表內刪除我們的模塊:
list_del_init(&__this_module.list);
list_del_init函數定義于include/linux/list.h中,我們可以看下它的實現:
static inline void list_del_init (struct list_head * entry)
{
__list_del (entry->prev, entry->next);
INIT_LIST_HEAD (entry);
}
static inline void __list_del (struct list_head * prev, struct list_head * next)
{
next-> prev = prev;
prev-> next = next;
}
static inline void INIT_LIST_HEAD (struct list_head * list)
{
list-> next = list;
list-> prev = list;
}
現在我們將"list_del_init(&__this_module.list);"加入到我們的初始化函數中,保存,編譯,裝載模塊,再輸入lsmod,這時你會發現,輸出中我們的模塊已經找不到了,我們在lsmod命令中成功的隱藏了我們的模塊!
3.從sysfs中隱藏我們的模塊
當然我們還不能高興的太早,除了lsmod命令和相對應的查看/proc/modules以外,我們還可以在sysfs中,也就是通過查看/sys/module/目錄來發現現有的模塊。
?這個問題也很好解決,在初始化函數中添加一行代碼即可解決問題:
kobject_del(&THIS_MODULE->mkobj.kobj);
THIS_MODULE在include/linux/module.h中的定義如下
extern struct module __this_module;
#define THIS_MODULE (&__this_module)
可以看出THIS_MODULE的作用是指向當前模塊。&THIS_MODULE->mkobj.kobj則代表的是struct module結構體的成員struct module_kobject的一部分,結構體的定義如下:
struct module_kobject{
struct kobject kobj;
struct module *mod;
};
其中kobj是一個struct kobject結構體,而kobject是組成設備模型的基本結構。這時我們又要簡單介紹下sysfs這個概念,sysfs是一種基于ram的文件系統,它提供了一種用于向用戶空間展現內核空間里的對象、屬性和鏈接的方法。sysfs與kobject層次緊密相連,它將kobject層次關系表現出來,使得用戶空間可以看見這些層次關系。通常,sysfs是掛在在/sys目錄下的,而/sys/module是一個sysfs的一個目錄層次, 包含當前加載模塊的信息. 我們通過kobject_del()函數刪除我們當前模塊的kobject就可以起到在/sys/module中隱藏lkm的作用。
好了,這時再將"kobject_del(&THIS_MODULE->mkobj.kobj);"也添加在初始化函數里,保存,編譯,裝載模塊,然后再去看看/sys/module,是不是什么也看不到了?
結語
對于lkm的入門以及lkm的簡單隱藏辦法已經介紹完了,但是這只是通向lkm rootkit的長征路上第一步,在下次的文章中,我會介紹lkm rootkit編寫中最為關鍵的技術:system call hook,也就是系統調用掛鉤技術。