Powershell Hunting IV: Powershell Command Length

Dieser Blogbeitrag aus der Reihe "PowerShell Threat Hunting" demonstriert, wie man mit CrowdStrike NG-SIEM verdächtige PowerShell-Downloads erkennt.
SIEM
04.02.2026

Powershell Command Length

Warum Powershell für Angreifer interessant ist

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

Living off the Land-Taktiken nutzen Powershell, um Angriffe unauffällig und ohne zusätzliche Tools durchzuführen.

Abweichungen der Befehlslänge

Angreifer verwenden häufig Methoden in Powershell, die zu ungewöhnlich langen Befehlszeilen führen. Das Überwachen von Abweichungen gegenüber der üblichen Befehlslänge kann daher ein wertvoller Ansatz zur Erkennung verdächtiger Aktivitäten sein. Ungewöhnlich lange Befehle können ein Indikator für einen Angriff sein.

Verschleierung / Obfuscation

  • Kodierung: Verwendung von Base64, Hexadezimal oder ASCII zur Darstellung von Befehlen oder Script-Blöcken.
  • Escape-Zeichen: Einsatz von Backticks (\) innerhalb von Zeichenketten oder Befehlen, um die Lesbarkeit zu beeinträchtigen.
  • Leerzeichen & Kommentare: Einfügen unnötiger Leerzeichen oder Kommentare.
  • Einbetten von Payloads & Skripten: Einfügen ganzer Skripte oder binärer Payloads direkt in die Befehlszeile.
  • Dateilose Ausführung & LotL: Fileless-Angriffe oder welche, die nur integrierte Werkzeuge verwenden (Living off the Land), verlassen sich oft auf komplexe, einzeilige Befehle, die an powershell.exe übergeben werden, um Payloads von entfernten Quellen herunterzuladen und auszuführen oder verkettete Aktionen durchzuführen. Diese komplexen Anweisungen führen naturgemäß zu längeren Befehlen.
  • Offensive Frameworks: Werkzeuge wie Empire, PowerSploit, Cobalt Strike usw. nutzen Powershell intensiv und generieren oft lange, verschleierte Befehle für ihre Payloads.

Ungewöhnlich lange Befehle sind ein starker Indikator, da sie direkt mit gängigen Angreifertechniken zur Umgehung und Ausführung korrelieren ([T1027.010] Obfuscated Files or Information). Legitime administrative Aufgaben erfordern selten die extremen Längen, die oft durch die genannten Methoden erzeugt werden. Legitime Administratoren verwenden typischerweise Skriptdateien (.ps1) für komplexe Logik , nicht verschleierte Einzeiler. Angreifer verwenden lange, verschleierte Befehlszeilen genau deshalb, um das Schreiben von Dateien auf die Festplatte zu vermeiden (dateilose Ausführung) und um einfache Erkennungslogiken zu umgehen. Dieser Unterschied in der Betriebspraxis macht die Länge zu einem relativ nützlichen Unterscheidungsmerkmal.

Die Macht des Baselining: "Normal" etablieren

Grundlagen der Anomalieerkennung

Die Kernidee besteht darin, darzustellen was "normale" Aktivität für die länge der ausgeführten Powershell-Befehle ist, und dann Abweichungen von dieser Norm zu identifizieren.

Erstellung der Baseline

Die Abfrage analysiert historische Powershell-Executions und die jeweiligen Befehlslängen über einen definierten Zeitraum. Sie berechnet statistische Maße der zentralen Tendenz (in diesem Fall den Mittelwert) für Befehlslängen während dieses Baseline-Zeitraums. Dies definiert den erwarteten Bereich der Befehlslängen.

Zeitraum: 7-Tage-Baseline

Erfassung wöchentlicher Zyklen

Viele organisatorische Arbeitsabläufe und administrative Aufgaben folgen wöchentlichen Mustern (z. B. unterschiedliche Aktivitäten an Wochentagen vs. Wochenenden, wöchentliche Berichte, Patching-Zeitpläne). Eine 7-Tage-Baseline reicht oft aus, um diese typischen Rhythmen zu erfassen.

Stabilität vs. Anpassungsfähigkeit

der gewählte Zeitraum ist eine Balance. Kürzere Baselines (z. B. 24 Stunden) können übermäßig empfindlich auf tägliche Schwankungen reagieren. Längere Baselines passen sich möglicherweise zu langsam an legitime Verhaltensänderungen an oder riskieren, vergangene Anomalien in das "normale" Profil zu integrieren.

Ein 7-Tage-Fenster glättet tägliche Variationen und bleibt gleichzeitig einigermaßen anpassungsfähig. Die Wahl einer 7-Tage-Baseline ist eine bewusste Abwägung, die darauf abzielt, das Signal/Rausch Verhältnis für die Erkennung von Anomalien im Zusammenhang mit wöchentlichen menschlichen/systemischen Verhaltensmustern zu maximieren. Geschäfts- und IT-Betriebe haben oft wöchentliche Rhythmen. Ein Server könnte bestimmte Wartungsskripte nur am Wochenende ausführen, oder Benutzer könnten bestimmte Aufgaben nur montags erledigen. Wenn am Mittwoch ein sehr langer Befehl auftaucht, aber ähnliche lange Befehle jeden Samstag aufgrund von Backups normal sind, hilft die 7-Tage-Baseline dabei, das potenziell anomale Ereignis am Mittwoch vom normalen Ereignis am Samstag zu unterscheiden. Dies reduziert Fehlalarme im Vergleich zu einer kürzeren Baseline, die den wöchentlichen Kontext nicht erfasst.

Anpassung an die betriebliche Realität

Je nach Umgebung kann eine Anpassung des Zeitraums für bessere Ergebnisse sorgen.

CrowdStrike NextGen SIEM Query

// Average Powershell Command Length
#event_simpleName=ProcessRollup2
| ImageFileName=/\\(powershell(_ise)?|pwsh)\.exe/i
| CommandLength := length("CommandLine") | CommandLength>0
| aid=?AID
// Classify Data into Historical and LastDay
| case {
    test(@timestamp < (end() - duration(7d))) | DataSet:="Historical";
    test(@timestamp > (end() - duration(1d))) | DataSet:="LastDay"; 
    *}
// Calculate Average Command Length
| groupBy([DataSet, aid], function=avg(CommandLength))
| case {
    DataSet="Historical" | rename(field="_avg", as="historicalAvg");
    DataSet="LastDay" | rename(field="_avg", as="todaysAvg");
    *
}
// Aggregate Averages
| groupBy([aid], function=[avg("historicalAvg", as=historicalAvg), avg("todaysAvg", as=todaysAvg)])
// Calculate Percentage Increase
| PercentIncrease := (todaysAvg - historicalAvg) / historicalAvg * 100
| format("%d", field=PercentIncrease, as=PercentIncrease)
| format(format="%.2f", field=[historicalAvg], as=historicalAvg)
// Filter and Sort Results
| PercentIncrease > 0
| sort(PercentIncrease, limit=10000)

Weitere Artikel

Vom Exploit bis zur Defense: Echte Security-Insights aus unserem SOC und Research-Lab

Zum Blog