In our last blog we walked through modifying the UnmanagedPowerShell project to produce a version of PowerShellRunner that’s compatible with DotNetToJScript. The end goal in that blog was to execute PowerShell code from client-side JavaScript from a PowerShell 2 Runspace. In our example, we executed the Get-Process commandlet in a downgraded PowerShell context, which allowed us to avoid more sophisticated PowerShell event logging. If we wanted to, we could try dropping a Cobalt Strike PowerShell one-liner into our code to obtain a 32-bit beacon without ever opening a PowerShell process.
In this blog we’ll take our weaponization a bit further and create a malicious infected document (maldoc) that runs our payload. We’ll stick to running Get-Process for the sake of simplicity, but feel free to plug in your PowerShell or executable of choice when you try testing this out yourself. I’ll also note that although we’re loading the PowerShellRunner .NET assembly, we could make ourselves any other assembly and load it with the DotNetToJScript technique. In our own Red/Purple Team testing we actually make use of a shellcode injector assembly to directly load Cobalt Strike beacons into WScript/CScript/Winword/etc. We won’t cover that methodology here, but it’s remarkably easy to implement if you have spare time on your hands.
The first step we’ll want to take is to prepare our maldoc. Depending on the antivirus solution you’re using the Document_Open() event handler in VBA may result in prevention. We’ve found a few alternatives to Document_Open() in a previous life, but for the sake of simplicity we’ll work with this event handler to kick off code execution. Let’s make a basic VBA subroutine that prints “Hello World” to the VBA immediate window when our document opens.
After creating the basic macro, saving and reopening our document “Hello World” is printed in the Immediate Window at the bottom of the VBA Editor. We did have to click “Enable Content” when we opened our document, but that’s pretty easy to achieve via social engineering a target.
In previous iterations, when we generated JScript payloads the output included a large chunk of Base64 text. This was actually a Base64-encoded .NET MemoryStream object that’s decoded, deserialized and instantiated into an object for us to work with. In fact, when generating a VBScript payload that MemoryStream is encoded in Base64 as well. However, when generating a VBA payload that MemoryStream is instead encoded as a hexadecimal string. In my previous life, we learned that due to differences between VBScript and VBA it’s actually sometimes easier to encode/decode a hexadecimal string in VBA.
However, when it comes to using DotNetToJScript techniques, the project intentionally breaks down the process to load a .NET assembly line by line. This is so that when you and I are learning about the techniques we can track what the code does. As it turns out, porting the VBScript code directly to VBA while using the same sequence of script commands doesn’t work. DotNetToJScript makes use of ComVisible .NET classes to perform the decoding, deserialization and instantiation steps, and as it turns out VBA doesn’t correctly pass some of these .NET objects among method calls. It’s possible to convert the VBScript code into VBA by passing results of .NET object operations as parameters to other .NET object methods, we won’t be covering that in this blog post. Instead we’ll simply use the drop-in VBA code that DotNetToJScript generates for us.
We’ll want to use the following command to generate a VBA payload that works with our previously compiled PowerShellRunner assembly:
Note that we set the language parameter to VBA, and set the output to be test.txt. Let’s open this output and see what it looks like:
The VBA payload is definitely using hexadecimal strings to encode the .NET MemoryStream. Now to make use of this payload, let’s copy/paste it into our maldoc:
If we remember from the previous blog, we could have used another parameter when generating our DotNetToJScript payload to include additional scripting code that would operate on the PowerShellRunner object that’s created. Instead we’ll just jump to the bottom of this macro and add in our output statement. This is what our macro looks like before modification:
And here’s the code we add to run the Get-Process commandlet:
Let’s test this macro by running the Run() subroutine:
Perfect! This is exactly what we wanted to see. The very last thing we’ll do to weaponized this payload is to modify the Document_Open() handler to call the Run() subroutine instead of printing “Hello World”:
Now the next time we open our macro it’ll call the Get-Process commandlet! Except, this isn’t super useful. Let’s make one more modification to our macro, and instead of run a commandlet we can have it open calc.exe:
And then we save/reopen:
Although these were fairly basic examples of how to use DotNetToJScript, and how to integrate UnmanagedPowerShell into the mix, you can achieve some fairly powerful results by using these techniques against your Blue Team or during a Red Team engagement. In my previous life, we used these techniques, albeit with heavy obfuscation of the macro code, to level up our Blue Team. I use variations of these techniques during my Red/Purple Team engagements at White Oak Security to bypass prevention and detection controls.
It’s also important to note that the technique used in DotNetToJScript is not the only way to achieve reflective loading of .NET assemblies via native Windows scripting languages. It’s fairly vital that if you’re on an internal Red Team and can achieve code execution using these techniques that you press your Blue Team to implement detection. Whether it’s running UnmanagedPowerShell from VBA or using a .NET shellcode injector, these are techniques that your Blue Team should be able to detect. Furthermore, if your organization continues to permit Office Macros or hasn’t embarked on application whitelisting, this may be the ammo you need to make the critical point of why it’s necessary to improve on prevention capability.
As we close out this blog series (part 1 and part 2), I thank you for following along. We’ve been able to demonstrate some powerful capability with a few Open Source projects to achieve code execution via a unique reflective .NET Assembly injection technique.
I’d also like to thank James Forshaw and Lee Christensen for their outstanding contributions to the security community by releasing these tools. Without people tinkering, we wouldn’t have such nice tools to use!