The Play ransomware group is one of the most successful ransomware syndicates today. All it takes is a quick peek with a disassembler to know why this group has become infamous. This is because reverse engineering the malware would be a Sisyphean task full of anti-analysis techniques. That said, it might come as a surprise that the malware crashes quite frequently when running. In this blog post, we will cover some of the anti-analysis techniques used by Play and look at the process the malware uses to encrypt network drives and how that can cause the malware to crash.
In a previous blog post, we briefly mentioned that Play is one of several ransomware groups that use intermittent encryption. In the same post, we introduced White Phoenix, an open-source tool that can help recover some content from large files encrypted with intermittent encryption. Now, we will introduce a new web version of the same tool.
Introduction to Play
Play ransomware first came to our attention in late December 2022, when the group made three headlines in less than a month. The first incident was an attack on the city of Antwerp, followed by an attack on the German hotel chain H-Hotels, and finally, when it was discovered they were employing a new variation of the ProxyNotShell exploits that bypassed the Microsoft mitigations that were available at the time.
We suspect the group has been active since July 2022. For the most part, it shares many features and mimics other successful and well-known ransomware groups. For instance, at one point, it was common for ransomware groups to target prominent organizations to gain attention in a practice known as ‘Big Game Hunting.’ At the time, Play did the same; however, now that the practice has become less common, Play hasn’t been drawing attention to itself using that approach.
Another commonality with successful ransomware groups is the use of ‘double extortion,’ or stealing information from their victims and later leaking it on a dedicated website on the dark web. As of September 2023, the group is in the top 5 ransomware groups by number of victims on their leak site.
Despite following many trends in the world of ransomware, the group does not seem to operate using the popular Ransomware-as-a-Service (RaaS) model. Instead, it prefers to target victims themselves to get the biggest payload.
Untangling the Strings
Not much has been written about the inner workings of Play ransomware. There is, however, one excellent article by Chuong Dong that covers a good amount of the anti-analysis techniques used by Play. In this blog, we will focus primarily on string decryption and the complicated process malware goes through to uncover the key to decrypt strings since these steps haven’t been covered in much detail.
It turns out that the key used by Play during string decryption isn’t stored directly in the malware. Instead, Play calculates the key dynamically at runtime. The function that generates the key is never called directly. Instead, the ransomware uses structured exception handler (SEH) hooking to reach it. SEH hooking is when a program manually modifies the exception registration list, a linked list of structures called exception registration records. The exception registration records hold pointers to exception handlers for the running thread. The first field in the Thread Information Block (TIB) points to the head of the exception registration list. Play implements SEH hooking and shortly after triggers an exception to move the execution to the handler (Figure 1). The exception handler defined will implement SEH hooking a second time and trigger another exception. The second exception handler is the function that calculates the key for the string decryption, which we will henceforth call the keygen function.
Figure 1. Play Implementing SEH Hooking and Triggering an Exception yo Use It
It’s good practice to dynamically analyze malware with tools like Procmon and WireShark before using IDA or similar programs to try to reverse engineer the Play malware statically. When we tested Play on a virtual machine, with Procmon running in the background, we noticed something unusual – a period of about 30 seconds where no activity was recorded from Play (Figure 2). It turns out the keygen function is to blame. The keygen function contains a loop that iterates over 900 million times! When we found this loop, we suspected this was an anti-sandboxing technique. Similar to calling the sleep API, this function might delay malicious behavior so that any sandbox trying to detonate the malware will likely timeout, resulting in no malicious activity being reported.
Figure 2. No Activity From Play for Approximately 35 Seconds
In addition to possibly being an anti-sandboxing technique, this loop calculates the eventual decryption key for the string decryption. The calculation starts with a hardcoded seed value, then each iteration of the loop squares the value and then adds the loop’s index. There are additional steps in the loop where the malware uses malloc to allocate some memory and then stores the intermediate results of the calculations in different parts of the allocated memory. However, that doesn’t seem too critical for the flow since the memory is freed shortly after. The decompiled code for the keygen function looks as follows (huge_number is larger than 900 million):
Figure 3. KeyGen Loop
Once the keygen function calculates the decryption key, the malware continues to the decryption of the strings. The decryption can be broken into two parts: Preprocessing each character with different combinations of “and,” “not,” and “or” followed by a “not” and a xor decryption using the key from the keygen function.
The preprocessing is quite complicated. It starts with a loop that iterates four times.
- Calculate the “shift right” encrypted character by zero bytes and check if the result is even or odd
- Calculate the “shift right” encrypted character by one byte and check if the result is even or odd
- If shift by zero is even and shift by one is odd:
- Calculate “not” of “2^1”
- And” result of the previous step with encrypted character
- “Or” result of the previous step with “2^0”
- Else if shift by zero is odd and shift by one is even:
- “Or” encrypted character by “2^1”
- Calculate “not” of “2^0”
- “And” result of the previous step with the encrypted character
After the first iteration, the loop will repeat another three times, except replacing 0,1 with 2,3, then with 4,5 and finally with 6,7. Finally, after the preprocessing, the result goes through one last “not,” which is then stored with the key.
Encryption Via SMB
The first thing the malware needs to target network shares is to enumerate all the IP addresses on the network. To start this process, the malware uses GetAdaptersInfo to learn the IP and subnet mask of the machine it’s running on, and from the subnet, it calculates the number of possible IP addresses in the network.
With that information in hand, the ransomware begins building structures (Figure 4) in allocated memory to track connections to all the different machines.
The structures created can be divided into a main structure and an array of secondary structures.
The main structure used by the malware comprises of:
- A number representing the amount of possible IP addresses in the network, not including the IP address of the current machine or the broadcast address.
- A number representing possible IPs, including those excluded in the first integer.
- A flag to be used later.
- A pointer to an array of secondary structures.
Each instance of the secondary structures in the array has three parts:
- IP Address the secondary structure represents.
- A flag that indicates whether the machine with the IP address responded to ping.
- A flag that indicates whether the machine with the IP address is open to connections over SMB.
Figure 4. Main Structure and Array of Secondary Structures
Once the structures are initialized, the malware will test the various IP addresses for connections. First, it’ll loop through all the IPs in the array and try to ping each of them. For those that respond, Play will switch the first flag of the structures in the array with the matching IP from false to true.
Next, for each IP address that responded to the ping, the malware will try to create a socket and connect to the address on port 445 (i.e., testing for an SMB connection). The ransomware will close the socket immediately without actually sending any data. For every machine Play successfully connects to, it’ll mark the second flag in the secondary structure in the array. In the next step, the malware shuffles the order of the secondary structures in the array.
As a last step in the enumeration, Play will loop through all the addresses in the structure again. This time, it only targets those responding to the SMB connection. Using NetShareEnum, it will retrieve all the SMB shares available on the targeted machines. Then it’ll loop through all the shares, checking that they are not of the type ADMIN$, SYSVOL or NETLOGON. For every share found that is not one of those three, the ransomware will encrypt that share. Finally, the malware will release the allocated memory where the array is stored.
Error 0xC0000005 – Memory Access Violation
Now that we’ve seen the big picture of how Play encrypts shares, we will dive deeper into the details of the functions that revolve around the ping tests that the malware performs. Play uses multithreading to save time in several places in the steps leading up to the encryption; however, the developers seem to have made a subtle yet critical error in the steps related to the pings.
Three functions are responsible for the ping process. The first function divides the IP range into blocks of up to 64 IPs. The malware then creates a thread for each block that runs the second function. The second function loops through all the IPs in the range of the block it was given and creates yet another thread for each. The new threads contain the actual ping-related logic. They call IcmpCreateFile, followed by IcmpSendEcho2, to ping the targeted machines. Depending on whether that malware got a response to the ping, the threads will then write either a 0 or a 1 to the ping flag in the secondary structures.
Since the code is multithreaded, the developers call WaitForMultipleObjects and WaitForSingleObject in various places throughout the code to sync the flow of the different threads. In most calls to WaitForMultipleObjects and all the calls to WaitForSingleObject, the developers gave either an infinite amount of time or 10 seconds to wait. The one exception is in the threads that perform the ping, where they only gave 1.4 seconds. To be honest, we have no idea why this is the case. The result is that occasionally, the ping will take too long to respond, and the rest of the code will continue. In the best case, this results in the malware skipping the specific IPs and missing potential targets. In the worst case, it takes so long to respond that the malware calls VirtualFree, releasing the memory allocated to the array before the thread tries to write the response, causing a memory access violation and leading to play crashing.
Figure 5. WaitForMultipleObjects with 1400 milliseconds
This would probably be considered a pretty serious bug for most programs – but it’s been a part of Play ransomware for a long time, perhaps even since the beginning. So, why haven’t they fixed it?
One possibility is that they were simply not aware of the issue. Even when it crashes, most, if not all, of the targeted machine will be encrypted. Opening a random folder will likely give the impression that the ransomware ran to completion and encrypted all the files. Simultaneously, when running on a victim’s network, there will be many instances of Play ransomware running, one for each infected endpoint. This means that most network shares will likely be encrypted even if some instances of the ransomware miss them. Only by using a tool like ProcessHacker will you maybe notice Play causes WERFault to be triggered right before closing. At the same time, if one lets the malware run using an IDE like VisualStudio during the development phase, a crash will likely occur and the IDE will alert the developer.
Another possibility we believe is more likely is that it’s simply not worth the effort to fix. As previously stated, if enough instances of Play are running on the network, they’ll likely end up covering most, if not all, of the network shares they’d like. We could only test the malware on a very small network with only a few VMs. In a more realistic environment with many machines with open SMB connections, Play might be kept busy going through and encrypting all the shares so that the ping threads have enough time to write their flags before the memory is released.
The Play ransomware group often exploits public-facing applications to gain initial access to a network. As mentioned earlier, the group has been known to use relatively new exploits and even modify them to bypass already released mitigations. However, they’ve also been known to use older exploits such as CVE-2018-13379, which preceded the group’s (estimated) formation by more than three years! As such, keeping software up to date is a good way to mitigate some of their initial access techniques. Similarly, other best practices, such as requiring MFA and phishing awareness training, can also help mitigate breaches by the group.
Removing local admins and enforcing least privilege through tools such as CyberArk Endpoint Privilege Manager (EPM) can constrain the options for execution, exploitation and lateral movement if a breach occurs, thus preventing or significantly limiting the impact. CyberArk EPM’s anti-ransomware policy has also proven effective at preventing Play ransomware from encrypting files.
Additionally, backups are a critical component of anti-ransomware mitigation. If done correctly, it can allow the restoration of encrypted files and minimize downtime after an attack is discovered.
Introducing: White Phoenix – Web
As one final mitigation for ransomware attacks, White Phoenix can be used to potentially recover some of the data from the larger files that have been encrypted by intermittent encryption. This can be done by downloading and running the source code from GitHub. Alternatively, we have created a website for White Phoenix for those who cannot or don’t want to work with the code. On the site, you can simply upload files for White Phoenix to recover whatever can be recovered.
We invite you to go to getmyfileback.com and have a look. The site is simple to use; just click on “Choose file,” pick the file you want to recover, verify the captcha, fill in your email (or select the “Rather not say” checkbox), and click “Recover file.” The recovered file should be downloaded within a short amount of time after that. See the demo below for an example:
Summary and Conclusion
As we’ve seen, Play ransomware has developed a fairly complex malware with many anti-analysis techniques. The group implemented a sophisticated multistep obfuscation process to encrypt their strings as just one of many anti-analysis techniques the malware used. The group keeps up with modern trends in the world of ransomware and maintains a solid position as one of the most successful ransomware groups in operation today. The developers, in particular, have demonstrated sufficient knowledge and skill to have created malware like Play. But ultimately, malware, like all software, is prone to bugs, often critical ones.
Like Edsger Dijkstra once said, “If debugging is the process of removing software bugs, then programming must be the process of putting them in.”
Ari Novick is a malware analyst at CyberArk Labs.