With more than 60 million websites, including 33.4 percent of the top 10 million global websites, built on the WordPress platform, it is big news when a new attack aimed at this popular tool surfaces. And, as you can probably guess, the Zscaler ThreatLabZ team recently noticed another campaign targeting WordPress sites.
Since the first week of April 2020, we observed several instances of malicious Java archive (JAR) files hosted on compromised WordPress websites. These JAR files used several layers of encryption to protect its final payload—the Adwind remote access Trojan (RAT).
In this blog, we describe two aspects of this campaign. In the first part, we describe the intelligence information we gathered from this campaign, which was used for threat attribution. In the second part, we explain in detail all the steps used for decrypting the multiple layers of encryption that were used to protect the final payload.
Compromised sites used for hosting the payload
We observed a common pattern shared among all the compromised websites in this campaign, which are used to host the malicious JAR payload. All these websites used the Content Management System (CMS) from WordPress. Attackers often exploit vulnerabilities in WordPress plugins to get access to the admin panel of the CMS. Once the access is obtained, they can host their payload on the server.
The WordPress version can be confirmed by checking the meta HTML tag in the source code with the “name” attribute field set to “generator” as shown below for one of the compromised sites observed in this campaign.
Figure 1: WordPress version in the HTML source code.
Most of the compromised websites in this campaign were running a fairly recent version of WordPress—5.3.x. Only a few sites were running outdated versions, such as 4.5.x or 3.3.x.
The file names for the payload varied between themes ranging from Coronavirus to payment invoices and shipping delivery services, such as DHL and USPS, as shown below:
Covid-19Update.jar
Reylontransport-covid19-statement20.jar
RescheduleUSPS.jar
DHLPaket.jar
Threat attribution
On some of the compromised WordPress sites used to host the malicious JAR files, we were able to find PHP web shells that attackers used to control the web server as shown in Figure 2.
Figure 2: PHP web shell on a compromised WordPress site.
There are some other web shells present in the same directory. After inspecting the different web shells, we located a PHP mailer script that would send a test email to attacker-specified email addresses, as shown in Figure 3.
Figure 3: PHP mailing script found on the compromised server.
Email addresses:
Technical analysis of the encrypted JAR
There were multiple layers of encryption used in the JAR files in this campaign, which made it clear that some form of crypting service was used by the threat actor to protect the final JAR payload. After decrypting several layers, we found a reference to “Qarallax”, which leads us to believe that the cryptor used can be attributed to the Qarallax crypt service.
In this section of the blog, we will go in to the details of the different layers of encryption and how we unpacked them one by one to reveal the final payload.
For the purpose of analysis, we have chosen the JAR file with MD5 hash: 0a5f34440389ca860235434eea963465
Filename of the JAR file: Covid-19Update.jar
Decryption: Stage 1
This JAR file contains two encrypted resources:
Resource 1: /cloud/file.update
Resource: 2: AaxIv/WEPcXKp/UBLah/kCQuJbJn
These resources will be loaded and decrypted at runtime. To understand the decryption process, let us look at the source code of rr.class present inside this JAR file. This class file is responsible for loading the above resources and calling the decryption routines. The code section is shown in Figure 4 with relevant comments added to the code.
Figure 4: Code for decrypting the resources in stage 1.
It is also important to note that the strings referenced in the above code are defined inside the gf.class file. All these strings are encrypted as shown in Figure 5.
Figure 5: Encrypted strings in stage 1.
The string decryption routine is shown in Figure 6.
Figure 6: String decryption routine in stage 1.
This string decryption routine was reused in the later stages as well. So we rewrote it in Python to make the decryption process of further layers easier. The code for string decryption is mentioned in the Appendix I section of this blog.
The different steps involved in decryption in above code are:
- The resource, “/cloud/file.update” is read using getClass.getResourceAsStream() into a byte array.
- This resource is decrypted using the AES key: “Psjduiwo8wosld9O” using the AES block cipher mode: AES/ECB/PKCS5PADDING.
- The result of the above decryption is an XML file, which is shown in Figure 7.
Figure 7: XML file obtained after decryption. It contains the AES key to decrypt the second resource.
4. This resource is loaded using the loadFromXML() method which allows individual properties from the XML to be accessed to continue the decryption process.
Decryption: Stage 2
The XML file, which was obtained after decrypting stage 1, is used to decrypt the second layer as defined below.
- The SERVER entry in this XML file corresponds to the second encrypted resource called: /AaxIv/WEPcXKp/UBLah/kCQuJbJn.Tje in the JAR file. The PASSWORD entry in the above XML file corresponds to the AES key, which will be used to decrypt this second resource.
- The AES key used for decrypting the second resource is: xslNGppgnJmTwGGH.
- Second resource is decrypted to a Gzip file, which gets decompressed to another JAR file.
Decryption: Stage 3
In this stage, we will look at the decrypted JAR file obtained from stage 2. The class files and resource file structure for this JAR is as shown in Figure 8.
Figure 8: JAR file structure of stage 3.
This JAR file has one encrypted resource called “/this/file.grt”.
Execution of this JAR file begins in the method: j2t.ple.IL as shown in Figure 9.
Figure 9: The main method in stage 3.
The strings in this method were encrypted using the same string encryption method as in stage 1. The only difference was in the initial one byte XOR key, which was changed to 0x58.
After decrypting the strings in the main method, we can see a reference to the Qarallax project. Qarallax provides crypting services for encrypting JAR files on underground hacking forums, leading us to correlate this to Qarallax.
Now let us look at the method, Il() defined in il_1.class file. This method performs the resource decryption as shown in Figure 10.
Figure 10: Code for performing decryption of resources in stage 3.
The different steps involved in the decryption are:
- It loads the encrypted resource: “/this/file.grt” using getClass.getResourceAsStream() into a byte array.
- It uses the DES key: R5uE7enKM8wK0qOk8s9di to decrypt the above resource.
- The result of decryption is an XML file as shown in Figure 11.
Figure 11: The XML file after decryption in stage 3.
4. The SERVER_BIN file in the above decrypted XML corresponds to the next stage encrypted file. PASSWORD_CRYPTED corresponds to an encrypted AES key, which will be used to decrypt the SERVER_BIN file. PRIVATE_PASSWORD is the RSA private key, which is used to decrypt the AES key.
5. Each of the above properties are loaded from the XML using loadFromXML() and getProperty() methods.
6. The RSA private key is stored as a serialized Java object. It is unserialized using the readObject() method.
7. The unserialized RSA key is used to decrypt the AES key defined in the PASSWORD_CRYPTED section of the XML.
8. The decrypted AES key is used to decrypt the SERVER_BIN file, which results in the next stage decrypted file.
Decryption: Stage 4
The decrypted payload obtained from stage 3 is the final JAR payload, which was protected with multiple layers of encryption. The JAR class file structure for this payload is as shown in Figure 12.
Figure 12: The JAR file structure in stage 4.
This file contains multiple encrypted resources.
Key1.json – RSA private key stored as a serialized Java object.
Key2.json – Encrypted AES key.
Config.json – Encrypted config file of the Java RAT.
Let us look at the main method—server.main.Start()—of this JAR file. We can see the use of encrypted strings in this JAR file as shown in Figure 13.
Figure 13: The encrypted strings in stage 4.
Figure 14 shows the string decryption routine.
Figure 14: The string decryption routine in stage 4.
This string decryption routine is different from the previous stages we analyzed. It is a variant of XOR decryption, which derives the decryption key in an interesting way.
The first two lines of the decryption routine are:
StackTraceElement stackTraceElement = new LinkageError().getStackTrace()[1];
String string = new StringBuffer(stackTraceElement.getClassName()).append(stackTraceElement.getMethodName()).toString();
These lines are used to fetch the class name and the method name from which the string decryption routine was called. To find the calling class name and method name, it generates an exception using LinkageError() and then fetches the first stack frame using getStackTrace()[1]. From this stack frame, the calling class name and method name are derived.
As an example, when the string decryption routine is called by the method "ii" in the class "Start", then the XOR decryption key will be: "Startii".
Upon further analysis, we discovered that this string decryption routine is the same as the one provided by the Java obfuscator called Allatori. Usually class files obfuscated with Allatori obfuscator use the method name: ALLATORIxDEMOxhthr().
However, in this case, the method name was also obfuscated to remove any reference to Allatori.
We rewrote the string decryption routine in Python to decrypt all the strings in this JAR file. The Python script is provided in the Appendix II section of this blog.
After decrypting the strings, the resulting code is shown in Figure 15.
Figure 15: The code after decrypting the strings.
Decryption of the config file
As a first step, we will decrypt the resources to get access to the config file. The steps involved in decryption are:
- Loads the serialized object from the resource: “/server/resources/Key1.json” using getClass.getResourceAsStream().
- Unserializes the Java object using readObject() to get access to the RSA private key.
- Loads the encrypted AES key from the resource called: "/server/resources/Key2.json".
- Loads the encrypted config file from the resource called: “/server/resources/config.json".
- Decrypts the AES key using the RSA private key.
- Decrypts the encrypted resource using the decrypted AES key.
The resulting decrypted config file is as shown below.
{"securityRetry":20,"vbox":true,"security":[],"nickName":"quarantoes","installation":{"jarName":"aDaGm","moduleFolder":"pWnmd","moduleEntry":"tUjeninoYOKbbABjEQOfwMmkkAV/iPYMlXQvBnHKdoBEfaulmhiFQGfShHjNdiXUwSYRhLsTHMKLKkpXcgQlsdxcFcRbKfmpoSbxtVnqmjBHZYAWtNtYoYCLANenc/bOErAlOWThJDSpULmJVQtqDhaxOKlhudNtTRmXmBLSbwTosaNniBOKWGfjLMnAhIFspJ.aHpXpONmqBPEdQLuDgrAhZChTbGSTZlKJXFxqBvREcuWrMTHhhIngmMTMtkrvrWhhjpluNgrCCbfVu","uniqueIDFile":".ntusernt.ini","delay":2,"jreFolder":"Oracle","active":true,"mainFolder":"pMbbW","moduleExtension":"riY","jarExtension":"class","jarRegistry":"UKikhtn"},"vmware":true,"encryptKey":"ClonWWwVrPXQwSEIcfITKNPMg","network":[{"delay":2,"port":9932,"dns":"212.114.52.236"}]}
We provide a description of the key fields present in the above configuration file.
Vbox: Indicates whether the presence of VirtualBox should be checked or not.
nickName: This is a unique identifier used while building the RAT. In our case, it is “quarantoes”.
Installation: A JSON that contains key-value pairs describing the location where the JAR file needs to be copied to on the file system.
jreFolder: Indicates the folder where all the files required for running the JAR are stored.
jarRegistry: The name of the Windows registry key used for persistence.
delay: Indicates the number of seconds to delay the execution.
Vmware: Indicates whether the presence of VMWare should be checked or not.
Port: The port number on which the RAT communicates with the server.
DNS: IP address of the callback server.
Activities performed by the RAT
Below are the main activities performed by the RAT.
-
It checks the OS name and if it is not Windows, then the RAT does not execute.
- It copies itself to the path: C:\Users\user\pMbbW\aDaGm.class. The directory name in this path is selected from the “mainFolder” parameter of the config file and the filename is selected using the “jarName” parameter in the config file. The file extension for the JAR file is selected as “.class” based on the configured value for parameter: “jarExtension” in the config file.
- It sets the Windows registry key for persistence to ensure that the above JAR file is executed automatically using javaw.exe upon reboot.
Key path: HKEY_USERS\Software\Microsoft\Windows\CurrentVersion\Run
Key name: UKikhtn
Key value: "C:\Users\user\Oracle\bin\javaw.exe" -jar "C:\Users\user\pMbbW\aDaGm.class"
The key name is fetched from the config file as well.
- It loads the DLL from the resource section: “/server/resources” based on the system architecture. For 32-bit system, it loads x86.dll and for 64-bit system, it loads amd64.dll.
This DLL will be loaded and copied to a temporary location on the file system with the file extension, “.xml”. The DLL is then loaded using the System.load() command as shown in Figure 16.
Figure 16: The code for loading the DLL.
5. It checks the value of the “active” key in the decrypted config.json file. If the value is set to true, then the RAT delays the execution by the “delay” number of seconds as configured in the config.json file.
6. It checks for the presence of a virtualization environment, such as VMWare, Virtualbox and Qemu. If it finds the presence of such an environment, then it exits the execution.
We will not be describing the functionality of this binary in detail in this blog since the final payload is a well-known jRAT (Java-based RAT).
Cloud Sandbox Detection
Figure 17 shows the Zscaler Cloud Sandbox successfully detecting this JAR-based threat.
Figure 17: Zscaler Cloud Sandbox detection.
In addition to sandbox detections, Zscaler’s multilayered cloud security platform detects indicators at various levels, as seen here: Java.Backdoor.Adwind.
Conclusion
This threat actor leverages compromised websites to serve heavily encrypted variants of a Java-based RAT, which makes the detection difficult over the network.
As an extra precaution, users should not run JAR files from untrusted and unknown sources since JAR files contain executable code and have the capability of infecting a system.
Web administrators who use WordPress installations should ensure that they are running the latest version of WordPress plugins and themes to prevent any vulnerability from being exploited.
The Zscaler ThreatLabZ team will continue to monitor this campaign, as well as others, to help keep our customers safe.
MITRE ATT&CK TTP Mapping
Tactic | Technique |
Persistence |
Registry Run Keys / Startup Folder - T1060 |
Obfuscation |
Obfuscated Files or Information - T1027 |
Software Packing |
T1045 |
Process Discovery |
Query and kill system processes - T1057 |
Security Software Discovery |
T1063 |
System Information Discovery |
T1082 |
System Network Configuration Discovery |
T1016 |
Windows Management Instrumentation |
T1047 |
Uncommonly Used Port | T1065 |
Indicators of Compromise (IOC)
URLs hosting JAR files
hxxp://haus-pesjak[.]at/Covid-19Update.jar
hxxps://digitaltextile.com[.]ru/lk/Deutsche%20Telekom.jar
hxxps://digitaltextile.com[.]ru/n/DHL%20paket.jar
hxxp://haus-pesjak[.]at/04-07-20Intuitinvoices.jar
hxxp://teddyshatsworld[.]pl/Reylontransport-covid19-statement20.jar
hxxp://thaivictory.co[.]th/pageconfig/album/dir/5/order.jar
hxxp://cherryemoore[.]com/USPS/RedeliveryUSPS.jar
hxxps://feylibertad[.]org/Amazon-PO20023938.jar
hxxp://mahalowood[.]com/USPS/USPSReschedulerLabel.jar
hxxps://newsha.jsonland[.]ir/wp-includes/css/DHLPaket.jar
hxxps://www.stillval[.]com/USPS/RescheduleUSPS.jar
hxxps://thediscoveryrun[.]com/UPS/ShippingInfo.jar
hxxp://jeddahcrumbly[.]com/DHLPAKET.jar
hxxps://dev.medialogistics2020[.]ca/wp-content/plugins/ubh/Quickbooks-INV5066.jar
Hashes of the samples
7e4bdf62d3ecd78b3f407f6ec1158678
0a5f34440389ca860235434eea963465
1da18ec639f7ec2a8aad58655d846e23
d7489b47e17630e5594a320b43b201db
da52c24302a03626d2175123b751f466
b766cf6695730b74a107cb73157262b1
919f2d0043f063a90702fb36887699e8
d470d5a428f99818278fb2816a8d03e9
8f5e55fbb1bee93dc5912dcbd0092519
4a97b2d004d72b69aa64f621b5b74775
051b4da1f0079c6f60d6c8eb62b3f586
2020551b5373121053abdbf3eaafa02d
a4da22e269b93148eb9857036b9a072a
876eb4208ef2eec6e9f12b13f764a975
1d77e96974e1e2301ed78cec19e8710b
Network Indicators
212.114.52[.]236:9932
unks123.duckdns[.]org:46865
lay.dubya[.]us:8181
fresh.ygto[.]com:1010
gwiza1988.hopto[.]org:6025
praisesalways.ddns[.]net:1010
wawa.cleansite[.]us:1010
dlee889.mywire[.]org:5858
Appendix I
String decryption routine
#! /usr/bin/python
# -*- coding: utf-8 -*-
import sys
# Replace encoded_string with the string to be decoded.
input = <encoded_string>
# Replace one_byte_key with the respective value found in the Java class file
key = <one_byte_key>
l = len(input) - 1
output = []
counter = l
while counter >= 0:
b1 = ord(input[l]) ^ key
t = (l ^ key) & 0x3f
output.append(chr(b1))
l = l - 1
if l < 0:
break
b2 = ord(input[l]) ^ t
key = (l ^ t) & 0x3f
output.append(chr(b2))
l = l - 1
counter = l
output.reverse()
print(''.join(output))
Allatori string decryption routine ported to Python
#! /usr/bin/python
import os
import sys
def decode(encrypted, c_method):
base_string = c_method
n2 = len(encrypted)
n3 = n2 - 1
# Replace n4_val and n5_val with the respective values used in the Allatori obfuscator.
# These are one byte values
n4 = <n4_val>
n5 = <n5_val>
n6 = n = len(base_string) - 1
string2 = base_string
result = []
while (n3 >= 0):
n7 = n3
n3 = n3 - 1
result.append(chr(n5 ^ (ord(encrypted[n7]) ^ ord(string2[n]))))
if (n3 < 0): break
n8 = n3
n3 = n3 - 1
result.append(chr(n4 ^ (ord(encrypted[n8]) ^ ord(string2[n]))))
m = n
n = n - 1
if (m < 0):
n = n6;
n9 = n3
return result
if __name__ == "__main__":
# Replace encrypted_string with the string to be decrypted
encrypted_str = <encrypted_string>
# Replace calling_class_name and calling_method_name with the names of the Class and Method from where the decryption routine was invoked
c_method = <calling_class_name> + <calling_method_name>
print ''.join(decode(encrypted_str, c_method))[::-1]