Summary of Execution
This sample is being delivered using the Angler Exploit Kit within a
drive-by download attack. The exploit is the latest Adobe Flash exploit.
The packing on the sample is very sophisticated; far more sophisticated than
the final payload. The packer looks to be written in C and utilizes many
techniques used by shellcode, but is repurposed to evade static analysis. The
packer also has many unnecessary API calls whose purpose could only be to
confuse dynamic and behavioral detection. Multiple layers of packing are
used to further conceal itself.
The ransomware payload is written in Delphi and only has a few dirty tricks
to mask it’s malicious intent. In addition to the ransom, the payload
hunts down bitcoin wallet files on the disk to steal their contents.
Why would an adversary spend more resources in the packing of ransomware instead
of the ransomware itself? I maintain that they are trying to increase the time
between the initial release of the malware and the time of detection by the
antivirus software. This longer time period will net them more infections, and
in turn, increase revenue on an individual campaign. It makes economical sense
to spend more resources on gumming up the antivirus analysis and detection
process, than building a better ransomware.
File size: 252,416 bytes
File size: 394,240 bytes
Packer Layer 0 (Psychological)
From an initial inspection, the sample looks like an innocuous DLL from Microsoft.
The file has a good sounding scary-to-delete-me name. The version strings
in the resource section are formatted exactly to Microsoft style. Most malware
I’ve seen that tries to masquerade as a Microsoft binary forgets certain features
like the copyright symbol.
All the DLL export symbol names are in the format lua_*. It looks like a
legitimate Lua library, but the disconnect begins here. When have you ever
heard of a Microsoft product written in Lua?
All of these exports are just stub functions. Just standard procedure
prolog and epilogs with no implementation in-between.
The sample is also hard to get started unless you have full context of how it is supposed to be run.
The sample is initially run using rundll32.exe
When first loaded, a call to the dll entry point is made, then a call to Working(). The
packed DLL does not have an export named Working(), but the unpacked payload
does. Working() will check the the command line string to ensure that it
was launched with rundll32.exe. If it isn’t, It will create a new process
using rundll32.exe. Once Working() is sure that the process was created
with rundll32.exe, it will then start a new process using rundll32.exe, but
this time it will run the AccessToken() export. AccessToken() is the
beginning of the ransomware code.
To successfully launch the ransomeware, you must pass the “Working” or
“AccessToken” parameter in the rundll32 command line. Without it, it will not
run. Why would the author be troubled to limit the infection?
I postulate that the author did this on purpose. I can see the scenario playing
out in my head: A forensics analyst has found this DLL, and determines that
it is related to the compromise. He sends the sample
to a malware analyst who tries running the DLL, but can’t get the
ransomeware to invoke.
The malware analyst will incorrectly respond to the forensic analyst,
saying that there must be more to the infection. This will slow down the
initial response process, allowing more infections. Before the malware analyst
can get the ransomeware to run, he will have to unpack the sample to get the
payload, and see the two exports of the payload.
The packer is riddled with API calls that have no effect whatsoever. This
makes it a nightmare for manual dynamic analysis. It was hard to tell what
code is important, and what is just a red herring. An analyst who sees
a call to GetLogicalDrives() might think, “I must be really close to the
ransomware code!” but only to be disappointed to find that the ransomware isn’t
even unpacked. Here is a call to MoveFile(), that might look like it’s using
obfuscated strings statically, but dynamically, you can see the function
return 0 indicating failure.
There are imports to winmm.dll (multi-media) that are never used. This might
be an attempt
to make the DLL look more legitimate (since the filename has HID in the name
and most MIDI devices also have a HID component), but I believe the author’s
reasoning to include all these unused imports is to hide one crucial
This one call to VirtualProtect() opens the gate to the rest of the packer.
Packer Layer 1
The first software layer of protection used by the packer is an encrypted region
of code. The code is decrypted in place, but because the “.text” section
is marked as read-execute in the PE header, a call to VirtualProtect() must
be made to allow write access.
Once the region is decrypted, it is run. Unfortunately, OllyDBG has determined
this region to be data, not code. So in the debugger, Olly will not display
the disassembly. I eventually discovered that you can remove Olly’s analysis
for a section using ctrl+backspace:
At this point, I made a dump of the DLL to analyze the uncovered region.
Packer Layer 2
The windows loader was not able to patch the relocations for this encrypted
region, because it was still encrypted during the loading process. As a result,
many restrictions are on the author for this region of code.
All the code must be written similar to shellcode. It has to run wherever it
is loaded in memory. This means no access to global variables, the import
table, or string literals.
The first thing that is done, is a large structure is allocated on the stack.
This structure contains the ‘global variables’ for this section of code. because
It doesn’t know where the “.data” segment is to access normal global variables.
You can see in my reverse engineered version of this structure, there
are pointers to API calls. This is done because the encrypted portion
of the packer was not linked during compile time. It also hides these
entries from the import section.
Now, the base address of the DLL is recovered using a technique similar to an
egghunter. a call $+5; pop eax is used to find the value of EIP, and
memory is search for ‘MZ’ at the start of a each 4k page. This value is stored
for later use.
Because this section of code isn’t linked into the main binary at compile time, its imports
have to manually be resolved. This is done with a technique commonly used in windows
shellcode. A pointer to a linked list of loaded modules is stored inside the
Process Environment Block (PEB). By traversing this linked list, the required
modules containing the exported functions needed can be found. Once the modules
are found, the individual exports can be found by following the PE structure.
Also, because this section of code can’t use any string literals, instead
of using a string compare, all strings needed are hashed and the hashes are compared.
In the following image, the hash value of the string “kernel32.dll” is 0x6a4abc5b.
The most important symbols to resolve are LoadLibrary() and GetProcAddress().
Both are in kernel32. Using these two functions, all other symbols can be resolved.
But because of the limitation of no string literals, some strings have to be built
on the stack first.
Now that all the prep-work for this stage is done, it must now unpack the payload
and position it in memory.
First the packed dll is read off of the disk and into memory. The region
containing the compressed payload is found.
The payload dll is decompressed using “RtlDecompressBuffer()”. The PE header is
parsed and the image is reconstructed in memory. This image must have its
imports manually linked in and relocations patch. But the address range the
payload image has been reconstruced in is not it’s final resting place. The
packed dll is occupying that region.
To remove the packed image from memory to make room for the final position of
the unpacked payload DLL, a bit of code must be placed outside of these two regions.
This portion of the code is identified by a ‘MOV EAX, 0x11223344’ instruction.
A VirtualAlloc() is performed, and the code is copied to the newly allocated region.
Execution continues at this region.
This last bit of code will unmap the packed dll from memory using a call
to UnmapViewOfFile(). Next, it reallocates this exact same region to copy
the unpacked payload image. The unpacked payload is freed from it’s temporary
position and execution continues at the entry point of the payload dll.
From here, the ransomware runs.
The ransomware communicates to one of two hard coded command servers.
The IP addresses of the command servers are 22.214.171.124 and 126.96.36.199.
There are no domain names stored in the sample, only the IP addresses.
Whois records for these ip ranges appear to be issued to Ukraine and Netherlands.
Domain names that are associated with these ip addresses in the past are:
Many of these records appear to be old by at least a year, and are probably not used in the attack.
Once the local files are encrypted, the user is presented with a ransom notice:
As adversaries continue to invest resources on packing we should expect to see more examples of how these tricks will still slow down the detection pipeline.
SentinelOne detects this specific attack at the initial exploit phase, before the ransomeware is even unpacked.
To learn more about how SentinelOne can help you prevent these kinds of attacks, please click here to download SentinelOne’s Technical Overview.