Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
Ne0nd0g committed Jul 30, 2021
1 parent 54bda16 commit 0e4da8f
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 53 deletions.
2 changes: 2 additions & 0 deletions docs/CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- `shinject` for `execute-shellcode`
- `ipconfig` for `ifconfig`
- `exec` for `run`
- Added `jobs` command to main menu to show jobs for all agents
- Added `queue` command to main menu to queue up jobs for one agent, all agents, or an agent that hasn't registered yet

### Changed

Expand Down
5 changes: 5 additions & 0 deletions pkg/api/agents/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,11 @@ func GetAgentStatus(agentID uuid.UUID) (string, messages.UserMessage) {
return status, messages.UserMessage{}
}

// GetJobs enumerates all created (but unsent) jobs across all agents
func GetJobs() [][]string {
return jobs.GetTableAll()
}

// GetJobsForAgent enumerates all jobs and their status
func GetJobsForAgent(agentID uuid.UUID) ([][]string, messages.UserMessage) {
jobsRows, err := jobs.GetTableActive(agentID)
Expand Down
50 changes: 49 additions & 1 deletion pkg/cli/menu/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package menu
import (
// Standard
"fmt"
uuid "github.com/satori/go.uuid"
"os"
"strings"
"time"
Expand Down Expand Up @@ -79,8 +80,35 @@ func handlerMain(cmd []string) {
if len(cmd) > 1 {
interactAgent(cmd[1])
}
case "jobs":
displayAllJobTable(agentAPI.GetJobs())
case "listeners":
Set(LISTENERS)
case "queue":
if len(cmd) > 2 {
if cmd[1] == "all" {
cmd[1] = "ffffffff-ffff-ffff-ffff-ffffffffffff"
}
id, err := uuid.FromString(cmd[1])
if err != nil {
core.MessageChannel <- messages.UserMessage{
Level: messages.Warn,
Message: "Invalid uuid",
Time: time.Now().UTC(),
Error: true,
}
} else {
agent = id
handlerAgent(cmd[2:])
}
} else {
core.MessageChannel <- messages.UserMessage{
Level: messages.Warn,
Message: "Not enough arguments provided",
Time: time.Now().UTC(),
Error: true,
}
}
case "remove":
if len(cmd) > 1 {
removeAgent(cmd[1])
Expand Down Expand Up @@ -163,7 +191,12 @@ func completerMain() *readline.PrefixCompleter {
readline.PcItem("interact",
readline.PcItemDynamic(agentListCompleter()),
),
readline.PcItem("jobs"),
readline.PcItem("listeners"),
readline.PcItem("queue",
readline.PcItem("all"),
readline.PcItemDynamic(agentListCompleter()),
),
readline.PcItem("quit"),
readline.PcItem("remove",
readline.PcItemDynamic(agentListCompleter()),
Expand Down Expand Up @@ -229,8 +262,10 @@ func helpMain() {
data := [][]string{
{"agent", "Interact with agents or list agents", "interact, list"},
{"banner", "Print the Merlin banner", ""},
{"listeners", "Move to the listeners menu", ""},
{"interact", "Interact with an agent", ""},
{"jobs", "Display all unfinished jobs", ""},
{"listeners", "Move to the listeners menu", ""},
{"queue", "queue up commands for one, all, or unknown agents", "queue <agentID> <command>"},
{"quit", "Exit and close the Merlin server", ""},
{"remove", "Remove or delete a DEAD agent from the server"},
{"sessions", "List all agents session information", ""},
Expand All @@ -250,3 +285,16 @@ func helpMain() {
Error: false,
}
}

// displayAllJobTable displays a table of agent jobs along with their status
func displayAllJobTable(rows [][]string) {
table := tablewriter.NewWriter(os.Stdout)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetBorder(false)
table.SetHeader([]string{"Agent", "ID", "Command", "Status", "Created", "Sent"})

table.AppendBulk(rows)
fmt.Println()
table.Render()
fmt.Println()
}
140 changes: 88 additions & 52 deletions pkg/server/jobs/jobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ func Add(agentID uuid.UUID, jobType string, jobArgs []string) (string, error) {
}

agent, ok := agents.Agents[agentID]
if !ok {
return "", fmt.Errorf("%s is not a valid agent", agentID)
}
//if !ok {
// return "", fmt.Errorf("%s is not a valid agent", agentID)
//}

var job merlinJob.Job

Expand All @@ -84,7 +84,9 @@ func Add(agentID uuid.UUID, jobType string, jobArgs []string) (string, error) {
}
case "download":
job.Type = merlinJob.FILETRANSFER
agent.Log(fmt.Sprintf("Downloading file from agent at %s\n", jobArgs[0]))
if ok {
agent.Log(fmt.Sprintf("Downloading file from agent at %s\n", jobArgs[0]))
}

p := merlinJob.FileTransfer{
FileLocation: jobArgs[0],
Expand Down Expand Up @@ -177,7 +179,9 @@ func Add(agentID uuid.UUID, jobType string, jobArgs []string) (string, error) {
if err != nil {
message("warn", fmt.Sprintf("there was an error generating a file hash:\n%s", err))
}
agent.Log(fmt.Sprintf("loading assembly from %s with a SHA256: %s to agent", jobArgs[0], fileHash.Sum(nil)))
if ok {
agent.Log(fmt.Sprintf("loading assembly from %s with a SHA256: %s to agent", jobArgs[0], fileHash.Sum(nil)))
}

name := filepath.Base(jobArgs[0])
if len(jobArgs) > 1 {
Expand Down Expand Up @@ -334,11 +338,13 @@ func Add(agentID uuid.UUID, jobType string, jobArgs []string) (string, error) {
if err != nil {
message("warn", fmt.Sprintf("There was an error generating file hash:\r\n%s", err.Error()))
}
agent.Log(fmt.Sprintf("Uploading file from server at %s of size %d bytes and SHA-256: %x to agent at %s",
jobArgs[0],
len(uploadFile),
fileHash.Sum(nil),
jobArgs[1]))
if ok {
agent.Log(fmt.Sprintf("Uploading file from server at %s of size %d bytes and SHA-256: %x to agent at %s",
jobArgs[0],
len(uploadFile),
fileHash.Sum(nil),
jobArgs[1]))
}

p := merlinJob.FileTransfer{
FileLocation: jobArgs[1],
Expand All @@ -350,57 +356,49 @@ func Add(agentID uuid.UUID, jobType string, jobArgs []string) (string, error) {
return "", fmt.Errorf("invalid job type: %d", job.Type)
}

var cmd string
if job.Type == merlinJob.CMD || job.Type == merlinJob.CONTROL {
cmd = strings.Join(jobArgs, " ")
} else {
cmd = jobType + " " + strings.Join(jobArgs, " ")
}

// If the Agent is set to broadcast identifier for ALL agents
if ok || agentID.String() == "ffffffff-ffff-ffff-ffff-ffffffffffff" {
if agentID.String() == "ffffffff-ffff-ffff-ffff-ffffffffffff" {
if len(agents.Agents) <= 0 {
return "", fmt.Errorf("there are 0 available agents, no jobs were created")
if agentID.String() == "ffffffff-ffff-ffff-ffff-ffffffffffff" {
if len(agents.Agents) <= 0 {
return "", fmt.Errorf("there are 0 available agents, no jobs were created")
}
for a := range agents.Agents {
// Fill out remaining job fields
token := uuid.NewV4()
job.ID = core.RandStringBytesMaskImprSrc(10)
job.Token = token
job.AgentID = a
// Add job to the channel
_, k := JobsChannel[agentID]
if !k {
JobsChannel[agentID] = make(chan merlinJob.Job, 100)
}
for a := range agents.Agents {
// Fill out remaining job fields
token := uuid.NewV4()
job.ID = core.RandStringBytesMaskImprSrc(10)
job.Token = token
job.AgentID = a
// Add job to the channel
_, k := JobsChannel[agentID]
if !k {
JobsChannel[agentID] = make(chan merlinJob.Job, 100)
}
JobsChannel[agentID] <- job
//agents.Agents[a].JobChannel <- job
// Add job to the list
Jobs[job.ID] = info{
AgentID: a,
Token: token,
Type: merlinJob.String(job.Type),
Status: merlinJob.CREATED,
Created: time.Now().UTC(),
Command: cmd,
}
// Log the job
JobsChannel[agentID] <- job
//agents.Agents[a].JobChannel <- job
// Add job to the list
Jobs[job.ID] = info{
AgentID: a,
Token: token,
Type: merlinJob.String(job.Type),
Status: merlinJob.CREATED,
Created: time.Now().UTC(),
Command: jobType + " " + strings.Join(jobArgs, " "),
}
// Log the job
if ok {
agent.Log(fmt.Sprintf("Created job Type:%s, ID:%s, Status:%s, Args:%s",
messages.String(job.Type),
job.ID,
"Created",
jobArgs))
}
return job.ID, nil
}
} else {
// A single Agent
token := uuid.NewV4()
job.Token = token
job.ID = core.RandStringBytesMaskImprSrc(10)
job.AgentID = agentID
// Add job to the channel
//agents.Agents[agentID].JobChannel <- job
_, k := JobsChannel[agentID]
if !k {
JobsChannel[agentID] = make(chan merlinJob.Job, 100)
Expand All @@ -413,14 +411,17 @@ func Add(agentID uuid.UUID, jobType string, jobArgs []string) (string, error) {
Type: merlinJob.String(job.Type),
Status: merlinJob.CREATED,
Created: time.Now().UTC(),
Command: cmd,
Command: jobType + " " + strings.Join(jobArgs, " "),
}
// Log the job
agent.Log(fmt.Sprintf("Created job Type:%s, ID:%s, Status:%s, Args:%s",
messages.String(job.Type),
job.ID,
"Created",
jobArgs))
if ok {
agent.Log(fmt.Sprintf("Created job Type:%s, ID:%s, Status:%s, Args:%s",
messages.String(job.Type),
job.ID,
"Created",
jobArgs))
}

}
return job.ID, nil
}
Expand Down Expand Up @@ -699,6 +700,41 @@ func GetTableActive(agentID uuid.UUID) ([][]string, error) {
return jobs, nil
}

// GetTableAll returns all unsent jobs to be displayed as a table
func GetTableAll() [][]string {
var jobs [][]string
for id, job := range Jobs {
var status string
switch job.Status {
case merlinJob.CREATED:
status = "Created"
case merlinJob.SENT:
status = "Sent"
case merlinJob.RETURNED:
status = "Returned"
default:
status = fmt.Sprintf("Unknown job status: %d", job.Status)
}
if job.Status != merlinJob.COMPLETE && job.Status != merlinJob.CANCELED {
var zeroTime time.Time
var sent string
if job.Sent != zeroTime {
sent = job.Sent.Format(time.RFC3339)
}

jobs = append(jobs, []string{
job.AgentID.String(),
id,
job.Command,
status,
job.Created.Format(time.RFC3339),
sent,
})
}
}
return jobs
}

// checkJob verifies that the input job message contains the expected token and was not already completed
func checkJob(job merlinJob.Job) error {
// Check to make sure agent UUID is in dataset
Expand Down

0 comments on commit 0e4da8f

Please sign in to comment.