6

如何隐藏一个 Linux 进程

 8 months ago
source link: https://liqiang.io/post/how-to-hide-a-linux-process
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

如何隐藏一个 Linux 进程

@POST· 2023-08-05 18:19 · 30 min read

最近看到一个项目:libprocesshider,它可以用于隐藏一个 Linux 进程,处于好奇,就尝试了一下,发现涉及到几个有意思的点,所以记录一下。

我们通常查看进程都是通过 ps 命令看的,或者当机器指标异常的时候,我们通常用 top 命令查看系统的进程情况,那么如果我们使这两个程序“看不到”我们想要隐藏的进程的话,不就完成了进程隐藏的功能了吗?

要实现这两个命令看不到我们的“隐藏进程”,那么还得回到这两个进程的原理上,它们的源码分别在:



  1. [[email protected]]# cat library/readproc.c
  2. //////////////////////////////////////////////////////////////////////////////////
  3. // This finds processes in /proc in the traditional way.
  4. // Return non-zero on success.
  5. static int simple_nextpid(PROCTAB *restrict const PT, proc_t *restrict const p) {
  6. static __thread struct dirent *ent; /* dirent handle */
  7. char *restrict const path = PT->path;
  8. for (;;) {
  9. ent = readdir(PT->procfs);
  10. if (!ent || !ent->d_name[0]) break;
  11. if (*ent->d_name > '0' && *ent->d_name <= '9') {
  12. errno = 0;
  13. p->tgid = strtoul(ent->d_name, NULL, 10);
  14. if (errno == 0) {
  15. p->tid = p->tgid;
  16. snprintf(path, PROCPATHLEN, "/proc/%d", p->tgid);
  17. return 1;
  18. }
  19. }
  20. }
  21. return 0;
  22. }

可以看到 ps 的原理是遍历 /proc 目录查看进程列表,然后对于每个进程目录:



  1. next_proc:
  2. new_p = NULL;
  3. for (;;) {
  4. // fills in the PT->path, plus skel_p.tid and skel_p.tgid
  5. if (!PT->finder(PT,&skel_p)) goto end_procs; // simple_nextpid
  6. if (!task_dir_missing) break;
  7. if ((ret = PT->reader(PT,x))) return ret; // simple_readproc
  8. }

再读取 /proc/<pid>/stat,io,smaps_rollup,statm,status,environ,cmdline,cgroup,ns 等目录,具体源码见:library/readproc.c 文件中的:static proc_t *simple_readproc(PROCTAB *restrict const PT, proc_t *restrict const p) {

可以看到这里用到了一个重要的系统调用:readdir 用于罗列 /proc 目录。

top 原理



  1. [[email protected]]# cat library/pids.c
  2. /* procps_pids_reap():
  3. *
  4. * Harvest all the available tasks/threads and provide the result
  5. * stacks along with a summary of the information gathered.
  6. *
  7. * Returns: pointer to a pids_fetch struct on success, NULL on error.
  8. */
  9. PROCPS_EXPORT struct pids_fetch *procps_pids_reap (
  10. struct pids_info *info,
  11. enum pids_fetch_type which)
  12. {
  13. ... ...
  14. if (!pids_oldproc_open(&info->fetch_PT, info->oldflags))
  15. return NULL;
  16. info->read_something = which ? readeither : readproc;

这里的 which ? readeither : readproc 的意思是如果指定了某个进程,那么就用 readproc,否则就通过 readeither 读取所有的进程,所以本质上还是和 top 一样。

从上面我们可以看到,无论是 ps 还是 top 都是通过读取 /proc/ 目录下的进程信息来展示,那么如果我们在这里做一下手脚是不是就可以让用户无法感知了?

在这里有很多动手脚的方式,一种简单的方式就是替换掉 readdir 的实现,让 readdir 读取 /proc/ 目录的时候忽略掉我们想要隐藏的进程,这样,当用户想通过 top 或者 ps 查看的时候就没办法查看了。

那么如何替换呢,在以前我写过一片 Linux 读取动态链接库的文章(Linux 关于模块和动态链接库(共享对象)的一些记录),里面提到了程序加载动态链接库的顺序,其中有一个 preload 的部分,我们可以通过环境变量或者配置文件的方式,在程序加载系统其他的动态链接库之前,提前用我们修改后的动态链接库覆盖标准的实现(一般是 glibc),这样就能达到替换 readdir,从而隐藏进程的目的了。

假如我有个恶意程序,文件名为:evil_script.py,然后我在 /etc/ld.so.preload 中添加我 mock 系统调用后的动态库,但是在替换之前,我先尝试一下我是可以看到这个进程的:

0603c74b93d5.png

然后我将 mock 的动态链接库添加到 /etc/ld.so.preload 中,然后你就会发现 ps 无法找到这个进程了:

e70b47026fa3.png

这个进程就这么消失了。

在我们知道原理之后,那么我们该如何应对呢?方法有很多,例如我举几个:

  • 不使用动态链接库,使用静态编译的 ps 或者 top 之类的程序来查看,因为这里只是替换了动态链接库,静态链接的不会受到影响;
  • 查找异常的网络连接,一般来说隐藏的进程都会它的目的,但是光本地跑程序没有太大意思(除非目的就是你的机器),所以一般都会有网络连接,可以通过排查异常的网络连接找到“不存在”的进程;
  • 手动/程序遍历 pid 查看,既然 mock 的系统调用是判断的进程名,那我们跳过这一步,自己遍历 pid 列表,一一地读取里面的进程信息来排查“不存在的”进程也不妨是一种方法;
  • 检查 /etc/ld.so.preload 等文件,这种属于见招拆招啦,既然我们知道这里可能有可能有问题,那么还不如检查一下直接了当。

本文从一个小项目入手,了解了一种“隐藏”进程的方法,并且简单地介绍了它的原理(但是,关于 topps 源码,我也是浅尝了一下,没有深入探究),并且提供了一个真实实例演示。最后,还根据个人的理解给出了个人的应对办法,可能方法不是最好的,但是我认为应该可以解决一些问题。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK