首页 | 资讯动态 | linux基础 | 系统管理 | 网络管理 | 编程开发 | linux数据库 | 服务器技术 | linux相关 | linux认证 | 嵌入式 | 下载中心 | 专题 | linux招聘 | 镜像站
OKLinux中文技术站
·设为首页
·加入收藏
·联系我们
系统管理: 中文环境 系统管理 桌面应用 内核技术 | Linux基础: 基础入门 安装配置 常用命令 经验技巧 软件应用 | Linux数据库: Mysql Postgre Oracle DB2 Sybase other
网络管理: 网络安全 网络应用 Linux服务器 环境配置 黑客安全 | 编程开发: PHP CC++ Python Perl Shell 嵌入式开发 java jsp | PHP技术: PHP基础 PHP技巧 PHP应用 PHP文摘
Linux资讯 Linux招聘 Linux专题 Apache | Linux相关: 硬件相关 Linux解决方案 Linux认证 企业应用 其它Unix | 相关下载: 资料下载 参考手册 开发工具 服务器类 软路由 其它
 技术搜索:
会员中心 注册会员 高级搜索  
  → 当前位置:首页>系统管理>系统管理>正文

Linux系统环境下的高级隐藏技术介绍

http://www.oklinux.cn  2007-04-28  来源:    会员收藏  游客收藏  【 】 

隐藏技术在计算机系统安全中应用十分广泛,尤其是在网络攻击中,当攻击者成功侵入一个系统后,有效隐藏攻击者的文件、进程及其加载的模块变得尤为重要。本文将讨论Linux系统中文件、进程及模块的高级隐藏技术,这些技术有的已经被广泛应用到各种后门或安全检测程序之中,而有一些则刚刚起步,仍然处在讨论阶段,应用很少。

   1.隐藏技术

   1.1.Linux下的中断控制及系统调用

   Intel x86系列微机支持256种中断,为了使处理器比较容易地识别每种中断源,把它们从0~256编号,即赋予一个中断类型码n,Intel把它称作中断向量。

   Linux用一个中断向量(128或者0x80)来实现系统调用,所有的系统调用都通过唯一的入口system_call来进入内核,当用户动态进程执行一条int 0x80汇编指令时,CPU就切换到内核态,并开始执行system_call函数,system_call函数再通过系统调用表sys_call_table来取得相应系统调用的地址进行执行。系统调用表sys_call_table中存放所有系统调用函数的地址,每个地址可以用系统调用号来进行索引,例如sys_call_table[NR_fork]索引到的就是系统调用sys_fork()的地址。

   Linux用中断描述符(8字节)来表示每个中断的相关信息,其格式如下:

   偏移量31….16  一些标志、类型码及保留位
   段选择符      偏移量15….0

   所有的中断描述符存放在一片连续的地址空间中,这个连续的地址空间称作中断描述符表(IDT),其起始地址存放在中断描述符表寄存器(IDTR)中,其格式如下:

   32位基址值  界限

   其中各个结构的相应联系可以如下表示:

   通过上面的说明可以得出通过IDTR寄存器来找到system_call函数地址的方法:根据IDTR寄存器找到中断描述符表,中断描述符表的第0x80项即是system_call函数的地址,这个地址将在后面的讨论中应用到。
1.2.Linux 的LKM(可装载内核模块)技术

   为了使内核保持较小的体积并能够方便的进行功能扩展,Linux系统提供了模块机制。模块是内核的一部分,但并没有被编译进内核,它们被编译成目标文件,在运行过程中根据需要动态的插入内核或者从内核中移除。由于模块在插入后是作为Linux内核的一部分来运行的,所以模块编程实际上就是内核编程,因此可以在模块中使用一些由内核导出的资源,例如Linux2.4.18版以前的内核导出系统调用表(sys_call_table)的地址,这样就可以根据该地址直接修改系统调用的入口,从而改变系统调用。在模块编程中必须存在初始化函数及清除函数,一般情况下,这两个函数默认为init_module()以及clearup_module(),从2.3.13内核版本开始,用户也可以给这两个函数重新命名,初始化函数在模块被插入系统时调用,在其中可以进行一些函数及符号的注册工作,清除函数则在模块移除系统时进行调用,一些恢复工作通常在该函数中完成。

   1.3.Linux下的内存映像

   /dev/kmem是一个字符设备,是计算机主存的映像,通过它可以测试甚至修改系统,当内核不导出sys_call_table地址或者不允许插入模块时可以通过该映像修改系统调用,从而实现隐藏文件、进程或者模块的目的。

   1.4.proc 文件系统

   proc文件系统是一个虚拟的文件系统,它通过文件系统的接口实现,用于输出系统运行状态。它以文件系统的形式,为操作系统本身和应用进程之间的通信提供了一个界面,使应用程序能够安全、方便地获得系统当前的运行状况何内核的内部数据信息,并可以修改某些系统的配置信息。由于proc以文件系统的接口实现,因此可以象访问普通文件一样访问它,但它只存在于内存之中。

   2.技术分析

   2.1 隐藏文件

   Linux系统中用来查询文件信息的系统调用是sys_getdents,这一点可以通过strace来观察到,例如strace ls 将列出命令ls用到的系统调用,从中可以发现ls是通过sys_getedents来执行操作的。当查询文件或者目录的相关信息时,Linux系统用sys_getedents来执行相应的查询操作,并把得到的信息传递给用户空间运行的程序,所以如果修改该系统调用,去掉结果中与某些特定文件的相关信息,那么所有利用该系统调用的程序将看不见该文件,从而达到了隐藏的目的。首先介绍一下原来的系统调用,其原型为:
int sys_getdents(unsigned int fd, struct dirent *dirp,unsigned int count)
其中fd为指向目录文件的文件描述符,该函数根据fd所指向的目录文件读取相应dirent结构,并放入dirp中,其中count为dirp中返回的数据量,正确时该函数返回值为填充到dirp的字节数。下图是修改后的系统调用hacked_getdents执行流程。

screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.alt='Click here to open new window';}" border=0>

图 系统调用hacked_getdents执行流程

   图中的hacked_getdents函数实际上就是先调用原来的系统调用,然后从得到的dirent结构中去除与特定文件名相关的文件信息,从而应用程序从该系统调用返回后将看不到该文件的存在。

   应该注意的是,一些较新的版本中是通过sys_getdents64来查询文件信息的,但其实现原理与sys_getdents基本相同,所以在这些版本中仍然可以用与上面类似的方法来修改该系统调用,隐藏文件。
2.2 隐藏模块

   上面分析了如何修改系统调用以隐藏特定名字的文件,在实际的处理中,经常会用模块来达到修改系统调用的目的,但是当插入一个模块时,若不采取任何隐藏措施,很容易被对方发现,一旦对方发现并卸载了所插入的模块,那么所有利用该模块来隐藏的文件就暴露了,所以应继续分析如何来隐藏特定名字的模块。Linux中用来查询模块信息的系统调用是sys_query_module,所以可以通过修改该系统调用达到隐藏特定模块的目的。首先解释一下原来的系统调用,原来系统调用的原型为:
int sys_query_module(const char *name, int which, void *buf, size_t bufsize , size_t *ret)
如果参数name不空,则访问特定的模块,否则访问的是内核模块,参数which说明查询的类型,当which=QM_MODULES时,返回所有当前已插入的模块名称,存入buff, 并且在ret中存放模块的个数,buffsize是buf缓冲区的大小。在模块隐藏的过程中只需要对which=QM_MODULES的情况进行处理就可以达到目的。修改后的系统调用工作过程如下:

   1)调用原来的系统调用,出错则返回错误代码;
   2)如果which不等于QM_MODULES,则不需要处理,直接返回。
   3)从buf的开始位置进行处理,如果存在特定的名字,则将后面的模块名称向前覆盖该名字。
   4)重复3),直到处理处理完所有的名字,正确返回。

   2.3 隐藏进程

   在Linux中不存在直接查询进程信息的系统调用,类似于ps这样查询进程信息的命令是通过查询proc文件系统来实现的,在背景知识中已经介绍过proc文件系统,由于它应用文件系统的接口实现,因此同样可以用隐藏文件的方法来隐藏proc文件系统中的文件,只需要在上面的hacked_getdents中加入对于proc文件系统的判断即可。由于proc是特殊的文件系统,只存在于内存之中,不存在于任何实际设备之上,所以Linux内核分配给它一个特定的主设备号0以及一个特定的次设备号1,除此之外,由于在外存上没有与之对应的i节点,所以系统也分配给它一个特殊的节点号PROC_ROOT_INO(值为1),而设备上的1号索引节点是保留不用的。通过上面的分析,可以得出判断一个文件是否属于proc文件系统的方法:

   1)得到该文件对应的inode结构dinode;
   2)if (dinode->i_ino == PROC_ROOT_INO && !MAJOR(dinode->i_dev) && MINOR(dinode->i _dev) == 1) {该文件属于proc文件系统}

   通过上面的分析,给出隐藏特定进程的伪代码表示:

   hacket_getdents(unsigned int fd, struct dirent *dirp, unsigned int count)
   {

   调用原来的系统调用;

   得到fd所对应的节点;

   if(该文件属于proc文件系统&&该文件名需要隐藏)
     {从dirp中去掉该文件相关信息}
}
  2.4 修改系统调用的方法

   现在已经解决了如何修改系统调用来达到隐藏的目的,那么如何用修改后的系统调用来替换原来的呢?这个问题在实际应用中往往是最关键的,下面将讨论在不同的情况下如何做到这一点。

   (1)当系统导出sys_call_table,并且支持动态的插入模块的情况下:

   在Linux内核2.4.18版以前,这种内核配置是非常普遍的。这种情况下修改系统调用非常容易,只需要修改相应的sys_call_table表项,使其指向新的系统调用即可。下面是相应的代码:

   int orig_getdents(unsigned int fd, struct dirent *dirp, unsigned int count)
   int init_module(void) 
   /*初始化模块*/
   {
   orig_getdents=sys_call_table[SYS_getdents];    //保存原来的系统调用
   orig_query_module=sys_call_table[SYS_query_module]
   sys_call_table[SYS_getdents]=hacked_getdents;  //设置新的系统调用
   sys_call_table[SYS_query_module]=hacked_query_module;
   return 0; //返回0表示成功
   }
   void cleanup_module(void)
   /*卸载模块*/
   {
   sys_call_table[SYS_getdents]=orig_getdents;    //恢复原来的系统调用
   sys_call_table[SYS_query_module]=orig_query_module;
   }

   (2)在系统并不导出sys_call_table的情况下:

   linux内核在2.4.18以后为了安全起见不再导出sys_call_table符号,从而无法直接获得系统调用表的地址,那么就必须找到其他的办法来得到这个地址。在背景知识中提到了/dev/kmem是系统主存的映像,可以通过查询该文件来找到sys_call_table的地址,并对其进行修改,来使用新的系统调用。那么如何在系统映像中找到sys_call_table的地址呢?让我们先看看system_call的源代码是如何来实现系统调用的(代码见/arch/i386/kernel/entry.S):

  ENTRY(system_call)
   pushl %eax      # save orig_eax
   SAVE_ALL
   GET_CURRENT(%ebx)
   cmpl $(NR_syscalls),%eax
   jae badsys
   testb $0x02,tsk_ptrace(%ebx)  # PT_TRACESYS
   jne tracesys
   call *SYMBOL_NAME(sys_call_table)(,%eax,4)
   movl %eax,EAX(%esp)    # save the return value
ENTRY(ret_from_sys_call)
这段源代码首先保存相应的寄存器的值,然后判断系统调用号(在eax寄存器中)是否合法,继而对设置调试的情况进行处理,在所有这些进行完后,利用call *SYMBOL_NAME(sys_call_table)(,%eax,4) 来转入相应的系统调用进行处理,其中的SYMBOL_NAME(sys_call_table)得出的就是sys_call_table的地址。从上面的分析可以看出,当找到system_call函数之后,利用字符匹配来寻找相应call语句就可以确定sys_call_table的位置,因为call something(,%eax,4)的机器指令码是0xff 0x14 0x85。所以匹配这个指令码就行了。至于如何确定system_call的地址在背景知识中已经介绍了,下面给出相应的伪代码:

   struct{ //各字段含义可以参考背景知识中关于IDTR寄存器的介绍
   unsigned short limit;
   unsigned int base;
   }__attribute__((packed))idtr;
   struct{ //各字段含义可以参考背景知识中关于中断描述符的介绍
   unsigned short off1;
   unsigned short sel;
   unsigned char none,flags;
   unsigned short off2;
   }__attribute__((packed))idt;
   int kmem;
   / *下面函数用于从kemem对应的文件中偏移量为off处读取sz个字节至内存m处*/
   void readkmem(void *m,unsigned off,int sz) {………}
   /*下面函数用于从src读取count个字节至dest处*/
   void weitekmem(void *src,void *dest,unsigned int count) {………..}
   unsigned sct;  //用来存放sys_call_table地址
   char buff[100]; //用于存放system_call函数的前100个字节。
   char *p;
   if((kmem=open(“/dev/kmem”,O_RDONLY))<0)
   return 1;
   asm(“sidt %0” “:=m” (idtr));          //读取idtr寄存器的值至idtr结构中
   readkmem(&idt,idtr.base+8*0x80,sizeof(idt))    //将0x80描述符读至idt结构中
   sys_ call_off=(idt.off2<<16)|idt.off1;       //得到system_call函数的地址。
   readkmem(buff,sys_call_off,100)   //读取system_call函数的前100字节至buff
   p=(char *)memmem(buff,100,”xffx14x85”,3);  //得到call语句对应机器码的地址
   sct=(unsigned *)(p+3)            //得到sys_call_table的地址。

   至此已经得到了sys_call_table在内存中的位置,这样在根据系统调用号就能够找到相应的系统调用对应的地址,修改该地址就可以使用新的系统调函数,具体的做法如下:

   readkmem(&orig_getdents,sct+ SYS_getdents*4,4)//保存原来的系统调用
   readkmem(&orig_query_module,sct+SYS_query_module*4,4);
   writekmem(hacked_getdents,sct+SYS_getdents*4,4);//设置新的系统调用
   writekmem(hacket_query_module,sct+SYS_query_module*4,4);

   2.5 其他的相关技术

   上面已经完全解决了隐藏的相关技术问题,在实际应用中,可以把启动模块或者进程的代码做成脚本加入到相应的启动目录中,假设你的Linux运行级别为3,则可以加到目录rc3.d中(该目录常存在于/etc/rc.d或者/etc目录下),然后把该脚本的名字改为可以隐藏的名字。另一种方法就是在一些启动脚本中加入启动你的模块或者进程的代码,但这样比较容易被发现,一个解决思路就是进程或模块启动以后马上恢复正常的脚本,由于系统关机时会向所有进程发送SIGHUP信号,可以在进程或模块中处理该信号,使该信号发生时修改启动脚本,重新加入启动模块的代码,这样当系统下次启动时又可以加载这个的模块了,而且管理员察看启动脚本时也不会发现异常。

   3.结束语

   本文对Linux环境下的一些高级隐藏技术进行了分析研究,其中所涉及的技术不仅可以用在系统安全方面,在其他方面也有重要的借鉴意义。由于Linux的开放特性,使得攻击者一旦获得了root权限就能够对系统进行较多的修改,所以避免第一次被入侵是至关重要的。


上一篇:菜鸟乐园 Linux系统各项开机服务介绍   下一篇:Linux操作系统下SAMBA服务的搭建过程


收藏于收藏夹】 【评论】 【推荐】 【打印】 【关闭
相关文档
·Linux操作系统下SAMBA服务的搭建过程
·深入浅出 Linux字符设备驱动程序解析
·Linux操作系统下NFS服务的搭建过程
·Linux下如何查看某一进程的CPU占用率
·OpenSSL中对称加密算法的统一接口详解
·如何在非root状态下用root身份执行命令
·Samba服务典型的三种共享配置
·在RHEL4上的日志服务器配置案例解析
·Fedora Core6中IPtables日志管理技巧
·配置文件/etc/syslog.conf的实例解析
·让Linux操作系统可以Mount NTFS分区
·Linux AS4系统下LVM的使用详细解析
·如何在Subversion系统中运行hook脚本
·用Subversion软件进行个人文档管理
·linux的运行模式:Runlevel详细解析
·Linux系统启动引导程序配置文件解析
发表评论
密码: 匿名评论
评论内容:

(不超过250字,需审核后才会公布,请自觉遵守互联网相关政策法规)
 
  最新文档
·系统管理:如何禁止RHEL AS4的图形启动
·Windows和Linux Fedora8双系统安装过程
·Linux通过ndiswrapper安装无线网卡驱动
·Linux系统利用Crontab命令实现定时重启
·Ubuntu Linux系统设定服务自行开机启动
·在Linux系统下查看端口的使用情况及启
·Linux系统下安装Perl及Perl模块方法
·Qtopia应用程序与Linux内核数据通信介
·Ubuntu下NVIDIA驱动安装及3D桌面的设置
·修改VMware下Ubuntu界面分辨率过高问题
·在Linux系统下如何调整文件打开方式
·如何在Linux系统下更改文件使用权限
  阅读排行
·使用源代码将 Glibc 升级到 2.6
·Linux下推荐的常用应用程序列表
·Linux串口上网的简单实现
·使用 ActiveScaffold 增强 Ruby on Rai
·什么是Linux?Linux与UNIX之间的关系
·应用:postfix邮件服务器安全策略
·提高Linux系统性能加速网络应用程序
·用 DCOP 连接 KDE 应用程序
·Linux用户(User)和用户组管理概述
·Linux操作系统的内核引导程序详细解析
·Linux下的网络HOOK实现以及使用方法
·在 Linux on POWER 上利用透明大内存页
·使用Linux脚本对无线网络进行管理
·RedHat9.0下的DNS和虚拟主机的配置
·Ubuntu Linux操作系统中的用户管理
网摘收藏: