⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 373.html

📁 关于jsp的一些好文章 主要介绍一些关于JSP的应用技巧方面的东西
💻 HTML
📖 第 1 页 / 共 3 页
字号:

<STYLE type=text/css>
<!--
body,td { font-size:9pt;}
hr { color: #000000; height: 1px}
-->
</STYLE>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<HTML>
<HEAD><TITLE>精选文章 >> solaris 专栏 >> Solaris 可装载内核模块</title>
</head>
<body >

<p><IMG SRC="../image/jsp001_middle_logo.gif" WIDTH="180" HEIGHT="60" BORDER=0 ALT=""></p>

<table width=100% bgcolor="#cccccc" align=center cellpadding="2" cellspacing="0" border=1 bordercolorlight="#000000" bordercolordark="#FFFFFF">
<tr bgcolor="#EFF8FF"><td>
<a href=http://www.jsp001.com/list_thread.php?int_attribute=2>精选文章</a>
>> <a href=http://www.jsp001.com/list_thread.php?forumid=39&int_attribute=2>solaris 专栏</a>
>> Solaris 可装载内核模块 [<a href=http://www.jsp001.com/forum/showthread.php?goto=newpost&threadid=373>查看别人的评论</a>]<br>

<hr><p>由 fei 发布于: 2001-02-12 13:59</p><p> </p><p>1 介绍<br><br>    可装载内核模块是内核体系结构中的重要一环。它们在内核空间中为硬件设备和数据提供了一个接口。大多数Unix系统都使用可装载内核模块,以在外围设备与核心之间提供最大限度的交互功能。<br><br>    由于内核模块具有这些特点,而且它们是在底层影响操作系统并能以一种有效且难以被检测到的方式管理系统,攻击者对内核模块发生了浓厚兴趣。在过去的几年中,一些Unix系统下(例如在Linux和FreeBSD下)的包含后门的可装载模块已经被公开发布。本文则讲述了在Solaris 7(Sparc/Intel平台)下开发"后门"模块的技术。本文中所涉及的模块没有在Solaris 2.6(Sparc)下测试过,如果你对测试这些模块感兴趣,请与我联系。<br><br>    尽管本文所讲的大部分内容都已经在多个运行Solaris 7(Ultra Sparc/Sparc/x86)和Solaris 2.6(Ultra Sparc)系统的计算机上测试过,它们仍然可能会让你的系统当掉甚至毁坏你的系统。所以在使用slkm-1.0.tar.gz中的模块程序时一定要小心,请先备份重要数据。<br><br>    另外需要说明的是,这些模块不是用Sun的C编译器(cc)编译的,而是用GNU的C编译器(gcc)编译的(它可以从sunfreeware.com下载)。<br><br>    本文以及所附程序都只是为了教育目的,我强烈建议你不要在不属于你或者你无权操作的系统上使用这些模块!<br><br>2 装载和卸载内核模块<br><br>    Solaris的很多功能都使用了内核模块,例如: ip/tcp,scsi,ufs等等。由其他的开发商和作者提供的工具也用到了这种机制,比如,ipf,pppd,oss等等。你可以用命令/usr/sbin/modinfo来得到所有已经装载模块的列表。<br><br># modinfo <br>Id Loadaddr Size Info Rev Module Name <br>4 fe8c6000 313e 1 1 specfs (filesystem for specfs) <br>6 fe8ca414 2258 1 1 TS (time sharing sched class) <br>7 fe8cc228 4a2 - 1 TS_DPTBL (Time sharing dispatch table) <br>8 fe8cc27c 194 - 1 pci_autoconfig (PCI BIOS interface) <br># <br>    "Id"就是模块号,"Loadaddr"是该模块的文本段的起始地址,"Size"是模块文本段,数据段再加上BSS段的大小(以16进制方式表示),"Info"是该模块的特定信息,"Rev"是该可装载模组系统的版本号,"Module Name"则是该模块的文件名与描述。<br><br>    设备驱动程序和伪设备驱动程序模块包含有一个Info号码。而那些不与设备通信的模块就不包含这些信息,这种模块也称为"其它"("misc")模块。既然我们要开发的是一个攻击模块,我们稍后也将产生一个这样的"其它"模块。有两个命令可以用来装载和卸载内核模块: /usr/sbin/moload和/usr/sbin/modunload。modload的命令行参数是模块名,而modunload的参数则是"-i ID",这里的ID是一个已经装载的模块的id号(参见上面提到的modinfo命令)<br><br># modinfo -i 125 <br>Id Loadaddr Size Info Rev Module Name <br>125 fe95959c 125 - 1 flkm (First Loadable Kernel Module) <br># modunload -i 125 <br>3 Solaris下内核模块的基本结构 <br>    在Solaris下,为了将内核模块装入系统,需要很多已定义的变量。这是与Linux内核模块的一个主要不同之处,Linux下可以通过使用init_module()和cleanup_module()函数来很容易的创建一个模块。(可以参看pragmatic写的关于Linux和FreeBSD的内核模块的文章)<br><br>3.1 标准的头文件和结构定义 <br>    虽然我们并不想开发一个设备驱动程序的模块,我们仍然不得不包含进DDI,SunDDI和modctl的头文件,因为它们为我们提供modlinkage和mod_ops等结构的定义。在一个模块程序中的开始几行通常是这样的:<br><br>#include <br>#include <br>/* <br>* 这是内核模块的wrapper. <br>*/ <br>#include <br>extern struct mod_ops mod_miscops; <br>/* <br>* 模块的连接信息。 <br>*/ <br>static struct modlmisc modlmisc = { <br>&amp;mod_miscops, <br>"First Loadable Kernel Module", <br>}; <br>static struct modlinkage modlinkage = { <br>MODREV_1, <br>(void *)&amp;modlmisc, <br>NULL <br>}; <br>    从上面的程序可以看出,我们在模块程序中包含了一些外部结构,并在modlmisc结构中定义了这个内核模块的名字。modlinkage结构中引用了modlmisc结构,它告诉核心这不是一个设备驱动程序模块,用modinfo查看时将不会显示Info标志。如果你想查看这些结构的细节内容或者想自己开发驱动设备或者伪设备驱动程序模块,可以看一下这些man手册内容:modldrv(9S),modlinkage(9S) 和modlstrmod(9S). 如果你只是想了解什么是"后门",只要继续往下读就好了。<br><br>3.2 如何隐藏模块 <br>    如果我们在modlmisc结构中将模块名改为空字符串(""),那么modinfo命令将不会显示这个模块,尽管它已经被装入内核而且它的ID号也被保留。这个特点可以让我们用来隐藏模块,如果你知道模块的ID,你仍然可以将其卸载。找到这个ID也很简单,只要比较一下装载该模块前后的所有模块ID信息:<br><br># modinfo <br>Id Loadaddr Size Info Rev Module Name <br>[...] <br>122 fe9748e8 e08 13 1 ptem (pty hardware emulator) <br>123 fe983fd8 1c0 14 1 redirmod (redirection module) <br>124 fe9f60a4 cfc 15 1 bufmod (streams buffer mod) <br># modload flkm <br># modinfo <br>Id Loadaddr Size Info Rev Module Name <br>[...] <br>122 fe9748e8 e08 13 1 ptem (pty hardware emulator) <br>123 fe983fd8 1c0 14 1 redirmod (redirection module) <br>124 fe9f60a4 cfc 15 1 bufmod (streams buffer mod) <br>126 fe9f8e5c 8e3c 13 1 pcfs (filesystem for PC) <br>127 fea018d4 19e1 - 1 diaudio (Generic Audio) <br>128 fe94aed0 5e3 72 1 ksyms (kernel symbols driver) <br>    我们可以看到,在我们的空名字的模块装入后,Id 125就为它保留了。以后再装入的模块将从126开始编号。因此如果我们想卸载它,只要卸载ID号125的模块就可以了。由于当你使用modunload卸载一个不存在的模块时,modunload并不会返回一个错误,因此没有人能通过使用modinfo或者modunload命令来检测我们的模块。在本文的下一个版中将讲述一种彻底避免我们的模块被列出和卸载的方法。这只能通过修改Solaris模块ksyms(它列出和管理所有的内核标记)来完成。不过即使是这种使用空模块名的办法,也可以满足你的需要,如果你的系统管理员不是一个真正的系统程序员的话。:-)<br><br>3.3 三个基本的调用:_ini(),_fini()和_info() <br>    在Soarlis下,一个内核模块必须至少包括以下三个函数: _init(), _fini() 和 _info()。_init()初始化一个可装载模块,它在任何其他的函数之前被调用。在一个_init()函数中你需要调用另外一个函数mod_install(),它用modlinkage结构作为它的参数。_init()返回被mod_install()返回的值。这个返回值是为了装载模块时进行错误处理之用。<br><br>int _init(void) <br>{ <br>int i; <br>if ((i = mod_install(&amp;modlinkage)) != 0) <br>cmn_err(CE_NOTE,"Could not install module\n"); <br>else <br>cmn_err(CE_NOTE,"flkm: successfully installed"); <br>return i; <br>} <br>    _info()函数返回一个可装载模块的有关信息,在这个函数里需要调用mod_info()函数。如果我们在modinfo结构中使用空名字,mod_info()将不会返回信息给/usr/sbin/modinfo命令。<br><br>int _info(struct modinfo *modinfop) <br>{ <br>return (mod_info(&amp;modlinkage, modinfop)); <br>} <br>    _fini()用来为卸载模块做准备。当系统想要卸载一个模块时,_fini()就被调用。在_fini()函数中必须调用mod_remove()函数,为了了解是否在卸载模块时发生了错误,_fini()会返回一个值(由mod_remove()函数返回).<br><br>int _fini(void) <br>{ <br>int i; <br>if ((i = mod_remove(&amp;modlinkage)) != 0) <br>cmn_err(CE_NOTE,"Could not remove module\n"); <br>else <br>cmn_err(CE_NOTE,"flkm: successfully removed"); <br>return i; <br>} <br>    在下列Solaris man手册中可以找到有关这些调用的更详细的资料:_info(9E)和mod_install(9F).如果你在一个运行模块中调用cmn_err()函数时,使用了CE_NOTE级别,那么输出将被作为一个notice(注意)传递给syslogd。cmn_err()是一个用来从核心空间输出信息的函数。如果你正在调试你的模块,它也能用来设置运行等级。<br><br>3.4 编译和链接模块 <br>    编译一个模块是非常简单的。你所做的只是做一些定义,说明被包含的程序将作为一个内核而不是一个普通的可执行文件。你应当总是用"-r"参数来连接你的模块的目标文件,否则这个模块不会被装载,因为核心模块连接器将不能连接这个模块。<br><br>gcc -D_KERNEL -DSVR4 -DSOL2 -O2 -c flkm.c <br>ld -o flkm -r flkm.o <br>    Solaris内核并不象Linux内核那样包含很多标准的C函数,因此如果你想用这些标准的libC函数,你需要从/lib/libc.a中提取它们并且用ar命令连接到你的模块中。<br><br>ar -x /lib/libc.a memmove.o memcpy.o strstr.o <br>ld -o flkm -r flkm.o memmove.o memcpy.o strstr.o <br>    在我的例子中,我包含了一个"DEBUG"开关,这个开关将激活很多调试输出信息。当然还有一些其他的内核函数可以帮助你调试,比如 ASSERT().<br><br>--&gt; 模块: flkm.c <br>    slkm-1.0.tar.gz中的flkm.c(First Loadable Kernel Module)模块是一个简单的例子,用来解释3.1-3.4节中所讲的内容。它构造了一个空的但可以工作的模块,该模块可以很容易的装入内核。<br><br>------------------------------------------------------------------------------- <br>4. 系统调用的重定向和内存管理 <br>    如果你要写一个后门模块而不是要开发自己的函数,那么重定向系统调用就显得很重要了.你可以将普通的系统调用重定向到你写的伪调用函数,然后就可以做你想做的事了.如果你想了解伪系统调用的基本概念,可以到www.infowar.co.uk看一下pragmatic的文章.<br><br>4.1 Solaris下的系统调用 <br>    Solaris下的系统调用都保存在一个sysent[]数组中.这个数组的每一项都指向一个结构,其中包含一个系统调用的相关信息.所有系统调用的值都可以在/usr/include/sys/syscall.h中找到.如果你认真察看这些系统调用的话,你会发现它们和Linux系统调用的头文件有一些显著的不同.因此如果你试图将一个Linux的内核模块移植到Soarlis的话,请一定小心.<br><br>    Solaris下的系统调用全都保存在一个sysent[]数组里面.这个数组中的每一项都指向一个结构,该结构含有有关某个系统调用的信息.所有系统调用的值都可以从/usr/include/sys/syscall.h中找到.如果你更仔细的看一下这些系统调用表的话,你会发现它们与Linux的系统调用确实有一些显著的不同.因此,如果你要将一个Linux的内核模块移植到Soarlis的话,一定要小心.<br><br>    在Solaris下,一些文件系统相关的函数并不使用open(),creat()这样的系统调用,而是使用open64(),creat64()等系统调用.在你试图在Soarlis下重定向一个系统调用时,最好先用/usr/bin/truss来跟踪一下程序,看看它到底使用了哪些系统调用.例如,ps使用open()调用来检查proc树中的文件,而cat则使用open64()来从文件系统中打开一个文件,即使这个文件也在proc树中.让我们来看一些例子:<br><br>/* 我们首先要宣称一下原来的系统调用 */ <br>int (*oldexecve) (const char *, const char *[], const char *[]); <br>int (*oldopen64) (const char *path, int oflag, mode_t mode); <br>int (*oldread) (int fildes, void *buf, size_t nbyte); <br>int (*oldcreat64) (const char *path, mode_t mode); <br>[...] <br>/* 定义我们自己的creat64() */ <br>int newcreat64(const char *path, mode_t mode) <br>{ <br>[...] <br>int _init(void) <br>{ <br>int i; <br>/* 根据modlinkage结构进行初始化 */ <br>if ((i = mod_install(&amp;modlinkage)) != 0) <br>cmn_err(CE_NOTE,"Could not install module\n"); <br>#ifdef DEBUG <br>else <br>cmn_err(CE_NOTE,"anm: successfully installed"); <br>#endif <br>/* 保存原来的系统调用的地址 */ <br>oldexecve = (void *) sysent[SYS_execve].sy_callc; <br>oldopen64 = (void *) sysent[SYS_open64].sy_callc; <br>oldcreat64 = (void *) sysent[SYS_creat64].sy_callc; <br>oldread = (void *) sysent[SYS_read].sy_callc; <br>/* 将新的系统调用的地址填到sysent数组的相应位置 */ <br>sysent[SYS_execve].sy_callc = (void *) newexecve; <br>sysent[SYS_open64].sy_callc = (void *) newopen64; <br>sysent[SYS_creat64].sy_callc = (void *) newcreat64; <br>sysent[SYS_read].sy_callc = (void *) newread; <br>return i; <br>} <br>    上面就是在3.3节中提到的_init()函数,在初始化完模块后,我们将储存在sysent[].sy_callc中的原系统调用的指针拷贝到另外一些指针中去(这些指针在模块的开始处被定义).这和在Linux模块中所做的一样.<br><br>    在保存了旧指针后,我们将新的系统调用(例如int newcreat64(const char *path,mode_tmode)拷贝到sysent[]数组中的相应结构成员中. <br><br>4.2 产生错误信息<br><br>    一些常用的内核模块产生错误信息的方法在Solaris下并不工作.在/usr/include/sys/errno.h中列出了一些所谓的错误代码,但这些代码不应以下列方式被返回:return -ENOENT;<br><br>    尽管上述代码也能工作,但既然一个返回的负值并不能告诉Solaris发生了什么错误,因此我们应该用set_errno()函数来解决这个问题.<br><br>set_errno(ENOENT); <br><br>return -1; <br>即使你在伪造一个错误信息,你也应该告诉你的操作系统是什么出问题了.:-)<br><br>4.3 在内核中分配内存空间 <br>    在内核中,你不能用alloc()和malloc()函数来分配内存,因为内核的内存空间与用户内存空间是隔离的.Solaris提供了两个函数来分配和释放内核内存.<br><br>name = (char *) kmem_alloc(size, KM_SLEEP); <br>kmem_free(name,size); <br>    kmem_alloc()分配size字节大小的内核内存,并返回一个指针,指向已经分配的内存区域.被分配的内存大小至少是双字的整数倍,以便能容纳任意的C语言的数据结构.第二个参数决定是否调用者可以进入休眠状态.KM_SLEEP型分配可能暂时休眠,但却保证可以成功分配内存;KM_NOSLEEP型分配保证不进入休眠态,但如果当前没有内存可供分配,它就会失败(返回空值).只有在用来中断上下文时,我们才应该使用KM_NOSLEEP分配内核内存.其他情况下都不应该使用它.用kmem_alloc()分配的内存中都是一些随机的数据.已分配的内存要用kmem_free(name,size)来释放,size就是已分配内存的大小.需要特别注意的是: 如果你释放的内存超过了你分配的实际大小,可能会有大麻烦了,因为某些本不该释放的内存被释放了.<br><br>    在Solaris 2.7 (x86)下,可以用memcpy()来完成在用户和内核空间之间传递数据的任务,而不需要其他特别的命令.但在Solaris (Sparc)下这种办法就行不通了.为了解决这个问题,需要用函数copyin()和copyout()来在用户和内核内存中传递数据.<br><br>    如果你要从用户空间将以空字节终止的字符串拷贝到内核空间的话,你可以用copyinstr(),它的函数原型是 copyinstr(char *src, char *dst, size_t length, size_t size)length定义有多少字节要读,而size是实际读取的字节数.<br><br>    这些函数的完整定义可以在下列Solaris man手册中查到: <br>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -