上期我們講了《 Linux系統可解除安裝核心子產品完全指南(上)》的内容,本期我們講中間部份的内容。
第二部分 漸入佳境
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[];
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;
}
編譯并啟動這個子產品(見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"], []) = 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);
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[];
int lock_mod=0;
int __NR_myexecve;
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";
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;
}
代碼本身很清楚.下面将會告訴你如何才能讓你的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'字元串,比如)