Bug in Nemty corrupting the encryption of large files

By 7 november 2019 november 11th, 2019 Blog

Introduction

Over the past weeks, Tesorion researchers have been working on free decryption tools for the Nemty ransomware (see also our previous blog posts A decryptor for the Nemty ransomware based on analysis of its cryptography and Nemty update: decryptors for Nemty 1.5 and 1.6). Our CSIRT team has in the meantime assisted a large number of Nemty victims to recover their files for free using these tools.

While the Tesorion CSIRT team was assisting victims, we found that unfortunately in some cases we were not able to decrypt files that we thought our decryptors should be able to handle just fine. After examining some of the problematic encrypted files and some careful additional inspection of the Nemty encryption code, we noted that very large files (several gigabytes) could not be decrypted correctly by our tools. Unfortunately, this was not caused by a bug in our tools, but rather by a bug in the Nemty ransomware file encryption code: we have come to the conclusion that the Nemty encryption process is irreversible for these files. This irreversibility stems from the fact that the encryption code overwrites a part of the file with the encrypted data of another part. There is therefore no way to restore the overwritten data, not by us, and not by any decryptor developed by the Nemty authors.

Partial file encryption

Reading a file, encrypting it and then writing it back again takes time. Some ransomware authors elect not to encrypt the entire file in the case of large files, but rather encrypt one or more smaller sections of the file. This usually renders the file effectively useless to the victim, but saves a significant amount of time in the encryption process of large files.

Nemty versions upto and including 1.5 were based on a custom (and bugged) AES-256 implementation (see also our first blog on Nemty). File encryption using this implementation was not very fast. The authors probably came to the same conclusion early on, and decided to encrypt only parts of larger files, instead of the entire files. There are some minor differences between older and newer Nemty versions regarding which parts of a file get encrypted. In this blog post, we only describe this for Nemty 1.6, but older versions work more or less the same (and contain the same bug).

In Nemty 1.6, when the malware starts to encrypt a file, it first determines the original file size. If the size is no larger than 6400000 bytes (a little over 6 MB), only the initial part of the file up to 102400 bytes is encrypted. The size of the encrypted part is rounded down to blocks of 16 bytes (128 bits, the AES block size). The encrypted part of the file is overwritten, and the remainder of the file stays unencrypted. A Nemty-specific footer is appended to the file, containing a bit of base64 encoded, RSA-encrypted data required for decryption, and a 15 byte ‘tag’ containing the string ‘_NEMTY_’, a 7 character random string specific to the infection, and a final ‘_’.

If the original size is larger than 6400000 bytes, three parts of the file are encrypted (in this order) : a block of 1600000 bytes just before the middle of the file, the first 102400 bytes and the last 102400 bytes. The encryption again happens in place, overwriting the original data. The remainder of the file stays unencrypted. And again, the Nemty-specific footer is added to the file.

These different cases of partial file encryption are shown in the image below (not to scale) :

The problem with large files

The partial file encryption as described in the previous section is not problematic for decryption: provided with the key and IV, we can simply decrypt the encrypted parts in place and leave the already unencrypted data intact. The encryption process is in principle reversible, as is also shown by our decryptor.

Unfortunately, the Nemty encryption code contains a serious bug that makes correct decryption of files larger than a little over 2GB impossible. To understand this bug, we first need to understand a bit more about the Windows file API: the functions that are used by Windows programs to read and write files. This API is based on the notion of a file pointer: the location in the file where the next read or write operation will take place. The file pointer is automatically moved to the end of a read or write operation, but it can also be moved explicitly, for example to write over a part of the file that was just read. The main functions used to manipulate files in the Nemty encryption process, are the following:

  • GetFileSizeEx: this function determines the size of a file
  • SetFilePointer: this function repositions the file pointer for the next read or write operation on a file
  • ReadFile / WriteFile: these functions perform read or write operations at the current file pointer

At the start of the encryption process for a file, the GetFileSizeEx function is called to determine the original file size. The size returned by this function is a 64 bit signed integer (a 64 bits signed integer value can correctly represent the number of bytes in files over an Exabyte in size). This value is then used in calculations such as the comparison to decide whether the file size is over 6400000 bytes, and the calculations to determine the middle of the file. For each part of the file that has to be encrypted, the file pointer is moved to the start of that part using SetFilePointer, the original data is read using ReadFile (implicitly moving the file pointer ahead as well), the file pointer is then moved back to the start of the part using SetFilePointer, and the encrypted data is written back using WriteFile (again moving the file pointer ahead as well).

The SetFilePointer function that is used to move the file pointer does not simply take a single 64 bit value as offset. Instead it takes two separate arguments: lpDistanceToMoveHigh, a pointer to the highest 32 bits of a signed 64 bits offset that can also be NULL to signify 32 bit operation, and lDistanceToMove, the lowest 32 bits of the signed offset. If lpDistanceToMoveHigh is NULL, only the value of lDistanceToMove is used, and it is interpreted as a signed 32 bits value. If the requested offset does not fit within a 32 bits signed value, it must be represented as a 64 bits signed value, that is split over lDistanceToMove and the value pointed to by lpDistanceToMoveHigh.

Unfortunately, the Nemty authors have not taken this into account when writing their software: in their calls to SetFilePointer lpDistanceToMoveHigh is always set to NULL, so the value of lDistanceToMove is used as a signed 32 bit value. For files under 2GB, this works fine: the highest 32 bits of all relevant values are always equal to 0 and the actual positive offset from the start of the file fits within the 32 bits signed value, so the code works as intended. For 2GB or larger files however, we run into a problem: values of 2GB and larger cannot be represented in a 32 bits signed value, and by simply interpreting the lowest 32 bits of the original 64 bits value the file pointer offset is sometimes interpreted as a negative value.

If the reinterpreted value is negative, the file pointer is moved backwards in the file. And as all moves in the Nemty code are relative to the start of the file, all negative offsets are invalid. If SetFilePointer fails to reposition the file pointer, for example because the new offset would be beyond the boundaries of the file, the file pointer remains at its old location. In the next section, we show by means of an example how this causes files to become irreparably damaged by the encryption process.

The easiest fix for this problem, would be to use SetFilePointerEx instead of SetFilePointer.

We debated internally whether we should point out this solution, as we don’t really like helping malware authors. But in this case we would rather have a victim’s files encrypted in a reversible manner, than encrypted and corrupted. So Nemty authors, if you are still reading our blogs: please fix this, so we can both recover the original data for your victims, instead of no one.

An example of file corruption during encryption

To illustrate how this causes a bug in the Nemty encryption code, consider a file of 2147586048 bytes. The offsets for the middle part and the start of this file cause no problems: they are calculated using 64 bits values and the results fit nicely in a 32 bits signed value, so no problems there. Then the file pointer is moved to the first offset in the file and the first 102400 bytes are encrypted as intended. However, when the offset of the last 102400 bytes of the file is calculated, the result is 2147586048 – 102400 = 2147483648. This value is 0x80000000 in hexadecimal, and can not be represented correctly using a 32 bit signed value. If this value is interpreted as a signed 32 bit integer, it becomes the value -2147483648. SetFilePointer then tries to move the file pointer to minus 2147483648. However, this operation fails, because this isn’t a valid position. The Nemty code does not check for errors on SetFilePointer function calls and proceeds to read 102400 bytes from the file. This read operation implicitly moves the file pointer 102400 bytes ahead. The ransomware then encrypts these bytes, and then calls SetFilePointer again with the value of -2147483648 to move back to the start of the data it read in order to overwrite it with the encrypted data. This call again fails, so the file pointer is not modified and is still at the end of the block that was just read. Again, no error checking takes place. The encrypted block is then written and ends up directly after the original data, overwriting a different part of the file that is then no longer available anywhere, either encrypted or unencrypted. Then, the SetFilePointer for the Nemty footer also fails, and another part of the unencrypted data is overwritten by the footer, leading to even more data loss.

The image below shows the effect of this bug (not to scale) :

We believe 2147586048 bytes is the smallest file size that triggers this problem causing irreversible encryption; it should be possible to correctly decrypt all files with an original size smaller than 2147586048 bytes. Depending on the type of file and the part of the data that is lost, the data that is unrecoverable can be just a slight nuisance (a glitch in a movie for example), or it can render the entire file unusable, even after best-effort decryption of the parts that can be recovered. Victims can only try to decrypt their large files and hope for the best.

Conclusion

Payment of the ransom is never recommended, because there is no guarantee that the malware authors will provide you with a decryptor after payment. Furthermore, by paying the ransom, the ransomware business model is reinforced and cyber criminals are likely to continue their malicious activities.

In this blog post we described a bug in the Nemty ransomware file encryption code that corrupts large files, making correct decryption impossible. This shows that even if a victim would pay the ransom to the Nemty authors (which should in most cases be unnecessary due to our free decryption tools up to Nemty version 1.6), there still would be no possible way to completely recover such corrupted large files; part of the data is simply destroyed by the Nemty ransomware instead of encrypted in a reversible way.

So the bad news is: if you are a Nemty victim and you have large encrypted files (2147586048 bytes or larger, i.e., a little over 2GB), it is unfortunately unlikely that you can recover all of the data in those large files. Due to the bug in the Nemty encryption code that causes part of the data to be destroyed, there simply is nothing we, or anyone else including the Nemty authors, can do about that.

Indicators of Compromise

SHA256 of the Nemty 1.6 binary used in this research: 98f260b52586edd447eaab38f113fc98b9ff6014e291c59c9cd639df48556e12