Unfinished .NET Ransomware
I’ve mainly focused on analyzing RAT’s, Backdoors, Trojans and Droppers/Downloaders in C, C++, Python, Powershell and VBS – but I have yet to analyze any .NET malware, and seeing as .NET Ransomware is very popular at the moment, lets have a look at a sample that I am hoping is either a joke or is just a template that will be changed completely, because it is pretty terrible. This sample was uploaded to VirusBay by XOR_hex.
MD5: e7bd6739e482645e2ca01d9f2ee204fb
I installed the latest .NET Framework onto my Virtual Machine and setup DnSpy, a .NET Debugger and Assembly Editor, which you can get here. Before transferring the executable to the VM, I ran strings on it to see if there was anything revealing in the sample. As the file was not obfuscated or packed (surprise surprise), there was a lot of information that was outputted by strings. Firstly, I would like to thank Tim for creating (Or at least compiling) this ransomware named “Tree.exe“, and I hope he becomes a leet h4x0r in the future, rather than making terrible malware that gets released before he has even finished it.
The program manifest is German, leading to the assumption that Tim is probably German or knows German. Helpful.
Anyway, back to analyzing the malware rather than the author. I transferred the file to my VM and opened it up in PEStudio, and checked the Indicators. One of the Indicators was that the file contained a URL – a link to a German Wikipedia page about the Advanced Encryption Standard (AES). Due to this, we can assume that the encryption algorithm that is used is AES, meaning the same key that is used to encrypt files can be used to decrypt the files.
I skimmed over the information given by PEStudio, but found nothing really interesting, so I decided to move onto analyzing the code itself by opening it in dnSpy.
I clicked on Form1 to view the code that I thought was executed first. I read over the code and found something quite peculiar in Form1_Load() – the ransomware would create key.txt on the Desktop and wrote whatever was stored in Form1.str to the text file. Perhaps that was just for debugging purposes, and the author would change it in the final sample? Does that mean Form1.str contains the encryption key? You can also see just underneath, getFiles() is being called with the argument “C:\\Users\\” – this is probably done so that the ransomware can be run with regular user privileges and not attempt and fail to encrypt hundreds of System files.
I also located the ransom note that would be displayed once the files had been encrypted, which stated that the key was completely random and the only way you can get it is by paying 50 Euros. This piqued my interest, as there must be some way that the “random” key is sent back to the author, otherwise how will they know this “random” key?
I had a look at getdiskFiles.getFiles(), which contained a for loop:
foreach (string text in Directory.GetFiles(Path)) { bool flag = text.Contains("key.txt"); if (flag) {} else { byte[] bytKey = crypt.CreateKey(Form1.str); byte[] bytIV = crypt.CreateIV(Form1.str); crypt.Encrypt(text, text + ".enc", bytKey, bytIV); DeleteFile(text); } }
This code gets all of the files in the directory Path and loops over each, where each file name is text. The program checks to see if the filename contains key.txt, and if it does, the file is ignored. Otherwise, it looks like a key is created and stored in bytKey, an IV is created and stored in bytIV, and then the file is encrypted, using the key and IV. The original file is deleted, after being replaced with the encrypted version. So rather than the key and IV being formed at the start, it is formed every time a file needs to be encrypted – seems like a bit of a waste, although I am not a professional ransomware developer so I wouldn’t know. Now we know how the files are encrypted, we need to figure out how the key and IV are formed.
As you can see, these functions are very similar. The only difference is the amount of bytes passed to GetBytes(). 32 bytes are passed for the key (meaning the key will be 32 bytes long), and 16 bytes are passed for the IV (meaning the IV will be 16 bytes long). Based off of the key and IV length, we can deduce that the encryption algorithm used is AES-256, due to the 16 byte IV and 32 byte Key. So whatever is in Form1.str is used to derive the key and IV, along with the salt, which is “salt” stored in a byte array. Because they are so similar, we can also determine that the IV is equal to the first 16 bytes of the key. Once they have been formed, they are returned to the calling function. Now we know how the key and IV is formed, it is time to examine the encryption algorithm.
So the Encrypt() function takes 4 arguments, the input file, the output file, the key, and the IV. The variable fsInput has a handle to the input file, and fsOutput has a handle on the output file. A while loop is started, and the program reads 4096 bytes from the input file into the byte[] array. cryptoStream.Write is called, which first encrypts the 4096 bytes in array with the Key and IV, and then writes it to the output file. Once the file has been fully encrypted, the file and cryptoStream handles are closed, and the function returns. The decrypt() function is also an almost exact match, however instead of rijndaelManaged.CreateEncryptor(), the function uses rjindaelManaged.CreateDecryptor().
So once we have figured out both the encryption and decryption routine and how the Key and IV are formed, we now need to figure out what is stored in Form1.str. In order to do this I scoured for any mentions of Form1.str and located this in Form1:
string text = Conversions.ToString(12); Form1.str = getdiskFiles.RandomString(ref text);
Therefore, whatever is returned by RandomString() is what is stored in Form1.str, so lets analyze RandomString().
As you can see, the function takes one argument as a string denoted as Length, which is then converted to an integer and stored in num. In this particular sample, num is equal to 12, as that was the string passed to this function. A for loop is started, which loops 12 times, or until i is equal to the value in num. num2 is declared as an integer – this is an important integer because it is what is used to store “random” integers, between 30 and 121 (122 is the maximum value and thus will be ignored) – this narrows any brute force attempts down to 91 numbers. The flag Boolean seems to be there to prevent negative numbers, as I ran a test to see what would result in flag being False, and the only time it was false was when the value was negative. Once num2 has been filled with a “random” integer between 30 and 121, the value is converted to a character code using Strings.Chr(), so the number 98 would be converted to a ‘b‘. It is then converted to a string and appended to the variable text. Once the 12 character/symbol long string has been formed and stored in text, it is returned to be stored in Form1.str. Whilst examining this sample, I had seen the encryption function being used and how it was called, but saw nothing about calling the decryption function, until:
This doesn’t really need a deep analysis – it is identical to the file encryption routine, but it calls the decrypt() function instead, and removes the extension. At this point I had pretty much analyzed the important functions, so it was time to detonate it in the VM. I ran both Wireshark and ProcessHacker to check for any network connections made by the ransomware – but I did not expect it to. Upon executing the binary, a message box popped up:
Upon seeing this all of my questions were answered. This most definitely is a work in progress ransomware, as what popped up was in fact the randomly generated string in Form1.str, so if you were to use PasswordDeriveBytes() on the string with the salt you would get the final encryption key, as well as the IV. I hit OK which took me to the most horrible ransomware note I have seen in a long time.
As you can see, the payment address has not been filled in yet, so obviously I can’t pay the 50 euros to get my files back, whatever will I do! You can also see that in the background all of the .LNK’s and .EXE’s have lost their icon and been replaced with a Wireshark file icon (.enc) – so the ransomware actually does something. I entered the key after clicking the Enter Key button, and then clicked the button. Surprisingly, the program actually decrypted the files, and soon enough, the original icons were back.
I used an online C# editor to display an example of the encryption key – I am quite new to C# so I executed certain code in order to figure out what it does, which I find extremely useful. Whilst it is longer than 32 bytes, it is because the original data is being transformed into a string, which obviously causes some issues.
I ran it once more to view the encryption of a string, so I created a document, test.txt, and filled it with the string This is a test, and then executed the ransomware. It was converted to a .enc file, and encrypted:
So, the analysis is finished. But you may be wondering why I put random in quotations? Well, it’s because it isn’t random, which is the specific vulnerability that a lot of ransomware authors fail to realize. The code that creates the randomized string utilizes Random(), which uses the Environment.TickCount to get the integer. The tick count is the uptime of the system in milliseconds, so as you can probably guess it is a very large flaw. If you want to learn more about this, here is a post from the creator of the HiddenTear honeypot ransomware.
IOCs
- Ransomware: e7bd6739e482645e2ca01d9f2ee204fb