Bypassing Application Whitelisting with runscripthelper.exe

Introduction

While running through one of the labs for our PowerShell course, I stumbled upon a Microsoft-signed PowerShell host process in System32 — runscripthelper.exe. This program was just introduced in Windows 10 RS3 and all it does is read in PowerShell code from a specific directory and execute it. As a byproduct of the way in which it executes PowerShell code, it makes for a great constrained language mode bypass (i.e. generic application whitelisting bypass).

Here is the decompiled entry point method of runscripthelper.exe:

Entry point of runscripthelper.exe

As you can see, the program accepts three command line arguments:

  1. Argument #1 must be “surfacecheck” in order to execute the ProcessSurfaceCheckScript method which is passed the 2nd and 3rd command line arguments.
  2. Argument #2 is comprised of a full path to a script and is compared against the “ k_utcScriptPath” global variable: \\?\%ProgramData%\Microsoft\Diagnosis\scripts
  3. Argument #3 is expected to be a path to a directory that exists. Command output is expected to be logged to this directory.

So right off the bat, it appears as though a constraint for execution is that a script must be located in the %ProgramData%\Microsoft\Diagnosis\scripts directory. By default (at least on my system), a standard user doesn’t have write access to that directory. Ideally, I would much rather attempt a bypass as a non-elevated user. So if I could somehow control the contents of %ProgramData% when runscripthelper.exe starts, I could have the program execute a script from a directory I control. Back to this soon but let’s first look at the ProcessSurfaceCheckScript method to see what it executes:

PowerShell invocation method in runscripthelper.exe

So literally all the ProcessSurfaceCheckScript method does is read the contents of a script (regardless of file extension, by the way) and execute it. On a system running AppLocker or Device Guard (now Windows Defender Application control), since it is likely that this program will be whitelisted as part of a Microsoft publisher rule, any PowerShell code executed in the process will execute in full language mode, hence bypassing the restrictions imposed on an attacker in constrained language mode.


Weaponization

As an attacker, we need to control the contents of %ProgramData% to get it to point to a directory we control. There are likely multiple ways of doing this but setting the EnvironmentVariables property in an instance of a Win32_ProcessStartup class upon calling Win32_Process Create is one of the methods I’m aware of. Additionally, WMI also offers the benefits of remote invocation as well as there being several WMI host applications that are unlikely to be blocked by application whitelisting policies. Also, many child processes will fail to load if you don’t pass many of the expected environment variables.

So upon controlling the environment variables passed to runscripthelper.exe, here is an example command line invocation that will execute our payload:

runscripthelper.exe surfacecheck \\?\C:\Test\Microsoft\Diagnosis\scripts\test.txt C:\Test

Here is a fully-weaponized PowerShell implementation of the bypass:

runscripthelper.exe bypass using PowerShell

Just to show that it’s also possible to execute the bypass without PowerShell, here’s demo of running the bypass in wbemtest.exe:

runscripthelper.exe bypass using wbemtest.exe

In the wbemtest.exe example, my payload was stored in C:\Test\Microsoft\Diagnosis\scripts\test.txt. I also supplied the following environment variables:

“LOCALAPPDATA=C:\\Test”
“Path=C:\\WINDOWS\\system32;C:\\WINDOWS”
“SystemRoot=C:\\WINDOWS”
“SESSIONNAME=Console”
“CommonProgramFiles=C:\\Program Files\\Common Files”
“SystemDrive=C:”
“TEMP=C:\\Test”
“ProgramFiles=C:\\Program Files”
“TMP=C:\\Test”
“windir=C:\\WINDOWS”
“ProgramData=C:\\Test”

Mitigations

If using Device Guard (now Windows Defender Application Control), you can block this binary by merging the following rule into your existing policy using the guidance described here:


Detections

PowerShell code executed through runscripthelper.exe (as is the case with any PowerShell host process) is subject to scriptblock logging and will generate 4014 events accordingly.

Example scriptblock logging of the bypass

Additionally, event ID 400 in the “Windows PowerShell” event log will capture command-line context of runscripthelper.exe.

Example engine logging of the bypass

What is runscripthelper.exe?

The following strings in the binary stood out to me:

InvokedFromUIF
k_utcScriptPath

After a little Googling, it became evident that UIF stands for “User Initiated Feedback” and UTC stands for “Unified Telemetry Client”. So clearly, this binary is used for some sort of telemetry collection purposes. Yay for Microsoft pushing unsigned PowerShell code (with likely no QA) to my computer and executing it. I’m more than happy to block this binary in my Device Guard code integrity policy.


Conclusion

So here is yet another abuseable signed application and further evidence that there is no limit these type of binaries as it seems new ones are introduced with each Windows release. This also helps confirm one of the fundamental challenges with application whitelisting (AWL) — in order to have a bootable, functional system that can actually update, you tend to need to blanket whitelist any code signed by Microsoft. As a byproduct, those who are serious about maintaining a strong whitelisting policy need to actively monitor articles like this and update their blacklist rules accordingly. Depending upon the whitelisting solution, such blacklist rules can be effective. The list is just difficult to maintain and it will always be growing. Just to be clear, this is not a weakness of AWL— it is a challenge. I personally use AWL and cannot speak highly enough of its efficacy. The overwhelming majority of attackers still drop untrusted script/binaries that are trivial to block with even the most basic whitelisting policy.

Independent of AWL, such abuseable binaries also offer attackers the ability to hide behind an otherwise benign, “trusted” application. So the lesson here is that one person’s whitelisting bypass is a generic addition to an attacker’s post-exploitation tradecraft arsenal that needs to be detected regardless of the presence of AWL.

Lastly, if anyone ever happens to capture any of the PowerShell code pushed by Microsoft for use by runscriphelper.exe, please upload it somewhere and let me know! Thanks!