10 minutes read

Tha crackme is created by @hasherazade for malware analysts.

You can download it from here

For the analysis environment, I used Windows 10 64-bit on VMware Workstation Pro, with an Internet connection.

During the analysis, I used the following tools:

Initial analysis

From static analysis we see that it’s a python based program bundled by PyInstaller or Py2exe (two of the most popular tools).

NOTE: I recommend you to use flare-floss instead of a strings tool.

We can use python-exe-unpacker tool to unpack the crackme . python-exe-unpacker is just the wrapper around unpy2exe , pyinstxtractor.py and uncompyle6 .

The main file is one with no extension: another , but that’s not the plain source code, it’s the compiled one.

We can decompile it with uncompyle6 . Occasionally, the main python file that is packed with PyInstaller does not contain the magic number - 0x03 0xF3 0x0D 0x0A 0x00 0x00 0x00 0x00 , we need to append the magic number (if there is none) and decompile the file using uncompyle6 :

NOTE: we need to append ".pyc" extension, otherwise "uncompyle6" complains

Now we have the source code:

Stage 1

At the first stage, it checks login, password, and PIN. We clearly see that login is hackerman and the password’s MD5 hash is 42f749ade7f9e195bf475f37a44cafcb , we can use an online decryptor tool to get the password:

It uses PIN to generate key value and checks MD5 hash for the value, at online databases there is no entry for fb4b322c518e9f6a52af906e32aee955 , so we need to brute-force one (assume that PIN is 4 bytes long and only numeric values from 0 to 9):

My script to brute-force PIN :

Stage 1 solved:

login: hackerman

password : Password123

PIN : 9667

Stage 2

At decode_and_fetch_url it decrypts the URL and downloads the image:

It uses get_encoded_data to extract data from the image, and is_valid_payl to check if the data is PE executable:

After that, it allocates memory, copies the data and jumps to allocated_mem + 2 location via call instruction (seems like the downloaded file contains shellcode functionality):

We need to analyze the decrypted file. Just dump the PE file while debugging the source.

The shellcode starts from the third byte of the file:

NOTE: base address of the file might be different on different screenshots. The important part is RVAs .

To debug the second stage file, we pause debugging source code at MR() call (transfer call), attach a debugger to the python.exe process and set EIP to allocated memory:

Instructions at the beginning are used to jump to base+0x6E0 location:

NOTE: 0x7BA0000 is the base address of the file in the memory

At base+0x6E0 it builds IAT (import address table )

After getting all necessary function addresses, it jumps to OEP (Original Entry Point) of the PE file (it’s .dll ) via call esi at the near end of base+0x6E0 function:

There is tons of unnecessary code, the most important part for us, starts at base+0x1170 :

It registers two vectored exception handlers, VEC_Handler_set_enviroment_var and handler_VEC_getEnvValue .

VEC_Handler_set_enviroment_var sets the current process ID as mb_chall environment variable for the process and returns 0 - EXCEPTION_CONTINUE_SEARCH

handler_VEC_getEnvValue increases EIP - instruction pointer by 6 (a.k.a skip next 6 bytes of instructions) and returns 0xFFFFFFFF - EXCEPTION_CONTINUE_EXECUTION

There is an anti-debug trick using INT 3 (0xCC) instruction.

INT 3 trick

Let’s say we have following code:

When the INT3 - EXCEPTION_BREAKPOINT occurs an attached debugger has the chance to handle the exception, if there is no a debugger attached, registered VEH and SEH executes, if any debugger implicitly handles the exception, it just skips one instruction 0xCC and continues execution, we can use this method to change our program behavior under some debuggers.

WinDbg implicitly handles INT 3 exception:

If there is no a debugger attached, which handles the exception, we can use the VEH or SEH handler to change the code execution path, by modifying EIP register (as shown at the code snippet above)

IDA Pro gives us the chance to decide which way is preferable to us:

In the crackme , INT 3 is used to execute the msg_sorry_failed function if the execution happens under a debugger:

If there is no debugger, which handles the exception, the crackme skips the next 6 bytes and continues execution from enum_windows__HERE function, which creates a new thread and executes enumWnd_10001110 function:

EnumWindows calls enumWindProc for each top-level window and checks if a window name contains Notepad and secret_console strings:

If so, it changes the window title to "Secret Console is waiting for the commands..."

After that, it enumerates child windows and checks if the content contains the dump_the_key string.

If so, it loads actxprxy.dll and overwrites the first 0x269 bytes with encoded/encrypted/compressed data:

NOTE: click here to read more about Windows messages .

We did it. Stage 2 solved:

Stage 3

Gets address of the recently loaded module, calculates and checks the checksum, decompresses the overwritten data and XOR s with a user entered key:

level3_colors is used to get a key from a user in form of an R G B color code:

After decoding the data, it tries to execute the data using exec command, the exec method executes the dynamically created program, which is either a string or a code object, simple example:

The challenge is that we should guess correct the R G B sequence to decrypt the data and execute it.

My brute-force script is the following:

The data variable is part of the encrypted data (there is no need to decrypt whole data to guess the correct sequence), and it the checks if the result is a printable string:

Stage 3 solved: R - 128, B - 0, G - 128

Thank you @hasherezade for such an interesting crackme.

Any feedback would be greatly appreciated.

Twitter: @_qaz_qaz

Discuss on Reddit