STRRAT (Strigoi) - Malware Analysis Lab
Overview
Part 1: STRRAT Dropper and STRRAT Deobfuscation
Letâs take a look at a recent sample of the Java-based malware known as STRRAT. We will cover some techniques on how to identify the malware and reverse engineer it. We will start with a malicious JAR file being distributed through email malspam campaigns, and in this instance the file had the name INQUIRY______535262623.jpg.jar
- Source: Malshare.com
- Extra Public Reporting: Karsten Hahn - G Data
- Extra Public Reporting: Microsoft
- Extra Public Reporting: Brad Duncan
First off we obtain a sample with a particular SHA256 hash:
Starting Host IOC: ec48d708eb393d94b995eb7d0194bded701c456c666c7bb967ced016d9f1eff5
Given this is a Java-based RAT we can start by opening it using JD-GUI, a Java Decompiler.
If we expand out the only class this contains, we immediately begin to see references to its resource section, wscript, and âuser.homeâ. Given these references, and the lack of classes, this is a good indicator that the Java Archive will likely function as a dropper, placing a script with the name âloorqhustq.jsâ in the users home directory.
To understand the script being dropped to âloorqhustq.jsâ, we can take a look at the resource section by extracting the Java Archive using 7-Zip.
At a glance this isnât too large; however, it does have a Base64 blob which looks to be present and some data manipulation to ensure this canât easily be decoded. One way to tackle this is to replace references of âevalâ (which is essentially telling JavaScript to execute and evaluate what it is being passed, and instead make this âconsole.logâ. By doing this the malicious JavaScript will instead log what it is being presented, rather than processing and evaluating it.
To do this we still need a way to interpret the JavaScript in a safe setting. By opening up Google Chrome and pressing âF12â we can view the console. At this point we can copy in our modified JavaScript and have Chrome interpret it for us.
The end result is a variable being defined containing an object called lmao$$$_. This is an ActiveXObject storing a Base64 encoded string; however, because we ran this inside of a browser and replaced the eval statements, we get an Uncaught ReferenceError. This is expected as we didnât want the dropper to progress, and only wanted to extract this Base64 string.
By copying solely the string into CyberChef, weâre able to begin extracting what will be dropped.
Examining this decoded string reveals a variable which is being defined as âlongTextâ, before being manipulated to make a valid Base64 string which is once again decoded to disk, this time in the userâs application data directory to a file called r.txt
Before we look into extracting âr.txtâ, we can continue to look at what is happening when the dropper triggers. Examining the first part of the dropper, we find it is enumerating if the Java Runtime Environment is present, and where it is, it will attempt to execute the dropped âr.txtâ as a Java Archive, if not, it will run a function called âGrabJreFromNetâ.
Looking at the function GrabJreFromNet, we can see that it attempts to reach out to hxxp[://]wshsoft[.]company/jre7[.]zip to pull down a version of JRE. This is then saved to a zip file in the userâs application data directory before it is extracted to disk, used to run r.txt, and a run key is setup for persistence to ensure it always runs whenever that user logs on.
Network IOC: hxxp[://]wshsoft[.]company/jre7[.]zip
Registry Run Key Persistence (Host IOC): HKCU\Software\Microsoft\Windows\CurrentVersion\Run\ntfsmgr
At this point we can go ahead and take the first 4 lines of this second stage dropper, and add an entry to once again log to the console the contents of âlongTextâ which will be decoded to r.txt as a Java Archive and run.
The output is a âPKZIPâ archive, which we can identify through the presence of a MANIFEST.MF as a Java Archive.
Weâre able to download this file to disk and retrieve the new hash of this Java Archive using PowerShell.
get-filehash .\STRRAT.jar
0A6D2526077276F4D0141E9B4D94F373CC1AE9D6437A02887BE96A16E2D864CF
Host IOC: 0A6D2526077276F4D0141E9B4D94F373CC1AE9D6437A02887BE96A16E2D864CF
By checking the hash against VirusTotal we can see it hasnât been analysed and shared publicly yet.
To help ensure AV vendors are aware of this STRRAT sample, we can submit it to VirusTotal for scanning.
27/60, not a bad rate of detection, noting this doesnât represent all detection capabilities by vendors, and just because a vendor doesnât flag it here, doesnât mean they wonât detect or prevent it in production.
Opening STRRAT in JD-GUI, we can take a look at the manifest file to find the âMainâ class which will be executed when the archive is run.
By examining the main class we get a lot of obfuscated classes, and references to âStringBuilderâ being leveraged. The string âSTRRATâ is also present once we deobfuscate this sample.
At this stage deobfuscating the code manually would be a bit of a challenge. Luckily we have java-deobfuscator available to us which can perform a lot of the heavy lifting.
To use this we will first create a yaml file called detect.yml which will be used to detect what obfuscator is in use.
input: C:\Users\User\Desktop\STRRAT\Stage 2\STRRAT.jar
detect: true
From here we can pass this as the configuration to deobfuscator, and find that this looks to be using âAllatoriâs Java Obfuscator - String Encryptionâ.
java -jar deobfuscator.jar --config detect.yml
The irony is that the website providing this tool is calling us a âhackerâ for reverse engineering the Java Archive.
From here we can create another file set to deobfuscate the Java Archive in question using the first recommended transformer.
input: C:\Users\User\Desktop\STRRAT\Stage 2\STRRAT.jar
output: C:\Users\User\Desktop\STRRAT\Stage 2\STRRAT-Deobfuscated.jar
transformers:
- com.javadeobfuscator.deobfuscator.transformers.allatori.StringEncryptionTransformer
After executing the transformer in question weâre presented with a number of âdecryption methodsâ, and âencrypted stringsâ being rectified. In this instance thereâs some output errors also, but this isnât of concern.
java -jar deobfuscator.jar --config config.yml
At this point we can see a number of Java Archive files being referenced:
Of these referenced includes a project âsystem-hookâ which is essentially being used here as a Java-based key-logger.
Examining the Main Class further we can now find reference to a pastebin URL being contacted. This gives us some potential C2 infrastructure leveraged by this malware. It should be noted this can be modified by the Pastebin user to point elsewhere.
Dynamic Infrastructure (Network IOC): hxxps[://]pastebin[.]com/raw/Jdnx8jdg
Pastebin Actor Account (Network IOC): hxxps[://]pastebin[.]com/u/wshsoft
At the time of writeup this page had been visited 32,736 times, and pointed to the domain pluginserver[.]duckdns[.]org
Current Infrastructure (Network IOC): pluginserver[.]duckdns[.]org
In addition thereâs a hardcoded URL mentioned which looks to contact the below:
Network IOC: hxxp[://]str-master[.]pw/strigoi/server/ping[.]php?lid=
Taking a look at the class cbnfdhn.class, we can get a feel for some potential functionality of STRRAT including shutting down an operating system, uploading and downloading filesâŚ
Enumerating the OS, processes, key-logging, stealing saved credentials.
In addition thereâs a reference to âcrimson_info.txtâ which has been publicly reported as a fake ransomware module.
Part 2: Extracting and Decrypting STRRAT Configuration
To perform more thorough analysis on the deobfuscated STRRAT, weâre going to leverage a tool called âRecafâ. This will allow us to decompile Java classes in a more user-friendly way given the malware authors have reused class and method names. Upon decompiling this weâre going to take a look at the primary entry point of the program in the âMainâ class.
An easy way to find this class is to search for âvoid mainâ because the main class method isnât intended to return any output. In the below we see a watermark added to the malware to show that âAllatori Obfuscator 7.3 DEMOâ was used to obfuscate it. In addition we can see it is checking for the number of arguments provided, and where this isnât provided it will run the Main.bsgshsbs method (which is returning an object to be created from the âdfghrthâ class). Further to this we can see a message of âThis PC is not supportedâ being returned after this if a string isnât able to be assigned from the output of Main.sdfsldf. To find the right method definition here we can search for âString sdfsldf()â given we know it is returning a string, and here we can see that it is simply looking for the path the running JAR file is running from (likely taken from this StackOverflow post).
Examining the object created from the âdfghrthâ class reveals this is a file lock being created in the userâs home directory. If there are command-line arguments added this will create a file lock with the name â64578lock.fileâ in the userâs home directory given this number is passed as a parameter when creating this lock object.
Host IOC: 64578lock.file
This serves as the port that the RAT will connect back to.
In the case where no command-line arguments are provided, this will first create an object called âobject2â from the Main class method âbsgshsbsâ. To make finding these references easier, weâll begin right clicking them and using âGoto definitionâ.
We can also reveal references to this particular method by instead clicking âSearch referencesâ.
Examining this method we can see that it looks to be taking in a stream from a file within its resources called âconfig.txtâ, Base64 decoding it, and then it passes this and the string âstrigoiâ to another method, this time within the sabretb class called âdfhttegdâ.
Within this class and method we can see that STRRAT is performing an AES decryption routine based on the provided AES encrypted stream from config.txt, and a âpasswordâ of âstrigoiâ.
Although Iâm no crypto expert, in short, the password is being used to derive a secret key via the âpassword-based key derivation function 2â (PBKDF2), 65536 SHA1 hashing iterations, a key size of 128, using CBC mode, and a salt which is the ânonceâ extracted from our AES encrypted string. In this instance the âsaltâ (nonce) functions as the Initialisation Vector used with the AES Key to decrypt the string.
- More reading around AES Encryption and Decryption in Java can be found created by baeldung
To decrypt this using CyberChef we would have to first get the salt (nonce) used, derive the PBKDF2 key, and then use this with the appropriate IV to decrypt our content.
This is a bit of work, and can quickly get confusing. Because STRRAT is implementing a decryption method, we can go ahead and take the relevant parts of the decryption method to decrypt the config file ourselves. If we copy âsabretbâ and âdfhttegdâ from âsabretbâ, and also âbsgshsbsâ from Main, weâve got the start of a decryption program (noting we will need to import the appropriate libraries in use).
In the above weâve copied the core content in; however, thereâs still some issues based on duplicate named variable declarations, classes which arenât throwing an exception, and an issue based on how it is reading the file input. Iâve gone ahead and rectified this with the end result being a Decrypter which takes 2 arguments, (input file, output file). Note: At present during our analysis thereâs some unknown configuration values as we havenât completely reveresed the malware.
The decrypter as it stands so far can be found below:
import java.nio.ByteBuffer;
import java.io.File;
import java.nio.file.StandardOpenOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.charset.StandardCharsets;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class STRRATConfigDecrypt {
public static void main(String[] args) throws Exception {
System.out.println("###################################################");
System.out.println("# STRRAT Malware (v.1.4) Decrypter by @CyberRaiju #");
System.out.println("###################################################");
if (args.length < 1) {
System.out.println();
System.out.println("PEBCAK - Use this with 2 arguments, an input and output file");
System.out.println("Example:");
System.out.println(
"java -jar STRRATConfigDecrypt.jar \'C:\\Users\\User\\config.txt\' \'C:\\Users\\User\\decrypted_config.txt'");
} else {
String inputdirectory = args[0].replace("\\", "\\\\");
String outputdirectory = args[1].replace("\\", "\\\\");
File configfile = new File(inputdirectory);
byte[] config = Files.readAllBytes(configfile.toPath());
byte[] parsedBytes = Base64.getDecoder().decode(config);
byte[] decryptedConfig = DecryptAES("strigoi", parsedBytes);
String string[] = new String(decryptedConfig, StandardCharsets.UTF_8).split("\\|");
System.out.println("Network IOC: " + string[0]);
System.out.println("File Lock: " + string[1] + "lock.file");
System.out.println("Network IOC: " + string[2]);
System.out.println("Network IOC: " + string[3]);
System.out.println("Unknown Parameter: " + string[4]);
System.out.println("Unknown Parameter: " + string[5]);
System.out.println("Unknown Parameter: " + string[6]);
System.out.println("Unknown Parameter: " + string[7]);
System.out.println("Possible License/Campaign ID: " + string[8]);
Path output = Paths.get(outputdirectory);
Files.write(output, "# STRRAT Malware (v.1.4) Decrypter by @CyberRaiju #\r\n".getBytes());
for (String str : string) {
str = str+"\r\n";
Files.write(output, str.getBytes(),StandardOpenOption.APPEND);
}
}
}
public static SecretKey SecretKey(String password, byte[] data) throws Exception {
PBEKeySpec a = new PBEKeySpec(password.toCharArray(), data, 65536, 128);
byte[] b = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(a).getEncoded();
return new SecretKeySpec(b, "AES");
}
public static byte[] DecryptAES(String password, byte[] bytes) throws Exception {
ByteBuffer byteBuffer;
int n;
byteBuffer = ByteBuffer.wrap(bytes);
n = byteBuffer.getInt();
if (n < 12 || n > 16) {
throw new IllegalArgumentException(
"Nonce size is incorrect. Make sure that the incoming data is an AES encrypted file.");
}
byte[] object3 = new byte[n];
byteBuffer.get(object3);
SecretKey key = SecretKey(password, object3);
byte[] byArray2 = new byte[byteBuffer.remaining()];
bytes = byArray2;
byteBuffer.get(byArray2);
Cipher crypto = Cipher.getInstance("AES/CBC/PKCS5PADDING");
IvParameterSpec IVSpec = new IvParameterSpec(object3);
crypto.init(2, key, IVSpec);
return crypto.doFinal(bytes);
}
}
After ensuring the config file has been extracted, by running this using Visual Studio Code and the following command line:
cd 'c:\Users\User\Desktop\STRRAT\Stage 2\STRRAT-Deobfuscated\carLambo\resources\Decrypter\STRRATConfigDecrypt'; & 'c:\Users\User\.vscode\extensions\vscjava.vscode-java-debug-0.31.0\scripts\launcher.bat' 'C:\Program Files\OpenJDK\openjdk-11.0.9_11\bin\java.exe' '-Dfile.encoding=UTF-8' '-cp' 'C:\Users\User\AppData\Roaming\Code\User\workspaceStorage\ed67bcda7ebbfaeaf8f2ea3a04d08723\redhat.java\jdt_ws\STRRATConfigDecrypt_7776d08d\bin' 'STRRATConfigDecrypt' "C:\Users\User\Desktop\STRRAT\Stage 2\STRRAT-Deobfuscated\carLambo\resources\config.txt" "C:\Users\User\Desktop\STRRAT\Stage 2\STRRAT-Deobfuscated\carLambo\resources\decrypted.txt"
Weâre presented with a decrypted configuration output which has also been stored in a file called âdecrypted.txtâ.
Network IOC: moregrace[.]duckdns[.]org
Host IOC: 3219lock.file
Network IOC: hxxp[://]jbfrost[.]live/strigoi/server/?hwid=1&lid=m&ht=5
Network IOC: palaintermine[.]duckdns[.]org
Although the config parameters have been extracted, itâs not clear on exactly what theyâre used for. By looking back into the method âsdfsldfâ in the Main class we can see it is checking arguments provided, and if an argument was provided, it would only use the 3rd configuration parameter from the file (the jbfrost[.]live URL) in a call to âcbnfdhn.sabretbâ.
In addition to this, the above mentioned method is all about making a GET request to the server and retrieving the response to return as a string.
We know that this string is being returned to cbnfdhn.dfhttegd which if we look into is holding a public string that can be referenced from multiple classes, so we know it is being stored for later usage.
By viewing references to this, we can see the different methods which either âPUTâ a value into this variable, or âGETâ the value from the variable.
At this point we know that the variable is written to within 5 methods, and read from 5 methods. If we examine where it is being read from by right clicking and selecting âEdit with Assemblerâ, we can see that all of these methods are creating sockets and using this as the callback address. In addition we can see reference to what appears to be âReverse Proxyâ and âHDRDPâ debug/response messages.
By performing some OSINT using urlscan.io we find that this URL used to just return âpluginserver[.]duckdns[.]orgâ, which is the same domain we found linked to our pastebin URL.
Itâs very likely that this domain was being used to fetch plugins for STRRAT. To tie most of this back together we can return to method âsdfsldfâ within the Main class and examine more closely what it is doing here.
In the above we see it is defining a couple of variables, one for the primary plugins URL which is extracted from the config file, and one which is pointing to the pastebin URL containing the same domain. We then see it is using this to download the appropriate Java Archive files (plugins), before a request is made to a master server which looks to have been registered to take a license ID. In this case the license ID would be âkhonsariâ.
We can also confirm that after this a file lock is being created in the user home directory by examining the object defined within object9 which in this case is an instance of âdfghrthâ.
At this stage we can begin to update our decrypter to refine what weâre pulling in from the config file.
It should be noted that weâve inferred this is STRRAT version 1.4 by examining the method âshshnfdnâ within âcbnfdhnâ which looks to be a check to see if STRRAT has been installed on this host or not.
If we move back to examining the main method which will continue to run, we can see that one of the parameters passed as âTrueâ is used to determine if persistence will be setup using a scheduled task called âSkypeâ, with another being used to determine if the configuration will be passed to a new array. This new array is then used within sstydgn.sdfsldf, where the stringArray3 7th parameter is also set to true.
Host IOC: Scheduled Task - "Skype"
If we once again examine references to this, it appears as if these values are also being used to determine if persistence is installed, only this time it is writing to the userâs startup directory.
At this stage we have enough information and context to complete our STRRAT configuration decrypter and can compile a Java Archive to extract the configuration from a given config.txt file.
"C:\Program Files\OpenJDK\openjdk-11.0.9_11\bin\java.exe" -jar STRRATConfigDecrypt.jar "C:\Users\User\Desktop\STRRAT\Stage 2\STRRAT-Deobfuscated\carLambo\resources\config.txt" "C:\Users\User\Desktop\STRRAT\Stage 2\STRRAT-Deobfuscated\carLambo\resources\decrypted.txt"
The completed STRRAT Config Decrypter has been made available for use here. This takes a configuration file and decryptâs it.
Alternatively, after collaborating with some bright minds on Twitter (shoutout to Chaitanya - @CbGmanit who reached out with assistance on properly getting the salt (nonce) used), the following recipe has been devised to fully decrypt this using CyberChef here
From_Base64('A-Za-z0-9+/=',true)
To_Hex('Space',0)
Register('([\\s\\S]{11}).([\\s\\S]{47})',true,false,false)
Register('([\\s\\S]*)',true,false,false)
Derive_PBKDF2_key({'option':'UTF8','string':'strigoi'},128,65536,'SHA1',{'option':'Hex','string':'$R1'})
Register('([\\s\\S]*)',true,false,false)
Find_/_Replace({'option':'Regex','string':'.*'},'$R2',false,false,true,false)
Find_/_Replace({'option':'Regex','string':'^([\\s\\S]{60})'},'',true,false,true,false)
AES_Decrypt({'option':'Hex','string':'$R3'},{'option':'Hex','string':'$R1'},'CBC','Hex','Raw',{'option':'Hex','string':''},{'option':'Hex','string':''})
Find_/_Replace({'option':'Regex','string':''},'',true,false,true,false)
Register('([a-zA-Z.]*).([a-zA-Z0-9.]*).([a-zA-Z0-9\\:?@=\\-&%//.]*).([a-zA-Z.]*).([a-zA-Z0-9.]*).([a-zA-Z0-9.]*).([a-zA-Z0-9.]*).([a-zA-Z0-9.]*).([a-zA-Z0-9\\:?@=\\-&%//.]*)',true,false,false)
Find_/_Replace({'option':'Regex','string':'.*'},'$R4',false,false,true,false)
Find_/_Replace({'option':'Simple string','string':'$R4'},'C2: $R4',false,false,true,false)
Register('([\\s\\S]*)',true,false,false)
Find_/_Replace({'option':'Regex','string':'.*'},'$R5',false,false,true,false)
Find_/_Replace({'option':'Simple string','string':'$R5'},'Primary Lock/Port: $R5',false,false,true,false)
Register('([\\s\\S]*)',true,false,false)
Find_/_Replace({'option':'Regex','string':'.*'},'$R6',false,false,true,false)
Find_/_Replace({'option':'Simple string','string':'$R6'},'Plugins Download URL: $R6',false,false,true,false)
Register('([\\s\\S]*)',true,false,false)
Find_/_Replace({'option':'Regex','string':'.*'},'$R7',false,false,true,false)
Find_/_Replace({'option':'Simple string','string':'$R7'},'Secondary C2: $R7',false,false,true,false)
Register('([\\s\\S]*)',true,false,false)
Find_/_Replace({'option':'Regex','string':'.*'},'$R8',false,false,true,false)
Find_/_Replace({'option':'Simple string','string':'$R8'},'Secondary Lock/Port: $R8',false,false,true,false)
Register('([\\s\\S]*)',true,false,false)
Find_/_Replace({'option':'Regex','string':'.*'},'$R9',false,false,true,false)
Find_/_Replace({'option':'Simple string','string':'$R9'},'Startup Folder Persistence: $R9',false,false,true,false)
Register('([\\s\\S]*)',true,false,false)
Find_/_Replace({'option':'Regex','string':'.*'},'$R10',false,false,true,false)
Find_/_Replace({'option':'Simple string','string':'$R10'},'Secondary Startup Folder Persistence: $R10',false,false,true,false)
Register('([\\s\\S]*)',true,false,false)
Find_/_Replace({'option':'Regex','string':'.*'},'$R11',false,false,true,false)
Find_/_Replace({'option':'Simple string','string':'$R11'},'Skype Scheduled Task Persistence: $R11',false,false,true,false)
Register('([\\s\\S]*)',true,false,false)
Find_/_Replace({'option':'Regex','string':'.*'},'$R12',false,false,true,false)
Find_/_Replace({'option':'Simple string','string':'$R12'},'License ID: $R12',false,false,true,false)
Register('([\\s\\S]*)',true,false,false)
Find_/_Replace({'option':'Regex','string':'.*'},'$R13\\n$R14\\n$R15\\n$R16\\n$R17\\n$R18\\n$R19\\n$R20\\n$R21',false,false,true,false)
Simply put in the config file above, and receive your decrypted configuration file.
Update: 6/4/2022 - This has also been ported to Python3 and improved upon for those who like to incorporate this into their automation pipelines, or who just enjoy some ASCII artwork ;) and can be found here. This takes a jar file (strrat) which contains the configuration file within its resources and automatically reads and decrypts this without the need to extract it first.
Usage: python3 decrypt-strrat.py [PATH TO STRRAT]
Part 3: Examining RAT Flow and Functionality
To drill into this a bit more, we need to know what Java classes are going to be most interesting. One way of doing this is to extract the classes and filter based on their size, because a larger class may often be indicative of more code making up more interesting functionality.
The easiest way to view this code is to view it in Decompiler view rather than viewing them in the âEdit with Assemblerâ view we used above.
If weâre having issues with using the Decompiler, it can often be useful to revisit previous steps. For example if weâre unable to decompile a class, perhaps we should try to deobfuscate the malware using the second transformer recommended by java-deobfuscator, or we could try a different decompiler within Recaf.
In this instance there doesnât appear to be any issues, so letâs first look back at the references to our public static string object âcbnfdhn.dfhttegdâ which is essentially holding the value âpluginserver[.]duckdns[.]orgâ. Within the fgfnbnc class we find this is referenced 3 times. In the first instance we see it is the method cbnfdhn.dfhttegd being run rather than the public string being accessed.
Because this is a method, we disregard this hit, but make note that whenever the method fgfnbnc.dfhttegd is run, it will attempt to run the method cbnfdhn.dfhttegd. Looking at the next method we can see the reference to HRDP-MGR we saw previously with a little more context.
In this instance we can see a new registry key being created which ensures that User Names are not shown at the login screen.
Host IOC: HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\System /v dontdisplaylastusername = 1
This is interesting as it immediately follows what looks to be ensuring that Terminal Services are enabled, and a call to add a user account. To determine if thereâs a hardcoded user account we can look out for we will need to examine âobject2â which is being cast to a string here; however, before we do weâll begin to rename some of these variables to help keep track of what weâre looking at. To do this we will need to ensure weâve highlighted the correct class reference, and right click > rename. Using Recafe all associated class references will be updated.
In this instance Iâve renamed it to PluginsServerURL, and also renamed the other reference in this socket connection to PluginsServerPort. Moving back to âobject2â, we can see this is being assigned by fgfnbnc3.dfhttegd. By viewing the definition of this variable, we find it is defined in the same class weâre currently in.
Renaming this variable and revisiting the call, we see that thereâs an object definition of fgfnbnc3 from the class fgfnbnc which is why we didnât see a class with the name fgfnbnc3.
The benefit of renaming the String is that now we can find all references to that easily. If we continue to examine this class we find that thereâs a definition of this variable within fgfnbncâs fgsbdfgsb method.
Examining this method reveals it is a small function used to build a random string to be used as a Username.
Itâs important to remember that because weâve managed to get some decompiled code, string builders like this can be replicated using VS Code to confirm what weâre reading. Although this is a basic method, we can still recreate it by making a class that spits out the output of this method.
From this we can see this will create a random string with 5 characters.
To get some structure behind analysing this malware flow, weâll return to the main class that we know will be run as soon as this malware kicks off. First weâll begin to rename any methods or variables weâve found so far and give them more descriptive names based on their perceived functionality. I wonât walk through all of the analysis before renaming the variables to assist with analysing the malware in a more meaningful way. Weâll continue to rename more as we determine their purpose; however to start off the following have been renamed:
- cbnfdhn.sabretb (Method) = cbnfdhn.MakeGetRetResponse
- Main.sdfsldf (Method) = Main.ObjMakeGetRetResponse
- Main.sdfsldf (Static String) = Main.STRPluginLocations
- Main.sabretb (Static String) = Main.STRFileSeparate
- Main.bsgshsbs (Object) = Main.ObjFileLock1
- Main.dfhttegd (Object) = Main.ObjFileLock2
- dfghrth (Class) = FileLock
- Main.sdfsldf (Method, no input) = Main.GetJarRunPath
- cbnfdhn.sdfsldf (Method, inputs) = cbnfdhn.C2ConnectAndRun
- cbnfdhn.mdgghtdsh (Static InputStream) = cbnfdhn.InStream1
- cbnfdhn.sgsfghhg (Static OutputStream) = cbnfdhn.OutStream1
- cbnfdhn.hghteerd (Method, no input) = cbnfdhn.ConnectToC2
- cbnfdhn.gsbthstgb (Static String) = cbnfdhn.Port1
- xbxcv (Class) = UserIdleObject
- Main.bsgshsbs (Method, no inputs) = Main.ExtractConfig
- sabretb.dfhttegd (Method, inputs) = sabretb.DecryptConfig
- sabretb.sdfsldf (Method, inputs) = sabretb.DeriveIV
- sabretb.sabretb (Method, inputs) = sabretb.CreateSecKey
- Main.dfhttegd (Method, no inputs) = Main.ConnectUnknownURL
- cbnfdhn.sdfsldf (Method, one int input) = cbnfdhn.GetFolderPath
- cbnfdhn.sdfsldf (Method, one string array input) = cbnfdhn.Uninstall
- cbnfdhn.bsgshsbs (Static String) = cbnfdhn.FolderPath
- cbnfdhn.sfsrgsbd (Static String) = cbnfdhn.STRLogsLocation
- cbnfdhn.sabretb (Static String) = cbnfdhn.STRBackupC2
- cbnfdhn.sdfsldf (Static Boolean) = cbnfdhn.ActiveConToC2
- cbnfdhn.thtyrths (Method, no input) = cbnfdhn.ParseNumOfCommands
- cbnfdhn.sabretb (Method, one int input) = cbnfdhn.ReceiveCommands
If we now examine the main method again we find the below:
If the malware is able to get the current malware run path and place dependency files in a folder called âlibâ, it will then attempt to get the AppData directory based on CSIDL 26. The malware will continue to extract/decrypt its config file and make requests to get any plugins from the URL configured. Finally after this we see it connect to the C2 ready to execute commands received. It should be noted that our defined variables PluginsServerURL and PluginsServerPort are both being reused here as the C2 configuration from within the decrypted config file.
In the above we see the start of our C2ConnectAndRun method. In this instance it will check if a directory already exists called strlogs in the malware working directory, if not it will create one. It will also create an object containing information on whether the infected user is idle, and how long theyâve been idle for, before it inevitably connects to the C2.
The ConnectToC2 method is pretty straight forward, based on the decrypted configuration file it will first close off any sockets if they already exist with the C2, next it will attempt to connect to the C2 server, and where that fails it will fallback to its backup C2.
At this point we can begin to see the different commands taken by this malware as theyâre received into the array âstringArray2â from the designated C2, which is also placed into stringArray3.
Based on this we know that STRRAT 1.4 has the following commands available.
- reboot
- shutdown
- uninstall
- disconnect
- down-n-exec
- update
- up-n-exec
- remote-cmd
- power-shell
- file-manager
- keylogger
- o-keylogger
- processes
- h-browser
- startup-list
- remote-screen
- rev-proxy
- hrdp-new
- hrdp-res
- chrome-pass
- foxmail-pass
- outlook-pass
- fox-pass
- tb-pass
- ie-pass
- all-pass
- save-all-pass
- chk-priv
- req-priv
- rw-encrypt
- rw-decrypt
- show-msg
- screen-on
Letâs examine these commands more in depth:
Command Functionality: reboot
This command simply runs the following:
Runtime.getRuntime().exec("cmd.exe /c shutdown /r /t 0");
Command Functionality: shutdown
This command simply runs the following:
Runtime.getRuntime().exec("cmd.exe /c shutdown /s /t 0");
Command Functionality: uninstall
This command runs the following (Noting weâve renamed the method to âUninstallâ):
cbnfdhn.Uninstall(stringArray);
The uninstall method will then proceed to delete the scheduled task persistence called skype regardless of if it exists, get the directory the malware is running from, delete it from the directory specified by CLSID 7 and 24 Listings can be found here, ping localhost to delay execution for a moment, and then delete the object passed to it, before what appears to be deletion of run keys.
It should be noted that the CLSID values are being passed as an integer (Decimal) rather than Hex and would need to be converted to get that 26 = AppData, and 7 = Users Startup Folder.
Command Functionality: disconnect
This command will set a boolean variable weâve renamed as ActiveConToC2 to false, before closing any stream inputs that are being used in ertdbdth to log key pushes, and then calls exit for this process thus terminating the running STRRAT instance.
In essence the disconnect method is terminating the process and disconnecting the C2 connection to STRRAT.
Command Functionality: down-n-exec
This command will call 2 more functions inside of cbnfdhn, sabretb (object, string), and dfhttegd (string) which weâll take a closer look at so they can be renamed.
The first passed parameter to sabretb is an object that will be containing a url from which to retrieve a remote file from. This takes the received file and stores it in object3 which is then launched using either java.exe, wscript.exe, or cmd.exe depending on its extension.
From here we can rename this sabretb method DownloadAndExecute. Finally examining dfhttegd (string) we can see this is just used to update the C2 on the STRRAT status, so we can name this and sdfsldf (string) StatusUpdate and StatusUpdateSend.
Command Functionality: update
This command will create a new object as a file passed to it by running cbnfdhn.gsbthstgb, release the current fileLock with a method called sdfsldf, execute the object that was passed down to the malware, and then uninstall the current STRRAT malware.
This seems to be a fairly hacked together (pun intended) update mechanism to send an updated version of STRRAT to infect the system, and remove the existing version. Thereâs not a lot of error handling to ensure that this is successful though, so if the update fails to execute the new version of STRRAT or the wrong number of parameters is sent, this can lead to STRRAT uninstalling itself without actually updating.
We will rename cbnfdhn.gsbthstgb to cbnfdhn.CreateFileObj for further analysis
Command Functionality: up-n-exec
This command will function similar to down-n-exec, only instead of it retrieving a file from a defined URL to run (download and execute) this is going to take an uploaded file directly from the C2 and execute it (upload and execute)
Command Functionality: remote-cmd
This command has 2 primary components, a new object creation with cbnfdhn.sfsrgsbd (this is very similar to sdfsldf (string) we renamed to StatusUpdateSend, except it does so using a new socket thatâs created to the C2), and a new instance of dgdfndnbcn which is created using the newly created socket, and a string array containing âcmd.exeâ.
Weâll rename cbnfdhn.sfsrgsbd to cbnfdhn.StatusUpdateSendNewSock. Analysing the new instance of dgdfndnbcn, we can see that this is about 150 lines of decompiled code long, but we can start with the definition using a socket object and a string array.
Although the obfuscation here makes things a little bit confusing, if we look at what weâre dealing with as a whole, we can see thereâs an input and output stream from the C2, a new process using ProcessBuilder, and new threads that will be started for a given process.
Whatâs interesting with this is that only a socket and cmd.exe is being passed to this process creation, and thereâs no command-line parameters given. The reason for this is because it creates a new instance of sabretb, which in this case is the process being run, and is redirecting input and output from cmd.exe directly to the established socket connection to the C2.
If an error occurs a boolean is switched and the socket is released. To this end, remote_cmd is a way of issuing remote commands to the endpoint in the form of a reverse shell.
We can rename dgdfndnbcn to ReverseShell for ease of analysis.
Command Functionality: power-shell
This command functions much the same as remote_cmd, only it is executing an instance of powershell.exe with the command-line â-â as opposed to running cmd.exe, which I assume is to support command-line arguments with PowerShell.
Command Functionality: file-manager
This command first creates connects to a new Socket on the C2 and sends a status update that the file-manager has been opened before creating a new instance of âghdfdndfnâ using the created socket as a passed parameter.
By examining âghdfdndfnâ we can see that a new object reference to itself is defined as ghdfdndfn2, before a variable of sdfsldf is set to the passed Socket, dfhttegd is set as the input stream to this socket, and sabretb is set as the output stream to this socket.
The main functionality of this object is stored within the class dfhttegd where commands can be sent via the established socket connection once the file-manager command has been run.
The commands that can be sent are:
- navigate
- nav-key-log
- open
- delete
- savefile
- bringfile
navigate
If navigate is sent, a new call is made to dfhttegd, passing a string that is returned from a call to sabretb which takes a passed string as a parameter. To figure out what is happening, we need to take a look at sabretb.
Taking a provided string, sabretb creates an array of files (directories included) based on the given string and then formats the returned file information for ease of visibility for each given file in the array. In addition this will check if the file is a directory, and if so it will insert a âDâ into the returned response. If not it will insert a âFâ, and perform calculations of the file size by dividing of 1024 to get the number of kilobytes for each file returned.
This means that ânavigateâ effectively functions as a command to âview inside this directory and show me the contentsâ.
nav-key-log
ânav-key-logâ performs the same call; however, instead of passing a string provided by the C2, it will automatically pass, cbnfdhn.STRLogsLocation which is the location of STRRAT logs weâve previously renamed, and thus we can infer this command is to navigate to the log directory and display contents.
open
If âopenâ is sent, STRRAT takes this as an instruction to execute the file using cmd.exe which will use the default COM object for the particular file extension it is attempting to run.
Runtime.getRuntime().exec(new StringBuilder().insert(0, "cmd.exe /c \"").append(stringArray[1]).append("\"").toString());
delete
If âdeleteâ is sent, STRRAT makes a call to sdfsldf (file) passing into it the output of a new file object being created with a given string passed to it. By examining sdfsldf we can see that this is a simple function designed to first determine if the object referenced by the string passed is a directory, if so then it will recursively delete all the files inside of this, and the directoy. If not, it will simply delete the file passed to it.
savefile
If âsavefileâ is sent, STRRAT makes a call to sdfsldf (String) passing in a given file which is read into a FileOutputStream to allow it to be written to disk.
bringfile
Finally, if âbringfileâ is sent, STRRAT creates a new file object and checks if the file object (handle) it now has is a file or not, if not an error is sent back stating that âYou cannot download a folderâ. Where it is a file, it will make a call to sabretb (file) passing it in this file.
sabretb (file) will send the bytes of the given file back through to a FileInputStream allowing the file to be downloaded.
From the above scenarios, although weâre not sure if these commands are sent manually or by an interactive file-manager hosted by the C2, we can conclude that âfile-managerâ provides a virtual file manager which allows the above functionality.
Command Functionality: keylogger
This command focuses primarily around the class ghgmgf. If ghgmgf.sabretb is not true, this will create a new object from the class ghgmgf, if it is true it will be set to false.
By examining ghgmgf we see that sabretb is a simple static boolean being referenced. Further to this we can examine the primary ghgmgf method to get an idea of what is happening.
Taking into account the existing socket connection which is passed into this method, thereâs also a null string being passed when creating the object, and due to this an entry at line 101 isnât triggered until after a new ghgmgf object has been instantiated and started. Examining line 101 reveals HTML text which is being passed to âbsgshsbsâ for writing, which includes the terms âKeyboard Logâ and âGenerated by Strigoi Masterâ
This is very basic HTML and winds up looking similar to the below, presumably appended with Keylogs as keystrokes are logged, and it tells us that the author of this malware possibly favors a dark theme.
It should be noted that if any failures occur an instance of âgsbthstgb()â is called which looks to be closing any existing sockets and logging that a Keylogger has been shutdown.
To understand what this command aims to do we need to look at the below line which runs when a new object of ghgmgf is created after the null check above is performed.
new Thread(new xnxcxvb((ghgmgf)object)).start();
By decompiling âxnxcxvbâ we can see that it implements the Runnable class with an instance of ghgmgf it is being passed that is assigned to âsdfsldfâ.
The key component here is that when a new thread is being created it will run âghgmgf.sabretb(this.sdfsldf)â, which means we can take a look at the method inside âsabretb(ghgmgf)â to determine what will happen.
Itâs within this method that a GlobalKeyboardHook is registered, which we know is used by the System Hook Plugin that STRRAT relies on. By decompiling this class we can see a number of keycodes being registered for listening, and a number of combination of keys to ensure that these are also logged.
Given this context and because we previously saw the below line within âghgmgfâ
this.sfsrgsbd = ((Socket)object).getOutputStream();
We can infer that all keys logged are sent directly back to the established socket connection to the C2 in realtime, and that the command âkeyloggerâ functions as expected as a keylogger leveraging the low level library hook freely available.
Command Functionality: o-keylogger
This command uses the same method as âkeyloggerâ, only it instead passes in a string specifying the location to log keys to disk instead of a socket. This file is a HTML file that takes the name âkeylogs_
From this we can infer that o-keylogger starts an offline keylogger which writes its output to disk rather than sending over an established socket.
Command Functionality: processes
This command creates a new object of class âsbsgssdfgâ, passing into it the existing socket connection.
Examining âsbsgssdfgâ we find that this implements another class that uses the Runnable class âsbsbgsrgâ, which if we examine this can see that upon running it will execute the âsdfsldf (sbsgssdfg)â method.
Examining this method we can see that it is looking for parameters to have been passed, as either âreloadâ or âkillâ.
Where âreloadâ is passed, it will run an instance of sdfsldf(), and where âkillâ is passed, it will use the inbuilt Windows âtaskkillâ command to kill a process based on its PID passed to the kill command. Examining sdfsldf(), we can see that this leverages the inbuilt Windows âwmicâ to enumerate process information.
From this we can infer that âprocessesâ is used to interact with running processes on a system to either enumerate and show them, or kill one specified by its PID.
Command Functionality: h-browser
This command primarily functions from within the âsgsfghhgâ class.
This leverages the âHBrowserNativeApisâ class to return appropriate bitmap representations for specific browser windows running on the victim machine.
Thereâs a lot of code in amongst here for drawing and sending over a socket the visual contents of essentially a âvirtual web browserâ to the adversary so weâll hone in on some specific code. The first thing we can notice inside âsgsfghhgâ is the presence of a number of commands being sent down the socket including âstartâ, âstopâ, âmouse-eventâ, and âkey-eventâ.
Within the above we also see that when âstartâ is sent we get another new thread spawning for an object âfdghdmhâ.
new Thread(new fdghdmh(sgsfghhg2, (String)object3)).start();
This gives us a new method which will be run when this command is sent down which is âsgsfghhg.sdfsldf(sgsfghhg, String)â.
By examining this method we can see this is largely based around creating a byte array thatâs written down the Socket in the form of a rendered PNG.
Although in this instance thereâs some broken decompiled code, we can see many instances of this referencing chrome.exe and firefox.exe within methods including sdfsldf().
and sabretb().
Finally if we cross reference any references to these we can see that a large proportion of this is facilitated in the method âdfhttegd(String)â which contains references to a âStrigoi Browserâ which starts as either a chrome or firefox process depending on what is on the victim computer.
From this we can infer that âh-browserâ is used to spawn a virtual âHTML Browserâ dubbed âStrigoi Browserâ using either Firefox or Chrome as the host process for this.
Command Functionality: startup-list
This command creates a new object of class âsstydgnâ, passing into it the existing socket connection.
Once again we see a familiar thread creation, this time defined within the class âncgdfhbnâ.
Once again implementing the runnable class this is set to kick off âsstydgn.sdfsldf(ssydgn)â.
From here we see this takes one of 3 commands, âreloadâ, âdeleteâ, or âaddâ.
reload
When âreloadâ is sent this command will proceed to run an instance of âsstydgn.sdfsldf()â. This will first run âwmicâ to enumerate current notable autostart entries on the system. When accounting for escape characters the command run is as follows.
wmic /node:. /namespace:'\\root\cimv2' path win32_startupcommand get name,location /format:list
This then performs data processing based on the way results are returned. An example of a returned entry is included below:
Location=HKU\S-1-5-19\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
Name=OneDriveSetup
If we look at how this manifests in the code we see the following:
From the above and example provided we can see this is splitting the results of the location and name results in what it is presenting based on the returned â=â. We can also see evidence that this is translating entries that simply say âStartupâ to the CSIDL ordinal (Folder path) â7â (CSIDL_STARTUP), âCommon Startupâ to the CSIDL ordinal (Folder path) â24â (CSIDL_COMMON_STARTUP), âHKUâ to the full string âHKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Runâ, and âHKLMâ to the full string âHKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Runâ.
Note: More information on CSIDL Ordinals
The issue with the above approach is that it contains a logic bug. It assumes that every result returned will be that of the current user (HKCU) - HKEY Current User; however, when running this WMI command it will return results from other user SIDs such as â.DEFAULTâ, âS-1-5-18â (NT AUTHORITY\SYSTEM), and in the case of running as Admin, other users on the system. Because of this the results returned will state the run keys are always that of the current user. Further to this it will also replace the key with the location of the standard âRunâ key; however, if this was instead a different key such as âRunOnceâ, it would still resolve as the âRunâ key location because of this logic flaw. This demonstrates an oversight by the malware author.
From this we can infer that âstartup-list reloadâ is used to show the current startup (autorun) items on this host.
delete
When âdeleteâ is sent this command will proceed to run an instance of âsstydgn.sdfsldf(String)â. This will check what is being passed to it, and where it starts with either HKCU or HKLM it will attempt to delete it from registry using the native âreg.exeâ command. Where this doesnât start with either of these characters the malware will attempt to delete it as if it was a file or folder on the system.
This demonstrates another oversight where the malware only has support for deleting startup keys with this for the current user or local machine, and not other user hives.
From this we can infer that âstartup-list deleteâ is used to remove either the current user/local machine registry run key, or a file present on the system.
add
When âaddâ is sent this command will proceed to run an instance of âsstydgn.sdfsldf(String, boolean, boolean)â. This functions as a way to add persistence through both a registry run key, or a file written to either CSIDL_STARTUP or CSIDL_COMMON_STARTUP.
This uses another method âsfsrgsbd.sdfsldf(int, String)â to perform the registry additions. Whatâs interesting here is that rather than using âreg addâ native commands as was done with the âdeleteâ command, this instead uses methods defined in Java that leverage relevant Windows APIs.
This difference highlights one of maybe 3 possible scenarios.
- The malware author intentionally chose to not leverage reg.exe to add in persistence to evade some EDR signatures.
- The malware author is actually instead malware authors, all of whom focus on a different part of the RAT, or it has been adapted from code âborrowedâ or âstolenâ over time.
- The malware author didnât realise that there was an âaddâ parameter for reg.exe.
Given the code quality inconsistencies Iâd say itâs likely this was made by multiple people, or it has been chopped and changed/stolen over time.
From this we can infer that âstartup-list addâ is used to add either an entry to the current user/local machine registry run key, or a file to one of 2 known startup directories on the system.
Command Functionality: remote-screen
This command primarily functions from within the âsabretb(Socket)â class.
At a glance we can see that this is importing a lot of classes to do with encryption and drawing pixels to the screen.
Note: To get a clean decompilation weâll be switching to the FernFlower decompiler built into Recaf.
Taking a look at the main class executed see 2 familiar thread creations in addition to some input and output stream declarations. This time the thread creations are defined within the classes âfgsbsfgsbâ and âhghteerdâ.
Class âfgsbsfgsbâ will be ensuring âsdfsldf(sabretb)â is run.
public final void run() {
sabretb.sdfsldf(this.sdfsldf);
}
Class âhghteerdâ will be ensuring âsabretb(sabretb)â is run.
public final void run() {
sabretb.sabretb(this.sdfsldf);
}
If we compare these classes we see that âsdfsldf(sabretb)â is expecting a successfully established socket so that it can send bytes which are stored from an instance of sabretb.sdfsldf(BufferedImage). We can also see reference to Remote Desktop in a debug message.
Examining this method we can see that it looks to be sending a ByteArray Stream of the userâs screen down the socket in the form of a continuous JPG.
Now looking at âsabretb(sabretb)â we see that this is looking for events indicating whether a mouse has been moved, key pushed, mouse wheel scrolled, or mouse double clicked.
These events directly cause events that are directly tied to updating the image being sent down the established socket and sends commands through the java.awt.Robot class that has been imported here.
From this we can infer that âremote-screenâ is used to spawn a âVNCâ or âRDPâ type connection to the system and allow control of the mouse and keyboard input.
Command Functionality: rev-proxy
This command primarily functions from within the âncnndfg(String)â class.
Examining this we can see that this looks to be establishing a new Socket connection to the configured âPluginsServerURLâ and âPluginsServerPortâ variables. From this we can begin to see this appears to function also as a facilitator for this ârev-proxyâ command.
Some key components with this stem from the definition of ncnndfg3.sdfsldf as a new âfgssdgâ object, passing in the âncnndfgâ object itself, before running this object, passing in a socket connection, and the object created (confusing I know).
ncnndfg3.sdfsldf = new fgssdg(this);
ncnndfg3.sdfsldf((Socket)object, ncnndfg3.sdfsldf);
Breaking down the crux of this we need to look at the âfgssdgâ class, and we can see stark similarities with the âncnndfg(String)â class.
The difference here is that instead of the above mentioned implemenetations this is running âncnndfg.sabretb(ncnndfg, Socket, xbvcxnx)â, and examining this we find yet another invocation, this time of âncnndfg2.sdfsldf(socket)â
Remaining cognizant that this is passing in an established socket and interface âxbvcxnxâ which references âsdfsldf()â, we finally can see this starts a new thread implementing ângdnbn(ncnndfg, Socket, xbvcxnx)â which weâre now well and truly familiar with thread runnable objects.
Pivoting back to ncnndfg we can now begin to see the main part of this commandâs functionality.
Some important parts of this method occurs at lines 104-114 which is involved in receiving a CONNECT network request and sending back a 200 Connection Established message. This is only possible due to an instance of ncnndfg (ncnndfg2) being defined as object3 at line 76.
Here we also see that when a socket is established a new instance of âghsghnbnâ will be created at line 112 whilst invoking a public method âsdfsldf(Socket, Socket)â. Examining what this method entails we can see it is largely around defining 2 socket connections passed in and then creating 2 new threads, one for the object âmdgghtdshâ and the other, ândgdfhfhâ.
Taking a look at what these are used for, both once again are starting a thread using the Runnable class, and one will execute the method âsdfsldf(ghsghnbn)â, whilst the other executes âsabretb(ghsghnbn)â, both inside of âghsghnbnâ. Taking a look closer we can see that both of these are very similar except with some subtle differences.
In the above we can see that the difference occurs with the input and output streams being used, and these are the exact same streams defined for each socket passed for âsdfsldf(Socket, Socket)â as shown previously.
Thereâs really not a lot of further investigation or code to unravel in this command, and from this we can infer that ârev-proxyâ is used to create a âreverse proxyâ or socks proxy on the host that would allow accessing an internal service/port on the host by hitting an external domain on the correct port (in this case the plugins server).
Command Functionality: hrdp-new
This command primarily functions from within the âfgfnbnc(String)â class. Weâve touched on some of the elements of this previously, but now weâll go into a bit more depth. Decompiling once again using FernFlower we can see that this gets reference to a hrdpinst.exe binary that will be stored within the Jar run path once downloaded, before noting the appropriate browser in use on the system (x86 or x64 version of Firefox or Chrome), and starting a new thread of âgmgmgmgm(fgfnbnc)â.
Examining this we see âsdfsldf(fgfnbnc, String)â being called which in turn runs âcbnfdhn.StatusUpdate(âInitializing HRDPâ)â to update the C2. In addition this will run âsdfsldf(fgfnbnc)â to ensure that HRDP has been downloaded and initialised, and that a new user account has been created that can be used with this tool.
The specific downloading of this tool is from within âertdbdth()â that is run within the if statement right before âHRDP Downloadedâ is sent back to the C2.
This gives us another wshsoft.company IOC which is used to download the binary into the previously mentioned hrdpinst.exe binary, noting the mismatched extension here.
Network IOC: hxxp[://]wshsoft[.]company/multrdp[.]jpg
Weâve mentioned some of this previously; however we can now see in more depth what is occurring when âhrdp-newâ is sent. Of interest is a new instance of an agafhas object being created, and if we investigate this further we see a message being sent to the C2 of âHRDP-SOCâ before invoking âsdfsldf(fgfnbnc, Socket, dfhdfndfg)â.
Based on what weâre seeing we can begin to believe that this is acting as a socket to support HRDP, and looking at the invoked method we find it invokes another method âsdfsldf(Socket, dfhdfndfg)â which finally kicks off another thread, this time of class âbmcvbmd(fgfnbnc, Socket, dfhdfndfg)â.
The above new thread creation is used on yet another class that implements the runnable class and invokes âsabretb(fgfnbnc, Socket, dfhdfndfg)â
By examining this further we find that this looks to be reading in an inputstream and determining whether it is sending the command âCLONEâ, âEXITSâ, or âEXITâ. In addition to this we can see if none of these are being sent it will create a socket tunnelling port 3389 (default RDP) to the C2 to allow RDP directly to the system.
Examining the clone thread we see evidence that this is being used to clone a userâs browser session via the new thread of âthrhhrth(fgfnbnc)â based on an update being sent to the C2, before running an invocation of âdfhdfndfg.sdfsldf()â which is defined within var2.
(new Thread(new thrhhrth(var0))).start();
var2.sdfsldf();
Taking a look at âfgfnbnc.sabretb(fgfnbnc)â we can see that this is looking to determine if a file exists which is an object holding the cloned Firefox profile.
If this profile exists it will check to see if a Firefox.bat file has been dropped to the created userâs desktop, and if it has it will send a message that Firefox has been cloned. If it hasnât, it will attempt to get a Firefox profile by running through âsbsbgrsg()â, and then from this attempt to clone the userâs Firefox profile by creating a launcher (a batch script, note the misspelling of launcher message also) which runs Firefox but uses the ââno-remote -profileâ parameters to specify starting with this found userâs profile.
If we instead look at the âEXITSâ flow, we see that this runs the below:
var0.sabretb();
Looking into this we find that itâs all about âcleaning upâ and covering tracks from running HRDP. In this instance it seeks to delete the temporary created account and restore visibility of the last logged on username at the logon screen.
Looking at the âEXITâ flow, we see that this runs the below:
var0.gsbthstgb(var0.UserName);
The method takes in a String and then proceeds to load in Kernel32.dll so that it can check within sfsrgsbd() whether this is running on a 64bit OS or not. This is done so that the Wow64DisableWow64FsRedirection windows API function can be called to disable redicrections that occur on 32bit applications running on 64 bit OSâ, before logging the user off and re-enabling this.
Based on this itâs inferred that âEXITâ is used to logoff of the RDP session established. Finally if we examine the method âsdfsldf(String)â within âfgfnbncâ, we find that this method is being embedded in checks performed whenever a new ânetâ command is being run from this malware, with the same occurring in âsabretb(String)â.
What we can see here is that the malware seems to have some sort of checks in place for different âcommand completed successfullyâ messages which could be being resolved in the following languages.
- English
- Spanish
- French
- German
- Italian
This may indicate that the intended targets of this malware or languages spoken of intended victims are that of the above. Regardless, from this we can infer that âhrdp-newâ is used to invoke a âHiddenâ RDP session which can clone userâs Firefox and Chrome browser sessions.
Command Functionality: hrdp-res
This command primarily functions from within the âfgfnbnc(String)â class much like the previous command; however the primary difference is that this takes in a variable which acts as the defined username rather than randomly creating a new one after ânullâ was previously passed in.
Not far into the routine a variable var2 is set to the username passed in, and checks are made using net commands to see if the user exists already or not and if it is in the local administrators group. Where it is then all checks are passed and the re-initialisation of HRDP wonât occur, but the session will be allowed to run.
From this we can infer that âhrdp-resâ is used to restore a âHiddenâ RDP session where a user was previously logged off using the âEXITâ command.
Command Functionality: chrome-pass
This command primarily functions from within the âthtyrths()â class.
After creating a new instance of this, it has the output of âsdfsldf()â appended to the message sent to the C2. If we examine this method, it first checks if it is running Windows or not, and if it isnât a message is displayed saying it is not supported, if it is it will proceed to run âsabretb()â.
If the output of this method is nothing then the message âNo passwords Foundâ will be sent back, if it contains something, that will instead be returned, so letâs investigate âsabretb()â.
This leverages the JDBC driver SQLite Wrapper to interface with the Google Chrome password SQL database. From here it proceeds to retrieve all columns from the âloginsâ table and extract their relevant elements. From here STRRAT proceeds to use the DPAPI CryptUnprotectData function to decrypt the userâs stored Google Chrome passwords on this system.
In the event that using DPAPI fails to retrieve credentials, STRRAT falls back to running the below.
this.sdfsldf(var8);
Within this function we see a number of operations occurring, but probably one of the best assumptions as to why this occurs is made apparent from the below blog post.
Kirby Angell - Decrypting Browser Passwords & Other Secrets
As Kirby explains it, the the âLocal Stateâ file which is being read in contains the DPAPI encrypted encryption key, and whereas older versions of Chromium (and hence Chrome) used solely the Login Data file and DPAPI, all passwords could be revealed using solely these and the relevant API (CryptUnprotectData), which we saw just before. Nowadays browsers need to first extract the internal key which has been encrypted from within the Local State file, and then calls CryptUnprotectData to decrypt this key. From here the key is then changed to a byte array and is used to create a secret key via AES encryption from this byte array, which is subsequently then used to decrypt the password given.
From this we can infer that âchrome-passâ is used to decrypt usernames and passwords stored from within the logged on userâs Google Chrome profile.
Command Functionality: foxmail-pass
This command primarily functions from within the âdsgsdfge()â class, specifically what kicks off from the âsdfsldf()â function.
Looking into this further we can see that it is using a registry class âFoxmail.url.mailtoâ, presumably to get the install path of Foxmail. From this it is then looking for the Account.rec0 file stored within aan âAccountsâ subdirectory, and this is then being read to decode the Email and Password from within it.
The actual decoding occurs from within the class âsdfsldf(int, String)â, and we can tell this is encoding as it is performing permutations from fixed characters rather than any type of encryption using a schema such as AES.
The operations here occur a lot against an array of characters, and although we could go into each entry, itâs sometimes useful to explore OSINT to see if we can infer what is happening. Whilst researching these methods I stumbled across the following foxDecode program on Github which I believe may have been used to heavily influence how this command works. It has been cloned on the off chance it changes over time and can be found below:
Comparing the first statement in this project we can see striking similarities in our decompiled code to that of the C Sharp program above, despite the differences in languages used.
Following on from this we see almost a like-for-like identical comparison occurring, with one making comparisons in decimal, whereas the other is checking the decimal hex equivalent value. For this to be a coincidence is highly unlikely, itâs almost certainly that this code has been ripped off and ported to function as part of this Trojan, and itâs not uncommon to have malware authors steal code as a means to an end.
The plus side to all of this is we have nicely annotated code and can understand what is happening⌠at this point I canât not put this here.
Examining âsdfsldf(int, String)â and how it applies to the rest of this code we find the following:
Based on this we can infer that âfoxmail-passâ is used to retrieve email addresses and decode passwords stored from within the installed Foxmail user profiles using ported C# code which is almost certainly plagiarised from âStarZHFâ and âJacob Sooâ.
Command Functionality: outlook-pass
This command primarily functions from within the âxncxbc()â class, specifically what kicks off from the âsdfsldf()â method.
Examining this method we can see that it looks to search through some specific registry keys for the current user which are associated with Outlook. In specifics this is searching for any entry which after being converted to lowercase simply contains the key âpasswordâ, if this is not found it will continue searching, but if it is found it will add this to an array called âobject5â.
Further on from this we find that it will examine any key which contains âIMAP Passwordâ, âPOP3 Passwordâ, âHTTP Passwordâ, or âSMTP Passwordâ, and will then pull out the corresponding SMTP Server, Email, and Password (after decrypting using âCryptUnprotectDataâ which once again uses DPAPI).
Based on this we can infer that âoutlook-passâ is used to retrieve the SMTP Server, Email, and Password values stored from within the appropriate Outlook registry keys if they exist on the system.
Command Functionality: fox-pass
This command primarily functions from within the âdncbnf(boolean)â class, this time things are a bit different when creating an instance of this object as it uses variables âsabretbâ, and âsdfsldfâ in determining what is happening when this command is run, it also has an error message to indicate the command will be targeting Firefox.
Examining this class we find that upon being instantiated it will set a number of variables and run âthis.bsgshsbs()â. This method looks to have support for both âThunderbirdâ and âFirefoxâ based on examined strings; however, we need to take note that the object is being created with âfalseâ in this instance and as such will have a variable âdfhttegdâ set to âfalseâ that plays a key part in determining whether this is looking at Firefox or Thunderbird.
In the above we can see that this is specifically looking for a âprofiles.iniâ file in the userâs roaming directory for Firefox to be used within a new object created from ânddfgndt(String)â, and is specifically looking for anything that starts with the string âProfileâ in the list of returned objects. The gist of this is that it will be extracting the relevant Firefox profile directory to then search for the files âlogins.jsonâ, âkey4.dbâ, and âcert9.dbâ, prior to running the method âsdfsldf(File, File, File)â over these 3 files.
The above clears up what this is doing. After locating the above files they will attempt to be compressed into an archive called ârpack.zipâ which is sent as a serialized sequence of data. This differs from the previously seen commands in that there are no attempts to decrypt or decode these files to retrieve the credentials in cleartext, but rather is exfiltrating 3 files âlogins.jsonâ, âkey4.dbâ, âcert9.dbâ in an attempt to clone the userâs Firefox profile. We can see these files are useful in doing so by looking at an answer given by âcor-elâ here on a Mozilla Support Forum, replicated below for convenience.
Firefox 58+ versions use key4.db for the key file (default salt and master password).
Previous Firefox versions used the key3.db file although they can use key4.db (SQLite).
Support for SQLite databases (key4.db and cert9.db) exists since 2011.
If you copy logins.json and key4.db to the current profile folder then Firefox should be able to find the usernames and passwords stored in logins.json.
Note the logins.json and key4.db should match.
Based on this we can infer that âfox-passâ is used to retrieve the userâs âlogins.jsonâ, âkey4.dbâ, and âcert9.dbâ files from their respective Firefox profile in order to later decrypt these or import these into a Firefox session to steal credentials.
Command Functionality: tb-pass
This command primarily functions from within the âdncbnf(boolean)â class much like the âfox-passâ command, only this time it is being run with a âtrueâ value rather than a âfalseâ value being passed.
The sole difference between this command being run and the âfox-passâ command being run is that this is instead looking for the common âThunderbirdâ working directories as opposed to the âFirefoxâ ones. Examining this post on MozillaZine, a Mozilla documentation website created by the user community, we find this also contains âlogins.jsonâ, âkey4.dbâ, and âcert9.dbâ which can be used to recover stored passwords.
Based on this we can infer that âtb-passâ is used to retrieve the userâs âlogins.jsonâ, âkey4.dbâ, and âcert9.dbâ files from their respective Thunderbird profile in order to later decrypt these or import these into a Thunderbird session to steal credentials.
Command Functionality: ie-pass
This command primarily functions from within the âdhgdghd()â class.
Examining this class we find that this is actually one of the more simple classes. Upon being instantiated this object will launch a new powershell process and create a new object of type Windows.Security.Credentials.PasswordVault. From here it will run the RetrieveAll method to quite literally retrieve all the credentials stored in the Credential Locker. This will generally bring back hidden passwords; however, a quick pipe to RetrievePassword successfully extracts these credentials.
Once again if we werenât sure about this we could do a search and wind up on Github with some code to retrieve credentials from IE & Edge which is identical.
# Content: Receive Credentials from IE & Edge
# Author: Florian Hansemann | @CyberWarship | https://hansesecure.de
# Date: 09/2020
[void][Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime]
$vault = New-Object Windows.Security.Credentials.PasswordVault
$vault.RetrieveAll() | % { $_.RetrievePassword();$_ } | select username,resource,password
Based on this we can infer that âie-passâ is used to retrieve all the credentials stored in the Credential Locker used by Edge and Internet ExplorerâŚand that malware authors rip code off from others, whoâd have thought?
Command Functionality: all-pass
This command begins to chain a number of the above credential harvesting techniques specifically called out in âdhgdghd()â (IE / Edge), âthtyrths()â (Google Chrome), âdsgsdfge()â (Foxmail), âxncxbc()â (Outlook), before checking if the command passed was âsave-all-passâ, in the event it was, it will send these to the C2 before further processing occurs.
From the above we also see that if the command didnât include âsave-all-passâ, STRRAT will continue processing âdncbnf(boolean)â to get both Firefox and Thunderbird related files which will be sent to the C2.
Based on this we can infer that âall-passâ is used to harvest credentials for IE/Edge, Google Chrome, Foxmail, and Outlook. In the event that âsave-all-passâ is being sent instead as the command, this will send all of these to the C2. If not, it will go out and try to retrieve Firefox and Thunderbird files needed to decrypt or import passwords.
Command Functionality: save-all-pass
This command is actually a part of the âall-passâ command mentioned above and determines if credentials will be sent to the C2 or not.
Command Functionality: chk-priv
This command functions by simply running cbnfdhn.ssdgsbh() and sending back your privileges as either âAdminâ or âUserâ.
Just when we thought commands couldnât be more simple we find this. By examining ssdgsbh() we can see that this is used to try and write a new file to âC:\Windows\System32\config\dummy.logâ.
Because the location this file is being written to has a default Access Control List (ACL) which restricts it to Administrators, this command takes a shortcut method of trying to write a dummy file, and if it canât do it just assumes youâre not an Administrator, cute.
Based on this we can infer that âchk-privâ is used to try and write a dummy file to C:\Windows\System32\config\dummy.log as a way to check user privileges. Where itâs successful it will send back âPrivilege: Adminâ, and where it fails it will instead send back âPrivilege: Userâ.
Command Functionality: req-priv
This command primarily functions from the âsstydgn()â method inside of cbnfdhn which if the method returns âtrueâ will cause this process to terminate via System.exit(0).
The reason this is occurring can be uncovered easily as we dive into âsstydgn()â. Looking at this method we can see that it is attempting to launch PowerShell in order to use the âstart-processâ commandlet to run STRRAT using â-verb runASâ. As this happens STRRAT will be attempting to use PowerShell to launch STRRAT as administrator which will generally function by sending a UAC prompt to the user asking if they want to run this.
If the user confirms this or UAC is not enabled then this will return true and kill the current STRRAT process as a new one will have been spawned with Administrator rights.
Based on this we can infer that âreq-privâ is used to use PowerShell in an attempt to run STRRAT with administrator rights/privileges.
Command Functionality: rw-encrypt
This command primarily functions from a newly created object of âsdfsldf(string)â.
Whatâs important here is that the method is passed in an string when it is created that gets assigned to âdfhttegdâ, this will later be used as a passphrase.
Thereâs also a variable âsdfsldfâ which contains the string â.crimsonâ and is used throughout this command being run. In summary this will take a passed string as a password, and upon being instantiated will locate the userâs Downloads, Documents, and Desktop directory and store this in an array. From here STRRAT will iterate over the array, and if a file exists at that location it will read in the bytes of that file into another array. From this it will use a passphrase stored in âdfhttegdâ and proceed to encrypt the bytes of the read in file one by one using AES-128 encryption. The encrypted bytes are then written back to disk at the same location, except with the â.crimsonâ extension, and the original file is then deleted.
This highlights the importance of examining malware revisions for changes and citing your sources given a number of companies copy work by other researchers or companies without giving proper credit or performing the analysis themselves. At the time of writing I have identified at least 2 posts publicly which are still stating that this RAT doesnât implement a ransomware routine and merely changes the extension of files on disk, and from what we can see here, this is simply not the case. The posts go into no technical detail and despite showing version 1.4 and 1.5 of STRRAT in the analysis, itâs mentioned this is a fake ransomware module. From at least version 1.4 this in fact does look to have a fully functioning ransomware routine as shown here, and I strongly believe anyone who thinks otherwise is citing (or not citing) old research without analysing the samples themselves.
Based on this we can infer that ârw-encryptâ is used as a ransomware module to encrypt the userâs Downloads, Documents, and Desktop directory using AES-128.
Command Functionality: rw-decrypt
This command primarily functions from a newly created object of âsdfsldf(string)â much like the ârw-encryptâ command, with the difference being that the new thread being created is instead occurring via âdfghdmcâ.
Examining this we can see that this will instead kickoff a method of âsabretbâ inside of âsdfsldfâ.
Taking a look into this we find it is much the same as ârw-encryptâ, except with a difference being it is using the previously established object in ârw-encryptâ and a passphrase down to decrypt the files of interest by reusing the âDecryptConfigâ method which we previously renamed. This method is more broadly used as a decryption method now, both for STRRATâs configuration file, and also the ransomware decryption module.
Based on this we can infer that ârw-decryptâ is used as the recovery method for STRRATâs ransomware module and is used to decrypt the userâs Downloads, Documents, and Desktop directory using AES-128.
Command Functionality: show-msg
This command works as the message component of the executed ransomware and is very basic in how it works. First off it will get the location of the userâs desktop and create a file called âcrimson_info.txtâ there. From here it will take a passed string to the command and write it within that file, and finally it will execute notepad to display the contents of this file to the user.
This is pretty unusual and bizarre to be honest, given most ransomware variants will encrypt a number of files and do this not only in multiple threads, but with multiple files being dropped and a message which is modular but baked into the ransomware sample. In comparison this seems to just give the functionality to either use this or not depending on what the operator wishes to do.
Based on this we can infer that âshow-msgâ is used to create and display a ransomware (or arbitrary) message to a victim by creating a text file on the userâs desktop and using notepad to open it.
Command Functionality: screen-on
This command kicks off a new thread of object âfhjtjtg()â which has been created.
Examining this we find it kicks off the method âbsgshsbs()â from within âcbnfdhnâ.
Examining this we can see that it is used to move the mouse ever so slightly as to keep the computer from falling asleep (prevent screensavers).
Based on this we can infer that âscreen-onâ is used to move the mouse ever so slightly as to keep the screen on and prevent any computer screensaver.
Wrapping up flow and functionality:
At this point thereâs not much we havenât covered off in this particular piece of malware, weâve well and truly reversed it and showed how we came to every conclusion along the way. The standard beaconing of STRRAT can be seen under the method âsbsgssdfgâ which we glossed over far earlier in this analysis piece. This sends certain information about the host thatâs been infected back to the C2 in the form of a âpingâ, which is actually a web request, and not an ICMP (ping) packet.
As the cherry on top we can see one last evidence of this retrieving the userâs publicly facing IP address by making a request to ip-api[.]com which is referenced within the method âsbsgssdfg()â and âfgssdg()â.
Network IOC: ip-api[.]com/json/
Network IOC (User Agen): Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
Part 4: Threat Intelligence and Detection Engineering
Detection Engineering
Yara Rule
To build out a Yara rule weâll take a look at some of the most common IOCs seen in this malware as analysed above, and select a subset of these in addition to other commands seen embedded into the malwareâs functionality:
Network IOCs:
hxxp[://]wshsoft[.]company/jre7[.]zip
hxxps[://]pastebin[.]com/raw/Jdnx8jdg
hxxps[://]pastebin[.]com/u/wshsoft
pluginserver[.]duckdns[.]org
hxxp[://]str-master[.]pw/strigoi/server/ping[.]php?lid=
moregrace[.]duckdns[.]org
hxxp[://]jbfrost[.]live/strigoi/server/?hwid=1&lid=m&ht=5
palaintermine[.]duckdns[.]org
ip-api[.]com/json/
hxxp[://]wshsoft[.]company/multrdp[.]jpg
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
Host IOCs:
HKCU\Software\Microsoft\Windows\CurrentVersion\Run\ntfsmgr
64578lock.file
3219lock.file
Scheduled Task - "Skype"
HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\System /v dontdisplaylastusername = 1
STRRAT/Dropper Specific IOCs:
carLambo
HBrowserNativeApis
config.txt
loorqhustq
Using the above we can create a fairly flexible Yara rule to detect this malware.
/*
Author: @CyberRaiju
Date: 2022-05-19
Identifier: STRRAT-Identification
Reference: https://www.jaiminton.com/reverse-engineering/strrat
*/
rule STRRAT_14 {
meta:
description = "Detects components or the presence of STRRat used in eCrime operations"
license = "Detection Rule License 1.1 https://github.com/Neo23x0/signature-base/blob/master/LICENSE"
author = "@CyberRaiju"
reference = "https://www.jaiminton.com/reverse-engineering/strrat"
date = "2022-05-19"
hash1 = "ec48d708eb393d94b995eb7d0194bded701c456c666c7bb967ced016d9f1eff5"
hash2 = "0A6D2526077276F4D0141E9B4D94F373CC1AE9D6437A02887BE96A16E2D864CF"
strings:
$ntwk1 = "wshsoft.company" fullword ascii
$ntwk2 = "str-master.pw" fullword ascii
$ntwk3 = "jbfrost.live" fullword ascii
$ntwk4 = "ip-api.com" fullword ascii
$ntwk5 = "strigoi" fullword ascii
$host1 = "ntfsmgr" fullword ascii
$host2 = "Skype" fullword ascii
$host3 = "lock.file" fullword ascii
$rat1 = "HBrowserNativeApis" fullword ascii
$rat2 = "carLambo" fullword ascii
$rat3 = "config" fullword ascii
$rat4 = "loorqhustq" fullword ascii
condition:
filesize < 2000KB and (2 of ($ntwk*) or all of ($host*) or 2 of ($rat*))
}
Performing a hunt using Hybrid Analysis reveals a number of hits on this malware, making this a useful Yara rule for finding samples. Of course this could be expanded out further, for example by adding checks for the dependencies STRRAT is known to use, but when creating these itâs always a balancing act of being broad enough to net new samples, but not get riddled with False Positives, but not too confined as to miss different variants.
Snort Rule
To build out a Snort rule weâll take a look at some of the network traffic described above, specifically zoning in on the beacon STRRAT sends frequently, and look for any tcp traffic to or from a server that contains the content âpingâ, a pipe, and then âSTRRATâ (this is as simple as converting it to hexadecimal and looking for the content, and giving it a unique sid). This could be expanded further to detect commands being sent to/from the C2; however, this will be a task left for the reader.
alert tcp any any -> any any (msg:"STRRAT C2 Beacon Detected"; content:"|70 69 6e 67 7c 53 54 52 52 41 54|"; priority:2; reference:url, www.jaiminton.com/reverse-engineering/strrat;sid:1120009; rev:1;)
In this example weâre going to use a pcap already captured previously from a system infected with a STRRAT variant provided by Brad Duncan to test the rule.
After setting up a few preprocessors for stream5_global, weâre good to test the rule over our pcap. By creating a snort config with the below (stored in a file called strrat.rules).
preprocessor stream5_global: track_tcp yes, \
track_udp yes, \
track_icmp no,
alert tcp any any -> any any (msg:"STRRAT C2 Beacon Detected"; content:"|70 69 6e 67 7c 53 54 52 52 41 54|"; priority:2; reference:url, www.jaiminton.com/reverse-engineering/strrat;sid:1120009; rev:1;)
We can easily test this using snort on a linux VM. Specifying the pcap file and testing against our created rule..
sudo snort -A fast --pcap-single=./pcap.pcap -c ./strrat.rules -l /var/log/snort
We can see this has 73 hits over the pcap, and has generated a number of alerts due to the consistent beaconing this malware presents.
Sigma Rule
Because STRRAT leverages a lot of native Windows utilities to enable persistence and execute commands, we can develop a fairly basic Sigma rule to detect possible infections of STRRAT. This could be expanded further to pick up on individual registry key creations from a sysmon event, or look at the behaviour of file writes; however, this will be a task left for the reader. This also hasnât been confirmed for false positives which may need to be excluded.
title: STTRAT Child Process Spawning
status: experimental
description: Detects suspicious child processes of Java(w) possibly associated with a STRRAT infection.
author: Jai Minton (@CyberRaiju)
references: https://www.jaiminton.com/reverse-engineering/strrat
date: 2022/05/28
modified: 2022/05/28
tags:
- attack.initial_access
- attack.persistence
logsource:
category: process_creation
product: windows
level: high
detection:
selection:
- ParentImage|endswith:
- '\java.exe'
- '\javaw.exe'
selection_2:
- Image|endswith:
- '\hrdpinst.exe'
- '\cmd.exe'
selection_cmd:
CommandLine|contains:
- 'schtasks /create /sc minute /mo 30 /tn Skype'
- 'reg add'
- 'shutdown /r /t 0'
- 'shutdown /s /t 0'
- 'wmic /node:. /namespace:'
- 'hrdpinst.exe'
condition: selection and selection_2 and selection_cmd
falsepositives:
- Legitimate calls of various java applications to system binaries with specific command lines
level: high
Useful Windows Event Logs
The following event log identifiers will be useful to track this piece of malware:
Microsoft-Windows-TaskScheduler/Operational
- 201 (Task registered)
- 129 (Task Launched)
Security
- 4688 (Process Creation)
- 4698 (Scheduled Task Creation)
- 4700 (Scheduled Task Enabled)
Sysmon
- 1 (Process Creation)
- 11 (FileCreate)
- 12 (Registry Create and Delete)
- 13 (Registry Value Set)
- 22 (DNS Query)
Threat Intelligence - The Crimson Shadow
Note: Threat intelligence is a broad term here which has been used to cover getting extended telemetry on the threat this particular malware poses to organisations and people around the world, and potential victims.
During analysis I was presented with an interesting opportunity to track this malware on a global scale due to the way it functions. In late 2021, over the course of a month, infections of STRRAT around the world were tracked and plotted on a map based on the infected clientâs IP address and Maxmindâs GeoLite2 Free Database. This clustered data is shown below.
By overlaying a heat map here we can see that although the numbers arenât massive, the number of locations globally which had seen this malware execute were quite diverse.
Further, each cluser point represents a single IP, and doesnât take into consideration the number of hits from that particular IP. To allow this to be easily navigated I overlayed IP address information along with ASN and count.
The method to turn the gathered data into such a map was to take a carefully crafted spreadsheet containing the data and plot it with Python3, pandas, and folium. The script created is show below (special thanks to umar-yusufâs blog which helped get me started on this venture).
import pandas as pd
from IPython.display import display
import folium
from folium import plugins
from folium.plugins import HeatMap
import branca.colormap
from collections import defaultdict
import sys
import time
steps=5
colormap = branca.colormap.linear.YlOrRd_09.scale(0, 2).to_step(steps)
gradient_map=defaultdict(dict)
for i in range(steps):
gradient_map[1/steps*i] = colormap.rgb_hex_str(1/steps*i)
lon, lat = 10.626065, 43.035950
for ii in range(1,33):
m = folium.Map([lat, lon],tiles='cartodbdark_matter', zoom_start=2)
sheet='Day'+str(ii);
heatmap_df = pd.read_excel(open('MapDataDaily.xls','rb'),sheet_name=sheet)
count=sheet+" ("+str(len(heatmap_df))+")"
title_html = '''
<h3 align="center" style="font-size:16px"><b>{}</b></h3>
'''.format(count)
# heatmap_df.head()
for i in range(0,len(heatmap_df)):
folium.CircleMarker(
location=[heatmap_df.iloc[i]["Latitude"],heatmap_df.iloc[i]["Longitude"]],
radius=heatmap_df.iloc[i]["Value"]*0.001,
line_color='#fc0000',fill_color='#fc0000',color='#fc0000').add_to(m)
m.get_root().html.add_child(folium.Element(title_html))
m.save(sheet+".html")
After running over the data I had an interactive HTML page for each and every day of data collection and quantity of infected, unique IPs on any given day. This was then turned into the below gif. Note: Day 5âs data got lost during collection (call it user error).
In addition the most common hour period that hosts were seen to be infected (UTC) was also plotted over time.
Statistics
From the data gathered during this one month period, the following became apparent:
- Approximately 5726 Unique IP addresses were seen to have been infected during this time.
- 6 out of 7 continents had seen to have been infected during this time (Antarctica was the odd one out).
- Africa: 589 IPs
- Asia: 2446 IPs
- Europe: 1454 IPs
- North America: 892 IPs
- Oceania: 125 IPs
- South America: 220 IPs
- Systems in approximately 101 countries had seen to have been infected during this time.
- Systems in approximately 1005 cities had seen to have been infected during this time.
- Approximately 100 different license keys appeared to be in use by STRRAT globally during this time.
Limitations and acknowledgements:
- Given telemetry was captured from a network level, there could have been multiple systems resolving to the same IP due to NAT which remains an unknown in the above figures.
- Some of the above figures were likely sandboxes or testing devices and didnât indicate a compromised organisation, a large subset was still found to have frequent enough telemetry and during likely business hours to assume a compromised system.
- Where a system was likely to be infected as identified above, this was mostly attempted to be reported to the relevant country Cyber Security / CERT Authority. Only a number of those contacted replied, and of these fewer confirmed an investigation of a machine occurring (some is better than none right?).
OSINT
By leveraging information known about this malware and the assumption it is likely being sold online or shared amongst groups, I was able to use OSINT to find the website being used to sell this piece of malware. From this we have a redacted screenshot which has the seller trying to give off the impression that this is âlegitimate softwareâ which you need to pay for on a monthly or 3 monthly basis. Despite this it says it is for âeducational use onlyâ which is a common theme amongst those developing tools they almost certainly know will be used for malicious purposes, it has fake location information, and itâs marketed with skulls and the grim reaper, and it accepts payments in only Perfect Money or Bitcoin.
An interesting component of the above is that the author is selling this capability on a monthly ($80) or 3 monthly ($200) basis, in addition it is touting the exact features of the malware which weâve uncovered above and shown exactly how they work.
Part 5: Miscellaneous Trivia
Striogoi References
Throughout this analysis we see numerous references to âStrigoiâ. From a search of Wikipedia and Strigoi fandom wiki pages, it was found that âStrigoiâ is a term from Romanian mythology which is used to refer to an evil, or troubled spirit, which can possess (or transform) into an animal. This has become synonymous with vampirism where a bat is said to have been posessed by a Strigoi, or are instead a Strigoi taking the form of a bat.
Concept art for this creature which I find appropriate is below, created by DavyWagnarok.
Strigoi artwork by DavyWagnarok:
Based on this we may infer that the creators are of Romanian descent or are targeting Romanian customers/users; however, this would be too easy and it could very much be that this is used as a method of misdirection. Nonetheless it does lead us to believe that the malware author felt âStrigoiâ to be a âcoolâ enough term to be used for the name of this malware, whether or not theyâre happy with a shortened âSTRRATâ being used globally based on the strings found in this malware is unknown.