Cyber Security Technical Blog

Advanced TTPs – DotNetToJScript – Part 2

Written by Jerry Odegaard | Oct 11, 2024 5:16:24 PM

Last time we went through an overview of the awesome DotNetToJScript project, and why you should be interested in it for your Red/Purple Team testing. In this blog we’ll cover modifying the UnmanagedPowerShell project’s PowerShellRunner to be compatible with DotNetToJScript payloads.

Modifying UnmanagedPowerShell

The UnmanagedPowerShell project (https://github.com/leechristensen/UnmanagedPowerShell) is a reference for accessing PowerShell Runspaces (read: interpreter) from Unmanaged C++ code. It includes a Managed (.NET) subcomponent called PowerShellRunner that is responsible for loading a PowerShell Runspace and executing arbitrary PS commands. We’ll first make some modifications of the PowerShellRunner class so that it’s compatible with DotNetToJScript.

First we’ll open the project up in Visual Studio:

Then let’s open up that PowerShellRunner.cs source file:

Now we’ll want to refer to both the DotNetToJScript project, as well as the ExampleAssembly code within that project, on how to modify PowerShellRunner to work with DotNetToJScript. From the project’s README file (https://github.com/tyranid/DotNetToJScript/blob/master/README):

In the assembly implement a class called TestClass which does something you want to do in the public, parameterless constructor.

Ensure it’s public. Then pass to this tool the path to the .NET assembly. If you annotate the class with the ComVisible attribute you can even interact with the object after it’s created. e.g.

If we look at the ExampleAssembly.cs file in the DotNetToJScript project we can see that James Forshaw included both a public constructor as well as setting the class to ComVisible:

So that looks pretty easy. We’ll need to add using System.Runtime.InteropServices to the PowerShellRunner.cs file so that we can set our class to ComVisible. Also, so that we can interact with PowerShellRunner we’ll want to add a public method. In the original version of the code there’s a public static (class) method called InvokePS() that’s run from Unmanaged C++ code to execute arbitrary PowerShell. We’ll keep this method and call it from a member method that we’ll call

InvokePSMember():

Note that we also changed the build type to Release in the above screenshot. Although this shouldn’t cause you any issues you might want to set it there just to be safe. Next we’ll compile our modified UnmanagedPowerShell project with CTRL+Shift+B, and open a command prompt to check the output folder:

This is probably a good time to mention that the default configuration of the UnmanagedPowerShell project is to build PowerShellRunner against the .NET 2.0 runtime. As long as we have the .NET 2.0 runtime installed on a victim host this script will run correctly, executing a PowerShell 2 Runspace, avoiding advanced PowerShell logging!

Now that we’ve successfully built the modified PowerShellRunner let’s generate a DotNetToJScript JScript payload.

Note that we include 2 additional flags:

  • -d (add debugging support so we can output results from InvokePSMember())
  • -c “PowerShellRunner.PowerShellRunner” (specify the namespace.class we want to load)

Now if we look back to the DotNetToJScript command line options we see that there was an option to include an additional script file. That script file would contain commands that operate on the object instantiated (a PowerShellRunner object) during execution of our payload. Instead of specifying this we’ll just open up our payload in Notepad++ and add an additional line. See before:

And with the modification to run the Get-Process PowerShell commandlet with output:

Since we’re just testing that our code works right, we’re only going to run Get-Process right now. You could substitute a binary to run like the standard calc.exe. Now, since this will output some text we’ll want to run our payload with CScript instead of double-clicking to trigger WScript so that we can see the output:

And there we have it! Running PowerShell code directly from native Windows scripting language without ever executing a PowerShell process! 

Wrap-Up

We’ve made some progress in weaponizing a DotNetToJScript payload. We repurposed the PowerShellRunner component from the UnmanagedPowerShell project to execute PowerShell commands directly from client-side JavaScript. Our payload completely avoids sophisticated PowerShell logging in environments that still have .NET 2.0 installed, which in our experience is most environments. In the next blog, we’ll take a look at further weaponizing DotNetToJScript by manually building a malicious document (maldoc) to execute our payload!