FLARE On Challenge (2015) #4

File youPecks is a 32 bit Windows Portable Executable file packed with UPX (according to the section name). And it can be unpacked with the UPX utility:
ch4_upx

When executes the unpacked file, it only prints out a strange expression “2 + 2 = 5”, and then exits:
ch4_output

Both the file structure and the resource show no suspicious, so I move on to the disassembly code in IDA. The APIs calls of this program suggest that it is probably written in C++. And at the beginning of the mian() function, here is a strange logic:
ch4_disass1

From above picture you can see that this program calls the function atoi() to convert a string “5” to an integer and stores it to register esi, and later it compares the esi with number 5 and jumps if it does not match. This conditional jump makes no sense because the esi should always equal to 5! However, if the jump does not take place, the program will just terminate itself. So let’s ignore this logic and just see what will happen if it jumps to the unreachable branch.

Firstly, it will check if the number of the argument is equal to 2:
ch4_disass2

If so, it will convert the second argument to an integer and calculate the MD5 hash of the least significant byte of this integer:
ch4_disass3

Next, a branch of bas64-like strings will be initialized and “appended” to a list. Please notice that the Append() function here (in the picture below) may not correct because I did not analyze it in details, the only thing I saw while debugging is that this function will put those bas64-like strings into an array:
ch4_disass4

After above Initializations, one of the Base64 string will be chosen and decoded, then the decoded data will compare with the MD5 hash calculated before:
ch4_disass5

Once the MD5 hash matches the Base64 decoded data, the program will continue executing, or it will exit. Now that we know the MD5 hash is only calculated based on one byte, we can brute-force all the possible value with following Python script:

import base64
import hashlib

for i in range(0, 256):
    print str(i) + ': ' + base64.b64encode(hashlib.md5(chr(i)).digest())

By observing the output of the script, we can find that those Base64 strings in this program are actually Base64 encoded MD5 hash of byte 0x00 to byte 0x17 (0 to 23), with all the uppercase letters changed to lowercase letters and all the lowercase letters changed to uppercase letters. That is to say, those Base64 strings are encoded with a different character set than the standard Base64 character set.

Now we have only one question left: which Base64 string will be picked out to compare with the hash calculated from our input? If you look back to the previous code, it is easy to answer: it is determined by the time, or more precise, the current hour (and that’s why it only need value from 0 to 23).

Knowing this, the things become easy, we can just patch the “jnz” at address 0x0040147F to “jz” so it can run into the unreachable branch and patch the “jnz” at address 0x00401BBC with “nops” so that no matter the hash matches or not the file can continue executing. Then we can execute this file with different time settings to see what will happen. The following Python code can help with this task:

import sys
import time
import win32api
import datetime
import subprocess

def set_hour(hour):
    time_tuple = ( 2015,    # Year
                      1,    # Month
                      1,    # Day
                      hour, # Hour
                      0,    # Minute
                      0,    # Second
                      0,    # Millisecond
                  )
    
    dayOfWeek = datetime.datetime(*time_tuple).weekday()
    time_tuple = time_tuple[:2] + (dayOfWeek,) + time_tuple[2:]
    win32api.SetSystemTime(*time_tuple)

for i in range(0, 24):
    set_hour(i)
    local_hour = time.localtime().tm_hour
    p = subprocess.Popen(['youPecks_patch.exe', str(local_hour)])
    time.sleep(1)

Unfortunately, things seems not that easy, the output of this script is as below:
ch4_pyout

Obviously, there is nothing useful. So I have to continue with the disassembly code to see what is going on. After compares the MD5 hash, another branch of Base64 strings are initialized and “appended” to another list. Then one of the Base64 strings will be picked out and decoded, and then XORed with the previous Base64 decoded data. Finally, the XOR result will be printed out.

Wait, the final output depends on the previous Base64 data, and the previous Base64 decoded data should match the MD5 hash of our input (from 0 to 23). However, according to current analysis, the first set of the Base64 strings seems uses a different character set than the standard one, if we decoded it using the standard Base64 character set which used by this program, the MD5 hash will never match!

What is the problem here? I must missed something!

It is time to go back to the original file (before unpack), let’s try to execute it in a CMD:
ch4_output1

What?! Instead of printing out “2 + 2 = 5”, it prints “2 + 2 = 4”!!! So how about we pass the current hour to it? Oops, we have the email address:
ch4_ans

Now I realize the secret of this challenge, the file unpacked by the unpack stub of itself is different from the file unpacked using the UPX tool! Here is how the unpacked file looks like in the debugger:
ch4_dbg1

And the Base64 character set also different from the file unpacked by the UPX tool:
ch4_b64_2
ch4_b64_1

This entry was posted in CTF and tagged , , , . Bookmark the permalink.