Linux系统可卸载内核模块完全指南

[复制链接]
查看11 | 回复9 | 2011-12-28 15:24:18 | 显示全部楼层 |阅读模式
简介
  
  将Linux操作系统用于服务器在现在是越来越普遍了。因此,入侵Linux在今天也变得越来越有趣.目前最好的攻击Linux的技术就是修改内核代码.由于一种叫做可卸载内核(Loadable
  
  KernelModules(LKMs))的机制,我们有可能编写在内核级别运行的代码,而这种代码可以允许我们接触到操作系统中非常敏感的部分.在过去有一些很好的关于LKM知识的文本或者文件,他们介绍一些新的想法,方法以及一名Hacker所梦寐以求的完整的LKMs.而且也有一些很有趣的公开的讨论(在新闻组,邮件列表).
  
  然而为什么我再重新写这些关于LKMs的东西呢?下面是我的一些理由:
  
  在过去的教材中常常没有为那些初学者提供很好的解释.而这个教材中有很大一部分的基础章节.这是为了帮助那些初学者理解概念的.我见过很多人使用系统的缺陷或者监听器然而却丝毫不了解他们是如何工作的.在这篇文章中我包含了很多带有注释的源代码,只是为了帮助那些认为入侵仅仅是一些工具游戏的初学者!
  
  每一个发布的教材不过把话题集中在某个特别的地方.没有一个完整的指导给那些关注LKMs的Hacker.这篇文章会覆盖几乎所有的关于LKMs的资料(甚至是病毒方面的).
  
  这篇文章是从Hacker或者病毒的角度进行讨论的,但是系统管理员或者内核的开发者也可以参考并从中学到很多东西.
  
  以前的文章介绍一些利用LKMs进行入侵的优点或者方法,但是总是还有一些东西是我们过去从来没有听说过的.这篇文章会介绍一些新的想法给大家.(不是所有的新的资料,只是一些对我们有帮助的)
  
  这篇文章会介绍一些简单的防止LKM攻击的方法,同时也会介绍如何通过使用一些像运行时内核补丁(Runtime Kernel Patching)这样的方法来对付这些防御措施.
  
  要记住这些新的想法仅仅是通过利用一些特殊的模块来实现的.要在现实中真正使用他们还需要对他们进行改进.这篇文章的主要目的是给大家在整个LKM上一个大方向上的指导.在附录A中,我会给大家一些实用的LKMs,并附上一些简短的注释(这是为那些新手的),以及如何使用他们.
  
  整篇文章(除了第五部分)是基于 Linux 2.0.x的80x86机器的.我测试了所有的程序和代码段.为了能够正常使用这里提供的绝大部分代码,你的Linux系统必须有LKM支持.只有在第四部分会给大家一些不需要LKM支持的源代码.本文的绝大多数想法一样可以在Linux2.2.x上实现(也许你会需要一些小小的改动).
  
  这篇文章会有一个特别的章节来帮助系统管理员进行系统安全防护.你(作为一名Hacker)也必须仔细阅读这些章节.你必须要知道所有系统管理员知道的,甚至更多.你也会从中发现很多优秀的想法.这也会对你开发高级的入侵系统的LKMs有所帮助.
  
  因此,通读这篇文章吧.
回复

使用道具 举报

千问 | 2011-12-28 15:24:18 | 显示全部楼层
第一部分. 基础知识
  
  1.1 什么是LKMs
  
  LKMs就是可卸载的内核模块(Loadable Kernel
  
  Modules)。这些模块本来是Linux系统用于扩展他的功能的。使用LKMs的优点有:他们可以被动态的加载,而且不需要重新编译内核。由于这些优点,他们常常被特殊的设备(或者文件系统),例如声卡等使用。
  
  每个LKM至少由两个基本的函数组成:
  
  int init_module(void) /*用于初始化所有的数据*/
  
  {
  
  ...
  
  }
  
  void cleanup_module(void) /*用于清除数据从而能有一个安全的退出*/
  
  {
  
  ...
  
  }
  
  加载一个模块(常常只限于root能够使用)的命令是:
  
  # insmod module.o
  
  这个命令让系统进行了如下工作:
  
  加载可执行的目标文件(在这儿是module.o)
  
  调用 create_module这个系统调用(至于什么叫系统调用,见1.2)来分配内存.
  
  不能解决的引用由系统调用get_kernel_syms进行查找引用.
  
  在此之后系统调用init_module将会被调用用来初始化LKM->执行 int inti_module(void) 等等
  
  (内核符号将会在1.3节中内核符号表中解释)
  
  OK,到目前为止,我想我们可以写出我们第一个小的LKM来演示一下这些基本的功能是如何工作的了.
  
  #define MODULE
  
  #include
  
  int init_module(void)
  
  {
  
  printk("Hello World\n&quot

;
  
  return 0;
  
  }
  
  void cleanup_module(void)
  
  {
  
  printk("Bye, Bye&quot

;
  
  }
  
  你可能会奇怪为什么在这里我用printk(....)而不是printf(.....).在这里你要明白内核编程是完全不同于普通的用户环境下的编程的.你只能使用很有限的一些函数(见1.6)仅使用这些函数你是干不了什么的.因此,你将会学会如何使用你在用户级别中用的那么多函数来帮助你入侵内核.耐心一些,在此之前我们必须做一点其他的.....
  
  上面的那个例子可以很容易的被编译:
  
  # gcc -c -O3 helloworld.c
  
  # insmod helloworld.o
  
  OK,现在我们的模块已经被加载了并且给我们打印出了那句很经典的话.现在你可以通过下面这个命令来确认你的LKM确实运行在内核级别中:
  
  # lsmod
  
  Module     Pages  Used by
  
  helloworld     1    0
  
  这个命令读取在 /proc/modules 的信息来告诉你当前那个模块正被加载.'Pages'
  
  显示的是内存的信息(这个模块占了多少内存页面).'Used by'显示了这个模块被系统
  
  使用的次数(引用计数).这个模块只有当这个计数为0时才可以被除去.在检查过这个以后,你可以用下面的命令卸载这个模块
  
  # rmmod helloworld
  
  OK,这不过是我们朝LKMs迈出的很小的一步.我常常把这些LKMs于老的DOS TSR程序做比较,(是的,我知道他们之间有很多地方不一样),那些TSR能够常驻在内存并且截获到我们想要的中断.Microsoft's Win9x有一些类似的东西叫做VxD.关于这些程序的最有意思的一点在于他们都能够挂在一些系统的功能上,在Linux中我们称这些功能为系统调用.
回复

使用道具 举报

千问 | 2011-12-28 15:24:18 | 显示全部楼层
1.2什么是系统调用
  
  我希望你能够懂,每个操作系统在内核中都有一些最为基本的函数给系统的其他操作调用.在Linux系统中这些函数就被称为系统调用(System Call).他们代表了一个从用户级别到内核级别的转换.在用户级别中打开一个文件在内核级别中是通过sys_open这个系统调用实现的.在/usr/include/sys/syscall.h中有一个完整的系统调用列表.下面的列表是我的syscall.h
  
  #ifndef _SYS_SYSCALL_H
  
  #define _SYS_SYSCALL_H
  
  #define SYS_setup 0
  
  /* 只被init使用,用来启动系统的*/
  
  #define SYS_exit 1
  
  #define SYS_fork 2
  
  #define SYS_read 3
  
  #define SYS_write 4
  
  #define SYS_open 5
  
  #define SYS_close 6
  
  #define SYS_waitpid 7
  
  #define SYS_creat 8
  
  #define SYS_link 9
  
  #define SYS_unlink 10
  
  #define SYS_execve 11
  
  #define SYS_chdir 12
  
  #define SYS_time 13
  
  #define SYS_prev_mknod 14
  
  #define SYS_chmod 15
  
  #define SYS_chown 16
  
  #define SYS_break 17
  
  #define SYS_oldstat 18
  
  #define SYS_lseek 19
  
  #define SYS_getpid 20
  
  #define SYS_mount 21
  
  #define SYS_umount 22
  
  #define SYS_setuid 23
  
  #define SYS_getuid 24
  
  #define SYS_stime 25
  
  #define SYS_ptrace 26
  
  #define SYS_alarm 27
  
  #define SYS_oldfstat 28
  
  #define SYS_pause 29
  
  #define SYS_utime 30
  
  #define SYS_stty 31
  
  #define SYS_gtty 32
  
  #define SYS_access 33
  
  #define SYS_nice 34
  
  #define SYS_ftime 35
  
  #define SYS_sync 36
  
  #define SYS_kill 37
  
  #define SYS_rename 38
  
  #define SYS_mkdir 39
  
  #define SYS_rmdir 40
  
  #define SYS_dup 41
  
  #define SYS_pipe 42
  
  #define SYS_times 43
  
  #define SYS_prof 44
  
  #define SYS_brk 45
  
  #define SYS_setgid 46
  
  #define SYS_getgid 47
  
  #define SYS_signal 48
  
  #define SYS_geteuid 49
  
  #define SYS_getegid 50
  
  #define SYS_acct 51
  
  #define SYS_phys 52
  
  #define SYS_lock 53
  
  #define SYS_ioctl 54
  
  #define SYS_fcntl 55
  
  #define SYS_mpx 56
  
  #define SYS_setpgid 57
  
  #define SYS_ulimit 58
  
  #define SYS_oldolduname 59
  
  #define SYS_umask 60
  
  #define SYS_chroot 61
  
  #define SYS_prev_ustat 62
  
  #define SYS_dup2 63
  
  #define SYS_getppid 64
  
  #define SYS_getpgrp 65
  
  #define SYS_setsid 66
  
  #define SYS_sigaction 67
  
  #define SYS_siggetmask 68
  
  #define SYS_sigsetmask 69
  
  #define SYS_setreuid 70
  
  #define SYS_setregid 71
  
  #define SYS_sigsuspend 72
  
  #define SYS_sigpending 73
  
  #define SYS_sethostname 74
  
  #define SYS_setrlimit 75
  
  #define SYS_getrlimit 76
  
  #define SYS_getrusage 77
  
  #define SYS_gettimeofday 78
  
  #define SYS_settimeofday 79
  
  #define SYS_getgroups 80
  
  #define SYS_setgroups 81
  
  #define SYS_select 82
  
  #define SYS_symlink 83
  
  #define SYS_oldlstat 84
  
  #define SYS_readlink 85
  
  #define SYS_uselib 86
  
  #define SYS_swapon 87
  
  #define SYS_reboot 88
  
  #define SYS_readdir 89
  
  #define SYS_mmap 90
  
  #define SYS_munmap 91
  
  #define SYS_truncate 92
  
  #define SYS_ftruncate 93
  
  #define SYS_fchmod 94
  
  #define SYS_fchown 95
  
  #define SYS_getpriority 96
  
  #define SYS_setpriority 97
  
  #define SYS_profil 98
  
  #define SYS_statfs 99
  
  #define SYS_fstatfs 100
  
  #define SYS_ioperm 101
  
  #define SYS_socketcall 102
  
  #define SYS_klog 103
  
  #define SYS_setitimer 104
  
  #define SYS_getitimer 105
  
  #define SYS_prev_stat 106
  
  #define SYS_prev_lstat 107
  
  #define SYS_prev_fstat 108
  
  #define SYS_olduname 109
  
  #define SYS_iopl 110
  
  #define SYS_vhangup 111
  
  #define SYS_idle 112
  
  #define SYS_vm86old 113
  
  #define SYS_wait4 114
  
  #define SYS_swapoff 115
  
  #define SYS_sysinfo 116
  
  #define SYS_ipc 117
  
  #define SYS_fsync 118
  
  #define SYS_sigreturn 119
  
  #define SYS_clone 120
  
  #define SYS_setdomainname 121
  
  #define SYS_uname 122
  
  #define SYS_modify_ldt 123
  
  #define SYS_adjtimex 124
  
  #define SYS_mprotect 125
  
  #define SYS_sigprocmask 126
  
  #define SYS_create_module 127
  
  #define SYS_init_module 128
  
  #define SYS_delete_module 129
  
  #define SYS_get_kernel_syms 130
  
  #define SYS_quotactl 131
  
  #define SYS_getpgid 132
  
  #define SYS_fchdir 133
  
  #define SYS_bdflush 134
  
  #define SYS_sysfs 135
  
  #define SYS_personality 136
  
  #define SYS_afs_syscall 137
  
  #define SYS_setfsuid 138
  
  #define SYS_setfsgid 139
  
  #define SYS__llseek 140
  
  #define SYS_getdents 141
  
  #define SYS__newselect 142
  
  #define SYS_flock 143
  
  #define SYS_syscall_flock SYS_flock
  
  #define SYS_msync 144
  
  #define SYS_readv 145
  
  #define SYS_syscall_readv SYS_readv
  
  #define SYS_writev 146
  
  #define SYS_syscall_writev SYS_writev
  
  #define SYS_getsid 147
  
  #define SYS_fdatasync 148
  
  #define SYS__sysctl 149
  
  #define SYS_mlock 150
  
  #define SYS_munlock 151
  
  #define SYS_mlockall 152
  
  #define SYS_munlockall 153
  
  #define SYS_sched_setparam 154
  
  #define SYS_sched_getparam 155
  
  #define SYS_sched_setscheduler 156
  
  #define SYS_sched_getscheduler 157
  
  #define SYS_sched_yield 158
  
  #define SYS_sched_get_priority_max 159
  
  #define SYS_sched_get_priority_min 160
  
  #define SYS_sched_rr_get_interval 161
  
  #define SYS_nanosleep 162
  
  #define SYS_mremap 163
  
  #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 /* */
回复

使用道具 举报

千问 | 2011-12-28 15:24:18 | 显示全部楼层
每个系统调用都有一个预定义的数字(见上表),那实际上是用来进行这些调用的.内核通过中断0x80来控制每一个系统调用.这些系统调用的数字以及任何参数都将被放入某些寄存器(eax用来放那些代表系统调用的数字,比如说)
  
  那些系统调用的数字是一个被称之为sys_call_table[]的内核中的数组结构的索引值.这个结构把系统调用的数字映射到实际使用的函数.
  
  OK,这些是继续阅读所必须的足够知识了.下面的表列出了那些最有意思的系统调用以及一些简短的注释.相信我,为了你能够真正的写出有用的LKM你必须确实懂得那些系统调
  
  用是如何工作的.
  
  系统调用列表:
  
  int sys_brk(unsigned long new_brk);
  
  改变DS(数据段)的大小->这个系统调用会在1.4中讨论
  
  int sys_fork(struct pt_regs regs);
  
  著名的fork()所用的系统调用
  
  int sys_getuid ()
  
  int sys_setuid (uid_t uid)
  
  用于管理UID等等的系统调用
  
  int sys_get_kernel_sysms(struct kernel_sym *table)
  
  用于存取系统函数表的系统调用(->1.3)
  
  int sys_sethostname (char *name, int len);
  
  int sys_gethostname (char *name, int len);
  
  sys_sethostname是用来设置主机名(hostname)的,sys_gethostname是用来取的
  
  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);
  
  改变fd(打开文件描述符)的属性的
  
  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);
  
  多路复用I/O操作
  
  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);
  
  用于模块的加载/卸载和查询.
  
  以上就是我认为入侵者会感兴趣的系统调用.当然如果要获得系统的root权你有可能需要一些特殊的系统调用,但是作为一个hacker他很可能会拥有一个上面列出的最基本的列表.在第二部分中你会知道如何利用这些系统调用来实现你自己的目的.
回复

使用道具 举报

千问 | 2011-12-28 15:24:18 | 显示全部楼层
第二部分 渐入佳境
  
  2.1 如何截获系统调用
  
  现在我们开始入侵LKM,在正常情况下LKMs是用来扩展内核的(特别是那些硬件驱动)。然而我们的‘Hacks’做一些不一样的事情。他们会截获系统调用并且更改他们,为了改变系统某些命令的响应方式。
  
  下面的这个模块可以使得任何用户都不能创建目录。这只不过是我们随后方法的一个小小演示。
  
  #define MODULE
  
  #define __KERNEL__
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  extern void* sys_call_table[];
  
  /*sys_call_talbe 被引入,所以我们可以存取他*/
  
  int (*orig_mkdir)(const char *path);
  
  /*原始系统调用*/
  
  int hacked_mkdir(const char *path)
  
  {
  
  return 0;
  
  /*其他一切正常,除了新建操作,该操作什么也不做*/
  
  }
  
  int init_module(void)
  
  /*初始化模块*/
  
  {
  
  orig_mkdir=sys_call_table[SYS_mkdir];
  
  sys_call_table[SYS_mkdir]=hacked_mkdir;
  
  return 0;
  
  }
  
  void cleanup_module(void)
  
  /*卸载模块*/
  
  {
  
  sys_call_table[SYS_mkdir]=orig_mkdir;
  
  /*恢复mkdir系统调用到原来的哪个*/
  
  }
  
  编译并启动这个模块(见1.1)。然后尝试新建一个目录,你会发现不能成功。由于返回值是0(代表一切正常)我们得不到任何出错信息。在移区模块之后,我们又可以新建目录了。正如你所看到的,我们只需要改变sys_call_table(见1.2)中相对应的入口就可以截获到系统调用了。
  
  截获系统调用的通常步骤如下:
  
  找到你需要的系统调用在sys_call_table[]中的入口(看一眼include/sys/syscall.h)
  
  保存sys_call_table[x]的旧入口指针。(在这里x代表你所想要截获的系统调用的索引)
  
  将你自己定义的新的函数指针存入sys_call_table[x]
  
  你会意识到保存旧的系统调用指针是十分有用的,因为在你的新调用中你会需要他来模拟原始调用。当你在写一个'Hack-LKM'时你所面对的第一个问题是:
  
  我到底该截获哪个系统调用?
回复

使用道具 举报

千问 | 2011-12-28 15:24:18 | 显示全部楼层
2.2一些有趣的系统调用
  
  你并不是一个管理内核的上帝,因此你不知道每一个用户的应用程序或者命令到底使用了那些系统调用。因此我会给你一些提示来帮助你找到获得控制的系统调用。
  
  读源代码。在一个象linux这样的系统中,你可以找到任何一个用户(或者管理员)所用的程序的源代码。一旦你发现了某个基本的函数,像dup,open,write.....转向b
  
  下面看看include/sys/syscall.h(见1.2)。试着去直接找相对应的系统调用(查找dup->你就会发现SYS_dup,查找write,你就会发现SYS_write;....)。如果没有找到转向c
  
  一些象socket,send,receive,....这样的调用并不是通过一个系统调用实现的--正如我以前说过的那样。这时就要看一看包含相关系统调用的头文件。
  
  要记住并不是每一个c库里面的函数都是系统调用。绝大多数这样的函数和系统调用毫无关系。一个稍微有一点经验的hacker会看看1.2里面的列表,那已经提供了足够的信息。例如你要知道用户id管理是通过uid的系统调用实现的等等。如果你真的想确定你可以看看库函数/内核的源代码。
  
  最困难的问题是一个系统管理员写了自己的应用程序来检查系统的完整性或者安全性。关于这些程序的问题在于缺乏源代码。我们不能确定这个程序到底是如何工作的以及我们应该截获那些系统调用来隐藏我们的礼物/工具。甚至有可能他引入了一个截获hacker们经常使用的系统调用的LKM来隐藏他自己,并检查系统的安全性(系统管理员们经常使用一些黑客技术来保护他们的系统)。
  
  那我们应该如何继续呢?
回复

使用道具 举报

千问 | 2011-12-28 15:24:18 | 显示全部楼层
2.2.1 发现有趣的系统调用(strace方法)
  
  假定你已经知道了某个系统管理员用来检查系统的程序(这个可以通过某些其他的方法得到,象TTY hijacking(见2.9/appendix
  
  a),现在唯一的问题是你需要让你的礼物躲过系统管理员的程序直到.....)。
  
  好,现在用strace来运行这个程序(也许你需要root权限来执行他)
  
  # strace super_admin_proggy
  
  这会给你一个十分棒的关于这个程序的每个系统调用的输出。这些系统调用有可能都要加入到你的hacking LKM当中去。我并没有一个这样的管理程序作为例子给你看。但是我们可以看看’strace whoami‘的输出:
  
  execve("/usr/bin/whoami", ["whoami"], [/* 50 vars */]) = 0
  
  mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =
  
  0x40007000
  
  mprotect(0x40000000, 20673, PROT_READ|PROT_WRITE|PROT_EXEC) = 0
  
  mprotect(0x8048000, 6324, PROT_READ|PROT_WRITE|PROT_EXEC) = 0
  
  stat("/etc/ld.so.cache", {st_mode=S_IFREG|0644, st_size=13363, ...}) = 0
  
  open("/etc/ld.so.cache", O_RDONLY)   = 3
  
  mmap(0, 13363, PROT_READ, MAP_SHARED, 3, 0) = 0x40008000
  
  close(3)                = 0
  
  stat("/etc/ld.so.preload", 0xbffff780) = -1 ENOENT (No such file or
  
  directory)
  
  open("/lib/libc.so.5", O_RDONLY)    = 3
  
  read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3"..., 4096) = 4096
  
  mmap(0, 761856, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4000c000
  
  mmap(0x4000c000, 530945, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED, 3, 0)
  
  = 0x4000c000
  
  mmap(0x4008e000, 21648, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3,
  
  0x81000) = 0x4008e000
  
  mmap(0x40094000, 204536, PROT_READ|PROT_WRITE,
  
  MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x40094000
  
  close(3)                = 0
  
  mprotect(0x4000c000, 530945, PROT_READ|PROT_WRITE|PROT_EXEC) = 0
  
  munmap(0x40008000, 13363)       = 0
  
  mprotect(0x8048000, 6324, PROT_READ|PROT_EXEC) = 0
  
  mprotect(0x4000c000, 530945, PROT_READ|PROT_EXEC) = 0
  
  mprotect(0x40000000, 20673, PROT_READ|PROT_EXEC) = 0
  
  personality(PER_LINUX)         = 0
  
  geteuid()               = 500
  
  getuid()                = 500
  
  getgid()                = 100
  
  getegid()               = 100
  
  brk(0x804aa48)             = 0x804aa48
  
  brk(0x804b000)             = 0x804b000
  
  open("/usr/share/locale/locale.alias", O_RDONLY) = 3
  
  fstat(3, {st_mode=S_IFREG|0644, st_size=2005, ...}) = 0
  
  mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =
  
  0x40008000
  
  read(3, "# Locale name alias data base\n#"..., 4096) = 2005
  
  brk(0x804c000)             = 0x804c000
  
  read(3, "", 4096)           = 0
  
  close(3)                = 0
  
  munmap(0x40008000, 4096)        = 0
  
  open("/usr/share/i18n/locale.alias", O_RDONLY) = -1 ENOENT (No such file
  
  or directory)
  
  open("/usr/share/locale/de_DE/LC_CTYPE", O_RDONLY) = 3
  
  fstat(3, {st_mode=S_IFREG|0644, st_size=10399, ...}) = 0
  
  mmap(0, 10399, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40008000
  
  close(3)                = 0
  
  geteuid()               = 500
  
  open("/etc/passwd", O_RDONLY)     = 3
  
  fstat(3, {st_mode=S_IFREG|0644, st_size=1074, ...}) = 0
  
  mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =
  
  0x4000b000
  
  read(3, "root:x:0:0:root:/root:/bin/bash\n"..., 4096) = 1074
  
  close(3)                = 0
  
  munmap(0x4000b000, 4096)        = 0
  
  fstat(1, {st_mode=S_IFREG|0644, st_size=2798, ...}) = 0
  
  mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =
  
  0x4000b000
  
  write(1, "r00t\n", 5r00t
  
  )         = 5
  
  _exit(0)                = ?
  
  这确实是一个非常美妙的关于命令’whoami‘的系统调用列表,不是么?在这里为了控制’whoami‘的输出需要拦截4个系统调用
  
  geteuid()               = 500
  
  getuid()                = 500
  
  getgid()                = 100
  
  getegid()               = 100
  
  可以看看2.6的哪个程序的实现。这种分析程序的方法对于显示其他基本工具的信息也是十分重要的。
  
  我希望现在你能够找到那些能够帮助你隐藏你自己的,或者做系统后门,或者任何你想做的事情的系统调用.
回复

使用道具 举报

千问 | 2011-12-28 15:24:18 | 显示全部楼层
第三部分 解决方案(给系统管理员)
  
  3.1 LKM检测的理论和想法
  
  我想现在该到帮助我们的系统管理员来保护他们的系统的时候了。在解释一些理论以前,为了使你的系统变的安全,请记住如下的基本原则:
  
  绝对不要安装你没有源代码的LKMs。(当然,这对于普通的可执行文件也适用)
  
  如果你有了源代码,要仔细检查他们(如果你能够的话)。还记得tcpd木马问题吗?大的软件包很复杂,因此很难看懂。但是如果你需要一个安全的系统,你必须分析源代码。
  
  甚至你已经遵守了这些原则,你的系统还是有可能被别人闯入并放置LKM(比如说溢出等等)。
  
  因此,可以考虑用一个LKM记录每一个模块的加载,并且拒绝任何一个不是从指定安全安全目录的模块的加载企图。(为了防止简单的溢出。不存在完美的方法...)。记录功能可以通过拦截create_module(...)来很轻易的实现。用同样的方法你也可以检查模块加载的目录.
  
  当然拒绝任何的模块的加载也是有可能的。但是这是一个很坏的方法。因为你确实需要他们。因此我们可以考虑改变模块的加载方式,比如说要一个密码。密码可以在你控制的create-module(...)里面检查。如果密码正确,模块就会被加载,否则,模块被丢弃。
  
  要注意的是你必须掩藏你的模块并使他不可以被卸栽。因此,让我们来看看一些记录LKM和密码保护的实现的原型。(通过保护的create_module(...)系统调用)。
  
  3.1.1 一个使用的检测器的原形
  
  对于这个简单的例子,没有什么可以说的。只不过是拦截了sys_create_module(...)并且记录下了加载的模块的名字。
  
  #define MODULE
  
  #define __KERNEL__
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  extern void* sys_call_table[];
  
  int (*orig_create_module)(char*, unsigned long);
  
  int hacked_create_module(char *name, unsigned long size)
  
  {
  
  char *kernel_name;
  
  char hide[]="ourtool";
  
  int ret;
  
  kernel_name = (char*) kmalloc(256, GFP_KERNEL);
  
  memcpy_fromfs(kernel_name, name, 255);
  
  /*这里我们向syslog记录,但是你可以记录到任何你想要的地方*/
  
  printk(" SYS_CREATE_MODULE : %s\n", kernel_name);
  
  ret=orig_create_module(name, size);
  
  return ret;
  
  }
  
  int init_module(void)
  
  /*初始化模块*/
  
  {
  
  orig_create_module=sys_call_table[SYS_create_module];
  
  sys_call_table[SYS_create_module]=hacked_create_module;
  
  return 0;
  
  }
  
  void cleanup_module(void)
  
  /*卸载模块*/
  
  {
  
  sys_call_table[SYS_create_module]=orig_create_module;
  
  }
  
  这就是所有你需要的。当然,你必须加一些代码来隐藏这个模块,这个应该没有问题。在使得这个模块不可以被卸载以后,一个hacker只可以改变记录文件了。但是你也可以把你的记录文件存到一个不可被接触的文件中去(看2.1来获得相关的技巧).当然,你也可以拦截sys_init_module(...)来显示每一个模块。这不过是一个品位问题。
回复

使用道具 举报

千问 | 2011-12-28 15:24:18 | 显示全部楼层
3.1.2 一个密码保护的create_module(...)的例子
  
  这一节我们会讨论如何给一个模块的加载加入密码校验。我们需要两件事情来完成这项任务:
  
  一个检查模块加载的方法(容易)
  
  一个校验的方法(相当的难)
  
  第一点是十分容易实现的。只需要拦截sys_create_module(...),然后检查一些变量,内核就会知道这次加载是否合法了。但是如何进行校验呢?我必须承认我没有花多少时间在这个问题上。因此这个方案并不是太好。但是这是一篇LKM的文章,因此,使用你的头脑去想一些更好的办法。我的方法是,拦截stat(...)系统调用。当你敲任何命令时,系统需要搜索他,stat就会被调用.
  
  因此,在敲命令的同时敲一个密码,LKM会在拦截下的stat系统调用中检查他.[我知道这很不安全;甚至一个Linux
  
  starter都可以击败这种机制.但是(再一次的)这并不是这里的重点....].看看我的实现(我从plaguez的一个类似的LKM中直接抢过来了很多现存的代码....)
  
  #define MODULE
  
  #define __KERNEL__
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  #include
  
  extern void* sys_call_table[];
  
  /*如果lock_mod=1 就是允许加载一个模块*/
  
  int lock_mod=0;
  
  int __NR_myexecve;
  
  /*拦截create_module(...)和stat(...)系统调用*/
  
  int (*orig_create_module)(char*, unsigned long);
  
  int (*orig_stat) (const char *, struct old_stat*);
  
  char *strncpy_fromfs(char *dest, const char *src, int n)
  
  {
  
  char *tmp = src;
  
  int compt = 0;
  
  do {
  
  dest[compt++] = __get_user(tmp++, 1);
  
  }
  
  while ((dest[compt - 1] != '\0') && (compt != n));
  
  return dest;
  
  }
  
  int hacked_stat(const char *filename, struct old_stat *buf)
  
  {
  
  char *name;
  
  int ret;
  
  char *password = "password";
  
  /*yeah,一个很好的密码*/
  
  name  = (char *) kmalloc(255, GFP_KERNEL);
  
  (void) strncpy_fromfs(name, filename, 255);
  
  /*有密码么?*/
  
  if (strstr(name, password)!=NULL)
  
  {
  
  /*一次仅允许加载一个模块*/
  
  lock_mod=1;
  
  kfree(name);
  
  return 0;
  
  }
  
  else
  
  {
  
  kfree(name);
  
  ret = orig_stat(filename, buf);
  
  }
  
  return ret;
  
  }
  
  int hacked_create_module(char *name, unsigned long size)
  
  {
  
  char *kernel_name;
  
  char hide[]="ourtool";
  
  int ret;
  
  if (lock_mod==1)
  
  {
  
  lock_mod=0;
  
  ret=orig_create_module(name, size);
  
  return ret;
  
  }
  
  else
  
  {
  
  printk("MOD-POL : Permission denied !\n&quot

;
  
  return 0;
  
  }
  
  return ret;
  
  }
  
  int init_module(void)
  
  /*初始化模块*/
  
  {
  
  __NR_myexecve = 200;
  
  while (__NR_myexecve != 0 && sys_call_table[__NR_myexecve] != 0)
  
  __NR_myexecve--;
  
  sys_call_table[__NR_myexecve]=sys_call_table[SYS_execve];
  
  orig_stat=sys_call_table[SYS_prev_stat];
  
  sys_call_table[SYS_prev_stat]=hacked_stat;
  
  orig_create_module=sys_call_table[SYS_create_module];
  
  sys_call_table[SYS_create_module]=hacked_create_module;
  
  printk("MOD-POL LOADED...\n&quot

;
  
  return 0;
  
  }
  
  void cleanup_module(void)
  
  /*卸载模块*/
  
  {
  
  sys_call_table[SYS_prev_stat]=orig_stat;
  
  sys_call_table[SYS_create_module]=orig_create_module;
  
  }
  
  代码本身很清楚.下面将会告诉你如何才能让你的LKM更安全,也许这有一些多疑了

:
回复

使用道具 举报

千问 | 2011-12-28 15:24:18 | 显示全部楼层
使用另外一种检验方式(使用你自己的用户空间接口,使用你自己的系统调用;使用用户的ID(而不仅仅是普通的密码);也许你有一个生物监测设备->读一些文档并且在linux下编写自己的设备驱动,然后使用他

...)但是,要记住:哪怕是最安全的硬件保护(软件狗,生物监测系统,一些硬件卡)也常常脆弱的不安全的软件而被击败.你可以使用一种这样的机制来让你的系统变得安全:用一块硬件卡来控制你的整个内核.
  
  另外一种不这么极端的方法可以是写你自己的系统调用来负责校验.(见2.11,那里有一个创建一个你自己的系统调用的例子)
  
  找到一个更好的方法在sys_create_module(...)中进行检查.检查一个变量并不是十分的安全.如果某些人控制了你的系统.他是可以修改内存的(见下一章)
  
  找到一个方法使得一个入侵者没有办法通过你的校验来加载他的LKM
  
  加入隐藏的功能.
  
  ...
  
  有很多工作可以做.但是即使有了这些工作,你的系统也不是完全就是安全的.如果某些人控制了你的系统,他是可以发现一些方法来加载他的LKM的(见下一章);甚至他并不需要一个LKM,因为他只是控制了这个系统,并不想隐藏文件或者进程(和其他的LKM提供的美妙的功能).
  
  3.2 防止LKM传染者的方法
  
  内存驻留的扫描程序(实时的)(就像DOS下的TSR病毒扫描;或者WIN9x下的VxD病毒扫描)
  
  文件检查扫描器(检查模块文件里面的特征字串)
  
  第一种方法可以通过拦截sys_create_module实现(或者init_module调用).第二种方法需要一些模块文件的特征字串.因此我们必须检查两个elf文件头或者标志位.当然,其他的一些LKM传染者可能使用一些改进了的方法.(加密,自我更改代码等等).我不会提供一个检查文件的扫描器.因为你只不过需要写一个小的用户空间的程序来读进模块文件,并且检查两种elf文件头('ELF'字符串,比如)
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

主题

0

回帖

4882万

积分

论坛元老

Rank: 8Rank: 8

积分
48824836
热门排行