Introducing Merlin — A cross-platform post-exploitation HTTP/2 Command & Control Tool
tl;dr Evade network detection during a penetration test/red team exercise by using a protocol that existing tools aren’t equipped to understand or inspect. Merlin is post-exploitation tool that is easily cross-compiled to run on any platform to achieve command and control of a host.
Merlin is available on GitHub: https://github.com/Ne0nd0g/merlin
HTTP/2 is a newly ratified protocol documented under RFC 7540 that aims to solve some of the problems with HTTP/1.x and provide functionality to support current web application operations. HTTP/2 communications are multiplexed, bi-direction connections that do not end after one request and response. Additionally, HTTP/2 is a binary protocol that makes it more compact, easy to parse, and not human readable without the use of an interpreting tool.
An HTTP/2 connection can be setup by upgrading a HTTP/1.x connection using the `Upgrade` header or during the negotiation of a TLS encrypted channel. Application-Layer Protocol Negotiation (ALPN) is a TLS 1.2 extension that is required to setup a HTTP/2 connection identified with the `h2` protocol string. TLS versions less than 1.2 are not equipped to negotiate a HTTP/2 connection. Oddly enough, the client will perform one final check to ensure that the server can speak HTTP/2 by sending the string PRISM. This reminds me of the NSA PRISM program.
The HTTP/2 RFC also requires the use of Perfect Forward Secrecy (PFS) cipher suites and recommends that all non-PFS-enable cipher suites are black listed. The RFC reads: “An HTTP/2 implementation MAY treat the negotiation of any of the following cipher suites with TLS 1.2 as a connection error of type INADEQUATE_SECURITY”. The list of adequate cipher suites are comprised of Elliptic Curve Diffie-Helman Exchange (ECDHE) and ephemeral Diffie-Helman Exchange (DHE) key exchange methods that benefit from PFS. When PFS cipher suites are used, captured traffic cannot be decrypted using only the server’s private key. In order to decrypt the traffic, the client’s session keying information is required. If you don’t control the client or you’re not using a client built on the NSS library (i.e. Firefox or cURL), this information can be hard to obtain.
HTTP/2 communications are expected to take place over TLS encrypted channels using PFS enabled cipher suites. Therefore, inspecting HTTP/2 traffic proves difficult. Another obstacle is that currently available WAF/IDS/IPS solutions are incapable of understanding the HTTP/2 protocol even if they were able to decrypt traffic for inspection. This combination of encryption and the lack of protocol support from inspecting tools, provide a great opportunity to evade inspection. Some possible solutions are to terminate HTTP/2 connections and downgrade them to HTTP/1.1, but that would remove all of the efficiencies gained with using HTTP/2. Additional options include downgrading the encryption to a non-PFS enable cipher suite or use a terminating proxy.
You can find additional information on HTTP/2 in a paper I wrote titled Practical Approach to Detecting and Preventing Web Application Attacks over HTTP/2.
I wanted to write a program that could be used during penetration testing to take advantage of the HTTP/2 protocol. I started looking at what languages had good HTTP/2 libraries. I wanted to stick with PowerShell so that I could create an in-memory agent. At the time of research, .Net didn’t support TLS 1.2. When TLS 1.2 support did come around, it still didn’t support ALPN, a requirement for establishing a HTTP/2 connection. After evaluating several libraries, I rested on Go programing language. This worked well because I also wanted to learn how to program in this language and I was hoping anti-virus solutions had a harder time working with it too.
Merlin is composed of two parts, the server and the agent. Due to the power of Go, both components can be compiled to run on any platform or can be run “like” a script. A server compiled to run on Linux, or any platform, can handle agents compiled for all other platforms. It recommended to download the pre-compiled binaries found under the “Releases” tab on GitHub.
The Merlin Server component should be run in a location where all agents can reach it. The interface and port Merlin Server listens on can be configured using command line flags, but defaults to interface 0.0.0.0 and port 443. By default, the server utilizes the x.509 certificates shipped with the program using the hard coded relative path of `data/x509/server.crt` and `data/x509/server.key`. I strong recommend that you generate your own x.509 key pair to replace these. The x.509 certificates can also be specified with command line options. Detailed information on how to interact with a server can be found on the Merlin Wiki.
Here is an image of starting the Merlin Server and subsequently having three new agents check in:
Merlin Server is enabled with tab completion. This is useful when you don’t know what command to type next. Just hit the tab key and all available options will show.
All agents are given a Universally Unique Identifier (UUID). In order to execute commands on a specific agent, the user must type in this identifier. Before you get too excited about typing in a really long unique identifier, just know that tab completion works here too. This means you only have to type enough unique characters and then hit tab to auto complete the rest of the agent identifier.
The Merlin Agent can be cross-compiled to run on any platform. The images above show agents checking in for Windows, Linux, and MacOS (Darwin). Agents can be compiled with a hard-coded string to connect to Merlin Server or the Merlin Server’s address can be specified on the command line with the `-url` flag (i.e. -url https://acme.com:443/). By default, Merlin Agent connects to https://127.0.0.1:443/. Interacting with the agents is done via the Merlin Server. Detailed information on how to interact with an agent can be found on the GitHub Wiki page.
You can execute any binary file that is with the target system’s PATH variable. This means that on a Windows agent, command can be run with cmd.exe or powershell.exe, and are not limited to one or the other. Here is an example of executing a PowerShell command on a Windows host:
You can execute Python commands on a Linux host as well:
Here is a peek at the /etc/passwd file on a Mac:
Agents have a number of other configurations such as sleep time, max retries, kill, and padding that can be adjusted from the server. The padding setting is used in an attempt to evade detection by ensuring message traffic is not same size. A random value is selected between 0 and the value of the Message Padding Max is selected and appended to the end of every message. If for some reason the server is not available, the agent will continue to try and check in until the Agent Max Retries value is reached and then it will exit. If the agent was communicating with the server and then it suddenly becomes unavailable, agents will be considered orphaned. The orphaned agents will be re-initialized once the server is available again provided they haven’t exceeded the Agent Max Retries value:
Merlin is a cross-platform post-exploitation framework that leverages HTTP/2 communications to evade inspection. HTTP/2 is a relatively new protocol that requests Perfect Forward Secrecy (PFS) encryption cipher suites are used. The use of these cipher suites makes it incredibly difficult to capture all of the keying material required to decrypt traffic for inspection. Additionally, many security technologies are not equipped with HTTP/2 protocol dissectors and are therefore not able to evaluate traffic even if keying material is provided. The magic of Merlin is found in its HTTP/2 protocol coupled with the use of the Go programming language and its easy to use cross-compiling capabilities.
More information about Merlin can be found on its GitHub page at https://github.com/Ne0nd0g/merlin. Be sure to view the README and to visit the Wiki. I’m always open to feedback. Keep in mind that Merlin is still in beta while it goes through some real-world testing scenarios over time. Be sure to open an issue for any bugs that you may find.