本文在不修改ps或top命令的任何代码与采用将进程号置0的方法的前提下,实现隐藏进程,本程序在CRUX 2.2上实现
1、原理
Linux中,可以通过/proc文件系统访问到许多内核的内部信息。/proc文件系统最初的设计也是用于方便地访问进程相关的信息,因此命名为proc。现在这个文件系统已用于反映系统中方方面面的信息,例如/proc/modules是模块的列表,/proc/meminfo则是内存使用的统计。/proc文件系统中的目录并非持久存储的信息,也就是说,其目录并不“真实”地存在于磁盘,而是在访问时动态生成。
本项目中我们感兴趣的是/proc文件系统中关于进程的信息。每一个进程在/proc文件系统中有一个目录即(/proc/
ps命令和top命令从/proc文件系统中读取进程信息并显示出来。因此,如果一个进程的进程号没有在/proc文件系统中反映出来,则这个进程被“隐藏”了,“隐藏”进程在ps或top命令的输出不出现。
2、实现
2.1 为task_struct添加变量hide如下:(include/linux/sched.h)
struct task_struct { unsigned did_exec:1; pid_t pid; pid_t tgid; ...(省略) char hide;(在最后一行添加该句) }
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个看书的源代码:
nt proc_pid_readdir(struct file * filp, void * dirent, filldir_t filldir) { unsigned int tgid_array[PROC_MAXPIDS]; char buf[PROC_NUMBUF]; unsigned int nr = filp->f_pos - FIRST_PROCESS_ENTRY; unsigned int nr_tgids, i; int next_tgid; if (!nr) { ino_t ino = fake_ino(0,PROC_TGID_INO); if (filldir(dirent, "self", 4, filp->f_pos, ino, DT_LNK) < 0) return 0; filp->f_pos++; nr++; } /* f_version caches the tgid value that the last readdir call couldn't * return. lseek aka telldir automagically resets f_version to 0. */ next_tgid = filp->f_version; filp->f_version = 0; for (;;) { nr_tgids = get_tgid_list(nr, next_tgid, tgid_array); if (!nr_tgids) { /* no more entries ! */ break; } next_tgid = 0; /* do not use the last found pid, reserve it for next_tgid */ if (nr_tgids == PROC_MAXPIDS) { nr_tgids--; next_tgid = tgid_array[nr_tgids]; } for (i=0;i
/* for the /proc/TGID/task/ directories */ static int proc_task_readdir(struct file * filp, void * dirent, filldir_t filldir) { unsigned int tid_array[PROC_MAXPIDS]; char buf[PROC_NUMBUF]; unsigned int nr_tids, i; struct dentry *dentry = filp->f_dentry; struct inode *inode = dentry->d_inode; int retval = -ENOENT; ino_t ino; unsigned long pos = filp->f_pos; /* avoiding "long long" filp->f_pos */ if (!pid_alive(proc_task(inode))) goto out; retval = 0; switch (pos) { case 0: ino = inode->i_ino; if (filldir(dirent, ".", 1, pos, ino, DT_DIR) < 0) goto out; pos++; /* fall through */ case 1: ino = parent_ino(dentry); if (filldir(dirent, "..", 2, pos, ino, DT_DIR) < 0) goto out; pos++; /* fall through */ } nr_tids = get_tid_list(pos, tid_array, inode); inode->i_nlink = pos + nr_tids; for (i = 0; i < nr_tids; i++) { unsigned long j = PROC_NUMBUF; int tid = tid_array[i]; ino = fake_ino(tid,PROC_TID_INO); do buf[--j] = '0' + (tid % 10); while ((tid /= 10) != 0); if (filldir(dirent, buf+j, PROC_NUMBUF-j, pos, ino, DT_DIR) < 0) break; pos++; } out: filp->f_pos = pos; return retval;
通过观察,我们可以大胆的猜测,filldir()函数 就是往/proc添加
修改完后编译内核:
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,具体代码如下(注释处即为添加的代码):
int proc_pid_readdir(struct file * filp, void * dirent, filldir_t filldir) { unsigned int tgid_array[PROC_MAXPIDS]; char buf[PROC_NUMBUF]; unsigned int nr = filp->f_pos - FIRST_PROCESS_ENTRY; unsigned int nr_tgids, i; int next_tgid; task_t *task; //声明一个 task_struct if (!nr) { ino_t ino = fake_ino(0,PROC_TGID_INO); if (filldir(dirent, "self", 4, filp->f_pos, ino, DT_LNK) < 0) return 0; filp->f_pos++; nr++; } /* f_version caches the tgid value that the last readdir call couldn't * return. lseek aka telldir automagically resets f_version to 0. */ next_tgid = filp->f_version; filp->f_version = 0; for (;;) { nr_tgids = get_tgid_list(nr, next_tgid, tgid_array); if (!nr_tgids) { /* no more entries ! */ break; } next_tgid = 0; /* do not use the last found pid, reserve it for next_tgid */ if (nr_tgids == PROC_MAXPIDS) { nr_tgids--; next_tgid = tgid_array[nr_tgids]; } for (i=0;i
另外需要在kernel/fork.c的copy_process()函数中添加初始化hide代码(虽可不用,但为了保证逻辑,还是添加吧)
...省略... if (p->binfmt && !try_module_get(p->binfmt->module)) goto bad_fork_cleanup_put_domain; p->did_exec = 0; copy_flags(clone_flags, p); p->pid = pid; p->hide = 0; retval = -EFAULT; ...省略...
重新编译内核
2.4 添加系统调用
为了验证我们2.3所修改的内核代码,我们需添加相应的系统调用,设置task_struct的hide,为此,我们添加2个系统调用,修改如下
2.4.1 kernel/sys.c添加源代码
asmlinkage long sys_hide() { if(current->hide == 1) return -EFAULT; else current->hide = 1; return 0; } asmlinkage long sys_unhide() { if(current->hide == 0) return -EFAULT; else current->hide = 0; return 0; }
2.4.2 include/asm-i386/unistd.h添加系统调用号及系统的调用总数
#define __NR_inotify_add_watch 292 #define __NR_inotify_rm_watch 293 #define __NR_hide 294 #define __NR_unhide 295 #define NR_syscalls 296 //系统调用总数
2.4.3 arch/i386/kernel/syscall_table.S在系统调用表中添加相应项,在最后一行添加
.long sys_hide .long sys_unhide
2.4.4 在include/linux/syscalls.h添加函数声明(貌似这个可有可无)
asmlinkage long hide(); asmlinkage long unhide();
至此,添加系统调用完成,重新编译内核
3 测试
我们编写了一个测试函数,用来我们修改的内核是否成功,代码hide.c如下:
#include
gcc hide.c -o hide后,执行 ./hide ,查看结果如图
测试成功,成功隐藏进程!
4、结束语
这只是实现隐藏的一种方式,网上看到的一个是拦截系统调用,我之前想的是拦截中断,直接在中断里面修改,这个有待验证,以后有机会实现的话也会贴出来和大家分享。
这个程序从开始到完成大概花了我6,7个小时的时间,说起来难度也不大,内核代码的修改量很小,问题的关键处是找到在何处修改代码,这个俺也是边查资料边做实验才找到的。
[2011-2-25]现已实现通过拦截系统调用隐藏进程的方式,分享与大家:http://blog.csdn.net/billpig/archive/2011/02/20/6196163.aspx
版权所有,转载请出明出处!