Hook the planet! Solving FlareOn4 Challenge6 with libPeConv
2017-12-02 04:25:59 Author: hshrzd.wordpress.com(查看原文) 阅读量:93 收藏

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.

TL;DR

The end result looks like this:

github  The repository with all the presented loaders (code + compiled binaries) is available here.

The full code of the final loader:

https://github.com/hasherezade/challs/blob/master/FlareOn2017/chall6/peconv_finished_sol/main.cpp

In this post I will to explain in details how I made it, show the experiments and the reasoning behind them.

Tool used

For analyzing the crackme:

For building the solution:

Overview

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 EntryPoint:

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: rundll32.exe payload.dll,#1:

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:

https://github.com/hasherezade/challs/blob/master/FlareOn2017/chall6/basic_ldr/main.cpp

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 0x5D30:

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 0x4710:

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:

loader1

Everything looks good and should work, but when I run it, I met an unpleasant surprise:

error

When we try to debug the code, we will find that the exception is thrown from inside the statically linked functions srand() and rand(). They were used by the function dexor_chunk_index, within the function to_overwrite_memory that we imported from payload.dll.

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:

crt_init.png

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:

crt_uninit.png

Its prototype is:

char __fastcall _scrt_uninitialize_crt(__int64 a1, __int64 a2);

We need to call them appropriately before and after our actions. Example:

fetch_and_release_crt.png

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.:

to_my_rand

Now, instead of running silently, it will print a value each time when the rand was called:

rand_results

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.
Instead of GetProcAddress, that I would use on the module loaded in a typical way, I used a function from PeConv with analogical API:

peconv::get_exported_func(loaded_pe, MAKEINTRESOURCE(1));

And this is the code of the full loader, this time using libPeConv:
https://github.com/hasherezade/challs/blob/master/FlareOn2017/chall6/peconv_basic_ldr/main.cpp

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:

leggykickedflutters

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:

get_exp

This is the improved version of loader:

https://github.com/hasherezade/challs/blob/master/FlareOn2017/chall6/peconv_autofill_ldr/main.cpp

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:

calc_index_func

We will use exactly the same function as we used before, to redirect the statically linked rand and srand:

peconv::redirect_to_local64

All we need to prepare is our own function returning the index. For example, we can enforce it to return some hardcoded index:

redirect1

And it works!

return_chunk_10

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:

https://github.com/hasherezade/challs/blob/master/FlareOn2017/chall6/peconv_basic_sol/main.cpp

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 my_MessageBoxA:

hooking_res

For example, we can replace it by the following function:

my_msgbox

Now, instead of displaying the MessageBox, with the character written in hex…

key_part_before

…it will display the same character as ASCII:

after

This is the code of the full loader:
https://github.com/hasherezade/challs/blob/master/FlareOn2017/chall6/peconv_hooked_msgbox_sol/main.cpp

We can use it along with a small batch script:

https://github.com/hasherezade/challs/blob/master/FlareOn2017/chall6/peconv_hooked_msgbox_sol/run.bat

@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:

result_batch

In the final version, the key is composed by the application itself:

https://github.com/hasherezade/challs/blob/master/FlareOn2017/chall6/peconv_finished_sol/main.cpp

Conclusion

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”.

Appendix

See also other approaches:


文章来源: https://hshrzd.wordpress.com/2017/12/01/hook-the-planet-solving-flareon4-challenge6-with-libpeconv/
如有侵权请联系:admin#unsafe.sh