Lessons Learned With Manual PowerShell Obfuscation


I recently ran into a situation where I wanted to obfuscate a PowerShell script, however Invoke-Obfuscation would not work due to constraints on the machine. Black Hills Information Security has a great piece on how to manually obfuscate scripts to get them past AV, however I’ve found that you may need to go a bit deeper than what’s discussed in that particular article. Here are some lessons I’ve learned with manual PowerShell obfuscation.

Note: The methods discussed here are strictly for the purposes of evading Anti-Virus and should not be considered valid techniques for the purposes of EDR evasion, or anti-forensics.

Before Getting Started

Prior to getting started you’ll need to get an environment set up for testing payloads before transferring them to the real target machine.

  • Spin up a VM with the same Operating System , PowerShell version and .NET version as your target. This will be your test target box.
  • If possible, get your hands on a copy of a trial of the AV you’re attempting to bypass. Update the signatures then disable the network adapter on your test VM to ensure the AV doesn’t phone home with updates on the script you’re trying to get past it.
  • Spin up a development VM – personally I choose to do this in Windows 7 using an IDE – either PowerShell ISE or Visual Studio Code. It’s important not to install either of these on your test target box as they will install .NET dependencies, which invalidates your testing platform.
  • This writeup also assumes you’ve performed the tasks outlined in the Black Hills article prior to starting.

Step 1 – Determine What AV is Flagging On

Even with comments removed, the Invoke-Mimikatz script is approximately 2500 lines of code. Prior to starting any work, it’s helpful to narrow down which section(s) of the script AV is flagging on. This is a tedious task, no doubt, but one that will help take away a bit of the guessing game. The process goes like this.

  • Delete the first half of the lines of code, save the file and scan it w/ the target AV. Is it flagged? If yes then a signature is being captured in the last half of the script, if it’s declared clean then a signature is being picked up in the first half of the code.
  • Delete the first quarter of the code. Repeat logic above.
  • Repeat the steps above until you get the code to as small as possible without getting flagged, and you then know the area of code you need to work on with respect to obfuscation.

Caveat : What you’ll discover is that sometimes deleting the first few lines of a script will declare it as clean, but this happens because you’ve deleted a reference to a function that the script is actually flagging on. If you obfuscate the top of the script heavily and it’s still getting flagged, jump to the section of code the function is referencing and repeat the steps above to isolate the issue. It’s helpful to narrow in on unique strings (ie sekurlsa::logonpasswordsin the case of Mimikatz).

Step 2 – Beginning Obfuscation

Prior to starting your obfuscation it’s important to note that testing the functionality of your script after each change is important. There’s nothing worse than performing 10 modifications to a script only to find out it’s failing to function and you have no idea which change caused the break.

Using Aliases

PowerShell provides aliasing functionality, which allows you to both leverage existing aliases (ie ls in PowerShell is really an alias for the Get-ChildItem cmdlet), as well as create your own aliases that point to the various cmdlets that may be located in your script.

In the Invoke-Mimikatz script, there are multiple cmdlets which we can create aliases for, then replace all instances of the Cmdlet with it’s alias throughout the script. This technique really just tries to change the signature of the entire script as much as possible.

You can create new Aliases with the following syntax (noting that sal is an Alias for Set-Alias). The following line will create an Alias called AdMe for the cmdlet Add-Member. Once you’ve created the alias (do so at the top of the script), do a Find for all instances of Add-Memberand replace it with AdMe.

sal -Name AdMe -Value Add-Member

It should also be noted that there might be PowerShell cmdlets throughout the script that can be swapped for alternate text which will accomplish the same functionality. One such example is the  Out-Null cmdlet, which can easily be swapped for > $nullwithout compromising functionality.


In the instance where you know a string is causing you issues, bypassing might be as simple as encoding the string to Base64 and then decoding it within the script. For instance, the string “sekurlsa::logonpasswords exit” is a dead giveaway of mimikatz. Luckily for us, Base64 provided an easy mechanism to get this string past the AV in question.

Within the script there’s a section that looks as follows

$ExeArgs = "sekurlsa::logonpasswords exit"

Changing all instances of $ExeArgs to $CmdOptions and then Decoding the Base64 for "sekurlsa::logonpasswords exit" provided an easy workaround.

$CmdOptions = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("asdasda=="))

String Concatenation

String Concatenation, which is a fancy term for “Joining of strings”, is another mechanism which can be utilized when AV scanners are looking for a specific string. This can be achieved by simply breaking out the strings into separate quotes and “adding them together”.

$ExeArgs = 'se'+'kur'+'las:'+':lo'+'gonp'+'assw'+'or'+'rds '+'exit'

Alternatively, and perhaps more preferably, you can assign these strings to variables, jumble them up and then re-assemble them.

Now everything is re-assembled

$ExeArgs = $cmd4 + $cmd1 + $cmd5 + $cmd2 + $cmd3 + $cmd8 + $cmd7 + $cmd9 + $cmd6

Dummy Code & Content

Again, the name of the game here is change the signature of the file as much as possible. Putting in dummy comments and code is another easy way that you can deceive some AV. Commenting is easy (You can even just put in blank lines), but when you’re putting in dummy code you want to be sure to add stuff that will have no affect on the functionality of the script. Your imagination is your only limit here, but a very simple example of some code that does nothing is as follows.

Remove Unnecessary Functionality

Just as we advise organizations to only run what’s absolutely necessary in an organization to reduce their attack surface, it’s also advisable for offensive security professionals to strip unnecessary functionality from tooling to reduce the likelihood of “flagged” lines of code.

For example, a script like PowerView is massive and provides a tonne of functionality, however some of that code would only be used in specific scenarios. By stripping out code and functions you don’t require for your current engagement you reduce the likelihood of AV identifying your script.

Back Ticks

Back ticks (`) are an easy way to break a string into multiple pieces without affecting functionality.

String Formatting

String formatting is another simple way to obfuscate potential text and phrases that may be flagged on by AV. String formatting in PowerShell is similar to other languages, and is invoked with the -fparameter.

A simple example of string formatting is as follows.

Each number contained within the {} specifies the location of what should be substituted within the string. In the example above the capital T represents location 1, the lower case e is location 0, the s and t are positions 3 and 2 respectively. When you assemble them together with string formatting you’re able to create values without utilizing exact strings which may be flagged upon.


Obfuscating scripts and creating payloads that bypass security controls can be a very time consuming endeavour so be patient while you conduct your work. It’s worth noting that many of these may not be effective against AMSI, which may require a bypass.