<img src="https://ws.zoominfo.com/pixel/JrRu3vUM8j33QSR7Bwxw" width="1" height="1" style="display: none;">
Blog |

The Hunt for NTCrypt: Exposing a Malicious Packer

Researchers demonstrate how threat hunting with Titanium Platform accelerates their analysis

Karlo Zanki
Blog Author

Karlo Zanki, Reverse Engineer at ReversingLabs. Read More...

Reverse Engineering a Packer

Packers are often used to hide the functionality of executable files from unwanted observers. This can be done for legitimate purposes, such as protecting your intellectual property, but also for malicious purposes; for example, hiding malicious content from security products.

ReversingLabs TitaniumCore is a fully automated static analysis engine with inspection capabilities that must overcome these obfuscation attempts in order to uncover the component elements, typically binary files. To improve visibility in our products, we continuously improve unpacking and broaden our support for new packer formats- some solely dedicated to hiding malware.

Legitimate-purpose packers are usually advertised on company websites, and are not difficult to find. On the other hand, packers used for malicious purposes are advertised in various hacking forums and websites usually not so open to the public. Those malicious packers are mostly advertised as truly, fully undetectable, and guarantee a period of this undetectability to the buyer. Their price varies from a few dollars to several thousand. During an investigation, we came across a packer named NTCrypt.

Crypter advertised as available on a purchasing website


Crypter advertised as available on a purchasing website


NTCrypt was being sold with a $950 monthly subscription, and we wanted to see what its authors offer for this price. One way to discover that is similar to playing poker - pay the price to see the cards. Since gambling is not really our game, we wondered if there was another, more reliable way to find out what NTCrypt does.

Our Titanium Platform is used by many customers for getting insight into files entering their business networks. While analyzing those files, Titanium Platform extracts metadata from different file formats, including Microsoft PE files. This haystack of files in the cloud can be searched based on that extracted metadata from Titanium Platform’s A1000, our cybersecurity threat analysis solution.

Not a lot of information is available about NTCrypt on its advertising page. In fact, the only thing we found was the product name, so we gave that a try. The A1000 Advanced Search allows us to find files even without precisely knowing the exact term we are searching for. This can be done by expanding the part of the term we know with wildcard characters. Searching metadata extracted from files for pe-product-name:NTCrypt* returned a few results.

Processing those files with our platform gave us detailed insight into their content. Most of them were quite similar native binaries, compiled in the range Tue Dec 06 11:20:04 2016 +/- 20 minutes. They all had the same “Rich header” and the same “imphash”. One interesting thing was the comment in the “Version info” structure which gave us a clue about the packer's real purpose (if there were ever any doubts).

Version info structure


Version info structure

The next step was to execute those files and try to see what options they offer in the packing process. Unfortunately, nothing really happened after starting the files. No user interface was displayed, no error message shown, no login screen presented. Nothing happened. It was a dead end - or was it?

Among our previous search results was also one PE/.Net Exe file. Its “Version info” structure was the same as for the other files. The main difference was the PE type - the earlier file versions were native PE files, but this one was a PE/.NET file. While browsing the website where the packer was sold, we encountered a few videos demonstrating the usage of the packer. One of the videos unveiled the contents of the directory where NTCrypt is run from.

Contents of the NTCrypt folder visible in the advertising video


Contents of the NTCrypt folder visible in the advertising video

The collected file had the same icon as the file visible in the packer's advertising video. Executing it returned a few errors about missing components (probably other files visible inside the directory shown in the video), but skipping through them actually got us to the user interface - the same one we saw in the video.

Packer builder user interface


Packer builder user interface

So we found ourselves a packer. Now we only needed to figure out how it works.

When reversing packers, we usually have files generated with it, or the packer executable itself which we then use to generate the files we are going to reverse. Let's try to encrypt a file with the program we just found. We give the program the path to the file we want to pack, leave all other settings on their default values, and try to create the packed executable. It's not successful; instead we get an error about a missing stub.

Missing stub error


Missing stub error

What is a stub? A stub is a skeleton program that knows how to unpack the files created with the target packer. During the packing process, the packer “patches” the stub with configuration values so the stub could know where in the packed file it can find the data it needs to unpack. Stubs are usually distributed together with the packer, but we don’t have them, since we found the packer executable in our cloud platform.

Another potential problem for us is that packers sold for malicious purposes often get distributed with “unique” stubs for different buyers, so there could be many different stubs in the wild. But we will solve those problems later; for now, let’s give the packer a dummy stub just to bypass the error. The packer doesn’t complain about the missing stub anymore - the new problem is the missing “CryptEngine.dll”.

Missing CryptEngine.dll error


Missing CryptEngine.dll error

We saw that file in the advertising video, and also clicked through a warning related to it during the packer's startup. Now we have obviously reached the point where we have to supply it to the packer if we want the packer to do anything. The DLL name tells us that this is probably the main component of the packer. The only things we know about this file are its name and size visible in the video. Unfortunately for us, A1000 search queries based on different variations of pe-product-name and pe-original-name in combination with the file size didn’t return any results.

Let’s try to find more information by examining the packer executable we had already found. Looking at the files extracted from this PE/.Net executable reveals that there is a DLL embedded in it.

DLL embedded in executable’s resources


DLL embedded in executable’s resources

Examining metadata extracted with Titanium platform shows us export names, including the original file name which confirms our assumption that this could be the missing “CryptEngine.dll”.

DLL exports


DLL exports

After putting the extracted DLL into the NTCrypt folder, renaming it to its original name, and starting the packer, we no longer get the “missing CryptEngine.dll” warning. Instead, we are now stuck in a login loop with all of the user interface controls disabled. A workaround for this problem is to temporarily rename “CryptEngine.dll” to something else, start the packer, and when it fully loads, rename “CryptEngine.dll” back to its original name. However, after this, trying to pack our program fails again (now with an unknown crash), but manages to create the output file we specified.

The output file and the stub have the same file size. Analyzing them with a hex editor indicates some differences. It looks like the packer crashed after configuring the stub. That is great - now we can see what the packer is modifying in the stub. Processing both files with Titanium platform shows us that the packer changes the file's checksum, timestamp, and debug directory entry fields in the PE header. It also modifies some data in the “.data” section.

Our earlier hypothesis was that “CryptEngine.dll” is the core component of the packer and does all the packing logic. Since our packing attempt failed with an unspecified error, the best thing to do now would be to reverse-engineer “CryptEngine.dll” to see how it works and what it does.

Disassembling it with IDA gives us a detailed insight into the packing process. Previously, our static analysis indicated that this DLL exports only one function named “CryptFile”. Looking at this function in IDA shows that it receives 10 arguments including FilePath, StubPath, OutputPath, and SectionName. It looks like we are dealing with quite a smart function.

CryptFile function declaration


CryptFile function declaration


The packing algorithm could be roughly described as follows:

read packing target into memory
if compression enabled:
compress packing target with LZ compression
encrypt packing target with Blowfish algorithm
configure the stub:
zero-out checksum
generate “random” timestamp
generate XOR encryption key from timestamp and entry point
encrypt packing configuration with generated key
write XOR encryption key to “.data” section
Write encrypted packing configuration to “.data” section
write stub to output file
evade ASLR if configured
generate random section name if configured
AddSection to output file
calculate new sizeOfInitializedData and write it to output file
generate new value for debug data directory field in the PE header
write that value to file

There are a few interesting things in that algorithm that we’re going to use to hunt down packed samples in our cloud. The first specific thing is the way XOR encryption key for configuration encryption is generated. The low word of the entry point becomes the high word of the key, and the low word of the generated timestamp becomes the low word of the key.

XOR key generation


XOR key generation


The second significant thing is the position in the “.data” section where the generated XOR encryption key is stored. It is exactly at the beginning of the second half of the section's raw data. Immediately after the XOR key follows the configuration encrypted with a simple algorithm, using this XOR key among other arithmetic operations. The data stored in this configuration includes random seed used in the Blowfish algorithm, and other options that can be configured during the packing process (like compression or startup name).

Decrypted configuration

 

Decrypted configuration


Decrypted configuration

The third notable thing is the AddSection function included from another DLL called - you would never have guessed it - “AddSection.dll”. It takes four parameters: OutputFilePath, OutputFileBuffer, OutputFileSize, and SectionName. We can take a pretty good guess that this function adds a section named SectionName to the file located at OutputFilePath. The section's content is filled with data from OutputFileBuffer, which is OutputFileSize bytes large. If we wanted to make our packer work, we could implement “AddSection.dll” on our own, but that is not really necessary since we already know everything “CryptEngine.dll” does.

AddSection call from CryptEngine.dll


AddSection call from CryptEngine.dll

Now that we know how the packer works, the only thing left unknown are the stubs. Remember those versions of NTCrypt mentioned earlier in this text - the ones that seemed not to do anything when executed? That looks very similar to the behavior of an unconfigured stub. Reversing one of those stubs in a debugger confirmed our assumptions. A few parts of the stub are encrypted with a simple XOR-NOT algorithm. This is the first layer of encryption, and it changes from stub to stub. It is used to encrypt the second layer Blowfish decryption we found in CryptEngine.dll. Setting a breakpoint after the part of code which decrypts the first layer of encryption reveals that encrypted data contains default S-box values of the Blowfish algorithm.

Decryption routine and decrypted default S-box values


Decryption routine and decrypted default S-box values

The same default S-box values are also present in the CryptEngine.dll.

Default S-box values in CryptEngine.dll


Default S-box values in CryptEngine.dll

We are now quite confident that these are the actual stubs, and that we can reverse them in detail, as well as create signatures used for identification. The only problem is that we have only 7 stub samples at the moment. As mentioned earlier, custom packers very often provide “unique” samples per buyer. It would be nice if we had more samples, as that would allow us to detect variations between stub versions and adapt our signatures so they could match a broader set of samples. The A1000 threat analysis platform offers us a way to create YARA rules that apply to all files that are being processed by our Titanium Platform. Its retrohunt feature even allows us to match those YARA rules against all samples that have been processed by the Titanium Platform in the last 90 days. So let’s go and hunt those stubs down.

import "pe"

rule NTCryptSample
{
   condition:
      uint16(0)  ==  0x5A4D and
         (
            (uint32(uint32(0x3C)  +  4  +  20  +  16)  &  0x0FFFF)  != 0 or
            (pe.timestamp & 0x0FFFF)  != 0
         )  and
         for any i in (0..pe.number_of_sections - 1):
         (
            pe.sections[i].name == ".data" and
            uint32(
                 pe.sections[i].raw_data_offset +
                 (pe.sections[i].raw_data_size >> 1)
            )  ==
            ( (uint32(uint32(0x3C)  +  4  +  20  +  16)  &  0x0FFFF)  << 0x10)  |
            (pe.timestamp  &  0x0FFFF)
         )
}

Yara rule for detecting NTCrypt packed samples


We created our YARA rule based on functionalities implemented in CryptEngine.dll, because that part of the packer is most likely to stay unchanged between versions. Most often, packer creators only generate new stubs or add new controls to the packer user interface, but the main packing logic implemented in a special DLL is very likely going to remain the same between smaller version changes. After we created the new YARA rule, we launched a retrohunt and it matched around 400 new files.

Yara retrohunt results


Yara retrohunt results

After analyzing those files, we discovered two new stub versions that significantly differed from the initial samples found earlier. We modified our signatures so they could match those stubs. Also, our YARA rule is continuously finding new stubs that use the same logic implemented in CryptEngine.dll. This approach offers our customers a great way to automatically discover new threats when they appear in the future.

Win32.Trojan.Konus sample AV detection rate


Win32.Trojan.Konus sample AV detection rate

Titanium Platform analysis also showed that NTCrypt-packed samples often have a low AV-detection rate. Although most of the AV solutions do eventually detect them during run-time, it is often enough for malware to slip by email and web gateway protections, and cause damage to an inadequately protected system.

Win32.Trojan.Remcos sample AV detection rate


Win32.Trojan.Remcos sample AV detection rate

The analysis also revealed that NTCrypt was used in Remcos and DarkRat campaigns. Especially interesting was the DarkRat packed sample. While the unpacked sample is detected as Win32.Trojan.Darkrat, the original NTCrypt-packed sample has a different detection - Win32.Trojan.Fuu. This illustrates how packing can hide the true nature of a threat.

Darkrat sample AV detection


Darkrat sample AV detection

Packed Darkrat sample AV detection rate


Packed Darkrat sample AV detection rate

In this blog we’ve demonstrated how our Titanium Platform can assist with threat hunting and malware analysis on a real-world example, the malicious packer NTCrypt. We’ve presented the features and functionalities of some of our analytic tools. Combine those with a large collection of metadata, and you can really accelerate analysis and quickly find that needle in a haystack.


NTCrypt builder (SHA256)
8de7b513d770188e3c99828605d04cfd1e232d40151089c300c407986d098706

CryptEngine.dll (SHA256)
5ea79e0706c036fb75734ef80825af4c8105214ffe3bcabe12dce1528c56201d

NTCrypt Stubs (SHA256)
6bbf873b3a409304993867b86c542cdc6c934dea7a70eca540e9c6521b8f83a7
200c14ac227451eb8c74a208d4f61650467a664d228f0e978a10ee4b53d742b9
0bc4545faff1c9379f727f5bef8eea7d7009d5e80f0d6dc99112abe796fc2327
96839a8229d09359724f9c361a1ef9f6ba8cf4af298062369aa0a0d4e1069715
2013f629eb39c967d3b7cf35f4f90c5704205faa96a1df8520c334fe44c625aa
7773d517b5f3dda8db812f16f6b9a916e1a6aadb5b4a99ec98f9492547e72945
23232211bb692367b59c2057cd931198821565dee963398d5fa2f0da92f4de21

NTCrypt packed Win32.Trojan.Konus sample (SHA256)
959269be199a17991ccf25d03f31a81bc5772673d7b8b6b680e102242dae0b35

NTCrypt packed Win32.Trojan.Remcos sample (SHA256)
44d84faec91b8939d3998f33e3602fa0e0ad5247975bcbc42c2016bb9ebd5fc2

NTCrypt packed Win32.Trojan.Darkrat sample (detected as Win32.Trojan.Fuu) (SHA256)
B2b16b16b63ab5f91e84e4bd84617cdd4c2657bb6cf2765c302db508805f46db

Unpacked Win32.Trojan.Darkrat sample (SHA256)
08edf586451da1d374517d4c13489d40c675926a4026ba99204b9f2323ea6a69