首页|操作系统|软件开发|网页制作|媒体动画|数据库|ASP教程|ASP.NET教程|PHP教程|JSP教程|XML教程|建站资料|软件学院|行业资讯|平面设计|网络安全|晒IT论坛|IT人才
  位置: 晒IT >> 操作系统 >> 系统综合技巧 >> 正文
 
 
Linux内核级后门的原理及简单实战应用
Linux内核级后门的原理及简单实战应用
 
 
以下代码均在linux i86 2.0.x的内核下面测试通过。它也许可以在之前的版本通过,但并没有被测试过。因为从2.1.x内核版本就引入了相当大的改变,显著地内存管理上的差别,但这些不是我们现在要讨论的内容。<!-- Copyright 1999-2000 ThruPort Technologies http://www.thruport.com --><!-- end dynamic banner insert -->

  用户空间与内核空间

  linux是一个具有保护模式的操作系统。它一直工作在i386 cpu的保护模式之下。

  内存被分为两个单元:内核区域和用户区域。内核区域存放并运行着核心代码,当然,顾名思义,用户区域也存放并运行用户程序。当然,作为用户进程来讲它是不能访问内核区域内存空间以及其他用户进程的地址空间的。

  核心进程也有同样的情况。核心代码也同样不能访问用户区地地址空间。那么,这样做到底有什么意义呢?我们假设当一个硬件驱动试图去写数据到一个用户内存空间的程序里的时候,它是不可以直接去完成的,但是它可以利用一些特殊的核心函数来间接完成。同样,当参数需要传递地址到核心函数中时,核心函数也不能直接的来读取该参数。同样的,它可以利用一些特殊的核心函数来传递参数。

  这里有一些比较有用的核心函数用来作为内核区与用户区相互传递参数用。

#include get_user(ptr)


  从用户内存获取给定的字节,字,或者长整形。这只是一个宏(在核心代码里面有此宏的详细定义),并且它依据参数类型来确定传输数量。所以你必须巧妙地利用它。

  put_user(ptr)和get_user()非常相似,但是,它不是从用户内存读取数据,而是想用户内存写数据。

memcpy_fromfs(void *to,const void *from,unsigned long n)


  从用户内存中的*from拷贝n个字节到指向核心内存的指针*to。

memcpy_tofs(void *to,const *from,unsigned long n)

  从核心内存中的*from拷贝n个字节数据到用户内存中的*to。

  系统调用

  大部分的c函数库的调用都依赖于系统调用,就是一些使用户程序可以调用的简单核心包装函数。这些系统调用运行在内核本身或者在可加载内核模块中,就是一些可动态的加载卸载的核心代码。

  就象MS-DOS和其他许多系统一样,linux中的系统调用依赖一个给定的中断来调用多个系统调用。linux系统中,这个中断就是int 0x80。当调用'int 0x80'中断的时候,控制权就转交给了内核(或者,我们确切点地说, 交给_system_call()这个函数), 并且实际上是一个正在进行的单处理过程。

  * _system_call()是如何工作的?

  首先,所有的寄存器被保存并且%eax寄存器全面检查系统调用表,这张表列举了所有的系统调用和他们的地址信息。它可以通过extern void *sys_call_table[]来被访问到。该表中的每个定义的数值和内存地址都对应每个系统调用。大家可以在/usr/include/sys/syscall.h这个头中找到系统调用的标示数。

  他们对应相应的SYS_systemcall名。假如一个系统调用不存在,那么它在sys_call_table中相应的标示就为0,并且返回一个出错信息。否则,系统调用存在并在表里相应的入口为系统调用代码的内存地址。这儿是一个有问题的系统调用例程:

[root@plaguez kernel]# cat no1.c#include #include #include extern void *sys_call_table[];sc(){ // 165这个系统调用号是不存在的。    __asm__(        "movl $165,%eax             int $0x80");}main(){    errno = -sc();    perror("test of invalid syscall");}[root@plaguez kernel]# gcc no1.c[root@plaguez kernel]# ./a.outtest of invalid syscall:Function not implemented[root@plaguez kernel]# exit

  系统控制权就会转向真正的系统调用, 用来完成你的请求并返回。 然后_system_call()调用_ret_from_sys_call()来检查不同的返回值, 并且最后返回到用户内存。

  * libc

  这int $0x80 并不是直接被用作系统调用; 更确切地是,libc函数,经常用来包装0x80中断,这样使用的。

  libc通常利用_syscallX()宏来描述系统调用,X是系统调用的总参数个数。

  举个例子吧, libc中的write(2)就是利用_syscall3这个系统调用宏来实现的,因为实际的write(2)原型需要3个参数。在调用0x80中断之前,这个_syscallX宏假定系统调用的堆栈结构和要求的参数列表,最后,当 _system_call()(通过int &0x80来引发)返回的时候,_syscallX()宏将会查出错误的返回值(在%eax)并且为其设置errno。

  让我们看一下另一个write(2)例程并看看它是如何进行预处理的。

[root@plaguez kernel]# cat no2.c#include #include #include #include #include #include #include #include #include _syscall3(ssize_t,write,int,fd,const void *,buf,size_t,count);/*构建一个write调用*/main(){    char *t = "this is a test.\n";    write(0, t, strlen(t));}[root@plaguez kernel]# gcc -E no2.c > no2.C[root@plaguez kernel]# indent no2.C -krindent:no2.C:3304: Warning:old style assignment ambiguity in"=-".  Assuming "= -"[root@plaguez kernel]# tail -n 50 no2.C#9 "no2.c" 2ssize_t write(int fd,const void *buf, size_t count){    long __res;    __asm__ __volatile("int $0x80":"=a" (__res):"0"(4), "b"((long) (fd)),  "c"((long) (buf)),  "d"((long) (count)));    if (__res >= 0)    return (ssize_t) __res;    errno = -__res;    return -1;};main(){    char *t = "this is a test.\n";    write(0, t, strlen(t));}[root@plaguez kernel]# exit

  注意那个write()里的"0"这个参数匹配SYS_write,在/usr/include/sys/syscall.h中定义。

  * 构建你自己的系统调用。

  这里给出了几个构建你自己的系统调用的方法。举个例子,你可以修改内核代码并且加入你自己的代码。一个比较简单可行的方法,不过,一定要被写成可加载内核模块。

  没有一个代码可以象可加载内核模块那样可以当内核需要的时候被随时加载的。我们的主要意图是需要一个很小的内核,当我们需要的时候运行insmod命令,给定的驱动就可以被自动加载。这样卸除来的lkm程序一定比在内核代码树里写代码要简单易行多了。

  * 写lkm程序

  一个lkm程序可以用c来很容易编写出来。它包含了大量的#defines,一些函数,一个初始化模块的函数,叫做init_module(),和一个卸载函数:cleanup_module()。

  这里有一个经典的lkm代码结构:

[1] [2]  下一页
  • 上一篇: 保护隐私 利用Win XP系统回收站隐藏文件
  • 下一篇: 全新方式 在网页中优化Windows和网络
  •  告诉好友  打印此文 关闭窗口 返回顶部
     
    热点文章
     
     
    推荐文章
     
     
    相关文章

    | 设为首页 | 加入收藏 | 联系我们 | 友情链接 | 诚聘英才 |
    Copyright© 2008 ShaiIT.Com .All Rights Reserved
    下载alexa工具,提升您的网站排名