A Guide to Kernel Exploitation Attacking the Core [Paperback]
465 pág.

A Guide to Kernel Exploitation Attacking the Core [Paperback]


DisciplinaLinux714 materiais1.843 seguidores
Pré-visualização50 páginas
is
generally a good way), different subsystems may also be identified through what they \u201cexport\u201d to
user land. We will see this on a case-by-case basis through the rest of the chapter.
EThis convention is also generally followed by nondistribution patches. For example, a grsecurity
patched kernel will show up as \u2013grsec (e.g., 2.6.25.10\u2013grsec).
108 CHAPTER 4 The UNIX Family
For long time the Linux kernel has not come with a default in-kernel
debuggerF and thus a few different approaches have traditionally been used and
mixed together to perform some rudimental debugging. Since some of these
approaches might still come in handy (for example, when just a quick check is
needed), we start our analysis from there.
The most classic and simplest form of debugging is the print-based approach.
Linux offers a function, printk(), which behaves much like printf() and allows you
to print a statement to user land from within kernel land. As a plus, printk() is inter-
rupt-safe and can thus be used to report values within the unfriendly interrupt context.
int printk(const char *fmt, \u2026)
printk(KERN_NOTICE "log_buf_len: %d\n", log_buf_len);
In the preceding code snippet, you can see the prototype of the function and a
typical usage example. KERN_NOTICE is a static value that defines the debug level,
that is, where and if the specific message will be pushed out (local console,
syslog, etc.). Linux defines eight different levels, ranging from KERN_EMERG
(highest priority) to KERN_DEBUG (lowest priority).
#define KERN_EMERG &quot;<0>&quot; /* system is unusable */
#define KERN_ALERT &quot;<1>&quot; /* action must be taken immediately */
#define KERN_CRIT &quot;<2>&quot; /* critical conditions */
#define KERN_ERR &quot;<3>&quot; /* error conditions */
#define KERN_WARNING &quot;<4>&quot; /* warning conditions */
#define KERN_NOTICE &quot;<5>&quot; /* normal but significant condition */
#define KERN_INFO &quot;<6>&quot; /* informational */
#define KERN_DEBUG &quot;<7>&quot; /* debug-level messages */
KERN_WARNING is the default level if nothing is specified. The printk() approach
is simple to use. All you need to do is modify the kernel sources, introducing the
printk() lines where necessary, and recompile. Its simplicity is also its major
strength. Despite looking rather rudimentary, it is surprisingly effective (a few of
the exploits in this book were originally worked out just through the use of print-
based debugging) and it is usable on any kernel (not only Linux) of which you
have access to the source. The main drawback is that it requires a recompilation
and a reboot each time you want to add a new statement and see it in action.
Although rebooting a few times may be acceptable (but not optimal) during
exploit development, it clearly does not \u201cscale\u201d for more extensive debugging (or
for debugging on a remote machine). To overcome this limitation, Linux kernel
developers introduced the kprobes framework. Documentation/kprobes.txt in the
kernel source tree contains a detailed description of what kprobes are, how they
work, and how we can use them. Quoting from the document1:
Kprobes enables you to dynamically break into any kernel routine and
collect debugging and performance information non-disruptively. You
FBoth KDB and KGDB have, for long time, been external patches.
The Members of the UNIX Family 109
can trap at almost any kernel code address, specifying a handler
routine to be invoked when the breakpoint is hit.
There are currently three types of probes: kprobes, jprobes, and
kretprobes (also called return probes). A kprobe can be inserted on
virtually any instruction in the kernel. A jprobe is inserted at the entry
to a kernel function, and provides convenient access to the function's
arguments. A return probe fires when a specified function returns.
In the typical case, Kprobes-based instrumentation is packaged as a
kernel module. The module's init function installs (&quot;registers&quot;) one or
more probes, and the exit function unregisters them. A registration
function such as register_kprobe() specifies where the probe is to be
inserted and what handler is to be called when the probe is hit.
The general idea is that we can write a module and register specific handlers
(functions) that will then be called whenever our probe gets hit. Although kprobes
allow for flexibility in that virtually any address can be associated with a pre- and
post-handler, most often we will find that all we are really interested in is the
state on function entry (jprobes) or exit (kretprobes). The following code shows
an example of a jprobe:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/kprobes.h>
#include <linux/kallsyms.h>
static struct jprobe setuid_jprobe;
static asmlinkage int
kp_setuid(uid_t uid) [1]
{
printk(&quot;process %s [%d] attempted setuid to %d\n&quot;, current->comm,
current->cred->uid, uid);
jprobe_return();
/*NOTREACHED*/
return (0);
}
int
init_module(void)
{
int ret;
setuid_jprobe.entry = (kprobe_opcode_t *)kp_setuid;
setuid_jprobe.kp.addr = (kprobe_opcode_t *)
kallsyms_lookup_name(&quot;sys_setuid&quot;); [2]
if (!setuid_jprobe.kp.addr) {
printk(&quot;unable to lookup symbol\n&quot;);
return (-1);
}
110 CHAPTER 4 The UNIX Family
if ((ret = register_jprobe(&setuid_jprobe)) <0) {
printk(&quot;register_jprobe failed, returned %d\n&quot;, ret);
return (-1);
}
return (0);
}
void cleanup_module(void)
{
unregister_jprobe(&setuid_jprobe);
printk(&quot;jprobe unregistered\n&quot;);
}
MODULE_LICENSE(&quot;GPL&quot;);
As we mentioned earlier, our jprobe (and kprobesG in general) lives inside a
kernel module, which uses the register_ jprobe() and unregister_ jprobe()
functions to place the probe in memory and activate it. Our probe is described by
a jprobe struct, which is filled with the name of the associated probe handler
(kp_setuid) and the address of the target kernel function. In this case, we use
kallsyms_lookup_name() [2] to gather the address of sys_setuid() at runtime,
but other approaches such as hardcoding the address, dumping it from vmlinuz,
or gathering it from System.map would work equally well. All the jprobe cares
about is a virtual address.
At [1], we prepare our handler. Note that for jprobes we have to reflect the
exact signature of our target function. In this case, it is especially important to
utliize the asmlinkage tag to correctly access the parameters passed to the function.
Here we use a very simple handler, just to show how we can access global kernel
structures (e.g., current) and local parameters (uid). All jprobes must finish with a
call to jprobe_return().H
Now that we have our code ready, it is time to test it. We prepare a simple
makefile:
obj-m := kp-setuid.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
clean:
rm -f *.mod.c *.ko *.o
GIn this case, we use the term kprobes to refer to the base framework.
HThis is necessary to restore the correct stack and registers for the original function and is due to
the way jprobes are implemented. Interested readers can find more details about the implementation
of the kprobes framework in the aforementioned Documentation/kprobes.txt file.
The Members of the UNIX Family 111
We also prepare some very simple testing code that invokes sys_setuid():
int main() {
setuid(0);
}
And we are ready to go:
linuxbox# make
make -C /lib/modules/2.6.31.3/build SUBDIRS=/home/luser/kprobe mod
make[1]: Entering directory '/usr/src/linux-2.6.31.3'
CC [M] /home/luser/kprobe/kp-setuid.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/luser/kprobe/kp-setuid.mod.o
make[1]: Leaving directory '/usr/src/linux-2.6.31.3'
linuxbox# insmod kp-setuid.ko
linuxbox#
[\u2026]
linuxbox# gcc -o setuid-test setuid.c
linuxbox# ./setuid-test
linuxbox# dmesg
[\u2026]
[ 1402.389175] process master [0] attempted setuid to -1
[ 1402.389283] process master [0]