As discussed in this previous post, Microsoft has provided valuable (explicit and implicit) insight into the inner workings of the functional components of the .NET ecosystem through online documentation and by open-sourcing .NET Core. .NET, in general, is a very powerful and capable development platform and runtime framework for building and running .NET managed applications. A powerful feature of .NET (on Windows in particular), is the ability to adjust the configuration and behavior of the .NET Common Language Runtime (CLR) for development and/or debugging purposes. This is achievable through various configuration interfaces such as environment variables, registry settings, and configuration files/property settings.
From an attacker’s perspective, configuration adjustments provide interesting opportunities for living-off-the-land-binary (lolbin) execution. In this short post, we’ll highlight a technique for turning pretty much any .NET executable into an opportunistic lolbin that abuses .NET development features by overriding Global Assembly Cache (GAC) path lookups. Furthermore, we’ll examine several defensive considerations for detecting malicious use of the presented technique.
The General Technique
Manipulating .NET development features to override the GAC is actually quite simple. As summarized from this Microsoft Doc, the following is required:
- Configuration File – An element called developmentMode must be specified and set to true in the application configuration file (e.g. app.config) or the machine configuration file. Note: The machine configuration file (machine.config) has system-wide scope and requires administrator-level privileges for modification. Application configuration files can be created and/or modified at an unprivileged level (e.g. as placed in a writable directory along with a sacrificial/target assembly). An example assembly configuration appears as follows with the developmentMode element:
- Environment Variable – An environment variable called DEVPATH must be set with a value that points to a file system directory path. When set, the CLR attempts to ‘resolve’ the target assembly dependencies in the path before locating the ‘unfound’ assemblies in the GAC (which is the default behavior).
Let’s take a look at two loading behavior examples when executing UevAppMonitor.exe, a .NET application natively located in System32…
Example 1: Normal Execution
When executed under normal conditions, the UevAppMonitor.exe application will load dependencies, including unmanaged libraries out of System32, the CLR components, and referenced managed libraries from the GAC directories as noted in the following ProcMon screenshot.
Example 2: Modified Execution
Interestingly, UevAppMonitor.exe actually has an application configuration filed located in the System32 directory:
For simplicity and to demonstrate the loading behavior, UevAppMonitor.exe and UevAppMonitor.exe.config are copied to a temporary directory, and UevAppMonitor.exe.config is modified with the required developmentMode element (as noted above):
As a result, managed binaries can be used for a number of use cases with control over the directory lookup path. This includes but is not limited to the possibility of application control bypass with managed assembly modification (depending on the solution), general DLL hijack/sideloading, and persistence. Further exploration of these particular offensive use cases is an exercise for the reader.
Consider these defensive opportunities for detection:
Monitor (and Hunt for) Application Configuration Files: There are numerous EXE/DLL app.config files located on the Windows Operating system to control .NET functionality including .NET managed binaries and unmanaged binaries that leverage managed components. Many of these .config files are Windows or Microsoft signed. Modification of existing .config files (e.g. system.config) and/or creation of new configuration files in another directory location should be a red flag. Furthermore, the developmentMode element in any app.config should be scrutinized (with very few exceptions such as development environments). This element should not appear legitimately in a production/working environment (unless introduced by accident).
Add this experimental Sysmon rule to @SwiftOnSecurity‘s Sysmon-Config under the EVENT 11: “File created” section or under @olafhartong‘s Sysmon-Modular “11_file_create” rules for detecting .config file creation (+ modification) events:
<TargetFilename condition="end with">.config</TargetFilename>
Monitor for DEVPATH Environment Variable Creation Events: Temporary environment variable usage is likely difficult to detect, but for permanent additions (e.g. for actor persistence), the following experimental Sysmon rule can be added to Sysmon-Config under the “SYSMON EVENT ID 12 & 13 & 14 : REGISTRY MODIFICATION” section or under the Sysmon-Modular “12_13_14_registry_event” rules to detect the addition of the DEVPATH variable:
<TargetObject condition="end with">\Environment\DEVPATH</TargetObject>
Leverage Application Control Audit Features: Application Control is not just a prevention mechanism. Leverage audit mode features of application control solutions to enhance detection telemetry (e.g. for detecting unsigned DLL loads that would have been prevented) without block rules. For AppLocker, the event log is located at: Event Viewer -> Application and Services Logs -> Microsoft -> Windows -> AppLocker. For WDAC, the log is located at: Event Viewer -> Application and Services Logs -> Microsoft -> Windows -> Code Integrity -> Operational. 3rd party security solutions may log events to another location.
If interested, check out research from others that have discovered interesting ways to leverage .NET configuration features (e.g. CLR Configuration Knobs) for different use cases:
- Hiding your .NET – COMPlus_ETWEnabled by Adam Chester (@_xpn_) uses a very interesting CLR configuration knob to disable Event Tracing for Windows (ETW).
- Bring your own .NET Core Garbage Collector by Paul Laîné (@am0nsec) focuses on abusing the GCName CLR configuration knob to specify a custom Garbage Collector (dll) for loading arbitrary code and bypassing application control solutions.
- Configuring our Machine for Persistence by Matt (@NotoriousRebel1) describes a method for privileged persistence by abusing the AppDomainManager.
- Casey Smith (@subTee) for all things .NET including COR_PROFILER unmanaged code loading for defense evasion/UAC bypass and the Ghost Loader AppDomainManager injection technique (as further described by @netbiosX) just to name a few.
Thank you for taking the time to read this post!