
PowerShellとNslookupを利用したDNSトンネリングツール。DNS TXT/MXレコードを介してデータを外部に送信し、ペイロードを配信することで、Constrained Language Modeとエンドポイント防御を回避します。
Inspired by recent work I did involving Cobalt Strike DNS beacons, in conjunction with a mission statement to try and evade Microsoft Defender for Endpoint, I spent some time looking into how DNS might be used to transfer a payload to a target machine. I further wanted to challenge myself by trying to do so in a way that is possible even when powershell is in Constrained Language Mode. This research was targeted at more modern implementations of Windows (i.e. Win10+, Server 2019+) but as you see later it may be possible in lower versions.
DNS tunneling is a technique that has been around for a long time and used by a variety of attackers. At a basic level it involves using the DNS protocol as a means for data infiltration/exfiltration or as a C2 communications channel. There are many blog posts you can reference for more information on this topic.
Because this is such a old and well known technique, many organizations have detection methods in place to try and prevent it.
The DNS record type of choice for DNS tunneling has historically been TXT. This is because TXT records can hold more data than other records and they are also case-sensitive, something that the other records are not which can have an impact when we start talking about encoding.
Constrained Language Mode (CLM) is a restrictive language mode for Powershell which greatly reduces the capabilities and allowed functionality of Powershell. As a short list, .NET, COM objects, and attacker favorites like (new-object net.webclient).downloadstring... are unavailable. This link provides more information. Organizations will put this policy in force for normal users as part of attack surface reduction rules. It in effect just makes our lives harder as attackers.
Most should be at least cursorily familiar with DNS from use of tools like Nslookup. But at a basic level, the client sends a query and the DNS server returns an answer to that query. There are several different kinds of DNS records: CNAME, A, AAAA, TXT, MX, and NS just to name a few. Each of these records can store and return different information. These records are configured in a Zonefile, which is served by a DNS server.
An example zonefile is shown here:``` $ORIGIN example.com. @ 3600 SOA ns1.p30.dynect.net. ( zone-admin.dyndns.com. ; address of responsible party 2016072701 ; serial number 3600 ; refresh period 600 ; retry period 604800 ; expire time 1800 ) ; minimum ttl 86400 NS ns1.p30.dynect.net. 86400 NS ns2.p30.dynect.net. 86400 NS ns3.p30.dynect.net. 86400 NS ns4.p30.dynect.net. 3600 MX 10 mail.example.com. 3600 MX 20 vpn.example.com. 3600 MX 30 mail.example.com. 60 A 204.13.248.106 3600 TXT "v=spf1 includespf.dynect.net ~all" mail 14400 A 204.13.248.106 vpn 60 A 216.146.45.240 webapp 60 A 216.146.46.10 webapp 60 A 216.146.46.11 www 43200 CNAME example.com.
example.comのNSレコードをクエリすると、ns1.p30.dynect.net、ns2.p30.dynect.net、ns3.p30.dynect.net、ns4.p30.dynect.netが返される。
# 研究
## ドメイン名登録
始める前に、DNSレコードを設定して、自分が制御しDNSサーバーを実行するIPを指定する方法について簡単に説明する必要がある。以下に示すように、ドメインを購入し、サブドメイン「dns」をパブリックIPが割り当てられた「ns1」サブドメインにポイントするDNSレコードを設定した。

つまり、「dns.edu....com」に対するクエリはすべて、IP 3..86が割り当てられた「ns1.edu....com」に転送される。そのIP上にDNSサーバーをセットアップし、レコードを提供する。これは後で再び登場する。
## クライアントサイドツールの検索
私の探求は「powershell dns module」という単純なGoogle検索から始まり、[この](https://docs.microsoft.com/en-us/powershell/module/dnsclient/?view=windowsserver2022-ps)リンクが返された。特に興味深かったのはResolve-DnsNameコマンドだ。これは基本的に、有名なNslookup.exeバイナリのPowerShell実装であるように見える。特定のタイプのレコードを要求できることに注意:

さて、DNSクエリを実行し回答を取得できるPowerShellモジュールがある。Constrained Language Modeでも動作するだろうか?答えは「まあまあ」だ。
ここでわかるように、新しいPowerShellウィンドウを開き、Resolve-DnsNameを実行し、PowerShellをCLMに設定し(単純な::WriteLine呼び出しでテスト)、その後再度Resolve-DnsNameを実行すると、問題なく動作する:

しかし、新しいPowerShellウィンドウを開き、すぐにCLMに設定してからResolve-DnsNameを実行しようとすると失敗する:

モジュールが事前にロードされていれば、CLMが適用された後でも実行できるが、CLMはまだロードされていないモジュールのロードを妨げるようだ。ユーザーに対してデフォルトでCLMが適用されているターゲット環境(また、特定のモジュールが事前にロードされているかどうか、DnsClientがその1つかどうかがわからない状態)を考慮すると、この時点でResolve-DnsNameをやめて、昔ながらのNslookup.exeに戻ることにした。

Nslookup.exeはITツールキットの定番であり、正当な目的で使用される非常に有名なバイナリである。アプリケーションホワイトリストが懸念される環境でも実行が許可される可能性は高い。
NslookupはResolve-DnsNameクエリとほぼ同じ情報を返すが、タイミングが来たら少し異なる方法で操作する必要がある。
## 実行ファイルをDNSレコードに変換する?
さて、被害者のコンピュータでDNSクエリを実行する手段は得た。ペイロードをNslookupが取得できる形式でどのように提供すればよいだろうか?
実行ファイルはもちろんバイナリファイルであり、人間が読めるものではない。その結果、データをDNSレコードに埋め込み、Nslookupのようなツールが回復できるものに変換する必要がある。利用可能なエンコードオプションは豊富にあるが、主な考慮事項は、被害者のマシンがネイティブのWindowsツールとCLMで利用可能な機能のみを使用してデコードできるものは何かということだ。Base64は明白でよく使われる答えである。
Base64を使用すると、実行ファイルを巨大な人間が読める文字列に変換し、それを複数のDNSレコードに分割してNslookupで回復できる。クライアント側では、有名なLOLBASのcertutil.exeを使用して、集約されたDNSレコードをBase64デコードしてバイナリ形式に戻すことができる。
これには、DNSレコードタイプについてもう少し説明する必要がある。各レコードタイプは特定の情報を特定の形式で保存する。たとえば、AレコードはIPV4アドレス(111.111.111.111)を保存して返す。AAAAレコードはIPV6アドレスを返し、MXレコードとNSレコードはドメイン名を返し、TXTレコードは255文字の文字列を返すことができる。前述のとおり、レコードの長さと大文字小文字の区別のため、TXTレコードは攻撃者にとって明らかな選択肢であり、必要なレコード数が少なく、Base64のようなエンコードと互換性がある。
これがどのように見えるか見てみよう。
Kali VM上で、実行ファイルを取得してBase64に変換する。-w 0スイッチを使用すると、すべての改行が削除され、Base64テキストの1行が残ることに注意:

ファイルを見るとBase64が表示される:

ここで、このBase64エンコードされたファイルを、DNSサーバーが提供するDNS TXTレコードに変換する必要がある。
この過程で学んだいくつかのことを、先に進む前に簡単にまとめておく:
**1.** 単一のDNSクエリに対して複数のレコードが返される場合、それらが「順序」通りに返される保証はない。これは私たちの目的にとって極めて重要であり、すべてのTXTレコードからファイルを再構築する必要があり、順序が狂っていると機能しない。
**2.** 重複レコードはクエリに対して返されない。たとえば、ゾーンファイルに3つのTXTレコードがあり、そのうち2つが同じ情報を含んでいる場合、そのドメインのTXTレコードをクエリすると、一意のレコードのみが返されるため、2つのレコードしか返されない。順序の問題はさておき、たとえば「AAAAA」の大きなセクション(Base64エンコードされたペイロードに見られる)を複数のTXTレコードに埋め込む必要がある場合、ゾーンファイルに複数存在しても、「A」で埋められたTXTレコードのうち1つだけが返される。
これらの点を踏まえ、各DNSクエリに対して1つのTXTレコードのみが返されるようにする必要がある。そこでサブドメインの登場だ。「dns.edu...com」を「edu....com」のサブドメインとして登録したのと同様に、さらに下位のサブドメイン(例:1.dns.edu....com)に対してレコードを提供できる。すべてのTXTレコードを提供するために必要な数のサブドメインを作成できる。
Base64エンコードされたペイロードを見てみよう:

前述のとおり、各TXTレコードには255文字まで詰め込める。413,696/255を計算すると、切り上げて1,623になる。これは多数のTXTレコード(ひいては多数のサブドメイン)である。しかし、それは出発点に過ぎない。
Python3スクリプトを作成して、Base64エンコードされたペイロードを読み込み、ゾーンファイルを作成した: