Linux中国 Linux中国门户站!
设为主页 设为主页
收藏本站 收藏本站
 
当前位置 :首页 ->Linux技术 ->系统管理 ->正文

Advances in Kernel Hacking II

来源:Linux-cn.com 作者:Webmaster 时间:2007-05-05 点击: [收藏] [投稿]

  

1.简介

  在kernel hacking领域有好多观点没有多大用处。但是,这并不意味着不能使用它们解决实际的问题,而它们能够解决的通常又不是人们最初希望解决的。

2.执行的重定向

  在收到执行某个可执行二进制文件的请求时,如果你重定向执行请求,转而执行其它的二进制可执行文件,将使用户很难察觉。我们通过几个内核模块就可以实现执行的重定向,从而使某个可执行文件只有在执行时,才会被替代,而真正的可执行文件根本不做任何修改。

  如果使用这种技术安装系统后门,由于不对任何的文件进行修改,因此将使Tripwire[1]和aide[2]等数据完整性检测工具无法察觉。而另一方面,如果把执行重定向技术用于蜜罐(honeypot)系统,可以起到迷惑攻击者的作用。

  即使经过多年活跃的内核开发,通过可加载内核(Loadable Kernel Module)实现执行重定向功能也只是使用同样的技术。因此,某些系统管理员能够很轻易地快速察觉系统的后门,而其他人对这种危险却无动于衷。不过,目前这种危险还并不存在。

3.技术分析

  下面,我将展示五种实现执行重定向的方式,附录A包含它们的示例代码。这些示例代码并不能用于实际,只是为了描述一些思想。

  读一下参考4或者参考5对于理解这些源代码很有帮助。

  

  这些示例代码只是描述了如何在一个LKM中实现这些技术,而且这都是在Linux系统的实现。不过,这些技术并不局限于Linux,大部分技术只要稍做修改可以移植到任何的UNIX系统。

3.1.Classic

  这里之所以包含这个技术,纯粹是出于完整性考虑,它是通过代替系统调用处理函数实现的。细节请看附录A中的代码classic.c,这里就不必多做赘述了。KNARK(参考3)rootkit采用了这个技术,有关的技术细节在参考6中有解释。采用这个技术的隐蔽性不好,只要检查指向系统调用表的地址就能够检测到。

3.2.Obvious

  系统调用和硬件的架构无关。在内核的源代码中,do_execve函数的作用是执行应用程序(在~/fs/exec.c文件中)。而execve系统调用可以理解为do_execve函数的一个封装。这里,我们将通过代替do_execve函数实现执行的重定向:

        n_do_execve (char *file, char **arvp, char **envp,
                        struct pt_regs *regs)
        ...
        if (!strcmp (file, O_REDIR_PATH)) {
                file = strdup (N_REDIR_PATH);
        }
        restore_do_execve ();
        ret = do_execve (file, arvp, envp, regs);
        redirect_do_execve ();
        ...

  为了实际的执行重定向,我们替代do_execve内核函数并随心所欲地用新的文件名代替原来的文件名。这显然也是一种封装execve系统调用的方式。有关的技术细节请参考附录A中的obvious.c文件。据我所知,目前还没有LKM采用这种技术。

  检测采用这种技术的后门不象采用classic技术的那样容易。检查函数的开头是否包含一个跳转指令是个不错的想法。

  注:采用这种技术,首先需要用一段代码(static char n_handler[] = "xb8x00x00x00x00xffxe0")替代目标函数的开头七个字节的指令,n_handler[]表示如下汇编指令:

     mov $0x0,%eax
     jmp *%eax

  然后,在模块的init_module()函数中使用*(long *) &n_handler[1] = (long) n_do_execve在n_handler[]中填入n_do_execve函数的地址。n_handler[]将变成

     mov $0x4xxxxxxx,%eax
     jmp *%eax

3.3.Waiter

  在执行一个程序之前,系统首先要打开需要执行的文件。内核提供了一个函数open_exec完成这个任务。它将打开要执行的二进制文件并做一些完整性检查工作。

  由于open_exec需要完整的路径才能打开要执行的二进制文件,因此我们可以任意地使用我们要执行的文件代替原来的文件名,然后调用原来的函数(open_exec)。open_exec在do_execve函数中调用。

  这种技术的实现方式和上一种差不多,因此也是可以检测的。

3.4.Nexus

  一旦二进制可执行文件被打开,就可以读入内存了,对吗?事实上,在这之前还要搜索对应的二进制格式处理器,由这个处理器处理这个二进制文件。通常,这个操作完成,就将启动一个新的进程。

  一个二进制格式处理器如下定义(参考~/include/linux/binfmts.h文件):

        /*
         * This structure defines the functions that are
         * used to load the binary formats that linux
         * accepts.
         */
        struct linux_binfmt {
                struct linux_binfmt * next;
                struct module *module;
                int (*load_binary)(struct linux_binprm *, 
                                struct pt_regs * regs);
                int (*load_shlib)(struct file *);
                int (*core_dump)(long signr, struct pt_regs * regs, 
                                struct file * file);
                unsigned long min_coredump;     /* minimal dump size */
        };

  在这个结构中提供了三个函数指针。load_shlib用于加载动态连接库;core_dump用于处理内核转储(core dump)文件;load_binary用来加载要执行的二进制文件。我们可以在load_binary上面做手脚,用新的处理函数替代这个指针。

  我们的load_binary函数如下所示:

        int n_load_binary (struct linux_binprm *bin,  
                                struct pt_regs *regs) {
                int ret;
                if (!strcmp (bin->filename, O_REDIR_PATH)) {
                        /*
                         * 如果是要重定向的二进制文件,
                         * 只要关闭原有文件描述符打开新的文件。
                         */
                        filp_close (bin->file, 0);
                        bin->file = open_exec (N_REDIR_PATH);
                        prepare_binprm (bin);
                        goto out;
                }
        out:
                return old_load_binary (bin, regs);
        }


 如果您对本文有任何疑问或者建议,请到讨论区发表您的意见: >> 论坛入口 <<



上一篇:Linux上的集群及其配置实例   下一篇:JAVA学习,是一条漫长的道路

文章评论】 【收藏本文】 【推荐好友】 【打印本文】 【我要投稿】 【论坛讨论
更多相关文章
Power by linux-cn.com 粤ICP备05006655号