This program doesn't seem to do anything other than display a simple mathematical equation.
Static analysis is made difficult due to the fact that the executable is UPX packed, but unpacking the
executable alters the program's behavior!
A hex dump of the first few bytes of the supplied file reveals that it is a Windows PE executable image, so
we're going to rename to .EXE and run it
2 + 2 does equal 4, but its not clear what to do as the program doesn't offer any obvious way to read input from the
user. Lets pull this up in IDA for static analysis. Unfortunately, IDA reports a warning upon load and the
disassembly listing doesn't seem to reflect the program that is actually running:
Using any PE viewer, we can see this EXE only contains 3 sections: UPX0, UPX1, and .rsrc, a telltale
sign that this is a UPX packed binary. Fortunately the free UPX tool has an unpack feature (-d option), so lets unpack it
into youPecks.unpacked.exe so it can be analyzed. Next, we'll ensure the unpacked version can be executed:
UNPACKED EXE NOW BROKEN:
The unpacked executable is now displaying 2 + 2 = 5! Unpacking changed the behavior, something we can assume the author
intended! Although IDA won't complain when it loads the unpacked version of the executable, I
didn't want to analyze a broken EXE unless I had exhausted all other options.
A LUCKY BREAK:
A console program that doesn't prompt the user might read its input from a file or command line arguments, so I
went back to the original packed version of the program and tried passing a command line argument.
I was quite surprised when my first try of a seemingly random string produced an @flare-on.com e-mail address.
I tried some more strings and got the same result. As
long as I specified *something* for the first argument, I received "Uhr1thm3tic@flare-on.com" in the 2nd line of
output and e-mailing this address did result in receiving Challenge #5, so the solution was confirmed.
It was not surprising that the unpacked version of the program (along with its broken equation) was not able to output the solution.
THE TRUE SOLUTION WAS MORE COMPLICATED:
When I happened upon the solution so quickly after beginning this challenge, I figured the lesson that was being
taught was not to over-think a simple thing. And, passing any argument to unlock a program was pretty darn
simple! This seemed like a plausible explanation at the time.
When I came back to this challenge during the writing of this tutorial, I discovered the steps I had taken in my notes no longer worked! I
couldn't get the program to produce the password, no matter what arguments I typed. At one point, I thought
FLARE might have e-mailed an "accidental" or patched version of the crackme, different than the version posted
publicly after the contest was finished. I pulled the original file from my e-mail history, and it was indeed
identical to what I was working with, but somehow behaving differently.
Still curious how the program altered its behavior after being unpacked, I decided to begin with
manually unpacking the executable (an exercise frequently discussed in the reverse engineering community).
MANUAL UPX UNPACKING USING OLLYDBG:
OllyDbg 2.01 has a neat feature where it automatically detects certain types of self-extracting executables,
such as UPX, and will set the
initial breakpoint on the first instruction after the executable has been unpacked.
You might miss this fact
when debugging if you didn't see the "Known entry point of packed file" message in the statusbar immediately
after that breakpoint is hit during a new debugging session. When you do this with youPecks.exe, if you are not at
address 403A8A, ensure you have Options (ALT+o) -> "Debugging" -> "SFX" -> "Unpack SFX modules automatically"
checked. This position is known as the OEP (Original Entry Point) and represents the entry point as it existed before the
executable was packed. Assuming you have the OllyDumpEx plugin installed (DLL present in the OllyDbg plugins
directory), select the "Plugins" menu -> "OllyDumpEx" -> "Dump process" and you will be presented the
Click on the "Get EIP as OEP" button to set the start address of the unpacked code. You should see the value 3A8A populate the "Entry Point" box.
The code entry point is specified as an RVA (relative virtual address) in the PE headers, excluding the module's
base address, so this is why you don't see 403A8A.
Click "Dump" and choose a filename or use
the default youPecks_dump.exe.
If you try and run the dump file, it will crash because it needs further processing to be a normal executable
Leave the dialogs in OllyDbg open and run the ImpREC tool (Import REConstructor).
NOTE: I was only able to find
ImpREC version 1.7e and its VirusTotal rating left something to be desired. Multiple virus scanners report that
it contains a virus, although some claim this is due to its low level PE handling code. To be on the safe side, I
ran OllyDbg and ImpREC in a virtual machine for this step and the tool had no problems. I would assume the tool is
otherwise safe, but I cannot be certain.
Within ImpREC, select "youpecks.exe" from the "Attach to an Active Process" drop-down listbox. ImpREC
only works on currently open processes.
Noting that the OEP box displays B440 (the UPX stub), we'll need to manually type in the correct OEP RVA of
3A8A, the same value that was populated in OllyDumpEx.
Now click the "IAT AutoSearch" button. A message box appears, "Found address which may be in the Original IAT,
Try 'Get Import'". Then click the "Get Imports" button. Finally, click the "Fix Dump" button and select the dump
file that OllyDumpEx created (youPecks_dump.exe). You should see "saved successfully" at the bottom of the log
pane with a newly created executable of the same name as created by OllyDumpEx, but with an underscore appended
This new executable is the final reconstruction of the original unpacked version. I prefer
to rename it to something more meaningful, such as youPecks.fixed.exe. Close ImpREC, then switch back to
OllyDbg, click "Finish" and you can close the debugging session. Running the manually unpacked executable from
the command prompt now yields the same output as in the original packed executable, so we know we're on the right
2 + 2 = 4
FIXED EXECUTABLE CAN NO LONGER BE DEBUGGED:
Unfortunately, OllyDbg can't seem to debug the fixed executable (unlike the original packed version).
You can see this when setting Olly's start config to options (ALT+o) -> "Debugging" -> "System breakpoint"
and attempting to debug. When NtContinue() is invoked in NTDLL to begin the debugged process, the process immediately terminates.
This isn't a huge deal as the new executable is still runnable and no longer resists static analysis.
However, I found the reason the process was being terminated was due to the flags in the UPX0 section's characteristics in the PE file.
Note that the UPX0 section in the fixed executable is where our original code entry point lies and is equivalent
to the .text section in the "upx -d" version. ImpREC had set the UPX0 characteristics bitmask to:
Although "upx -d" version somehow broke the program's functionality, it can be assumed that it generated a more structurally correct PE
file. Considering that the "upx -d" .text section had the characteristics of 60000020
(contained IMAGE_SCN_CNT_CODE instead of IMAGE_SCN_CNT_UNINITIALIZED_DATA), the fix is to change the
UPX0 characteristics to:
This simple alteration (I used CFF Explorer) made the executable debuggable once again and truly fixed!
Note that these characteristics were identical to the "upx -d"
version except with the addition of the IMAGE_SCN_MEM_WRITE flag (80000000). If this flag was missing, an
access violation was thrown upon execution as some part of the UPX0 section is still being written to in the fixed version.
STATIC CODE ANALYSIS:
With all those "hoops" out of the way, let's take a look at the fixed executable in IDA:
Despite IDA's warning of a destroyed IAT, we can still analyze the code. Hitting the <TAB> key from
within "IDA View-A", produces a C-like pseudocode listing of the main() function. Note that IDA's interpretation
is a bit "off", this decompilation listing still serves our purposes here. Scrolling past the vast amount of
variable declarations, but near where the actual code begins, there are a number of interesting things. We can
see where the number "4" string is converted to a number (using atoi()) and called with "cout" along with the "2
+ 2 = " string. Below that, the code checks that if our converted number is 5, we go down a branch that seems
to clean up some things (likely from classes that go out of scope) and exit. At this point, it has not yet been
explained why the "upx -d" unpacked version of this program resulted in a "5" instead of a "4" here, but we can
see that the possibility for "5" is explicitly checked and causes a premature exit. This explains why we could
never get the password to display in the "upx -d" version. Additionally, if argc is anything other than the
value 2 (i.e. anything but a single argument passed on the command line), the program also exits prematurely.
It is now clear that for the program to do anything useful, a single command line argument needs to be passed.
The program then proceeds to call localtime64() and store the current hour in a variable (v9 in this example).
The hour is then used throughout the routine in different ways.
As explained in the
The official solution, this function
essentially performs a comparison with a number you type on the command line with the
current system hour (military hour). If you happen to type in the hour that matches the system clock, the
program decodes the stored password and outputs it to the console. This comparison is done in a
counterintuitive way so it is difficult to see:
comparing the MD5 hash of the command line argument (interpreted as a number)
with the base64-decoded constant that is the MD5 hash of the current hour.
When I originally ran the program passing some non-numeric string, this was evaluated by
atoi() as zero. I happen to have been running the program just after midnight, so the hour was also zero.
Because the two values correlated, the program decrypted the solution. The only time I would experience those
results would be between the midnight and 1am window! :)
To patch the program so it will always decrypt itself, regardless of the hour, change the instruction in the
manually unpacked executable at 401BA5 from a JZ to a JMP.
THE MAGIC UPX VALUE:
One last question remains about how the program was able to alter its behavior depending on whether it was
in its original packed form or unpacked with "upx -d". If we debug the packed version in OllyDbg with the
"Unpack SFX modules automatically" unchecked (ALT+o -> "Debugging" -> "SFX"), we begin at the
PE code entry point of 40B440 which places us at the beginning of the UPX unpacking stub.
Step once beyond the PUSHAD instruction. We want to execute until everything the PUSHAD instruction placed on
the stack is removed (indicating we have returned back to the same nesting level). To do this, we set
a hardware breakpoint on the stack by right-clicking
the top of the current stack address (to the right of the memory dump) and selecting "BreakPoint" -> "Hardware...". In the
example below, this is happens to be 12FFA4. Leave "Access (R/W)" selected, "Data size" at "Dword" and click OK.
Press F9 to run and the breakpoint should be hit just beyond the POPAD instruction at 40B614. Just
below is the jump into the OEP (the newly unpacked code at the original entry point). Looking
just above is a loop that reverses the case of the hardcoded base64 character table (used by the main
routine in the decryption process). This is followed by the instruction at 40B60B that moves the character "4" to some memory
That memory location happens to be the spot read by the main routine to be displayed next to
"2 + 2 = " on the console. Since the code in its original decoded form has the value "5" at that address, the
removal of the UPX stub code (from "upx -d") also removes the functionality that changes the "5" to the "4".
While the persistence of the "5" was left by the author as visual clue that something is wrong, the real problem
in the unpacked executable is that the base64 table adjustment would also be removed, making a proper
decryption in the main routine impossible.
While the UPX packer certainly didn't add this code, it illustrates how a clever individual might alter (and
extend) the unpacking stub to include instructions relied upon by the program such that when the stub is removed
in the automatic unpacking process, the program becomes broken.
Response from Uhr1thm3tic@flare-on.com:
Subject: FLARE-On Challenge #4 Completed!
Date: Fri, 14 Aug 2015 03:19:53 -0400
You make it look easy. Are you some sort of wizard? Our cyberspace commandos captured some network traffic and a program they think may be responsible but the turbo hackers nuked everything else. Find the key, the password to the zip archive is "flare" once again.
We are counting on you!