Einführung
Im ersten Teil haben wir eine kurze Beschreibung des Mechanismus des encrypted SNI (eSNI) gegeben. Wir haben gezeigt, wie man mit Hilfe dieses Mechanismus modernen DPI-Systemen (am Beispiel des DPI von Beeline und des verbotenen RKN Torrent-Trackers) entkommen kann, und einen neuen Ansatz des Domain-Fronting basierend auf diesem Mechanismus betrachtet.
Im zweiten Teil des Artikels werden wir zu praktischeren Aspekten übergehen, die für RedTeam-Spezialisten in ihrer anspruchsvollen Arbeit nützlich sein werden. Letztendlich ist unser Ziel nicht der Zugang zu gesperrten Ressourcen (für solche banalen Dinge haben wir unseren alten, bewährten VPN). Glücklicherweise gibt es viele VPN-Anbieter, wie man so schön sagt, für jeden Geschmack, jedes Budget und jede Vorliebe.
Wir werden versuchen, den Mechanismus des Domain-Fronting auf moderne RedTeam-Tools anzuwenden, wie z.B. Cobalt Strike, Empire usw., und ihnen zusätzliche Möglichkeiten zur Mimikry und zum Entkommen vor modernen Inhaltsfilterungssystemen zu geben.
Beim letzten Mal haben wir den eSNI-Mechanismus in die OpenSSL-Bibliothek integriert und erfolgreich in dem weit verbreiteten Tool curl verwendet. Aber mit nur curl wird man nicht satt, wie man so schön sagt. Natürlich will man etwas Ähnliches in Hochsprachen umsetzen. Leider enttäuscht uns eine schnelle Suche im Internet, denn die Unterstützung des eSNI-Mechanismus ist nur in GOLANG vollständig realisiert. Damit haben wir nicht viel Wahl: Entweder wir programmieren in reinem C oder C++ unter Verwendung einer gepatchten OpenSSL-Bibliothek, oder wir nutzen einen separaten GOLANG-Fork von CloudFlare und versuchen, unser Toolkit dorthin zu portieren. Prinzipiell gibt es noch eine andere Möglichkeit, die klassischere, aber auch zeitaufwändige – nämlich die Unterstützung von eSNI in Python zu implementieren. Schließlich verwendet Python ebenfalls OpenSSL für die Arbeit mit https. Aber diese Option lassen wir jemand anderem zur Entwicklung und sind zufrieden mit der Umsetzung in Golang, insbesondere da unser Lieblingswerkzeug Cobalt Strike hervorragend mit einem über externe Mittel aufgebauten Kommunikationskanal (External C2 channel) arbeiten kann – darüber werden wir am Ende des Artikels berichten.
Versuchen Sie es härter…
Ein von uns entwickeltes Go-Tool – unser interner Tunneler , das übrigens heutzutage von Microsoft und Symantec als hochgradig schadhafter Software zur Störung der globalen Stabilität erkannt wird…

Es wäre großartig, die vorherige Entwicklung auch in diesem Fall zu nutzen. Doch hier gibt es ein kleines Problem. Die Sache ist die, dass rsockstun ursprünglich eine synchrone SSL-Verbindung zum Server voraussetzt. Das bedeutet, dass die Verbindung einmal hergestellt wird und während der gesamten Laufzeit des Tunnelers bestehen bleibt. Wie Sie verstehen, ist das https-Protokoll jedoch nicht wirklich für diesen Betriebsmodus geeignet – es funktioniert im Anforderungs-Antwort-Modus, wo jede neue http-Anfrage im Rahmen einer neuen tcp-Verbindung existiert.
Der Hauptnachteil eines solchen Schemas besteht darin, dass der Server keine Daten an den Client übertragen kann, solange der Client keine neue HTTP-Anfrage sendet. Glücklicherweise gibt es viele Lösungen für dieses Problem – die Datenübertragung über das HTTP-Protokoll (schließlich gelingt es uns ja irgendwie, unsere Lieblingsserien zu streamen und Musik von Portalen zu hören, die über HTTPS arbeiten, und die Übertragung von Video und Audio ist nichts anderes als Datenstreaming). Eine Technologie, die die Funktionalität einer vollständigen TCP-Verbindung über das HTTP-Protokoll emuliert, ist die WebSocket-Technologie, deren Hauptzweck darin besteht, eine vollständige Netzwerkverbindung zwischen Client und Webserver zu ermöglichen.
Zu unserem Glück (hurra!!!) ist diese Technologie standardmäßig in allen CloudFlare-Tarifen aktiviert und funktioniert hervorragend in Kombination mit eSNI. Genau diese werden wir verwenden, um unseren Tunnel zu lehren, Domain-Fronting zu nutzen und sich vor modernen DPI zu verbergen.
Ein wenig über WebSockets
Zunächst werden wir in einfachen Worten kurz über WebSockets sprechen, damit alle ein Verständnis für das haben, womit wir arbeiten werden.
Die WebSocket-Technologie ermöglicht es, temporär von einer HTTP-Verbindung auf einen standardmäßigen Datenstrom über einen Netzwerksocket umzuschalten, ohne die bestehende TCP-Verbindung zu unterbrechen. Wenn der Client zu WebSocket wechseln möchte, sendet er in seiner HTTP-Anfrage mehrere HTTP-Header. Zwei erforderliche Header sind: Connection: Upgrade und Upgrade: websocket. Außerdem kann er die Version des WebSocket-Protokolls erzwingen (Sec-WebSocket-Version: 13) und einen Art von Base64-Identifikator für den WebSocket (Sec-WebSocket-Key: DAGDJSiREI3+KjDfwxm1FA==). Der Server antwortet ihm mit dem HTTP-Code 101 Switching Protocols und setzt auch die Header Connection, Upgrade und Sec-WebSocket-Accept. Der Prozess des Umschaltens wird im Screenshot unten anschaulich dargestellt:

Danach kann die Einrichtung der WebSocket-Verbindung als abgeschlossen betrachtet werden. Jegliche Daten, sowohl vom Client als auch vom Server, werden nun nicht mehr mit HTTP-, sondern mit WebSocket-Headern versehen (sie beginnen mit dem Byte 0x82). Der Server muss nicht mehr auf eine Anfrage des Clients warten, um Daten zu senden, da die TCP-Verbindung nicht unterbrochen wird.
In Go gibt es mehrere Bibliotheken für die Arbeit mit WebSockets. Die bekanntesten sind und die Standardbibliothek. . Wir werden die letzte Option nutzen, da sie einfacher, kleiner und, wie man sagt, etwas schneller ist.
Im Client-Code von rsockstun müssen wir die Aufrufe von net.dial oder tls.dial durch die entsprechenden WebSocket-Aufrufe ersetzen:


Wir möchten den Client-Teil unseres Tunnelers universell gestalten, sodass er sowohl über eine direkte SSL-Verbindung als auch über das WebSocket-Protokoll funktioniert. Dazu werden wir eine separate Funktion erstellen: func connectForWsSocks(address string, proxy string) error {…} analog zu connectForSocks() und wir werden sie für die Arbeit mit WebSockets verwenden, falls die Serveradresse, die beim Start des Clients festgelegt wurde, mit ws: oder wss: beginnt (im Falle von Secure WebSocket).
Für den Serverteil des Tunnelers werden wir ebenfalls eine separate Funktion für die Arbeit mit WebSockets erstellen. Darin wird eine Instanz der http-Klasse erstellt und der http-Verbindungs-Handler (Funktion wsHandler) festgelegt:

Die gesamte Logik zur Verarbeitung der Verbindung (Authentifizierung des Clients mit Passwort, Einrichtung und Beendigung der yamux-Sitzung) werden wir in den WebSocket-Verbindungs-Handler einfügen:

Lassen Sie uns das Projekt kompilieren und den Serverteil starten:
./rsockstun –listen ws:127.0.0.1:8080 –pass P@ssw0rdUnd dann den Clientteil:
./rsockstun -connect ws:127.0.0.1:8080 –pass P@ssw0rdUnd wir überprüfen die Funktion auf dem lokalen Host:


Lassen Sie uns zu Domain-Fronteingängen übergehen
Mit Web-Sockets scheinen wir bereits klarzukommen. Lassen Sie uns nun direkt zu eSNI und Domain-Fronteingängen gehen. Wie bereits erwähnt, benötigen wir für die Arbeit mit DoH und eSNI einen speziellen Branch von Golang von der Firma . Wir benötigen den Branch mit eSNI-Unterstützung (pwu/esni).
Klonen Sie ihn lokal oder laden Sie die entsprechende Zip-Datei herunter und entpacken Sie sie:
git clone -b pwu/esni https://github.com/cloudflare/tls-tris.gitAnschließend müssen wir das GOROOT-Verzeichnis kopieren, die entsprechenden Dateien aus dem geklonten Branch ersetzen und es als Hauptverzeichnis festlegen. Um den Entwickler von diesem Kopfzerbrechen zu befreien, haben die Leute von CloudFlare ein spezielles Skript vorbereitet – _dev/go.sh. Starten Sie es einfach. Das Skript wird zusammen mit dem Makefile alles selbst erledigen. Zum Interesse – Sie können ins Makefile schauen, um Details zu erfahren.
Nach der Ausführung des Skripts müssen wir bei der Kompilierung des Projekts das lokale Verzeichnis, das durch das Skript vorbereitet wurde, als GOROOT angeben. In unserem Fall sieht es so aus:
GOROOT="/opt/tls-tris/_dev/GOROOT/linux_amd64" go build ….Zunächst müssen wir im Tunnel die Funktionalität für die Abfrage und das Parsen der öffentlichen eSNI-Schlüssel für die entsprechende Domain implementieren. In unserem Fall handelt es sich um die öffentlichen eSNI-Schlüssel von den Frontend-Servern von CloudFlare. Dafür werden wir drei Funktionen erstellen:
func makeDoTQuery(dnsName string) ([]byte, error)
func parseTXTResponse(buf []byte, wantName string) (string, error)
func QueryESNIKeysForHost(hostname string) ([]byte, error)Die Funktionsnamen sprechen eigentlich für sich. Die Implementierung werden wir aus der Datei esni_query.go entnehmen, die Teil von tls-tris ist. Die erste Funktion erstellt ein Netzwerkpaket für die Abfrage beim DNS-Server von CloudFlare unter Verwendung des Protokolls DoH (DNS-over-HTTPS), die zweite analysiert die Ergebnisse der Anfrage und erhält die Werte der öffentlichen Schlüssel der Domain, während die dritte als Container für die ersten beiden dient.
Als Nächstes fügen wir in unsere neu geschaffene Funktion zur Verbindung mit dem Websocket connectForWsSocks die Funktionalität zur Abfrage der eSNI-Schlüssel für die Domain ein. Dort, wo der Serverbetrieb erfolgt, setzen wir die TLS-Parameter und definieren den Namen der fiktiven "Maskierungsdomain":

Es ist wichtig zu beachten, dass der tls-tris-Branch ursprünglich nicht für die Verwendung von Domain-Fronting vorgesehen ist. Daher wurde dem gefälschten Servernamen (das serverName-Feld im client-hello-Paket wird leer übermittelt) keine Beachtung geschenkt. Um dies zu beheben, müssen wir das entsprechende Feld FakeServerName in die TlsConfig-Struktur einfügen. Das Standardfeld ServerName der Struktur können wir nicht verwenden, da es von internen tls-Mechanismen genutzt wird und, wenn es von der ursprünglichen Bezeichnung abweicht, der tls-Handshake mit einem Fehler endet. Die Beschreibung der TlsConfig-Struktur befindet sich in der Datei tls/common.go – die wir anpassen müssen:


Zusätzlich müssen wir Änderungen in der Datei tls/handshake_client.govornehmen, um unser Feld FakeServerName bei der TLS-Handshakerstellung zu verwenden:

Das wäre alles! Sie können Ihr Projekt jetzt kompilieren und testen. Aber bevor Sie mit dem Test starten, müssen Sie Ihr Cloudflare-Konto einrichten. Eigentlich ist es einfach – erstellen Sie einfach ein Konto bei Cloudflare und verknüpfen Sie Ihre Domain damit. Alle Funktionen in Bezug auf DoH, WebSocket und ESNI sind bei Cloudflare standardmäßig aktiviert. Nachdem die DNS-Einträge aktualisiert wurden, können Sie die Funktionalität Ihrer Domain überprüfen, indem Sie eine Anfrage nach den eSNI-Schlüsseln durchführen:
dig +short txt _esni.df13tester.info 
Wenn Sie etwas Ähnliches für Ihre Domain sehen, bedeutet das, dass alles funktioniert und Sie mit den Tests fortfahren können.
Starten Sie einen Ubuntu VPS, zum Beispiel bei DigitalOcean. P.S. In unserem Fall war die gerade vom Anbieter zugewiesene IP-Adresse des VPS auf den Blacklists der RK-Behörden. Seien Sie also nicht überrascht, wenn Ihnen etwas Ähnliches passiert. Wir mussten einen VPN verwenden, um auf unseren VPS zuzugreifen.
Wir kopieren das bereits kompilierte rsockstun auf den VPS (übrigens ein weiterer Vorteil von Go – Sie können das Projekt auf Ihrem System kompilieren und dann auf jedem Linux-Betriebssystem ausführen, solange die Systemarchitektur übereinstimmt) und starten den Serverteil:

Und dann den Client-Teil:

Wie wir sehen, hat der Kunde erfolgreich über den CloudFlare-Frontend-Server eine Verbindung zum Server über WebSocket hergestellt. Um zu überprüfen, ob das Tunnel wirklich als Tunnel funktioniert, kann man eine Curl-Anfrage über den lokal auf dem Server offenen Socks5 durchführen:

Schauen wir uns jetzt an, was die DPI im Kommunikationskanal sieht:

Zunächst kontaktiert der Tunnel mit dem DoH-Mechanismus den DNS-Server von Cloudflare, um eSNI-Schlüssel für die Ziel-Domain abzurufen (Pakete Nr. 1-19), und verbindet sich dann mit dem Frontend-Server, um eine TLS-Verbindung aufzubauen, während er sich hinter der Domain (dies ist der Standardwert, wenn beim Start des Clients keine gefälschte Domain angegeben ist). Um eine eigene gefälschte Domain anzugeben, muss der Parameter -fronfDomain verwendet werden:
![]()

Ein weiterer Punkt. Standardmäßig ist im CloudFlare-Konto die Flexible SSL-Arbeitsweise aktiviert. Das bedeutet, dass HTTPS-Anfragen an die CloudFlare-Frontend-Server von den Kunden in unverschlüsseltem (HTTP) Format an unseren Server weitergeleitet werden. Aus diesem Grund haben wir die Server-Teile des Tunnelers im non-ssl-Modus (-listen ws:0.0.0.0) gestartet, und nicht (-listen wss:0.0.0.0).

Um in den vollständigen Verschlüsselungsmodus zu wechseln, müssen Sie wählen Voll, oder Voll (streng) bei Vorhandensein eines gültigen Zertifikats auf dem Server. Nach dem Wechsel des Modus können wir Verbindungen von CloudFlare über das HTTPS-Protokoll annehmen. Vergessen Sie nicht, ein selbstsigniertes Zertifikat für den Serverteil des Tunnels zu generieren.

Ein aufmerksamer Leser könnte fragen: „Wie sieht es mit dem Client für Windows aus? Schließlich ist die Hauptanwendung des Tunnels, eine Back-Connection mit Unternehmensrechnern und Servern herzustellen, und dort läuft in der Regel immer Windows. Wie kompiliere ich den Tunnel für Windows, und das noch mit einem spezifischen TLS-Stack?“ Lassen Sie uns nun eine weitere Funktion vorstellen, die zeigt, wie benutzerfreundlich Go ist. Wir kompilieren direkt unter Windows aus Kali, indem wir einfach den Parameter GOOS=windows hinzufügen:
GOARCH=amd64 GOROOT="/opt/tls-tris/_dev/GOROOT/linux_amd64" GOOS=windows go build -ldflags="-s -w"Oder die 32-Bit-Variante:
GOARCH=386 GOROOT="/opt/tls-tris/_dev/GOROOT/linux_amd64" GOOS=windows go build -ldflags="-s -w"Das ist alles! Keine weiteren Komplikationen nötig. Das funktioniert wirklich!

Die Compiler-Flags -w und -s sind notwendig, um überflüssigen Müll aus der ausführbaren Datei zu entfernen und sie um einige Megabyte kleiner zu machen. Zusätzlich kann die Datei später mit UPX verpackt werden, um die Größe weiter zu reduzieren.
Abschließend
In diesem Artikel zeigen wir anhand eines in Go geschriebenen Tunnellers anschaulich die Anwendung der neuen Technologie des Domain-Fronting, die auf einer interessanten Eigenschaft des TLS 1.3-Protokolls basiert. Ebenso kann das bestehende Werkzeug, das in Go geschrieben wurde, für die Arbeit über Servers von CloudFlare angepasst werden, beispielsweise. — ein bekannter C2, oder um CobaltStrike Beacon zu zwingen, eSNI Domain-Fronting bei der Kommunikation mit dem Teamserver über , realisiert in Go oder der Standard-C++-Version mit einer gepatchten OpenSSL-Version, über die wir im letzten Teil des Artikels berichtet haben. In der Tat sind der Fantasie keine Grenzen gesetzt.
Das Beispiel mit dem Tunneller und CloudFlare wird als Konzept präsentiert, und es ist derzeit schwierig, über die weitreichenden Perspektiven dieser Art des Domain-Frontings zu spekulieren. Momentan wird eSNI nur von CloudFlare unterstützt, und theoretisch könnte nichts sie daran hindern, diese Art des Frontings auszuschalten und beispielsweise TLS-Verbindungen zu unterbrechen, wenn SNI und eSNI nicht übereinstimmen. Die Zukunft wird es zeigen. Doch derzeit sieht die Perspektive, unter dem "Schutz von kremlin.ru" zu arbeiten, ziemlich verlockend aus. Oder etwa nicht?
Der aktualisierte Tunnel-Code sowie die kompilierten ausführbaren exe-Dateien sind in einem separaten Projektzweig auf . Bei möglichen Problemen mit dem Tunnel ist es besser, ein Issue auf der Projektseite auf GitHub zu erstellen.
Quelle: habr.com
