Exploiting Loadable Kernel Modules
Michael Reiter
MBUS 543: Incident Handling and Malicious Code
Exploit Details
Name: Kernel Subversion
Variants: A number of programs have been written to take advantage
of this vulnerability, including rootkits like Knark and Adore. This
paper will use the Adore suite as its primary example, with illustrations
from other code.
Operating System: Most modern Unix variants including Linux, Solaris,
FreeBSD, and others. Windows NT does not use loadable kernel modules;
however, a kernel patch rootkit has been released.
Protocols/Services: Loadable Kernel modules allow a user with
root access to dynamically load software routines into a running kernel.
Brief Description: If a kernel module is malicious for instance,
providing a backdoor to the system then the kernel may be subverted to
the malicious user's purposes, often transparently.
Protocol Description
The kernel of an operating system (OS) is the core function the most
basic part of the OS that provides the framework and routines that all
the other OS functions work from. To keep the kernel to a minimum
of complexity, or to extend the kernel's functionality, kernel modules
dynamically loaded software feeding routines or data into the kernel
may be used. They may be loaded into or unloaded from the kernel
from user space, without recompiling the kernel. Code running in
kernel space has access to the lowest levels of the OS, and can intercept
any system call made. Therefore, this code may wield tremendous power.
Kernel modules may be used for any purpose the software designer wishes.
In fact, a kernel module may do nothing at all. In Linux, at least,
it must simply be able to be loaded into the kernel when needed, and unloaded
when no longer needed. Solaris loadable kernel modules have somewhat
more detailed requirements. However, as there would be little point
in expending the effort to write, test, and use a kernel module that does
nothing, software writers have found innovative ways to use modules.
For example, in Linux, PCMCIA card and network interface card drivers are
written as modules that are loaded at boot time. In Solaris, the
TCP/IP stack is a kernel module. With such power, it was only a matter
of time before people started writing kernel modules with less than pure
motives. As always, papers written by people with extremely creative
names cloak themselves with the educational purposes caveat; however,
one may safely assume in most cases that backdoors are not written solely
to satisfy intellectual curiosity.
The obvious issue here is that a kernel module cannot be loaded by just
anyone. Only the superuser, or the kernel itself (which runs with
root privileges and is owned by root), may load or unload a kernel module.
This paper is concerned with the security issues of the loadable kernel
module. How, then, is this a security vulnerability if the attacker
already has root?
The answer is that for an attacker, gaining root privileges is only
beginning. He must carry out a number of vital steps, to include
removing traces of the attack from logs, hiding processes and files from
the true superuser (the system administrator), redirecting execution of
programs, monitoring and shaping network traffic and perhaps most important
providing a way back into the system. True, any of these functions
could be carried out with trojanized versions of commands like ps, ls,
cd, netstat, and others, but these may be detected by their altered fingerprints,
if the administrator took the precaution of using Tripwire, MD5 digests,
or something similar. By using a loadable kernel module, the attacker
can leave all of the normal functions in place, and subvert their function
at kernel level without leaving a trace at least none that the average
administrator will see. Better, those Tripwire/MD5 fingerprints will
all be the same, and with a few tricks, the kernel module itself may be
rendered almost invisible. For instance, according to pragmatic of
THC, by removing the module name in the init_module routine and setting
other references to zero, the module will be invisible (and unremovable).
Therein lies the true problem.
Description of Variants
One might consider the idiosyncrasies of each OS with loadable kernel
modules to be a variant; however, with each, the underlying principle remains
the same. Code from user space has become part of the kernel.
Many programs have now been written to exploit this vulnerability.
They really differ only in the particular function they code for.
The Adore LKM, for instance, allows the malicious user to become root;
hide and unhide files; execute commands as root; make a process ID (PID)
visible or invisible, or remove it forever (certainly a risky endeavor);
or uninstall the LKM entirely. Of course, with these simple (!) functions,
the malicious user has the run of the system, undetected. The Knark
LKM, covered in the lecture notes, has these capabilities and more, including
the capability to execute a command on a remote knark'd computer.
In the Solaris world, Plasmoid has developed the Solaris Integrated Trojan
Facility. This can hide files and processes, cover up directories
and file content, and place backdoors in hidden directories.
Potentially a much worse variant is the capability to create virus LKM's.
If a loadable kernel module contains a payload of viral code, it could
intercept system calls from any executable file and modify the file with
the payload. If a worm were to be designed as a loadable kernel
module, it could do tremendous damage (or collect unlimited information
from the servers it infects).
From the white hat standpoint, administrators with programming skills
could develop a loadable kernel module to monitor the system and prevent
foreign or unauthorized LKM's from being loaded. The ability to access
the kernel space allows for attacks, countermeasures, and counter-countermeasures
to take place. Plainly the field is ripe for development. Some
of the variants are described as follows:
Rubberhose Rubberhose (pronounced marutukku, for assorted reasons),
by Julian Assange et al, is available at http://www.rubberhose.org
and transparently encrypts data on a storage device, such as a hard drive,
and allows you to hide that encrypted data. It is written for Linux
and NetBSD, and is implemented as kernel modules and associated user space
programs. The code is alpha, at version 0.8.3. The authors
state, DO NOT TRUST THIS CODE, so it is not quite ready for the average
user. The stated purpose is to allow activists in repressive countries
hide sensitive data; however, it could easily be put to less beneficial
uses. Rubberhose has a number of security features which would make
it difficult to crack.
Pragmatic of THC (mentioned above) devoted some of his talents to the
FreeBSD operating system, and came up with some interesting ways to abuse
the kernel. FreeBSD kernel modules are written differently
from Linux kernel modules, but the principles remain the same. He
demonstrates how to hide files and processes, and most interesting, shows
code for a module that effectively allows a user to su without a password.
The module takes the PID of a process (in this case, the local user's shell),
and alters the owner's UID to zero root. To his credit, pragmatic
also suggests ways to thwart malicious FreeBSD kernel modules as well.
FreeBSD LKM's are now referred to as KLD's.
How the Exploit Works - Adore
Adore comes in several parts, which may be installed by hand or via
an included configure script. It has a three tier architecture
adore.c, the code for the actual kernel module itself; an intermediary
set of library routines, libinvisible.c, which is an upper layer to be
independent from implementation of kernel-hacks; and finally, ava.c, which
when compiled runs in user space and provides the user interface.
The configure script includes functions which generate variables, ELITE_CMD
and an ELITE_UID above 30, from which compiler flags are created and hard
coded into the executable binaries. There is also a HIDDEN_SERVICE
defined, which hides the backdoor listening port from netstat, but makes
it available to the hacker. Notably, the pre-generated Makefile.gen
includes a definition of this HIDDEN_SERVICE, but the configure script
overlooks it. Was this accidental? It seems too trivial to
be a deliberate breaking of the system, as addition of the field is certainly
within the skill level of all but the newest of newbie script kiddies.
Configure also looks for vital functions like currently loaded modules,
the presence of the insmod command, and the presence of SMP (Symmetric
Multi-Processing). The two main pieces are ava.c and adore.c.
A closer look at adore.c is in order.
Adore contains the crucial routines that make up the actual hack.
As it is loaded (via insmod) into the kernel, it temporarily replaces a
number of system calls with modified calls these are discussed below.
Adore gets some of its raw data about kernel processes from the /proc directory
in the Linux file structure. Remember that this filesystem is where
the kernel stores user space data about kernel processes. The files
in /proc are dynamically created as data from the kernel is generated.
When a user (or a user space program) accesses one of these files, the
data presented is a snapshot of the system at that moment.
From the man page for proc:
/proc is a file system
that provides access to the state of each process and light-weight process
(lwp) in the system. The name of each entry in the /proc directory is a
decimal number corresponding to a process-ID. These entries are themselves
subdirectories. Access to process state is provided by additional files
contained within each subdirectory
. The owner of each /proc file and subdirectory
is determined by the user-ID of the process.
Other raw data is captured by intercepting specific normal user input.
After a number of housekeeping and background routines, marking selected
processes for invisibility, adore gets to the heart of the matter.
To make a process disappear, adore runs a routine which changes the process
ID (PID) of all processes tagged by the user to zero. This has the
effect of rendering those processes invisible to the get_pid_list() call.
The get_pid_list() call returns a compressed list of process ID's for the
application using it. Before setting the PID to zero, the code takes
the vital step of saving the original, valid PID to a variable called exit_code.
This allows the reverse process (making the process visible again) possible.
Accomplishing these tasks involves replacing some normal system calls
fork(), clone(), and kill() with altered versions.
In a very different vein, adore hides files with action from both ava
and adore. It takes place in the binary ava via the libinvisible.c
code. Ava simply does a chown on the file to the ELITE_UID.
Adore checks if a file marked for hiding is owned by ELITE_UID, and then
uses a modified getdents() call to remove files from dirent (directory
entries).
For someone bent on maintaining access to a remote compromised system,
continued access is critical. This generally entails leaving a service
of some type listening on a defined port, with authentication methods of
the intruder's choice. However, this is extremely vulnerable to detection.
A system administrator need only type in netstat
a at the console to list all network connections and their
status. A strange service listening or established on an uncommon
port, say, 55,555 would (hopefully) trigger an investigation. This
is where the HIDDEN_SERVICE comes in. Adore contains a routine that
listens to user input, and searches all input strings for netstat.
If it finds the netstat
string, it then looks at the output for the HIDDEN_SERVICE string, which
in this case would be 55,555.
That specific line in the netstat
a return is stripped out, and the resulting altered data
written to the terminal (or other stdout). In this way, netstat can
be defeated without actually altering the netstat binary. All of
this is accomplished with a modified write() call. Stealth is quite
impressed with this particular innovation /*
Woa! We don't hide by read() but by write()!!! Groundbreaking new
and effective */. It is a nicely written
routine.
The actual rootshell backdoor is rather simple. With a modified
setuid() call, adore checks to see if the existing uid is ELITE_CMD.
If so, it sets the current uid (user id), euid (effective user id), gid
(group id), egid (effective group id), suid (set user ID, a flag that allows
a program to execute with the privileges of the pertinent user, usually
root), sgid (set group id), fsuid and fsgid (same as suid and sgid, applied
to filesystems), to zero. Zero, you will recall, identifies root.
Capabilities (mechanisms providing fine grained controls over the privileges
of a process) are also set to zero. The intruder is now root.
If adore is unloaded from the kernel (via rmmod), it restores the normal
system calls, leaving no trace of itself.
Conspicuously missing from adore is a specific method for remote entry
an actual service to listen for the returning intruder. Such a
service could be written in to the code, but there is a better way, described
below in the section entitled How to Use the Exploit.
Ava (when used with the appropriate switch) calls various functions
which are defined in libinvisible.c. These in turn make use of the
altered system calls discussed above. Note: the returns for
all of the ava options include a failure mode, adding to the robustness
of the code. For instance: to hide a process with PID = 493,
an intruder would type in ava i 493.
Within the ava routine, this would call the adore_hideproc routine.
This in turn is defined in libinvisible.c, and returns results of the altered
kill() system call, which hid the PID. With this data, ava prints
out the result either it hid the process or not.
Ava is available to anyone with the appropriate rights presumably
root, since it was compiled by root. Therefore, it should itself
be hidden when the intruder is not making use of it.
Diagram
Using the Adore Rootkit and Maintaining a Backdoor for Continued Access
How to Use the Exploit
Use the configure script to generate the Makefile, or use the hand-edited
pre-existing Makefile. If using the configure script, add a routine
to generate a HIDDEN_SERVICE or edit the generated Makefile to include
it. Be sure to change any default values. Run make. Make
triggers compilation of ava.c and libinvisible.c together into a single
binary, ava. Adore is compiled into adore.o, a loadable kernel module.
Dummy.c and cleaner.c are also compiled (both the generic Makefile and
the configure script neglect to include the o switch and a named output
binary, which in these cases would be file.o). These are also loadable
kernel modules, used in housekeeping functions. Now, use the startadore
shell script to bootstrap adore. The adore module will not show up
in lsmod output when installed this way, because the cleaner.o module is
also loaded, hiding the adore.o module. Cleaner.o is then removed.
Ava is then used with the appropriate switches to tell the adore module
to hide/unhide files and processes, and execute commands as root.
Again, adore did not include a method for the intruder to return to
the scene of the incident and easily get back in. This should be
accomplished with Netcat, the Swiss Army knife of network utility tools.
The Unix version was written by Hobbit, and it was ported to Windows NT
by Weld Pond of L0pht. Netcat reads and writes data across network
connections, using the TCP or UDP protocol. It has a number of features
which make it a highly capable backdoor. One of those features is
the capability to bind to any local port. Another is the ability
to act as either a network client or a server. If the source code
is compiled with -D_GAPING_SECURITY-HOLE, the e argument to netcat specifies
a program to execute after making or receiving a successful connection,
similar to the Unix inetd daemon. These (and others) make it a
powerful tool.
After installing adore and ava, bring netcat onto the compromised machine.
Bind it to the HIDDEN_SERVICE port, in a listening mode. Set netcat
to spawn a shell when a client (the intruder) connects. The command
for this would be:
nc l p 55555 e /bin/sh
nc calls netcat; -l p 55555 sets it listening on port 55555 (our sample
HIDDEN_SERVICE); -e executes the /bin/sh binary, which spawns a shell.
With ava, hide the nc process and file. The intruder is now free
to use ava/adore at his/her leisure, which means complete undetected access
to the compromised machine. Adore itself is only the means to an
end; it is up to the intruder to supply code or data to be used on the
compromised machine. Of course, an astute system administrator would
eventually notice the loss of bandwidth and hard drive space resulting
from serious abuse, but a careful intruder might go unnoticed indefinitely.
The most obvious indicator of netcat's presence would be an outside port
scan revealing an open high port. Many port scanners will not test high
ports unless specifically configured to do so, due to the time involved
in scanning every possible port on a particular host especially when
there are hundreds or even thousands of machines on a network.
Signature of the Attack
The first sign to look for is any evidence of a root compromise.
These would be unexplained bandwidth usage, especially during off hours;
unexplained loss of disk space; strange syslog entries (adore does not
address cleaning the logs); directories with names like
; and of course
many others. If such evidence is found, the system administrator
should go into Incident Response mode. A decision must be made
look for evidence of the rootkit and eliminate it, or nuke the hard drive,
reload the OS (with all security patches) and restore the data from backup?
Although ultimately these modules might be detected, the effort involved
could easily exceed the time and effort required to reformat the hard drive
and reinstall the OS from the source disk and the data from backups.
You did backup your data, of course.
A scanner capable of detecting Adore versions 0.14, 0.2b, and 0.24,
and Knark version 0.59, has been written by Stephane Aubert. It should
also work on the latest version of Adore, 0.31. It scans for all
possible values of ELITE_CMD in Adore and KNARK_GIMME_ROOT in Knark.
Obviously it would be a simple matter to write functionality into the kernel
modules to detect this type of activity, so the utility of this scanner
is likely to be short-lived. The author of rkscan recognizes this,
and even shows some ways his scanner can be defeated.
Since all of the intruder's processes and files may be hidden, along
with the kernel module itself, there is no obvious signature of the module
activity. A quiet hacker might escape detection for a very long time,
potentially capturing tremendous amounts of data. Therefore, any
root compromise must be assumed to involve loadable kernel modules, even
if evidence of other rootkits is found the non-module rootkit could itself
be a ruse, designed to throw the Incident Response team off the trail.
Chkrootkit is a tool designed to look locally for signs of a rootkit.
It includes chkproc.c, which specifically looks for signs of loadable kernel
module rootkits. It does this by comparing the output of the ps
command with the contents of the /proc contents. The author does
not list Adore among the rootkits it detects. I was not able to test
the functionality of chkrootkit vs. Adore.
How to Protect Against It
The most obvious way to protect against this type of attack is to disable
loadable kernel modules, and build your kernel with all necessary functions
integrated. This has the effect of severely restricting the system
administrator's options. For instance, given that many device drivers
are implemented as kernel modules, upgrading hardware might also involve
recompiling the kernel if the driver could be found in the proper format.
This would require rebooting the computer sometimes difficult in high
availability environments.
On the topic of rebooting, we come to a weakness in the adore concept.
If the host is rebooted, adore is no longer part of the kernel. It
does not load itself. So, if the intruder wishes to retain access
after a reboot, he/she must write a script to be run during the boot process
which will load the module, hide it, and start netcat (or whatever method
is used to maintain a listener on the HIDDEN_SERVICE port). This
script would then have to be hidden possibly by hard-coding that function
into the adore kernel module itself. However, this script would have
to be called by one of the standard init scripts, or one of the other initialization
functions. If these are properly fingerprinted in advance, the change
would show up the next time the fingerprints are compared. The counter-countermeasure
to this might be to identify a kernel module that normally is loaded at
boot time, and replace it with a trojaned version.
A utility called lcap may provide some protection. Lcap takes
advantage of the same concept of capabilities that adore does (described
above). It allows a system administrator to remove specific capabilities
from the kernel. One of those capabilities is to allow/disallow loading
kernel modules. Therefore, lcap could make it impossible to load
Adore, or Knark, or any other kernel module after boot time. Of course,
since our attacker has to have root already to load a kernel module, he/she
might discover lcap by typing lcap
h, and use the command to allow loading kernel modules.
Pragmatic of THC (discussed above) suggests building, loading, and hiding
a kernel module which checks module loading to see if the new module is
coming from a secure directory. Authentication could be as simple
as a password (pragmatic's implementation), or have the added security
of a strong encryption algorithm and a private key stored on a smart card.
In the long run, if loadable kernel modules are to remain as viable
options, it will be necessary to implement some type of cryptographic protection.
The kernel will have to recognize itself and legal modules via standard
techniques like hash based signatures; perhaps even a public key implementation,
in which legal modules are compiled with a certificate containing a private
key; the kernel holds the matching public keys. The kernel would
encrypt random data with the public key; if the module can decrypt it properly,
it would be allowed to load; otherwise, it would be rejected and an alert
sent to the administrator. The original private key would be maintained
offline, either in a smart card or on secure read-only media. Of
course, this would significantly add to the complexity of kernel module
programming.
Source Code/Pseudo Code
Ava.c / libinvisible.c
- Default: print options
- Check Adore version
- Read switch; trigger appropriate action
h : hide a file
u : unhide a file
i : hide a process ID
v : unhide a process ID
r : execute a command as root
R : remove process ID forever (not recommended)
U : uninstall Adore
- Else: print Did nothing or failed
Adore.c
- During module loading - replace system calls with altered versions
[getdents(); kill(); write(); fork(); clone(); setuid()]
- Check if PID is flagged to be hidden with the PF_INVISIBLE flag
- Check if file is hidden
- If called, mark a PID to be hidden
- If called, remove a process forever
- Make a process invisible by setting PID to zero, storing original
PID in variable exit_code
- Make a process visible by restoring the PID from exit_code
- Remove files from dirent (directory entries) to hide them
- Fork (create a new process). The child process inherits the
PF_INVISIBLE flag
- Prevent other, illegitimate signals from reaching the kernel
module
- Look for netstat in user strings; if it exists, look for the HIDDEN_SERVICE
port in the output; strip it out so the listening port remains
secret
- In the altered setuid() call,use the ELITE_CMD to trigger conversion
to root; in the same routine, use other instances of ELITE_CMD to uninstall
adore and check if adore is present. If no ELITE_CMD, carry out the
normal setuid() call
- During module unloading restore altered system calls with standard
versions
Additional Information
The aspiring kernel hacker will want to begin with The Linux Kernel,
by David Rusling (http://www.linuxdoc.org/LDP/tlk/tlk.html).
This online publication is a bit dated last updated in 1999, with kernel
version 2.0.33. However, it goes into good detail understandable
by non-programmers, and it covers all aspects of kernel functioning.
For those more adventuresome, and with good programming skills, the next
in line to read is the Linux Kernel Module Programming Guide, written in
1999 by Ori Pomerantz (http://www.linuxdoc.org/LDP/lkmpg/mpg.html).
Beginning with the infamous Hello World example, it is an overview of
the issues involved in kernel programming, but does not go into great depth.
Pomerantz also has a book of the same name, published in August 2000, available
from Amazon.com (naturally). For those who wish to get a little deeper
in, plaguez wrote an article for Phrack in January 1998, Weakening the
Linux Kernel (http://phrack.infonexus.com/search.phtml?view&article=p52-18).
This is more concerned with programming examples to redirect system calls
to newly written system calls within the loadable kernel module and force
the kernel to do their bidding. It includes alpha code for an lkm
backdoor, the Linux Integrated Trojan Facility. Another Phrack article,
a bit more recent (1999) and written by kossak and lifeline (http://phrack.infonexus.com/search.phtml?view&article=p55-12),
goes into the possibilities of using lkm's to affect the network layer
for example, a kernel layer packet sniffer. And, last but not least,
intelligent questions about kernel module programming will frequently receive
intelligent answers on the many newsgroups available dedicated to Linux
(and other OS's as well).
Given the power of the loadable kernel module as a malicious tool, it
is somewhat surprising that the body of literature is so small and, in
general, outdated. The papers and tools listed in this paper pretty
much cover the entire field. This may indicate that the relative
difficulty of writing a workable kernel module rootkit that a.) works,
and b.) doesn't crash the system. Undoubtedly there are many programmers
with the requisite skills; however, these programmers might not be the
types who publish exploits on web sites and news groups. In short,
these exploits may be out there, but not publicized outside of small groups.