In this post I will begin showcasing some of Volatility’s
new Linux features by analyzing a popular Linux kernel rootkit named “Average
Coder”. These new features will include
recovering .bash_history from memory, finding userland processes elevated to
root by kernel mode components of the rootkit, and discovering overwritten file
operation structure pointers.
If you would like to follow along or recreate the
steps taken, please see the LinuxForensicsWiki
for instructions on how to do so.
Obtaining the Samples
To have samples to test against, I used two sources. The
first was the sample stored on the SecondLook “Linux Memory Images” page (big thanks to SecondLook for creating the
samples and hosting them!), and the second sample was from my own virtual
machine that I infected to showcase parts of the rootkit not used by
SecondLook.
Average Coder Rootkit
The Average Coder Rootkit is a Linux kernel rootkit that operates as a loadable kernel module
and provides the ability to hide processes, logged in users, and kernel modules.
It also provides the ability for a userland process (e.g. bash) to elevate
itself to root privileges by writing to a file inside of /proc.
Average Coder gains control over a computer by loading as a kernel module and then overwriting a number of file_operation structures within the kernel. file_operations is a C structure used by the Linux kernel to provide generic handling of files by a number of subsystems within the kernel and contains function pointers such as read, write, readdir, open, close, etc. Each active file in the operating system has its own file_operations structure that is referenced every time activity is performed on the file (e.g when a file is read, the read function pointer of the structure is eventually called to handle reading the file contents from disk).
We will now go through each piece of functionality the
rootkit offers, how it accomplishes it, and how we can detect it with
Volatility.
The Loadable Module
The first thing we notice, by using the linux_lsmod plugin, is that the rootkit does not unlink itself from
the module list (it is named rootkit):
# python vol.py --profile=LinuxCentOS63x64
-f avgcoder.mem linux_lsmod | grep -i root
Volatile Systems Volatility Framework
2.2_alpha
rootkit 13438
Instead, as
we will see, this rootkit hides from userland by hooking /proc/modules, and while this does effectively hide from userland,
tools analyzing kernel memory will have no issues finding the module.
By reading
the rootkit source,
we also see that the rootkit takes optional parameters for configuration files
and directories. We can recover these options with the –P/--params parameter to linux_lsmod:
# python vol.py --profile=LinuxCentOS63x64
-f avgcoder.mem linux_lsmod –P | grep –A 6 rootkit
Volatile Systems Volatility Framework
2.2_alpha
rootkit 13438
mod_dir=(null)
mod_name=(null)
rc_dir=(null)
rc_name=(null)
In this
case we see that the SecondLook engineers did not pass any options when they loaded
the rootkit, so all the values are null.
Since
rootkits that are deployed in the wild will hide themselves from lsmod, to
perform the rest of the analysis, I modified Volatility’s module finding code
to skip the rootkit module in order to mimic a real compromise. We will see in a later
blog post how Volatility detects modules that unlink themselves from the module
list.
Hiding
Users
Effect on Forensics
To hide
users, Average Coder hooks the read member
of /var/run/utmp in the kernel in order to hide logged in users from applications
such as w and who. This effectively hides
logged in users from network administrators and incident response members
working from userland on a live computer.
How Average Code Accomplishes it
The rootkit
hides users in its init_users_hide_hook function
(here).
It does this by calling the kernel’s path_lookup
function on /var/run/utmp in
order to find the inode structure of
the file. The path_lookup function
works by enumerating the filesystem’s directory structure and then locating the
file of interest. Average Coder wants
the inode structure because its i_fop member is a pointer to the particular
file’s file_operations structure. Once the i_fop
member is found, it can simply be overwritten with the rootkit’s function
that filters users from utmp on
demand.
How Volatility Detects This
To detect
this part of the rootkit, we need to verify that the file operations function
pointers for /var/run/utmp are valid.
Valid in this case means that the function pointers point to a function inside
the base kernel or within a known kernel module. If the function pointers are
invalid, we report them so they can be investigated.
To do this
for /var/run/utmp, we first find its inode using the linux_find_file command with utmp as the parameter:
# python vol.py -f avgcoder.mem
--profile=LinuxCentOS63x64 linux_find_file -F "/var/run/utmp"
Volatile Systems Volatility Framework
2.2_rc1
Inode Number Inode
---------------- ------------------
130564 0x88007a85acc0
This gives us
the inode number and address of the inode structure.
We can use the number to investigate the file on disk with tools, such as the
Sleuthkit, and we can use the address to investigate the file in memory with
Volatility. To verify that the function
pointers are correct we pass the address to linux_check_fop:
# python vol.py -f avgcoder.mem
--profile=LinuxCentOS63x64 linux_check_fop -i 0x88007a85acc0
Volatile Systems Volatility Framework
2.2_rc1
Symbol Name Member Address
----------------------------- ---------------------- ------------------
inode at 88007a85acc0 read 0xffffa05ce4d0
This plugin,
when given the –i/--inode option, reads the inode at
the given address and verifies each member of its i_fop pointer. As we can see, the plugin tells us that the read member is hooked and the address of
the hooked function. This verifies what we see in the Average Coder source code
and if this was an unknown rootkit, we could then perform binary analysis of
the function if we desired.
Since we now know
for certain that w and who were being lied to on a live system,
we want to figure out what the correct values were and who was actually logged
in. To do that, we are again going to use the linux_find_file plugin but this time we will have it recover the /var/run/utmp file from
memory for us:
# python vol.py -f avgcoder.mem
--profile=LinuxCentOS63x64 linux_find_file -i 0x88007a85acc0 -O utmp
This writes
the contents of /var/run/utmp from
memory into the local “utmp” file. We can then use the who binary on our analysis system to determine who was logged in:
# who utmp
centoslive tty1 2013-08-09 16:26 (:0)
centoslive pts/0 2013-08-09 16:28 (:0.0)
Ignoring the
fact that the SecondLook system had a skewed clock, we know now which user was
logged in, how they were logged in (tty1,
pts/0), and the time they logged in. Again, any IR script that ran on the
live system would have missed these logins (we will soon see that centoslive
was a user hidden by the rootkit), and we recovered this information by pulling
/var/run/utmp directly out of memory
and did not rely on the disk.
Hiding
Processes, Kernel Modules, and Communicating with Userland
Effect on Forensics
Average Coder
also allows users to hide processes and kernel modules from userland tools. It does this by setting up a communications
channel on which the kernel module accepts commands from userland. This has a
very negative effect on forensics as investigators working on a live machine
cannot trust the output of a majority of the tools and operating system
interfaces that they normally rely on for gathering evidence.
How Average Code Accomplishes it
Commands are taken
from userland by hooking the write member
of /proc/buddyinfo, which is a file
that normally does not support writes, and data written to this file (commands)
can be used to hide process, users, and to elevate privileges.
Processes are
hidden by hooking the readdir member
of the root of the /proc filesystem.
Every active process in Linux has a corresponding directory under /proc whose name is the PID of the
process (e.g init has a directory of /proc/1/). To hide processes, the
hijacked readdir function simply
filters out the directories that correspond to hidden processes. This effectively
hides the process from a number of userland tools.
Kernel
modules are hidden from lsmod by
hooking the read member of /proc/modules. The modules file is the only source used by lsmod to list loaded
modules, so this effectively hides it from the application. Again, we will see in a future MoVP post how
to find hidden modules on both a live system and from a memory image by leveraging
sysfs.
How Volatility Detects This
To detect
these overwritten file_operations structures,
we will use linux_check_fop in its
default mode. This mode checks the file_operations
structure for a number of common filesystems and files, including all of /proc:
# python vol.py -f avgcoder.mem
--profile=LinuxCentOS63x64 linux_check_fop
Volatile Systems Volatility Framework
2.2_rc1
Symbol Name Member Address
------------------------ ---------------- ------------------
proc_mnt: root readdir 0xffffa05ce0e0
buddyinfo write 0xffffa05cf0f0
modules read 0xffffa05ce8a0
As we can see
from the output, Volatility was able to report the three hooks placed by
Average Coder (readdir from root of proc, write of buddyinfo, and read of modules), by enumerating all the
files and directories under /proc and
verifying their members. From here, the investigator knows the machine is
compromised and can begin to investigate the rootkit.
Recovering
.bash history from Memory
Effect on Forensics
.bash_history is a file on disk that
stores all the commands run by a user directly on the bash command line. This
file is a forensics goldmine when populated as the investigator can recreate
everything done by a logged in user. Due to its importance to forensics, any
reasonable attacker is going to make every effort to avoid having commands
logged to this file. Common methods to accomplish this include:
- Logging in with ssh –T, which does not allocate a pseudo terminal and therefore does not spawn bash
- Setting HISTFILE to /dev/null or unsetting it from the process environment, which effectively stops logging
- Setting HISTSIZE to 0 which prevents any logging
The
exclusion of these commands to disk makes traditional disk forensics much more
difficult and means we have to rely on in-memory information.
How Volatility Handles This
Fortunately
for investigators, no matter which of the previous anti-forensics tricks are
used (or any others), bash still stores the history of commands in memory for
the current session along with the time
executed – regardless of if timestamps are enabled for disk logging.
To recover
this information, the linux_bash plugin
can be used. This plugin finds running bash instances, walks their history
structures in memory, and pulls out the commands executed and their time ran (epoch). Running linux_bash on the Average Coder
memory sample produces the following output:
# python vol.py -f avgcoder.mem
--profile=LinuxCentOS63x64 linux_bash -H 0x6e0950 -P
Volatile Systems Volatility Framework
2.2_rc1
Command Time Command
-------------------- -------
#1376083693 dmesg | head -50
#1376085088 df
#1376085110 dmesg | tail -50
#1376085118 sudo mount /dev/sda1 /mnt
#1376085122 cd /mnt
#1376085122 ls
#1376085128 sudo insmod rootkit.ko
#1376085176 echo "hide" >
/proc/buddyinfo
#1376085180 lsmod | grep root
#1376085194 w
#1376085218 echo "huser centoslive"
> /proc/buddyinfo
#1376085220 w
#1376085229 sleep 900 &
#1376085241 echo "hpid 2872" >
/proc/buddyinfo
#1376085253 ps auwx | grep sleep
<unallocated entries>
#1376085241 echo "hpid 2872" >
/proc/buddyinfo
<unallocated entries>
#1376085128 sudo insmod rootkit.ko
As we study
this output, we see a number of interesting results. First we see that the rootkit was inserted
into the kernel sudo insmod rootkit.ko.
We then see “hide" being sent to /proc/buddyinfo
which hides the kernel module from lsmod.
Next, we see the person checking to be sure that rootkit does not appear in
lsmod output. The user then hides the centoslive
user from utmp (we covered this earlier). The user checks the success of
this by running the w command. Finally, we see the user spawn and background the sleep command, which will print the PID of it to the command line, and then
this PID is sent to buddyinfo to be
hidden. The user then checks this to make sure sleep does not appear in ps auxw.
Recovering these
commands confirms much of what we have seen with the previous Volatility
plugins and also would give us a starting point in a real investigation. Due to the way this rootkit hides data, by
modifying function pointers instead of in-kernel structures, we do not have to
do anything special with Volatility to recover the hidden information, it is
just simply there. We then verify the presence of malicious hooks using methods
shown throughout the post.
Note: The -P/--printunalloc option to linux_bash tells the plugin
to print unallocated entries as well as the currently allocated ones. This has
the effect of printing overwritten entries to the terminal, so it is
recommended to redirect this output to a file. In the above output I have taken
out the unreadable unallocated entries and replaced them with a label.
Elevating
Process Privileges
Effect on Forensics
Average Coder
provides the ability for userland processes to send a command to /proc/buddyinfo in order to have their privileges
elevated to root (user ID 0). This is a
very common method used by attackers in order to maintain persistence on
compromised resources.
The sample
provided by SecondLook did not utilize this portion of the rootkit, so I made
my own sample to test it out and to verify that the created plugin, linux_check_creds, detects it. The
following shows my interaction with the rootkit to elevate my bash shell:
x@debian:~$ id
uid=1000(x) gid=1000(x)
groups=1000(x),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev)
x@debian:~$ echo $$
9673
x@debian:~$ echo "root $$" >
/proc/buddyinfo
-bash: echo: write error: Operation not
permitted
x@debian:~$ id
uid=0(root) gid=0(root) groups=0(root)
As can be
seen, I made my user ‘x’ go from privileges of uid 1000 to uid 0 (root) by
writing the PID of my bash shell to /proc/buddyinfo
with the root command. I then took a memory
capture of the infected VM using a version of LiME already compiled for my VM:
# ls
disk.c
lime-2.6.32-5-686.ko lime.h main.c
Makefile Makefile.sample out
tcp.c
# insmod ./lime-2.6.32-5-686.ko
"format=lime path=avg.hidden-proc.lime"
This wrote
the memory sample to avg.hidden-proc.lime
How Average Code Accomplishes it
On older 2.6
kernels, the user ID and group ID of a process were kept as simple integers in
memory. For a rootkit to elevate the privileges of a process, it simply set
these two values to zero. This
simplicity also made it very difficult to use only the information in the
process structure itself to detect which processes had been elevated and which
were simply spawned by root.
This changed
in later versions of 2.6 as the kernel adopted a cred structure to hold all information related to the privileges of
a process. This structure is fairly
complicated and forced rootkits to adapt their process elevation methods. Although the kernel provides the prepare_creds and commit_creds functions to allocate and store new credentials, a number of rootkits choose not to use this functionality. Instead, they simply find another process that has the privileges of root
and that never exits, usually PID 1, and set the cred pointer of the target process to that of PID 1’s. This
effectively gives the attacker’s process full control and the rootkit does not
have to attempt the non-trivial task of allocating its own cred structure.
How Volatility Detects This
The borrowing
of cred structures leads to an
inconsistency that Volatility can leverage to find elevated processes. In the normal
workings of the kernel, every process gets a unique cred structure and they are never shared or borrowed. The linux_check_creds plugin utilizes this
by building a mapping of processes and their cred structures and then reports any processes that share them. The following output shows the cred structure
running on my infected VM and showing that PID 1 has the same cred structure as
my elevated bash shell (PID 9673):
# python vol.py -f avg.hidden-proc.lime --profile=Linuxthisx86 linux_check_creds
Volatile Systems Volatility Framework
2.2_rc1
PIDs
--------
1, 9673
In real
investigations, we could now focus our efforts on PID 9673 - using the bash plugin
would be a good start – as we know that the attacker used that shell in
conjunction with the rootkit!
Conclusion
We have
thoroughly investigated the Average Coder rootkit, including its internals,
artifacts left on a system, and interactions with the attackers who place it on
a system. This includes finding the
module, locating its hooks throughout the kernel, finding interactions with the
rootkit by userland processes, and recovering files that it filtered on the
running machine.
In tomorrow’s
post we will analyze another kernel rootkit that requires other plugins not
covered in this post to detect. If you have any questions or comments please
use the comment section of the blog or you can find me on Twitter (@attrc).
No comments:
Post a Comment