Recently I started making a small library for loading and manipulating PE files (libpeconv – it’s open source, available on my GitHub). In my previous post, I demonstrated how the Challenge 3 from FlareOn4 could be solved with it’s help: I used libPeConv to import the function from the original crackme, so that it can be used as local – without the need of re-implementing it or emulating.
This time, we will have a closer look at challenge 6 from FlareOn4. This challenge is a bit more difficult, so it is a good opportunity to show some other capabilities of libPeConv – not only importing functions, but also hooking the imported code in various ways.
When I solved this crackme for the first time, during the FlareOn competition, my approach was very dirty – it required me to go through 26 MessageBoxes, write down each value, convert them from hex to ASCII and put them together to make the full flag – oh, my! Looking at the write-ups afterwards, I noticed that most of the people did it this way (check appendix for more details). But I was sure that there must be a better solution – and with the help of libPeConv, I finally did it in a way in which I wanted: no pop-ups to click, the flag is automatically composed by the loader.
The end result looks like this:
The repository with all the presented loaders (code + compiled binaries) is available here.
The full code of the final loader:
In this post I will to explain in details how I made it, show the experiments and the reasoning behind them.
For analyzing the crackme:
- IDA + HexRays decompiler (optional)
- PE-sieve (aka hook_finder) – for dumping modified PE
For building the solution:
- Visual Studio + CMake
The challenge named
payload.dll is a 64bit PE file. When we look at it’s export table, we can find that it exports one function, named
But if we try to run it in a typical way, by
rundll32.exe payload.dll,EntryPoint, it turns out that the function cannot be found:
Pretty weird… So, let’s try to run it by ordinal:
This way works – however still we are far from getting the flag.
The curious thing is why the exported function was not found by the name? It seems that the export name has been overwritten while the DLL was loading. To check it very fast, we can use PE-sieve, a tool that detects modifications of the running PE vs the PE on the disk.
I called the function again by the ordinal, and when the MessageBox popped up, I scanned the running rundll32.exe process by PE-sieve. Indeed we can see that the DLL was overwritten:
The modified image has been automatically dumped by the PE-sieve, so we can open it by typical tools. First, I use PE-bear to take a look at the exports table:
And yes, now it looks very different… Let’s see this function in IDA.
Looking inside we can confirm that this was the function responsible for displaying the message that saw before:
This message is displayed when the function is called without any parameters. If, in contrast, it is called with proper parameters, some further chunk of code is decrypted and executed:
The name of the function is used as the key for the decryption. So, what are the conditions that the supplied arguments must fulfill?
The exported function expects 4 arguments:
As we can see, the checked argument is the third one of the arguments supplied:
It is compared against the function name. If it is exactly the same as the function name, the decryption proceeds – otherwise, the fail MessageBox (“Insert clever message…”) is shown.
Let’s run the function with proper parameters and see what happens.
This is a small wrapper that will help us call this function from our code:
We can do the same from the command line:
rundll32.exe payload.dll [func_name] [checked_str]
Cool, a new message popped up. It seems to be a chunk of the key: 0x75 -> ASCII ‘u’. But this is just one of the pieces, and we have to get the full key.
For this purpose, we will look inside the DLL to find the the code that was responsible for overwriting the exports table. That function starts at RVA
This is the pseudocode:
It decrypts the code chunk pointed by the given index and redirects to the new exported function. We want to manipulate the indexes in order to get all the remaining key parts. The index of the chunk is calculated basing on the current time, inside the function at RVA
We can see operation modulo 26, so it means it is the maximal value. There are 26 possible indexed -> 26 pieces of the key. The calculated index is then supplied to the decrypting function. The decrypting function is pretty simple – based on XOR:
First, the random generator is initialized basing on the supplied chunk_index + a constant. Then, pseudo-random values, retrieved by
rand() are used as the XOR key. Thanks to the feature of this (weak) random generator, values are not really random – the same seed gives always the same sequence, so it works pretty well as the key.
The simplest to implement (and terribly annoying to execute) approach to solve this task is to keep changing the system time, running the DLL and writing down the popping up chunks of the key. Sounds too ugly? Let’s see what libPeConv can do about it…
Importing and hooking function with LibPeConv
Preparations and tests
In the previous post I gave some overview of PeConv library, so if you didn’t read that part, please take a look. This time, I will use the features that I introduced before, plus some others. We will not only import and use the code of the original crackme, but also mix it with our own code, to alter some behaviors.
First, I want to import from the crackme the function that overwrites the exports. This function has at RVA
0x5D30 and it’s prototype is:
__int64 __fastcall to_overwrite_mem(__int64 a1);
Let’s make a loader that will load the crackme to the current process. This was my first version:
Everything looks good and should work, but when I run it, I met an unpleasant surprise:
When we try to debug the code, we will find that the exception is thrown from inside the statically linked functions
rand(). They were used by the function
dexor_chunk_index, within the function
to_overwrite_memory that we imported from
This happened due to the fact that this DLL requires that the CRT should be initialized prior to use. It is easy to fix – we just need to find the proper functions, responsible for the CRT initialization, and then call them manually. Let’s have a look in IDA – it should automatically recognize and name the functions related to CRT.
Function for CRT initialization has an offset 0x664C:
Its prototype is:
char __fastcall _scrt_initialize_crt(int a1);
And the function for releasing CRT (we need to call it at the end, in order to avoid stability issues in our application) has an offset 0x6824:
Its prototype is:
char __fastcall _scrt_uninitialize_crt(__int64 a1, __int64 a2);
We need to call them appropriately before and after our actions. Example:
And now everything works smoothly without any crashes.
Eventually, if for some reason we don’t want to, or cannot initialize CRT, we can redirect the needed CRT functions to their local copies:
If we want to log the rand values, instead of making redirection to the original function, we can redirect to our own wrapper, i.e.:
Now, instead of running silently, it will print a value each time when the
rand was called:
Now, let’s test if the exported function has been overwritten properly. If so, we should be able to use it analogically like in the case of the previous basic loader.
GetProcAddress, that I would use on the module loaded in a typical way, I used a function from PeConv with analogical API:
And this is the code of the full loader, this time using libPeConv:
And yes, it works exactly the same:
As we know, the argument that we supply to the function changes depending on the current month and year. For December 2017 it is:
But it would be nice if our loader can fill it automatically.
This argument must me exactly the same as the exported function name. We can take advantage of this and use a libPeConv’ feature of listing exported function names. Code:
This is the improved version of loader:
Everything works fine:
Ok, tests and preparations are over, now is time for the solution.
Manipulating the chunk index
We have everything ready to start manipulating the index. There are various approaches – one of them is to hook the imported function
GetSystemTime. But with libPeConv we can hook also local functions, so let’s make it even simpler.
The function that calculates the index can be found in the payload.dll at RVA 0x4710:
We will use exactly the same function as we used before, to redirect the statically linked
All we need to prepare is our own function returning the index. For example, we can enforce it to return some hardcoded index:
And it works!
But recompiling the loader each time when we want to change the index is not a good idea. So, I made an improved version of the loader that allows you view the chunk at the index supplied as the argument. Code of the loader:
See it in action:
By this way, we can retrieve all the key pieces one by one. But still, we need to encounter those annoying MessageBoxes. Let’s replace them and redirect the message that was going to be displayed to our own function. This time we will be hooking a function linked dynamically – so, installation of the hook will be a bit different.
Hooking IAT with libPeConv
When libPeConv loads executable, it resolves each imported function with the help of a specially dedicated class: peconv::t_function_resolver. The library allows also to use non-standard resolvers instead of the default one. The only condition is that they have to inherit from this base class.
One of the resolvers that comes in the full package is a
hooking_func_resolver. It allows to hook IAT. Basically, when it loads imports, it may substitute some of the imported functions by our own functions (the only condition is that they must have the same API). For now, this resolver supports replacing functions defined by names. So, for example if we want to replace
MessageBoxA by our own
For example, we can replace it by the following function:
Now, instead of displaying the
MessageBox, with the character written in hex…
…it will display the same character as ASCII:
This is the code of the full loader:
We can use it along with a small batch script:
@echo off set loopcount=0 :loop peconv_hooked_msgbox_sol.exe payload.dll %loopcount% set /a loopcount=loopcount+1 if %loopcount%==26 goto exitloop goto loop :exitloop echo. pause
As the result we get the full flag printed and 0 annoying pop-ups:
In the final version, the key is composed by the application itself:
LibPeConv is my new project, still on very early stage of development, so many things may change – but it already proven that it can be useful in solving some challenges. It gives you possibility not only to import code of other executables to your projects, but also to hook it and modify. I hope you will try it and have so much fun with it as I have developing it. I am looking forward to hear some feedback from you!
All the binaries that were used in this demo are here – the password to the zip is “crackme”.
See also other approaches: