Friday 21 June 2013

A Linux rootkit tutorial - Makefile and symbols

We're going to use the classic method of loading our rootkit as a device driver.  The reason this is used so often is that it's an established method of granting ring-0 access to code.  However - we don't really want to be a device driver (indeed for this reason I'm going to refer to the rootkit as a module from now on), and indeed loading ourselves as one will create various issues and could leave behind an irremovable audit trail (see load_module in kernel/module.c in the kernel source code for more).  Later we'll look at other methods we could use - which assume we can get ring-0 some other way - normally due to a device driver bug.

To start with however, we're going to go through the method required to build the rootkit at https://github.com/nnewson/km.git and why it's set-up that way.  I'm not however, going to go in-depth into how a basic module is built and how it works.  You can get that here - http://lwn.net/Kernel/LDD3/ - in the second chapter.  It does a much better job that I could.

One thing I will quickly discuss is that the Makefile we use will actually call the kernel's Makefile, which will set up the environment, and then call our Makefile to retrieve some variables.  It's using these variables that the kernel's Makefile builds the module for us.

These means we're somewhat limited by what we can do without Makefile - as it's almost like it's a include file rather than a proper Makefile.  For instance, most module Makefiles on the Internet explicitly name each source file - mainly because the wildcard pattern doesn't work when the kernel Makefile calls back into our one.  I get around this by explicitly using the ls shell command to find all the .c files.  It saves us having to edit the file every time we add a new compilation unit.

The Makefile has one real build option (that calls the kernel Makefile) and two phony ones.  The first is the standard 'clean' entry - which again calls the kernel Makefile to handle the actual clean.  The second is more interesting - it's called symbols.

Building Symbols
Before I discuss what building symbols does, it's important to describe why we need to do this at all. We're going to be using a lot of functions and global variables from the kernel - and most of the time these will be exported.  This means we refer to them by name (i.e. symbol) in our built module, and when it's loaded these are mapped to the actual addresses of the function or global variable by looking up these symbols.

Not all functions will be exported though.  Static ones certainly won't - indeed by default the kernel build system will only export symbols which are explicitly registered as such - using the EXPORT_SYMBOL/EXPORT_SYMBOL_GPL define.  So anything that isn't registered as such isn't going to be available to us - either our module will fail to build, or it simply won't load.

However - these unexported symbols are available to us - via the System.map file (stored in your /boot directory).  This file details the location and name of all the function and global variables (amongst other things) that the kernel uses.  As such we can grep through this file to discover memory address as a function.  For instance if we use the following command (which we need to sudo because we want to read from /boot):

sudo grep -w sysfs_mutex /boot/System.map-$(uname -r)

We get the following:

ffffffff81c5d940 D sysfs_mutex

This means the sysfs_mutex global variable is actually at memory address 0xffffffff81c5d940.  Of course this is only true for this kernel build - if you update or rebuild your kernel, you'd have to perform this check again.  We don't need to rebuild this for changes to our module however.

What this means is that using awk we can extract that memory address into a Makefile variable - which you see here:

SYSFS_MUTEX_SYMBOL = $(shell grep -w sysfs_mutex /boot/System.map-$(shell uname -r) | awk '{print $$1}')

This variable can then be placed in a file to be used by the code as the location of the sysfs_mutex variable.  So after looking up a number of various symbols, the code used sed to process a symbols_template.h file, and replace specific tokens with the addresses we looked up.  So building symbols should give us a symbols.h file in our src directory.

sudo make symbols

With this done you should now see the resulting entry in the symbols.h file as:

#define      c_sysfs_mutex_symbol  ffffffff81c5d940

This can now be assigned to a pointer - meaning we now have a pointer to the sysfs_mutex variable - and can access it as though it were exported.

static mutex*   g_sysfs_mutex = ( mutex* ) c_sysfs_mutex_symbol;

So, to finalise, to build the module the first time you need two Make commands:

sudo make symbols
make

Wednesday 19 June 2013

A Linux rootkit tutorial - an introduction

I haven't really done a lot with this blog since starting it - mainly because I became swamped with other things. However, I recently visited a webpage from Volatility Labs detailing detection of the KBeast rootkit.

Well - challenge accepted. I'm going to provide a tutorial on how to create a Linux rootkit that can hide from the methods detailed in the page above - and show you how we interact with the kernel to accomplish this.  All the code featured will be made publish on github here: https://github.com/nnewson/km

We'll go through it in various stages:

  1. Create our build environment.
    This will involve detailing the creation of a very basic device driver and the Makefile that will produce it.  It'll also go into loading and unloading the driver.  This is important - as this is a development project any change made by the rootkit should be reversible and allow you to unload it and return your system to it's previous state.
  2. Generate a symbols file.  
    While some of the symbols (functions, global variables etc.) are exported by the kernel, most aren't - and we're going to need a few of those to ensure we activate and deactivate our functionality in the correct way.  As such we're going to have to look some of these symbols up.  I'll show you how you can do this and integrate this process into the Makefile to auto-generate a symbols header file.
  3. Add a basic /proc control entry.
    For the sake of simplicity we're going to add a simply entry to allow us to pass commands to rootkit, allowing us to hide or show it when we want to.  Later we'll change this to something more covert, but for ease of understanding this will do for now.  This will provide a basic level of 'command and control'.
  4. Hide our rootkit from lsmod, /proc/modules and /sys/module.
    Our rootkit should be able to hide itself from this listing mechanisms (I'll also explain why our rootkit appeared in these lists in the first place).  Of course it should also be able to reappear in these list - so we can properly unload it as a normal device driver.
  5. Provide the ability to hook various function calls.
    Once a rootkit is hidden, one the most obvious things for to do is to intercept or filter functions calls.  It's this mechanism that allows you to add the hidden functionality of the rootkit.  The hooking needs to be done in such a way as to minimise detection without causing an exceptions. Again - we should be able to roll-back any changes made.
  6. Use a covert network communication channel.
    Once we've got the system hidden and doing work, the next obvious thing is to provide an ability to communicate with a remote source - for command and control.  This will allow us to take instructions and export any date collected.
The code on github already provides the first four stages, and we'll be adding to this as we go along - but each stage will be detailed in turn and we'll build on them one step at a time.

In the next update, I'll write about what a rootkit actually is, why we use the insmod command to load it a device driver (and the various problems this creates) and how we'd ideally like to do this.

Hopefully, eventually I'll be able to get around to talking about the really interesting stuff - namely covertly loading our rootkit, remotely providing code updates and patching as well as dealing with anti -forensics.

All the work for this will be done on the latest kernel on the development machine I'm using - namely 3.8.0-19.