Abusing and Detecting LOLBIN Usage of .NET Development Mode Features


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:
Source: Microsoft Docs
  • 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):

Next, The DEVPATH environment variable is set to point to a desired load directory. As described in this post, there are several ways to create/inject an environment variable. In this case, the DEVPATH variable is assigned (temporarily) within the shell to point to the non-existent “c:\zzz” path for chosen effect:

When running UevAppMonitor.exe in the temporary directory that contains the UevAppMonitor.exe.config file, the CLR attempts to locate the assembly references in “C:\zzz” before properly resolving them in the GAC folders (since a reference is not found):

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.

Defensive Considerations

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.

Related Research

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:


Thank you for taking the time to read this post!