Everything in Linux is a file, that even goes for your process information. This lived inside the /proc directory on your filesystem. Today we will use and abuse this knowledge to hide a target process from the ps command in Linux, and in essence other Unix based systems. But first…
How does the ps command work?
As mentioned previously everything in Linux is a file, including the process tree in
Each sub directory in the
/proc file system corresponds to a process id, almost always these days process id 1 is systemd, This can be verified by reading
Now that we understand the basics we can look at how the pscommand works.
By using the
strace ps command we can begin to inspect how this command works.
ps launches and loads the
libprocps.so shared library. From here we can go lookup the code for procps, but wheres the fun in that. Lets continue through the trace.
As we read through we come to a section where ps is opening the file
/proc/<PID>/status These files are read using
read() to determine the data shown to the user by the command such as the process name, State, Umask, Memory usage etc…
Now we understand how the
ps command works lets move on.
Hiding a process using C
Before we start hiding a process we need a target process. I selected the process
pipewire from my process list using
ps -U affix as seen below.
Function Hooking, the basics
Function hooking is the act of intercepting calls to already existing functions, like
write() to build a wrapper function that performs additional tasks before returning the data to the calling application. In linux this can be achieved using the Dynamic Loader API that allows us to dynamically load and execute functions from shared libraries at runtime. This can be abused using the
LD_PRELOAD environment variable or the
LD_PRELOAD variable is used to specify some pre-loaded libraries that should be loaded first by the link loader, Similar to
AppInit_DLLs on windows or
DYLD_INSERT_LIBRARIES on MacOS.
How does this help us?
As we discussed above the
ps command makes use of the
read() call to read data from the
/proc filesystem to determine process information to be displayed to the user.
By hooking the
read() call we can determine the process name and hide it, or re-write it, to prevent
ps from knowing our true process name.
In order to hook the
read() function we need to define it in our code, From the man page we know the function signature is
ssize_t read(int fd, void *buf, size_t count);
fd is the file descriptor we are reading from, This is defined in the
pscommand using the
*buf is our target read buffer, This is passed to
read()as a pointer to a location in memory
count is the number of bytes to be read from fd
So lets define our hooking function.
In order to successfully mimic the read() function we need to make use of
dlsym() to get the existing read() symbol address. This can be done using the following code.
Now our function will call the original read function and return the correct result.
Line 7 defines our
*orig_read() pointer to match the method signature of
Line 10 makes use of
dlsym() to get the address of the
read() symbol and assign it to our
RTLD_NEXT ensures it will return the address of the next declaration of the
read() symbol so we don’t call the one we have just created.
Line 12 calls the
new_read() method to read the data and stores the return value in a result variable to be returned to the calling function.
Congratulations you have written a functioning function hook. At this stage we can compile our code to ensure it works, However we won’t see any difference at this stage.
Hiding our Target Process
Since our function already calls the original
read() function we have all the information we need to hide the process in the
Our updated code now fully hooks and hides our target process defined in the
PROC_NAME constant defined on Line 6
Line 16 does a comparison with
strstr() to check if our process name exists in the buffer, If it does we
return 0 to the caller. This tricks
ps into thinking it has read an empty file and the process gets ignored.
Compiling and Testing
Now we have finished our code we can compile it with gcc as a shared library with the
-shared flag, for this to work correctly however we need to compile it as Position In dependant Code with
To make proper use of the
dlsym function we need to link with libdl using
-ldl and define the
_DNU_SOURCE constant with
-D_GNU_SOURCE . This could also be defined in the code with
#define _GNU_SOURCE as a pre processor directive.
# gcc hookread.c -o hookread.so -shared -ldl -fPIC -D_GNU_SOURCE
We can then test the library with the
LD_PRELOAD environment variable.
Success! Our target process, in my case pipewire, has been hidden from the
ps command. Take that blue team!
In order to persist this and not require an environment variable, we can define the full path to our so file in
/etc/ld.preload.so this will mean we can stay hidden when other users make use of the system.
Other Uses for hooking
There are numerous other offensive uses for hooking, and this only covers hiding a process. Other uses include but are not limited to :
- Hiding from netstat
- Hooking SSH Function calls to build a rootkit
- MiTM hooking SSL Calls
- Privilege Escellation
Thank you for taking the time to read my tutorial. I hope you found it informative. If you have any questions or comments please feel free to reach out to me on twitter @affixsec.