Making Powershell Forensics Hard for Fun and Profit

I don’t know about you but when I am doing something related to pen-testing, I like to make it hard for the defenders to figure out what I had done. Everyone just gets a better overall and more realistic experience. You need challenges to grow! So, here is just another little trick to make it harder on forensic analysts when it comes to discovering what commands were run from a powershell process.

First thing here, I’d like to cover a few concepts of how powershell works and how we can exploit these nuances of the powershell scripting language. When you load up a powershell console, a “profile” will be loaded in the background. These files can do all sorts of things, from customizing your prompt to preloading variables and aliases for commands you frequently use. Also, there are something called “proxy functions”, which essentially allow you to overload built in cmdlets and the like. If you stick the two together, there are all sorts of things you can do to make forensics harder — I mean seriously, how many people really know a lot about the profile.ps1 file in the defenders realm? Hopefully this will aid in raising awareness of it.

So, here is the game plan:

  1. Create a profile.ps1 file, which will be loaded into powershell consoles every time the process is run
  2. Overload a built in, harmless looking cmdlet — lets go with Write-Output — to do something malicious
  3. Send an obfuscated command to a powershell console, and use the harmless looking “Write-Output” cmdlet, to execute something malicious
  4. Watch the forensics guys scratch their heads as to why someone is executing “Write-Output” commands repeatedly with nothing else to further their foothold

So where is this mythical “profile” file you speak of? Well, there are several, but we will go with only one — which is loaded for every user by default when they run the powershell.exe process. It is located at $SystemRoot\System32\WindowsPowershell\v1.0\profile.ps1, and if it isn’t there already you can just create one.

**PROTIP: Have you seen how when people are running malicious powershell scripts they use the powershell.exe -noprofile switch? This prevents a profile from being loaded upon process execution.

So one step down, lets move on to step two: overloading a built in cmdlet! Here is how you do it. First, we need to get the code for our “Write-Output” cmdlet so that we can retain it’s normal functionality to keep it less suspicious. In order to do this, we have to first get the metadata for the command, then retrieve the code for the cmdlet with the metadata we got for the cmdlet. Here is how you do it.

These commands first fetch the metadata for the command “Write-Output”, then generates the code for the cmdlet based on the retrieved metadata. It should spit out some code that looks like this:

Note the comment block at the end — It even forwards requests for help related to the proxy command on to the actual Write-Output help! Thank you for that powershell. Now we just have to wrap it into a function called Write-Output.

Now that we have our new Write-Output cmdlet, we can simply add our own code into it to make it do what we want. Say we want to get a directory listing every time we do a write-output, we can just put a call to Get-ChildItem inside the proxy function. I do so, and save it in my profile.ps1 file, then start up a new powershell.exe console window and…

Overloaded Cmdlet

Great! Now to make it do something useful, and to hide what we’re doing as well! For obfuscation, I decide that I want to hide what is happening by first xoring the script with a hardcoded key to be executed by the Write-Output command, then reverse it and base64 encode it to make it hard to find out what the script does. I make a little python script to take care of the encoding piece and make my malicious profile.ps1 file which automates the decoding/execution of our obfuscated / hidden script. Here are what they look like (rough draft code — just threw it together real quick, it works, but it isn’t pretty! It’s just to get the point across)

Encoding the command you want to execute:

Overloaded Cmdlet

And for the decoding “profile.ps1” piece…

You can see that the way it works is by first looking for a preset variable called “$dfgDFHdHDFhdfHFD”, and XORs it with a preset key stored in “$XK”. It also modifies the ErrorActionPreference variable temporarily in case the variable doesn’t exist. We should now be able to simply drop this script into profile.ps1, set our variable “$dfgDFHdHDFhdfHFD” to our obfuscated command we want to run, then make a call to Write-Output to have it execute our commands!

Remember that our obfuscated script simple write “Testing 1 2 3” to a file it creates on the desktop. Lets see if it works!



I’d be a little confused if I didn’t know about profiles.ps1 and I saw only those two commands being executed!

Thanks for reading!

Leave a Reply

Your email address will not be published. Required fields are marked *