Cat and Mouse in the Age of .NET
Penetration testers have long exploited the PowerShell scripting language to gain a foothold in systems and execute an attack. PowerShell is installed on every Windows machine, has direct access to the Windows application programming interface (API), and is rarely disabled. Over the years, red teams have created extensive tool suites to leverage the language for offensive tradecraft. Blue teams have kept pace by developing defenses to stop these tools. Eventually, changes in the PowerShell landscape caused the toolsets to shift their target from PowerShell to .NET. This blog post explores that shift, what .NET is, how it normally runs on Windows systems, and how red teams weaponize it. This information can help blue teams prepare their defenses and possibly their threat hunting techniques. Red teamers can use this information to better understand what their tools do during an attack and create more evasive exploits.
In 2015, Microsoft published a blog post discussing new features they planned to release in version 5 of PowerShell. These features were geared to arm blue teams with tools, notably deep script block logging and the antimalware scan interface (AMSI), to log and catch activities running in PowerShell in their environments.
A script block is the base level of executed PowerShell code. Malicious actors often obfuscate their PowerShell scripts to the point where the system will not be able to break it down into the original intended script block before it executes. Lacking the original, non-obfuscated script blocks makes it difficult to spot the possible malicious behavior of a script. In PowerShell version 5, Microsoft introduced deep script block logging, which enabled defenders to log every script block that processes on the system. Deep script block logging allowed defenders to break down obfuscated scripts into readable script blocks.
With the 2015 release of Windows 10, Microsoft also introduced a new security interface: the antimalware scan interface (AMSI). The purpose of AMSI was to standardize the integration of anti-malware products with applications and services on a machine. AMSI could stream file data, memory streams, URLs, and more to anti-malware software before a payload executes on the system. This capability proved a real problem for offensive PowerShell tradecraft because it allowed defenders to learn what a script might do before it executed.
PowerShell version 5 and Windows 10 loaded up blue teams with new defense tools, which left many red teams in an odd position. Both deep script block logging and AMSI could be bypassed, but every attempt to bypass a defense was another avenue for the red team to get caught. Red teams decided it was not worth the risk. They started looking at something deeper in PowerShell.
.NET is a software framework from Microsoft that allows language interoperability in a software environment. Originally a closed-source development language for Windows applications, .NET has existed in different forms since the early 2000s. In 2003 Microsoft started packaging .NET, complete with a common language runtime (CLR) environment and base class libraries to run applications, with subsequent versions of Windows.
This combination caught the attention of offensive tool developers for several reasons, one being that they could use .NET to bypass the version 5 PowerShell security monitoring. Deep script block logging monitors when PowerShell executes script blocks, not the functions that the modules use in the underlying C# language. PowerShell scripts could easily be retooled into .NET assemblies by using the C# functions that the PowerShell modules utilize. Once the new code had been sneaked into an environment, offensive developers had some guarantee their executables would run because .NET was part of every Windows installation.
A few key features of .NET Core allow malicious actors to weaponize it, but the main one is the CLR, a virtual environment inside which .NET interprets its code at runtime. When it is needed, the CLR can be loaded into individual processes, which are then called managed processes. Processes without CLR are unmanaged. When a process is managed, a user can load .NET assemblies for execution inside the process. In-process execution is desirable to adversaries because it helps evade detection on the system by masking itself inside a nonmalicious process. It also allows malicious .NET assemblies to run code in the security context of the processes, for example, as administrator.
.NET code is interpreted at runtime and can be in written in several different languages. Consequently, .NET utilizes a common intermediate language that is generated when the code is assembled. This assembled code is placed into .NET assemblies, which are stored in a portable executable (PE) format. Once in this format, the assemblies are fed into a just-in-time compiler that translates the intermediate code into useable machine code. .NET allows for the use of application domains, which isolates the payload from the process during execution, and it also provides the ability to unload the assembly from memory by unloading the application domain. Adversaries use this ability to clean up their activity once their payloads have run.
.NET Bites Back
The goal for red teams is now to load a malicious .NET assembly into processes for execution on the victim machine. Luckily for them, the Windows API has the Assembly.load function, which allows users to load .NET assemblies from memory, to avoid the payload touching the disk and getting detected. Unfortunately for red teams, this function works only in managed processes. If they want to inject an unmanaged process, they would have to take steps like the following:
- Create a sub-process under the process you currently reside in.
- Use reflective dll injection to load the CLR into the process.
- Use the same reflective dll injection or another to load the payload from memory and execute at the entry point of the payload.
Using these techniques to inject into managed and unmanaged processes, adversaries can now use .NET to avoid PowerShell-specific monitoring and focus on evasion of other defenses.
Red teamers have other considerations than just executing the attack and accomplishing the main goal. They also have to evade detection, during and after, by tools such as AMSI. Defenders can easily monitor for processes loading libraries such as the CLR, so it is advantageous to migrate or inject into processes that already have CLR loaded. In addition, .NET assemblies cannot be unloaded from memory after they have been loaded, unless the application domain it runs in is unloaded. Attackers will consider creating other application domains in a process to have the ability to load and unload assemblies when they are done running.
Cat and Mouse
Red teams and blue teams have long been playing cat and mouse, developing new attacks and finding new ways to negate them. The shift away from PowerShell attacks to .NET attacks is another new chapter in Windows tradecraft.
Many developers have already adapted their PowerShell tool suites, such as PowerSploit, Inveigh, and Empire, to .NET. Ghostpack, for example, is a retooling of the PowerSploit tools. Some, like the developers of Donut, have gone even further and created tools for payload generation that allow for injection of .NET assemblies into remote processes in the form of shell code.
Blue teams can develop more detection techniques based on these tools, such as Windows API monitoring to detect possible malicious use of the Windows API in .NET applications. At the same time, red teams can increase their awareness of how their tools function and create more complex tools to evade the detection.