Investigating the Gootkit Loader
Gootkit has been tied to Cobalt Strike as well as other ransomware attacks in the past. Some of these recent victims later suffered SunCrypt ransomware attacks, although it is unclear if this was because of the Gootkit threat actor or if access was sold to other threat actors.
Since October 2020, we saw an increase in the number of Gootkit cases targeting users in Germany. We investigated this development and found that the Gootkit loader was now capable of sophisticated behaviour that enabled it to surreptitiously load itself onto an affected system and make analysis and detection more difficult.
This capability was used to deploy a DLL file. Gootkit has, in the past, been tied to Cobalt Strike as well as other ransomware attacks. Some of these recent victims later suffered SunCrypt ransomware attacks, although it is unclear if this was because of the Gootkit threat actor or if access was sold to other threat actors. We've also discovered in recent weeks that the Gootkit loader is being used in combination with REvil/Sodinokibi ransomware.
Infection vector: Malicious search engine results
In the cases we saw, the Gootkit loader initially arrives via a ZIP archive downloaded from a website. These malicious websites can be found in malicious search engine results, like this:
In this particular instance, fifa manager kostenlos can be translated as fifa manager free. Note that the search term used can vary significantly; we just used this search term as an example. We have encountered other cases where the search terms were Aldi Talk postident coupon and Control Centre 4 download.
Clicking the link leads to a page on a legitimate site; however, the site has been compromised and used to host a malicious page. The aforementioned page looks legitimate:
It is meant to look like a legitimate forum, with a post containing a link to a file relevant to the search engine query. This particular link is more sophisticated than it looks, however. Attempting to redownload the same file from the same URL from the same host/machine fails; however, doing so from a different one succeeds, but the downloaded file has a different hash than the original file. This suggests that the server generates this file as it is needed, uniquely for each download attempt.
Analysis of the downloaded file
The downloaded file is a ZIP file that contains a heavily encoded JS file (which shares the same filename as the ZIP file, save for the extension). We were able to use JSNice to produce human-readable code:
Of interest here is the function “MT71,” which contains a variable with very long content. Trying to run the script with online runtimes such as at Ideone fails with the following error:
What becomes apparent through the error is what the WScript.Shell object is trying to do:
A new object is created (“WScript.Shell”), which tries to read the registry key “HKCU\SOFTWARE\nTpm\”. In case this registry key does not exist, it performs the following actions:
- A key with an empty value will be written at HKCU\SOFTWARE\nTpm
- The value of “bE50” will be set to 32
If the key already exists, the execution of the script will fail since “bE50” is not set. It becomes clear that this key is being used as a marker to check if the initial loader was already executed on an infected host.
To sandbox this script, we used malware-gaol from HynekPetrak. A line had to be added to define the variable of bE50 as 32; otherwise, the script will fail (due to the requirement of accessing the registry key).
The following command was then used to run the JS file in the gaol:
node jailme.js -c./config_wscript_only.json --t404 tr_input/fifa.js tr_output/test -o tr_output/fifa_out.json --trace
This command would return a 404 error whenever the Javascript file sends a request upon execution. It created the following output files:
Looking at the output JSON file shows that the variable “qI27” is an array of three domains:
- www.adpm.com[.]br
- windowp[.]org
- www.ai-tech[.]paris
Converting the whole line of “qI61” into a readable format reveals the following code block:
The snippet defines the following flow, running the following code against at least one of the URLs stored in QI27:
- “Og13” will be set to a random number after the point with a maximum length of 100
- The local user’s DNS domain is queried
- if the machine is joined to a domain, “Og13” will have 278146 added at the end
- A web request to the URL selected in step 1 will be initiated, using sub-parameters
- search.php?gqhncrqossifzp={the number in the variable of Og13}
- It cheques for the return code of the web request a. if not 200 (OK), the script goes to sleep and then tries the next URL.
- If the web request is 200 (answer received), it stores the response in variable “zI11”
- It cheques whether the response text from the server contains the “Og13” value
- If it does not, it goes to sleep and then tries the next URL.
- If the value is in the response, it removes the “Og13” value from “zI11”
- It replaces a double-digit number in brackets, e.g. (12), with a response from a function using the variable “KT44”, which is unknown at this stage.
- It then calls another function “Nm34” (also currently unknown), passing the new “va67” variable on.
With the above information, we now know that this loader makes a difference between domain and non-domain hosts (by adding “278146” at the end of the search parameter).
We can change environmental variables via the wscript.js file. As the malicious script is looking for the environmental variable “UserDNSDomain,” this was added to the configuration; we also changed the default username:
Rerunning the script — after changing the wscript.js parameter to provide a domain — reveals the request of the following URL:
Based on this observation, we can now run jailme.js without the --t404 option, but with the --down=y option. This allowed us to send the queries and download any requested files. By default, the jailme.js will stop executing the script after 60 seconds. The result of the queried URL now includes all three identified domains, shown below and including the responses:
While we received HTTP 200 responses, none of these included the random strings needed for the script to proceed. This was true using both samples we received. We are unsure why this is the case; all we can say for sure is that the servers are currently not providing the files to be downloaded by Gootkit if they are analysed in this manner.
Registry Analysis
We can use the presence of registry keys known to Gootkit to test if it has been deployed on an affected system.
On test machines, we were able to verify that the created registry entries were present:
The registry values in the last key can be merged into a PowerShell script:
Most of this script is encoded; decoding it results in the following:
This code is, by default, loaded into memory. If this code is instead saved to a file, this turns out to be a.NET DLL file. (This particular file is detected as Trojan.Win32.DELF.WLDT).
Opening this particular file in a.NET decompiler shows that it also contains more encoded code. Using a similar technique to dump the contents into a file reveals that this is also an executable file. This one is detected as Trojan.Win32.MALREP.THJBGBO, which we believe is the payload that this loader delivered to the affected system.
Conclusions and Trend Micro solutions
This particular threat highlights the sophistication of today’s malware-delivering loaders. In a system without any security solutions enabled, there would be barely any sign of the infection, making analysis and removal more difficult.
With the appropriate Trend Micro solutions, the user would have been protected from this threat. Deep Discovery Analyzer would have proactively detected the script as a backdoor and classified it as malicious; Apex One would also have been capable of blocking the threat once it was executed.