PowerShell ist ein integraler Bestandteil moderner Windows-Umgebungen und ein unverzichtbares Werkzeug für Systemadministratoren. Doch die Vielseitigkeit macht Powershell auch zu einem gefährlichen Angriffswerkzeug.
In unserer Blog-Reihe "PowerShell Threat Hunting" zeigen wir Powershell-basierte Risiken auf und erklären wie wir CrowdStrike NG-SIEM verwenden um verdächtige Aktivitäten erkennen und analysieren.
Jeder Beitrag in dieser Reihe widmet sich einem spezifischen SIEM Use-Case, vom Erkennen von Powershell-Downloads bis hin zur De-Ofuscation von Base64 enkodierten Powershell Scripts. Wir teilen Best Practices, praktische Beispiele und umsetzbare Strategien, um Ihre Fähigkeiten im Threat Hunting zu verbessern.
PowerShell bietet die Möglichkeit, Befehle in encodierter Form auszuführen, was die direkte Erkennung des tatsächlich ausgeführten Codes erheblich erschwert. Diese Funktion wird von Angreifern gezielt ausgenutzt, um ihre Aktivitäten zu verschleiern und Sicherheitslösungen zu umgehen. Insbesondere durch die Nutzung von Base64-Encoding lassen sich schädliche PowerShell-Befehle in Skripten oder Befehlszeilen verstecken, ohne dass sie auf den ersten Blick als verdächtig erscheinen.
Diese Technik fällt unter die von MITRE ATT&CK dokumentierte Technik T1027.010 – Obfuscated Files or Information: Command Obfuscation, die verschiedene Ansätze zur Tarnung von Befehlen und Skripten beschreibt. In diesem Use-Case konzentrieren wir uns auf die Erkennung und Analyse von Base64-encodierten PowerShell-Befehlen. Durch gezielte Dekodierung und Untersuchung der ausgeführten Befehle können verdächtige Aktivitäten identifiziert und mögliche Angriffe frühzeitig erkannt werden.
Der Nachfolgende Use-Case basiert auf einer LogScale Query, welche ursprünglich im CrowdStrike Subreddit veröffentlicht wurde. Wir haben die Erkennung erweitert, sodass beispielsweise auch die Ausführung via pwsh und unterschiedliche Schreibweisen von "-EncodedCommand
" erkannt werden.
Das Ziel dieses SIEM Use-Cases ist die:
PowerShell bietet zahlreiche Möglichkeiten zur Verschleierung (Obfuscation), die sowohl für legitime als auch für bösartige Zwecke genutzt werden können. Angreifer setzen verschiedene Techniken ein, um ihre Aktivitäten zu verstecken – von Variablen-Manipulationen bis hin zu mehrstufigen Encodings.
Eine besonders häufig genutzte Methode ist die Base64-Kodierung von Befehlen. Sie ermöglicht es, PowerShell-Skripte oder -Kommandos auszuführen, ohne dass sie in Klartext in den Logs erscheinen.
Angreifer beschränken sich jedoch nicht nur auf die Base64-Kodierung selbst – auch die Verwendung des Parameters -EncodedCommand
kann zusätzlich verschleiert werden. Durch verschiedene Obfuscation-Techniken, wie das Zerlegen des Befehls in mehrere Teile, das Einfügen von unnötigen Zeichen oder das Umgehen des Parameters durch alternative Methoden, versuchen sie, eine direkte Erkennung zu erschweren. Deshalb ist es nicht ausreichend, nur nach -EncodedCommand
zu suchen – nachfolgend einige Beispiele, welche wir im Use-Case berücksichtigen wollen:
//Get Powershell, Powershell_ise and pwsh events
#event_simpleName=ProcessRollup2
| event_platform=Win
| ImageFileName=/\\(powershell(_ise)?|pwsh)\.exe/i
Erklärung
CrowdStrike speichert Prozessausführungen im Feld "ProcessRollup2", weshalb wir im ersten Schritt dort nach allen PowerShell-Prozessen suchen. Dabei wird berücksichtigen wir neben Powershell.exe auch Prozesse wie powershell_ise.exe
und pwsh.exe
//Search for "-EncodeCommand" and variations
| CommandLine=/\s-[eE^]{1,2}[ncodema^]*\s(?<base64String>\S+)/i
| groupby([ParentBaseFileName, CommandLine], function=stats([count(aid, distinct=true, as="uniqueEndpointCount"), count(aid, as="executionCount")]), limit=max)
| uniqueEndpointCount < 3
Erklärung
Hier wird gezielt nach PowerShell-Befehlen gesucht, die mit -EncodedCommand
(und ähnlichen Schreibweisen wie -enc
, -e
) aufgerufen wurden – ein Hinweis auf verschleierte Aktivitäten. Dabei wird auch gleich der Base64-String extrahiert, der den eigentlichen PowerShell-Befehl enthält.
Anschließend werden die Ergebnisse gruppiert: nach dem aufrufenden Prozess und dem genauen PowerShell-Befehl. Dabei wird gezählt, wie oft dieser Befehl insgesamt ausgeführt wurde und auf wie vielen einzigartigen Endpunkten. Diese Metriken helfen, die Verbreitung eines Befehls einzuschätzen – ein wichtiger Faktor zur Risikobewertung.
Mit dem Schwellenwert "uniqueEndpointCount" wird die Verbreitung eingegrenzt: Kommandos, die nur auf sehr wenigen (hier: unter 3) Endpunkten auftreten, gelten als verdächtig. Typischerweise handelt es sich dabei nicht um administrative Skripte, sondern einen potenziellen Angriff.
//Calculating command length & Isolate Base64 sting
| cmdLength := length("CommandLine")
| CommandLine=/\s-[eE^]{1,2}[ncodema^]*\s(?<base64String>\S+)/i
Erklärung
Hier wird die Länge des gesamten Befehls berechnet – oft ein Indikator für komplexe oder zusammengesetzte Payloads. Zudem wird der zuvor extrahierte Base64-String nochmals isoliert, um ihn in weiteren Schritten analysieren zu können.
//Get Entropy of Base64 String
| b64Entroy := shannonEntropy("base64String")
// Set entropy threshold
| b64Entroy > ?EntropyGreaterThan
Erklärung
Die Entropie – also die Informationsdichte – des Base64-Strings wird mithilfe der Shannon-Entropie berechnet. Hohe Entropiewerte deuten auf verschlüsselten oder obfuskierten Code hin. Dieser Schritt hilft, harmlose Base64-Kommandos von tatsächlich gefährlichen zu unterscheiden.
Nur Strings, deren Entropie über einem definierten Schwellenwert liegt, werden weiter betrachtet. Das reduziert Fehlalarme und fokussiert die Analyse auf wirklich verdächtige Kodierung. Je nach Umgebung lohnt es sich diesen Wert anzupassen und mit unterschiedlichen Werten zu testen.
//Decode encoded command blob
| decodedCommand := base64Decode(base64String, charset="UTF-16LE")
//Outputting to table
| table([ParentBaseFileName, uniqueEndpointCount, executionCount, cmdLength, b64Entroy, decodedCommand])
Erklärung
Die dekodierten PowerShell-Kommandos werden hier im Klartext sichtbar gemacht. Base64-codierte PowerShell-Befehle sind in der Regel im UTF-16LE-Zeichensatz kodiert – deshalb wird dieser hier explizit verwendet. Der dekodierte Befehl kann anschließend auf IOCs oder gefährliche Funktionen untersucht werden.
Alle relevanten Informationen werden übersichtlich in einer Tabelle dargestellt: der aufrufende Prozess, die Verbreitung, die Entropie des kodierten Strings, die Länge des Kommandos – und vor allem das entschlüsselte Kommando selbst. Das schafft Transparenz und ermöglicht eine schnelle Bewertung durch Analysten.
//| decodedCommand=/https?/i
//|regex("(?<ip>[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\:(?<port>\d{2,5})", field=decodedCommand)
//|regex("(?<ip>[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\"", field=decodedCommand)
Erklärung
(Optional - hier auskommentiert) In einem weiteren Schritt könnten URL- oder IP-Adressen in den dekodierten Kommandos gesucht werden. Das erlaubt eine noch tiefere Analyse und die Extraktion möglicher Command-and-Control-Server oder exfiltrierter Ziele.
//Get Powershell, Powershell_ise and pwsh events
#event_simpleName=ProcessRollup2
| event_platform=Win
| ImageFileName=/\\(powershell(_ise)?|pwsh)\.exe/i
//Search for "-EncodeCommand" and variations
| CommandLine=/\s-[eE^]{1,2}[ncodema^]*\s(?<base64String>\S+)/i
| groupby([ParentBaseFileName, CommandLine], function=stats([count(aid, distinct=true, as="uniqueEndpointCount"), count(aid, as="executionCount")]), limit=max)
//Set endpoint prevalence threshold
| uniqueEndpointCount < 3
//Calculating command length & Isolate Base64 sting
| cmdLength := length("CommandLine")
| CommandLine=/\s-[eE^]{1,2}[ncodema^]*\s(?<base64String>\S+)/i
//Get Entropy of Base64 String
| b64Entroy := shannonEntropy("base64String")
// Set entropy threshold
| b64Entroy > ?EntropyGreaterThan
//Decode encoded command blob
| decodedCommand := base64Decode(base64String, charset="UTF-16LE")
//Outputting to table
| table([ParentBaseFileName, uniqueEndpointCount, executionCount, cmdLength, b64Entroy, decodedCommand])
//Search for URLs and IPs in decoded command
//| decodedCommand=/https?/i
//|regex("(?<ip>[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\:(?<port>\d{2,5})", field=decodedCommand)
//|regex("(?<ip>[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\"", field=decodedCommand)
Vollständige Query
Die vollständige Query