Quarians, Turians and…QuickHeal
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”
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:
Here is another example of hardcoded header in a different sample:
2 — Receive response from Server
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.
3 — Prepare and send “Client Key Exchange”
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:
Here is an example from another sample:
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:
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.
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:
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?
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.
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.
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:
The decryption algorithm is as follows, where the XOR key value is incremented starting with 0x38.
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.
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.
C2 configuration in 2016 sample is also not encrypted.
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.
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.
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.
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.
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.
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.
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.
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).
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:
Later on, the malware does the Run regkey insertion and removal through the use of a .bat file:
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).
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.
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