It’s rare when the Cylance Threat Guidance team comes across a freshly compiled piece of malicious code that we struggle to place into a known malware family. We recently found such a sample after CylancePROTECT® quarantined a threat in the System32 directory on a customer endpoint. The location of the file, the recent compile date, and the lack of similar files on known malware repositories combined to flag this sample as something we should take a deeper look at. Let’s get to it.
Our sample is a 32-bit DLL compiled from C++ that exports only a single anonymous function. The code is conveniently unobfuscated and without any anti-VM or anti-debug tricks. The data, as we’ll see later, is another story. When run via its exported function, the sample performs a straight call back to the IP address 18.104.22.168. No DNS lookups needed here.
At the time of our testing, the Korean address was only returning TCP reset packets, so we decided to connect our malware to a fake server. Doing this allowed us to view a HTTP POST over port 443 (Figure 1). Note the Host portion of the header is bogus and does not correspond to the command and control (C2) address. We’ll see later the entire HTTP header is hardcoded as a single string and only the POST data changes. After the POST and lacking an interesting response, the malware exits with no notable changes to the OS.
Figure 1: HTTP POST Over Port 443
Ok, so nothing too strange so far. Let’s check out the code.
On to the Code
The first thing our exported function does is call an initialization function that sets up two scrambled buffers that will be used later by the main encode/decode function. This initialization code also decodes and returns our C2 IP as seen in Figure 2:
Figure 2: Init Routine and Decoded IP
Once this has been set up, it’s off to the information gathering phase with a number of library calls, grouped here by type (thanks PortEx!):
Figure 3: Querying the Local Admin Group Members
Figure 4: Querying For CPU Information
Figure 5: Querying for the RDP Port Number
Encode/ Decode Function
Now that we know what information is being collected, it’s probably a good time to cover the author’s extensive use of a custom algorithm for encoding and decoding all their pilfered info. This encoding scheme is called from 90 different places in the code; generally, both before and after any place the data is used (Figure 6).
Figure 6: Code References to the Encode_Decode Function
A function declaration for this algorithm would look something like this:
void encode_decode( int key, char* s, int* max_size )
The key argument is used to lookup two values from a buffer initialized at program startup. The first value is the ‘decode length’ and is compared to the max_size argument, throwing an error if decode length is larger. The second extracted value is used as an offset into the second initialized buffer, where we find a piece of our XOR loop key. A full reversal of this algorithm is left for a later time.
Figure 7: Encode Function Call with key=402 and max_size=9E
Pipes and Hashes
One of the more interesting pieces in this sample is the ability to use named pipes and its enabling of NULL session pipes. Use of named pipes for communication is not unheard of in malware; PlugX and Duqu are two famous examples that have both been known to use them. When found, it is typically used for communication between different pieces of malware on a host, or between infected systems inside a LAN. Duqu was able to use it to proxy C2 commands through internet connected hosts to hosts they wouldn’t otherwise be able to reach.
More information on NULLSessionPipes, including how to enable them and the security implications of that, can be found on Microsoft’s Support site: https://support.microsoft.com/en-us/help/813414/how-to-create-an-anonymous-pipe-that-gives-access-to-everyone
Figure 8: Enabling NullSessionPipes
The final piece of functionality in this piece of malware we should cover is its ability to dump password hashes. The sample has two pwdump dll’s embedded inside: one 32-bit version and one 64-bit version.
The author also left a very telling format string in clear text in the binary, which is interesting considering how much effort was spent encoding the other data (Figure 9). This is used as a format string to print the hashes once dumped.
Figure 9: Format string used to print the dumped hashes.
This sample piqued our interest for a number of reasons, and digging further did not leave us disappointed. Our inability to tie it to a known malware family and the compilation date just two days before detection could indicate this is a targeted attack. The named pipe communication, a custom encoder, the lack of attempted persistence and a single hard-coded IP all show this malware author is not doing things we would traditionally expect.
Only by fully understanding the threats we’re facing can we have confidence in the tools we’re using to stop them. If your anti-malware solution allows this file to run, you may be able to develop additional checks for a full defense-in-depth information security strategy. A simple host-based or network firewall rule checking for non-SSL/TLS traffic over port 443 may indicate a compromise. Also, enterprise logging and alerting on unexpected processes running from System32 is an additional step an organization can take to provide early warning of an intrusion.
If you use our endpoint protection product CylancePROTECT, you were already protected from this attack. If you don't have CylancePROTECT, contact us to learn how our AI-driven solution can predict and prevent unknown and emerging threats.
Indicators of Compromise (IoCs)
Section Hashes (MD5):
- .text: 36F524B956614AC0276F96A0D95079D7
- .rdata: 3D5F551FDFA53827D1C8772A66A3FC72
- .data: 9466A34589323441604D1319B12675D1
- .reloc: D4AAA928D8E7D7D42C123FE232D0B4C0
- POST /fd/ls/lsp.aspx HTTP/1.1\r\nUser-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)\r\nHost: www(dot)update(dot)com\r\nConnection: keep-alive\r\nAccept: */*\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length:
- ####gethash####\r\n%s####gethash end####\r\n\r\n
- ####hide account####\r\nno need to hide\taccount number:%d,53 port:%s, 80 port:%s\r\n\r\n
- decode:length too small
- decode:not found