|size||11.67 mb (12,233,088 bytes) |
|type||Win32 Console App with Embedded Python 2.7 and Qt4 Interpreters (so called "Frozen" Python)|
|Original FLARE Author||Bob Jung|
||OllyDbg 2.01 / Debugger
||Python or Windows Script Interpreter
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!"
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:
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.
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.
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
), 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
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.
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".
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:
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.
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. :)
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:
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?
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.
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!
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.
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
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:
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).
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:
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:
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
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
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:
- Run elfie.exe
- Attach OllyDbg to the process that had created the window (elfie.exe #2)
- Resume (run) the process in OllyDbg
- Switch back to the Elfie window and type any random text followed by <ENTER>
- Switch back to OllyDbg and pause the process
- Open the OllyDbg the Memory Map window using ALT+m
- Select the top memory section in the list (this sets the starting point for the memory search)
- 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")
- 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.
Response from Elfie.L0000ves.YOOOO@flare-on.com:
Subject: FLARE-On Challenge #3 Completed!
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-On 2015 Index
-- Go on to Challenge #4