From bae1f88709c17f1e75f701d231fe2dcb7e96b6b2 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Thu, 30 Apr 2020 20:55:39 -0400 Subject: [PATCH] Added ShellcodeUtils and enabled Go modules --- README.MD | 91 ++++++++++- cmd/ShellcodeUtils/main.go | 309 +++++++++++++++++++++++++++++++++++++ go.mod | 8 + go.sum | 17 ++ 4 files changed, 424 insertions(+), 1 deletion(-) create mode 100644 cmd/ShellcodeUtils/main.go create mode 100644 go.mod create mode 100644 go.sum diff --git a/README.MD b/README.MD index 92981b3..7b6afe4 100644 --- a/README.MD +++ b/README.MD @@ -1,6 +1,6 @@ # go-shellcode -`go-shellcode` is a repository of Windows Shellcode runners and supporting utuilies. The applications load and execute Shellcode using various API calls or techniques. +`go-shellcode` is a repository of Windows Shellcode runners and supporting utilities. The applications load and execute Shellcode using various API calls or techniques. The available Shellcode runners include: @@ -12,6 +12,7 @@ The available Shellcode runners include: * [CreateThreadNative](#CreateThreadNative) * [RtlCreateUserThread](#RtlCreateUserThread) * [Syscall](#Syscall) +* [Shellcode Utils](#ShellcodeUtils) ## CreateProcess @@ -81,3 +82,91 @@ This application **DOES NOT** leverages functions from the `golang.org/x/sys/win This application executes Shellcode in the current running proccess by making a Syscall on the Shellcode's entry point. This application **DOES NOT** leverages functions from the `golang.org/x/sys/windows` package. The application can be compiled wit the following command on Windows host from the project's root directory: `set GOOS=windows GOARCH=amd64;go build -o Syscall.exe .\cmd\Syscall\main.go` + +## ShellcodeUtils + +This application is used to transform shellcode binary files. The program depends that the input file is a binary file (.bin) that contains the hex bytes of the shellcode. ShellcodeUtils can just base64 encode your input file or it can XOR, RC4, or AES256-GCM encrypt it. The tools can also be used to decrypt files as well. + +ShellcodeUtils help menu: + +```text + -base64 + Base64 encode the output. Can be used with or without encryption + -i string + Input file path of binary file + -key string + Encryption key + -mode string + Mode of operation to perform on the input file [encrypt,decrypt] (default "encrypt") + -nonce string + Nonce, in hex, used to decrypt an AES256 input file. Only used during decryption + -o string + Output file path + -salt string + Salt, in hex, used to generate an AES256 32-byte key through Argon2. Only used during decryption + -type string + The type of encryption to use [xor, aes256, rc4, null] + -v Enable verbose output +``` + +Example of only Base64 encoding the input file and saving it a text file: + +```text +PS C:\Users\bob> .\ShellcodeUtils.exe -i C:\Users\bob\calc.bin -o C:\Users\bob\calc.b64.txt -base64 -v +[-]Output directory: C:\Users\bob\ +[-]Output file name: calc.b64.txt +[-]File contents (hex): 505152535657556a605a6863616c6354594883ec2865488b32488b7618488b761048ad488b30488b7e3003573c8b5c17288b741f204801fe8b541f240fb72c178d5202ad813c0757696e4575ef8b741f1c4801fe8b34ae4801f799ffd74883c4305d5f5e5b5a5958c3 +[-]No encryption type provided, continuing on... +[+]Output (string): +UFFSU1ZXVWpgWmhjYWxjVFlIg+woZUiLMkiLdhhIi3YQSK1IizBIi34wA1c8i1wXKIt0HyBIAf6LVB8kD7csF41SAq2BPAdXaW5Fde+LdB8cSAH+izSuSAH3mf/XSIPEMF1fXltaWVjD +[+] encrypt input and wrote 140 bytes to: C:\Users\bpb\calc.b64.txt +``` + +Example XOR encrypting input file with a key of `Sh3!1z` AND base64 encoding the output: + +```text +PS C:\Users\bob> .\ShellcodeUtils.exe -i C:\Users\bob\calc.bin -o C:\Users\bob\calc.xor.b64.txt -mode encrypt -type xor -key Sh3!1z -v +[-]Output directory: C:\Users\bob\ +[-]Output file name: calc.xor.b64.txt +[-]File contents (hex): 505152535657556a605a6863616c6354594883ec2865488b32488b7618488b761048ad488b30488b7e3003573c8b5c17288b741f204801fe8b541f240fb72c178d5202ad813c0757696e4575ef8b741f1c4801fe8b34ae4801f799ffd74883c4305d5f5e5b5a5958c3 +[-]XOR encrypting input file with key: Sh3!1z +[+]Output (hex): +03396172672d0602537b5919320450756832d0841b4479f16120b8572932d81e23699c32d8587baa4f4a503f0faa6d6d7be3473e11325296b8752e5e5cdf1f36bc2851c5b21d362d3a067654def127772f693084d85c9d69308dca97e469b2be63356c7f6a200a30f0 +[+]xor encrypt input and wrote 105 bytes to: C:\Users\bob\calc.xor.b64.txt +``` + +Example AES256-GCM encrypting the input file with a password of `Sh3!1z` WITHOUT base64 encoding the ouput: + +```text +PS C:\Users\bob> .\ShellcodeUtils.exe -i C:\Users\bob\calc.bin -o C:\Users\bob\calc.aes.bin -mode encrypt -type aes256 -key Sh3!1z -v +[-]Output directory: C:\Users\bob\ +[-]Output file name: calc.aes.bin +[-]File contents (hex): 505152535657556a605a6863616c6354594883ec2865488b32488b7618488b761048ad488b30488b7e3003573c8b5c17288b741f204801fe8b541f240fb72c178d5202ad813c0757696e4575ef8b741f1c4801fe8b34ae4801f799ffd74883c4305d5f5e5b5a5958c3 +[-]AES256 encrypting input file +[+]Argon2 salt (hex): db6126d3ac640f8aaa67cda74b8cf1d2c54513db7bf4fbe3422d1b276af1367e +[+]AES256 key (32-bytes) derived from input password Sh3!1z (hex): 096a40f1aef38dd9b5d63284acc19727c4420dd98f21ea052112bef63eb7d94a +[+]AES256 nonce (hex): 13802153c4b2fb6a3e545ff4 +[+]Output (hex): +44a974233e37b460dc2181b16846f265e8e3a07959abf9c8760f7d0ac8029575e67571ea5b313bc8b011739db57c690ec156a4b0bba4e4d632c35c1490aeaac24f5ae05e90934adf57798ee3c702a3c27073fe976fbcc6ee5db355da186c1add58913e41a8c5716a0fcfc27371f0cae906e50e680366496a00 +[+]aes256 encrypt input and wrote 121 bytes to: C:\Users\bob\calc.aes.bin +``` + +AES256 requires a 32-byte key. This program uses the Argon2 ID algorithm to take the password provided with the `-key` input paramter to derive a 32-byte key while using a randomly generate salt. You will need the same input password and the salt used with the Argon2 algorithm and the same nonce used with the AES256 algorithm to successfull decrypt the file. Alternatively, the decryption function _could_ be updated to just use the 32-byte Argon2 key instead of the input password and salt. + +>**NOTE:** It is up to the operator to decide to just use the generated Argon2 key or to use the password and salt that are used to generate the password. + +Example AES256 decrypting the input file: + +```text +PS C:\Users\bob> .\ShellcodeUtils.exe -i C:\Users\bob\calc.aes.bin -o C:\Users\bob\calc.aes.decrypted.bin -mode decrypt -type aes256 -key Sh3!1z -nonce 13802153c4b2fb6a3e545ff4 -salt db6126d3ac640f8aaa67cda74b8cf1d2c54513db7bf4fbe3422d1b276af1367e -v +[-]Output directory: C:\Users\bob\ +[-]Output file name: calc.aes.decrypted.bin +[-]File contents (hex): 44a974233e37b460dc2181b16846f265e8e3a07959abf9c8760f7d0ac8029575e67571ea5b313bc8b011739db57c690ec156a4b0bba4e4d632c35c1490aeaac24f5ae05e90934adf57798ee3c702a3c27073fe976fbcc6ee5db355da186c1add58913e41a8c5716a0fcfc27371f0cae906e50e680366496a00 +[-]AES256 decrypting input file +[-]Argon2 salt (hex): db6126d3ac640f8aaa67cda74b8cf1d2c54513db7bf4fbe3422d1b276af1367e +[-]AES256 key (hex): 096a40f1aef38dd9b5d63284acc19727c4420dd98f21ea052112bef63eb7d94a +[-]AES256 nonce (hex): 13802153c4b2fb6a3e545ff4 +[+]Output (hex): +505152535657556a605a6863616c6354594883ec2865488b32488b7618488b761048ad488b30488b7e3003573c8b5c17288b741f204801fe8b541f240fb72c178d5202ad813c0757696e4575ef8b741f1c4801fe8b34ae4801f799ffd74883c4305d5f5e5b5a5958c3 +[+]aes256 decrypt input and wrote 105 bytes to: C:\Users\bob\calc.aes.decrypted.bin +``` diff --git a/cmd/ShellcodeUtils/main.go b/cmd/ShellcodeUtils/main.go new file mode 100644 index 0000000..f9be1be --- /dev/null +++ b/cmd/ShellcodeUtils/main.go @@ -0,0 +1,309 @@ +package main + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/rc4" + "encoding/base64" + "encoding/hex" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + + // X Packages + "golang.org/x/crypto/argon2" + + // 3rd Party + "github.com/fatih/color" +) + +func main() { + verbose := flag.Bool("v", false, "Enable verbose output") + encryptionType := flag.String("type", "", "The type of encryption to use [xor, aes256, rc4, null]") + key := flag.String("key", "", "Encryption key") + b64 := flag.Bool("base64", false, "Base64 encode the output. Can be used with or without encryption") + input := flag.String("i", "", "Input file path of binary file") + output := flag.String("o", "", "Output file path") + mode := flag.String("mode", "encrypt", "Mode of operation to perform on the input file [encrypt,decrypt]") + salt := flag.String("salt", "", "Salt, in hex, used to generate an AES256 32-byte key through Argon2. Only used during decryption") + inputNonce := flag.String("nonce", "", "Nonce, in hex, used to decrypt an AES256 input file. Only used during decryption") + flag.Usage = func() { + flag.PrintDefaults() + os.Exit(0) + } + flag.Parse() + + // Check to make sure the input file exists + _, errInputFile := os.Stat(*input) + + if os.IsNotExist(errInputFile) { + color.Red(fmt.Sprintf("[!]The file does not exist: %s", *input)) + os.Exit(1) + } + + shellcode, errShellcode := ioutil.ReadFile(*input) + + if errShellcode != nil { + color.Red(fmt.Sprintf("[!]%s", errShellcode.Error())) + os.Exit(1) + } + + // Check to make sure an output file was provided + if *output == "" { + color.Red("[!]The -o output argument is required") + os.Exit(1) + } + + // Check to make sure the output directory exists + dir, outFile := filepath.Split(*output) + if *verbose { + color.Yellow(fmt.Sprintf("[-]Output directory: %s", dir)) + color.Yellow(fmt.Sprintf("[-]Output file name: %s", outFile)) + } + + outDir, errOutDir := os.Stat(dir) + if errOutDir != nil { + color.Red(fmt.Sprintf("[!]%s", errOutDir.Error())) + os.Exit(1) + } + + if !outDir.IsDir() { + color.Red(fmt.Sprintf("[!]The output directory does not exist: %s", dir)) + } + + if *verbose { + color.Yellow(fmt.Sprintf("[-]File contents (hex): %x", shellcode)) + } + + if strings.ToUpper(*mode) != "ENCRYPT" && strings.ToUpper(*mode) != "DECRYPT" { + color.Red("[!]Invalid mode provided. Must be either encrypt or decrypt") + os.Exit(1) + } + + // Make sure a key was provided + if *encryptionType != "" { + if *key == "" { + color.Red("[!]A key must be provided with the -key parameter to encrypt the input file") + os.Exit(1) + } + } + + var outputBytes []byte + + switch strings.ToUpper(*mode) { + case "ENCRYPT": + var encryptedBytes []byte + switch strings.ToUpper(*encryptionType) { + case "XOR": + // https://kylewbanks.com/blog/xor-encryption-using-go + if *verbose { + color.Yellow(fmt.Sprintf("[-]XOR encrypting input file with key: %s", *key)) + } + encryptedBytes = make([]byte, len(shellcode)) + tempKey := *key + for k, v := range shellcode { + encryptedBytes[k] = v ^ tempKey[k%len(tempKey)] + } + case "AES256": + // https://github.com/gtank/cryptopasta/blob/master/encrypt.go + if *verbose { + color.Yellow("[-]AES256 encrypting input file") + } + + // Generate a salt that is used to generate a 32 byte key with Argon2 + salt := make([]byte, 32) + _, errReadFull := io.ReadFull(rand.Reader, salt) + if errReadFull != nil { + color.Red(fmt.Sprintf("[!]%s", errReadFull.Error())) + os.Exit(1) + } + color.Green(fmt.Sprintf("[+]Argon2 salt (hex): %x", salt)) + + // Generate Argon2 ID key from input password using a randomly generated salt + aesKey := argon2.IDKey([]byte(*key), salt, 1, 64*1024, 4, 32) + // I leave it up to the operator to use the password + salt for decryption or just the Argon2 key + color.Green(fmt.Sprintf("[+]AES256 key (32-bytes) derived from input password %s (hex): %x", *key, aesKey)) + + // Generate AES Cipher Block + cipherBlock, err := aes.NewCipher(aesKey) + if err != nil { + color.Red(fmt.Sprintf("[!]%s", err.Error())) + } + gcm, errGcm := cipher.NewGCM(cipherBlock) + if err != nil { + color.Red(fmt.Sprintf("[!]%s", errGcm.Error())) + os.Exit(1) + } + + // Generate a nonce (or IV) for use with the AES256 function + nonce := make([]byte, gcm.NonceSize()) + _, errNonce := io.ReadFull(rand.Reader, nonce) + if errNonce != nil { + color.Red(fmt.Sprintf("[!]%s", errNonce.Error())) + os.Exit(1) + } + + color.Green(fmt.Sprintf("[+]AES256 nonce (hex): %x", nonce)) + + encryptedBytes = gcm.Seal(nil, nonce, shellcode, nil) + case "RC4": + if *verbose { + color.Yellow("[-]RC4 encrypting input file") + } + cipher, err := rc4.NewCipher([]byte(*key)) + if err != nil { + color.Red(fmt.Sprintf("[!]%s", err.Error())) + os.Exit(1) + } + encryptedBytes = make([]byte, len(shellcode)) + cipher.XORKeyStream(encryptedBytes, shellcode) + case "": + if *verbose { + color.Yellow("[-]No encryption type provided, continuing on...") + } + encryptedBytes = append(encryptedBytes, shellcode...) + default: + color.Red(fmt.Sprintf("[!]Invalid method type: %s", *encryptionType)) + os.Exit(1) + } + + if len(encryptedBytes) <= 0 { + color.Red("[!]Encrypted byte slice length is equal to or less than 0") + os.Exit(1) + } + if *b64 { + outputBytes = make([]byte, base64.StdEncoding.EncodedLen(len(encryptedBytes))) + base64.StdEncoding.Encode(outputBytes, encryptedBytes) + } else { + outputBytes = append(outputBytes, encryptedBytes...) + } + case "DECRYPT": + var decryptedBytes []byte + switch strings.ToUpper(*encryptionType) { + case "AES256": + // https://github.com/gtank/cryptopasta/blob/master/encrypt.go + if *verbose { + color.Yellow("[-]AES256 decrypting input file") + } + // I leave it up to the operator to use the password + salt for decryption or just the Argon2 key + if *salt == "" { + color.Red("[!]A 32-byte salt in hex format must be provided with the -salt argument to decrypt AES256 input file") + os.Exit(1) + } + if len(*salt) != 64 { + color.Red("[!]A 32-byte salt in hex format must be provided with the -salt argument to decrypt AES256 input file") + color.Red(fmt.Sprintf("[!]A %d byte salt was provided", len(*salt)/2)) + os.Exit(1) + } + + saltDecoded, errSaltDecoded := hex.DecodeString(*salt) + if errShellcode != nil { + color.Red(fmt.Sprintf("[!]%s", errSaltDecoded.Error())) + os.Exit(1) + } + if *verbose { + color.Yellow("[-]Argon2 salt (hex): %x", saltDecoded) + } + + aesKey := argon2.IDKey([]byte(*key), saltDecoded, 1, 64*1024, 4, 32) + if *verbose { + color.Yellow("[-]AES256 key (hex): %x", aesKey) + } + + cipherBlock, err := aes.NewCipher(aesKey) + if err != nil { + color.Red(fmt.Sprintf("[!]%s", err.Error())) + } + + gcm, errGcm := cipher.NewGCM(cipherBlock) + if err != nil { + color.Red(fmt.Sprintf("[!]%s", errGcm.Error())) + os.Exit(1) + } + + if len(shellcode) < gcm.NonceSize() { + color.Red("[!]Malformed ciphertext is larger than nonce") + os.Exit(1) + } + + if len(*inputNonce) != gcm.NonceSize()*2 { + color.Red("[!]A nonce, in hex, must be provided with the -nonce argument to decrypt the AES256 input file") + color.Red(fmt.Sprintf("[!]A %d byte nonce was provided but %d byte nonce was expected", len(*inputNonce)/2, gcm.NonceSize())) + os.Exit(1) + } + decryptNonce, errDecryptNonce := hex.DecodeString(*inputNonce) + if errDecryptNonce != nil { + color.Red("[!]%s", errDecryptNonce.Error()) + os.Exit(1) + } + if *verbose { + color.Yellow(fmt.Sprintf("[-]AES256 nonce (hex): %x", decryptNonce)) + } + + var errDecryptedBytes error + decryptedBytes, errDecryptedBytes = gcm.Open(nil, decryptNonce, shellcode, nil) + if errDecryptedBytes != nil { + color.Red("[!]%s", errDecryptedBytes.Error()) + os.Exit(1) + } + case "XOR": + // https://kylewbanks.com/blog/xor-encryption-using-go + if *verbose { + color.Yellow(fmt.Sprintf("[-]XOR decrypting input file with key: %s", *key)) + } + decryptedBytes = make([]byte, len(shellcode)) + tempKey := *key + for k, v := range shellcode { + decryptedBytes[k] = v ^ tempKey[k%len(tempKey)] + } + case "RC4": + if *verbose { + color.Yellow("[-]RC4 decrypting input file") + } + cipher, err := rc4.NewCipher([]byte(*key)) + if err != nil { + color.Red(fmt.Sprintf("[!]%s", err.Error())) + os.Exit(1) + } + decryptedBytes = make([]byte, len(shellcode)) + cipher.XORKeyStream(decryptedBytes, shellcode) + default: + color.Red("[!]Invalid method") + os.Exit(1) + } + if len(decryptedBytes) <= 0 { + color.Red("[!]Decrypted byte slice length is equal to or less than 0") + os.Exit(1) + } + if *b64 { + outputBytes = make([]byte, base64.StdEncoding.EncodedLen(len(decryptedBytes))) + base64.StdEncoding.Encode(outputBytes, decryptedBytes) + } else { + outputBytes = append(outputBytes, decryptedBytes...) + } + } + + if *verbose { + if *b64 { + color.Green("[+]Output (string):\r\n") + fmt.Println(fmt.Sprintf("%s", outputBytes)) + } else { + color.Green("[+]Output (hex):\r\n") + fmt.Println(fmt.Sprintf("%x", outputBytes)) + } + } + + // Write the file + err := ioutil.WriteFile(*output, outputBytes, 0660) + if err != nil { + color.Red(fmt.Sprintf("[!]%s", err.Error())) + os.Exit(1) + } + color.Green(fmt.Sprintf("[+]%s %s input and wrote %d bytes to: %s", *encryptionType, *mode, len(outputBytes), *output)) + +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1bc2fc0 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/Ne0nd0g/go-shellcode + +go 1.14 + +require ( + github.com/fatih/color v1.9.0 + golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..dda244e --- /dev/null +++ b/go.sum @@ -0,0 +1,17 @@ +github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc h1:ZGI/fILM2+ueot/UixBSoj9188jCAxVHEZEGhqq67I4= +golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=