首页 -> 安全研究

安全研究

绿盟月刊
绿盟安全月刊->第22期->技术专题
期刊号: 类型: 关键词:
Solaris 2.x 可加载系统调用模块

作者:FooPirata < mailto: Unknown >
整理:小四 < mailto: scz@nsfocus.com >
出处:Kernel Mumblings
日期:2001-06-15


    本文目的在于快速介绍Solaris 2内核结构,介绍一些相关的Kernel Hacking工
具。调试SLKM,不可避免地会碰上系统崩溃,这里还将介绍这种情形下如何处理。

    与心爱的Linux(鬼话,谁心爱Linux了)不同,即使Solaris x86版,也没有提供
源码。需要花费大量金钱从Sun公司购买源码,可惜我们没有这笔钱。两个顶级黑客
介绍了Kernel Hacking之后,我对此非常感兴趣。可能你并不知道他们,TeleMig 和
Snoopy(我对老外名字一向记不准确,并且他们化名太多)。

    为了方便朋友们实践调试,我以SPARC/Solaris 2.6为蓝本翻译、测试,7下的系
统调用接口有变化,<<Solaris Internals>>在这里也没有专门强调,不知道为什么。
感兴趣的自行观察/usr/include/sys/systm.h,或做其他方式的Kernel Hacking。

介绍
====

    内核是Unix系统的一部分,负责内存管理、I/O、定时器(调度)、线程。

    为什么要Kernel Hacking?

    可能最冠冕堂皇的理由是好奇。必须承认,肯定有人在他们肮脏的脑海中带着对
黑暗的向往,任何以破坏系统为目的而关注内核的朋友看过本文后注定失望。

    CPU将在两种模式下工作:用户模式和内核模式。二者之间的区别在于用户模式
下进程使用局部数据、局部映射文件和局部堆栈。内核模式下所有进程共用一个内核
映象。

    每次我们从用户模式切换进入内核模式或者反过来,都将发生上下文(context)
切换。这个过程花费特别大,降低了系统性能,所以绝大多数现代内核设计人员采用
汇编语言编写这部分代码。

    每次发生系统调用,用户进程将从用户模式切换进入内核模式。系统调用和其他
函数一样以函数调用方式提供,通过syscall(3B)切入内核,参看syscall(3B)手册页

--------------------------------------------------------------------------
SunOS/BSD 兼容库函数                                  syscall(3B)

名字

     syscall - 间接系统调用

摘要

     /usr/ucb/cc [ flag ... ] file ...

     #include <sys/syscall.h>

     int syscall ( number, arg, ... );

描述

     syscall()完成指定编号的系统调用功能,形参arg变长,根据不同的
     系统调用编号而不同。参看头文件/usr/include/sys/syscall.h

返回值

     -1 发生错误,外部全局变量errno被设置

文件

     <sys/syscall.h>

参看

     intro(2)、pipe(2)

注意

     仅当在BSD平台上编写应用程序时使用这种接口。多线程应用程序中
     不要使用这种接口。

警告

     pipe(2)的返回值无法用单一硬件寄存器存放,syscall()接口不能用
     于此类系统调用

     许多系统以库函数封装方式提供,其man手册中介绍的行为可能并不
     适合syscall()调用方式,因为缺少了库函数封装。不推荐使用
     syscall()接口。

译注

     如果编写socket shellcode for sparc,可能需要使用syscall()接
     口,参看<<SPARC/Solaris下的Unix后门初探(灌水交流版V)>>

     <syscall>   :  clr   %g1
     <syscall+4> :  ta    8
--------------------------------------------------------------------------

系统调用名与系统调用号之间的映射关系由/etc/name_to_sysnum文件定义


系统调用机制
============

        ++++++++++++++
        +  用户进程  +
        ++++++++++++++
              |
              |
          系统调用( X, .... )
              |
              |
              V                                 用户模式
--------------------------------------------------------
                                                内核模式
        sysent[X].sy_callc()
--------------------------------------------------------

所有系统调用相关信息集中在/usr/include/sys/systm.h文件中

--------------------------------------------------------------------------
/*
* Structure of the system-entry table.
*
*      Changes to struct sysent should maintain binary compatibility with
*      loadable system calls, although the interface is currently private.
*
*      This means it should only be expanded on the end, and flag values
*      should not be reused.
*
*      It is desirable to keep the size of this struct a power of 2 for
quick
*      indexing.
*/
struct sysent
{
        char            sy_narg;        /* total number of arguments */
        char            sy_flags;       /* various flags as defined below */
        int             (*sy_call)();   /* argp, rvalp-style handler */
        krwlock_t       *sy_lock;       /* lock for loadable system calls */
        longlong_t      (*sy_callc)();  /* C-style call hander or wrapper */
};

extern struct sysent    sysent[];
extern struct sysent    nosys_ent;      /* entry for invalid system call */

#define NSYSCALL        250             /* number of system calls */

/*
* sy_flags values
*      Values 1, 2, and 4 were used previously for SETJUMP, ASYNC, and
IOSYS.
*/
#define SE_LOADABLE     0x08            /* syscall is loadable */
#define SE_LOADED       0x10            /* syscall is completely loaded */
#define SE_NOUNLOAD     0x20            /* syscall never needs unload */
#define SE_ARGC         0x40            /* syscall takes C-style args */
--------------------------------------------------------------------------

extern struct sysent sysent[]定义了内核结构数组,一个元素对应一个系统调用。
数组下标即系统调用号,由/etc/name_to_sysnum文件确定。这样做的好处在于不需
要内核源代码,也不需要重新编译内核,就可以增加系统调用。许多系统调用以动态
可加载模块方式实现,相应系统调用第一次被激活时动态加载进入系统。可加载系统
调用存放在/kernel/sys和/usr/kernel/sys目录下。

下表SPARC/Solaris 2.6的可加载系统调用:

--------------------------------------------------------------------------
/kernel/sys/

c2audit
doorfs
inst_sync
kaio
msgsys
nfs
pipe
pset
rpcmod
semsys
shmsys

/usr/kernel/sys/

sysacct
--------------------------------------------------------------------------

sysent结构的sy_narg成员指明了系统调用形参个数。sy_flags成员对应一组标志,
系统调用是否可动态加载,一次性加载完整还是之后加载其他部分,是否可卸载,是
否支持C风格的形参传递。

Solaris众多美好特性之一是支持可加载内核模块。对于Linux世界来说,这很习以为
常了,但是高端系统(比如SPARC/Solaris)上的用户可能把这个特性当做新生事物。

我们可以编写自己的系统调用,并且方便地加入到内核中去。现在来看一个庸俗的例
子,一个显示"Hello World"的可加载系统调用。

首先,看一下/etc/name_to_sysnum:

--------------------------------------------------------------------------
... ...
mount                   21
umount                  22
setuid                  23
getuid                  24
stime                   25
alarm                   27
fstat                   28
pause                   29
... ...
--------------------------------------------------------------------------

缺省情况下,179、180、181、182、183这5个系统调用未被实现,初始化成
nosys_ent对应的值。我喜欢180,编辑这个文件,增加如下行:

mySyscall  <TAB> <TAB>  180

保存这个文件并重启系统。必须重启系统,以便内核读取该文件为新的系统调用分配
内存空间。

好了,下面是源码mySyscall.c:

--------------------------------------------------------------------------
/*
* gcc -D_KERNEL -c mySyscall.c
* ld -r -o mySyscall mySyscall.o
*
* these are all the includes normally needed to a general sys call
* we don't use many of them, but this is the normal load you'll see
* on a more evolved syscall
*/
#include <sys/types.h>
#include <sys/vnode.h>
#include <sys/file.h>
#include <sys/cred.h>
#include <sys/stropts.h>
#include <sys/systm.h>
#include <sys/pathname.h>
#include <sys/exec.h>
#include <sys/thread.h>
#include <errno.h>
#include <sys/modctl.h>
#include <sys/syscall.h>

/* our entry point */
static int mySyscall ( void );

static struct sysent mySysent =
{
    0,                           /* number of arguments */
    0,                           /* load flags          */
    ( int ( * ) () )&mySyscall,  /* the function        */
    ( krwlock_t * )NULL,         /* kernel lock         */
};

/*
* this is the dynamic load & link stuff. It is sort of fill-in-the-blanks
* and seems to be standard for all system calls
*/
extern struct mod_ops mod_syscallops;

static struct modlsys modlsys =
{
    &mod_syscallops,          /* define loader routines */
    "My little system call",  /* decsriptive string */
    &mySysent                 /* pointer to our sysent structure */
};

static struct modlinkage modlinkage =
{
    MODREV_1,          /* loader revision number */
    (void *)&modlsys,  /* start of list of things to load here */
    0                  /* end of list */
};
/* end of dynamic load stuff */

/*
* this is a counter on the instances of the loaded call. this is needed
* in case we want to unload but it is still in use, or something. notice
* that it is static on purpose
*/
static int refcnt = 0;

/*
* this little routine is the load entry point. when we instruct the
* system to load our loadable system call, the kernel will call this
* function - so, if you need any initialization done, here is the place
* for it
*/
int _init ( void )
{
    printf( "MY SYSTEM CALL INITIALIZED\n" );
    return( mod_install( &modlinkage ) );
}

/*
* here we do the opposite of _init, and deallocate any resources we may
have
* been using before unloading the system call code
*/
int _fini ( void )
{
    /*
     * in case we ask to have the syscall unloaded while it is still
     * in use, we refuse the download with a BUSY return code
     */
    if ( refcnt != 0 )
    {
        return( EBUSY );
    }
    printf( "MY SYSTEM CALL REMOVED\n" );
    return( mod_remove( &modlinkage ) );
}

/*
* tools like modinfo will return information about loadable modules
* installed. this answers to those requests.
*/
int _info ( struct modinfo * modinfop )
{
    printf( "REQUEST INFO ABOUT MY SYSTEM CALL\n" );
    return( mod_info( &modlinkage, modinfop ) );
}

/*
* here we do the real magic. one work of advice concerning which functions
* you can use here - anything that compiles without an explicit library
* addition will do just fine
*/
static int mySyscall ( void )
{
    printf( "Hello World\n" );
    return( 0 );
}
--------------------------------------------------------------------------

$ gcc -D_KERNEL -c mySyscall.c
$ ld -r -o mySyscall mySyscall.o

你是root吗?什么?你不是root,难道你以为这篇文章教你怎么获取root吗?嘿嘿
现在你有一个名为mySyscall的文件,下面是一个小测试程序:

--------------------------------------------------------------------------
/* gcc -o mySyscallTest mySyscallTest.c */
#include <sys/syscall.h>

int main ( int argc, char * argv[] )
{
    int i = syscall( 180 );
}
--------------------------------------------------------------------------

# modload ./mySyscall
# ./mySyscallTest

观察/var/adm/messages尾部新数据,SYSCALL SLKM的输出信息都送往该文件了,主
控台(console或者xconsole)上也能看到调试信息的输出。

译注:直接观察/var/adm/messages或者dmesg查看,都不是很理想。考虑使用
      microcat在华中站Security版提供过的getlog.c,效果非常理想。

      尽管目前printf()和uprintf()存在,但是如果编写Solaris DDI-compliant的
      驱动程序,就不应该使用它们。

      应该使用cmn_err(9F)。

如果你得到这样的错误信息"can't load module: Out of memory or no room in
system tables",可能是你忘记修改/etc/name_to_sysnum,或者修改后忘记重启系
统了。

用modinfo命令确认mySyscall的ID号,用modunload卸载可加载系统调用模块

# modunload -i <ID of mySyscall>

今天到此为止,下次我们将讨论adb、kadb以及如何调试SLKM,可能还要做针对已有
系统调用的hook操作。

<待续>
版权所有,未经许可,不得转载