I found an interesting article about how to hide specific processes on linux from process monitoring tools like
Person states that there are several possible ways to hide processes:
- Using a proper framework: there are a bunch of very good frameworks, like
Grsecuritythat do, among other things, exactly this. In a production system, I would absolutely consider these, although today I want to get my hands dirty and have fun creating something from scratch.
top/ps/...binaries: I could grab the source code of each of these tools, implement my own "hiding linux processes" logic, recompile, and replace the binaries. Very inefficient and time consuming.
libc: I could modify the
libcand input the code to exclude the access to some
/procfiles. But recompiling
libcis a burden, not to mention the
libccode tends to be very hard to understand.
- Modify the system calls in the kernel: This is the most advanced, and it would work by intercepting and modifying the
getdents()system call directly in the kernel with a custom module. It’s definitely tempting, but I won’t follow this route today because I’m already very familiar with how the system call interception works in
sysdig, so I want to do something new.
I decided to go for an intermediate solution, one that is interesting and simple enough to implement in an hour or so: it’s a variant of "modifying libc" based on a tricky feature offered by the Linux dynamic linker (the component that takes care of loading the various libraries needed by a program at runtime), called preloading.
With preloading, Linux is kind enough to give us the option to load a custom shared library before the other normal system libraries are loaded. This means that, if the custom library exports a function with the same signature of one found in a system library, we are literally able to override it with the custom code in our library, and all the processes will automatically pick our custom one!
This sounds like a solution to my problem, because I could write a very simple custom library that overrides libc's
readdir(), and write the logic to hide the process! The logic would be fairly straightforward too: every time I see that the
/proc/PIDdirectory (where PID is the PID of the process having the name
"evil_script") is being read, I just block that access in a clean way, thus hiding the entire directory! I went ahead and implemented these thoughts in code. You can get the sources at https://github.com/gianlucaborello/libprocesshider/blob/master/processhider.c (it’s actually less than 100 lines of code including comments, so go read it!). Once the code is written, let’s compile it as a shared library, and install it in the system path.
Source code: processhider.c
So the steps are:
gcc -Wall -fPIC -shared -o libprocesshider.so processhider.c -ldl
mv libprocesshider.so /usr/local/lib/(as root)
echo /usr/local/lib/libprocesshider.so >> /etc/ld.so.preload
Now back to my question/problem: Here it is described for Linux systems. I tested it on my Ubuntu 18.04 (64 bit) machine and everything worked fine - process was now hidden from
ps. Additionally I tested it on my another machine where FreeBSD 11.0 (64 bit) is installed.
First I had to remove following part of the code:
because I got an error (
dirent64 wasn't defined in dirent.h - I just used
locate dirent.h and compared the code to some internet sources):
processhider.c: In function 'readdir64': processhider.c:87:37: error: dereferencing pointer to incomplete type 'struct dirent64' get_process_name(dir->d_name, process_name) && \ ^ processhider.c:97:1: note: in expansion of macro 'DECLARE_READDIR' DECLARE_READDIR(dirent64, readdir64); ^ *** Error code 1
DECLARE_READDIR(dirent64, readdir64); I got another error complaining about
/usr/local/bin/gcc5 -Wall -fPIC -shared -o libprocesshider.so processhider.c -ldl /usr/local/bin/ld: cannot find -ldl collect2: error: ld returned 1 exit status *** Error code 1 Stop.
Then I was able to compile the code. I placed the library into
/usr/local/lib/ and added it to
However when I called my script
evil_script.py (Code was different (no more UDP packets spam), but I still have
while true loop and
time.sleep(60) so the process should be there) it still appeared in the process list (
ps auxww). Maybe
/etc/ld.so.preload is not working? Can be there some problem with
ld.so shared library? Is there any way how I can test what goes wrong at which point?