In my previous several blog posts (here, here, and here) we covered usage of a really interesting tool released about three years ago: the DotNetToJScript project (https://github.com/tyranid/DotNetToJScript). Although it’s been several years since its original release, it’s still a valuable tool for building payloads during delivery, execution, and persistence when performing Red Team operations. Although many antivirus products are capable of preventing unmodified payloads generated by DotNetToJScript, several that we’ve tested during our Purple Team engagements have NO preventative capability against these payload variants. Furthermore, with a bit of tweaking DotNetToJScript-generated payloads (or building your own randomizing and obfuscating payload generators) you can bypass most antivirus products on the market.
But why do another blog on this topic? Well, I was doing research that was tangentially related to DotNetToJScript for an upcoming blog series and realized that it’s worth the effort to demonstrate basic payloads for the creation of malicious documents (maldocs) that perform process injection using utilizing DotNetToJScript techniques. So, let’s jump right in!
Process Injection?
Process injection is a set of techniques used to inject a malicious payload into a running process to disguise its presence on a computer. Although all common operating systems have some susceptibility to process injection techniques, we’ll be focusing on process injection on Windows, which is fairly well documented. There are 2 types of process injection:
- Self-Injection: this injection variation makes use of a scripting language, or DLL hijack or hooking method to obtain code injection into the currently running process. On Windows, the frontend API function that’s most commonly used is CreateThread()
- Remote Injection: this injection variation commonly requires existing malicious code running in one process that writes malicious code into another process and then triggers execution. On Windows, the frontend API function that’s most commonly used is CreateRemoteThread()
The topic of process injection on Windows has quite a bit of depth, but that’s best left to another blog. Since process injection techniques have been around for a really long time, it’s pretty easy to use Google/Bing to find both walk-throughs and source examples in the meantime. For the purposes of this blog, however, we’ll be making use of Self-Injection during the creation of our payload.
Self-Injection Payload Creation
Since we’ll be making use of DotNetToJScript to create a payload we’ll need to start with a language that supports .NET. C# is a great candidate for this purpose. Let’s start by opening up Visual Studio and creating a new project.
For this project, we’ll select the project type of “Class Library” and give it the name “InjectShellcode”. After we click “OK” we’ll see the following stub project code that’s been created.
Although we can leave the automatically created class name of Class1 , I’m going to change it to something more meaningful and give it the same name as the namespace.
There, that looks nicer. So, the key with using any .NET language for performing process injection is that you’ll need to import unmanaged API calls. To do this you’ll need to first import the namespace System.Runtime.InteropServices and then define those functions. While we’re at it, we’ll also remove unnecessary imports. Specifically, the API functions that you’ll need are:
- VirtualAlloc() – Allocate memory within the current process
- CreateThread() – Create a new thread within the current process
- WaitForSingleObject() – Wait until the object has sent a signal, or timer has expired
You may note that we’ve set our class InjectShellcode to be ComVisible for usage with DotNetToJScript and added a few variables MEM_COMMIT and PAGE_EXECUTE_READWRITE above the API imports. When you develop native code (e.g. unmanaged C/C++) these memory flags are defined ahead of time in system header files. We could instead just supply the values when we actually call our functions, but we want cleaner example code! The next thing we’ll want to do is define an empty class constructor and write our injection function.
You’ll note that we have a stub entry for base64-encoded shellcode. We’ll be generating and inserting that in the next section.
Generate Shellcode and Build Payload
Since Metasploit is freely available, we’ll make use of it for our demonstration payload. It’s worth mentioning at this point that our testing version of Microsoft Office utilizes 32-bit binaries, creating 32-bit processes. As a result, we’ll want to generate x86 Metasploit shellcode for loading into our maldoc. Feel free make use of x86 Cobalt Strike stages in place of these Metasploit shellcodes, as they’ll work just as well.
In the above screenshot we use MSFVenom to create an x86 Meterpreter payload, and output it to a file. The second command performs a base64 encode, stripping all newlines so that we have just one big blob of base64 text. Now we’ll take that base64 code and insert it into our Visual Studio project.
I’ll mention that although in previous blogs we’ve changed the build type from Debug to Release. Although making that change likely wouldn’t have altered the outcomes in the previous blogs, it definitely won’t in this example since we haven’t added any extra code that could be used for debugging purposes. You can try it yourself, building in both Debug and Release mode produce the same-sized output file for this project.
Now that we’ve inserted the shellcode into our project let’s build our project and generate our VBA code! To build the project we can either use the menu or shortcut keys. Via the menu we go to Build —> Build Solution. Via the shortcut you can type Ctrl+Shift+B.
Good deal, the code built correctly! Now that we’re done with this step, let’s open up a command prompt, change directory to our previously built DotNetToJScript project and generate a VBA payload.
And the generated VBA code should look just like the following:
Excellent, this is exactly what we’re expecting. In the previous blog, we prepped a maldoc with a Document_Open() handler to trigger our payload, and we made an additional adjustment to the payload in the VBA Editor to trigger our function. Let’s take care of these steps now. Let’s open up Word, add our Document_Open() event handler, paste in the DotNetToJScript-generated payload, and prep the Document_Open() handler to run our payload.
Then we’ll want to update the Run() function to execute the method we wrote in C# to inject our shellcode: LoadShellcode32()
Note that we didn’t include parenthesis to this method call. Since we’re not passing a value into the method call there’s no reason to add parenthesis, and VBA will also complain to you about doing so.
The next step is to save our Word document as a macro-enabled document and call it InjectShellcode.docm. This is vital, because if we test our code without saving, we’ll have to reinsert our macro as we’ll explain shortly. But once we’ve saved that document, we’ll prep our Metasploit server to receive our shell.
To receive our callback, we make use of Multi Handler, and the appropriate x86 Meterpreter reverse-tcp payload module. We nearly always set ExitOnSession to False, so we won’t need to restart the handler if we want to test our payload a few times.
Once this is done we open up our InjectShellcode.docm file and click “Enable Content” if prompted.
But when we click “Enable Content” we see that it’s not responding. It looks like it’s hung, and we can’t interact with it. Did our payload work?
Yeah, our payload called back! Word will naturally be hung at this point, because we chose to make use of the API function WaitForSingleObject() in our C# payload. This causes Word to hang until our shellcode exits and removing the API call should alleviate the issue.
For good measure, let’s interact with that session just to make sure that staging completed, and we have a fully functional meterpreter callback:
Outstanding!
Additional Remarks
In practice we generally won’t want to use such simple C# code. We’ll likely want to break the functions apart, change variable and function names, and spread any strings around within the C# code. We may even want to take additional steps to compile and pack the initial C# code within another C# project and load the assembly via Assembly.Load() from the System.Reflection namespace to avoid antivirus products.
Additionally, the unmodified DotNetToJScript-generated VBA Macro is likely to be detected by some antivirus solutions as well. To defeat antivirus, you’ll likely want to rename subroutines/functions, break them apart, encode your strings and embed the .NET memory stream (all the hexadecimal strings) somewhere else in the document for later extraction. In my previous life we made use of ActiveDocument.Variables to store our generated memory streams, broken apart into the maximum length allowed by a single document variable, and reconstructed during macro evaluation prior to the decoding/reflection stage.
Lastly, delivering a payload like this via email may require a sandbox bypass if your target utilizes sandboxed detonation of email attachments (i.e. FireEye EX or Proofpoint). These sandbox bypasses are out of scope for this blog post, but if you’re on an internal Red Team at an organization that uses sandbox detonation of email attachments or files downloaded through a proxy you probably already have some bypasses implemented!
Wrap-Up
Throughout this blog post we spent time revisiting the DotNetToJScript project to build a more complex maldoc payload. Specifically, we’ve implemented a Self-Injection method as a C# library which will load a Meterpreter shellcode into the current process’s memory. Then we made use of DotNetToJScript to create a VBA Macro that when evaluated in Microsoft Word will reflectively load our C# library, allowing us to trigger our shellcode injection method, ultimately injecting a Meterpreter agent directly into Word. This powerful technique can be used to load any shellcode, not just Meterpreter, into Microsoft Office products when macros can be enabled.
Thanks for taking the time to read this post. Stay tuned for an upcoming series where we beg the question: It’s [INSERT CURRENT YEAR], why hasn’t your company disabled Office Macros yet?