Recently, a researcher nicknamed MalwareTech famous from stopping the WannaCry ransomware got arrested for his alleged contribution to creating the Kronos banking malware. We are still not having a clear picture whether the allegations are true or not – but let’s have a look at Kronos itself.

Background

This malware has been first advertised on the black market since around June 2014, by an individual nicknamed VinnyK, writing in Russian:

Source: https://twitter.com/x0rz/status/893191612662153216

The full text of the advertisement, translated into English, has been included in the IBM’s Security Intelligence article.

We found Kronos being spread by various exploit kits, i.e. Sundown (more information here). The malware is being distributed up to now – some of the recent samples have been captured about a month ago, dropped from Rig EK.

Nowadays, Kronos is often used for the purpose of downloading other malware. One of the campaigns using Kronos as a downloader was described by Proofpoint.

Analyzed samples

Samples from 2014:

Sample #1 (from 2016)

Sample #2 (from 2017):

Behavioral analysis

After being run, Kronos installs itself in a new folder (%APPDATA%/Microsoft/[machine-specific GUID]):

The dropped sample has a hidden attribute.

Persistence is achieved with the help of a simple Run key:

At the beginning of the execution, the malware modifies the Firefox profile, overwriting user.js with the following content:

user_pref("network.cookie.cookieBehavior", 0);
user_pref("privacy.clearOnShutdown.cookies", false);
user_pref("security.warn_viewing_mixed", false);
user_pref("security.warn_viewing_mixed.show_once", false);
user_pref("security.warn_submit_insecure", false);
user_pref("security.warn_submit_insecure.show_once", false);
user_pref("app.update.auto", false);
user_pref("browser.safebrowsing.enabled", false);
user_pref("network.http.spdy.enabled", false);
user_pref("network.http.spdy.enabled.v3", false);
user_pref("network.http.spdy.enabled.v3-1", false);
user_pref("network.http.spdy.allow-push", false);
user_pref("network.http.spdy.coalesce-hostnames", false);
user_pref("network.http.spdy.enabled.deps", false);
user_pref("network.http.spdy.enabled.http2", false);
user_pref("network.http.spdy.enabled.http2draft", false);
user_pref("network.http.spdy.enforce-tls-profile", false);
user_pref("security.csp.enable", false);

The new settings are supposed to give to the malware more control over the browser’s behavior and downgrade the security settings. Then, the malware injects itself into svchost, and continues running from there. We can find it listening on local sockets.

It is worth noting, that Kronos deploys a simple userland rootkit, that hides the infected process from the monitoring tools. So, the process running the main module may not be visible. The rootkit is, however, not implemented in a very reliable way, and the effect of hiding does not always work.

Whenever some browser is deployed. Kronos injects its module there and connects with the main module, that runs inside the svchost process. Looking at the TCP connections established by the particular processes (i.e. using ProcessExplorer), we can see that a browser is paired with the infected svchost:

This trick is often used by banking trojans for the purpose of stealing data from the browser. The module injected in the browser hooks the used API and steals the data. After that, it sends this data to the main module that process it further, and reports to the CnC.

Network communication

The analyzed sample was connecting to CnCs at two addresses:

http://springalove.at:80/noix/connect.php
http://springahate.at:80/noix/connect.php

At the time of analysis, each CnC was dead (sinkholed), but still, we could spot some patterns typical for this malware family.

First, the malware sends a beacon that is 74 bytes long:

Then, follows another chunk of data:

In both cases, we can see that the requests are obfuscated by XOR with a random character. This is how the beacon looks after being XOR-decoded:

We can see that all the requests start from the same header, including the GUID specific to the infected machine.

Detailed research about decrypting Kronos communication has been already described here.

Inside

Interesting strings

Like most malware, Kronos is distributed packed by various packers/crypters. After unpacking the first layer, we get the malicious payload. We can easily identify Kronos by the typical strings used:

There are more strings that are typical for this particular malware:

Those strings are hashes used to dynamically load particular imported functions. Malware authors use this method to obfuscate used API functions, and by this way, hide the real mission of their tool. Instead of loading function using its explicit name, they enumerate all imports in a particular DLL, calculate hashes of their names, and if the hash matches the hardcoded one, they load that function.

Although the approach is common, the implementation seen in Kronos is not typical. Most malware stores hashes in the form of DWORDs, while Kronos stores them as strings.

Inside the early samples of Kronos, we can find a path to the debug symbols, revealing the structure of directories on the machine where the code was built. The following path was extracted from one of the Kronos samples observed in wild (01901882c4c01625fd2eeecdd7e6745a):

C:\Users\Root\Desktop\kronos\VJF1\Binaries\Release\VJF.1.pdb

The PDB path can be also found in the DLL (6c64c708ebe14c9675813bf38bc071cf) that belongs to the release of Kronos from 2014:

C:\Users\Root\Downloads\Kronos2\VJF1\Bot\injlib\bin\injlib-client-Release\injlib-client.pdb

This module, injlib-client.dll, is the part injected into browsers. In the newer version of Kronos, analogical DLL can be found, however, the PDB path is removed.

Injection into svchost

The main module of Kronos injects itself into svchost (version from 2014 injects into explorer instead). In order to achieve this initial injection, the malware uses a known technique, involving the following steps:

  1. creates the svchost process as suspended
  2. maps its sections into its own address space
  3. modifies the sections, adding its own code and patching the entry point in order to redirect the execution there
  4. resumes the suspended process, letting the injected code execute

Below, you can see the memory inside the infected svchost (in early versions, the injection was targeting explorer). The malware is added in a new, virtual section – in the given example, mapped as 0x70000:

This is how the patched entry point of svchost looks like – as we can see, execution is redirected to the address that lies inside the added section (injected malware):

The execution of the injected PE file starts in a different function now – at RVA 0x11AB0:

– while the original Entry Point of the malware was at RVA 0x12F22:

The malware defends itself from the analysis, and in the case of the VM or debugger being detected, the sample will crash soon after the injection.

Running sample from new Entry Point

The main operations of the malware starts inside the injected module. This is how the new Entry Point looks like:

The main function is responsible for loading all the imports and then deploying the malicious actions.

If you are an analyst trying to run Kronos from that point of the execution, below you will find some tips.

The first block of the function is responsible for filling the import table of the injected module. If we want to run the sample from that point, rather than following it when it is injected, there are some important things to notice. First of all, the loader is supposed to fill some variables inside the injected executable, i.e. the variable module_base. Other functions will refer to this, so, if it does not contain the valid value, the sample will crash. Also, the functions filling the imports expects that the section .rdata (containing the chunks to be filled), is set as writable. It will be set as writable in the case when the sample is injected because then, the full PE is mapped in a memory region with RWX (read-write-execute) access rights. However, in the normal case – when the sample is run from the disk – it is not. That’s why, in order to pass this stage, we need to change the access rights to the section manually.

Another option is to run Kronos sample starting from the next block of the main function. This also leads to successful execution, because in case if the sample is run from the disk rather than injected, imports are filled by windows loader and doing it manually is just redundant.

The last issue to bypass is the defensive check, described below.

Defensive tricks

The malware deploys defense by making several environment checks. The checks are pretty standard – searching blacklisted processes, modules etc. The particular series of checks are called from inside one function, and results are stored as flags set in a dedicated variable:

If the debugger/VM is detected, the variable has a non-zero value. Further, the positive result of this check is used to make the malware crash, interrupting the analysis.

The crash is implemented by taking an execution path inappropriate to the architecture where the sample was deployed. The malware is a 32 bit PE file, but it has a bit different execution paths, depending if it is deployed on 32 or 64-bit system. First, the malware fingerprints the system and sets the flag indicating the architecture:

DWORD is_system64_bit()
{
	DWORD flag = 0;
	__asm {
		xor eax, eax
		mov ax, cs
		shr eax, 5
		mov flag, eax
	};
	return flag;
}

This trick uses observations about typical values of CS registry on different versions of Windows (more information here). It is worth to note, that it covers most but not all the cases, and due to this on some versions of Windows the malware may not run properly.
If the debugger/VM is detected, the flag indicating the architecture is being flipped:

That’s why the sample crashes on the next occasion when the architecture-specific path of execution should be taken.

For example, if the sample is deployed on 64-bit machine, under Wow64, the syscall can be performed by using the address pointed by FS:[0xC0]. But if the malware runs on a 32-bit machine, the value pointed by FS:[0xC0] will be NULL, thus, calling it crashes the sample.

This way of interrupting analysis is smart – sample does not exit immediately after the VM/debugger is detected, and it makes it harder to find out what was the reason of the crash.

Using raw syscalls

As mentioned in the previous paragraph, Kronos uses raw syscalls. Syscall basically means an interface that allows calling some function implemented by kernel from the user mode. Applications usually use them via API exported by system DLLs (detailed explanation you can find i.e. on EvilSocket’s blog).

Those API calls can be easily tapped by monitoring tools. That’s why, some malware, for the sake of being stealthier reads the syscalls numbers from the appropriate DLLs, and calls them by it’s own code, without using the DLL as a proxy. This trick has been used i.e. by Floki bot.

Let’s have a look how is it implemented in Kronos. First, it fetches appropriate numbers of the syscalls from the system DLLs. As mentioned before, functions are identified by hashes of their names (full mapping hash-to-function you can find in Lexsi report).

For example:

B6F6X4A8R5D3A7C6 -> NtQuerySystemInformation

The numbers of syscalls are stored in variables, xored with a constant. Fragment of the code responsible for extracting raw syscalls from the DLL:

In order to use them further, for every used syscall Kronos implements its own wrapper function with an appropriate number of parameters. You can see an example below:

The EAX registry contains the number of the syscall. In the given example, it represents the following function:

00000105 -> NtQuerySystemInformation

Kronos uses raw syscalls to call the functions that are related to injections to other processes because they usually trigger alerts. Functions that are called by this way are listed below:

NtAllocateVirtualMemory
NtCreateFile
NtCreateSection
NtGetContextThread
NtOpenProcess
NtProtectVirtualMemory
NtQueryInformationProcess
NtQuerySystemInformation
NtResumeThread
NtSetContextThread
NtSetValueKey

It matches the black market advertisement, stating: “The Trojan uses an undetected injection method” (source).

Rootkit and the hooking engine

One of the features that malware provides is a userland rootkit. Kronos hooks API of the processes so that they will not be able to notice its presence. The hooking is done by a specially crafted block of the shellcode, that is implanted in each accessible running process.

First, Kronos prepares the block of shellcode to be implanted. It fills all the necessary data: addresses of functions that are going to be used, and the data specific to the malware installation, that is intended to be hidden.

Then, it searches through the running processes and tries to make injection wherever it is possible. Interestingly, explorer.exe and chrome.exe are omitted:

The shellcode is deployed in a new thread within the infected process:

Below you can see the shellocode inside the memory of the infected process:

When it runs, it hooks the following functions in the address space of the infected process:

ZwCreateFile
NtOpenFile
ZwQueryDirectoryFile
NtEnumerateValueKey
RtlGetNativeSystemInformation
NtSetValueKey
ZwDeleteValueKey
ZwQueryValueKey
NtOpenProcess

The interesting thing about this part of Kronos is its similarity with a hooking engine described by MalwareTech on his blog in January 2015. Later, he complained in his tweet, that cybercriminals stolen and adopted his code. Looking at the hooking engine of Kronos we can see a big overlap, that made us suspect that this part of Kronos could be indeed based on his ideas. However, it turned out that this technique was described much earlier (i.e. here, //thanks to  @xorsthings for the link ), and both authors learned it from other sources rather than inventing it.

Let’s have a look at the technique itself. During hooking, one may experience concurrency issues. If a half-overwritten function will start to be used by another thread, the application will crash. To avoid this, it is best to install a hook by a single assembly instruction. MalwareTech’s engine used for this purpose an instruction lock cmpxch8b. Similar implementation can be found in Kronos.

The hooking function used by Kronos takes two parameters – the address of the function to be hooked, and the address of function used as a proxy. This is the fragment of the implanted shellcode where the hooking function is being called:

First, the hooking function searches the suitable place in the code of the attacked function, where the hook can be installed:

The above code is an equivalent of the following:

https://github.com/MalwareTech/BasicHook/blob/master/BasicHook/hook.cpp#L103

Then, it installs the hook:

As we can see, the used method of  installing hook is almost identical to:

https://github.com/MalwareTech/BasicHook/blob/master/BasicHook/hook.cpp#L77

Below you can see an example of Kronos hooking a function ZwResumeThread in the memory of the attacked process. Instruction lock cmpxch8b is indeed used to overwrite the function’s beginning:

After the hook installation, whenever the infected process calls the hooked function, the execution is redirected to the proxy code inside the malicious module:

The hooking engine used in Kronos is overall more sophisticated. First of all, even the fact that it is a shellcode not a PE file makes a difficulty level of implementing it higher. The author must have taken care of filling all the functions addresses by his own. But also, the author of Kronos shown some more experience in predicting possible real-life scenarios. For example, he took additional care for checking if the code was not already hooked (i.e. by other Trojans or monitoring tools):

Attacking browsers

The malware injects into a browser an additional module (injlib-client.dll). Below we can see an example of the DLL injected into Firefox address space:

The malware starts the injected module with the help of the injected shellcode:

We can see some API redirections added by the malware. Some of the functions imported by the attacked browser are hooked so that all the data that passes through them is tapped by the Kronos module.

The data that is being grabbed using the hooked browser API is then sent to the main module, that is coordinating malware’s work and reporting to the CnC server.

Conclusion

An overall look at the tricks used by Kronos shows that the author has a prior knowledge in implementing malware solutions. The code is well obfuscated, and also uses various tricks that requires understanding of some low-level workings of the operating system. The author not only used interesting tricks, but also connected them together in a logical and fitting way. The level of precision lead us to the hypothesis, that Kronos is the work of a mature developer, rather than an experimenting youngster.

Malwarebytes users are protected against the Kronos malware.

Appendix

Overview of the Kronos banking malware rootkit” by Lexsi

Decrypting the configuration

See also:

Inside the Kronos malware – part 2


This was a guest post written by Hasherezade, an independent researcher and programmer with a strong interest in InfoSec. She loves going in details about malware and sharing threat information with the community. Check her out on Twitter @hasherezade and her personal blog: https://hshrzd.wordpress.com.