Custom Runspaces
Before exploring constrained language bypass techniques we
must first explore the various components
of a typical PowerShell implementation.
PowerShell.exe is essentially a GUI application
handling input and output.
The real functionality lies inside the managed
System.Management.Automation.dll
which PowerShell.exe calls into to create a runspace.
It is possible to leverage multithreading and parallel
task execution through either Jobs or Runspaces.
The APIs for creating a runspace are public
and available to managed code written in C#.
This means we could code a C# application that creates a
custom PowerShell runspace and executes our script inside it.
This is beneficial since, as we will demonstrate,
custom runspaces are not restricted by AppLocker.
Using this approach, we can construct a constrained language
mode bypass to allow arbitrary PowerShell execution.
To begin, let’s turn to our Windows 10 development machine
to create a new C# Console App project.
In this project we’ll create a runspace through the
CreateRunspace method of the
System.Management.Automation. Runspaces namespace:
Unfortunately Visual Studio can not locate
System.Management. Automation.Runspaces,
to resolve this we must manually add the assembly reference.
First we’ll right-click the References folder
in the Solution Explorer
and select Add Reference....
In most cases, the reference can be found in existing assemblies,
but in this particular case,
we need to specify a file location instead.
To do this, we’ll select the Browse button
at the bottom of the window,
and navigate to the relevent directory,
where we will select System.Management.Automation.dll
After adding the assembly reference, the previous
errors are resolved.
Now we can dig into the code.
Calling CreateRunspace creates a custom runspace
and returns a Runspace object.
We can invoke the Open method on this object, after which
we may interact with the custom runspace.
With the custom runspace created we can instantiate a PowerShell
object and assign the runspace to it which allows us to pass
and invoke arbitrary PowerShell commands.
This is implemented through the
Create method of the PowerShell class.
The final line of code will set the runspace property
to our custom runspace.
At this point we have created a custom runspace and associated
it with a PowerShell object
now we're ready to pass in a command or script and execute it
As a proof of concept, we’ll simply write the contents of
the $ExecutionContext.Session State.LanguageMode variable to a
file so we can verify the language mode
of the custom runspace.
The PowerShell script is added to the pipeline
through the AddScript method,
after which the Invoke method is used to execute the script.
Finally, the Close method is called to close the
custom runspace for cleanup.
Before compiling the project, we’ll switch from “Debug”
to “Release” mode,
and select 64-bit for compilation.
After compilation
we’ll copy the executable to the Windows 10 victim VM,
and execute it:
AppLocker blocks our C# executable because we executed
it from an non-whitelisted directory.
So let’s copy the executable into a whitelisted directory
to verify our constrained language mode bypass:
Our PowerShell script executed without restrictions inside
the custom runspace, and our code achieved the desired goal.
Good.
Additionally, we did not use PowerShell.exe, which means
that even if an AppLocker deny rule was configured to block
its execution, we could still use this method to run
arbitrary PowerShell scripts.
As an expanded use case, let’s leverage the custom runspace
to fetch and execute the PowerUp PowerShell privilege
escalation enumeration script.
We’ll download the script and copy it to our Kali machine’s
Apache webserver, and update the C# application
to invoke inside the custom runspace.
Once the C# project has been modified
with the new script and recompiled,
we can execute the C# executable
and enumerate possible avenues of privilege escalation
The power of custom runspaces allows us to reuse all our
previous PowerShell-based tradecraft.
However, we are still hindered by AppLocker’s C#
executable rules.
In the next section, we’ll solve this problem by using
a technique called living off the land,
in which we misuse a native Windows application.