crackme.zip is downloaded from join.eset.com.
It turns out that this is valid PE32 executable for Windows. Let's change extension to .exe. Before analysing the file it's worth to check its entropy. I used simple program called 'Ent' by Gynvael Coldwind. On image below we can see the results, 1 pixel of image width translates to 256 bytes in file. From histogram we can clearly see that code is not encrypted and it has standard as for x86 code entropy. In data section there is rather few null bytes.
There could be strings (encrypted or such that are reversible to plaintext by simple operations like xor). I spotted interesting peak of entropy in data section (pointed on picture below). After analysis in IDA Pro I found out that these bytes are used by library function, which is not really important in this analysis. Next thing I noticed is block of high entropy at the end of the file. After looking in hex-editor at these bytes it's clear that they describe certificate signed by ESET in 2017.
I also checked compiler, which generated the file. With a high degree of probability it's MSVC v.11. According to entropy analysis the file is not packed and it's quite possible the file was generated with default configuration of MSVC. Sha-256 of binary is e3ea2ca8a1ab8500c71cba1298a45efaf516a4d8f99e7f1c17db870c9aa344d9. VirusTotal considered the file harmless. Next I used strings program, but I did not found any unique and interesting strings. Is that means strings are encrypted? Later I checked functions imported by the binary. IAT looks like in any typical application. Some functions that I found important are highlighted on the screen below. Two of them I couldn't fit on the same one screen but their names are GetCurrentProcess and GetProcAddress. Of course these functions could be used by compiler itself (e.g in program prolog) but IsDebuggerPresent function on the other hand may be used explicit to prevent inexperienced analysts from debugging.
Static analysis with the help of debugger
I statically analysed this crackme using IDA 7.0 before executing this binary to get to know what's generally is going under the hood.
Binary firstly checks if it is attached to debugger. Later on function called by me 'szyfrowanie' XORs data from address 0x418000 with dynamically generated key (other function).
Shortly explaining this function takes value of first character of key and length of it. Each next value of character is computed by adding 3 to previous value of character (in this case 0x25,0x28,0x2b…))
Later decrypted data is printed out by WriteConsoleA and again encrypted to not remain decrypted in memory. 'szyfrowanie' uses XOR, so as we know XORing something twice will result in the same data.
Crackme ask us to input a password as shown above. After providing it application takes number of milliseconds that have elapsed since the system was started using GetTickCount to be used later. Password is validated by checking conditions on image above. Generally we can think of it as set of equations in which variables takes values from characters of password. Program checks 6 equations but there is 10 variable values to compute so at this point we are not able to recover valid password. I jumped over some code looking just at the graph of basic blocks and I realized that function 'szyfrowanie' is used later in code. Taking in consideration unnecessity of valid password to use 'szyfrowanie' I jumped over process of checking password by modifying EFLAGS register using debugger (ZF flag of it excatly) and named interesting blocks of code
('hint' and 'wrong_pasword')
First of them is responsible for printing out 'Wrong password', and the other one shows us hint which can be seen below.
Having 4 more equations we are able to solve the set of equations. For that purpose I used for that python language with Z3 framework by Microsoft.
And the password is:
Important things I want to highlight are two anti-debug protections. We can see them in screen below
Block of code starting at 0x401622 saves pointer to Process Environment Block which is save in FS segment register shifted by 0x30 bytes (FS:0x30). Next instruction checks PEB's second boolean variable which is set if process is debugged. (isBeingDebugged used before refers to the same variable)
Next block of code (starting at 0x40163A) checks whether since providing password until its checking elapsed less than 64 milliseconds, which is false for sure. By bypassing these protections against debugging we can freely analyse application code.
Password we provided is used for calculating hash. C code of this hashing algorithm is shown below.
For password 'Pr0m3theUs' execution flows to ' win' block. String informing us about valid password is decrypted using password we provided by XOR. Message is printed out by WriteConsoleA, then decrypted back. It inform us that password is valid but we need to decrypt other piece of data to get what we need.
Using IDA I found piece of data which is not referenced anywhere, but it is close (meaning offsets) to other strings used in program. I copied binary representation of the data and decrypted using found password.
I wrote simple python script that automates decrypting this message and I placed it below.
Decrypted message is URL to next crackme.
crackme.zip (second crackme)
File downloaded is valid zip archive containing two files: EsetCrackme2015.exe (14 KB) and EsetCrackme2015.dll (654 KB). I did analysed entropy of these files. Histograms shown below.
Histogram for .dll
Histogram for .exe
EsetCrackme2015.dll for almost every 256 bytes sample has entropy reaching up to 97 %, so file is strongly encrypted. I suspect there is small piece of code (stub) in that which decrypts other data or even executables judging by filesize. Almost whole file consists of its data section. At the end of file there is certificate.
Exeinfo informs 'Self Write code ! unknown protection', so this binary could be doing something nasty or just program cannot determine known packer.
EsetCrackme2015.exe looks like it contains only executable code with certificate.
Both binaries ale valid PE32 executables.
What's interesting DLL does not export or import any functions. It's quite unusual and looking suspicious. Binary will probably refer to PEB (Process Environment Block) saved at offset 0x30 in FS segment register to gather all needed imports runtime.
Exe file imports only 5 files. Most interesting of them is LoadLibraryA. It could be used for load attached DLL file, or other needed libraries. Looking for human readable string I could not find anything interesting.
I parsed the provided files by binwalk, but there are any other structures than usual ones for PE files.
.exe file analysis
For whole analysis I was using interchangeable IDA and x32dbg. Code in entry point has simple layout of blocks.
Program makes sure there is only one instance of itself running. It is checked by trying to acquire mutex named 'EsetCrackme2015'. In the case of failure program knows there is another instance running in OS. Then crackme checks path for executable that started current process by calling GetGetModuleFileNameA. Later on simply by using mov mnemonic there is exchanged 'exe' string with 'dll' in path to executable got earlier. Then this string is used to load into address space library EsetCrackme2015.dll as I presumed before.
.dll stub analysis
The only function that I can analyse at this point is DllEntryPoint, because further code is likely to be decrypted runtime later. Analysing code of stub I came to following conclusion:
DllEntryPoint for 'EsetCrackme2015.dll' finds entrypoint for module that loaded it, so in this case it is 'EsetCrackme2015.exe'. When it is found then it proceed to find unique 12 bytes (action called egg hunting) determining function from EsetCrackme2015.exe which then calls (address 0x401E9F VA). This function I'm going to analyse in subsequent part in this paper. List of steps to achieve described actions:
- Get from segment register FS:[0x30] address to PEB (Process Environment Block);
- From PEB at offset 0xC get pointer to structure PPEB_LDR_DATA;
- From PPEB_LDR_DATA get head of double linked list containing entries of type LDR_DATA_TABLE_ENTRY. Each item of this type contains information about module loaded into adress space. We can use double linked list to iterate over all modules;
- Get first module name and compare it with string 'exe'. Technically there is computed FNV hash for checked module name and compared with hash for 'EsetCrackme2015.exe'. I recognized this by constant values used by this hashing algorithm 0x1000193 and 0x811c9dc5;
- Get base address of the module;
- Increment (base address + 0x1000) till next 12 bytes are equal to '\x05\x14\x12\xfa\xde\x69\xc0\x1f\xa1\x5f\x32\xc3';
- Get address just after found bytes;
- Call function at computed earlier address.
After all these steps execution flow take us to EsetCrackme2015.exe module.
Let's prepare environment
Function, that we jump from DLL code is called by me 'JumpedFromDLL'. Firstly it gets base address of kernel32.dll. 'getFunc2' returns address of Windows API function shifted by 2 bytes. First argument is FNV hash of function we want to get and second (in ESI register) base address of library where to search. 'JumpedFromDLL' then gets Sleep and CreateThread functions using 'getFunc2' from kernel32.dll. Calling CreateThread creates new thread with entry point function that I named 'newThreadAddr'. Argument to pass through to new thread is address of Sleep function. 'get_kernel32addr' get address of PEB. Then reads its structures, get module names, calculate FNV hash of them and compares with 0x29CDD463, which corresponds to 'kernel32.dll'. If they matches function returns image base of this module. During the analysis I noticed there are a lot cross references to 'getFunc2' and I wrote script to automate process of reverse lookup of function names. Script puts comment with name of desired function next to every function 'getFunc2' occurrence. Script needs binary to find functions. I assumed all functions are from kernel32.dll and in fact all of them were found. 'newThreadAddr' checks whether 'EsetCrackme2015.dll' is loaded into address space of process; If not then it calls Sleep function which address was handed over before. If that's true then it calls 'setup_playground', which prepares most things that are needed for this crackme to run.
setup_playground, let's the game begin
In this function there is created new frontend process which handles GUI for this crackme and established communication between 'EsetCrackme2015.exe' and frontend itself (Inter Process Communication). File 'EsetCrackme2015.dll' except stub consists of resources, each of them has its own ID (2 bytes) and size (4 bytes). These resources includes passwords, encryption keys and next binaries - generally speaking all data that is obligatory to build structure of crackme. In adress space there is created global pointer in order to save important addresses and states of application. I called it 'offsets'. 'setup_playground' creates event (CreateEventA), which is later used later for communication during passwords checking. Then new thread is created handling resource requests and simple signals about state (function 'handleRequests').
Requests, responses, resources
HandleRequests at first searches by 'eggHunt' resource with ID equal to 2 (packed into 'EsetCrackme2015.dll'). 'EggHunt' traverses all resources within file by getting their size and add this value to current file offset. If ID is equal to requested then address of resource is returned. Resource with ID equal to 2 is path to named pipe, which is used for transferring data between processes.
Path is exactly '//./pipe/EsetCrackmePipe', which is decrypted by XOR with key 'PIPE'. Transferring data works in client-server architecture. Client sends 2 packets: first is type of request (0x1 - data transfer, 0x2 - send signal) and second one with ID of resource/signal. Server in case of data transfer responds the same two packets, third packet contains 4 bytes size of data and fourth data requested.
On screenshoot below side we can see functions used to response request.
Firstly there is opened pipe by 'CreateNamedPipeA' then connected to other side by 'ConnectNamedPipe'. If there is some problem error is checking by 'GetLastError'. Then function reads data request data: request type (values 1 or 2) and its ID. Depending on them 'responseRequest' reply with requested data or response to signal. 'responseRequest' at first replies with type of request and its ID, then depending on request type takes appropriate actions. For type equals 1 it finds by 'eggHunt' resource address, then sends its size and data. Block diagram on next page.
For type equals to 2 signal for valid password entered is served (meaning back-end). 'handle_type_2_request' firstly checks by value of [offsets+0x10b] state whether frontend of crackme is running. If so then save to specific memory address value of ID placing them next to each other like in array. 'offsets' is used to get memory addresses to write.
Next use 'SetEvent' to trigger earlier created event from 'setup_playground', which is going to be served in another thread. Requesting signal or data results in sending to client message 'OK' or 'ER' giving feedback.
Back to 'setup_playground'
When thread to communicate between processes is created next step is to save addresses of 'decryptResource' and 'getFunc2Overlay' to memory obtained by 'offsets' and find resources with IDs 0x3 and 0x101.
Resource with ID equals to 0x3 is decrypted by XOR with key '!This program cannot be run in DOS mode', which resides in any not by hand modified PE executable. In this case it is retrieved from 'kernel32.dll' executable which is present in address space. Decrypted resource 0x3 is base64 string 'SXJyZW4lMjBpc3QlMjBtZW5zY2hsaWNo'. However it's not converted to plain text anywhere. The string is passed to function responsible for decrypt resource 0x101. I left in-depth analysis for this function and treat it as black-box. Function gets base64 as key and decrypt resource.
Resource with ID 0x101 is decrypted (by function 'decrypt_binary'). It is valid PE32 executable with relocations, which are applied by function 'apply_relocations'. This binary is frontend of crackme. At address 0x402262 there is instruction obtaining entry address of binary from resource 0x101; virtual address of this entry point in my case is 0x10000CDD. Then this pointer is saved to local variable 'code'. I dumped (using x32dbg) newly decrypted executable as 'front-end.exe' for later analysis on disk.
We are moving to loop that iterate until the frontend is running (for flag [offsets+0x108] equals 0 it is active, for 1 inactive) and sends signals. Block of code with name 'wait_for_event_from_front_end' waits for event and then returns back to beginning of the loop. If the frontend was shut down or all conditions to complete crackme were met then function is exiting and thread for handling requests is terminated as well. At 0x402278 there is call to the frontend executable at its entry point got before (0x10000CDD). Function at this address is called by me 'checks_n_hollowing', which simplified block diagram is shown on next page. Main task earlier mentioned function is to perform process hollowing. Generally this technique is based on creating a seemingly innocent process in a suspended state then copy code contents into it and resume the process. To achieve this following steps are taken. Get resources with IDs ID 0x102, 0x103 and 0x104. Resource 0x102 is position independent code for process hollowing (PIC, using relative addressing, tricks like call $+5, pop eax). Pointer to resource 0x103 is passed as argument to this code. At first it creates new process 'svchost.exe' (with flag CREATE_SUSPENDED). Code is quite complicated for me so I took dynamic approach. I set breakpoints on suspected API functions responsible for operating on processes and I was able to determine steps took by process hollowing code.
Create new 'svchost.exe' process (using Create ProcessA);
- Save current context of 'svchost.exe' (using GetThreadContext); 3.Allocate big chunk of data and copy there frontend binary. (VirtualAlloc and WriteProcessMemory);
- Set new context (set new EIP using SetThreadContext);
- Resume process at frontend entry point (ResumeThread).
'checks_n_hollowing' checks whether there were send 3 signals about validity of passwords (ID: 0xBB01, 0xBB02, 0xBB03) and additional 0xFF01. If all signals were delivered message 'That's all, congratulations' by MessageBox is displayed.
As I learned whole frontend binary was copied to new process 'svchost.exe' so I dumped its content into 'front-end.exe' for later analysis in IDA.
Frontend, windows internals
Entry point for this binary is WinMain, so we we can assume that it is GUI only application. This function only calls DialogBoxParamW, which creates window using resource included in binary that describe its appearance (argument 'lpTemplateName' that equals 0x67 means we refer to resource ID 0x67). Most important argument is pointer to function 'DialogFunc'. This function is called whenever event connected with this window is received.
'DialogFunc' takes four arguments, first is handle to dialog box, second type of message as integer, third and fourth additional data depending upon message type.
In function 'DialogFunc' shown partially below we can see three lstrcmpA functions called, each of them is responsible for other password. From each password there is calculated its SHA1 hash and compared to valid ones. Of course I found valid SHA1 hashes but it is impossible to reverse them into plaintext.
When message type sent to 'DialogFunc' is WM_INITDIALOG (signal sent before creating window) then new thread is created. Its task is to change function that handles event of class 'EDIT' (that is first textfield to type in password) with 'custom_first_passwd_windowproc' function. It is done by using SetWindowLongA function.
Newly assigned function checks whether event dispatched is WM_GETTEXT. Generally it intercepts event from ‚DialogFunc' and returns another string there. If so then gets string of first password typed into this textfield (maximum 0x40 bytes), then converts it to its base64 representation. This representation is then slightly modified: every even character within string is decremented (meaning ASCII values).
If we compare this string with valid one ([offsets+0x118]) using 'lstrcmp' then signal about valid password is sent to backend (signal ID 0xBB01).
Valid modified base64 representation of password is earlier got by request to resource 0xBB02. During dynamic analysis I copied valid modified base64 representation of first password 'RFV1aV4fQ1FydFxk' ('front-end.exe', 0x401734) after decrypting with XOR in function 'get_sha1_of_passwd_and_custom_base64_first_passwd' ('front-end.exe'). To acquire plaintext of password I wrote one liner in python (maybe a little bit exaggerated but works :) ).
When 'check' button is clicked it calls 'get_string_from_windowtext_and_generate_sha1', then WM_GETTEXT message is intercepted and signal for good password is sent. When it returns to DialogFunc it is also checked whether SHA1 hashes of passwords match, if so then it send the same 0xBB01 signal, so two signals are sent. Signal is received in module 'EsetCrackme2015.exe' at address 0x4022C5 (with WaitForSingleObject function), which calls again 'checks_n_following'. This one unpacks next resource (0x152) 'drv.zip'. After unpacking archive we get driver with instruction. Unfortunately I was not able to analyse it well.
Task demanded from me in depth analysis most of the functions included in binaries. It was crucial in order to get to know principles of operation of this crackme. For this analysis I used mainly IDA, x32dbg for code understanding and Synalyze It hex editor when dealing with binary files to check PE structures of binaries. Simultaneously using deasembler and debugger I note interesting variables or offsets to my notepad. During dynamic analysis I check register values and dumps of memory regions. By this I conclude how application works, and by naming functions, variables or adding comments to assembly lines I make more and more coherent whole. For more complex functions I use decompilers (e.g. this one from Ghidra). First task was not a problem for me, second one turned out to be more complicated but step by step I went through to obtain first password having fun and learning new things about Windows internals.
I add to this document .idb files, extracted binaries and scripts that made analysis easier.