<-- Flare-On 2015 Index / FLARE-On 2015 Challenge #4 
FLARE-On 2015 Challenge #4

Date: Aug 14, 2015


filename:    youPecks    DOWNLOAD
size12 k (12,800 bytes)
typeUPX-packed Win32 Console (VS10 runtimes + MFC dependencies)
Original FLARE AuthorJosh Homan
tool:    OllyDbg 2.01 / Debugger    Visit Website
tool:    OllyDumpEx 1.50 / Process Dumper plugin for OllyDbg    Visit Website
tool:    UPX 3.91w / Unpacker    Visit Website
tool:    ImpREC 1.7e / IAT rebuilder    Visit Website
tool:    CFF Explorer / PE Viewer/Editor    Visit Website
tool:    IDA / Disassembler    Visit Website

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

Flare-On 2015 Challenge #4 - First Launch

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:

Flare-On 2015 Challenge #4 - IDA error on UPX executable

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:

Flare-On 2015 Challenge #4 - un-UPXing breaks it

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 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.

Flare-On 2015 Challenge #4 - Solution shown to seemingly random argument

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

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 OllyDumpEx dialog:

Flare-On 2015 Challenge #4 - OllyDumpEx dialog

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 again.

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 (e.g. youPecks_dump_.exe).

Flare-On 2015 Challenge #4 - ImpREC dialog

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 track!
2 + 2 = 4
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.

With all those "hoops" out of the way, let's take a look at the fixed executable in IDA:

Flare-On 2015 Challenge #4 - IDA Error using Manually Unpacked Executable

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.

Flare-On 2015 Challenge #4 - Main Function Top

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.

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.

Flare-On 2015 Challenge #4 - Packed Executable UPX Stub Entry Point

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 location.

Flare-On 2015 Challenge #4 - Packet Executable UPX Stub Complete

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! From: Uhr1thm3tic@flare-on.com To: <HIDDEN> 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! -FLARE attachment_filename="062FB655852EAF0CD96325631FD90920.zip"

<< Flare-On 2015 Index  --  Go on to Challenge #5 >>