Flare-On 6 (tasks 10-12)

Flare-On 6

Flare-On Challenge is an annual competition organized by FireEye (the FLARE team). It is like a marathon of reverse engineering. Each year we get 12 crackmes of increasing difficulty to solve. You can download the tasks here.

This year I finished as 106.

In this post I will describe the last 3 tasks of the competition:

WARNING: Work in progress. I will be adding more details to this post.


Task 10 – “Mugatu”

[Mugatu.7z; password: flare]

In this task we get an EXE (Mugatu.exe) and two encrypted GIFs: best.gif.Mugatu, the_key_to_success_0000.gif.Mugatu.

files.png

The EXE is a ransomware, and the two GIFs are encrypted by it. We are supposed to decrypt one of those GIFs (best.gif.Mugatu) in order to get the flag.

The EXE is slightly obfuscated. For example, the Imports are replaced at runtime by some other imports. So, analyzing it statically we may get confused. In order to analyze it statically with a valid result, we should recover its real imports first. In order to do this, we can just dump it from memory once it is run by any dumper that can reconstruct the imports. In my opinion, the best for this task is Scylla. Once we have the main exe dumped with proper imports reconstructed, it becomes much more readable.

Inside the main executable there is a payload, that is the core of the ransomware. It is manually loaded by the main EXE. We can unpack this DLL statically in the following way:

  • in the resources of the main EXE there are 2 bitmaps on the same size.
    mugatu_bitmaps
  • We need to XOR one with another. (I did it using: dexor.py )
  • As a result, we will get an executable (with some padding at the beginning).
    decoded_dll
  • We need to remove the padding, and that’s how we’ve got the resulting DLL, named Derelicte.dll.dll_name

However, it is not that simple. If we extracted the DLL statically, we will find that its imports don’t make much sense. It is because the main EXE replaces them on load. So, we need to find the valid imports for this DLL.

We can see the fragment of EXE’s code where the DLL is manually loaded.

The imports are loaded in an obfuscated way, that makes them quite difficult to reconstruct. They are not filled directly to the thunks, but into a proxy list, that looks in the following way:

Due to the used import obfuscation, the previous simple trick of running it and dumping won’t work again. We could try to deobfuscate them from the memory, but the better approach is to patch the loader, and just prevent the obfuscation from being applied.

Let’s take a look at the function that do the import loading. Just after the Import address is fetched, it is obfuscated:

It is being filled in the chunk of the emitted code:

In order to prevent the obfuscation, I applied some patches in the loader:

1) do not obfuscate the import address

patch0.png

2) write the import address directly to the thunk, not to the proxy

patches1.png

Then I dumped it with PE-sieve with option imp 3 (complete Import Table reconstruction). As a result I got a valid DLL that I could easily analyze statically. The import table reconstructed by PE-sieve:
valid_import_table

After the DLL is manually loaded within the main EXE, it’s Entry Point (the DllMain function) is being called:

Then, an exported function is being called, with a parameter “CrazyPills!!!”:

call_export.png

Once we follow this function in a DLL, we will see the logic responsible for encrypting files.

The function that does the encryption is not called directly, but via obfuscated callback:

callback_used.png

This callback is deobfuscated by XOR with the argument supplied to the function:

deobfuscate_callback.png

By following it in the debugger to the place where the deobfuscation is done, we can see the address of the callback function:


The callback is the function at RVA = 0x16b9.

We can follow it in IDA:

key_val

If we analyze it closer, we will find that it is an XTEA algorithm, but with few modifications. First we need to write a decrypting function for it.

I found this implementation very helpful to base my decryptor upon. The few things that are changed comparing to this implementation are: the delta, and the key buffer type (in the original implementation the key is an array of DWORDs). The second modification makes the strength of the crypto significantly lower: the key has only  4 BYTEs, not 4 DWORDs as in the valid implementation, so it is easy to be bruteforced.

The solution to this task:

https://github.com/hasherezade/flareon2019/blob/master/task10/task10_sol.cpp

mugatu_flag.png


Task 11 – “vv_max”

[vv_max.7z; password: flare]

In this task we are facing a Virtual Machine, using AVX2 instructions. That’s why it will not work on some older processors which have no AVX2 support. If we try to run it on such processor we get the following message: “Your processor/OS is ‘too old'”.

too_old.png

If the machine supports AVX2, it passes the verification, and prints “Nope!” in case of a wrong input.

Overview of the main function responsible for verifying the arguments.

The function that I renamed to “vm_process_bytecode” is responsible for calculating some “hash” from the input. Then in the function “vm_check_flag” this “hash” is being compared to a hardcoded one.

Inside this function “vm_check_flag”:

At this moment we know that the crackme expects 2 commandline arguments. The first one must be “FLARE2019”, the second: a 32 bit long string. The second argument is processed by a function implemented by the VM, and the result is compared with a hardcoded “hash” that is 24 bytes long.

The fragment of code responsible for making the comparison:

The valid “hash”:

70 70 B2 AC 01 D2 5E 61 0A A7 2A A8 08 1C 86 1A E8 45 C8 29 B2 F3 A1 1E

Rather than analyzing the functionality in the details, I decided to treat the VM as a black-box, and make some tests, checking how the output changes depending on the given input.

I noticed that the input is processed in chunks. A single chunk of 4 bytes gives 3 bytes of the output. Also, I understood that it is not a hash, but rather some encoding, because a change in a single byte of the the chunk content was not fully changing the output content.

At this moment I decided that I  will try to brutforce the solution, by finding appropriate chunk of the input for each chunk of the output. Yet, I wanted to avoid re-implementing the full VM, so I decided to go for some sort of instrumentation of the original code.

After trying various options, I decided to use the patched version of the original sample. I patched in this way that the returned value (DWORD) will contain the selected bytes of the output.

The modified version of the  “vm_check_flag” function:

I removed the part responsible for comparing the “hash” calculated from the input with the hardcoded one. Instead, we will just copy its chunk into EAX register. Then, we need to NOP out the code that sets the EAX register to 0.

Let’s test the prepared sample using one of the saved input-output sets. We will be checking the output chunk with the help of a command:

echo %errorlevel%

Example:

Input: "01111111111111111111111111111119"
Output: D3 5D 75 D7 5D 75 D7 5D 75 D7 5D 75 D7 5D 75 D7 5D 75 D7 5D 75 D7 5D 7D

DWORD=-680174125 -> D7755DD3 (little endian) -> D3 5D 75 D7

As we can see, the returned value is valid.

Now we just need to write a brutforcing application that will integrate the patched module, and crack the full value, chunk by chunk. After each iteration we need to patch the app again and change the ECX value, in order to advance to the next chunk. So, in the first round ECX = 0.

Since each output chunk is 3 bytes long, we will use only 3 bytes from the returned DWORD. Also, the value of the ECX will be advancing in the increments of 3.

During the tests with the brutforcer I noticed, that rather than trying to crack the 4 byte long input chunk as a whole, I should crack it by finding:

  1. 1-st byte of the input that gives the 1-st byte of the output
  2. 4-th (last) byte of the output hat gives the 3-rd (last) byte of the output
  3. two middle values of the input (2nd and 3rd) that gives middle (3rd) value of the output

Finding this was able to speed up the cracking time a lot. I also automated the process of patching the ECX in the modified vv_max executable.

This is the complete solution:

https://github.com/hasherezade/flareon2019/tree/master/task11

cracking
During the process of cracking I started to notice that the output looks like something familiar… Yes, it is Base64! I noticed it too late – but from the other hand side it was so much fun to write this crazy bruteforcer for it!


Task 12 – “help”

In this task we receive a memory dump: help.dmp, along with a pcap: help.pcap. Both have been captured on the infected system. Our task is to analyze the infection.

I started by using volatility. First, I found what was the profile appropriate to analyze the given OS. For some reason volatility detected it as Windows 10 64 bit. However, loading the dump with this profile resulted in errors. Most of the volatility functions were not working. It turned the OS is just detected wrongly. If we open the same dump in WinDbg, we see that in reality it is Windows 7 SP1 64bit. We needed to manually find the appropriate volatility profile. The one that turned out to be valid is:

Win7SP1x64_23418

Finally, after this change, we could see a significant improvement, and volatility started to work as it was supposed to.

I poked around, listing processes, network connections, drivers… One thing that drawn my attention was a driver man.sys, with a path containing “Flare On 2019” keywords.

volatility -f help.dmp --profile=Win7SP1x64_23418 modules

Volatility Foundation Volatility Framework 2.6
Offset(V)          Name                 Base                             Size File
------------------ -------------------- ------------------ ------------------ ----
0xfffffa800183e890 ntoskrnl.exe         0xfffff80002a49000           0x5e7000 \SystemRoot\system32\ntoskrnl.exe
0xfffffa800183e7a0 hal.dll              0xfffff80002a00000            0x49000 \SystemRoot\system32\hal.dll
0xfffffa800183e6c0 kdcom.dll            0xfffff80000bac000            0x2a000 \SystemRoot\system32\kdcom.dll
...
0xfffffa80039c4630 bthpan.sys           0xfffff880032c8000            0x20000 \SystemRoot\system32\DRIVERS\bthpan.sys
0xfffffa800428ff30 man.sys              0xfffff880033bc000             0xf000 \??\C:\Users\FLARE ON 2019\Desktop\man.sys

Unfortunately we cannot dump it by volatility, because its header is erased. So, I loaded the same dump to WinDbg and dumped it using .writemem:

.writemem C:\dumps\man1.bin fffff880`033bc000 fffff880`033cb000

Since the driver has no header, we need to reconstruct it manually. We don’t need to get all the sections right – we need just basic things to make it suitable for static analysis. The most important is to get the imports right.
First, I copied the PE-header from another driver – I used it as a base on which I started to rebuild. Then, I reviewed the file in a hexeditor, in search for familiar patterns. I could distinguish two sections, so I added their headers:
man_sys_sec
I noticed where the list of the imported DLLs is located, and tried to find the beginning of the structure, in order to fill it in the Data Directory.
My final version of Data Directory has the following form:
data_dir
Now we can open the file in IDA, just like any other PE. We still need to find the Entry Point (DriverEntry), and fill it in the header. I found some unreferenced function it at RVA 0x5110 – it is very likely to be the Entry Point:

unreferenced

The full reconstructed Optional Header:
opt_hdr1

Let’s open the driver in IDA again, and analyze what is going on in DriverEntry. We can see that the driver injected something in the process 876:

Let’s dump this full process using volatility, so that we can see what was injected there:

volatility -f help.dmp --profile=Win7SP1x64_23418 memdump -p 876 -D mem_dumps/

Indeed – this element contains other pieces of the “malware”. I carved them out using a hexeditor.

extracted_dlls

Most of the strings used in the “malware” are encrypted with RC4 – each using a different, hardcoded key. The same obfuscation method is used in each module. So, it is useful to make a decoder that would be able to statically deobfuscate it.

We are also given a PCAP file. So, we need to somehow make sense out of the network traffic, and what is its relationship with the found “malware”. The volatility will also be helpful in seeing which process was responsible for what part of the traffic. We can see it using the command:

volatility -f help.dmp --profile=Win7SP1x64_23418 netscan

We can correlate the traffic generated by the svchost (PID 876) with the traffic recorded in the PCAP. Let’s dump the packages and try to decode them. There is a huge amount of the traffic on the port 7777. When we dump those packages, we can see inside some repeating patterns. I visualized one of the dumps (using file2png.py) to get an idea what can possibly be hidden inside. This is the result:

Looking at the visualization we can guess, that it is not a PE file encoded, but rather a bitmap. (If it was a PE the patterns inside would look very different: in that image there is a lot of content that looks to be filled by the same characters – and in PE we would not have so much padding between the sections.)

After finding the proper XOR key (thanks to Mark Lechtik), I got the decoded content, that was indeed a series of bitmaps. As it turned out: screenshots from the infected system.

The screenshots give some very important hints on how is the flag stored. As we can see, it is in the KeyPass database. The masterkey is covered, but we know that it is typed on the screen, so we can suspect that the keylogger component should have caught it. It will probably be sent in some other part of the traffic.

I decided to find the KeyPass database first. I needed to check what exactly was the version of KeyPass. In order to do this, I dumped the KeyPass process. It turned out to be KeyPass 1.37. I installed the same version and checked what is the header for this format. Then, I carved out the valid file with this header.

keys_kdb.png

The next step is to find the password! I confirmed that crypto.dll is the layer that decrypts that part of the traffic (thanks to Alex Polyakov and Alex Skalozub for answering my questions and confirming that this is the good direction to follow). I analyzed the crypto.dll and made a decryptor for the packets.

https://github.com/hasherezade/flareon2019/tree/master/task12

The traffic at the port 8888 contained the keylogged content (captured by keylog.dll), and the traffic on the port 6666 – the stolen files (fetched by filedll.dll). It turned out that the uploded file was keys.kdb – the same file that I carved out from the disk – so it was another way of retrieving this piece. I found that the hashes of both match, so I confirmed that I have the valid kdb. I found also something that looked like the key to the database: “th1sisth33nd111”.

packet_decoded.png

Yet, this key didn’t work!

At this point I wasn’t sure if I went in a good direction, so I asked Alex Polyakov for the hint. He confirmed that this is indeed the key captured by the keylogger, but it is in a bit different form than the key that was typed… I tried to find something similar but yet different recorded in the memory, by grepping through the strings of the main dump (help.dmp). After many failed attempts, I got an idea that the number ‘1’ at the end can be in reality ‘!’. And I tried the following command:

cat help_strings.txt | grep -i 3nd!

I found the following string:

!s_iS_th3_3Nd!!!

Still, it didn’t work… But I noticed that the first characters are missing, so I tried:

Th!s_iS_th3_3Nd!!!

And finally it worked, I got the kdb unlocked and the flag has shown up!

That’s all! I hope you enjoyed my writeup. Sourcodes of all my Flare-On solutions are available here: https://github.com/hasherezade/flareon2019

Thanks to the Flare team for the great contest!

Appendix

FlareOn6 Write-Up of Write-Ups – aggregator and summary of varorious solutions
View at Medium.com

Posted in CrackMe | Tagged , | 1 Comment

Application shimming vs Import Table recovery

In this post I am sharing a case that I investigated recently, during the tests of my application, PE-sieve. It demonstrates how the shims applied by the operating system can disrupt Imports recovery.

Tested features

Recently I had a new release of PE-sieve. One of the added features was a complete Import Table recovery. From now, if you run the PE-sieve with an option /imp 3 it will collect all the addresses that the scanned module imported from the DLLs in the memory, and construct a new Import Table out of them. It is a very useful feature that many PE dumpers have. It helps i.e. to deal with packed applications. Let’s take an example of UPX: it may compress the Import Table of the payload, and load it dynamically during unpacking.

PE-sieve offers also another, “milder” mode of the recovery (/imp 2). In this case PE-sieve bases on the existing import table, and only reconstruct the erased elements. It can be used i.e. in the following case, when the Thunks were overwritten by the functions addresses during the imports loading:

thunks1.png

PE-sieve is able to recognize the exports that are at those addresses, and fills their names back into the table:

fill_back.png

Test cases

I decided to test my application on some ready-made and well-known examples. I selected Anthracene’s unpacking series, available here.

The first sample (1dbfd12ad3ee39930578b949c6899d0a) looks pretty straight-forward. It is a simple application showing a MessageBox, packed with the help of UPX.

Peculiarity

I run this example on one of my Virtual Machines (Windows 8 64 bit) and the imports got recovered flawlessly (video).

Later, I tried to do the same on a different machine, with another set of patches installed. For some reason, I could not reproduce the valid results. Import recovery “magically” stopped working – the received results were incomplete. When I tried to dump the payload with PE-sieve, using the option /imp 2 , all the functions imported from Kernel32.dll got recovered, but the function from User32: MessageBoxA did not.

First I assumed that it must be a bug in PE-sieve, but it turned out that other applications i.e. Scylla have the same problem: they cannot map this address to any of the exported functions.

I investigated the mentioned address under the debugger, and this is what I saw. The call to a MessageBoxA was proxied via apphelp:

The function used from the apphelp was not present in the export table, so it is logical that the applications recovering imports could not recognize it. So, it is not really a bug in the application – but a problem caused by some peculiar way in which this import was loaded.

Explanation

Of course I wanted to understand what is the reason of such behavior. I suspected that there must be some shimming going on, but why did it happen? I checked other applications importing MessageBoxA, but each of them used this function directly from User32.dll, and apphelp.dll was not used as a proxy.

I started thinking that it may be related with the fact that my test case is a very old application.

The OS Version in the PE header is set to 4 (Win 95):

I made an experiment and”upgraded it”, just by changing the version number:

And it worked! After this simple change, the shim was no longer applied. The application used the import directly, and, as a result PE-sieve (and other applications) were able to properly recognize the function.

So, it turned out that the operating system applies this shim automatically for the backward compatibility with old applications.

Now when I think of it, it looks pretty obvious, but it was not so intuitive when I saw it for the first time, that’s why I decided to document this case. So, just a small heads-up: when the import recovery is not going right, first check if shims are not the reason! I hope you enjoyed my small writeup.

 

Posted in Programming, Uncategorized | Tagged , , , , , , | Leave a comment

PE-bear – version 0.3.9 available

[UPDATE] This release introduced some stability issues, fixed in 0.3.9.5

Hello! Several months have passed since I released PE-bear 0.3.8. Since it was my old, abandoned project, I did not plan to start developing it again. Initially, I got convinced to be adding only bugfixes, treating it rather as a legacy app. However, it started doing pretty good for a “dead” project. It got 15K+ new downloads, has been mentioned in some cool presentations, featured on OALabs, and added to FlareVM. It all made me reconsider my decision. Also, I started getting messages from users requesting new features. Finally, I decided to break what I said before, and prepare another release.

The current one (0.3.9) comes with some new features. You can download it from the main site of the project:

https://hshrzd.wordpress.com/pe-bear/

1. Added Rich Header (viewing and editing), with calculated checksum. Preview:

rich_hdr.png

New PE-bear displays all the fields of RichHeader, and allows for their editing. It automatically calculates and verifies the Checksum, so it can help spotting the cases when the Rich Header was forged.

2. Added support for the new fields in Load Config Directory. Preview:

load_config.png

Since PE-bear is a pretty old project, it was not able to parse the full Load Config Directory, but only its basic form, ending on SEHHandlerCount. Now it supports the extensions introduced in Windows 8.1 and Windows 10.

3. In Debug Directory: parse and display RSDSI Table (including PDB path etc):

debug_dir.png

In the old version, Debug Directory was displayed, but without parsing the structure nested inside. Now, one of the most popular types, including PDB path, is also parsed: you can view the project path, and also edit it.

In addition, project underwent some internal refactoring, and I added some other tiny improvements.

I must say I started enjoying working on PE-bear again, and already got several new ideas that I am planning to implement. So, this release is not gonna be the last.

Big thanks to all of you who motivated me to “resurrect” this project. I hope you will enjoy the new version, and the PE-bear’s comeback. As always, I am open for any comments and suggestions.

Posted in PE-bear, Tools | 6 Comments

How to compile a PIN tool using Visual Studio 2017

UPDATE: the described problems in compiling the default PIN projects seems to be fixed in the new PIN release: 3.10.

PIN (of Intel) is a great platform for dynamic binary instrumentation. I use it on daily for tracing and deobfuscating malware, and I often recommend it to others. Unfortunately, figuring out how to set it up is not so straight-forward. If you want to compile the default projects that are distributed in the package, you may get multiple errors.

I never saw the full process of fixing them documented. I struggled with this myself, and from time to time people approach me asking for help. That’s why, I decided to make a walk-through, describing all the steps I did in order to get a tool compiled.

    • Used PIN package:
      • pin-3.7-97619-g0d0c92f4f-msvc-windows (link)
    • Environment:
      • Microsoft Visual Studio Community 2017 (Version 15.6.5)
      • Windows 8.1 64bit

Step 0 – I downloaded the PIN package and unpacked it into C:\pin\C_pin

I will be compiling MyPinTool, that is a part of the PIN Package:

my_pin_tool

Step 1 – I opened the single tool in Visual Studio and tried to compile it.

32bit

I got an error:

error1

So, I searched the pin main directory, and I found where this file is. It was in “C:\pin\extras\xed-ia32\include\xed” (we need to pick a 32 bit version for a 32 bit build).

32_or_64

So, I included that folder:

additional_include.png

[C/C++] -> [General] -> [Additional Include Directories]

Step 2 – I  tried to compile it again and got another error:

safeseh

So, I went to disable SAFESEH. From:

option

[Linker] -> [Advanced] -> [Image Has Safe Exception Handlers]

I switched to:

disable

[Linker] -> [Advanced] -> [Image Has Safe Exception Handlers] -> [No]

Step 3 – Another attempt of compilation, and another set of errors. This time at linking level:

unresolved_externals

I googled for those errors and I found this blog. Following the advice,  I solved it by adding “crtbeginS.obj” to additional dependencies:

dependencies.png

[Linker] -> [Input] -> [Additional Dependencies] -> add: crtbeginS.obj

And finally! It compiled:

result.png

I can only say that it was the nastiest part of PIN, and now it should go much easier. There are various sample projects included in the package, very helpful in learning the functionality.

To make working with it even easier, I made some scripts that are adding PIN along with my favorite tracer to the context menu. Thanks to them, I can start tracing any EXE just by one click. You can find them here.

run_with_pin

Appendix

Posted in Tutorial | 6 Comments

PE-bear – version 0.3.8 available

It has been a long time since I abandoned PE-bear project (version 0.3.7 was released in 2014!). But due to the fact that it still has new downloads, and I keep getting messages from its users, I understood it would be a shame to leave it without any support. A tool is alive as long as someone wants to use it, so, here is an update for PE-bear.

https://hshrzd.wordpress.com/pe-bear/

As I wrote in the release notes, the latest release fixes several bugs . In this post I will elaborate on the most important changes and illustrate them with examples.

  1. Fixed bugs in parsing Delay Load Imports (64bit)

So, this is the old, incorrect version (example: winnet.dll, 64bit)

delayed_imp_old

And in the new, corrected one:

delayed_imp_new2. Fixed bugs in parsing Load Config Directory (64bit)

This is the old, incorrect version:

load_config_old The fields ProcessHeapFlags and ProcessAffinityMask should be flipped, otherwise their sizes are incorrectly identified. It is fixed in the new release:

ld_cfg_new

3. While adding a new section, the selected access rights were applied only if the section was loaded from the file. Also, in some alignments, there was a cave appearing between the previous section and the added one, that needed to be fixed manually in headers, or otherwise the application won’t run. This all is fixed in the current version.

add_section

Section test added by new version:

added_by_new

I fixed also some other, smaller bugs here and there. So if you like PE-bear, it’s time to update. And if you don’t know it yet, feel free to give it a try, because from now onward I am not gonna leave this app without support, and if you find any bug it will be fixed as soon as possible. However, I will do only minimalistic mantainance, so don’t ask me for some super cool new extra features. (Or maybe I get tempted for more… No, I won’t 😉)

 

Posted in PE-bear | 4 Comments

White Rabbit crackme!

UPDATE: We already got the three winners. Good job guys! However, we are waiting for the writeups to select the reward for the best one – so if you are still in between of doing the crackme, don’t give up!

UPDATE2: We got first writeups! All the upcoming ones will be linked under the section “Writeups”. The submission for the contest closes 20th February.

UPDATE3: Contest closed! The winner is @Eleemosynator with his writeup available here. Both writeups were very good and detailed, so we decided that the second one, by @pieceofsummer, also deserved a distinction and a bonus reward. Big thanks to both authors!


This time I would like to introduce a small contest organized by me and Grant Willcox. I wrote a small crackme and he volunteered to sponsor the rewards. The first 3 solutions will be rewarded by books chosen by the winners.

The crackme is a 32bit PE file. It shouldn’t be too difficult, but I didn’t want to make it boring either, so it has few tricks.

rabbit

Disclaimer: I am not an author of the graphics used in the application, such as ASCII arts, icons and others. I don’t claim any rights to them.

Rules:

You need to find the flag in format flag{...} and submit it ASAP to any of us as a DM on twitter (@hasherezade or @tekwizz123). After we announced that the contest is closed, we would like you to make a writeup explaining how did you solved it.

There will be an additional reward for the best writeup – so even if you was not the fastest, you still have a chance to get a book for free.

If you have any questions, you can write them as comments to this post and I will be answering them. I am not giving hints via private messages – I want the contest to be fair for everyone.

At the end I will publish my own writeup with a detailed explanation.

Download:

https://goo.gl/6iG4Ri (password: crackme)

Mind the fact, that the crackme contains some small obfuscation and malware-like tricks, so it may be flagged by some of the AV systems as malicious. False positives are very common when it comes to crackmes – it can’t be helped, sorry! I recommend you to run it on a Virtual Machine.

6iG4Ri.qr

check_mark  Finished? You can rate it!

Writeups:

Posted in CrackMe | Tagged , | 9 Comments

Unpacking a malware with libPeConv (Pykspa case study)

In one of the recent episodes of “Open Analysis Live!” Sergei demonstrated how to statically unpack the Pykspa Malware using a Python script. If you haven’t seen this video yet, I recommend you to watch, it is available here – and the full series is really cool.

The video inspired me to use the same sample and demonstrate an alternative solution, applying my library, libPeConv . The advantage of using libPeConv is that you don’t have to spend time on understanding and rewriting the unpacking algorithm. Instead, you can import the original unpacking function from the original malware. It can speed up the work and be helpful also in the cases when the function of our interest is obfuscated.

Analyzed sample

bd47776c0d1dae57c0c3e5e2832f13870a38d5fd

Static analysis

The static analysis of this malware is already well demonstrated in the mentioned video. I will just recall the important points to which we are going to refer.

Function definition

The function that is responsible for unpacking is available at RVA 0x4520. It has the following prototype:

header

By analyzing how it is applied, we can find out what are the arguments that should be passed:

unpack_blob

The first one is a blob of data (BYTE*), second – size of the blob (DWORD), next comes the name of the file where the output will be written, and last one is some magic char. This is how the function declaration should look:

int __cdecl *unpack_func(BYTE* blob, DWORD blob_size, LPCSTR lpFileName, char magic_val);

Function arguments

The function is applied twice, to decrypt two blobs of data (I call them blob1 and blob2). Important things to note are: the offsets of the blobs, their sizes and the passed magic values.

Decrypting blob1:

blob1

  • Blob1 RVA: 0xC030
  • Blob1 size: 0x11000

By following the code before the function call, we can find that the last argument (the magic char) must have the value ‘r’.

mov_r

Decrypting blob2:

blob2

  • Blob2 RVA: 0x1D038
  • Blob2 size: 0x50000

Again, the magic value is ‘r’:

val_r

Now we have all the data to implement a static unpacker.

Writing a unpacker

Setting up the project

For this part you need to have Visual Studio, CMake and Git installed.

I already prepared a template that you can use to make a libPeConv-based project, so it is enough to fetch it from my Github: https://github.com/hasherezade/libpeconv_project_template

git clone --recursive https://github.com/hasherezade/libpeconv_project_template.git

Now use the CMake to generate a VisualStudio project:

step1

The malware is 32bit, so it is important to generate a project for 32bit build, otherwise we will not be able to import the sample. Example:

example

Click “Finish” then  “Generate” and finally you can open the project in Visual Studio.

Unpacker’s code

Code of the full unpacker is very short:

https://gist.github.com/hasherezade/21f0858ee713b60070e2f33ffef44b5f

#include <stdio.h>
#include <windows.h>
#include "peconv.h"
// for the sample: bd47776c0d1dae57c0c3e5e2832f13870a38d5fd
// from: "Unpacking Pykspa Malware With Python and IDA Pro – Subscriber Request Part 1"
// https://www.youtube.com/watch?v=HfSQlC76_s4
int (__cdecl *unpack_func)(BYTE* blob, DWORD blob_size, LPCSTR lpFileName, char r_val) = nullptr;
int main(int argc, char *argv[])
{
if (argc < 2) {
std::cerr << "Args: <path to the malware>" << std::endl;
system("pause");
return 0;
}
DWORD blob1_offset = 0xC030;
DWORD blob1_size = 0x11000;
DWORD blob2_offset = 0x1D038;
DWORD blob2_size = 0x50000;
DWORD unpack_func_offset = 0x4520;
size_t v_size = 0;
LPCSTR mal_path = argv[1];
std::cout << "Reading module from: " << mal_path << std::endl;
BYTE *malware = peconv::load_pe_executable(mal_path, v_size);
if (!malware) {
return1;
}
std::cout << "Loaded" << std::endl;
ULONGLONG func_offset = (ULONGLONG)malware + unpack_func_offset;
unpack_func = (int (__cdecl *) (BYTE*, DWORD, LPCSTR, char)) func_offset;
DWORD res1 = unpack_func((BYTE*)((ULONGLONG) malware + blob1_offset), blob1_size, "blob1_unpack.bin", 'r');
std::cout << "Unpacked blob1, res:" << res1 << std::endl;
DWORD res2 = unpack_func((BYTE*)((ULONGLONG) malware + blob2_offset), blob2_size, "blob2_unpack.bin", 'r');
std::cout << "Unpacked blob2, res:" << res2 << std::endl;
peconv::free_pe_buffer(malware, v_size);
return 0;
}

view raw
unpack.cpp
hosted with ❤ by GitHub

Firstly, we load the original malware (by a function from peconv). We need it to be loaded with all the dependencies and ready to be executed. A function that allows to achieve it is load_pe_executable:

BYTE* peconv::load_pe_executable(LPCSTR path_to_pe, size_t &out_size);

This malware sample has no relocation table, so we not only need it loaded, but it must be loaded at it’s original base. This operation may fail on some runs, so we have to keep it in mind.

size_t v_size = 0;
BYTE *malware = peconv::load_pe_executable(mal_path, v_size);
if (!malware) return -1;

Then, using the known offset and the reconstructed declaration of the unpacking function, we are importing it from the loaded malware.

ULONGLONG func_offset = (ULONGLONG)malware + 0x4520;
unpack_func = (int (__cdecl *) (BYTE*, DWORD, LPCSTR, char)) func_offset;

We also use the known offsets of the blobs, and make pointers to the data. After we called the unpacking function with appropriate arguments, our payloads will be dumped to files with the supplied names.

DWORD res1 = unpack_func((BYTE*)((ULONGLONG) malware + blob1_offset), blob1_size, "blob1_unpack.bin", 'r');
std::cout << "Unpacked blob1, res:" << res1 << std::endl;

DWORD res2 = unpack_func((BYTE*)((ULONGLONG) malware + blob2_offset), blob2_size, "blob2_unpack.bin", 'r');
std::cout << "Unpacked blob2, res:" << res2 << std::endl;

At the end we can free the loaded malware:

peconv::free_pe_buffer(malware, v_size);

That’s all, the unpacker is ready. One last thing we can do is preparing a .bat file that will run the unpacker until the malware get loaded (remember the loading base issue caused by the missing relocation table).

Example of the batch script:

@echo off
:try_load
peconv_project.exe malware.bin
IF NOT ERRORLEVEL 0 GOTO try_load
echo.
pause

The full package (except the malware) is available here:
https://drive.google.com/file/d/1ogRJvhEB_rFV5s9wSYVl7MbAEv6xcgyU/view

Finally, let’s see it in action:

Posted in Malware, Programming, Tutorial | Tagged | Leave a comment

Solving a PyInstaller-compiled crackme

I got this crackme from one of my readers, who asked me for the help in understanding how to solve it. As he wrote in the e-mail, it comes “from last year competition by the CheckPoint company”. I promised to make a writeup, so here it is :). I hope it will benefit others also.

The crackme is for the beginners, so don’t expect any fireworks ;). But it was relaxing and fun to solve.

The crackme can be found here (password: crackme), also available at HA: 8ee7382cfdf632c29df5f2d9d3286614

Overview

This is how the application looks:

pycrackme1

When we run in, it asks for a username:

enter_name

And when we give an invalid one, it responds with a text:

“Go away, you are not me”

The first important step in solving the crackme, is noticing how exactly was it made and what tools are to be applied. As the icon hints, it seems to be an application written in Python and converted into EXE. But let’s confirm it by looking inside. The main process runs another instance of itself:

Let’s attach the debugger to the child process and see the loaded modules:

We can find that indeed Python2.7 is loaded to interpret the code (the module is marked red on the picture).

At this moment we can confirm that this EXE is in reality a wrapper for a Python script. There are several applications that allows to achieve it. Depending on which application produced the wrapping, the output has a bit different format and requires a different decompiler.

The popular converters of Python scripts into EXE format, are, i.e. Py2Exe and PyInstaller. This time, PyInstaller was applied.

Tools required

Step 1 – Unwrapping the exe

Unpacking the EXE is easy with the appropriate tool. In this case I used PyInstallerExtractor, written in Python.

python pyinstxtractor.py pycrackme.exe

This is the output:

[*] Processing pycrackme.exe
[*] Pyinstaller version: 2.1+
[*] Python version: 27
[*] Length of package: 2604972 bytes
[*] Found 20 files in CArchive
[*] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap
[+] Possible entry point: black_box
[*] Found 196 files in PYZ archive
[*] Successfully extracted pyinstaller archive: pycrackme.exe
You can now use a python decompiler on the pyc files within the extracted directory

The script directly hints, that the next step will be to use a Pyhon decompiler and turn the obtained pyc files into Python scripts.
It also hints about the possible entry point of the application. This information helps us to find where the code of our interest is located.

Step 2 – Decompiling the pyc

The produced output is stored in the directory corresponding to the name of the input executable. We can see there multiple modules extracted, but the interesting one seems to be the file called “black_box”:

extracted_files

The black_box is a pyc file with a magic number removed, so we can just copy this part  from some other pyc file that we found in the extracted set, i.e.

get_hdr.png

Let’s paste it at the beginning of the black_box:

paste_hdr

After this step we are ready to save it as black_box.pyc and decompile:

decompiler

And here is the result:
https://gist.github.com/hasherezade/5c91433bc2461f59657921004c505e3e#file-black_box-py

# File: b (Python 2.7)
from time import sleep
from random import sample, getrandbits, randrange
from string import ascii_letters, digits
import sys
import random
import string
import hashlib
import base64
import zlib
import locale
import binascii
import socket
from hashlib import sha256
f = 'kukuriku'
k = 'elvis presley'
c = '789p8594po76n34n0p457s8947p85n0p0q31506093s02nn06654912r30p563o50q367p7q0o3o9qo6q37q6s061r182471246q1qq2o9138q8p3rpssp9rqnn246o638315o9s4oq3580n7o3oso71rr915o8p54n4o2o87s4s6q2pq8661868664pnp0o3853s443n1606907rsqro3oq670np348643s8o65778o5r248r0r7or5350r34p40714601snqps985255so43n278q69s582sq27n92554q919682n941rs05034r241pnqs1nrn40q44p147s73popp45268qqrns2o3os206q6s22ps6p441024r136n9373n32q965ss128p507sqpp78qo63orp2s9017p0379235ps8so96ns2n12169prn9nq7745snq47o71rs2173p39163pp45nn49p844o559p37sq390998267qn5n2712416qp7nq4n591s6867p8n5p9ro1062498678q88n1934s3788q8o7o8rrnr656ps162q72s0423277pp618roopr5q4np71q98p93o645qp6pp093n2sqn546s9171rqs59543rr9ns7n0p575r86p54n5626928320855139r672rrp449o601sn0ro69qpq5p8soppoorq6184590rn071303s6n32s82r857qn1n612q4or4pp8p40orpppn18nqr20279p90r30o12191nq4ns50oq11p8p271883srn3n655sn4177816pr659n3pqs6673so31qo5n985n4rnp0q7s1015q850s71psn99s3npsp01snps6516s4n8p16369r319oq2427628onrp882ooopr019r63q3pq4spnq559po661r2r250969r91rq57q4s6656n5op0n4q681qr3r8sn495555r40nqp27p98p199o5622ppq4q9q022s81p04r067op9151q38o6r65o7r399436r76ro6rsq87poo8557r89opq08s213rrqqsn3sn26q652412ss5r9s6308q612r8471r68po780roo2o22o9661q964824o754750s305sr0rr52oqss6367q7oo32o9rp05770p5ps9813r6324so7575068q4on9r833o1s4nr4ps1p2r6662p6pspo4pr9464or0487p0qs6289475o2o8r5rsnoosn5s751o15537q15343568nors5084sr8npq3o57r4r9s15rn3n0p0p803n35s668q55s00pr8sq6ro715r2sqs70ppqro6qs953nr9p44696n0o55sp9s04o8s9s496p6o23349op863838qrp5p5o1r4rs32297492p6s277r6r6sp303o673sp2or47qnr203qp411s61p308o76104pp5soqp08o9n87s9rs3n5sq016orn986o3o7s5qsp04q865q1o0nn8q1908877ro9pqqo979r460q60rr0210oopssopo32s3r023p9q79r994q32r2555nr8613nor196o3o0272o1sn95s95o698686q0pp0r04p324o2rr0o689s9p483p880592627780s376r1pn8120n98610571po9n7ooo593195r03s2495no223q037361p3qn950op4qq0s4so9q77orq05ss66r7ossqrp1n77rs63qs316nso13o1s19sso07060349qs86s1ss31r8poo9r2nss11530q17ss6688o656525078s784s75q95557nsoo4p54395qrs8no43ns8483sn0os39npn1s7nn38qs6op347n63po2s6p55o33q6rn23993sor0q77009679n44n39q09n0sos00070p6111'
exec base64.b64decode(zlib.decompress(binascii.unhexlify(c.decode('rot13'))))
g = 'lsdjfiownv9037la1sdf10'
p = 'byebye'

view raw
black_box.py
hosted with ❤ by GitHub

The file got decompiled properly, and at this point we can rename it to py and run like any other Python script. We can see the same prompt as it was in the EXE:

enter_py

Looking at the code we can see that is is mildly obfuscated. The important part is hidden in the variable “c” that is obfuscated by ROT13 and compressed by ZLIB:

c = '789p8594po76n34n0p457s8947p85n0p0q31506093s02nn06654912r30p563o50q367p7q0o3o9qo6q37q6s061r182471246q1qq2o9138q8p3rpssp9rqnn246o638315o9s4oq3580n7o3oso71rr915o8p54n4o2o87s4s6q2pq8661868664pnp0o3853s443n1606907rsqro3oq670np348643s8o65778o5r248r0r7or5350r34p40714601snqps985255so43n278q69s582sq27n92554q919682n941rs05034r241pnqs1nrn40q44p147s73popp45268qqrns2o3os206q6s22ps6p441024r136n9373n32q965ss128p507sqpp78qo63orp2s9017p0379235ps8so96ns2n12169prn9nq7745snq47o71rs2173p39163pp45nn49p844o559p37sq390998267qn5n2712416qp7nq4n591s6867p8n5p9ro1062498678q88n1934s3788q8o7o8rrnr656ps162q72s0423277pp618roopr5q4np71q98p93o645qp6pp093n2sqn546s9171rqs59543rr9ns7n0p575r86p54n5626928320855139r672rrp449o601sn0ro69qpq5p8soppoorq6184590rn071303s6n32s82r857qn1n612q4or4pp8p40orpppn18nqr20279p90r30o12191nq4ns50oq11p8p271883srn3n655sn4177816pr659n3pqs6673so31qo5n985n4rnp0q7s1015q850s71psn99s3npsp01snps6516s4n8p16369r319oq2427628onrp882ooopr019r63q3pq4spnq559po661r2r250969r91rq57q4s6656n5op0n4q681qr3r8sn495555r40nqp27p98p199o5622ppq4q9q022s81p04r067op9151q38o6r65o7r399436r76ro6rsq87poo8557r89opq08s213rrqqsn3sn26q652412ss5r9s6308q612r8471r68po780roo2o22o9661q964824o754750s305sr0rr52oqss6367q7oo32o9rp05770p5ps9813r6324so7575068q4on9r833o1s4nr4ps1p2r6662p6pspo4pr9464or0487p0qs6289475o2o8r5rsnoosn5s751o15537q15343568nors5084sr8npq3o57r4r9s15rn3n0p0p803n35s668q55s00pr8sq6ro715r2sqs70ppqro6qs953nr9p44696n0o55sp9s04o8s9s496p6o23349op863838qrp5p5o1r4rs32297492p6s277r6r6sp303o673sp2or47qnr203qp411s61p308o76104pp5soqp08o9n87s9rs3n5sq016orn986o3o7s5qsp04q865q1o0nn8q1908877ro9pqqo979r460q60rr0210oopssopo32s3r023p9q79r994q32r2555nr8613nor196o3o0272o1sn95s95o698686q0pp0r04p324o2rr0o689s9p483p880592627780s376r1pn8120n98610571po9n7ooo593195r03s2495no223q037361p3qn950op4qq0s4so9q77orq05ss66r7ossqrp1n77rs63qs316nso13o1s19sso07060349qs86s1ss31r8poo9r2nss11530q17ss6688o656525078s784s75q95557nsoo4p54395qrs8no43ns8483sn0os39npn1s7nn38qs6op347n63po2s6p55o33q6rn23993sor0q77009679n44n39q09n0sos00070p6111'
exec base64.b64decode(zlib.decompress(binascii.unhexlify(c.decode('rot13'))))

To understand it better what happens here, we should dump the content after the deobfuscation, rather than executing it. In order to do so, I slightly modified the script. I removed the code responsible for executing the second stage, and substituted it with the function that writes the decompressed result into a file. This is my modified version:
https://gist.github.com/hasherezade/5c91433bc2461f59657921004c505e3e#file-black_box_patched-py

Now, once we execute this script, we get the next stage dumped. And this is how it looks:
https://gist.github.com/hasherezade/5c91433bc2461f59657921004c505e3e#file-decoded-py

from hashlib import sha256
from time import sleep
import socket, sys

PASSWORD = "36949"
HASH = sha256(PASSWORD).hexdigest()
USER = 'Nigel'
CODE = "807290"

IPADDR = '104.25.199.31'
PORT = 587

def login():
    print ""
    username = raw_input("Enter First Name: ")
    if username.rstrip(' \n\t') != USER:
        print "Go away! You are not me..."
        sys.exit()

    print "Hello %s, Good to see you!" % USER
    while True:
        password_guess = raw_input("Enter 5-digit password: ")
        print "[DEBUG]: calculating sha-256 hash"
        print "[DEBUG]: comparing with %s's hash: %s"  % (USER, HASH)
        print "[DEBUG]: performing anti-brute-force delay..."
        sleep(5)
        if sha256(password_guess).hexdigest() == HASH:
            print "Password OK!"
            break
        else:
            print "Wrong password!"

    while True:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
        s.connect((IPADDR, PORT))
        s.send(str(CODE).decode('hex'))
        s.close()

        print "%s, two-factor authentication is required. A one-time code was sent to your email address" % USER
        code_guess = raw_input("Enter code: ")
        sleep(5)
        if code_guess == CODE:
            print "Success! The code is what you're looking for :)"
            break
        else:
            print "Wrong code!"

login()

The script is not further obfuscated.
Once we read it, it’s pretty straight-forward what to do next. So, the username was Nigel. Then, we have to give his password that is 36949 and finaly his code: 807290. This was my final conversation with the crackme confirming that the code is valid.

python decoded.py

Output:

Enter First Name: Nigel
Hello Nigel, Good to see you!
Enter 5-digit password: 36949
[DEBUG]: calculating sha-256 hash
[DEBUG]: comparing with Nigel's hash: 6912863904dab1ddc332a928bf6df7f365bf1131906f3424aa931c6c85595c34
[DEBUG]: performing anti-brute-force delay...
Password OK!
Nigel, two-factor authentication is required. A one-time code was sent to your email address
Enter code: 807290
Success! The code is what you're looking for :

Exactly the same results we get when we talk with the original EXE:
crackme1
So, the final answer is 807290.

Conclusion

This crackme can be solved very easily if we know the few tricks. The most important was to find what are the proper tools to be applied. Once we got them, we could easily decompile the code and read the answer.

Appendix

Posted in CrackMe, Tutorial | Tagged , | 4 Comments

Process Doppelgänging – a new way to impersonate a process

Recently at Black Hat Europe conference, Tal Liberman and Eugene Kogan form enSilo lab presented a new technique called Process Doppelgänging. The video from the talk is available here. (Also, it is worth mentioning that Tal Liberman is an author of the AtomBombing injection).

This technique is a possible substituent of the well-known Process Hollowing (RunPE), that is commonly used in malware. Both, Process Doppelgänging and Process Hollowing, gives an ability to run a malicious executable under the cover of a legitimate one. Although they both serve the same goal of process impersonation, they differ in implementation and make use of different API functions. This is why, most of the current antivirus solutions struggled in detecting Process Doppelgänging. In this post we will take a closer look on how the Process Doppelgänging works and compare it with the popular RunPE.

WARNING: Running this PoC on Windows 10 may cause a BSOD – the reason is a bug in Windows 10. Details here.

Process Doppelgänging vs Process Hollowing (aka RunPE)

The popular RunPE technique substitutes the PE content after the process is created (suspended), but before it is fully initialized. In order to implement this technique, we need to do by our own the step that WindowsLoader took so far: converting the PE file from it’s raw form into a virtual form, relocating it to the base where it is going to be loaded, and pasting into the process’ memory. Then, we can awake the process from the suspended state, and the WindowsLoader will continue loading our (potentially malicious) payload. You can find a commented implementation here.

The Process Doppleganging, in contrary, substitutes the PE content before even the process is created. We overwrite the file image before the loading starts – so, WindowsLoader automatically takes care of the fore-mentioned steps. My sample implementation of this technique can be found here.

NTFS transactions

On the way to it’s goal, Process Doppelgänging uses a very little known API for NTFS transactions.

Transactions is a mechanism commonly used while operating on databases – however, in a similar way it exists in the NTFS file system. It allows to encapsulate a series of operations into a single unit. Thanks to this, multiple operations can be treated as a whole: they can either succeed as a whole – and be committed, or fail as a whole – and be rolled back. Outside of our transaction, the result of the operations is not visible. It starts to be noticeable after the transaction is closed.

Windows API makes several functions available for the purpose of transactions:

Briefly speaking, we can create a file inside a transaction, and for no other process this file is visible, as long as our transaction is not committed. It can be used to drop and run malicious payloads in an unnoticed way. If we roll back the transaction in an appropriate moment, the operating system behaves like our file was never created.

The steps taken

Usage of NTFS transactions

Firstly, we need to create a new transaction, using the API CreateTransaction.

https://github.com/hasherezade/process_doppelganging/blob/master/main.cpp#L144

Then, inside of this transaction we will create a dummy file to store our payload (using CreateFileTransacted).

https://github.com/hasherezade/process_doppelganging/blob/master/main.cpp#L149

This dummy file will be then used to create a section (a buffer in a special format), which makes a base for our new process.

https://github.com/hasherezade/process_doppelganging/blob/master/main.cpp#L173

After we created the section, we no longer need the dummy file – we can close it and roll back the transaction (using RollbackTransaction).

https://github.com/hasherezade/process_doppelganging/blob/master/main.cpp#L188

Usage of undocumented process creation API

So far we created a section containing our payload, loaded from the dummy file. You may ask – how are we going to create a process out of this? The well known API functions for creating processes on Windows require file path to be given. However, if we look deeper inside those functions, we will find that they are just wrappers for other, undocumented functions. There is a function Zw/NtCreateProcessEx which, rather than the path to the raw PE file, requires a section with a PE content to be given. If we use this function, we can create a new process in a “fileless” way.

Definition of the NtCreateProcessEx:

https://github.com/hasherezade/process_doppelganging/blob/master/main.cpp#L196

Creation of process by this way requires more steps to be taken – there are some structures that we have to fill and setup manually – such as process parameters (RTL_USER_PROCESS_PARAMETERS). After filling them and wring into the space of the remote process, we need to link them to the PEB. Mistake in doing it will cause the process to not run.

https://github.com/hasherezade/process_doppelganging/blob/master/main.cpp#L76

After setting everything up, we can run the process by creating a new thread starting from it’s Entry Point.

https://github.com/hasherezade/process_doppelganging/blob/master/main.cpp#L246

Despite some inconveniences, creating the process by a low-level API gives also interesting advantages. For example, we can set manually the file path – making an illusion, that this is the file that has been loaded, even if it was not. By this way, we can impersonate any windows executable, but also we can make an illusion, that the PE file runs from a non-existing file, or a file of a non-executable format.

Below you can see an example where the illusion was created, that the PE file runs from a TXT file:

How to detect?

Although this technique may look dangerous, it can be easily detected with the help of any tool that compares if the image loaded in the memory matches the corresponding file on the disk. Example: detection with PE-sieve (former hook_finder):

The process of injection is also not fully stealthy. It still requires writing into the memory (including PEB) of the newly created process, as well as creating a remote thread. Such operations may trigger alerts.

In addition, the mechanism of NTFS  transactions is very rarely used – so, if any executable call the related APIs, it should become an object of a closer examination.

So far this technique is new, that’s why it is not broadly recognized by AV products – but once we are aware of it’s existence, implementing detection should not be difficult.

Posted in Malware, Programming, Techniques | Tagged , | 5 Comments

Hook the planet! Solving FlareOn4 Challenge6 with libPeConv

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:

 

Posted in CrackMe, Programming, Tools | Tagged , , , | Leave a comment