Hello, today I’ll crack a .NET crackme, it was featured on Himayatic CTF, November 2nd 2017. Download link : https://drive.google.com/open?id=0B7U3AsTA9UVfRHdTY2hfQzZrQm8
Let’s start :)
First, we notice that it’s a .NET crackme, it asks for a serial, and displays
"Wrong Serial ... !!!" when we enter a random one.
We open it in a .NET decompiler (I used
dnSpy, which is a fork of
ILSpy), and we immediately locate this function :
The code looks obfuscated, we follow the
M functions in the namespace
A, and we find this:
Looks like they used
CryptoObfuscator to obfuscate the code, we’ll use the popular .NET deobfuscator de4dot to clean the executable.
We open the cleaned executable in
dnSpy, the obfuscation is gone!
But it’s a wrong flag, if it were the real flag, the else part would display
"Wrong Serial ... !!!" and not
"Illusion ... !!" (of course, we still try it, who knows :p).
We don’t find anything interesting in the
Form class, so we try debugging in
x64dbg, we notice that when we open the program with
x64dbg, it spawns itself in a new process and then exits from the main process, we breakpoint on
CreateProcessW, just to ensure that we are correct, and yes, we hit the breakpoint on
CreateProcessW, I did not notice the
CREATE_SUSPENDED state in
dwCreationFlags, I immediately went back to the decompiler, to find out where it spawns the new process, and what it does exactly.
We suspect that it uses the
RunPE technique, which in short works like this:
- It spawns itself as a new process in suspended state
- It locates the base address of the new exe.
- It uses
WriteProcessMemoryor a similar API to write every section to the new exe
- It sets the context of the main thread of the new process
- It resumes the new process
We go back to
dnSpy, and we find out that we were correct, the code is in the
Now, to analyze the code in the new process, we have two possiblities:
- dump it while it’s running
- hook the API that writes to the new process, and intercept the data written
Let’s follow the first option, I used pd (process dump) to dump the process and all its DLLs while it was running, this way I got the final exe.
The new exe looks a lot like the original one.
More fake flags :p.
after more investigation, we find this function :
It Loads another Base64-encoded assembly, we look for
T4.Text, they are initialized in the
InitializeComponent method in
FZero class, all this way:
this.T1.Text = componentResourceManager.GetString("T1.Text");
We find the strings in the resources
We concatenate them, decode as
base64, and save output as a file, it’s a DLL.
We load it in
dnSpy, and this time, we find the real check :
V calls the method
E to encrypt the user input, then it calls the method
VR to check if it’s correct,
VR compares a fixed, twice-
base64-encoded string (stored encoded once in ressources) with the encrypted and twice-encoded user input, then it calls
DS if the check succeeds, we find
"Wrong Serial ... !!|Yeah,, you did it :)'" in the resources.
E (which encrypts) looks like this:
It concatenates the second argument with
"Himayatic_0xC001", then hashes the result using
md5, then it initializes a buffer of
32 characters, then it copies the
md5 hash to a buffer starting at index
16 characters), then copies the same
md5 hash to the same buffer starting at index
16 characters), so it overwrites the last character of the first copy, then it uses the buffer as the key to encrypt the first function parameter, it encrypts it using
ECB mode, then encodes it using
We go back to where the assembly is loaded and used, we look for references to the function
GetSkin, which returns an instance of
We find it in
this.o = RuntimeHelpers.GetObjectValue(this.Himayatic.getSkin());
We look for references to
this.o, and we find this :
It’s pretty straightforward to understand this code, it’s calling the method
V, something like this :
V( this.FL__.Text, this.Name, true)
this.FL__.Text is the user input,
this.Name can be found in
FZero class (
this.Name = "FZero"; ).
After that, it’s pretty simple to get the flag, just a simple Ruby script:
require'base64'; require'openssl'; require'digest'; begin enc = Base64.decode64 "FQUFl/85WMFJp5XXfJX5Xykt8WhTPcy1MD0/0+SqEsj/IdMrolb3Haaq9yiZvcuH"; e = OpenSSL::Cipher::AES.new(256,:ECB) e.decrypt; # initialize it for decryption md5 = Digest::MD5.digest("FZero" + "Himayatic_0xC001"); key = "\x00" * 32; key[0,16] = md5; key[15, 16] = md5; e.key = key; puts (e.update(enc)+e.final); # should output the flag rescue Exception => e # show the exception p e; p e.message; p e.backtrace ensure # wait before exiting gets end
Really nice challenge.