Skip to content

End-to-end Encrypted DNS Tunnelling and C2 framework

License

Notifications You must be signed in to change notification settings

mosajjal/dnspot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

63 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

dnspot

End-to-end Encrypted DNS Tunneling and C2 framework

dnspot

Motivation

I was always fascinated by Thinkst's canary technology. Thinkst canaries exclusively use DNS tunneling to act as C2 and push configuration. I wanted to build something similar. In the meantime, I had to do more work on DNS since it's one of my favorites.

I'm hoping to later on evolve this into a Deception framework, especially in areas that opening a fw rule for your canary is not an optimal solution.

Note that this project is in alpha stage. Crashes, bugs and sometimes duplicate actions are expected. PRs and contributions are more than welcome. If you want to see where to start, do a find on //todo:, you'll find a bunch of them!

Supported platforms

Right now the aim is to make it work for Linux/*NIX. Honestly don't see the "good guy" point of having this on Windows, so not really keen to have a Win implementation of the agent itself. for server, I think Windows can work. Haven't tested it though.

Can't see why it wouldn't run on Mac, Rpi, etc since the libraries are not CGO dependent. Test it and let me know so I can add it to supported platforms :)

Architecture

dnspot uses a simple implementation of NIST's P-256 elliptic curve + SHA-256 to transfer data securely over an A query. The payload is encoded using an implementation of base36, to ensure case sensitivity is not an issue while in transit. The responses from the C2 to each agent is a CNAME response with the same algorithm, so a lot of underlying C2 and Cryptography functions are shared between the agent and the server. A custom transport protocol has been implemented to ensure the larger payload's delivery. Before encryption, each packet looks like this:

type MessagePacket struct {
	TimeStamp     uint32
	PartID        PartID
	ParentPartID  PartID
	IsLastPart    bool
	MessageType   MsgType
	Command       CmdType
	PayloadLength uint8
	Payload       []byte
}

If there's a large command or response, this message structure allows it to be split into multiple queries, while maintaining the cryptographic integrity of each packet, since the signature and encryption will happen on top of MessagePacket, meaning all the MessagePacket will be encrypted and signed, not just the payload. Right now, PAYLOAD_SIZE is 80 bytes, which gives the user the ability to have up to ~30 character DNS suffix and the system still works.

The server includes a TUI interface to see the list of agents beaconing to the server:

Server TUI sample

By default, the beacon (which I call healthcheck), will happen every 10 seconds. Each 60 seconds, there's a clean-up job on the server side to remove idle agents. After the agent registers itself with the server, the server can send commands to it. the server put the agent in runcommand mode, and will respond to the next healthcheck with a runcommand payload, and proceeds with sending the payload to the agent. After the payload is delivered, the agent will run the command. For security and stability purposes, there's a 2 second timeout for each command's run, so any accidental ping command won't send the agent to abyss :) after the command has finished, the stdout and stderr of the command is captured and sent to the server via runcommandres message type. after both these transactions are finished, both the server and the agent will go back to healthcheck mode and can run a different command if need be.

The project's long-term purpose is to remove the need for a UI, focusing more on building a framework that can be imported and used in any other app, to act as a messaging protocol over DNS.

Getting Started

Get a (sub)domain

In order to make this work over the internet, you need to have a NS record pointing to your internet-accessible DNS server (a Digitalocean VPS for example) with port 53 open. If you're dedicating a full TLD to this, you need to go to your registrar's administration page, and change your DNS servers and make them point to your VPS. If you want to use the sub-domain method, you need to go to your DNS administration (bind configuration or your cloud DNS provider's web admin page) and add a NS record, that points the subdomain you'd like to your VPS. For example, if your VPS's public IP is 1.1.1.1 and your domain is example.com, and you'd like to have c2.example.com as your C2 suffix, you need the following record:

NS c2.example.com   1.1.1.1

Depending on your provider, this might be different. For example, Cloudflare only allows FQDNs to be set as NS record's values, so you might need to add another A record to point to your VPS IP and your NS record to point to the A record, like so:

A myns.example.com  1.1.1.1
NS c2.example.com   myns.example.com

Download the binaries

from the releases page, you have some options to download the binary. For the agent, there's dnspot-agent-cli-* and for the server, there are dnspot-server-tui-* and dnspot-server-cli-*. Download the appropiate version for your OS (Linux and FreeBSD supported), and place the server binary where the NS record is pointing at.

Now, we can start configuring the server.

Server

Generate Keys

This part should be easy. Run the following:

$ dnspot-server-cli generate
public  key: eg0he2d9cr3hpvt3z76trvkl7n2bivgscpwd5xfgn0oqgqlq00
private key: 5slcifus86ojqs51ubctih0p35izi3x6ynyo2q2pnqs7syvnqa

You need to run this once for the server, and once for each agent you're planning to run.

Run the Server

For running the server, you must provide a suffix and a privatekey. Everything else is optional. Keep in mind that if you don't use the enforceClientKeys option, anyone that has your server's public key, can register themselves as an agent. If you're using enforceClientKeys, acceptedClientKeys becomes mandatory. You can also provide a logFile address to save the output persistently. Otherwise it's all gone when you press "Ctrl+C".

Usage:
  server [arguments] [flags]
  server [command]

Available Commands:
  completion  Generate the autocompletion script for the specified shell
  generate    generate a pair of keys randomly
  help        Help about any command

Flags:
      --acceptedClientKeys strings   Accepted Client Keys
      --dnsSuffix string             Subdomain that serves the domain, please note the dot at the beginning and the end (default ".example.com.")
      --enforceClientKeys            Enforce client keys. Need to provide a list of accepted public keys if set to true
  -h, --help                         help for server
      --listenAddress string         Listen Socket (default "0.0.0.0:53")
      --logFile string               Log to file. stderr is used when not provided. Optional
      --logLevel uint8               Log level. Panic:0, Fatal:1, Error:2, Warn:3, Info:4, Debug:5, Trace:6 (default 1)
      --mode string                  Run mode. choices: exec, chat (default "exec")
      --outFile string               Output File to record only the commands and their responses
      --privateKey string            Private Key used

Run the Agent(s)

To run each agent, you must provide a suffix, and the server's public key. By default, the agent reads /etc/resolve.conf, and will leverage the first DNS server and its DNS server address, but you can provide another serverAddress in the socket format: 1.1.1.1:53. DoT, DoH and DoQ are not (yet) supported.

Usage:
  agent [arguments] [flags]
  agent [command]

Available Commands:
  completion  Generate the autocompletion script for the specified shell
  generate    generate a pair of keys randomly
  help        Help about any command

Flags:
      --dnsSuffix string         Subdomain that serves the domain, please note the dot at the beginning and the end (default ".example.com.")
  -h, --help                     help for agent
      --logLevel uint8           log level. Panic:0, Fatal:1, Error:2, Warn:3, Info:4, Debug:5, Trace:6 (default 1)
      --privateKey string        Private Key used. Generates one on the fly if empty
      --serverAddress string     DNS Server to use. You can specify custom port here. Leave blank to use system's DNS server
      --serverPublicKey string   Server's public Key
      --timeout duration         Timeout for command execution (default 2s)``

at this stage, the agent should be able to beacon to the server, and will await commands. back at the server, you have a prompt to issue commands on an agent. After typing out the command you will be presented with a popup menu to pick an agent to run the command on. let's look at some screenshots. Top is the server, bottom is the agent connecting to it

configuration before executing the server and the agent CLI 1 issue a command from the server and choose the agent to run it on CLI 2 the response from the agent is shown in the server's CLI CLI 3

Profit

The agent should instantly show up in Server's TUI, identified by its public key. You can then select an agent from the left panel (by clicking on it), and type the desired command and wait for the output to show up.

Couple of notes:

  • Source IP is pretty much useless in DNS, so dnspot discards it immediately, and focuses on public key of each agent.
  • Command's execution is done after the next healthcheck, so the interactive shell feel is not there. However, there are plans to make the healthcheck interval dynamic, which makes it possible for us to build an interactive shell if need be.
  • The project does NOT aim to be discrete. It does NOT aim to be covert. The last thing I want is giving the attackers another tool. It's intended to be noisy with 200+ chars A queries and CNAME responses, so any IDS/IPS/EDR can easily detect it.

TODO

As mentioned above, dnspot is buggy. Polishing the source and framework is the main goal. There are a bunch of duplicate code and functions need to be fixed. There are also a lot of "static" numbers I put in the code, that need to be extracted and at least be a list of const values in the config, rather than scattered within the code.

There's also a plan to implement file transfer both from server to agent and vice-versa. I'm thinking of leveraging a high-ratio compression algorithm to make it different than cat'ing a file to the agent. ideas are welcome!

  • File transfer
  • better configuration
  • TUI improvements and different command options
  • Agent watchdog to reset agent on failure or error rather than crash and die
  • DoT and DoH support
  • Decouple framework and UI to make it usable outside the project
  • Standard API and documentation
  • Possible Web UI for server
  • Alternative Query and response types (TXT, ANY, etc)

About

End-to-end Encrypted DNS Tunnelling and C2 framework

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Packages

No packages published

Languages