Summary

Recently I came across a project: libprocesshider, which can be used to hide a Linux process, and for curious, I tried it out and found that it involves a few interesting points, so I’ll document it.

Theory

We usually view the process through the ps command, or when the machine indicator of anomalies, we usually use the top command to view the system’s processes. So if we make these two programs “can not see” we want to hide the process, don’t we complete the process of hiding the function?

To make these two commands can not see our “hidden processes”, we have to go back to the principle of these two processes, their source codes are:

how ps work

  1. [root@liqiang.io]# 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. }

What we can find is that the ps program will list the /proc/ directory to view the process list, and for each process directory:

  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. }

It will read the /proc/<pid>/stat,io,smaps_rollup,statm,status,environ,cmdline,cgroup,ns directories,the source code can be found at:library/readproc.c with function:static proc_t *simple_readproc(PROCTAB *restrict const PT, proc_t *restrict const p) {.

Here we can find a important system call:readdir was used to list the /proc directory.

how top work

  1. [root@liqiang.io]# 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;

The which ? readeither : readproc means that if we specify a process, it will use function readproc,otherwise it will use readeither to read all the process, so it’s the same as what top does essentially.

hidden principal

As we can see from the above, both ps and top display process information by reading the /proc/ directory, so wouldn’t we be able to make it invisible to the user if we tinkered with it a bit?

There are many ways to do this, one simple way is to replace the implementation of readdir, so that when readdir reads the /proc/ directory, it will ignores the processes we want to hide, and then when the user wants to view them via top or ps, there is no way to view them.

And how can we replace it? In the past I wrote a piece of Linux read dynamic link library article (Linux about modules and dynamic link library (shared object) some records), which mentioned the order in which the program loads the shared objects, there is a preload part, we can use environment variables or configuration files to override the standard implementation (usually glibc) with our modified shared objects in advance before the program loads the rest of the system’s shared objects, which will achieve the goal of replacing readdir, thus hiding the readdir. readdir` to hide the process.

Practice

Let’s say I have a malicious program with the file name: evil_script.py, and then I add the dynamic libraries after my mock system call in /etc/ld.so.preload, but before replacing them, I try to see that I am able to see the process:

Then I added mock’s shared object to /etc/ld.so.preload and you’ll see that ps can’t find the process:

The magic happen, the process disappear!

How to prevent

After we know the principle, then how do we deal with it? There are many ways, for example, let me cite a few:

Summary

In this article, we started with a small project to understand a way to “hide” a process, and briefly introduced its principle (however, I did not explore the source code of top and ps in depth), and provided an example to demonstrate it. Finally, it also gives a personal response based on my personal understanding, which may not be the best method, but I think it should solve some problems.