Powershell Obfuscation


Due to its deep roots on Windows systems and access to .NET libraries, Powershell is a powerful tool for administrators and attackers alike. It offers a wide range of possibilities to write regular and malicious script code and obfuscate it to the point of unreadability and undetectability by antiviruses. Moreover the code can be executed directly in memory, so that no files and further traces are left on the drive.

The using of Powershell allows an attacker initial access to the victim system - for example through phishing attachments - as well as to move laterally on the victim network and inject further malicious code.

So it is not surprising that this tool stands at the top of the attacker tool list. Be it through self-written and obfuscated code or through frameworks, such as Empire and PowerSploit.

In this blog post I will show many of the attacker methods to obfuscate and hide malicious Powershell code and how to detect and analyze it. The shown obfuscation techniques must not stand alone but can also be used in combination.


Powershell offers a variety of ways to obfuscate the script code. In the following the language-specific features of Powershell are described, that are suitable for script obfuscation.

Function Overwriting

Powershell offers built-in functions (Cmdlets) that can be overridden in the profiles. This way malicious code can be added to a regular Cmdlet, which is executed in the background when the Cmdlet is called.

In the following example the build-in function "Get-ChildItem" is extended through the profile.

function Get-ChildItem { Microsoft.PowerShell.Management\Get-ChildItem # malicious code... Write-Output "[+] Malicious code executed!" } # PS> Get-ChildItem # # Directory: C:\Users\Victim\Desktop\Files # # Mode LastWriteTime Length Name # ---- ------------- ------ ---- # -a---- 01.05.2023 13:05 0 file1 # -a---- 01.05.2023 13:05 0 file2 # -a---- 01.05.2023 13:05 0 file3 # # [+] Malicious code executed!


Powershell allows a large number of alternative notations and abbreviations for built-in functions, which can be viewed using the 'Get-Alias' command. Parameters can also be abbreviated as desired, as long as there are no ambiguities (e.g. 'ExecutionPolicy' to 'ep' or 'exe'). All of the commands listed below result in identical output.

Write-Output "Malicious code executed!" write "Malicious code executed!" echo "Malicious code executed!" # PS> Malicious code executed!

Calloperators, Wildcards

With the call operators ('&' or '.') a function can be called. In combination with the 'Get-Command' function (abbr. 'gcm'), which enables the command search with the wildcard character '*', a command call can be shortened and thus obfuscated.

&(gcm *te-Ou*) "Malicious code executed!" # PS> Malicious code executed!

Case, Escape-, Whitespacecharacters

There is no distinction between upper and lower case. Spaces are ignored regardless of their length, allowing malicious code to be hidden at the end of a regular command. The backtick '`' is used as an escape character. Unless this is used before a predefined character such as '`n' for a new line or '`t' for a tab, it is also ignored.

&("`wR"+ "It`e" + "-o"+"ut`Put") "`Ma`licious` code e`xecuted`!" # PS> Malicious code executed!


Through .NET access, the .NET functions can also be addressed instead of native Powershell functions.

[System.Console]::WriteLine("Malicious code executed!") # PS> Malicious code executed!


Another obfuscation option is to assign special character variables '${}' to integers, which can be incremented through the increment operator '++' and casted to suitable characters. The special variable assignment using '${}' allows the use of special characters (including spaces) as variable identifiers. The cast operator and required Cmdlets - such as 'iex' - can be assembled using the subexpression operator '$()', method resolutions and array index operators.

The following special character command was obfuscated with Invoke-Obfuscation by Daniel Bohannon.

${~%=}=+$();${~^}=${~%=};${=[}=++${~%=};${+-}=++${~%=};${=-^}=++${~%=};${*]}=++${~%=};${~``}=++${~%=};${)^}=++${~%=};${)}=++${~%=};${$^}=++${~%=};${.]}=++${~%=};${``}="["+"$(@{})"[${)}]+"$(@{})"["${=[}${.]}"]+"$(@{})"["${+-}${~^}"]+"$?"[${=[}]+"]";${~%=}="".("$(@{})"["${=[}${*]}"]+"$(@{})"["${=[}${)^}"]+"$(@{})"[${~^}]+"$(@{})"[${*]}]+"$?"[${=[}]+"$(@{})"[${=-^}]);${~%=}="$(@{})"["${=[}"+"${*]}"]+"$(@{})"[${*]}]+"${~%=}"["${+-}"+"${)}"];.${~%=}("${``}${$^}${)}+${``}${=[}${=[}${*]}+${``}${=[}${~^}${~``}+${``}${=[}${=[}${)^}+${``}${=[}${~^}${=[}+${``}${*]}${~``}+${``}${)}${.]}+${``}${=[}${=[}${)}+${``}${=[}${=[}${)^}+${``}${=[}${=[}${+-}+${``}${=[}${=[}${)}+${``}${=[}${=[}${)^}+${``}${=-^}${+-}+${``}${=-^}${.]}+${``}${)}${)}+${``}${.]}${)}+${``}${=[}${~^}${$^}+${``}${=[}${~^}${~``}+${``}${.]}${.]}+${``}${=[}${~^}${~``}+${``}${=[}${=[}${=[}+${``}${=[}${=[}${)}+${``}${=[}${=[}${~``}+${``}${=-^}${+-}+${``}${.]}${.]}+${``}${=[}${=[}${=[}+${``}${=[}${~^}${~^}+${``}${=[}${~^}${=[}+${``}${=-^}${+-}+${``}${=[}${~^}${=[}+${``}${=[}${+-}${~^}+${``}${=[}${~^}${=[}+${``}${.]}${.]}+${``}${=[}${=[}${)}+${``}${=[}${=[}${)^}+${``}${=[}${~^}${=[}+${``}${=[}${~^}${~^}+${``}${=-^}${=-^}+${``}${=-^}${.]}|${~%=}") # PS> Malicious code executed!

The Powershell code after resolving the special characters:

[CHar]87+[CHar]114+[CHar]105+[CHar]116+[CHar]101+[CHar]45+[CHar]79+[CHar]117+[CHar]116+[CHar]112+[CHar]117+[CHar]116+[CHar]32+[CHar]39+[CHar]77+[CHar]97+[CHar]108+[CHar]105+[CHar]99+[CHar]105+[CHar]111+[CHar]117+[CHar]115+[CHar]32+[CHar]99+[CHar]111+[CHar]100+[CHar]101+[CHar]32+[CHar]101+[CHar]120+[CHar]101+[CHar]99+[CHar]117+[CHar]116+[CHar]101+[CHar]100+[CHar]33+[CHar]39|iex # PS> Malicious code executed!

The final code after resolving the byte characters:

Write-Output 'Malicious code executed!' | iex # PS> Malicious code executed!


In the following common string obfuscation techniques are shown, which can be used with any programming language in this or similar form.


Characters can be concatenated by '-split', '-join' or the '+' sign.

Write-Output ("Malicious" + " " + "code" + " " + "executed!") Write-Output ("MaliciousXXXcodeXXXexecuted!" -split "XXX" -join " ") # PS> Malicious code executed!


Character strings can be exchanged using the '-replace' option.

Write-Output ("MaliciousXXXcodeXXXexecuted!" -replace("XXX"," ")) # PS> Malicious code executed!


The format operator '-f' can be used to rearrange strings based on number order.

Write-Output ("{1} {0} {2}" -f "code", "Malicious", "executed!") # PS> Malicious code executed!


Characters can be reversed via array slicing.

Write-Output (-join "!detucexe edoc suoicilaM"[24 .. 0]) # PS> Malicious code executed!


Obfuscation can also be achieved using common encoding methods.


The code to be executed can be encoded and passed to Base64. The command encoded in this way can be started via Powershell with the '-EncodedCommand' option.

[Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes("Write-Output 'Malicious code executed!'")) | Write-Host # PS> VwByAGkAdABlAC0ATwB1AHQAcAB1AHQAIAAnAE0AYQBsAGkAYwBpAG8AdQBzACAAYwBvAGQAZQAgAGUAeABlAGMAdQB0AGUAZAAhACcA powershell -EncodedCommand "VwByAGkAdABlAC0ATwB1AHQAcAB1AHQAIAAnAE0AYQBsAGkAYwBpAG8AdQBzACAAYwBvAGQAZQAgAGUAeABlAGMAdQB0AGUAZAAhACcA" # PS> Malicious code executed!


ASCII-encoded commands can be executed after a byte conversion, for example via 'Invoke-Expression'.

[Text.Encoding]::ASCII.GetBytes("Write-Output 'Malicious code executed!'") | Out-String | ForEach-Object {$_.Replace("`n",",").TrimEnd(",")} # PS> 87,114,105,116,101,45,79,117,116,112,117,116,32,39,77,97,108,105,99,105,111,117,115,32,99,111,100,101,32,101,120,101,99,117,116,101,100,33,39 Invoke-Expression( -join [char[]](87,114,105,116,101,45,79,117,116,112,117,116,32,39,77,97,108,105,99,105,111,117,115,32,99,111,100,101,32,101,120,101,99,117,116,101,100,33,39)) # PS> Malicious code executed!


The use of hex encoding is possible in a similar way.

[Text.Encoding]::UTF8.GetBytes("Write-Output 'Malicious code executed!'") | ForEach-Object {"'" + $_.ToString("X2") + "'"} | Out-String | ForEach-Object {$_.Replace("`n",",").TrimEnd(",")} # PS> '57','72','69','74','65','2D','4F','75','74','70','75','74','20','27','4D','61','6C','69','63','69','6F','75','73','20','63','6F','64','65','20','65','78','65','63','75','74','65','64','21','27' Invoke-Expression( -join ('57','72','69','74','65','2D','4F','75','74','70','75','74','20','27','4D','61','6C','69','63','69','6F','75','73','20','63','6F','64','65','20','65','78','65','63','75','74','65','64','21','27' | ForEach-Object { ([Convert]::ToInt16($_.ToString(),16) -as [char]) })) # PS> Malicious code executed!


Similar with octal encoding.

[Text.Encoding]::UTF8.GetBytes("Write-Output 'Malicious code executed!'") | ForEach-Object { [Convert]::ToString($_, 8) } | Out-String | ForEach-Object {$_.Replace("`n",",").TrimEnd(",")} # PS> 127,162,151,164,145,55,117,165,164,160,165,164,40,47,115,141,154,151,143,151,157,165,163,40,143,157,144,145,40,145,170,145,143,165,164,145,144,41,47 Invoke-Expression( -join (127,162,151,164,145,55,117,165,164,160,165,164,40,47,115,141,154,151,143,151,157,165,163,40,143,157,144,145,40,145,170,145,143,165,164,145,144,41,47 | ForEach-Object { ([Convert]::ToInt16($_.ToString(),8) -as [char]) })) # PS> Malicious code executed!


And the same with binary encoding.

[Text.Encoding]::UTF8.GetBytes("Write-Output 'Malicious code executed!'") | ForEach-Object { [Convert]::ToString($_, 2) } | Out-String | ForEach-Object {$_.Replace("`n",",").TrimEnd(",")} # PS> 1010111,1110010,1101001,1110100,1100101,101101,1001111,1110101,1110100,1110000,1110101,1110100,100000,100111,1001101,1100001,1101100,1101001,1100011,1101001,1101111,1110101,1110011,100000,1100011,1101111,1100100,1100101,100000,1100101,1111000,1100101,1100011,1110101,1110100,1100101,1100100,100001,100111 Invoke-Expression( -join (1010111,1110010,1101001,1110100,1100101,101101,1001111,1110101,1110100,1110000,1110101,1110100,100000,100111,1001101,1100001,1101100,1101001,1100011,1101001,1101111,1110101,1110011,100000,1100011,1101111,1100100,1100101,100000,1100101,1111000,1100101,1100011,1110101,1110100,1100101,1100100,100001,100111 | ForEach-Object { ([Convert]::ToInt16($_.ToString(),2) -as [char]) })) # PS> Malicious code executed!


XOR- and SecureString encryption are shown below. Because of the .NET interface all the other .NET encryption algorithms can also be used with Powershell.


A command can be obfuscated using XOR and converted back again. The following example performs an XOR operation on each byte of the instruction with the value '0x42'. The bytes can be reconverted with the same value and ASCII casting.

[Text.Encoding]::UTF8.GetBytes("Write-Output 'Malicious code executed!'") | ForEach-Object { $_ -bxor 0x42 } | Out-String | ForEach-Object {$_.Replace("`n",",").TrimEnd(",")} # PS> 21,48,43,54,39,111,13,55,54,50,55,54,98,101,15,35,46,43,33,43,45,55,49,98,33,45,38,39,98,39,58,39,33,55,54,39,38,99,101 Invoke-Expression (-join (21,48,43,54,39,111,13,55,54,50,55,54,98,101,15,35,46,43,33,43,45,55,49,98,33,45,38,39,98,39,58,39,33,55,54,39,38,99,101 | ForEach-Object { [char]($_ -bxor 0x42) })) # PS> Malicious code executed!

SecureString (AES)

A command can be AES encrypted by using the SecureString function.

# 16, 24 or 32 byte key size $key = ([Text.Encoding]::Default.GetBytes("MySecretPassword")) "Write-Output 'Malicious code executed!'" | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString -Key $key # PS> 01000000d08c9ddf0115d1118c7a00c04fc297eb01000000178e91759fcacf468c330156d58c66bc000000000200000000001066000000010000200000003a3a0b242a307a48d03f6e105db3e209fa3b58021775721753365e5e72bc4f56000000000e8000000002000020000000cf4f9098c27d1aeeb7ba3ecd88252461b812830d3ea934b54e135c270c174a0e500000009e08b1ff1bc319d94ff2611253ecefdc46bb64e5a8a55ed2457cfc516ff6512cdebf9466e52452e4495e1582e2c048097fe6e317ad00692147dd03c8d6db7995028a534a807f22b44d720ebc80d9788a40000000c026f435426f31cdca4c087487fd32ea9ab53498ad8de3bfd2a73b27a59deb46f41c6670f3641a20323573e29ed04d0cc60d07ca3efcec0c6069bcda3d30edbb Invoke-Expression( ([Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR( ("01000000d08c9ddf0115d1118c7a00c04fc297eb01000000178e91759fcacf468c330156d58c66bc000000000200000000001066000000010000200000003a3a0b242a307a48d03f6e105db3e209fa3b58021775721753365e5e72bc4f56000000000e8000000002000020000000cf4f9098c27d1aeeb7ba3ecd88252461b812830d3ea934b54e135c270c174a0e500000009e08b1ff1bc319d94ff2611253ecefdc46bb64e5a8a55ed2457cfc516ff6512cdebf9466e52452e4495e1582e2c048097fe6e317ad00692147dd03c8d6db7995028a534a807f22b44d720ebc80d9788a40000000c026f435426f31cdca4c087487fd32ea9ab53498ad8de3bfd2a73b27a59deb46f41c6670f3641a20323573e29ed04d0cc60d07ca3efcec0c6069bcda3d30edbb" | ConvertTo-SecureString -Key $key))))) # PS> Malicious code executed!


In Powershell the RAW- and GZip compressions are mainly used.


The command can be compressed and encoded as Base64. It can be decoded or decompressed in the reverse order.

$memory_stream = New-Object IO.MemoryStream $compressor = New-Object IO.Compression.DeflateStream($memory_stream, [IO.Compression.CompressionMode]::Compress) $stream_writer = New-Object IO.StreamWriter($compressor) $stream_writer.Write("Write-Output 'Malicious code executed!'") $stream_writer.Close() [System.Convert]::ToBase64String($memory_stream.ToArray()) # PS> Cy/KLEnV9S8tKSgtUVD3TczJTM7MLy1WSM5PSVVIrUhNLi1JTVFUBwA= Invoke-Expression(New-Object IO.StreamReader((New-Object IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String("Cy/KLEnV9S8tKSgtUVD3TczJTM7MLy1WSM5PSVVIrUhNLi1JTVFUBwA="),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd() # PS> Malicious code executed!


The compression is done the same as in RAW except that the 'GZipStream' function is used.

$memory_stream = New-Object IO.MemoryStream $compressor = New-Object IO.Compression.GZipStream($memory_stream, [IO.Compression.CompressionMode]::Compress) $stream_writer = New-Object IO.StreamWriter($compressor) $stream_writer.Write("Write-Output 'Malicious code executed!'") $stream_writer.Close() [System.Convert]::ToBase64String($memory_stream.ToArray()) # PS> H4sIAAAAAAAEAAsvyixJ1fUvLSkoLVFQ903MyUzOzC8tVkjOT0lVSK1ITS4tSU1RVAcAcDv1sCcAAAA= Invoke-Expression(New-Object IO.StreamReader((New-Object IO.Compression.GZipStream([IO.MemoryStream][Convert]::FromBase64String("H4sIAAAAAAAEAAsvyixJ1fUvLSkoLVFQ903MyUzOzC8tVkjOT0lVSK1ITS4tSU1RVAcAcDv1sCcAAAA="),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd() # PS> Malicious code executed!


Powershell code can be hidden at operating system level, so that no malicious script files have to be applied to the victim system. This way persistence can be achieved on the system or the anti-virus protection can be bypassed.

The following shows ways in which Powershell code can be applied to the system and executed without dropping files.


As described at 'Function Overwriting' the native Powershell functions can be overwritten system- and user-wide using Powershell profiles. This way malicious code can be executed automatically as soon as Powershell is started. An automated start of Powershell in the background can be forced via the Task Scheduler or WMI for example.

The following code was saved in the user profile file, which is always executed automatically when Powershell is started.

# Microsoft.PowerShell_profile.ps1 Write-Output "Malicious code automatically executed from: $($PSCommandPath)" # PS> Malicious code automatically executed from: C:\Users\Victim\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

Task Scheduler

Powershell code can be persisted and executed from the Task Scheduler. In the following a Powershell window with the output listed below is displayed every minute.

$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-ep bypass -NoExit -c Write-Output 'Malicious code executed every minute!'" $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 1) $principal = New-ScheduledTaskPrincipal -UserId $env:username -LogonType Interactive Register-ScheduledTask -TaskName "MALICIOUS" -Action $action -Trigger $trigger -Principal $principal # PS> Malicious code executed every minute!


Powershell code can also be stored in the Registry. Here the command is first base64-encoded and stored in a newly created registry key 'XXX'. The value can then be read out with Powershell.

$reg_val = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes("Write-Output 'Malicious code executed!'")) New-Item "HKCU:\SOFTWARE" -Name "Malsoft" New-ItemProperty "HKCU:\SOFTWARE\Malsoft" -Name "XXX" -Value $reg_val powershell -EncodedCommand (Get-ItemProperty "HKCU:\SOFTWARE\Malsoft" -Name "XXX").XXX # PS> Malicious code executed!


The environment variables also offer the possibility to save Powershell code.

In the following example the environment variable 'XXX' is created, where a base64-encoded Powershell command is stored. The command can then be invoked using the environment variable value.

$env_var = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes("Write-Output 'Malicious code executed!'")) [Environment]::SetEnvironmentVariable("XXX", $env_var, [EnvironmentVariableTarget]::User) powershell -EncodedCommand ([Environment]::GetEnvironmentVariable("XXX")) # PS> Malicious code executed!

Environment variables can also be used in this way to create a Cmdlet or a command from individual characters.

In the following the abbreviation for the Invoke-Expression Cmdlet 'iex' is created of the ComSpec environment variable from the 4th, 26th and 25th character (counted from 0).

$env:ComSpec # PS> C:\Windows\system32\cmd.exe &($env:ComSpec[4,26,25]-join'') ("Write-Output 'Malicious code executed!'") # PS> Malicious code executed!

Windows Management Instrumentation (WMI)

Windows Management Instrumentation offers the possibility of reacting to specific events using Event Filters, such as logging onto a system, the start of a specific process or the reaching of a specific date and time. This event can then be linked to an Consumeraction, which can be used to execute PowerShell code.

The following code sets an Event Filter, that triggers every minute at second 42. This filter is connected to a 'CommandLineEventConsumer', which executes the obfuscated PowerShell code, which leads to the output via 'msg.exe'. The configuration saved in this way remains also active after a system restart.

# Filter $filter = Set-WmiInstance -Namespace "root\subscription" -Class "__EventFilter" -Arguments @{ Name = "MyEventFilter" EventNameSpace = "root\cimv2" QueryLanguage = "WQL" Query = "SELECT * FROM __InstanceModificationEvent WHERE TargetInstance ISA 'Win32_LocalTime' AND TargetInstance.Second=42" } # Consumer $consumer = Set-WmiInstance -Namespace "root\subscription" -Class "CommandLineEventConsumer" -Arguments @{ Name = "MyEventConsumer" CommandLineTemplate = "powershell.exe -ExecutionPolicy Bypass -EncodedCommand bQBzAGcALgBlAHgAZQAgACoAIAAnAE0AYQBsAGkAYwBpAG8AdQBzACAAYwBvAGQAZQAgAGUAeABlAGMAdQB0AGUAZAAhACcA" } # Binding Set-WmiInstance -Namespace "root\subscription" -Class "__FilterToConsumerBinding" -Arguments @{ Filter = $filter Consumer = $consumer }
message output

Alternate Data Streams (ADS)

ADS is an NTFS feature, which allows to append several data streams to a file. The default data stream is stored in the '$DATA' attribute.

In the following a base64-encoded command is written to another stream of a regular file named 'hidden', which then can be executed by getting the contents of this hidden stream.

Set-Content '.\RegularFile.txt' -Stream hidden -Value "VwByAGkAdABlAC0ATwB1AHQAcAB1AHQAIAAnAE0AYQBsAGkAYwBpAG8AdQBzACAAYwBvAGQAZQAgAGUAeABlAGMAdQB0AGUAZAAhACcA" powershell -ec (Get-Content '.\RegularFile.txt' -Stream 'hidden') # PS> Malicious code executed!


This section describes some tools to detect and analyze obfuscated Powershell code.


Since Powershell 5 the call to arguments and commands of typical obfuscated scripts - such as 'Bypass', 'FromBase64String', 'DeflateStream' - are automatically logged along with its contents at the warning level in the following event log under event ID 4104.

eventlog output

To get more inside of the logged obfuscated script, it is recommended to turn on 'Script Block Logging' in the Group Policy.

Group Policy > Computer Configuration > Administrative Templates > Windows PowerShell > Turn on PowerShell Script Block Logging

By activating 'Script Block Logging' the resolved script block layers are additionally logged at the verbose level under event ID 4104

eventlog output

For an even deeper logging of Powershell scripts the 'Module Logging' and 'PowerShell Transcription' can also be activated in the Group Policy.


Revoke-Obfuscation is a Powershell module by Daniel Bohannon, which is using two main functions 'Get-RvoScriptBlock' and 'Measure-RvoObfuscation'.

The function 'Get-RvoScriptBlock' enables the extraction of local or extern Eventlogs, while the function 'Measure-RvoObfuscation' is trying to classify a Powershell script as obfuscated or not. This function takes whole Powershellscript files or extracted script blocks as input.

In the following example the script blocks of the Powershell Eventlog are extracted through 'Get-RvoScriptBlock' and then analyzed through 'Measure-RvoObfuscation'.

$skriptblock = Get-WinEvent Microsoft-Windows-PowerShell/Operational | Get-RvoScriptBlock | Measure-RvoObfuscation -Verbose

The output shows the recognized obfuscated scripts and their hash values.

... [34 of 46] NOT OBFUSCATED :: (34496E64527BF40DEAD53CF193D97E55F3...) [35 of 46] NOT OBFUSCATED :: (B5CA27664A1E634B4E551F638FF3A4ACD9...) [36 of 46] OBFUSCATED :: (FF19200070919B612CAFDDBC1F03541FAE...) [37 of 46] OBFUSCATED :: (25E323F9DA18AC845E1C436538C74A2D82...) ...

The scripts can been viewed over the index (-1) of the previously defined variable.

$skript_block[35].Source # @{ScriptBlock=.("{0}{2}{1}"-f'seT','item','-') ('vaRI'+'ABLE'+':U'+'7…

Chainsaw, Hayabusa

Chainsaw and Hayabusa are both CLI applications, which are working with Sigma rules to scan one or more Eventlogs. The applications can be executed with the following commands to scan for suspicious Powershell code:

chainsaw-2.5.0.exe hunt ".\Microsoft-Windows-PowerShell%4Operational.evtx" -s sigma/ --mapping mappings/sigma-event-logs-all.yml
hayabusa-2.2.2.exe csv-timeline -f ".\Microsoft-Windows-PowerShell%4Operational.evtx"

Besides obfuscation rules, there are several other rules for suspicious Powershell code among others:

Rule Suspicious Script ---- ----------------- Powershell Token Obfuscation obfuscated.ps1 Change PowerShell Policies to an Insecure Level obfuscated.ps1 Malicious PowerShell Keywords obfuscated.ps1 Potential WinAPI Calls Via PowerShell Scripts obfuscated.ps1 Suspicious PowerShell Keywords obfuscated.ps1 ...


THOR and the free THOR-lite CLI applications are also working with Sigma rules. Furthermore they are using YARA rules, so obfuscated or suspicious Powershell code can be recognized in other files. For example in LNK files, which can contain malicious Powershell code to load further code or files.


PSDecode is a helpful Powershell module when analyzing obfuscated Powershell code manually. It can recognize and reverse typical obfuscation techniques - like base64- or string obfuscation - so the analyzed code can become much more readable for further analysis.