Powershell ist ein fester Bestandteil moderner Windows-Systeme und bietet durch .NET-Anbindung mächtige Möglichkeiten zur Automatisierung. Diese Eigenschaften machen es auch für Angreifer attraktiv:
- Auf jedem Windows-System vorhanden (kein zusätzlicher Code nötig)
- Direkter Zugriff auf Windows-APIs und Netzwerkressourcen
- Kann Code aus dem Speicher ausführen (fileless execution)
- Oft unzureichend überwacht oder eingeschränkt
Warum Encoded Powershell ein Risiko darstellt
Angreifer codieren PowerShell-Befehle um Sicherheitsmechanismen zu umgehen:
* Einfache Signaturen und Filter, Intrusion Detection Systems und SIEM-Regeln können die eigentlichen Befehle nicht erkennen, wenn sie Base64-kodiert sind. Die Codierung verschleiert Schlüsselwörter wie `Invoke-Expression`, `Net.WebClient`, `DownloadString` oder Namen bekannter Schadsoftware wie Mimikatz.
* Fileless Execution: Codierte Befehle werden oft verwendet, um Skripte oder Binaries direkt aus dem Internet in den Speicher zu laden und auszuführen (`IEX (New-Object Net.WebClient).DownloadString(...)`), ohne dass eine Datei auf die Festplatte geschrieben wird. Dies erschwert die forensische Analyse und die Erkennung erheblich.
* Umgehung von Execution Policies: Die Verwendung von Parametern wie `-Command` oder `-EncodedCommand` kann die Powershell Execution Policy umgehen, die standardmäßig das Ausführen von Skripten einschränken soll.
* Verschleierung der Gesamtaktivität: Codierung ist oft Teil einer mehrstufigen Obfuskationsstrategie. Angreifer können Base64 mit anderen Techniken wie String-Manipulation, Zeichenkettenzerlegung, zufälliger Groß- / Kleinschreibung oder Komprimierung kombinieren, um die Analyse weiter zu erschweren.
* Living off the Land: Als vorinstalliertes Windows Tool verhält sich Powershell mit codierten Befehlen unaufälliger als externe Tools / Frameworks.
Laut Microsoft-Dokumentation ist der Hauptzweck von `-EncodedCommand`, die Ausführung von Befehlen zu ermöglichen, die komplexe Anführungszeichen, geschweifte Klammern oder andere Sonderzeichen enthalten, die bei der direkten Übergabe an die Kommandozeile zu Problemen führen könnten. Durch die Kodierung des Befehls als Base64-String werden diese Interpretationsprobleme vermieden. Außerdem sind wir in Kundenumgebungen bereits auf die folgenden Verwendungen gestoßen:
* Systemadministration & Automatisierung:
* Ausführen von Skripten mit komplexen Parametern: Übergabe von Parametern, die Sonderzeichen, JSON-Strings oder andere komplexe Daten enthalten, an ein PowerShell-Skript, das über powershell.exe aufgerufen wird.
* Scheduled Tasks / Remote-Ausführung: In Szenarien, in denen Befehle über mehrere Schichten oder Systeme übergeben werden, die komplexe Strings verändern könnten (z.B. Argumente im Task Scheduler, bestimmte Remote-Management-Tools, Skripte, die andere Skripte aufrufen).
* Einbetten von Befehlen: Speichern oder Übertragen von Powershell-Befehlen innerhalb anderer Dateiformate (z.B. XML-Konfigurationsdateien, JSON-Nutzdaten, HTML-Seiten), bei denen die direkte Einbettung zu Syntaxkonflikten führen würde.
* Interoperabilität: Bei Aufrufen von Powershell aus anderen Programmier- oder Skriptsprachen (z.B. Python, Batch, C#), bei denen das korrekte Escaping der Powershell-Syntax schwierig sein kann.
* Seltene / Nischenfälle: In wenigen Fällen verwendet von legitimen Software-Installationsprogrammen oder Konfigurationstools.
* Einbetten von nicht-textuellen Daten (wie Icons für GUI-Skripte) direkt in eine Skriptdatei. Dies verwendet jedoch typischerweise `[Convert]::FromBase64String` innerhalb des Skripts, nicht den `-EncodedCommand` Parameter beim Aufruf.
Der Parameter `-EncodedCommand` ist ein legitimes Feature von `powershell.exe` (Windows Powershell 5.1 und früher) und `pwsh.exe` (Powershell 6 und neuer). Powershell erlaubt es, Parameter in vollständig ausgeschriebener Form (z. B. `-EncodedCommand`) oder in verkürzter Schreibweise (wie `-enc` oder sogar `-e`) zu verwenden, solange die Kurzform eindeutig auf einen gültigen Parameter verweist. Dieses Verhalten basiert auf einem eingebauten Mechanismus zur automatischen Parametervervollständigung, der dem Nutzer Arbeit abnehmen und Fehler reduzieren soll. Um codierte Powershell-Befehle zu finden, reicht es also nicht nach dem Parameter `-EncodedCommand` zu suchen. Die alternativen Schreibweisen müssen in der Suche berücksichtigt werden.
# Der ursprüngliche Befehl
$command = "ping 8.8.8.8"
# 1. String in Bytes umwandeln (unter Verwendung von UTF-16LE)
$bytesUtf16le = [System.Text.Encoding]::Unicode.GetBytes($command)
# 2. Bytes in einen Base64-String umwandeln
$base64CommandUtf16le = [System.Convert]::ToBase64String($bytesUtf16le)
# Ausgabe
Write-Host "Ursprünglicher Befehl: $command"
Write-Host "Bytes (UTF-16LE) als Hex-String: $($bytesUtf16le | ForEach-Object { $_.ToString('X2') })"
Write-Host "Base64-kodierter Befehl (aus UTF-16LE Bytes): $base64CommandUtf16le"
powershell.exe -EncodedCommand $encodedCommand
1//Get Powershell and pwsh events
2#event_simpleName=ProcessRollup2
3| event_platform=Win
4| ImageFileName=/\\(powershell|pwsh)\.exe/i
5//Search for "-EncodeCommand" and variations
6| groupby([ParentBaseFileName, CommandLine], function=stats([count(aid, distinct=true, as="uniqueEndpointCount"), count(aid, as="executionCount")]), limit=max)
7//Set endpoint prevalence threshold
8| uniqueEndpointCount < 3
9//Calculating command length & Isolate Base64 sting
10| cmdLength := length("CommandLine")
11//| CommandLine=/\s(|[\^])-(|[\^])[e]{1,2}[ncodema^]*\s(?<base64String>\S+)|^/i
12//| CommandLine=/\s-[eE^]{1,2}[ncodema^]*\s(?<base64String>\S+)/i
13| CommandLine=/\s(|[\^])-(|[\^])[e]{1,2}[ncodema^]*\s(?<base64String>\S+)/i
14//| replace("^", with="", field=base64String, as=CleanBase64String)
15//Get Entropy of Base64 String
16| b64Entropy := shannonEntropy("base64String")
17// Set entropy threshold
18| b64Entropy > ?EntropyGreaterThan
19//Decode encoded command blob
20| decodedCommand := base64Decode(base64String, charset="UTF-16LE")
21//Outputting to table
22| table([ParentBaseFileName, uniqueEndpointCount, executionCount, cmdLength, b64Entropy, decodedCommand, CommandLine])
23//Uncomment next line to search URLs in the decoded command
24//| decodedCommand=/https?/i
25//Uncomment next line to search IP:Port in the decoded command
26//|regex("(?<ip>[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\:(?<port>\d{2,5})", field=decodedCommand)
27//Uncomment next line to search IP in the decoded command
28//|regex("(?<ip>[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})", field=decodedCommand)