Linux入侵監測系統LIDS原理(轉)

BSDLite發表於2007-08-12
Linux入侵監測系統LIDS原理(轉)[@more@]  一、入侵

  隨著Internet上的Linux主機的增加,越來越多的安全漏洞在當前的GNU/Linux系統上發現。你也許在Internet上聽說過 在Linux下發現bug,它會導致系統很容易的被駭客攻擊。

  因為Linux是一個開放程式碼的系統,漏洞很容易發現,並且也會很快的有補丁出來。但是當漏洞沒有公佈的時候,並且管理員很懶,沒有去打補丁。駭客就會很容易的攻擊這個系統,取得root許可權,在現有的GNU/Linux下,他就可以做任何他想做的事情。現在你可以問,我們現在到底可以做些什麼呢?

  1.1 現在的GNU/Linux錯誤在哪裡?

  超級使用者會濫用職權,他能夠做所有他要做的事情。作為root。他會改變所有的東西。

  許多系統檔案很容易被更改。這些檔案可能是很重要的檔案,如/bin/login,如果一個駭客進入,他可以上傳一個login程式來覆蓋/bin/login,這樣他就可以不用登陸名和密碼來登陸系統。但是這些檔案不需要經常改動,除非你要升級系統。

  模組modules很容易用來中斷核心。模組是為了讓Linux核心更模組話和更高效而設計的。但是當模組加入到核心,它就會成為核心的一部分並且能做原始核心能做的工作。因此,一些不友好的程式碼可以寫成模組來加入到核心裡,這些程式碼就會重定向系統呼叫並且作為一個病毒來執行。

  程式是不受保護的,一些程式,如後臺的web伺服器,一直都認為是沒有嚴格保護的程式。因此,他們就會很容易被駭客攻擊。

  1.2 LIDS的設想是什麼。

  保護重要檔案。因為檔案很容易被root更改,為什麼不嚴格檔案操作呢?因此,LIDS改變了檔案系統在核心裡的安全系統呼叫。如果某個時候一些人訪問一個檔案,他就會進入系統呼叫然後我們就可以檢查檔名並且看她們是否被保護。如果它已經被保護,我們就可以拒絕這個訪問者的要求。

  保護重要的程式。這個和上面的保護程式的想法不是一樣的。當一個系統裡執行一個程式,它會在/proc 檔案系統裡有一個用pid作為路徑名的入口。所以,如果你用“ps –axf”你就可以顯示出當前執行的程式。你可以問如果保護這些程式。如果你要殺死一個程式的話,首先,你鍵入“ps”來得到程式的PID,然後,你鍵入“kill 〈pid〉”來殺死它。但是,如果我不讓你看到程式,你怎麼來殺死這個程式呢?因此,LIDS是用隱藏程式來保護它的。

  另外一個重要的方法就是不讓任何人可以殺死程式,包括root使用者。LIDS能夠保護父程式是init(pid=1)的所有程式 。

  封裝核心。有時候我們需要要把一些必要的模組加入到核心裡來使用,另外,我們也要拒絕任何人包括root使用者向核心插入模組。那麼如何來平衡這個矛盾的問題呢?我們可以只允許在系統啟動的時候插入模組,然後我們封裝模組,在封裝後,核心不允許任何人插入模組到核心裡。透過這種封裝功能,我們能用它來保護重要的檔案,程式,我們可以在系統啟動的時候只允許必要的程式,只改變必要的檔案。在封裝核心後,我們就不能在對檔案有任何的修改。

  二、保護檔案系統

  2.1 保護檔案系統是LIDS的重要功能之一。這個功能是在核心的VFS(虛擬檔案系統)層實現的,我們可以保護任何種類的檔案系統,如EXT2,FAT。

  在LIDS,保護的檔案按種類分為以下幾種:

  只讀的檔案或目錄。只讀檔案意味著它們不被允許改寫,如,在目錄/usr/bin,/sbin。這些型別的檔案大多數都是二進位制系統程式或是系統配置檔案,除了在升級系統的時候,我們不需要改變它們。

  只可增加檔案或目錄。這些檔案是那些只可以增加大小的檔案。大多數是系統的日值檔案,如在/var/log裡的只可增加檔案。

  額外的檔案或目錄,這些檔案沒有被保護。一般來說,你想要保護目錄下的所有檔案,但是,還需要有一些特殊的檔案不要被保護。所以我們可以定義這些檔案作為額外的其他的只讀檔案。

  保護掛載或解除安裝檔案系統。當你在啟動的時候掛載檔案系統的時候,你可以禁止所有人,甚至是root,去解除安裝檔案系統。你也可以禁止任何人在當前檔案系統下掛載檔案系統來覆蓋它。

  2.2 LIDS如何在核心保護檔案

  在這部分,我們會看到一些核心的程式碼來理解LIDS是如何保護檔案的。

  Linux檔案系統資料結構程式

  首先,我們必須瞭解Linux的虛擬檔案系統。

  在Linux裡的每一個檔案,不管是什麼樣子的,都有一個結點inode數,檔案系統提供了以下資料結構。

在/usr/src/Linux/include/Linux/fs.h

struct inode {
struct list_head i_hash;
struct list_head i_list;
struct list_head i_dentry;

unsigned long i_ino; ----&gt inode number.
unsigned int i_count;
kdev_t i_dev; ----&gt device number.
umode_t i_mode;
nlink_t i_nlink;
uid_t i_uid;
......
}



  注意:用來鑑定一個結點inode。這個意思是你可以用一對來得到一個系統裡獨一無二的inode。

在/ur/src/Linux/cinclude/Linux/dcache.h裡
struct dentry {
int d_count;
unsigned int d_flags;
struct inode * d_inode; /* Where the name belongs to - NULL is negative */
struct dentry * d_parent; /* parent directory */
struct dentry * d_mounts; /* mount information */
struct dentry * d_covers;
struct list_head d_hash; /* lookup hash list */
struct list_head d_lru; /* d_count = 0 LRU list */
struct list_head d_child; /* child of parent list */
struct list_head d_subdirs; /* our
......
}


  dentry是一個目錄檔案的入口。透過這個入口,我們可以很容易的在檔案的父目錄下移動。

  例如,如果你一檔案的inode是(struct inode*)file_inode,如果你可以用file_inode->d_entry來得到它的目錄入口並且用file_inode->d_entry->d_parent來得到父目錄的目錄入口。

  LIDS保護資料結構

  在分析完Linux檔案系統後,讓我們來看看LIDS是如何容VFS來保護檔案和目錄的。

在/usr/src/Linux/fs/lids.c

struct secure_ino {
unsigned long int ino; /* the inode number */
kdev_t dev; /* the dev number */
int type; /* the file type */
};



  上面的結構用一對來儲存保護檔案或目錄的結點。“type”是用來標明保護結點檔案型別的。

  LIDS有4種型別

  在/usr/src/Linux/include/Linux/fs.h
  #define LIDS_APPEND 1 /* APPEND ONLY FILE */
  #define LIDS_READONLY 2 /* Read Only File */
  #define LIDS_DEVICE 3 /* Protect MBR Writing to device */
  #define LIDS_IGNORE 4 /* Ignore the protection */

  透過secure_ino結構,我們能很容易的初使化保護的檔案或是在核心裡執行以下函式。

在/usr/src/Linux/fs/lids.c
int lids_add_inode(unsigned long int inode ,kdev_t dev , int type)
{

if ( last_secure == (LIDS_MAX_INODE-1))
return 0;

secure[last_secure].ino = inode;
secure[last_secure].dev = dev;
secure[last_secure].type = type;

secure[++last_secure].ino = 0;

#ifdef VFS_SECURITY_DEBUG
printk("lids_add_inode : return %d
",last_secure);
#endif
return last_secure;
}



  就象你在上面程式碼上可以看到的,給secure_ino加到一個結點上是非常容易的。被保護的結點會在系統啟動的時候初使化。初使化程式在/usr/src/Linux/fs/lids.c的init_vfs_security()裡。

  現在,讓我們看看LIDS是如何來檢查是否一個結點已經受到保護。

在/usr/src/Linux/fs/open.c
int do_truncate(struct dentry *dentry, unsigned long length)
{
struct inode *inode = dentry->d_inode;
int error;
struct iattr newattrs;

/* Not pretty: "inode->i_size" shouldnt really be "off_t". But it is. */
if ((off_t) length < 0)
return -EINVAL;

#ifdef CONFIG_LIDS
if (lids_load && lids_local_load) {
error = lids_check_base(dentry,LIDS_READONLY);
if (error) {
lids_security_alert("Try to truncate a protected file (dev %d %d,inode %ld)",
MAJOR(dentry->d_inode->i_dev),
MINOR(dentry->d_inode->i_dev),
dentry->d_inode->i_ino);
.....................



  這個是LIDS加到核心裡做檢測的一個例子。你會看到lids_check_base()是LIDS保護方法的一個核心函式。

  你可以在LIDS要保護的地方看到很多LIDS保護方法用到lids_check_base()函式,特別是在Linux核心的子目錄下。

在/usr/src/Linux/fs/lids.c

int lids_check_base(struct dentry *base, int flag)
{
..................
inode = base->d_inode; /* get the inode number */
parent = base->d_parent; /* get the parent diretory */

.................
----&gt do {
if ( inode == parent->d_inode)
break;
if ((retval = lids_search_inode(inode))) {
if ( retval == LIDS_IGNORE ||
(retval == LIDS_DEVICE && flag != LIDS_DEVICE))
break;
if ( flag == LIDS_READONLY ||
( flag == LIDS_APPEND && retval >flag ) ||
( flag == LIDS_DEVICE && flag == retval )) {
return -EROFS;
}
break;
}
inode = parent->d_inode;
} while( ((parent = parent->d_parent ) != NULL) );

return 0;
}
lids_check_base()會檢查一個給定檔案的dentry和它的父目錄是否被保護。



  注意:如果它的父目錄被保護,它下面的檔案也會被保護。

  例如,如果“/etc/”被保護,“/etc/passwd”也一樣被保護。

  在核心保護系統呼叫

  為了保護系統,LIDS會在一些檢查臨界的系統呼叫的時候做檢查。因此,我們可以保護系統呼叫和限制檔案系統的使用者呼叫。

  這些是一些例子,

  open(),open是透過禁止一些權利來保護檔案的開啟。 你可以在開啟呼叫open_namei()呼叫的時候LIDS在檢測它。
  mknod(),mknod是用來在指定目錄下保護mknod。
  unlink(), 在核心程式碼檢查do_unlink()。

  三、保護裝置

  Linux的裝置會在/dev/目錄下以檔案的形式列出,我們可以用上面保護檔案的方法來保護裝置。但是在一些情況下,使用者也可以用IO操作來旁路檔案系統來讀寫裝置,我們必須注意這個問題。

  3.1 裝置,核心I/O

  在GNU/Linux系統下的裝置會以檔案的形式表達,所以我們可以用保護檔案系統那樣來保護裝置。

  使用者的I/O訪問是透過系統呼叫sys_operm和sys_iopl來實現的。你可以看看/usr/src/Linux/arch/i386/kernel/ioport.。這個是要基於系統結構的,要是到其他平臺,就需要注意它們的變化。

  3.2 如何用LIDS來保護

  大多數情況下,程式不需要透過在/dev的裝置檔名稱來訪問裝置。但是,一些特殊的程式需要直接訪問,如X Server,這個會寫到/dev/mem和甚至是I/O裝置。我們需要一些額外的東西來保護裝置。LIDS會在配置核心的時候來定義這個功能。

  CONFIG_LIDS_ALLOW_DEV_MEM,如果你選擇了開啟這個功能,你就可以允許一些特殊程式來訪問/dev/men和/dev/kmen這些核心臨界的裝置。如果你想要用核心的X Server,選擇這個功能就會在配置核心的時候提供整個路徑和檔名。

  CONFIG_LIDS_ALLOW_RAW_DISKS,如果選擇這個開啟,你就可以允許一些特殊的程式來訪問物理磁碟。

  CONFIG_LIDS_ALLOW_IO_PORTS,如果你選擇了開啟這個功能,你就可以允許一些特殊的程式來訪I/O埠。

  當系統執行fs/lids.c裡的init_vfs_security()的時候初使化就被呼叫。

#ifdef CONFIG_LIDS_ALLOW_DEV_MEM
lids_fill_table(allow_dev_mem,&last_dev_mem,LIDS_MAX_ALLOWED,CONFIG_LIDS_DEV_MEM_PROGS);
#endif

#ifdef CONFIG_LIDS_ALLOW_RAW_DISKS
lids_fill_table(allow_raw_disks,&last_raw_disks,LIDS_MAX_ALLOWED,CONFIG_LIDS_RAW_DISKS_PROGS);
#endif

#ifdef CONFIG_LIDS_ALLOW_IO_PORTS
lids_fill_table(allow_io_ports,&last_io_ports,LIDS_MAX_ALLOWED,CONFIG_LIDS_IO_PORTS_PROGS);
#endif



  如果一個程式或是程式要直接訪問ip埠或是磁碟裝置,LIDS就會檢查它在陣列 allow_raw_disk,last_io_ports,等)。這個檢查是透過呼叫lids_check_base()裡的lids_search_inode(inode)來實現的。

  如,讓我們看看CONFIG_LIDS_ALLOW_DEV_MEM

/* in lids_search_inode() */

#ifdef CONFIG_LIDS_ALLOW_DEV_MEM
for( i = 0 ; i < last_dev_mem ;i++ ) {
if ( allow_dev_mem.ino == ino && allow_dev_mem.dev == dev) {
return LIDS_READONLY;
}
}
#endif
#ifdef CONFIG_LIDS_ALLOW_RAW_DISKS



  在allow_dev_mem包括了哪一個程式結點在系統啟動的時候在init_vfs_security()裡初使化。用同樣的方法,除了一些特殊程式,我們可以保護裝置,I/O訪問等等。

  四、保護重要程式

  程式是作業系統的動態入口。核心裡有兩個特殊程式,程式ID 0 (swapd) 和程式ID 1(init)。Init程式是在系統啟動的時候所有程式的父程式。

  4.1 不可殺死的程式。

  就象你可以看到是否有人要奪得root特權一樣,我們可以很容易的殺死那些該核心傳送特別訊號的程式。為了殺死一個程式,你必須得到程式的ID,然後用kill命令來殺死它。

  系統殺死程式的呼叫是kill,是在核心裡的sys_kill()命令裡的呼叫。

  讓我們看看LIDS的保護程式碼

在/usr/src/Linux/kernel/signal.c裡

asmlinkage int
sys_kill(int pid, int sig)
{
struct siginfo info;

#ifdef CONFIG_LIDS_INIT_CHILDREN_LOCK pid_t this_pid;
int i;
#ifdef CONFIG_LIDS_ALLOW_KILL_INIT_CHILDREN
if (!(current->flags & PF_KILLINITC))
#endif
if (lids_load && lids_local_load && LIDS_FISSET(lids_flags,LIDS_FLAGS_LOCK_INIT_CHILDREN)) {
this_pid = pid>0?pid:-pid;
for(i=0;i if( this_pid == lids_protected_pid) {
lids_security_alert("Try to kill pid=%d,sig=%d
",pid,sig);
return -EPERM;
}
}
}
#endif
...
}
你可以在核心裡看到兩個標籤,,CONFIG_LIDS_INIT_CHILDREN_LOCK 和CONFIG_LIDS_ALLOW_KILL_INIT_CHILDREN.



  在CONFIG_LIDS_INIT_CHILDREN_LOCK的開啟狀態,LIDS能保護初使的執行程式。如,如果你在系統裡執行inetd程式,你可以在隱藏核心前執行它,然後,你還可以殺死它。但是一些人如果telnet到你的機器,inetd就會創造子程式來為使用者服務,這個子程式不會被LIDS保護,因為使用者在任何時候退出和殺死程式。

  4.2 隱藏程式

  另外一個保護程式的方法就是隱藏程式。當一個駭客危機你的系統。他會登陸,然後會看看有沒有一些已知的程式在監視它。然後他就殺死它。如果你隱藏了這個功能的程式,駭客就不會知道程式的所有情況並且你可以記錄他在你係統上做的任何事情。

  如何隱藏程式

  為了隱藏程式,你必須在配置核心的時候提供一個完全的路徑名。

  當核心啟動的時候,LIDS會訪問檔案結點到一個叫proc_to_hide[]的結構裡。

在include/Linux/sched.h裡

#ifdef CONFIG_LIDS_HIDE_PROC
#define PF_HIDDEN 0x04000000 /* Hidden process */
#endif

/* in fs/lids.c */

#ifdef CONFIG_LIDS_HIDE_PROC
struct allowed_ino proc_to_hide[LIDS_MAX_ALLOWED];
int last_hide=0;
#endif
....

/* in fs/lids.c , init_vfs_security(),
fill up the hidden process in proc_to_hide[]
*/
#ifdef CONFIG_LIDS_HIDE_PROC
lids_fill_table(proc_to_hide,&last_hide,LIDS_MAX_ALLOWED,CONFIG_LIDS_HIDDEN_PROC_PATH);
#endif



  PF_HIDDEN是否使用者可以用顯示程式的命令(如“ps –a”)來顯示和檢查程式,如果一個程式被LIDS隱藏,當他執行的時候,程式就會得到一個PF_HIDDEN的屬性。然後,當系統輸出系統程式資訊到使用者的時候,它就會可以檢查當前輸出程式是否有PF_HIDDEN標誌。如果發現了,它就不會輸出這個程式的資訊。

在in fs/exec.c

int do_execve(char * filename, char ** argv, char ** envp, struct pt_regs * regs)
{
...
if (retval >= 0) {
#ifdef CONFIG_LIDS_HIDE_PROC
if (lids_search_proc_to_hide(dentry->d_inode))
current->flags |= PF_HIDDEN;
...



  因為每一個Linux的程式都有一個在/proc檔案系統的入口,我們為了隱藏程式也需要修改proc的檔案入口。

在fs/proc/root.c

static struct dentry *proc_root_lookup(struct inode * dir, struct dentry * dentry)
{
...
inode = NULL;

#ifdef CONFIG_LIDS_HIDE_PROC
if ( pid && p && (! ((p->flags & PF_HIDDEN) && lids_load && lids_local_load)) ) {
#else
if (pid && p) {
#endif
unsigned long ino = (pid >> 16) + PROC_PID_INO;
inode = proc_get_inode(dir->i_sb, ino, &proc_pid);
if (!inode)
return ERR_PTR(-EINVAL);
inode->i_flags|=S_IMMUTABLE;
}
...
}



  然後如果程式被PF_HIDDEN標記,它就不會在proc檔案系統裡顯示。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10617542/viewspace-950233/,如需轉載,請註明出處,否則將追究法律責任。

相關文章