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:
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:
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.
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:
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)
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!
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: