LOWKEY: Hunting for the Missing Volume Serial ID
Mandiant
Written by: Tobias Krueger
In August 2019, FireEye released the “Double Dragon” report on our newest graduated threat group: APT41. A China-nexus dual espionage and financially-focused group, APT41 targets industries such as gaming, healthcare, high-tech, higher education, telecommunications, and travel services.
This blog post is about the sophisticated passive backdoor we track as LOWKEY, mentioned in the APT41 report and recently unveiled at the FireEye Cyber Defense Summit. We observed LOWKEY being used in highly targeted attacks, utilizing payloads that run only on specific systems. Additional malware family names are used in the blog post and briefly described. For a complete overview of malware used by APT41 please refer to the Technical Annex section of our APT41 report.
The blog post is split into three parts, which are shown in Figure 1. The first describes how we managed to analyze the encrypted payloads. The second part features position independent loaders we observed in multiple samples, adding an additional layer of obfuscation. The final part is about the actual backdoor we call LOWKEY, which comes in two variants, a passive TCP listener and a passive HTTP listener targeting Internet Information Services (IIS).
Figure 1: Blog post overview
DEADEYE – RC5
Tracking APT41 activities over the past months, we observed multiple samples that shared two unique features: the use of RC5 encryption which we don’t encounter often, and a unique string “f@Ukd!rCto R$.”. We track these samples as DEADEYE.
DEADEYE comes in multiple variants:
- DEADEYE.DOWN has the capability to download additional payloads.
- DEADEYE.APPEND has additional payloads appended to it.
- DEADEYE.EXT loads payloads that are already present on the system.
DEADEYE.DOWN
A sample belonging to DEADEYE.DOWN (MD5: 5322816c2567198ad3dfc53d99567d6e) attempts to download two files on the first execution of the malware.
The first file is downloaded from hxxp://checkin.travelsanignacio[.]com/static/20170730.jpg. The command and control (C2) server response is first RC5 decrypted with the key “wsprintfA” and then RC5 encrypted with a different key and written to disk as .mui.
The RC5 key is constructed using the volume serial number of the C drive. The volume serial number is a 4-byte value, usually based on the install time of the system. The volume serial number is XORed with the hard-coded constant “f@Ukd!rCto R$.” and then converted to hex to derive a key of up to 28 bytes in length. The key length can vary if the XORed value contains an embedded zero byte because the lstrlenA API call is used to determine the length of it. Note that the lstrlenA API call happens before the result is converted to hex. If the index of the byte modulo 4 is zero, the hex conversion is in uppercase. The key derivation is illustrated in Table 1.
Table 1: Key derivation example
The second file is downloaded from hxxp://checkin.travelsanignacio[.]com/static/20160204.jpg. The C2 response is RC5 decrypted with the key “wsprintfA” and then XORed with 0x74, before it is saved as C:\Windows\System32\wcnapi.mui.
Figure 2: 5322816c2567198ad3dfc53d99567d6e download
The sample then determines its own module name, appends the extension mui to it and attempts to decrypt the file using RC5 encryption. This effectively decrypts the file the malware just downloaded and stored encrypted on the system previously. As the file has been encrypted with a key based on the volume serial number it can only be executed on the system it was downloaded on or a system that has the same volume serial number, which would be a remarkable coincidence.
An example mui file is the MD5 hash e58d4072c56a5dd3cc5cf768b8f37e5e. Looking at the encrypted file in a hex editor reveals a high entropy (7.999779/8). RC5 uses Electronic Code Book (ECB) mode by default. ECB means that each code block (64 bit) is encrypted independent from other code blocks. This means the same plaintext block always results in the same cipher text, independent from its position in the binary. The file has 792933 bytes in total but almost no duplicate cipher blocks, which means the data likely has an additional layer of encryption.
Without the correct volume serial number nor any knowledge about the plaintext there is no efficient way to decrypt the payload e58d4072c56a5dd3cc5cf768b8f37e5e with just the knowledge of the current sample.
DEADEYE.APPEND
Fortunately searching for the unique string “f@Ukd!rCto R$.“ in combination with artifacts from RC5 reveals additional samples. One of the related samples is DEADEYE.APPEND (MD5: 37e100dd8b2ad8b301b130c2bca3f1ea), which has been previously analyzed by Kaspersky (https://securelist.com/operation-shadowhammer-a-high-profile-supply-chain-attack/90380/). This sample is different because it is protected with VMProtect and has the obfuscated binary appended to it. The embedded binary starts at offset 3287552 which can be seen in Figure 3 with the differing File Size and PE Size.
Figure 3: A look at the PE header reveals a larger file size than PE size
The encrypted payload has a slightly lower entropy of 7.990713 out of 8. Looking at the embedded binary in a hex editor reveals multiple occurrences of the byte pattern 51 36 94 A4 26 5B 0F 19, as seen in Figure 4. As this pattern occurs multiple times in a row in the middle of the encrypted data and ECB mode is being used, an educated guess is that the plaintext is supposed to be 00 00 00 00 00 00 00 00.
Figure 4: Repeating byte pattern in 37e100dd8b2ad8b301b130c2bca3f1ea
RC5 Brute Forcer
With this knowledge we decided to take a reference implementation of RC5 and add a main function that accounts for the key derivation algorithm used by the malware samples (see Figure 5). Brute forcing is possible as the key is derived from a single DWORD; even though the final key length might be 28 bytes, there are only 4294967296 possible keys. The code shown in Figure 5 generates all possible volume serial numbers, derives the key from them and tries to decrypt 51 36 94 A4 26 5B 0F 19 to 00 00 00 00 00 00 00 00. Running the RC5 brute forcer for a couple of minutes shows the correct volume serial number for the sample, which is 0xc25cff4c.
Note if you want to run the brute forcer yourself
The number of DWORDs of the key in the reference implementation we used is represented by the global c, and we had to change it to 7 to match the malware’s key length of 28 bytes. There were some issues with the conversion because in the malware a zero byte within the generated key ultimately leads to a shorter key length. The implementation we used uses a hard-coded key length (c), so we generated multiple executables with c = 6, c = 5, c = 4… as these usually only ran for a couple of minutes to cover the entire key space. All the samples mentioned in the Appendix 1 could be solved with c = 7 or c = 6.
Figure 5: Main function RC5 brute forcer
The decrypted payload belongs to the malware family POISONPLUG (MD5: 84b69b5d14ba5c5c9258370c9505438f). POISONPLUG is a highly obfuscated modular backdoor with plug-in capabilities. The malware is capable of registry or service persistence, self-removal, plug-in execution, and network connection forwarding. POISONPLUG has been observed using social platforms to host encoded command and control commands.
We confirmed the findings from Kaspersky and additionally found a second command and control URL hxxps://steamcommunity[.]com/id/oswal053, as mentioned in our APT 41 report.
Taking everything into account that we learned from DEADEYE.APPEND (MD5: 37e100dd8b2ad8b301b130c2bca3f1ea), we decided to take another look at the encrypted mui file (e58d4072c56a5dd3cc5cf768b8f37e5e). Attempts to brute force the first bytes to match with the ones of the decrypted POISONPLUG payload did not yield any results.
Fortunately, we found additional samples that use the same encryption scheme. In one of the samples the malware authors included two checks to validate the decrypted payload. The expected plaintext at the specified offsets for DEADEYE.APPEND (MD5: 7f05d410dc0d1b0e7a3fcc6cdda7a2ff) is shown in Table 2.
Table 2: Byte comparisons after decrypting in DEADEYE.APPEND (MD5: 7f05d410dc0d1b0e7a3fcc6cdda7a2ff)
Applying these constraints to our brute forcer and trying to decrypt mui file (e58d4072c56a5dd3cc5cf768b8f37e5e) once more resulted in a low number of successful hits which we could then manually check. The correct volume serial number for the encrypted mui is 0x243e2562. Analysis determined the decrypted file is XMRig miner. This also explains why the dropper downloads two files. The first, .mui is the crypto miner, and the second C:\Windows\System32\wcnapi.mui, is the configuration. The decrypted mui contains another layer of obfuscation and is eventually executed with the command x -c wcnapi.mui. An explanation on how the command was obtained and the additional layer of obfuscation is given in the next part of the blog post.
For a list of samples with the corresponding volume serial numbers, please refer to Appendix 1.
Additional RC4 Layer
An additional RC4 layer has been identified in droppers used by APT41, which we internally track as DEADEYE. The layer has been previously detailed in a blog post by ESET. We wanted to provide some additional information on this, as it was used in some of the samples we managed to brute force.
The additional layer is position independent shellcode containing a reflective DLL loader. The loader decrypts an RC4 encrypted payload and loads it in memory. The code itself is a straight forward loader with the exception of some interesting artifacts identified during analysis.
As mentioned in the blog post by ESET, the encrypted payload is prepended with a header. It contains the RC4 encryption key and two fields of variable length, which have previously been identified as file names. These two fields are encrypted with the same RC4 encryption key that is also used to decrypt the payload. The header is shown in Table 3.
Table 3: RC4 header overview
Looking at the payloads hidden behind the RC5 layer, we observed, that these fields are not limited to file names, instead they can also contain commands used by the reflective loader. If no command is specified, the default parameter is the file name of the loaded payload. In some instances, this revealed the full file path and file name in the development environment. Table 4 shows some paths and file names. This is also how we found the command (x -c wcnapi.mui) used to launch the decrypted mui file from the first part of the blog post.
Table 4: Decrypted paths and file names
LOWKEY
The final part of the blog post describes the capabilities of the passive backdoor LOWKEY (MD5: 8aab5e2834feb68bb645e0bad4fa10bd) decrypted from DEADEYE.APPEND (MD5: 7f05d410dc0d1b0e7a3fcc6cdda7a2ff). LOWKEY is a passive backdoor that supports commands for a reverse shell, uploading and downloading files, listing and killing processes and file management. We have identified two variants of the LOWKEY backdoor.
The first is a TCP variant that listens on port 53, and the second is an HTTP variant that listens on TCP port 80. The HTTP variant intercepts URL requests matching the UrlPrefix http://+:80/requested.html. The + in the given UrlPrefix means that it will match any host name. It has been briefly mentioned by Kaspersky as “unknown backdoor”.
Both variants are loaded by the reflective loader described in the previous part of the blog post. This means we were able to extract the original file names. They contain meaningful names and provide a first hint on how the backdoor operates.
HTTP variant (MD5: c11dd805de683822bf4922aecb9bfef5)
E:\code\PortReuse\iis-share\2.5\IIS_Share\x64\Release\IIS_Share.dll
TCP variant (MD5: 7f05d410dc0d1b0e7a3fcc6cdda7a2ff)
E:\code\PortReuse\3389-share\DeviceIOContrl-Hook\v1.3-53\SK3.x\x64\Release\SK3.x.exe
The interesting parts are shown in Figure 6. PortReuse describes the general idea behind the backdoor, to operate on a well-known port. The paths also contain version numbers 2.5 and v1.3-53. IIS_Share is used for the HTTP variant and describes the targeted application, DeviceIOContrl-Hook is used for the TCP variant.
Figure 6: Overview important parts of executable path
Both LOWKEY variants are functionally identical with one exception. The TCP variant relies on a second component, a user mode rootkit that is capable of intercepting incoming TCP connections. The internal name used by the developers for that component is E:\code\PortReuse\3389-share\DeviceIOContrl-Hook\v1.3-53\NetAgent\x64\Release\NetAgent.exe.
Figure 7: LOWKEY components
Inner-Loader.dll
Inner-Loader.dll is a watch guard for the LOWKEY backdoor. It leverages the GetExtendedTcpTable API to retrieve all processes with an open TCP connection. If a process is listening on TCP port 53 it injects NetAgent.exe into the process. This is done in a loop with a 10 second delay. The loader exits the loop when NetAgent.exe has been successfully injected. After the injection it will create a new thread for the LOWKEY backdoor (SK3.x.exe).
The watch guard enters an endless loop that executes every 20 minutes and ensures that the NetAgent.exe and the LOWKEY backdoor are still active. If this is not the case it will relaunch the backdoor or reinject the NetAgent.exe.
NetAgent.exe
NetAgent.exe is a user mode rootkit that provides covert communication with the LOWKEY backdoor component. It forwards incoming packets, after receiving the byte sequence FF FF 01 00 00 01 00 00 00 00 00 00, to the named pipe \\.\pipe\Microsoft Ole Object {30000-7100-12985-00001-00001}.
The component works by hooking the NtDeviceIoControlFile API. It does that by allocating a suspiciously large region of memory, which is used as a global hook table. The table consists of 0x668A0 bytes and has read, write and execute permissions set.
Each hook table entry consists of 3 pointers. The first points to memory containing the original 11 bytes of each hooked function, the second entry contains a pointer to the remaining original instructions and the third pointer points to the function hook. The malware will only hook one function in this manner and therefore allocates an unnecessary large amount of memory. The malware was designed to hook up to 10000 functions this way.
The hook function begins by iterating the global hook table and compares the pointer to the hook function to itself. This is done to find the original instructions for the installed hook, in this case NtDeviceIoControlFile. The malware then executes the saved instructions which results in a regular NtDeviceIoControlFile API call. Afterwards the IoControlCode is compared to 0x12017 (AFD_RECV).
If the IoControlCode does not match, the original API call results are returned.
If they match the malware compares the first 12 bytes of the incoming data. As it is effectively a TCP packet, it is parsing the TCP header to get the data of the packet. The first 12 bytes of the data section are compared against the hard-coded byte pattern: FF FF 01 00 00 01 00 00 00 00 00 00.
If they match it expects to receive additional data, which seems to be unused, and then responds with a 16 byte header 00 00 00 00 00 91 03 00 00 00 00 00 80 1F 00 00, which seems to be hard-coded and to indicate that following packets will be forwarded to the named pipe \\.\pipe\Microsoft Ole Object {30000-7100-12985-00001-00001}. The backdoor component (SK3.x.exe) receives and sends data to the named pipe. The hook function will forward all received data from the named pipe back to the socket, effectively allowing a covert communication between the named pipe and the socket.
SK3.x.exe
SK3.x.exe is the actual backdoor component. It supports writing and reading files, modification of file properties, an interactive command shell, TCP relay functionality and listing running processes. The backdoor opens a named pipe \\.\pipe\Microsoft Ole Object {30000-7100-12985-00001-00001} for communication.
Data received by the backdoor is encrypted with RC4 using the key “CreateThread“ and then XORed with 0x77. All data sent by the backdoor uses the same encryption in reverse order (first XOR with 0x77, then RC4 encrypted with the key “CreateThread“). Encrypted data is preceded by a 16-byte header which is unencrypted containing an identifier and the size of the following encrypted packet.
An example header looks as follows:
00 00 00 00 00 FD 00 00 10 00 00 00 00 00 00 00
Table 5: Subcomponents of header
The backdoor supports the commands listed in tables Table 6 and Table 7. Most commands expect a string at the beginning which likely describes the command and is for the convenience of the operators, but this string isn't actively used by the malware and could be anything. For example, KILL could also be A . Some of the commands rely on two payloads (UserFunction.dll and ProcTran.dll), that are embedded in the backdoor and are either injected into another process or launch another process.
UserFunction.dll
Userfunction.dll starts a hidden cmd.exe process, creates the named pipe \\.\pipe\Microsoft Ole Object {30000-7100-12985-00000-00000} and forwards all received data from the pipe to the standard input of the cmd.exe process. All output from the process is redirected back to the named pipe. This allows interaction with the shell over the named pipe.
ProcTran.dll
The component opens a TCP connection to the provided host and port, creates the named pipe \\.\pipe\Microsoft Ole Object {30000-7100-12985-00000-00001} and forwards all received data from the pipe to the opened TCP connection. All received packets on the connection are forwarded to the named pipe. This allows interaction with the TCP connection over the named pipe.
Table 6: C2 commands
The commands listed in Table 7 are used to provide functionality of a TCP traffic relay. This allows operators to communicate through the backdoor with another host via TCP. This could be used for lateral movement in a network. For example, one instance of the backdoor could be used as a jump host and additional hosts in the target network could be reached via the TCP traffic relay. Note that the commands 0xD2, 0xD3 and 0xD6 listed in Table 7 can be used in the main backdoor thread, without having to use the ProcTran.dll.
Table 7: C2 commands TCP relay
Summary
The TCP LOWKEY variant passively listens for the byte sequence FF FF 01 00 00 01 00 00 00 00 00 00 on TCP port 53 to be activated. The backdoor then uses up to three named pipes for communication. One pipe is used for the main communication of the backdoor, the other ones are used on demand for the embedded payloads.
- \\.\pipe\Microsoft Ole Object {30000-7100-12985-00001-00001} main communication pipe
- \\.\pipe\Microsoft Ole Object {30000-7100-12985-00000-00001} named pipe used for interaction with the TCP relay module ProcTran.dll
- \\.\pipe\Microsoft Ole Object {30000-7100-12985-00000-00000} named pipe used for the interactive shell module UserFunction.dll
Figure 8 summarizes how the LOWKEY components interact with each other.
Figure 8: LOWKEY passive backdoor overview
Appendix
Appendix 1: List of samples with RC5 encrypted payloads