linux 隐藏进程

来源:互联网 发布:如何生成短信淘宝链接 编辑:程序博客网 时间:2024/06/10 15:50

转自http://blog.csdn.net/billpig/article/details/6038330


本文在不修改ps或top命令的任何代码与采用将进程号置0的方法的前提下,实现隐藏进程,本程序在CRUX 2.2上实现

 

1、原理

   Linux中,可以通过/proc文件系统访问到许多内核的内部信息。/proc文件系统最初的设计也是用于方便地访问进程相关的信息,因此命名为proc。现在这个文件系统已用于反映系统中方方面面的信息,例如/proc/modules是模块的列表,/proc/meminfo则是内存使用的统计。/proc文件系统中的目录并非持久存储的信息,也就是说,其目录并不“真实”地存在于磁盘,而是在访问时动态生成。

     本项目中我们感兴趣的是/proc文件系统中关于进程的信息。每一个进程在/proc文件系统中有一个目录即(/proc/<pid>),目录名即进程号。self目录是一个链接,指向当前进程。

     ps命令和top命令从/proc文件系统中读取进程信息并显示出来。因此,如果一个进程的进程号没有在/proc文件系统中反映出来,则这个进程被“隐藏”了,“隐藏”进程在ps或top命令的输出不出现。

 

2、实现

2.1 为task_struct添加变量hide如下:(include/linux/sched.h)

 

    

[c-sharp] view plain copy
  1. struct task_struct {  
  2.     unsigned did_exec:1;  
  3.     pid_t pid;  
  4.     pid_t tgid;  
  5.     ...(省略)  
  6.     char hide;(在最后一行添加该句)  
  7. }  
 

 

2.2 哪里修改proc代码

     在linux代码树中,所有文件系统的代码都放在linux/fs/目录中,其中,proc文件系统的原始码在linux/fs/proc中,下面我简单介绍一下proc目录中的源文件。 

     在目录中共有11个相关文件,他们是: 
     procfs_syms.c inode.c generic.c base.c 
     array.c root.c proc_tty.c proc_misc.c 
     kmsg.c kcore.c proc_devtree.c

     这么多的文件我们如何去找呢,借鉴网上的资料,对各个文件进行查找可知:
     其中,procfs_syms.c,generic.c及inode.c和proc文件系统的管理相关,包括proc文件系统的注册,及向内核其他子系统提供的例程等等。 
     源文件root.c和proc文件系统的根结点的管理相关。 
     而base.c,array.c则用来处理/proc目录中进程的信息,包括命令行,进程状态,内存状态等等和进程相关的内容。proc_tty.c用来处理/proc/tty信息,proc_misc.c则用来管理和/proc目录中的大多数文件。 
     除此之外,更有两个非常重要的头文件proc_fs.h,proc_fs_i.h,我们能在/linux/include/linux/目录中找到。

     同时,最重要的是,我们确定了我们所要需要的相关信息在base.c文件中,主要是在这2个函数之间:proc_pid_readdir,proc_task_readdir,但是是哪个函数呢?由于网上的资料很少,没办法一个一个试。通过查看着2个看书的源代码:

 

    

[c-sharp] view plain copy
  1. nt proc_pid_readdir(struct file * filp, void * dirent, filldir_t filldir)  
  2. {  
  3.     unsigned int tgid_array[PROC_MAXPIDS];  
  4.     char buf[PROC_NUMBUF];  
  5.     unsigned int nr = filp->f_pos - FIRST_PROCESS_ENTRY;  
  6.     unsigned int nr_tgids, i;  
  7.     int next_tgid;  
  8.     if (!nr) {  
  9.         ino_t ino = fake_ino(0,PROC_TGID_INO);  
  10.         if (filldir(dirent, "self", 4, filp->f_pos, ino, DT_LNK) < 0)  
  11.             return 0;  
  12.         filp->f_pos++;  
  13.         nr++;  
  14.     }  
  15.     /* f_version caches the tgid value that the last readdir call couldn't 
  16.      * return. lseek aka telldir automagically resets f_version to 0. 
  17.      */  
  18.     next_tgid = filp->f_version;  
  19.     filp->f_version = 0;  
  20.     for (;;) {  
  21.         nr_tgids = get_tgid_list(nr, next_tgid, tgid_array);  
  22.         if (!nr_tgids) {  
  23.             /* no more entries ! */  
  24.             break;  
  25.         }  
  26.         next_tgid = 0;  
  27.         /* do not use the last found pid, reserve it for next_tgid */  
  28.         if (nr_tgids == PROC_MAXPIDS) {  
  29.             nr_tgids--;  
  30.             next_tgid = tgid_array[nr_tgids];  
  31.         }  
  32.         for (i=0;i<nr_tgids;i++) {  
  33.             int tgid = tgid_array[i];  
  34.             ino_t ino = fake_ino(tgid,PROC_TGID_INO);  
  35.             unsigned long j = PROC_NUMBUF;  
  36.             do  
  37.                 buf[--j] = '0' + (tgid % 10);  
  38.             while ((tgid /= 10) != 0);  
  39.             if (filldir(dirent, buf+j, PROC_NUMBUF-j, filp->f_pos, ino, DT_DIR) < 0) {  
  40.             /* returning this tgid failed, save it as the first 
  41.             * pid for the next readir call */  
  42.             filp->f_version = tgid_array[i];  
  43.             goto out;  
  44.             }  
  45.             filp->f_pos++;  
  46.             nr++;  
  47.         }  
  48.     }  
  49. out:  
  50.     return 0;  
  51. }  
 

 

    

[c-sharp] view plain copy
  1. /* for the /proc/TGID/task/ directories */  
  2. static int proc_task_readdir(struct file * filp, void * dirent, filldir_t filldir)  
  3. {  
  4.     unsigned int tid_array[PROC_MAXPIDS];  
  5.     char buf[PROC_NUMBUF];  
  6.     unsigned int nr_tids, i;  
  7.     struct dentry *dentry = filp->f_dentry;  
  8.     struct inode *inode = dentry->d_inode;  
  9.     int retval = -ENOENT;  
  10.     ino_t ino;  
  11.     unsigned long pos = filp->f_pos;  /* avoiding "long long" filp->f_pos */  
  12.     if (!pid_alive(proc_task(inode)))  
  13.         goto out;  
  14.     retval = 0;  
  15.     switch (pos) {  
  16.     case 0:  
  17.         ino = inode->i_ino;  
  18.         if (filldir(dirent, ".", 1, pos, ino, DT_DIR) < 0)  
  19.             goto out;  
  20.         pos++;  
  21.         /* fall through */  
  22.     case 1:  
  23.         ino = parent_ino(dentry);  
  24.         if (filldir(dirent, "..", 2, pos, ino, DT_DIR) < 0)  
  25.             goto out;  
  26.         pos++;  
  27.         /* fall through */  
  28.     }  
  29.     nr_tids = get_tid_list(pos, tid_array, inode);  
  30.     inode->i_nlink = pos + nr_tids;  
  31.     for (i = 0; i < nr_tids; i++) {  
  32.         unsigned long j = PROC_NUMBUF;  
  33.         int tid = tid_array[i];  
  34.         ino = fake_ino(tid,PROC_TID_INO);  
  35.         do  
  36.             buf[--j] = '0' + (tid % 10);  
  37.         while ((tid /= 10) != 0);  
  38.         if (filldir(dirent, buf+j, PROC_NUMBUF-j, pos, ino, DT_DIR) < 0)  
  39.             break;  
  40.         pos++;  
  41.     }  
  42. out:  
  43.     filp->f_pos = pos;  
  44.     return retval;  
 

 

    通过观察,我们可以大胆的猜测,filldir()函数 就是往/proc添加<pid>目录的函数,我们可以做个实验,即,在filldir()下面再添加一个filldir()函数,这样的话,一个进程在/proc目录下就会有2个目录,同时,ps -ax显示的同一个进程也会有2个。

    修改完后编译内核:

    cd /usr/src/linux-<版本号>

    make bzImage

    cp arch/i386/boot/bzImage /boot/vmlinuz-0.1

    cp System.map /boot/System.map-0.1

    修改grub,为修改的内核添加相应的启动项

    vim /boot/grub/menu.lst

    

    reboot重新启动,在启动后选择CRUX_TEST选项,使用我们修改完的内核,

    输入ps -ax或者查看/proc文件便会出现我们预先猜测的结果,同时,经过验证可知,ps, top命令调用的是proc_pid_readdir函数,而不是proc_task_readdir函数。嘿,在这,我们就发现新大陆了!

 

 

2.3 怎么修改proc

     由2.2可知,我们需要修改在函数为proc_pid_readdir,仔细阅读相关代码可知,我们只需在if (filldir(dirent, buf+j, PROC_NUMBUF-j, filp->f_pos, ino, DT_DIR) < 0) 前判断进程是否是否有设置为隐藏,而在之前,我们要先获取该进程的task_struct,具体代码如下(注释处即为添加的代码):

 

    

[c-sharp] view plain copy
  1. int proc_pid_readdir(struct file * filp, void * dirent, filldir_t filldir)  
  2. {  
  3.     unsigned int tgid_array[PROC_MAXPIDS];  
  4.     char buf[PROC_NUMBUF];  
  5.     unsigned int nr = filp->f_pos - FIRST_PROCESS_ENTRY;  
  6.     unsigned int nr_tgids, i;  
  7.     int next_tgid;  
  8.     task_t *task; //声明一个 task_struct  
  9.     if (!nr) {  
  10.         ino_t ino = fake_ino(0,PROC_TGID_INO);  
  11.         if (filldir(dirent, "self", 4, filp->f_pos, ino, DT_LNK) < 0)  
  12.             return 0;  
  13.         filp->f_pos++;  
  14.         nr++;  
  15.     }  
  16.     /* f_version caches the tgid value that the last readdir call couldn't 
  17.      * return. lseek aka telldir automagically resets f_version to 0. 
  18.      */  
  19.     next_tgid = filp->f_version;  
  20.     filp->f_version = 0;  
  21.     for (;;) {  
  22.         nr_tgids = get_tgid_list(nr, next_tgid, tgid_array);  
  23.         if (!nr_tgids) {  
  24.             /* no more entries ! */  
  25.             break;  
  26.         }  
  27.         next_tgid = 0;  
  28.         /* do not use the last found pid, reserve it for next_tgid */  
  29.         if (nr_tgids == PROC_MAXPIDS) {  
  30.             nr_tgids--;  
  31.             next_tgid = tgid_array[nr_tgids];  
  32.         }  
  33.         for (i=0;i<nr_tgids;i++) {  
  34.             int tgid = tgid_array[i];  
  35.             ino_t ino = fake_ino(tgid,PROC_TGID_INO);  
  36.             unsigned long j = PROC_NUMBUF;  
  37.             //获得进程的pid  
  38.             task = find_task_by_pid(tgid);  
  39.             do  
  40.                 buf[--j] = '0' + (tgid % 10);  
  41.             while ((tgid /= 10) != 0);  
  42.               
  43.             printk(KERN_DEBUG "pid:%d, hide:%d/n", task->pid, task->hide);  
  44.             //判断进程是否为隐藏  
  45.             if(task != NULL && task->hide == 0)  
  46.             {  
  47.                 printk(KERN_DEBUG "task:%d no hide/n", task->pid);  
  48.                 if (filldir(dirent, buf+j, PROC_NUMBUF-j, filp->f_pos, ino, DT_DIR) < 0) {  
  49.                     /* returning this tgid failed, save it as the first 
  50.                      * pid for the next readir call */  
  51.                     filp->f_version = tgid_array[i];  
  52.                     goto out;  
  53.                 }  
  54.             }  
  55.             filp->f_pos++;  
  56.             nr++;  
  57.               
  58.         }  
  59.     }  
  60. out:  
  61.     return 0;  
  62. }  
 

 

     另外需要在kernel/fork.c的copy_process()函数中添加初始化hide代码(虽可不用,但为了保证逻辑,还是添加吧)

    

[c-sharp] view plain copy
  1. ...省略...  
  2.     if (p->binfmt && !try_module_get(p->binfmt->module))  
  3.         goto bad_fork_cleanup_put_domain;  
  4.     p->did_exec = 0;  
  5.     copy_flags(clone_flags, p);  
  6.     p->pid = pid;  
  7.     p->hide = 0;  
  8.     retval = -EFAULT;  
  9. ...省略...  
 

     重新编译内核

 

2.4 添加系统调用

     为了验证我们2.3所修改的内核代码,我们需添加相应的系统调用,设置task_struct的hide,为此,我们添加2个系统调用,修改如下

     2.4.1 kernel/sys.c添加源代码

    

[c-sharp] view plain copy
  1. asmlinkage long sys_hide()  
  2. {  
  3.     if(current->hide == 1)  
  4.         return -EFAULT;  
  5.     else  
  6.         current->hide = 1;  
  7.     return 0;  
  8. }  
  9. asmlinkage long sys_unhide()  
  10. {  
  11.     if(current->hide == 0)  
  12.         return -EFAULT;  
  13.     else  
  14.         current->hide = 0;  
  15.     return 0;  
  16. }  

 

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

    

[c-sharp] view plain copy
  1. #define __NR_inotify_add_watch  292  
  2. #define __NR_inotify_rm_watch   293  
  3. #define __NR_hide               294  
  4. #define __NR_unhide             295  
  5. #define NR_syscalls 296 //系统调用总数  
 

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

    

[c-sharp] view plain copy
  1. .long sys_hide  
  2. .long sys_unhide  
 

     2.4.4 在include/linux/syscalls.h添加函数声明(貌似这个可有可无)

    

[c-sharp] view plain copy
  1. asmlinkage long hide();  
  2. asmlinkage long unhide();  
 

 

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

 

3 测试

    我们编写了一个测试函数,用来我们修改的内核是否成功,代码hide.c如下:

 

    

[c-sharp] view plain copy
  1. #include <stdio.h>    
  2. #include <sys/types.h>    
  3. #include <linux/unistd.h>    
  4. #define __NR_hide 294    
  5. #define __NR_unhide 295    
  6. int main(int argc, char **argv)    
  7. {    
  8.     int i=0;    
  9.     printf("original/n");     
  10.     system("ps");  
  11.     int i = syscall(__NR_hide);//调用系统号为294的系统调用    
  12.     printf("hide:/n");     
  13.     system("ps");    
  14.     i = syscall(__NR_unhide);    
  15.     printf("unhide:/n");    
  16.     system("ps");     
  17.     return 0;    
  18. }   

 

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

 

     

 

 

      测试成功,成功隐藏进程!

 

4、结束语

     这只是实现隐藏的一种方式,网上看到的一个是拦截系统调用,我之前想的是拦截中断,直接在中断里面修改,这个有待验证,以后有机会实现的话也会贴出来和大家分享。

     这个程序从开始到完成大概花了我6,7个小时的时间,说起来难度也不大,内核代码的修改量很小,问题的关键处是找到在何处修改代码,这个俺也是边查资料边做实验才找到的。

     [2011-2-25]现已实现通过拦截系统调用隐藏进程的方式,分享与大家:http://blog.csdn.net/billpig/archive/2011/02/20/6196163.aspx

0 0
原创粉丝点击