This looks like the usual command line crackme prompting for a password, but because the application was
written using the .NET framework, we must take a different approach to getting it running inside a debugger.
A hex dump of the first few bytes of YUSoMeta indicates it is a PE executable image, so we rename and launch it
from the command line. The challenge prompts us for "the correct password" after a warning that the program is
"100% tamper-proof". Cute.
Viewing the executable in
CFF Explorer indicates this is a .NET command line executable. This is also evident by the presence of the CLR/.NET data directory
in the NT Optional Headers. Had you tried to debug this executable using OllyDbg, you would initially find
yourself at the ".NET entry point" breakpoint in the mscoree module.
A .NET executable can also be identified in the Depends
(Dependency Walker) tool due to the dependency on MSCOREE.DLL.
VIEW THE .NET/CLR RUNTIME INFORMATION:
In CFF Explorer, navigate to the ".NET
Directory" -> "MetaData Header" in the left pane. Note the .NET/CLR version string is v4.0.30319. If you
don't have CFF Explorer handy, a quick way to find the version string of a .NET framework application is to
view the executable in a hex editor and search for the ASCII string "BSJB", the signature of the MetaData
header. Once found, look ahead 12 bytes and there's the version string.
Lucky for us, .NET code can usually be decompiled very easily. First, let's glean any additional information
we can find from the embedded .NET assembly manifest using
Microsoft's own ildasm.exe tool (Intermediate Language
Disassembler). Ildasm is available with all versions of Visual Studio and the Microsoft SDKs. When we attempt to load YUSoMeta.exe
in ildasm, we get the message "Protected module -- cannot disassemble". This error is a
telltale sign that the module has been obfuscated, and its no surprise a Microsoft tool chooses not to cooperate
in bypassing any sort of protection scheme.
There is a reason the first incarnation of .NET programming (Visual Studio .NET 2002) bundled a free version of
a program named "Dotfuscator". Because of the nature of how .NET code runs on top of a virtual machine (IL assembly is
essentially Microsoft's version of Java bytecode), you can achieve a near-perfect reconstruction of the original
source code with only the released executable and the right tools. Microsoft knew they had to offer something to help developers adopt the
.NET paradigm, while dissuading the casual reverse engineer.
One method to prevent prying eyes from the inner workings of
a .NET module (without breaking it) is to obfuscate it. "Dotfuscator" does this, and dozens of other
more elaborate obfuscation schemes have been developed to tackle the problem over the years.
This usually messes up the namespace, class and method names,
but can also add code and utilitize other techniques to make the flow of the program more difficult to understand, while retaining the
same program functionality.
VIEWING THE ASSEMBLY MANIFEST:
The ILSpy tool has no problems displaying the assembly manifest, even if the assembly has been obfuscated.
After we load YUSoMeta.exe in ILSpy, we can see the assembly manifest at the root of the module's tree. The "Powered by SmartAssembly 126.96.36.199" is
the indicator of the obfuscation method used.
Near the bottom, we find the string "SuppressIldasm". This was probably the
flag that caused ildasm to refuse to cooperate.
DEOBFUSCATING THE EXECUTABLE:
Luckily, the de4dot tool will deobfuscate the executable so we can analyze it with a decompiler (or anything
else). Running the following command will produce a "clean" executable:
We now have YUSoMeta-cleaned.exe in the same directory, which is about 6k smaller! Load this version
in ILSpy and navigate through the module's tree in the left pane. After a bit of trial and error, you'll find
there are two source files that appear to have useful source code; Class5.cs and Class3.cs. The namespaces and
other identifiers used in the source code are generic, such as "ns1", "string_0", "Class1", but that shouldn't
be a problem because we can now see what these identifiers do and how they are used. Class3.cs happens to have the program's entry point at main().
After main() declares some byte arrays, we see the following code beginning at line #355. NOTE: You can turn on line
numbers in the program options as well as save off copies of the source files, should you want to analyze them
in your own viewer/editor.
string text = Console.ReadLine().Trim();
string b = Class3.smethod_0(class1_, byte_2) + '_' + Class3.smethod_3();
if (text == b)
Right away, we can see those byte arrays we passed were just cryptic ways of declaring the application's
"welcome" strings along with some seemingly unused arrays thrown in the mix. Console.ReadLine().Trim() reads the password
from the user, with the surrounding whitespace removed. The next line calls two functions to piece together two
strings separated by an underscore "_", assigning the result to variable "b". Because the contents of "b" are
compared on the next line against the user input, "b" is probably the password string "expected" by the program.
This is a boon for us, because we can just read the value of variable "b" in a debugger without spending
time investigating the calls to the two mystery functions (or other parts of the algorithm).
The only thing we need to figure out is how to get to this spot within the debugger. The Class3
method symbols are tricky to set breakpoints on because the names were obfuscated, but the System methods
breakpoint on the first Console.Write() should get us to the right spot in main() fairly quickly, followed by a
subsequent break on String.Trim() to get us adjacent to the code we are interested in. We'll step through the rest of
the instructions, watching the contents of the registers until we see an arbitrary string in one register and
our user input in another. We can guess our user input won't be loaded until just prior to the comparison
statement "if (text == b)". We'll then know that the "b" variable has been fully built.
HOOKING UP A DEBUGGER:
Since we'll be debugging managed code, we're going to use the WinDbg family of debuggers with the SOS and SOSEX
managed extensions. We primarily need these extensions to set managed breakpoints.
You should find SOS.DLL in the .NET Framework 4.0 system directory as
"%WINDIR%\Microsoft.NET\Framework\v4.0.30319\SOS.dll". Copy this file to the to where you installed the Microsoft
Debugging Tools (i.e. windbg.exe, cdb.exe). Also copy into this directory the latest SOSEX.DLL from the link at the top of
this page. From the command prompt, launch the debugger as follows:
NOTE: If you prefer, you can use the GUI version of the debugger by replacing cdb.exe with windbg.exe.
The following debugger commands will load the managed extensions and configure our breakpoints:
The first "g" command will run the program to the first Console.Write(). You'll see the 1st line of
the "welcome" message from the prior Console.WriteLine() call.
The next !mbm command allows us to break on the next String.Trim() call. The 2nd "g" command runs the program
again, but we'll be prompted for the password before the breakpoint on String.Trim() is hit. Type in anything that you will recognize as "your" string
when stepping through the debugger. I used "HOWDY".
FINDING THE PASSWORD:
Now issue the following two-command combination separated by a semicolon:
We're using the assembly step-over command ("p"), since there is no managed equivalent (that I was aware of
anyway), so its going to take many more assembly instructions to step over this single line of managed code.
The !mdso dumps CPU registers and managed object references on the stack, so we can see the state of
the local variables after each instruction executes. Press the <UP> key to bring up another copy of
our command string "p;!mdso" and press <ENTER>. Continue doing this until you see your user input string appear in
one of the CPU registers. After about 6 instructions, you'll begin to see the "password" string taking shape. After
about 30, you should see your user input appear in one of the registers. Don't step any further. Look in the
local variable dump output for a plaintext string that looks like it might be the password.
In the screenshot above, we know its not the string in EBX because this string doesn't contain the underscore
"_". The string in EBX is a temporary result of one of the "halves" of the final password string. The only
other string is the one in ESI that contains "metaprogrammingisherd_DD9BE1704C690FB422F1509A46ABC988". This
string certainly isn't a flare-on.com e-mail address, but we'll try plugging it in anyway. Issue the "q"
command to exit the debugger and rerun YUSoMeta.exe without the debugger.
TRYING THE PASSWORD:
Plugging in the password we found confirmed we got it right. The program proceeded to decrypt the final solution, which
was indeed a flare-on.com e-mail address. It was nice we could take advantage of a shortcut, thanks to the
ability to view the deobfuscated code!
Two anomalies I noticed while doing this challenge were that the managed breakpoints didn't seem to work on the deobfuscated version of
the program once the user input string was entered. Also, if you try to enter the correct password in the deobfuscated version,
you'll find it is not recognized (i.e. you get the generic "Y U tamper with me?" message).
The official solution used the dnSpy tool as the .NET
decompiler, but that wasn't the only difference. The author digs in to how you can invoke some of the
decryption methods in the program from
a PowerShell script to piece together the two halves of the password, something I had no idea was possible!
An alternative method is then offered for finding the password as a memory artifact once the program terminates.
This is similar to my method, but relies on a neat debugger
script rather than stepping through the code. Finally, it was explained why the deobfuscated version failed to
decrypt the password. The password logic was based on attributes from the obfuscated version of the executable!
Response from Justr3adth3sourc3@flare-on.com:
Subject: FLARE-On Challenge #7 Completed!
Date: Wed, 19 Aug 2015 00:00:03 -0400
That challenge was supposed to stop you. I suppose we underestimated you, Contestant #743. We didn't want to, but we're going to have to start bringing out our hard ones. The password to the attached zip archive is "flare". I hope your good fortune continues.