About four months ago I developed a reliable exploit for vulnerability CVE-2008-3531, which is also addressed in the advisory FreeBSD-SA-08:08.nmount. In this post I will use this vulnerability to provide an overview of the development process for FreeBSD kernel stack exploits.

CVE-2008-3531 is a kernel stack overflow vulnerability that affects FreeBSD versions 7.0-RELEASE and 7.0-STABLE, but not 7.1-RELEASE nor 7.1-STABLE as the CVE entry seems to suggest.

The first step of the exploit development process involves identifying the vulnerability’s conditions and assessing its impact.

In line 1833 sprintf() is used to write an error message to a locally declared static buffer, namely errmsg declared in line 1804 with a size of 255 bytes. The variable p used in sprintf() is a pointer to the mount option’s name. Conceptually a mount option is a tuple of the form (name, value). The vulnerable sprintf() call can be reached from userland when p‘s (i.e. the mount option’s name) corresponding value is invalid, but not NULL (due to the checks performed in the first TAILQ_FOREACH loop). For example, the tuple (“AAAA”, “BBBB”) satisfies this condition; the mount option’s value is the string “BBBB” which is invalid and not NULL therefore p would point to the string “AAAA”. Both the mount option’s name (p) and the mount option’s value are user-controlled. This allows the overflow of the errmsg buffer by supplying a mount option name of arbitrary length and as we will see below, less importantly in this case, arbitrary content. Since errmsg is on a kernel stack, we can use the overflow to corrupt the current stack frame’s saved return address with the ultimate goal of diverting the kernel’s execution flow to code of our own choosing.

Now that we have explored the conditions and concluded that we can indeed achieve arbitrary code execution we have to explore the ways we can trigger the vulnerability. There are many possible execution paths to reach vfs_filteropt() from userland. After browsing FreeBSD’s file system stacking source code for a couple of minutes I decided to use the following:

nmount() -> vfs_donmount() -> msdosfs_mount() -> vfs_filteropt()

By default on FreeBSD the
nmount(2) system call
can only be called by root. In order for it to be enabled for unprivileged users the
sysctl(8) variable vfs.usermount must be set to a non-zero value.

At this point we know that the vulnerability can potentially lead to arbitrary code execution and how to trigger it. The next step is to find a place to store our arbitrary code and divert the kernel’s execution flow to that memory address. Due to the structure of the format string used in the sprintf() call, we do not have direct control of the value that overwrites the saved return address in vfs_filteropt()‘s kernel stack frame.

However, indirect control is more than enough to achieve arbitrary code execution. When p points to a string of 248 ‘A’s followed by NULL (i.e. 248 * ‘A’ + ‘\0’) the saved return address is overwritten with the value 0x6e776f, that is the “nwo” of “unknown” in the sprintf()‘s format string. Using the exploitation methodology of kernel NULL pointer dereference vulnerabilities, we can use
mmap(2) to map memory at the page boundary 0x6e7000. Then we can place our arbitrary kernel shellcode 0x76f bytes after that. Therefore, when the corrupted saved return address with the value 0x6e776f is restored into the EIP register the kernel will execute our instructions that have been mapped to this address.

The next step in the exploit development process is to write these instructions. Specifically, our kernel shellcode should:

locate the credentials of the user that triggers the vulnerability and escalate his privileges,

ensure kernel continuation. In other words, the system must be kept in a running condition and stable after exploitation.

User credentials specifying the process owner’s privileges in FreeBSD are stored in a structure of type ucred defined at src/sys/ucred.h:

The address of the proc structure can be dynamically located at runtime from unprivileged processes in a number of ways:

The
sysctl(3)kern.proc.pid kernel interface and the kinfo_proc structure.

The allproc symbol that the FreeBSD kernel exports by default.

The curthread pointer from the pcpu structure (segment FS in kernel context points to it).

You can find more information about the first alternative in the
talk I gave on FreeBSD kernel stack overflows at the University of Piraeus Software Libre Society, Event #16: Computer Security (unfortunately the slides from the talk are only available in Greek currently). The second alternative will be the subject of a future post. In the developed exploit I will use the third alternative.

The other task that our shellcode should perform is to maintain the stability of the system by ensuring the kernel’s continuation. One way to approach this would be to port Silvio Cesare’s “iret” return to userland approach (presented at his “Open source kernel auditing and exploitation”
Black Hat talk) to FreeBSD. Although a full investigation of Silvio’s “iret” technique on FreeBSD would be very interesting, it is beyond the scope of this post.

In order to successfully return to userland from the kernel shellcode I will use another approach. Remember that the execution path I decided to take is nmount() -> vfs_donmount() -> msdosfs_mount() -> vfs_filteropt(). After the shellcode has performed privilege escalation it could return to where vfs_filteropt() was supposed to return, that is in msdosfs_mount(). However that is not possible since msdosfs_mount()‘s saved registers have been corrupted when vfs_filteropt()‘s stack frame was smashed by the overflow. The values of these saved registers cannot be restored, consequently there is no safe way to return to msdosfs_mount() after privilege escalation. The solution I have implemented in the exploit bypasses msdosfs_mount() completely and returns to the pre-previous from vfs_filteropt() function, namely vfs_donmount(). The saved registers’ values of vfs_donmount() are uncorrupted in msdosfs_mount()‘s stack frame. To make this more clear, consider the following pseudocode that is based on the relevant deadlisting part:

In the talk I explored in detail the process of exploiting kernel stack overflows in the 7.0 production release of the
FreeBSD operating system. There were extensive examples and live experimentation, all of which are unfortunately not reflected in the supporting slides. The main contribution of the study is the development of a kernel exploitation algorithm and the presentation of comprehensive i386 kernel shellcode. Although the focus was on the then most current production release of FreeBSD (7.0), the presented algorithm and methodologies are applicable to both the latest stable (7.1) and current (8.0) FreeBSD versions.

The FreeBSD kernel can be debugged with the
ddb(4) interactive kernel debugger. Although the latest production release of FreeBSD (7.1 at the time of this writing) adds some
very useful features, ddb is still lacking the flexibility of
gdb.

The FreeBSD developer’s handbook has a section on
kernel debugging using remote gdb, but it is not directly applicable to VMware-based installations. The solution is to use VMware’s feature of creating virtual serial ports as named pipes to emulate a serial connection between two FreeBSD virtual machines.

The first FreeBSD virtual machine, let’s call it the target host to follow the handbook’s terminology, is the machine that the experimental kernel code runs on. The second, the debugging host, is the one that will run gdb and connect over the virtual serial connection to the target host. The target host’s kernel needs to be compiled with the following options:

makeoptions DEBUG=-g
options GDB
options DDB
options KDB

Furthermore, the serial port needs to be defined in the device flags in the /boot/device.hints file of the target host by setting the 0x80 bit, and the 0x10 bit for specifying that the kernel gdb backend is to be accessed via remote debugging over this port:

hint.sio.0.flags="0x90"

Also, edit the target host’s /etc/sysctl.conf file to include the following self-explanatory kernel parameters:

debug.kdb.current=ddb
debug.debugger_on_panic=1

After the compilation and installation of the new kernel on the target host, the /usr/obj/usr/src/sys/TARGET_HOST directory (assuming you have named the new kernel TARGET_HOST) needs to be copied to the debugging host (for example with scp -r).

For the following steps both virtual machines need to be turned off. In VMware go to the tab of the target host, click Edit virtual machine settings->Add->Serial Port->Output to named pipe. Enter /tmp/com_1 (or whatever you want) as the named pipe, select This end is the server and The other end is a virtual machine. Then perform the same steps on the debugging host’s virtual machine, enter the same named pipe, but select This end is the client in this case. The
/tmp/com_1 named pipe on the machine that runs VMware (Linux in our case) will be used as a virtual serial connection between the two FreeBSD guests.

Now power on the target host normally, cause a kernel panic or start the kernel debugger manually, and type gdb and then s:

On the debugging host you need to find the device that corresponds to the virtual serial port you defined in VMware. On our setup it is
/dev/cuad0. Then start a kgdb remote debugging session in the /usr/obj/usr/src/sys/TARGET_HOST directory, passing as arguments the serial port device and the kernel to be debugged:

My contribution to Phrack issue #66 is an
article
on exploiting FreeBSD's kernel memory allocator, or UMA - the universal memory
allocator. The initial inspiration to work on this subject came to me from
signedness.org challenge #3 by karl, therefore I felt it was the right
thing to do to add karl as a co-author.

Stack-smashing detection and protection for the kernel has
been enabled
by default in the latest snapshot of FreeBSD 8.0-CURRENT
(200811).
This was accomplished by utilizing the incorporation of
SSP
(also known
as ProPolice) in gcc version 4.1 and later (the 200811
snapshot uses gcc
4.2.1).

Specifically, src/sys/kern/stack_protector.c,
which is
compiled with gcc's -fstack-protector option,
registers
an event handler that generates a random canary value (the
``guard'' variable
in SSP terminology) placed between the local variables and
the saved frame
pointer of a kernel process's stack during a function's
prologue. When the
function exits, the canary is checked against its original
value. If it has
been altered the kernel calls
panic(9)
bringing down the whole system, but also stopping any
execution flow redirection
caused by manipulation of the function's saved frame pointer
or saved return
address.

In contrast to StackGuard and StackShield (or even
Microsoft's /GS), SSP has
been effective against attacks aiming to directly bypass it.
This relates to
research
I have done in the near past on the subject of kernel
stack-smashing attacks.
However, SSP can be indirectly bypassed by several methods,
for example heap
overflows, integer and/or signedness vulnerabilities, and
stack overflows on
buffers smaller than 8 bytes, among others.

The majority of published papers and articles on the area of information
security use the terms privilege and permission
interchangeably. Even
Wikipedia's
entry on privilege seems to follow this practice. However, one of the
foundations of dynamic trust management is the clear distinction between an
entity's privileges and its permissions.

A privilege is an authority given to an entity that approves a specific
operation on a specific resource. For example, an entry in an Access Control
List (ACL) specifies a privilege, not a permission. A permission, on the other
hand, is a value reached when an entity's privileges, as well as other of its
attributes, are evaluated. Therefore, the fact that an entity has been granted
a privilege does not necessarily mean that it is able at a given time to perform
the specified operation on the specified resource.

The dynamic trust management system
æther
I have designed and implemented as part of my Ph.D. provides an example of using
this distinction between privilege and permission in practice.

Last week I was experimenting with various changes to
OpenWrt Kamikaze version
7.09 on my
Linksys
WRT54GL wireless router. The objective was to modify
the Kamikaze firmware
for WRT54GL in order to implement a rogue access point for
use in various
penetration testing contracts. I decided to start the whole
endeavor since the
Airsnarf
Rogue Squadron
firmware only supports the WRT54G model. After a lot of
successful firmware
flashings during testing, I eventually (and perhaps
unavoidably) flashed my
router with a corrupted firmware. The result was a dead
WRT54GL that was not
replying to pings, not even after a hard reset.

To resurrect it I followed
void
main's
WRT54G revival guide. Although the guide was written
for the WRT54G model,
it is mostly applicable to WRT54GL as well. One of the main
differences is that
I had to short pins 16 and 17, not 15 and 16 (see the
photograph):

A rather important tip is that right after a successful
flashing you should
always enable the boot_wait NVRAM option in
order to be able to
use the TFTP bootloader. This will save you a lot of time
if you are in the
edit-compile-upload firmware-debug cycle.