#NotPetya and #Petya compared: any hope for decrypting files?
Positive Technologies expert Dmitry Sklyarov provides here his comparison of NotPetya ransomware, which attacked companies this week, with a sample of Petya from 2016. Is decryption of ransomed files possible? And what does the code tell us about the malware's creation?
This post considers the portions of the two viruses responsible for MFT encryption. This encryption runs when the ransomware has administrator rights.
What NotPetya does
At the moment of infection (while Windows is still running), the virus writes code to the start of the disk. This code will be run after restart. The virus writes its configuration, verification data, and original MBR to certain sectors.
Let's start by looking at disk sector 0x20, which is something like a machine-specific configuration. During an infection, the following values are written to sector 0x20:
- Indicator that the MFT is not encrypted (value 0)
- EncryptionKey (random sequence 32 bytes long)
- Nonce (random sequence 8 bytes long)
- Personal Installation Key (random sequence of 60 characters from the following alphabet: 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz)
Random data is generated by the CryptGenRandom function, which is believed to be cryptographically strong.
512 bytes with the value 0x07 are written to sector 0x21.
A version of the original MBR, in which every byte has been XOR’ed with the value 0x07, is written to sector 0x22.
After the initial restart, the MFT is encrypted. Before this happens:
- Sector 0x20 is read
- MFT encryption indicator is set (value 1)
- EncryptionKey is copied to a temporary buffer
- The field with EncryptionKey is overwritten with null bytes
- Sector 0x20 is written to disk
- Sector 0x21 (all 0x07) is read
- Contents of that sector are encrypted using EncryptionKey + Nonce
- Sector 0x21 is written to disk
Then the MFT sectors are encrypted with the same EncryptionKey + Nonce. The code of the encryption algorithm strongly resembles the Salsa20 algorithm, but there are some differences. Instead of the constant "expand 32-byte k," the constant "-1nvalid s3ct-id" is used. So far I have not been able to repeat the results of encryption with a known key. Possibly the authors have made an error somewhere, which would seem to be confirmed by this post: https://twitter.com/kryptoslogic/status/880058211516260352
The Salsa20 algorithm is strong.
When everything is encrypted, the machine restarts again, and the ransomware appears on screen requesting the decryption key.
The key is supposed to be a 32-character string containing a combination of the following characters: 0123456789abcdef. This string is run through a function that accepts an arbitrary number of bytes as input, and outputs 32 bytes. Presumably this is the SPONGENT hash function (to be confirmed). Then the output is fed through the same function 128 times, which gives us EncryptionKey. To check whether the key is valid, an attempt is made to decrypt the contents of sector 0x21, and if the expected unencrypted text is found there (all 0x07), MFT decryption and MBR restoration are started.
Can the attacker decrypt user files?
In my view, the authors of NotPetya did not intend for files to be recoverable after receipt of payment. Here's why:
1. The Personal Installation Key, which needs to be given to the virus creators after paying the ransom, is not related in any way to EncryptionKey. Both keys are random data. One key does not tell us anything about the other key, unless the attackers have some special knowledge about the workings of CryptGenRandom. Alternatively, the authors are supposed to send both EncryptionKey + Personal Installation Key to their own server, but nobody has reported such activity (and I have not seen such indications in the code, although it cannot be ruled out).
2. If my guess about the SPONGENT hash function proves correct, the decryption key is supposed to be the output of the hash. In order to calculate a valid key, this hash would need to be reversed (129 times), which is impossible with today's technology.
3. The entropy of EncryptionKey is 32*8 == 256 bits. The entropy of the hex key (entered by the user) is 32*4 == 128 bits. Any operation can only reduce entropy. Therefore, 32 hexadecimal characters cannot give us 32 bytes with definite values.
Differences from Petya (sample dated January 9, 2016)
Petya did not want to infect my test machine. Maybe it requires a network connection or something else—in any case, I had to do a memory dump.
I have not managed to review the code that generates the sectors used in the MBR set by Petya, but I did look at screenshots and the code run after restart.
1. Sectors 0x36–0x39 are used (compare to NotPetya: 0x20–0x23).
2. Most auxiliary functions (text display, sector read/write) are identical to Petya.
3. Petya contains a function and strings for displaying a skull-and-bones banner. NotPetya has a very similar function but it is probably never called, and the strings have been zeroed out.
4. The length of the Personal Installation Key is 90 characters (15 groups of 6 characters each) versus 60 in NotPetya. Using an alphabet of 58 characters, a maximum of 527 bits of information can be so conveyed (versus 351 in NotPetya).
5. The Petya dump shows strings secp256k1 and secp192k1, which might mean that the Personal Installation Key is derived from EncryptionKey, and is calculated using elliptic-curve cryptography.
6. The user-entered key to start decryption should be a 16-character string from the following alphabet: 123456789abcdefghijkmnopqrstuvwxABCDEFGHJKLMNPQRSTUVWX.
7. Nothing resembling SPONGENT (or any other hash) is found.
8. Salsa20 uses the original constant "expand 32-byte k." The code of the functions is nearly identical, and while the Petya code was likely generated by a compiler (optimization was applied to repeating characters), it seems that in NotPetya, the constants were simply replaced.
The evidence, in my view, suggests that another strain of Petya existed, and after replacing constants and strings, that it was used to create NotPetya.
Again, I do not believe that NotPetya was intended to support decryption of victims' files, while Petya did in fact have such functionality. Disk recovery may still be an option, however. Both viruses have similar errors in the implementation of encryption algorithms, which could make it possible to quickly brute-force an encryption key to recover all encrypted data. Back in 2016, researchers described a method for recovering Petya-encrypted data without paying a ransom.
NotPetya: MFT encryption code
NotPetya: MFT encryption code
Among the versions of Petya encountered in 2016—with their different colors (1, 2), "duet" with mischa ransomware, and this form as well—it is worthwhile to look at PetyaGoldenEye.malware, which was first uploaded to VirusTotal last December.
The code that NotPetya writes to the beginning of a disk, as well as the code run from the MBR, is extremely similar to the code written by PetyaGoldenEye: SHA256: b5ef16922e2c76b09edd71471dd837e89811c5e658406a8495c1364d0d9dc690.
Differences we found in NotPetya compared to PetyaGoldenEye:
- Many text strings have been changed (the ransom demand has been corrected and the skull picture has been removed).
- Offsets for several strings have been changed (message size changed, requiring that the beginnings of the strings be repositioned).
- In the function at address 0000:86E0, a piece of code jumps around (and is never run); this code is responsible for displaying a banner (flashing skull and crossbones) before any key is pressed.
- The banner color has been changed from yellow (0xE) to red (0xC), although the banner is not displayed.
- At address 0000:848E, a function call has been removed and replaced by three NOP instructions. The function is responsible for clearing the keyboard buffer (which is no longer necessary, since keystrokes are not anticipated).
- In the function at address 0000:96D4 (expand for Salsa20), the initial state of the string has been changed from "expand 32-byte k" to "-1nvalid s3ct-id".
- In the function at address 0000:998E (permute for SPONGENT), the initial value of LFSR (linear-feedback shift register) has been changed from 0x9E to 0xA3.
No other changes in the code were found.
SPONGENT hash function
We believe that code for implementing SPONGENT was taken from here. We can easily make that code equivalent to what we see in NotPetya: in the permute() function, replace the initial value of the lfsr variable and rewrite the spongent() function so that it takes an array pointer and array length as input, instead of a null-terminated string.
Notably, the initial value of LFSR == 0x9E (as described in the original specification for SPONGENT-256/256/16) yields 140 rounds, but the initial value 0xA3 used in NotPetya results in 152 rounds, slightly increasing cryptographic robustness.
Salsa20 encryption function
The code for implementing Salsa20 was likely taken from here. By replacing the "o" array value in the function s20_expand32() and replacing the body of the function s20_littleendian() with the string return *(__int16*)b; we get code equivalent to that used in NotPetya.
Because of incorrect implementation of s20_littleendian() (most likely, due to incorrect typing or 16-bit compiler error), two of each four bytes in the keystream array are not used at all. In effect, this makes the encryption key 128-bit, instead of 256-bit (halving its robustness). However, bruteforcing the entire 128-bit key space is still unrealistic with today's technologies.
The creators of Petya implemented MFT encryption using robust (although uncommon) cryptographic primitives for which they used code from GitHub repositories.
Errors in the first version of Petya (Petya Red) made it possible to decrypt data without paying a ransom.
Later versions (Petya Green, PetyaGoldenEye) corrected many of these mistakes, leaving only the type conversion error that halved the effective key length used for encryption. Attempts to take advantage of this weakness in Petya Green were unsuccessful.
The most likely scenario is that the creators of NotPetya did not have access to the Petya sources, and could not make necessary changes to them and recompile the project. Instead, they based NotPetya on existing code from PetyaGoldenEye, which they analyzed with a disassembler, and made changes using a hex editor.
Efforts to find ways to recover files encrypted by NotPetya are still ongoing.