Deep-Dive into the Snake Keylogger Loader
Snake Keylogger is a .NET-based malware focused on stealing user data through logging keystrokes, taking screenshots, and exfiltrating saved credentials.
The following analysis is of a sample pulled from Malware Bazaar that had been tagged as being Snake Keylogger. In this post, we’ll take apart the loader layer and dump the final payload. Along the way, we’ll have to bypass several layers of anti-analysis.
Initial Triage
File Name
dd4427142c65ffcc233559f074db38e19105d60a100b4c1abf688e8463f90cba.exe
Hashes
MD5: 155b68804bf403e30af13ab6d1777b47
SHA1: 97c58117dbc97c894443c4cd8c148b623e9eb067
SHA-256: dd4427142c65ffcc233559f074db38e19105d60a100b4c1abf688e8463f90cba
Anti-Virus Detection Rate (22⁄71)
Dropping it into pestudio, we can see that it is detected by several anti-virus companies as being Snake Keylogger.
Architecture
According to Detect It Easy, it is a 32-bit .NET executable. The high entropy suggests that it may be packed, although no signatured packer was detected.
Resources
With pestudio, I got a gauge of the embedded resources. The resource following that, EnglishSquares.Properties.Resources.resources has a very high file ratio of 66.86%, which might indicate that this could contain the packed payload.
Sections
Nothing appeared unusual with the PE sections. Primarily, i was looking for unusual section names that might indicate packing, or unusual file ratios in the .rsrc section that might indicate a payload. However, as seen in the list of resources above, the majority of the resources actually reside in the .text section, so looking at the .rsrc section would not be a good indicator in this case.
Imports
The sample did not appear to directly import any suspicious libraries, as even the import of kernel32.dll did not yield any interesting functions that might be utilized maliciously. This could be an indicator in itself that the file is packed and so hiding suspicious imports.
Although many functions could be seen in the imports, none were interesting. The names seemed to suggest the sample was attempting to masquerade as a game.
Version
Looking at the version information of the PE, we can see that the sample appears to masquerade as a game called English Squares with an original file name of cTDo.exe.
Strings
A few interesting strings were found, with several URLs that might be C2 servers (if not decoys). The WMi string was interesting along with the strings above it as it indicates that a payload may be executed using the Windows Management Instrumentation tooling.
Looking at the domain in the string, we can see that it points to a server running on Microsoft Azure.
The last update to the domain was on 11 Aug 2022, so fairly recent. The domain looks to have been originally created in the year of 1999, which might suggest that the operators chose to purchase an expired domain in order to evade heuristics that look at a domain’s age as an indicator of trustworthiness.
Dynamic Analysis
Resources (again)
As the sample is .NET-based, we can use advanced tools like dnSpy in order to decompile a lot of the code and get a better look at the resources. Using it, we can see details of the resources we saw earlier in pestudio. We can see a suspicious bitmap within the EnglishSquares.Properties.Resources.resources entry, which might be a compressed payload. We can also see some suspicious resources below, identified by their cyrillic (Russian) names for a game that was supposedly developed by an English developer (Lee H Fuller, as seen in the version data).
We can also see this Ruby resource that is 80416 bytes, which is also a candidate as being a potential payload or stage.
Unpacking
We can see that when the application starts, it initialized an instance of the NewGame class.
Instantiating the “game” will trigger the following process.
1) The embedded payload will be extracted and decrypted to a DLL
2) NewGame.RandomizedComparer = extracted DLL
3) Pass the input of UnderlingSystemType to the payload
We can see that process more in-depth in the image above. InitializeComponent calls a suspicious function named sss which loads the aforementioned, suspicious Ruby resource. The resource is decrypted using the Rijindael symmetric encryption algorithm using a hard-coded key: 55R7SPC4B54JQGN4C547H4.
The decrypted resource is a DLL which is then loaded by the NewGame.ParseFailure function (implementation not in image), with the end result being a payload being stored in the NewGame.RandomizedComparer field.
Back in the constructor of NewGame, that payload is activated by a call to Activator.CreateInstance, with the object array stored in UnderlyingTypes being used as input.
Debugging the program, we can see that the value at array3 is a PE file by the magic signature of 0x4D5A (or, MZ).
After dumping the variable to a file, I loaded it back into Detect It Easy for verification.
Indeed, we can see a .NET 2.0 DLL that is packed with the .NET Reactor obfuscator. As expected, loading it into pestudio yield no interesting results as the file was packed.
I then used de4dot, a .NET de-obfuscation tool, in order to unpack the dumped payload.
Unfortunately, after loading the “deobfuscated” sample back into dnSpy, it looked like de4dot had failed and the file was stilly heavily obfuscated. Snake Keylogger can have multiple unpacking stages to slow down or deter analysis, so this is to be expected.
Returning to the debugger, we can see that after returning to the NewGame constructor that the RandomizedComparer attribute has now been set to the constructor of the PS class in the OE namespace (remember, these are obfuscated names, so they have arbitrary names) of the KeysNormalize module. The KeysNormalize module is the loaded DLL that had been retrieved and decrypted.
To debug a DLL loaded from memory, we can navigate to Debug > Windows > Modules.
Find the module you want to set breakpoints on. As shown in an image above, we want to set breakpoints in the KeysNormalize module as that is the DLL that has been loaded in memory, and is where the OE.PS code resides.
Looking at the PS constructor, we can see 3 function calls. The first two calls (lines 5-6) did not yield anything interesting. The third function call to PS.z0 was different.
Line 72 shows an obfuscated function call that results in the application sleeping for 45877 milliseconds, or about 45 seconds. This is likely an anti-sandbox feature, as many will have timeouts to prevent a DoS of the system or wasted resources. Stepping into the call exposes the call to Sleep.
You probably won’t want to wait 45 seconds to continue debugging, so you can skip over this by right-clicking the next line and selecting Set Next Statement in the context menu that will appear, as shown above.
After skipping over the sleep, a new DLL was loaded and base64-decoded before being loaded.
Dumping this new assembly was as simple as right-clicking on the byte-array variable and clicking the Save button in the context menu.
The dumped file appeared to be either packed or simply another part of the unpacking stub, and was not the final payload.
Continuing the process of stepping into the obfuscated code, we can see that it eventually attempts to load one of the suspicious resources from the original assembly noted in the beginning.
Eventually, after multiple obfuscated transformations and resources being loaded, we find another DLL being loaded by the process. Following the same process as before, I dumped the payload and then ran it with DIE. It was detected as using an obfuscator. Running it through de4dot did not help deobfuscate the code.
Continuing from the debugger, at line 125 of OE.PS, there is a call to OE.PS.th, passing it the loaded assembly. That triggers another long process of extracting another embedded resource, before applying some transformations (including XOR) to the data.
As shown in the image above, by the time the final payload could be extracted, 3 layers of obfuscation had to be bypassed (KeysNormalize, WebName, Metal), and which required the loading of the VisualBasic .NET language runtime.
Finally, in BA3X456jSWU4hgctNB.iTMKN8sx0iLux6udnG.LVWC1Fsou of the Metal assembly, you can dump the final payload.
In the image above, we can see the MZ signature (represented as bytes 0x4D 0x5A) that signifies a PE executable. Dumping that byte array as a file as shown previously, I checked it with DIE and pestudio.
No packer was recognized, and the result was a 32-bit .NET executable.
Perusing the imports and strings found by pestudio exposes many of the tell-tale signs of an information stealer and keylogger, showing that we have found the final payload.
As this report was intended to expose and dump the payload from the loader, that will conclude this report. Further analysis is required to understand the final payload and extract IOCs to be used for more accurate detection.
If you’re interested in learning how to use AWS, Cloudflare, Google Workspace and more with modern DevOps practices in order to create a hybrid cloud/on-prem malware lab, check out my book on Cyber Range Essentials!