首页 -> 安全研究
安全研究
绿盟月刊
绿盟安全月刊->第22期->技术专题
作者: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操作。
<待续>
版权所有,未经许可,不得转载