Quarians, Turians and…QuickHeal

asuna amawaka
insomniacs
Published in
14 min readAug 29, 2021

I know, that third name in the title didn’t quite fit into the “Mass Effect” bucket. But hey, I was not the one who named this malware family ;)

A month or so ago, I took notice of a set of malware identified as “QuickHeal”, and I thought of looking into researching it. Turned out to be a pretty interesting piece of work I started. I found resemblances between QuickHeal and a malware detailed by ESET (1) dubbed as Turian. And this malware seemed to have been associated with many threat actors e.g. Nomad Panda, APT15, RedFoxTrot — all of which has somewhat similar victimology. Does that mean these groups are related somehow? Or perhaps they are in fact the same group that went by a different name to various security companies?

I’m going to share my findings on QuickHeal and its variants, hopefully someone out there finds it useful. And of course, I’ll be happy to chat on Twitter :)

The samples analyzed:

My starting point was the sample with hash C6B84755AF54768C0B8676CB6551DF1A29B4DFDDB04FAF4BBF7AE3E6DC3636E2.

This sample was identified as “QuickHeal” by FireEye researcher Ashley Shen in her “Return of IceFog APT” presentation in 2019. I analyzed this file in 2019, along with another sample I took notice from the presentation (FunRun, analysis of it here: https://medium.com/insomniacs/analysis-walkthrough-fun-clientrun-part-1-b2509344ebe6)

Then recently, I found other samples that resembled QuickHeal which led to digging up many more other files. Coincidentally, I managed to get at least 1 sample with compilation year from 2012–2021. This allowed some comparison of features’ development across the years.

A common feature: Faking SSL traffic

This family of malware is sneaky! Its communications are made up of XOR-encrypted data that comes prepended with TLS/SSL header (I’m just going to refer to the protocol generally as SSL, the intricate differences doesn’t really matter since the malware just faked it). Network defenses would not suspect anything and allow these data to strut through port 443. Even if there is content inspection done by perimeter appliances, these traffic will not be decrypted properly like standard SSL. To make things even more believable, QuickHeal’s communications protocol even involve a SSL-like handshake procedure to exchange the XOR key with the C2. Let’s take a deep look at the protocol:

1 — Prepare and send “Client Hello”

Figure 1 Hardcoded SSL header seen within c6b84755af54768c0b8676cb6551df1a29b4dfddb04faf4bbf7ae3e6dc3636e2

The malware hardcodes a SSL header within itself, and generates random values at runtime that overwrites part of this header to give the impression of a real SSL handshake.

In the example shown in the screenshot above, 0x34 bytes of “Client Hello” header is loaded, and 0x1C bytes starting from offset 0xF of this header is replaced with random values (strictly speaking, the original values are XORed with random values). This header is then sent to the C2.

A breakdown of the hardcoded header shows that it follows the definition of SSL Client Hello structure:

Figure 2 breakdown of header structure using hardcoded value from c6b84755af54768c0b8676cb6551df1a29b4dfddb04faf4bbf7ae3e6dc3636e2

Here is another example of hardcoded header in a different sample:

Figure 3 Hardcoded SSL header seen within 727093e220e39f73b341acf9cc5bff2c4fa727013173bdf4afac3e81399139e0
Figure 4 breakdown of header structure using hardcoded value from 727093e220e39f73b341acf9cc5bff2c4fa727013173bdf4afac3e81399139e0

2 — Receive response from Server

Figure 5 disassembly snippet on first 5 bytes of data expected to be received in response to “Client Hello”, from 271d4b9ee4d563953f41193c98a6687418166b96185e8b87052863f0ae705048

The malware then expects to receive a response from the C2 server that would start with the byte “16”, followed by 2 bytes of length information in the 4th and 5th position, exactly like a standard SSL server response:

The length is read (ntoh) and checked to be smaller or equal to 0x3FF9. The malware then proceeds to continue to receive that number of bytes from the server, 1 byte at a time. The content received is not checked by the malware.

Figure 6 disassembly snippet on rest of data expected to be received in response to “Client Hello”, from 271d4b9ee4d563953f41193c98a6687418166b96185e8b87052863f0ae705048

3 — Prepare and send “Client Key Exchange”

Figure 7 disassembly snippet on next part of handshake sent to C2, from 271d4b9ee4d563953f41193c98a6687418166b96185e8b87052863f0ae705048

After all expected bytes have been received from the server, the malware then replaces bytes at 3 locations within another hardcoded SSL-like data with random values. One of these random values is to be used to derive XOR key after an exchange with the C2 server. The structure breakdown is as follows:

Figure 8 breakdown of next header structure using hardcoded value from 271d4b9ee4d563953f41193c98a6687418166b96185e8b87052863f0ae705048

Here is an example from another sample:

Figure 9 breakdown of next header structure using hardcoded value from d014bf062872eb8ba138bf3a70f96cdcf90f6bae7369e62971821e0ddbd2cc5f
Figure 10 disassembly snippet on generating malware-side random value to form final XOR key for communications, from 271d4b9ee4d563953f41193c98a6687418166b96185e8b87052863f0ae705048

The 28 bytes of malware-side value(client key) is generated within a function during the malware initialization stage. This client key is used with the 28 bytes (server key) received from the C2 server to derive the final XOR key used to XOR-encrypt subsequent communications between the malware and the C2, where the encrypted is appended to the SSL Application Data header to pass off as standard SSL communications.

4 — Receive response from Server

The expected response from the server comes in 2 parts:

First the malware receives 5 bytes, check that it begins with 0x14, check for the length (just like before), and continue to receive the remaining bytes (the contents are not checked);

The second part expected contains important content. The malware also starts with receiving 5 bytes, check that it begins with 0x16, and then check the length of data to receive, followed by receiving the rest of the data. This data contains the server key, and along with the earlier generated client key, the final communications’ XOR key is derived in an algorithm as such:

Figure 11 decompiled function from 271d4b9ee4d563953f41193c98a6687418166b96185e8b87052863f0ae705048

The above 4 steps that I’ve describe correspond (sort of, with some length differences) with a diagram from ESET, which was how I was able to correlate the QuickHeal sample that I had started with, to the Turian malware.

Credits: ESET

Aliens, aliens everywhere: Comparison analysis

I’ve checked across all the samples and found similar fake SSL handshake exchange. There are some changes in the samples as the years go by. For example…

The final XOR key

The algorithm to derive the final XOR key differs slightly between the oldest sample (compiled 2012) and the newer ones. The keylength is also different — used to be 8 bytes, and latest sample we are seeing 28 bytes keys. Here’s how the function that derives the final XOR key differs:

Figure 12 comparing 0ae045cad78021e0772ee49c1c135091bc64a91c8e940e3746785603178a10f6 and 271d4b9ee4d563953f41193c98a6687418166b96185e8b87052863f0ae705048

The 8-bytes XOR key derivation within the 2012 sample has been documented by Cisco Talos (2).

I tried to compare across all the samples and found one interesting observation. The 8 bytes key was replaced with 28 bytes from 2013 onwards. But one particular sample compiled in 2020 also used 8 bytes key. And I found that the 2012 sample and 2020 sample are of the same variant (with similar command IDs supported). More on this later. Based on VirusTotal’s submission information, this sample was uploaded by a user in China on 15 Jul 2021. Is this a test sample uploaded by the malware author or operator?

Figure 13 compare get_final_xorkey from samples across the years

A more believable handshake

The “SSL” handshake observed within the 2012 sample is much shorter (only 1 send and 1 receive involved) than the later samples.

Figure 14 disassembly snippet on simple handshake, from 0ae045cad78021e0772ee49c1c135091bc64a91c8e940e3746785603178a10f6

Another interesting observation when doing comparison between 2012 sample and 2020 sample — the “SSL” handshake process in the 2020 sample is the “better” one (or at least, bear closer resemblance to the real SSL handshake). However, when examining the shape of the graph, it still looks different from the other samples. So it is not the case of simply recompiling old code. From Figure 15, notice how the 2021 sample’s graph for the same function also changed from earlier samples. Suggests that this malware family is still under active development.

Figure 15 compare graphs of “SSL” handshake process across all samples

C2 configuration

I will go through each sample by the years, to see how the “evolution” happened for how the malware handles its C2 configuration.

In the 2012 sample, the C2 configuration is embedded within the code in the form of a singlebyte XOR-encoded string:

Figure 16 disassembly snippet on reading C2 data, from 0ae045cad78021e0772ee49c1c135091bc64a91c8e940e3746785603178a10f6

The decryption algorithm is as follows, where the XOR key value is incremented starting with 0x38.

Figure 17 disassembly snippet on decrypting C2 string, from 0ae045cad78021e0772ee49c1c135091bc64a91c8e940e3746785603178a10f6

Here’s a quick python snippet to decrypt the C2 address:

The algorithm found in the 2013 and 2015 samples is slightly different from the above.

Figure 18 disassembly snippet on reading C2 data, from 836f7cf5190efd313cad36acd794c19b199d6d1807675d453eedf270116a12ee
Figure 19 disassembly snippet on reading C2 data, from a3a7faa58dac9b5d3e4640df62cce2d41605d5d43153630b796cb53fcd19a6ff
Figure 20 disassembly snippet on decrypting C2 string, from 836f7cf5190efd313cad36acd794c19b199d6d1807675d453eedf270116a12ee

In the 2014 sample, there are no encrypted C2 addresses — we are able to see the configuration in plain. The malware writes a secondary set of configuration information into an .ini file (the ini filename follows the malware’s filename). The configuration information written include: the secondary C2 IP address, port number, username and password.

The malware will first try to establish connection with the primary C2 address, if that fails, then it will read proxy settings from the victim machine and try again. If that still fails, then it proceeds to read the ini file for the secondary C2 configuration and try connection with that second address.

Figure 21 disassembly snippet on reading C2 data, from 3af4f3a9a0210a1021d01b18b623367699bab6273fb42d2f028c1600ecda3ddb

C2 configuration in 2016 sample is also not encrypted.

Figure 22 disassembly snippet on reading C2 data, from 727093e220e39f73b341acf9cc5bff2c4fa727013173bdf4afac3e81399139e0

2016 sample is a nice sample to reverse engineer, because it comes with many debugging comments :D I’ll show some of these in the next section, when I talk about the RAT features.

The 2017 sample’s C2 configuration is also in plain, and looks similar to what was found in the 2015 sample. However there is no “.ini” file being used in here.

Figure 23 disassembly snippet on reading C2 data, from c6b84755af54768c0b8676cb6551df1a29b4dfddb04faf4bbf7ae3e6dc3636e2

The 2018 sample is worth taking a good look at — it comes with RTTI, so we can see helpful names which helps us to understand the other samples as well :D Let’s see how it stores its C2 configuration — no encryption, and looks similar to the earlier samples.

Figure 24 disassembly snippet on reading C2 data, from aa5a313d1f0cbbf6900e55a16a6737068d9d8831a7ad49b285d5796aa589036a

I have 2 samples which are compiled in 2019. Previously, we have noticed that the function graphs of “SSL” handshake as well as the derivation of final XOR key differ in these two samples. Here, we see that the C2 configuration function in both samples are identical. Seems to suggest that there is one source code shared between the two binaries, yet each has its own changes.

Figure 25 disassembly snippet on reading C2 data, from 7bb281fb5bce830c60610c4b75bd024ccb9b18f8e38f7ad9991259071eeb0350
Figure 26 disassembly snippet on reading C2 data, from 271d4b9ee4d563953f41193c98a6687418166b96185e8b87052863f0ae705048

Notice how the functions in these samples so far sort-of resemble one another, despite the differences in how the C2 strings are being read. This changed in the sample compiled in 2020.

In the 2020 sample, the C2 configuration and the function that handles the startup of the malware is vastly different from the earlier samples. There was also an attempt to look for a running process named “cvnjmpcp.exe” which I would think is the name of the malware process (so this is to ensure there is only 1 instance of malware executing on the victim).

There is a loopback address found in plain. There is also a “fallback” encrypted C2 address, but this secondary C2 address is not found alongside the loopback address within the function unlike earlier samples.

Figure 27 disassembly snippet on decrypting secondary C2 address, from d014bf062872eb8ba138bf3a70f96cdcf90f6bae7369e62971821e0ddbd2cc5f

The secondary C2 address is found when following the malware’s attempts to connect to the C2.

The decryption algorithm is also different from earlier samples (2012, 2013 and 2015). Although it is also a XOR-decryption, but the XOR key used is not a simple incremented counter.

Figure 28 disassembly snippet on C2 string’s decryption function, from d014bf062872eb8ba138bf3a70f96cdcf90f6bae7369e62971821e0ddbd2cc5f

Here’s a python snippet to decrypt the C2 string for this sample:

There are also additional configuration and log files being used by the malware, named “cf” and “the.db” respectively.

Figure 29 disassembly snippet on writing error message into “the.db” file, from d014bf062872eb8ba138bf3a70f96cdcf90f6bae7369e62971821e0ddbd2cc5f

The configuration file only seems to contain a 4 byte value that is used as sleep interval. In earlier samples, this configuration file would be named “.ini” and contain C2 information as well. This is yet another change found within this sample.

Figure 30 disassembly snippet on reading sleep interval from “cf” file, from d014bf062872eb8ba138bf3a70f96cdcf90f6bae7369e62971821e0ddbd2cc5f

The latest sample compiled in 2021 contains C2 configuration decrypted in a different manner as the previous samples. The XOR key used is an incremented counter XORed with the value 0x7E (previously the key used was an incremented counter added to a fixed value). The way that the encrypted string is being stored and used within the code is a little different from the other samples. These changes suggest that there is an effort from the adversary to keep changing the binary (especially in recent years) in attempt to make reverse engineering less convenient. It may also not be a deliberate effort, but an unintended side effect due to different teams editing and recompiling the malware (stemmed from the same source code).

Figure 31 disassembly snippet on decrypting C2 string, from e4fdb279a4792ad516592076ce9a6a40c803af84bcc2e2e4f9ee48df6af9e88b

Persistency Mechanism

The code for setting persistency also changed across the years:

In the earliest sample, the approach is to directly set the Run regkey:

Figure 32 decompiled snippet on modifications to registry values, from 836f7cf5190efd313cad36acd794c19b199d6d1807675d453eedf270116a12ee

Later on, the malware does the Run regkey insertion and removal through the use of a .bat file:

Figure 33 decompiled snippet on writing commands to modify registry values to bat file, from 3af4f3a9a0210a1021d01b18b623367699bab6273fb42d2f028c1600ecda3ddb

The malware also seems to like using a mix of upper and lowercase letters to be written into the bat file, possibly to avoid string matching detections (but ironically made it possible for us to write YARA rules for them).

Figure 34 disassembly snippet on hardcoded commands used to modify registry key, from a3a7faa58dac9b5d3e4640df62cce2d41605d5d43153630b796cb53fcd19a6ff

Here’s where the roads diverge: The commands / RAT features

While the network-related functions are almost the same, and the other features that I compared suggested minor changes to the code, the command IDs accepted by the RAT can be used to clearly classified into 2 variants (though the actual features are more or less the same). I’m not exactly sure how the community now differentiates “Quarian” and “Turian”, and I’m not going to contribute more confusing names to this set of malware. They shall just be QuickHeal variants to me.. I’m more than happy to chat about this, drop me a DM on Twitter :)

I was able to figure out the command IDs being accepted by the malware, based on the big switch table found that handles data received from the C2 server. Here’s the breakdown of command IDs:

Two of the samples (compiled in 2016 and 2018) made understanding the other samples much easier with RTTI and debugging strings compiled into the binaries :)

Some of the debugging strings (printed via OutputDebugString) within the 2016 sample are:

· [%08X]: CClient::Connect(%S:%d)…

· [%08X]: CProtocolClient::ShakeHands() OK

· [%08X]: CProtocolClient::Connect() OK

The malware refers to the function that performs the fake SSL handshake as “ShakeHands”. Cute.

Figure 35 disassembly snippet showing debugging strings to denote status of connection to C2 and fake SSL handshake, from 727093e220e39f73b341acf9cc5bff2c4fa727013173bdf4afac3e81399139e0

The 2018 sample comes compiled with RTTI. The classes in the malware are:

· CClient
· CProtocolClient
· CNetwork
· CCrypt
· ProxyOperation
· TransferParam

Notice how some of these class names were also in the 2016 sample (within the debugging strings).

If you’re interested, here are details of how the set of Command IDs in each sample are different from one another.

2012 Sample

Victim information collected includes:

  • OS Version
  • Memory
  • Hostname
  • IP address
  • Username

2013/2014 Samples

The overlapped commands e.g. file-related commands, look very much like in the 2012 sample, though the command IDs changed.

Victim information collected includes:

  • OS Version
  • Memory
  • Hostname
  • IP address
  • Username
  • Malware configuration (C2 addresses, ports, username, password)

2015 Sample

The command IDs look similar to the 2013/2014 samples. The differences/improvements are:

  • 1 new command ID supported 0x100 that serves as a server heartbeat
  • 1 new command ID supported 0x800 that gives the ability to update the C2 address
  • change in 0x600 that turned it into a dedicated malware removal command

2016 Sample

Victim information collected includes:

  • OS Version
  • Memory
  • Hostname
  • IP address
  • Username
  • Computer role (acronyms used within data sent to C2)
    o Standalone Workstation — [WW]
    o Domain Workstation — [DW]
    o Standalone Server — [WS]
    o Domain Server — [DS]
    o Domain Backup Controller — [DC]
    o Domain Primary Controller — [DP]
    o Unknown — [UK]
  • Malware configuration (C2 addresses, ports, username, password)

2017 Sample

This sample’s command IDs look very much like the ones found in 2015 sample. It comes with the set/unset Run regkey in command 0x600 and the close connection command in 0x400 (I’m not sure why there are 2 command IDs that does connection closure). The ability to update C2 address with command ID 0x800 is gone in this variant.

2018 Sample

This sample introduced 2 major features as an improvement from earlier samples of the same variant:

  • Command ID 0x5 which starts interactive shell
  • Command ID 0x6 which starts a relay between newly defined sockets and the C2

2019 Sample

The samples compiled in 2019 are different from the others: the command IDs/RAT features seem to be minimal. Nevertheless, the code overlaps within the implementation of these features remain to be seen, so I’m still certain these are QuickHeal variants.

2020 Sample

The sample that was compiled in 2020 bear strong resemblances to the sample compiled in 2012. The only difference in the command IDs supported is that 0x4 will now terminate the malware instead of doing nothing.

2021 Sample

This latest sample has command ID 0x600 “removed”, otherwise all the other command IDs and implementations are identical to the 2018 sample.

Last Words

As I lack the telemetry on the victims receiving these malware, I’m not able to provide further insights on whether these samples are different because they are used on different geographical campaigns (and thus edited by different operators), or because there are many versions of QuickHeal builder (which was improved over the years). I can almost guess that there is some sort of a “checkbox” that the user can click on to select Command IDs (features) to be compiled into the binary. Really would be interesting if there can be more information available :)

===

Hashes analyzed in this post:

0AE045CAD78021E0772EE49C1C135091BC64A91C8E940E3746785603178A10F6

836F7CF5190EFD313CAD36ACD794C19B199D6D1807675D453EEDF270116A12EE

3AF4F3A9A0210A1021D01B18B623367699BAB6273FB42D2F028C1600ECDA3DDB

A3A7FAA58DAC9B5D3E4640DF62CCE2D41605D5D43153630B796CB53FCD19A6FF

727093E220E39F73B341ACF9CC5BFF2C4FA727013173BDF4AFAC3E81399139E0

C6B84755AF54768C0B8676CB6551DF1A29B4DFDDB04FAF4BBF7AE3E6DC3636E2

AA5A313D1F0CBBF6900E55A16A6737068D9D8831A7AD49B285D5796AA589036A

7BB281FB5BCE830C60610C4B75BD024CCB9B18F8E38F7AD9991259071EEB0350

271D4B9EE4D563953F41193C98A6687418166B96185E8B87052863F0AE705048

D014BF062872EB8BA138BF3A70F96CDCF90F6BAE7369E62971821E0DDBD2CC5F

E4FDB279A4792AD516592076CE9A6A40C803AF84BCC2E2E4F9EE48DF6AF9E88B

===

[1] BackdoorDiplomacy: Upgrading from Quarian to Turian

[2] Quarian: Reversing the C&C Protocol

~~

Asuna | https://twitter.com/AsunaAmawaka

--

--