Ransomware - level: stupid
The opposite of sophisticated malware sometimes does the trick. In this blog post I will go over why less is sometimes better.
We (Data-Sec) just came out of an IR. After everything had been wrapped up, we quickly went back to our usual daily tasks and also our normal working hours. Other than the last IR this one still annoys me a bit. The attacker we encountered this time seemed to be the least sophisticated attacker in all IRs I was a part in so far.
Over 30 direct RDP-Connection-Attempts from his VPS, no OPSEC, keyboard-layouts which he forgot to remove, hardcoded strings...This was just too easy after everything came together. Sure, this could all have been a ploy and 5D-Chess of some super secret ransomware-group that no one ever heard anything about but I doubt it because the basic "ransomware-one-man-group-guy-theory" just fits better at this point.
But still, no matter how simple the ransomware it still did its job and got me into this IR. Super-sophisticated over-engineering was not even necessary in this case.
This kept me thinking and what made me write this blog post after all. How hard is it really to write ransomware and deliver it on a modern system? How hard would it be to encrypt each file and how many hours do you need to invest to get past EDR?
It is easy for someone like me to shit on simple ransomware but could I do a better job? Would I be able to implement at least similar techniques? I would never call myself a a developer and anything I ever coded barely worked and any Dev worth his/her salt would laugh about what I hammered together.
With those 'skills' how hard could it really be? ;)
I will not share the full code for obvious reasons
For starters I wanted my ransomware to be the following:
- Compile as dll because usually gets flagged a bit less
- pure C (just because)
In terms of functions it should be able to:
- Get all drives
- Encrypt each file
- Do not run if already running
- Skip some directories like Windows and stuff
- Show ransomnote
- Change wallpaper to something cool (of course!!)
Yeah, I know this seems very basic and thats because it is. I wanted to build a foundation first and then see how it performs and then build on top. Keep in mind, I did not mention evasion or Anti-Debug or Anti-VM-capabilities at all. This is because I did not see that in the IR-sample as well.
First thing to do, enumerate every drive and ignore the usual Windows-folders. We do not want to screw with Windows-stuff. Reason being, that we want the victim to be able to restore files and still use the computer to send us the cryptocoins or contact us. We also want to make sure to also touch every file on every remote drive. This way you should get all the juicy data in most office-networks.
To do so I decided to target the following type of drives:
- FIXED
- REMOTE
- REMOVABLE
Like I mentioned I wanted to skip the Windows-Folders. Most ransomware will also skip files with a certain file-extension such .exe or .dll but I decided against it as I am mostly targeting User-Data-Folders and did not touch anything regarding Windows. If someone installed Software in any of the folders not mentioned here then this would stop working as well. But who cares? I am the bad guy here ;)
To keep running time to a minimum and not to alert the user ("My PC is so slow, let me call IT-Support") I decided to only encrypt the first 420 bytes of each file. If the file is too small (<420 bytes) then it should be skipped.
With the 420 bytes in memory I had to implement the most important part, the encryption. Up to this point I relied on normal Windows-APIs without API-Hashing, shellcode or indirect shellcode or anything that could be considered fancy. However, implementing RSA or AES-encryption (by importing libs) would show up in the IAT of the file and raise the suspicion for any EDR or Analyst.
To avoid that I decided to stick with very simple sort of encryption. I went with ROT47 and simply adding/subtracting bytes to the buffer. To be fair, this is not bulletproof (duh!) and very simple to RE and it would not take very long to write a decryptor for this particular encryption-routine. Still, this way I would not have to implement any dangerous Crypto-libs or call anything with crypto at all without resorting to fancier solutions.
If you look closely you can see that my code is wrong (told you so) and does not do what it is supposed to do. The buffer[i] is not written back into itself therefore the second part is kinda useless. Still, it encrypts the file and this is enough for a PoC.
With the most important part all set I had to think about how to deliver the file. The easiest solution would be to write a phishing mail and deliver it in a pw-protected zip-file. This is a) very simple to build and b) realistic as current ransomware-operations are using the same techniques. But, I decided on compiling a .dll so a simple .exe was out of the question. I needed a simple solution to run the ransom-dll. Next best and simplest thing I could think of was Dll-Proxying.
This should also help you in lowering the "yeet-it"-score that the AV/EDR would use on your file, to decide if its malicious or not. Reason being that if the proxy being a signed and trusted Windows or Adobe or whatever binary then it would be "trusted" in a way and should help us running our malicious code.
To not complicate thinks I decided to check up on Hijacklibs.net and shop around for a binary that would allow us to plant our own .dll. The way this works is the following:
When writing software developers - to not end up with one big ass binary - will split different parts of a program into different files/libraries. Those libraries are used for different parts of the program. Edge for example is able to open websites but also PDF-Files. Those 2 functions are very likely kept in different libraries or rather use different functions and therefore different libraries.
Developers do not need to re-implement the wheel for everything all the time. For very simple things such as writing a file those devs can import system libraries that export such basic functions like CreateFileA for example. When loading in those libraries Windows will follow a search order trying to find the file. Say you want to import user32.dll. Windows would then try to find the file in the following order:
- DLL redirection.
- API sets.
- Desktop apps only (not UWP apps). SxS manifest redirection.
- Loaded-module list.
- Known DLLs.
- The package dependency graph of the process. This is the application's package plus any dependencies specified as
<PackageDependency>
in the<Dependencies>
section of the application's package manifest. Dependencies are searched in the order they appear in the manifest. - The folder the calling process was loaded from (the executable's folder).
- The system folder (
%SystemRoot%\system32
).
Interesting here is "7. The folder the calling process was loaded from..." which means if you write a program that would try to load abcde.dll at some point and then run it from C:\Users\user\Desktop\ then this would basically mean that IF the dll abcde.dll could not be found in step 1-6 the program would try to find the dll in step 7 in C:\Users\user\Desktop\abcde.dll. This only works if the dll is not a know dll.
Dll-Hijacking/Proxying is very common and is constantly used by threat actors to deliver payloads which makes it a simple and reliable way of delivering our ransomware. After all, can't expect the user to run rundll32.exe for you ;)
To manually find possible candidates for your proxy-dll you can use Procmon and set it up with the name of the binary as Process Name and the Result set to "NAME NOT FOUND". In this case I am going with tscon.exe which is a Microsoft file usually found in C:\Windows\System32\tscon.exe. I chose this file because it does not do anything visually and is pretty small in size.
With procmon setup, simply run the binary on Desktop or somewhere and check for the dlls it tried to load.
In this case tscon.exe is trying to load winsta.dll directly from Desktop which won't work of course. This allows us to inject our own winsta.dll and run malicious code. By the way tscon.exe is trying to load several dlls. I just took the first and did not check the others.
Manually checking tscon.exe we can see the import as well as the functions of the winsta.dll.
Next thing to do is to create the proxy-dll to implement our own stuff. It is important here to get all the imports as otherwise the program may crash and never reach our malicious code. You can do this manually by walking down the file and getting all the imports or by using tools such as Spartacus which will create the proxy-dll for you....Yeah lazy!!
To test it I used a simple MessageBox and then double-clicked on tscon.exe which resulted in the following:
As you can see Dll-Proxying is pretty simple and therefore a good way for us to deliver our payload. There is still the annoying black cmd-box that would be an obvious indicator. For now I decided to leave it there as this was supposed to be a simple and dumb ransomware. In future posts I will try to improve on the delivery-part and keep it hidden.
With the payload-delivery taken care of it was time to think about how I wanted the file to get to the victim. To keep it real I decided to send it via mail as a link uploaded to a random filehoster. This is a) realistic and b) simple.
Uploaded to ufile.io and double zipped with the pw 20232023 to also get around the MOW this should do the trick. In an engagement you would be wise to go with something less likely to get blocked like onedrive, gdrive or even discord cdn. Even so, with this approach it should get you past most firewalls and sandboxes. Again, a real phishing mail would obviously look way better ;)
First heart-wrenching moment...actually downloading and unpacking the file to the EDR-machine. For this test I used my daily driver Windows 10 Pro 22H2 VM with Sophos Intercept X EDR with all the modules activated. The first test would be to see if Sophos would actually yeet the file as soon as it touched the disk. As we did not implement any anti-sandbox or any anti-anything techniques, anything fishy should get us flagged immidiately. But...nothing happened (contact.pdf.exe is the file, renamed from tscon.exe, wsinsta.dll is hidden)
Time to actually run this thing. A better approach would be to also replace the RSRCICO in the tscon.exe with something that fits your decoy (pdf in this case) and maybe add more decoy files as well to actually give your target any reason to actually download your files.
With that said, it was time to run the file. But before we do that let's go over the file itself again. We compiled the ransomware as winsta.dll. Now for a quick IOC-Check lets go over the strings:
We have a few strings that show us the exports, functions regarding files and typical imports and the mutex itself
Nothing interesting here really. Kernel32 and user32.dll are common and the api-ms* are from visual studio (which you would remove for real engagements)
Entropy is also pretty low because obviously we did not pack anything:
Exports shows all our exports we took from the original winsta.dll:
PEStudio is pointing out a few functions such as FindFirstFileA which is going to be used to find and encrypt the next file or SystemParameters to change the wallpaper.
Still, scanning the file shows 0 detections so far:
With that being said, lets run the file for real and see how it performs.
As you can see we still have to deal with the annoying black screen from tscon.exe but the ransomware is running nonetheless and encrypting files either way. After a few minutes of encryption we get the following:
Yes, best wallpaper...thank you ;)
Remember, this was just a PoC and a short one for that but still, encryption worked and I am eager to improve this basic and dump solution. I am still a bit surprised that Sophos did not intervene and allowed me to encrypt my own files so easily, however I guess this is because no one would ever use ROT47 and simply adding a few bytes and call it ransomware. ;)