Linux内核hack-运行中动态添加系统调用


     Linux中每次添加一个系统调用都要完成重新编译内核,然后制作initrd等工作,不得不说这是一件繁重的工作,很多人本来已经构思好了自己的一个系统调用,要添加到内核,然后却被这些工作所中断,毫不夸张的说,制作initrd就很麻烦,虽然基于cpio的initrd可以利用几条命令完成,然而只要有一个错误,你就不得不重启系统。

     我们都知道,内核模块运行在内核态,可以访问所有的内存空间,那么能不能在系统运行期间,不用重新编译内核,而运用内核模块机制实现添加系统调用呢?答案是肯定的,因为一个基本原理:内核态能读写所有的内存,内存全部在我们手中,没有什么事情做不到, 那么怎么做到呢?

     如果我们了解了系统调用执行的过程,那么我们就知道在执行一个系统调用之前,有两个限制,第一就是系统调用数量在编译时定死,系统调用号必须在这个被允许的系统调用号区间内,第二就是系统调用表的地址未导出。 由于系统调用入口也是一种中断/异常,因此我们看一下代码,看个究竟,基于i386的内核代码在arch/i386/kernel/entry.S(基于2.6.8内核,虽然有点老,但能说明问题),我们看一下系统调用的入口

  1. system_call:   
  2. ENTRY(system_call)   
  3.         pushl %eax                      # save orig_eax   
  4.         SAVE_ALL   
  5.         GET_THREAD_INFO(%ebp)   
  6.         cmpl $(nr_syscalls), %eax    #限制系统调用号在允许的范围内   
  7.         jae syscall_badsys   
  8.                                         # system call tracing in operation   
  9.         testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)   
  10.         jnz syscall_trace_entry   
  11. syscall_call:   
  12.         call *sys_call_table(,%eax,4)  #调用系统调用,其中sys_call_table是系统调用表的地址   
  13.         movl %eax,EAX(%esp)             # store the return value   
  14. ...  

通过以上的代码可以看出,如果我们想添加一个新的系统调用,第一,它的系统调用号不能超过nr_syscalls,第二,它的地址必须在系统调用表地址加上系统调用号*4的位置处, 也就是说,满足这两个条件,内核逻辑就会将执行流路由到我们调用的系统调用服务程序。

     如果我们希望在系统运行期动态添加一个系统调用,那么就必须破除上述的两个神话。第一,我们必须修改nr_syscalls的值,第二,我们必须修改sys_call_table的值,并且新的sys_call_table内容值必须保留原有的sys_call_table内容值。 这个需求怎么做到呢?很简单,扫面二进制机器码!记住,内存在我们手中,还要记住,我们的这次行为不是攻击行为,只是为了调试一个新增加的系统调用而不想重新编译内核...!!!

     到底怎么做到呢?首先,我们只需要修改掉system_call中的cmpl $(nr_syscalls), %eax语句中的nr_syscalls,我们首先从/proc/kallsym中取得system_call的地址,然后在其下面搜索cmpl $(nr_syscalls), %eax的机器码,搜到后将nr_syscalls修改;其次我们在/boot/Sysmap中取得sys_call_table的地址,然后在system_call的下面搜索这个地址的机器码,取得后我们将其修改为新的系统调用表的地址,需要指出的是,新的系统调用表的前nr_syscalls个字段必须和原始的系统调用表sys_call_table的相同。另外,如何获得sys_call_table的地址,这个技术值得商榷,其实有很多方法,本文使用最直接的方式,那就是从/boot/Sysmap中获取。

     有一个细节,那就是如何在system_call附近寻找匹配检测系统调用号范围的机器码呢?最简单的方式莫过于模拟, 那就是写一个程序,调用cmpl $(nr_syscalls), %eax,其中nr_syscalls在2.6.8中为284,那么我们就写下面的代码:

  1. int main(int argc, char **argv)   
  2. {   
  3.     asm("cmpl $284, %eax \n\t");   
  4.     return 0;   
  5. }  

编译后使用objdump得到了3d 1c 01 00 00 cmp    $0x11c,%eax这样的机器码/汇编码,于是我们可以断定,机器特征码为3d 1c 01 00 00 。类似的,sys_call_table的地址也可以这样来求得,不过记住,没有必要,既然你有root权限了,还有必要这样吗?

     那么到底怎么做到的呢?请看模块dynamic_add_syscall:

  1. #include <linux/init.h>   
  2. #include <linux/kernel.h>   
  3. #include <linux/module.h>   
  4. #include <linux/sched.h>   
  5. #include <linux/fs.h>   
  6. #include <linux/proc_fs.h>   
  7. unsigned long entry_addr, table_addr;   
  8. unsigned long nowa_num, want_num;   
  9. module_param(entry_addr, long, S_IRUSR);   
  10. MODULE_PARM_DESC(entry_addr, "system call entry");  //参数保存system_call的地址   
  11. module_param(table_addr, long, S_IRUSR);   
  12. MODULE_PARM_DESC(table_addr, "syscall table");    //参数保存sys_call_table的地址   
  13. module_param(nowa_num, long, S_IRUSR);   
  14. MODULE_PARM_DESC(nowa_num, "system calls number");  //参数保存现有的系统调用数量   
  15. module_param(want_num, long, S_IRUSR);   
  16. MODULE_PARM_DESC(want_num, "system calls number");  //参数保存希望扩充到的系统调用数量   
  17. unsigned int table_new[500] = {0x00};    //新的系统调用表   
  18. unsigned int old_table;     //保存原有的系统调用表,以备恢复   
  19. unsigned int old_num;   //保存原有系统调用数量,以备恢复   
  20. unsigned int old_table_i;  //保存系统调用表机器码命中的偏移地址   
  21. unsigned int old_num_i;  //保存系统调用数量机器码命中的偏移地址   
  22. unsigned char *entry;   
  23. unsigned char *table;   
  24. unsigned int orig_table;   
  25. unsigned char *nowa_num_addr;   
  26. unsigned int appendi;   
  27. //以下的procfs文件展示了一些地址信息,包括新的系统调用表地址以及当前到达哪个系统调用号了。   
  28. static int  
  29. read_syscall_info(char *page, char **start, off_t off, int count, int *eof, void *data)   
  30. {   
  31.     int len;   
  32.     len = sprintf(page, "[table_addr:%p]   [nowa number:%d]  [please inc the value at address   
  33. %p, thank you!]\n", table_new, appendi, &appendi);   
  34.     len -= off;   
  35.     *start = page + off;   
  36.     if (len > count)   
  37.         len = count;   
  38.     else  
  39.         *eof = 1;   
  40.     if (len < 0)   
  41.         len = 0;   
  42.     return len;   
  43. }   
  44. int __init rm_init(void){   
  45.     unsigned int new_table = table_new;   
  46.     unsigned char *ntp = &new_table;   
  47.       
  48.     unsigned char num_test_op[5] = {0}; //定义一个数组,包含了匹配的检测系统调用号范围的机器码   
  49. :3d 1c 01 00 00。   
  50.     int i, j = 0, k = 0;   
  51.     entry = entry_addr;   
  52.     table = table_addr;   
  53.     nowa_num_addr = &nowa_num;   
  54.     unsigned char *want_num_addr = &want_num;   
  55.     orig_table = table;   
  56.     unsigned char *otp = &orig_table;   
  57.       
  58.     num_test_op[0] = 0x3d;   
  59.     num_test_op[1] = *(nowa_num_addr+0);  //这两行初始化匹配机器码   
  60.     num_test_op[2] = *(nowa_num_addr+1);   
  61.     num_test_op[3] = 0x00;   
  62.     num_test_op[4] = 0x00;   
  63.     for (i = 0; i < nowa_num; i ++) {   
  64.         unsigned int *p = table+i*4;   
  65.         table_new[i] = *p;   
  66.     }   
  67.     appendi = i;   
  68.       
  69.     j = 0;   
  70.     for (i = 0; i < 256; i ++) {   
  71.         if ( !j                   &&   
  72.             *(entry+i+0) == num_test_op[0] &&   
  73.             *(entry+i+1) == num_test_op[1] &&   
  74.             *(entry+i+2) == num_test_op[2] &&   
  75.             *(entry+i+3) == num_test_op[3] &&   
  76.             *(entry+i+4) == num_test_op[4]   
  77.         ) {   
  78.             old_num_i = i;   
  79.             j = 1;   
  80.             *(entry+i+1) = *(want_num_addr+0); //这两行修改系统调用号的范围   
  81.             *(entry+i+2) = *(want_num_addr+1);   
  82.         }   
  83.         if ( !k                 &&   
  84.             *(entry+i+0) == *(otp+0) &&   
  85.             *(entry+i+1) == *(otp+1) &&   
  86.             *(entry+i+2) == *(otp+2) &&   
  87.             *(entry+i+3) == *(otp+3)   
  88.         ) {   
  89.             old_table_i = i;   
  90.             k = 1;   
  91.                 *(entry+i+0) = *(ntp+0);  //这四行修改系统调用表的地址   
  92.                 *(entry+i+1) = *(ntp+1);   
  93.                 *(entry+i+2) = *(ntp+2);   
  94.                 *(entry+i+3) = *(ntp+3);   
  95.         }   
  96.     }   
  97.      //在procfs中导出一些信息,供实现新系统调用的模块参考   
  98.     create_proc_read_entry("new_syscall", S_IFREG, NULL, read_syscall_info, NULL);   
  99.     return 0;   
  100. }   
  101. void __exit rm_exit(void){   
  102.     //针对init的修改进行还原   
  103.     unsigned char *otp = &orig_table;   
  104.     *(entry+old_table_i+0) = *(otp+0);   
  105.     *(entry+old_table_i+1) = *(otp+1);   
  106.     *(entry+old_table_i+2) = *(otp+2);   
  107.     *(entry+old_table_i+3) = *(otp+3);   
  108.       
  109.     *(entry+old_num_i+1) = *(nowa_num_addr+0);   
  110.     *(entry+old_num_i+2) = *(nowa_num_addr+1);   
  111.     remove_proc_entry("new_syscall", NULL);   
  112. }   
  113. EXPORT_SYMBOL(appendi); //导出现在已经到哪个系统调用号了   
  114. module_init(rm_init);   
  115. module_exit(rm_exit);   
  116. MODULE_LICENSE("GPL");  
  • 1
  • 2
  • 下一页

相关内容