In this post we will discuss two new plugins in Volatility 2.3 that were contributed by Joe Sylve @jtsylve of 504ensics. These plugins are used to detect the two kernel-level keylogging techniques presented in "Bridging the Semantic Gap to Mitigate Kernel-level Keyloggers". Both of these techniques were previously not covered by Volatility.
The first plugin, linux_check_tty, works by validating the receive_buf function pointer for every active tty driver on the system. The reason rootkits target this function pointer is because it allows for recording of every keystroke typed. The function pointer is considered valid if it points to a function defined in the System.map file for the kernel. If the pointer is not valid then "HOOKED" is printed instead of the function name. The following shows the output of the plugin on a clean system:
# python vol.py -f centos.lime --profile=LinuxCentos63Newx64 linux_check_tty
Volatile Systems Volatility Framework 2.3_alpha
Name Address Symbol ---------------- ------------------ ------------------------------ tty1 0xffffffff8131a0b0 n_tty_receive_buf tty2 0xffffffff8131a0b0 n_tty_receive_buf tty3 0xffffffff8131a0b0 n_tty_receive_buf tty4 0xffffffff8131a0b0 n_tty_receive_buf tty5 0xffffffff8131a0b0 n_tty_receive_buf tty6 0xffffffff8131a0b0 n_tty_receive_buf
The second plugin, linux_keyboard_notifier, walks the keyboard_notifier_list and validates that each
notifier pointers within the System.map of the kernel. Hooking of this callback allows for the rootkit
to receive every keystroke entered by the user.
The linux_keyboard_notifier plugin uses a number of functions in the Linux API, and is a good example to study this API. If we were to remove the header information, we would see that the plugin is only around 45 lines of code.
The first function in the process, calculate, starts by using the get_symbol call of the Linux profile to retrieve the address of keyboard_notifier_list for the particular kernel version:
knl_addr = self.addr_space.profile.get_symbol("keyboard_notifier_list")
if not knl_addr:
debug.error("Symbol keyboard_notifier_list not found in kernel")
It then instantiates the symbol as its type within the kernel, atomic_notifier_head. To walk the list of notifers, the walk_internal_list function is used:
knl = obj.Object("atomic_notifier_head", offset = knl_addr, vm = self.addr_space)
for callback in linux_common.walk_internal_list("notifier_block", "next", knl.head):
yield callback.notifier_call
This function takes the type of each list element ("notifier_block"), the pointer to the next element in the list ("next"), and the start of the list (knl.head). It then enumerates each element of the list and instantiates it as the given type. The plugin yields the notifier_call pointer of each element.
If we look at the C definition of notifier_block (include/linux/notifier.h), we see that its 'next' member is of type notifier_block, which we use to walk the list, and that it has a notifier_call member that pointers to the call back function. These are the members used by the previous code snippet:
If we look at the C definition of notifier_block (include/linux/notifier.h), we see that its 'next' member is of type notifier_block, which we use to walk the list, and that it has a notifier_call member that pointers to the call back function. These are the members used by the previous code snippet:
struct notifier_block {
int (*notifier_call)(struct notifier_block *, unsigned long, void *);
struct notifier_block *next;
int priority;
};
Studying the render_text function shows how to validate each notifier_call pointer:
if symbol_cache.has_key(call_addr):
sym_name = symbol_cache[call_addr]
else:
sym_name = self.profile.get_symbol_by_address("kernel", call_addr)
if not sym_name:
sym_name = "HOOKED"
symbol_cache[call_addr] = sym_name
self.table_row(outfd, call_addr, sym_name)
This code snippet relies on the get_symbol_by_address function of the profile to validate that each callback pointer is within the kernel. It then builds a cache of addresses seen to save time while examining many callbacks. The self.table_row function is used to print the results of each lookup to the terminal.
As can be seen, the Linux Volatility API provides a number of useful features that many plugins
leverage for convenience and code cleanliness. Developers wishing to create Linux plugins should
study volatility/plugins/linux/common.py and volatility/plugins/overlays/linux.py. Both of these
contains pieces of the API and can greatly enhance the efficiency of Linux memory forensics research.
No comments:
Post a Comment