[WU] Switch's challs 0x1 - reverse.me

Posted on Wed 05 December 2018 in Reverse

This is a small 32 bits reverse challenge made by Switch, available here.

When run, this binary ask the user an input, and seems to crash right after that (whatever our input is). After loading the binary in Cutter, we can spot in the import table some well known curl functions. As well as valloc and mprotect. It seems that this binary will be doing some network operation and that it will run dynamically created code.

cURL

The first call to cURL is perfomed at 0x080489fb where curl_easy_init is called, the return object is store and represents a CURL\*. After this call, we see that an address is loaded into the ecx register and that points to that picture.

Due to the way cURL works, the other calls to cURL are the same but with different parameters, the curl_easy_setopt is called three times:

  1. at 0x08048a39, the constant value used as second parameter (0x2712) corresponds to CURLOPT_URL, meaning that the last parameter of this function should be the URL we're interested in. And this is indeed the case, the previous ecx register, that holds a pointer to the dns_rebind picture is 'pushed' into the stack.

  2. at 0x08048a6a, the value 0x2711 is the value of the CURLOPT_WRITEDATA, and as described in the doc, it sets a custom pointer that will be passed to the write function.

  3. at 0x08048a9b, this call uses the WRITEFUNCTION to set a callback for receiving data (see the doc). The pointer in EDX (that is copied in the stack) is hence a function pointer to the cURL callback function.

cURL WriteFunction

From all of this, we can guess that our binary will probably download the above picture and write it to the buffer in memory, this is a very simple cURL example that is describe on the documentation. Let's take a look to cURL's callback function for this binary.

Callback

To mark memory address as code in Cutter, right click on the first address (0x080487c0) and select Set to Code (or press the c key).

cURL Callback

This function is called with four parameters, the first one is a pointer to the HTTP page's content, the second one is always one and the third is the size of the input. The last parameter is a pointer to the address set during the WRITEDATA call.

The core of the function is shown below:

WriteFunction body

In this code, we can see that we access using the counter to some values stored in local_output+4 (which is in fact the array of number from the user's input). It then uses the read value to access into the cURL buffer (the image's content).

To sum that up, we can write the pseudo following pseudo code:

user_input = [1, 2, 3]
img_data = ...
output = []

for i in range(len(user_input)):
    output.append(img_data[user_input[i]])

User input

As spoiled previously, when the user runs the binary he/she must give an input to the program. All of this is performed in a function located at 0x08048860. This function uses fgets to read the input from the user, then performs a strtok to split it into space separated tokens. Each token is then converted to an integer using atoi and store into an array (the third parameter of the function).

Conclusion

We can now connect the two parts of this binary, first, the user is asked for a serie of number. Those numbers are then used by the cURL's callback to access a specific offset inside the picture's content.

The resulting buffer (output in the above pseudocode) is then make executable using mprotect with PROT_READ | PROT_WRITE | PROT_EXECUTE.

mprotect

After this call, we can see that EIP is set to point to this buffer using the call instruction, it will basically executes the content of our buffer. This explain why we were getting segfault each and every time we entered something on the input.

Our goal will hence be to build a shellcode out of the byte available in the first 2000 bytes of the picture, find the indexes of those byte and use them as the input, here is a small python script that does that:

#!/usr/bin/env python3


# Constraint, the input number for fgets must be <= 2000
# We can use at most 31 bytes

# 28 bytes /bin/sh shellcode (shamelessly taken from http://shell-storm.org/shellcode/files/shellcode-811.php)
shellcode = b"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80"


def create_mapping(data):
    mapping = {}
    for position, b in enumerate(data):
        if not b in mapping:
            mapping[b] = position
    return mapping


if __name__ == "__main__":
    available = open("picture.jpg", "rb").read()[:2000]
    mapping = create_mapping(available)

    for b in shellcode:
        if b in mapping:
            print(mapping[b], end=" ")
        else:
            print(f"Unable to generate shellcode, missing byte: {hex(b)}")

This will print a serie of number to use as the input to run a /bin/sh command from the picture's content.

This was a rather simple, but fun binary :)

ex0ns