当前位置:Linux教程 - Linux - 在Linux可加载内核模块中探秘(3)

在Linux可加载内核模块中探秘(3)



         第一部分:基础知识
    第三章:系统调用和内核符号表
    作者:CoolBoy
    上节我们讲到了insmod hello.o命令,在执行这个命令的时候,会呼叫系统调用create_module为LKM分配内存,并且会去读取内核符号表为LKM分配内核符号。那么,系统调用和内核符号表到底是什么呢?今天我们就要讨论这两样东西。

      我想大家都知道,任何一种操作系统都会有一些内建于核心中的函数,这些函数能接触到计算机最底层的部分,实际上也就是操作系统的核心功能,它们虽然为数不多,并且相对来说比较简陋,但却可以完成对整个系统的任意操作。

      ——Linux中的这种函数就被称作“系统调用”。

      系统调用是用户和核心空间之间传递信息的桥梁。例如,在用户空间中打开一个文件(比如c语言中很常见的fopen)在核心空间就由sys_open系统调用来解释。那么,我们的系统究竟提供了哪些系统调用呢?在linux系统中有这样一个文件/usr/include/sys/syscall.h,里面列出了所有的系统调用。比如我的syscall.h文件就是这个样子的:

    #ifndef _SYS_SYSCALL_H
    #define _SYS_SYSCALL_H

    #define SYS_setup 0 /* Used only by init, to get system going. */
    #define SYS_exit 1
    #define SYS_fork 2
    #define SYS_read 3
    ....
    #define SYS_setresuid 164
    #define SYS_getresuid 165
    #define SYS_vm86 166
    #define SYS_query_module 167
    #define SYS_poll 168
    #define SYS_syscall_poll SYS_poll

    #endif /* */

      从上面这个文件中,我们看到每个系统调用都对应一个预定义的数字代码(见上表),这个代码是用来代表每个系统调用的。

      在Linux的核心中有一个特殊的结构,sys_call_table[]数组,数字代码和系统调用的映射关系就保存在这个数组里,一当系统要去调用某个系统函数时,就会根据这个数组查出相应的系统调用代码,然后根据这个代码通过0x80中断来执行真正的系统功能。

      OK,掌握了以上知识之后,现在我们可以继续往下学习了。下面的表格中列出了一些最有趣最常用的系统调用,并给出了简短的功能描述。不过,如果你真想写一些派得上用场的LKM的话,可得仔细了解这些调用的工作过程哦。

    系统调用:int sys_brk(unsigned long new_brk);
    功能:改变DS(数据段)的大小(后面我们还将详细介绍它)。

    系统调用:int sys_fork(struct pt_regs regs);
    功能:和用户空间的fork()函数有相同功能。

    系统调用:int sys_getuid () int sys_setuid (uid_t uid) ...
    功能:管理用户ID(UID)的系统调用。

    系统调用:int sys_get_kernel_sysms(struct kernel_sym *table)
    功能:访问核心的系统表。见I.3

    系统调用: int sys_sethostname (char *name, int len);
    int sys_gethostname (char *name, int len);
    功能:设置主机名字和查询主机名字。

    系统调用: int sys_chdir (const char *path);
    int sys_fchdir (unsigned int fd);
    功能:二者均用于设置当前的目录。(即cd...命令)

    系统调用: int sys_chmod (const char *filename, mode_t mode);
    int sys_chown (const char *filename, mode_t mode);
    int sys_fchmod (unsigned int fildes, mode_t mode);
    int sys_fchown (unsigned int fildes, mode_t mode);
    功能:管理用户权限。

    系统调用:int sys_chroot (const char *filename);
    功能:设置调用过程的根目录。

    系统调用:int sys_execve (struct pt_regs regs);
    功能:重要!这是执行文件的系统调用,pt_regs是寄存器栈。

    系统调用:long sys_fcntl (unsigned int fd, unsigned int cmd, unsigned long arg);
    功能:改变文件描述子的属性。

    系统调用: int sys_link (const char *oldname, const char *newname);
    int sym_link (const char *oldname, const char *newname);
    int sys_unlink (const char *name);
    功能:硬/软 链接的管理。

    系统调用:int sys_rename (const char *oldname, const char *newname);
    功能:文件重命名。

    系统调用: int sys_rmdir (const char* name);
    int sys_mkdir (const *char filename, int mode);
    系统调用: 功能:创建和删除目录。

    系统调用: int sys_open (const char *filename, int mode);
    int sys_close (unsigned int fd);
    功能:打开文件和关闭文件。

    系统调用: int sys_read (unsigned int fd, char *buf, unsigned int count);
    int sys_write (unsigned int fd, char *buf, unsigned int count);
    功能:读文件和写文件。

    系统调用:int sys_getdents (unsigned int fd, struct dirent *dirent, unsigned int count);
    功能:查询文件列表(即ls...命令)。

    系统调用:int sys_readlink (const char *path, char *buf, int bufsize);
    功能:读取符号链接。

    系统调用:int sys_selectt (int n, fd_set *inp, fd_set *outp, fd_set *exp, struct timeval *tvp);
    功能:IO复合操作。

    系统调用:sys_socketcall (int call, unsigned long args);
    功能:socket函数 。

    系统调用: unsigned long sys_create_module (char *name, unsigned long size);
    int sys_delete_module (char *name);
    int sys_query_module (const char *name, int which, void *buf, size_t bufsize, size_t *ret);
    功能:加载/卸载和查询LKM程序。

      从黑客的角度来看,上述系统调用是最有趣的一部分,当然,也许在你的cool系统上还需要用到某些特殊的调用,但是,对于普通的黑客来说,使用上述列举的已经足够了。在后面几节,我们将学习怎么使用这些调用。

      那么,什么又是内核符号表呢?

      在/proc/ksyms文件中,就清清楚楚地把各种内核符号展示出来了,这个文件中的每一行就表示一个分配好了的(并且是没有保密的)内核符号,所以我们的LKM可以轻易读取这些核心符号。为什么我们的LKM需要访问这里?各位同学可以仔细看看这个文件,呵呵,我敢打包票你越看就会觉得越有趣,这里面不仅能看到一些很敏感的函数,甚至还能发现我们自己的LKM函数,因为这块公用田对什么都不保密,全都公开了。

      不过,有一点要注意的是,既然我们能从这里看到LKM,那么系统管理员也一定能看到,说不定管理员看到后只一瞬间就把我们的LKM给灭了,那样就不大有趣了。那么,要把LKM隐藏起来不让人知道,有什么好办法呢?在后面有部分篇幅我们会讲述多种隐藏之道,不过这里我们要先讲最简单、最有效、最高明的一种办法。:)

      其实,我们根本就不需要用到任何非法的手段,或是利用系统缺陷来防止我们的LKM输出到这个公开的内核符号表中,我们唯一需要做的就是另外建立一个我们自己的内核符号表,让LKM输出的内核符号到我们自己的内核表中去!怎么做呢?很简单:

    static struct symbol_table module_syms= { /*定义我们自己的符号表!*/
    #include /*这就是我们要输出的符号表*/
    ...
    };
    register_symtab(&module_syms); /*注册我们的符号表*/

    因为我们不要输出任何符号到公共地段,所以我们使用下面的代码来初始化我们的LKM:

    register_symtab(NULL);

    注意:这行代码必须写在init_module()函数中,它表示我们不要使用缺省的符号表。

      好啦,系统调用和内核符号就解释到这里,今天主要是给大家为下一节的课程做一个准备,了解这些知识之后,我们就好在下一节的“切入核心”课程中游刃有余啦。
    发布人:Crystal 来自:Linux专区