Fusion level02 is considerably more challenging than the previous two levels. This post details my thought process and steps involved in writing the exploit and developing a Metasploit module.
The server accepts messages of two types, an encrypt message and a quit message. The quit message is a single character ‘Q’. The encrypt message is a type-length-value (TLV) format.
| 'E' | length | data |
The program stores the data from the encryption message into a fixed size buffer. The length specified in the message is how many bytes are copied.
A stack buffer overflow occurs if the message has a bigger size than the buffer. Specifially a length greater than 32*4096. The overflow will overwrite the return address of the encrypt_file function frame. Note that the data is encrypted before return address is used.
Building The Client
The server is listening for commands on port 20002:
Create an empty Metasploit module and update the register_options array with the IP of your local fusion VM. I used the following path:
The Ruby recv method accepts a maximum length of bytes, but the client needs to read in all data before trying to send a message. below is a buffered recv that will keep reading data until the full message is consumed. I created a second convenience function to send the encrypt command.
Fill the exploit method to send an encrypt command then quit.
The module successfully encrypts some data.
Retrieving The Key
The server uses a XOR cipher to encrypt the data. The key can be retrieved using the following three steps:
Generate some data: A
The program will xor the input with a key: A ^ K
Xor the encrypted data with the input: A ^ A ^ K = K
Before retrieving the key we will need to know the key size. Attach gdb to the server and print the size of the key buffer:
The encryption key is 32 integers long or 128 bytes.
Like the previous levels this program will fork a new process for each connection, make sure to enable follow-fork-mode child. While creating the exploit we will be attaching gdb many times,.gdbinit can save a lot of typing. I recommend adding the following lines:
To perform the xor operation I created another convenience function:
To prove we can retrieve the key we will send two sets of data to be encrypted, use the first to find the key, and decrypt the second set.
Execute the module:
Success, we have leaked the key.
Creating the Proof Of Concept
How much data do we need to send to overwrite the return address in the encrypt_file frame?
This will be the distance between the start of the buffer and the return address of the encrypt_file frame. Using gdb we can subtract these two values:
The return address is located 0x20010 bytes from the start of the buffer. Lets confirm by sending 0x20014 bytes of encrypted data.
Attach gdb, reload the exploit, and send it:
Confirmed we can control eip! Time to start the exploit!
Making a Plan
We have control of eip and can put arbitrary data on the stack, but NX is enabled.
To get around this protection we will use return oriented programming (ROP). ROPGadget is a fantastic tool to find rop gadgets. This tool found 126 gadgets in the level02 binary.
We can use execve to execute netcat and open a connect back shell. The execve function takes an array of string pointers as an argument. Therefore We must have a number of strings at known memory locations. We can store data from the socket at an arbitrary location using nread. We will store this data in the bss section because it is not ASLR enabled, it is readble, and it is writable.
Lets build a ROP chain to store arbitrary data as 0x0804b420
Note: Preferably we would execute a mprotect call to change the stack memory as executable, then execute an arbitrary payload. Unfortunately most of the gadgets are not very useful and I was not able to figure out a rop chain to execute mprotect.
First ROP chain - nread
We control the value of eip, lets set eip to execute the nread function, which is located:
The nread function will expect the stack to have a return address and 3 function arguments: a file descriptor to read from, a buffer to write data, and a length argument. Since we control the values on the stack we can put arbitrary values at these locations.
The fake stack frame will:
Return to 0xdeadbeef
Use the 0 file descriptor to read from the socket
Write data to the bss segment at `0x0804b420
Read the amount of data we plan to send
The fake stack frame is created like so:
Attach gdb, reload the module, and send it:
Confirmed the rop chain executed, wrote the data, and returned to our controlled address.
At the time of the crash the next 12 bytes on the stack are from the rop chain just executed.
To get a clean stack back return to a pop;pop;pop;ret gadget, one exists at 0x08048f85
The rop chain becomes
Attach gdb, reload the exploit, run it again and we crash on 0xdeadbeef again with a clean stack!
Setting up the execve frame
We can read in arbitrary data to 0x0804b420, but what data is required to fake the execve frame?