This post spoils a CTF challenge … Don’t read if you want to try it !

ECW (European Cyber Week) is a Jeopardy student CTF challenge. It is organized by Thales, Airbus and the Bretagne region. I had a great time solving these challenges, especially reverse and pwn ones. Hurry to hand over this !

[+] Presentation

Once analyzed, this binary will probably give you a flag!

[+] Recon

This is a Windows x86 binary, as file tells us.

file recon


Let’s run the binary with some sample inputs to have a first look at it !
recon


It looks very classic, we must provide the flag as argv and the binary will check if it is alright.

[+] Reversing

The binary is stripped :(

So, we will first need to identify the entry point and the main function, in order to find the password checking code.
With the help of static strings, we can find the main function at 0x1520.
The main function simply check if there are two args, print the usage string if not or check the input password and print the win/lose message.

main func

check_password function first calculates a value based on the input length. It basically calculates the value -length * 2, with length as the input length. Let’s call this value the reverse length.

check passw func

Then, the code computes a division (0x79cb0347 / 3). It then substracts 0x79cb0379 to the result.

except handler

If the new result is equal to the reverse length computed before, it goes to the following checks, which are simple checks on the first 4 chars and last 4 chars.

comp func

The problem is that the code will never pass this comparison since it needs an impossible input length : 0x79cb0347 / 3 - 0x79cb0379 = 0xaecdfd9e <=> input_length = 681115953.

One interesting thing is that the division code is executed inside a try/catch statement.
mov [ebp+ms_exc.registration.TryLevel], 0 ; marks the beginning of the try statement, and mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh marks the end.

If this statement catches an exception, an handler function is called and it then resumes to the handler_func_return label (in red).

except handler colored

The exception handler function code can be easily found because this function is passed as reference to the try/catch statement at the beginning of the check_password function.
After reviewing it’s code, it appears that the exception handler calls VirtualProtect function from the Windows API to remap the text segment of the executable as RXW, modify it and remap it as RX after. It basically modifies some of the checks on the password in check_password.

After digging deeper in the executable, I found that the presence of a debugger is checked 2 times :

So, the final strategy is to put a breakpoint after the calls to IsDebuggerPresent() and IsDebuggerPresent_custom(), set their return value to 0 (False) and set another breakpoint at the beginning of the password checking code to extract the real conditions.
Let’s run the binary with some sample inputs to have a first look at it !

recon

I made a quick and dirty Python script which recover the password from the conditions we can extract from the patched check_password function.

#!/usr/bin/python3
import struct

def ROR(x, n, bits = 32):
	mask = (2**n) - 1
	mask_bits = x & mask
	return (x >> n) | (mask_bits << (bits - n))
 
def ROL(x, n, bits = 32):
	return ROR(x, bits - n, bits)

flag_0 = ROR(0x79d71377, 5) ^ 0xdeadc0de
flag_0 = struct.pack("I", flag_0)

flag_1 = ROL(0x100000000 - 0x1a9391dd, 5) ^ 0xfaceb00c
flag_1 = struct.pack("I", flag_1)

print("Password : %s" % (flag_0 + flag_1).decode('utf-8'))
#ExceptCW

Finally, let’s use this password to get the flag !!

recon

[+] Bye

Feel free to tell me what you think about this post :)