<-- Flare-On 2015 Index / FLARE-On 2015 Challenge #3 
1
FLARE-On 2015 Challenge #3

Date: Aug 12, 2015

CHALLENGE MATERIALS:

filename:    elfie    DOWNLOAD
md58f0400fe6d897ddbcef2aaf9f9dbd0a4
size11.67 mb (12,233,088 bytes)
typeWin32 Console App with Embedded Python 2.7 and Qt4 Interpreters (so called "Frozen" Python)
Original FLARE AuthorBob Jung
 
tool:    OllyDbg 2.01 / Debugger    Visit Website
tool:    Hex Editor
tool:    Python or Windows Script Interpreter

SUMMARY:
This challenge consists of a GUI password-entry window with a picture of the goat "Elfie" (probably because of the ears) who says "Elfie loves magical keys". The titlebar contains the text "Look inside! you can find one there!"

FIRST LAUNCH:
A hex dump of the first few bytes of the supplied file reveals that it is a Windows PE executable image. Rename the file so it has the .EXE extension, and run it. This is what you'll see:

Flare-On 2015 Challenge #3 - Elfie Loves Magical Keys

If you look closely, you'll notice the flashing cursor in the upper left. This is where text appears when you type, but pressing <ENTER> doesn't seem to do anything such as confirm that you got the key wrong. You can assume this is where you might enter the key (or keys), should you find one.

LAUNCHING THE DEBUGGER:
The first thing to zero-in on is this input dialog with Elfie. Since the program has to know what input is correct, where this decision is made is the first logical place to focus our efforts. Launch elfie.exe in OllyDbg and it should break at the code entry point 40B2F7. If not read how to configure OllyDbg in challenge #1. Lets do a process of elimination in finding which branch (CALL or JMP instructions) ultimately cause the Window to pop up. Begin stepping over instructions (F8 key) until the Elfie window displays. You'll step over a CALL to GetCommandLineA() followed by a CALL that returns what looks like the contents of the process Environment Block (containing the current environment variable name/value pairs). Eventually, you'll find that the CALL instruction at 40B29B is the last one you step over before the Elfie window displays. Close the window and you'll see the debugger regain control by breaking on the instruction below it.

Now that we've found our first spot to analyze, lets place a breakpoint on 40B29B so we can skip stepping over all the prior instructions again. Select the line 40B29B with the mouse (or using the arrow keys) and press the F2 key (or right-click and select "Breakpoint" -> "Toggle"). You'll notice the color change in this instruction's address column indicating you have set a software (INT3) breakpoint.

Flare-On 2015 Challenge #3 - First Breakpoint

With the breakpoint set, we're going to restart debugging, effectively reruning the program. Click on the double-left arrow button in the OllyDbg toolbar (looks like OllyDbg restart debugging button), or press CTRL+F2 which is known as "Restart last debugged executable". After the executable is restarted, OllyDbg will begin again at the executable's entry point. Press F9 (or the play-like triangle button on the toolbar) to resume running the process. Almost immediately, you should see that we've hit our breakpoint at 40B29B. Now press the F7 key to step-into this CALL and you'll be one nesting level deeper. I personally call this the drill-down method. As you get closer to your target, you will usually want to remove at least some of the previous breakpoints you used to get to where you currently are. As you encounter old breakpoints you no longer want, use the F2 key again to remove them before re-running.

Continue with the drill-down method, stepping over instructions until you find the next deeper CALL instruction that causes the Elfie window to appear. Notice the interesting string "_MEIPASS2" that is PUSHed on the stack at 4039C4:

Flare-On 2015 Challenge #3 - _MEIPASS2 Pushed on Stack

Shortly after, you'll find you're looping through characters of the directory path (the one you are running elfie.exe in). The loop terminates once the zero byte it hit (NULL terminator). It can be tedious to step over these instructions repeatedly, especially if the loop body executes thousands of times! Once you realize you are in a loop you are not interested in, set a breakpoint on the instruction that is executed after the loop terminates. Many times this is the instruction following the jump or LOOP instruction at the bottom of the loop, but these jumps can also be within the loop body. Study the assembly until you figure it out the exit paths. In this case, you want to set a breakpoint on 4039E7. With the breakpoint set, press the F9 key to run, and the program quickly advances past the loop. The breakpoint can then then be removed. Unfortunately, OllyDbg doesn't have a "Run to cursor" feature, which is the common debugger feature we just simulated.

DEPENDENCIES EXTRACTED IN TEMP DIRECTORY:
Continue stepping-over, trying to locate the next deeper branch instruction responsible for showing the Elfie Window. Within the current routine, not much further down at 403B72 you may notice that the EDI register has a path in it that will look something like "c:\temp\_MEI24722".

Flare-On 2015 Challenge #3 - Temp directory with Extracted Files

Have a look inside that directory! You should find 18 files totaling just over 24 MB were just created. Based on the size of elfie.exe, we know that these files must have been compressed. Actually you can find that the routine invoked by the CALL instruction a little higher at 403B46 created the directory and all the files there. The files created are:
   358,400 _hashlib.pyd
    44,544 _socket.pyd
   899,584 _ssl.pyd
    68,608 bz2.pyd
       472 elfie.exe.manifest
     1,857 Microsoft.VC90.CRT.manifest
   224,768 msvcm90.dll
   568,832 msvcp90.dll
   655,872 msvcr90.dll
 1,853,440 PySide.QtCore.pyd
 6,947,328 PySide.QtGui.pyd
   110,592 pyside-python2.7.dll
 2,449,920 python27.dll
 2,554,880 QtCore4.dll
 8,360,448 QtGui4.dll
    10,240 select.pyd
   108,544 shiboken-python2.7.dll
   686,080 unicodedata.pyd
Wow, we can see this app is somewhat of a pig using all of these frameworks just to display a password input window with a picture! Monolithic, yes, but the added complexity (and size!) is precisely on purpose. We can see this process extracts the Python and Qt4 frameworks, MSVC runtimes and some python dependencies. This foreshadows that we may need to rise out of the debugger and disassembler, possibly into Python before we can accomplish the mission. Although just because you see something while reverse engineering doesn't mean you'll have to "go through it". The objective is to find Elfie's key, zeroing-in on those parts of the program, ignoring whatever else we can get away with.

Using your favorite PE viewer tool (I used bytepatch in info mode), you can see there is approximately 11.5 MB of data past the end of the PE image. This must be where the embedded files are being stored.

Flare-On 2015 Challenge #3 - Elfie PE Info

I didn't analyze which code created the path to the directory where these files were extracted, but on my system it was always a concatenation of the TEMP environment variable + "\_MEI" + the current process id + "2". Depending on how many times you have previously run elfie.exe without letting it complete (and thus clean up after itself), you'll notice a similarly named unique directory was created each time in the location designated by your TEMP environment variable. Please note that you can safely clean up Elfie's "droppings" after the corresponding debugging session that created it is over. :)

PLURAL ELFIES:
Soon you will you find that stepping over 403B8D, causes the Elfie window to appear at the current nesting level. Drilling down one level deeper, you'll find that this routine invokes CreateProcessW() followed by WaitForSingleObject(), waiting on the process it just created. If you are not already looking at the Elfie Window, go ahead and run the program from its current position so that you can see our good goat friend again. From your favorite task list utility, list the active processes in the system. I used killproc's process list because I can quickly see them sorted by most recently created. You can also use the built-in Windows task manager (CTRL+SHIFT+ESC, processes tab) and sort the processes by name:

Flare-On 2015 Challenge #3 - Multiple Process Copies Running

You saw correctly, there are two elfie.exe's running. This is a classic anti-debugging technique, where you've spent a lot of effort debugging the wrong process. The currently running process we thought was displaying our goat is actually sleeping in WaitForSingleObject() for the 2nd copy of elfie.exe to (display its window and) ultimately terminate. This led us to assume our process was responsible for displaying the window. All is not lost however as this realization makes our next step crystal clear. Since we know we are dealing with two copies of the same EXE, somewhere in the code we've been stepping through are instructions that differentiate one copy of the process from the other; otherwise elfie.exe would launch copies of itself infinitely until system resources run out. We need to find out where this determination is made.

Remember when we saw that "_MEIPASS2" string being PUSHed on the stack earlier at 4039C4? The routine that utilized that argument was invoked by the CALL instruction at 4039C9. Lets jump to that address to set a breakpoint so we can step into that routine. Press CTRL+g to bring up the "Enter expression to follow" dialog (should be called the "Go to address" dialog IMHO). Type 4039C9 and hit <ENTER>. Set a breakpoint, restart the debugging session, and advance past any prior breakpoints. When you've arrived, step into this CALL and you should be within the routine that begins at 401CF0 (a.k.a. function 401CF0). At the top of this routine is a call to GetEnvironmentVariableA(), passing "_MEIPASS2" as the name. After stepping over this call, notice the LastErr in OllyDbg's register window reports "ERROR_ENVVAR_NOT_FOUND". Perhaps the presence of this variable is the determining factor between process #1 and process #2? If the variable does exist, its value is loaded and some other stuff is done, otherwise the function soon returns and the "_MEIPASS2" string is referenced again at 403B67. The string is then passed into a routine that calls SetEnvironmentVariableA(), setting that variable name equal to the path with all of the extracted files. When this variable is set in the current process, it is inherited by default to any child processes created. Therefore, the 2nd copy of elfie.exe launched shortly thereafter would have access to this variable's value. You can assume that since the value is a path to the extracted files, that these files better be accessible to the 2nd copy of elfie.exe. So we know one difference between the user-launched elfie (#1) and the elfie-launched elfie (#2) is the existence of the _MEIPASS2 environment variable, but is that the only deciding factor?

Flare-On 2015 Challenge #3 - Checking Environment Variable

DEBUGGING ELFIE #2 WITHOUT ELFIE #1:
To get further information about how elfie.exe works, we want to debug Elfie #2 as it is responsible for displaying the goat window. We can easily attach a debugger to Elfie #2 after it loads, but we'd miss what happens during initialization. We could certainly analyze the EXE and manually try and figure out the code paths without debugging, but we would only do that as a last resort since it is more time intensive.

At this point we only have a hunch that the _MEIPASS2 variable is the deciding factor between which process displays the the Elfie window. It is worth taking a minute to test it out rather than combing through more disassembly. First go into your TEMP directory and copy of one of the _MEInnnnn subdirectories (with all of the extracted files) into a predictable location for you to work on. You can also close the current OllyDbg session. I created the following batch file to set the special environment variable to the working copy of the extracted files just before launching elfie.exe in the debugger. The hope is that the elfie.exe we launch WILL NOT create another copy of itself (because it thinks it is the second copy) and will follow all the code paths it needs to display the Elfie window. We will then be debugging the correct process.

debugelfie.bat
    @echo off
    set _MEIPASS2=\path\to\elfies_extracted_files
    "\path\to\ollydbg\ollydbg.exe" elfie.exe
Run the batch file and you'll notice that OllyDbg will still handily hit all of your previously set breakpoints. Stepping again into the CALL instruction at 4039C9, we can see what happens when the _MEIPASS2 environment variable exists after the call to GetEnvironmentVariable(). Different routines are executed and the function ultimately returns. Continuing to step-over instructions you'll soon find that the CALL instruction at 403B33 pops up the Elfie window. You can guess you are in the correct process because the OllyDbg log window will report the various Python DLLs and other modules freshly loaded as a result of executing this routine. OllyDbg will take a while to load and analyze these modules the first time they are loaded while running under the debugger. When the goat window displays, verify that your process list shows only one copy of elfie.exe in the system. With that out of the way, now we can make some real progress!

IN-MEMORY SOURCE CODE EXTRACTION:
After you drill-down 2 levels deep into the CALL instruction at 403B33, you'll be in a loop which appears to extract source code data in memory and execute it with the Python interpreter. Within the loop, the CALL instruction at 401611 returns a pointer in EAX that is always a NULL terminated string of an entire Python source code file, with comments and everything! To see it, right click on EAX and use OllyDbg's "Follow in Dump" feature.

Flare-On 2015 Challenge #3 - Load Embedded Python File Loop

Shortly thereafter, the PUSH instruction at 40165E (just before executing the Python interpreter) passes what is must be the name given to the each chunk of source code data in the ECX register. Breaking on these addresses, you will find that the Python interpreter executes a total of 3 source files entirely from memory, whose names are "_pyi_bootstrap.py", "pyi_carchive.py", and "elfie.py" respectively. Save off each of these files from the memory dump window by using OllyDbg's binary copy feature. To know how large your memory selection needs to be for each file, click and drag the selection until you reach the NULL terminator marked by hex "00"). Now you can paste the contents into a hex editor so the file can be saved.

Note that the last source file, "elfie.py" is almost 4 MB, so you can't select it with the mouse in any reasonable amount of time; instead, right click in the memory dump window and choose "Edit" -> "Select all". This will select the entire memory section, but in this case, most of it is indeed "elfie.py" and we can trim away the top and bottom bytes later. Once pasted in your hex editor, manually remove the leading bytes by comparing with the pointer's start address in the OllyDbg memory dump. Then remove the trailing bytes at the end, where anything past and including the NULL terminator should be deleted. Once you save these files, they should be identical copies to what the Python interpreter is loading. We assume that these are probably responsible for displaying the goat window, but at this point we aren't sure.

After looking at the first two files, nothing is very interesting other than the handling of the _MEIPASS2 environment variable. What the comments say about this variable certainly reflects what we've found by reversing engineering how it is handled internally. But "elfie.py" on the other hand, looks like it has something to hide, an indication we are on the right track. The following snapshots show the first and last lines from that 3.86 MB file:

Flare-On 2015 Challenge #3 - elfie.py Top
    ...
Flare-On 2015 Challenge #3 - elfie.py Bottom

DECODING ELFIE.PY:
You can see this file is declaring thousands of variables whose names are odd combinations of zeroes and letter O's. The values assigned to these variables consist of random seeming characters in the printable ASCII range, so right off the bat we might guess this is base64 data. Luckily, the last line of the source code appears to concatenate 64 of the variables producing another string that is then base64-decoded and executed with exec(). This file is already set up to decode itself, but you wouldn't want to decode it manually! The majority of the other variable-lines in this file are not being used here. Rather than execute it, we'd rather save off the resultant string into another file as the decoded source so we can take a look at what the interpreter is seeing, so we need to modify it slightly. Lets create a copy of elfie.py, we'll call elfie.mod.py, replacing the exec call with a print call so that last line looks like:
print base64.b64decode(OOO0OOOOOOOO0000O000O00O0OOOO00O + O0O00OO0OO00OO00OO00O000OOO0O000 +
O00OO0000OO0OO0OOO00O00000OO0OO0 + O00OO00000O0OOO0OO0O0O0OO0OOO0O0 + O0O00OO0O0O0O00OOO0OOOOOO00OO0O0 +
O0OO00O0000OOOO00OOO0OO0000O0OO0 + O0O0OO000OOO00000OO0OOOO0OO00000 + OO0OOO0O00000O00OOOOO0OO0OO00OOO +
O000000000OOO00OO00000OOOO00OOOO + OOO0OOO00O0OO0O0OOOO00OO00OO0O00 + O0OO0OOO00O0OOO0O00OOO0O0OOOO00O +
OOOO000O0OOOOO0O0O0000O0O0OO00O0 + O00O0O00OO0O0OO00O0OO0O00O0O00OO + OOOOOOO0O0O00OOO0OO00O0O00OOOOO0 +
O00O000O0O00000O00OO0OO0OOO000O0 + O0O000O0O0O0OO0000O0O0OOO0OOOOO0 + O000000OOO0O00OO00OO00OO0OO00O0O +
OOOOOO000OOO0O000O0O00OO0OOOO0O0 + O00OOOO0OO0O000OO0OOOOO0OOOO0000 + O0OO00000OOOOO0OOO000O00000OOO00 +
OOO00O0OOOOO00OO0OOOOO0O0O00O0O0 + OOO000O0OOO0OOO0OOOO0OOOOO0O0O00 + OO0OOOO0O00OOOO0OOOO0O0OOO0OO0O0 +
O0OO0OO00000OOOO0OOOOO0O00O0O0O0 + OO00OOO00OO0OOO000O000000O0O0000 + OO0O0000OO0000OO000OO0O0OO0O00OO +
OO00000O0OO000O0O000OOOOOOO0O0OO + O0O00O000OOOO000O0OOOOO0O000000O + O000OOOOOOOOOO0O0OO0OO000OO000O0 +
O0OO00OOO0O0OOOO0O0O0000000O0OOO + O0000O0OOOO00OO0OOO00OO000O0000O + OOOOO0OO0O00OOOOOO00O00000O0OO0O +
OO0O0OOOO0O000OOOO00O0O00O0O0O0O + O0OOO00OOOO0OO0OO0O000O0OO0OO000 + O0OOOO0O00OOOOO0OOO000O00O00OO00 +
OO0OO00000O00O0000000O00O0OO0O00 + OOO0000OO00000OO00O0OO0000OOOO00 + O00O000O00O0O00OO0OO0O000000OOOO +
O000O00O000O00O000O0OO00O0000O0O + O0O0OO0OO0O0OOO0O0OOO00O0OOOOO00 + O0OOO0OO00OO0OOO00OO0O0O0O0O00OO +
OO0OOO00000OO000O0OOOO00O0O0O00O + O00OO00OOO0OOOO0OOOO0OO00000OOO0 + OOOOOO0OO0O0OO0O0000OOO0O00O0O0O +
O0O0O00O0000O00OOOO000O00OO00O00 + OOOOO0000OO000O0O0000OOOOO0000OO + OO00OOO0O00OO0OO0OOOO000OO0000OO +
O00000O0OO00O0OO00000O000OOO00O0 + O0OOO00O0O0O0OO00000OO0OO00O00OO + O0000OO00000000OO000O0OOO000OO00 +
OO0OO0O00000O0O000OOO0O0O0O000O0 + OOOOO0O00OOOOO0O0OOOOOOO0OO0OO00 + OOOOO00O0O0O0O0O0OO00O0OOOO00O0O +
O0OOOO0OO000OOOOOO0O0OO0OOO0O000 + OOOO000OOOOO00000O000OO0O00O0O0O + O00OOO000O0O0OOOO00O0O00O0OO00OO +
OO0O00OO0OO00O0O000O0000O0OOOOO0 + OO0O0O00OO00OOOOOO0O0O0OOO0OOO0O + OO0OOO00OOO00OOOOOOOOOOOO00OO00O +
OOO0000O0OO0OOOOO000O00O0OO0O00O + OOO0O00O00OOOOOOO00OOOO0000O0O00 + O0O00OO00O0O00O0O00O0OOO00O0O0OO +
O00OOOOO000O00O0O00000OOO0000OOO + O0O0OOO000O000OO0O0O0OOOOO0OO000)
Now, Python will dump out the decoded source instead of running it and we'll pipe it into elfie_decoded.py:
python elfie.mod.py > elfie_decoded.py
If you don't have a Python interpreter handy (as I didn't when originally doing the challenges), the variable assignments were generic enough to pass as VBScript. I modified the last two lines of elfie.py to remove the "import base64" and changed the last line from "exec(base64.b64decode( variables ))" to "WScript.echo( variables )". I then ran the modified script through the Windows Script Host interpreter that comes with Windows: "cscript //nologo elfie.mod.vbs > elfie.base64.txt" and used another tool to decode the elfie.base64.txt into elfie_decoded.py (Windows Script Host does not have inherent support for Base64).

CRACKING THE PASSWORD:
Now that we have created elfie_decoded.py, lets have a look. Right away, we can see what looks like more-normal source code despite the ugly references to the cryptic zero-O variables and a lot of strings that look backwards. This is apparent due to the position of the uppercase letters and punctuation coming first. On line 16, this reversed string kind of jumps out:
self.OO0O0O0O0OO0OO00000OO00O0O0000O0.setWindowTitle('!sseccus taerg'[::-1])
Despite not having Python experience at the time of doing this challenge, it was still easy to notice these backwards strings. The reversed setWindowTitle() call above was "great success!". You can assume the title would only say something like this if we got the key right. The line above it began with "if" and compared against another backwards-looking string running through a function named "reversed":
if (O0O0O0000OOO000O00000OOO000OO000 == ''.join((OO00O00OOOO00OO000O00OO0OOOO0000 for OO00O00OOOO00OO000O00OO0OOOO0000 in reversed('moc.no-eralf@OOOOY.sev0000L.eiflE')))):
Reversing that string certainly looks like Elfie's magical key! Let's rerun the app (no debugger needed) to confirm. Make sure you press <ENTER> after you type it in:
Elfie.L0000ves.YOOOO@flare-on.com
Flare-On 2015 Challenge #3 - Entering the Magical Key

CONCLUSION:
I now know that the [::-1] is a reversing construct in the Python language, but at the time, it wasn't completely necessary to have Python skills. If you've had enough exposure to different programming languages, you might be able to make some logical guesses about what some source code is doing in a language you aren't familiar with. Also, according to the official solution, the elfie.exe executable is known as "frozen" Python (a clue provided by the file's icon). I could have used a utility called the Pyinstaller Extractor to extract the source code without having to spend the time extracting it from memory during debugging, but the problem in situations like this is knowing the name of what to Google. It shows that there are numerous ways to arrive at the solution and even if you are missing some skills, others can make up for it!

ALTERNATIVE METHOD:
An alternative method was described in the official solution that involves searching the process memory after the Elfie window appears (although missing the minor detail of attempting a password first). I verified that it does indeed work; here are the steps that worked for me:
  1. Run elfie.exe
  2. Attach OllyDbg to the process that had created the window (elfie.exe #2)
  3. Resume (run) the process in OllyDbg
  4. Switch back to the Elfie window and type any random text followed by <ENTER>
  5. Switch back to OllyDbg and pause the process
  6. Open the OllyDbg the Memory Map window using ALT+m
  7. Select the top memory section in the list (this sets the starting point for the memory search)
  8. Open the search window with CTRL+b and enter "flare-on.com" in the ASCII box (since we know all the solution answers are e-mail addresses ending in "@flare-on.com")
  9. Click the "Search" Button
Voila! OllyDbg pops up memory dump window with the search string hilighted in the plaintext key! Note that this won't work unless you've tried to enter a key. This is because the "if" statement in the Python code needs to execute at least once so the reverse function on the string leaves its artifacts in memory. Alternatively, you can search memory for the backwards version (moc.no-eralf) without attempting a key as the source code remains is memory when the Elfie window displays. This shortcut might not have as much educational value, but searching memory for patterns (especially on your own input data to find out where it is being stored) is a common and valuable technique.

Flare-On 2015 Challenge #3 - Finding the Leftover Key in Memory

E-MAIL RESPONSE:
Response from Elfie.L0000ves.YOOOO@flare-on.com:
Subject: FLARE-On Challenge #3 Completed! From: Elfie.L0000ves.YOOOO@flare-on.com To: <HIDDEN> Date: Wed, 12 Aug 2015 21:22:18 -0400 Very good. I am most pleased with your progress. Attached is the next challenge and the password to the zip archive is "flare" again. Despite what they say about you, I think you're doing great. Always be sure to run the challenge on the command line to confirm that it is actually doing what you think it's doing. -FLARE attachment_filename="CB931CA00859C5D1356CB2733B11EBF2.zip"

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

 1:1