Lateral Movement Using Outlook’s CreateObject Method and DotNetToJScript

In the past, I have blogged about various methods of lateral movement via the Distributed Component Object Model (DCOM) in Windows. This typically involves identifying a DCOM application that has an exposed method allowing for arbitrary code execution. In this example, I’m going to cover Outlook’s CreateObject() method.

If you aren’t familiar with CreateObject(), it essentially allows you to instantiate an arbitrary COM object. The issue with abusing DCOM applications for lateral movement is that you are normally at the mercy of the method being used. The majority of the talked about techniques involve abusing a ShellExecute (or similar) method to start an arbitrary process or opening a malicious file on the target host, which requires placing a payload on disk (or a network share). While these techniques work great, they aren’t ideal from a safety perspective.

For example, the ShellBrowser/ShellBrowserWindow applications only allow you to start a process with parameters, which makes the technique susceptible to command line logging. What about the Run() methods for macro execution? Well, that requires the document with the malicious macro to be local or hosted on a share (not exactly ideal).

What if we could get direct shellcode execution via DCOM and not have to worry about files on the target or arbitrary processes such as powershell or regsvr32? Luckily, Outlook is exposed via DCOM and has us covered.

First, we need to instantiate Outlook remotely:
$com = [Type]::GetTypeFromProgID('Outlook.Application’,’192.168.99.152’)
$object = [System.Activator]::CreateInstance($com)

After doing so, you will have the CreateObject() method available to you:

 

As discussed above, this method provides the ability to instantiate any COM object on the remote host. How might this be abused for shellcode execution? Using the CreateObject method, we can instantiate the ScriptControl COM object, which allows you to execute arbitrary VBScript or JScript via the AddCode() method:

$RemoteScriptControl = $object.CreateObject(“ScriptControl”)

If we use James Forshaw’s DotNetToJScript technique to deserialize a .NET assembly in VBScript/JScript, we can achieve shellcode execution via the ScriptControl object by passing the VBScript/JScript code to the AddCode() method. Since the ScriptControl object was instantiated remotely via Outlook’s CreateObject() method, any code passed will be executed on the remote host. To demonstrate this, I will use a simple assembly that starts calc. The PoC C# looks something like this:

Note: Since it is just C#, this can be a full shellcode runner as well 🙂

After compiling the “payload”, you can pass it to DotNetToJScript and get back some beautiful JScript/VBScript. In this instance, it will be JScript.

Now that the payload has been generated, it can be passed to the ScriptControl COM object that was created via Outlook’s CreateObject method on the remote host. This can be accomplished by storing the entire JScript/VBScript code block into a variable in PowerShell. In this case, I have stored it in a variable called “$code”:

Finally, all that needs done is to set the “Language” property on the ScriptControl object to whatever language that will be executed (JScript or VBScript) and then call the “AddCode()” method with the “$code” variable as a parameter:

$RemoteScriptControl.Language = “JScript”
$RemoteScriptControl.AddCode($code)

After the “AddCode()” method is invoked, the supplied JScript will execute on the remote host:

As you can see above, calc.exe has spawned on the remote host.

Detections and Mitigations:

You might have noticed in the above screenshot that Outlook.exe spawned as a child of svchost.exe. That is indicative of Outlook.Application being instantiated via DCOM remotely, so that should stick out. In most cases, the process being started will contain “-embedding” in the command line, which is also indicative of remote instantiation.

Additionally, module loads of vbscript.dll or jscript/jscript9.dll should stand out as well. Normally, Outlook does not load these and those being loaded would be indicators of the ScriptControl object being used.

In this example, the payload was running as a child process of Outlook.exe, which would be weird. It is important to remember that ultimately, a .NET assembly is being executed, meaning that shellcode injection is absolutely doable. Instead of simply starting a process, an attacker can write an assembly that injects shellcode into another process, which would bypass the parent-child relationship detection. Ultimately, enabling the Windows Firewall will prevent this attack as it stops DCOM usage.

-Matt N

A Look at CVE-2017-8715: Bypassing CVE-2017-0218 using PowerShell Module Manifests

Recently, Matt Graeber (@mattifestation) and I have been digging into methods to bypass User Mode Code Integrity (UMCI) in the context of Device Guard. As a result, many CVEs have been released and Microsoft has done a good job at mitigating the attack surface that PowerShell imposes on UMCI by way of continuing to improve Constrained Language Mode (CLM) – the primary PowerShell policy enforcement mechanism for Device Guard and AppLocker. The following mitigations significantly reduce the attack surface, and were a result of CVE-2017-0218.

The vast majority of these injection bugs abuse Microsoft-signed PowerShell scripts or modules that contain functionality to run unsigned code. This ranges all the way from calling Invoke-Expression on a parameter to custom implementations of PowerShell’s Add-Type cmdlet. Microsoft-signed PowerShell code is targeted because an AppLocker or Device Guard policy that permits Microsoft code to execute will execute in Full Language Mode – i.e. with no restrictions placed on what code can be executed.

To fully understand the fix, let’s examine the behavior before the patch. The gist of this bug is that an attacker can use functions within a Microsoft-signed PowerShell script to bypass UMCI. To demonstrate this, let’s look at the script “utils_SetupEnv.ps1”, a component of Windows Troubleshooting Packs contained within C:\Windows\diagnostics. This particular script has a function called “import-cs”. This function simply takes C# code and calls Add-Type on it (note: Add-Type is blocked in constrained language mode). Since the script is Microsoft-signed, it executes in Full Language mode, allowing an attacker to execute arbitrary C#.

As you can see above, we imported “utils_SetupEnv.ps1”, which exposed the “import-cs” function to us. Using that function, we can pass it our own C# which gets executed, bypassing constrained language mode. The above bug was issued CVE-2017-0218 and fixed.

In response to bugs like the one above, Microsoft imposed a few additional restrictions when running in PowerShell’s Constrained Language Mode. The first one is that you are no longer allowed to import PowerShell scripts (.PS1s) via Import-Module or other means. If you try to import a script in CLM, you will see something like this:

One possible workaround would be to rename a PowerShell script (.PS1) to a module file (.PSM1) and import it that way, right? Well, that is where the second mitigation comes in.

Microsoft introduced a restriction on what you can import and use via PowerShell modules (.PSM1s). This was done with “Export-ModuleMember”; if you aren’t familiar with Export-ModuleMember, it defines what functions in a module are able to be used once its imported. In Constrained Language Mode, a module’s function has to be exported via Export-ModuleMember in order for it to be available. This significantly reduces the attack surface of functions to abuse in Microsoft signed PowerShell modules. It’s also just good practice in general to explicitly define what functions you want to expose to users of your module.

If we rename “utils_SetupEnv.ps1” to “utils_SetupEnv.psm1” and try to import it, it will appear to import successfully. You will notice that the “import-cs” function we used earlier isn’t recognized. This is because “import-cs” is not exposed via Export-ModuleMember.

If we look at a valid PowerShell module file, the Export-ModuleMember definition will look something like this:

What that essentially means is that only “Export-ODataEndpointProxy” can be used when the module is imported. This severely limits the abuse potential for Microsoft-signed PowerShell scripts that contain functions that can be abused to run unsigned code as most of them won’t be exposed. Again, the PowerShell team is slowly but surely mitigating many of the bypass primitives we use to circumvent Constrained Language Mode.

After investigating that fix, I discovered and reported a way around the Export-ModuleMember addition, which was assigned CVE-2017-8715 and fixed in the October Patch Tuesday release. This bypass abuses a PowerShell Module Manifest (.PSD1). When investigating the implications of these files, I realized that you could configure module behavior via these files, and that they weren’t subject to the same signing requirements that many of the other PowerShell files are (likely since PSD1 files don’t contain executable code). Despite that, PSD1s can still be signed.

If we go back to the “utils_SetupEnv.ps1” script, the “import-cs” function is no longer available since no functions from that script are exposed via Export-ModuleMember. To get around this, we can rename “utils_SetupEnv.ps1” to “utils_SetupEnv.psm1” so we can import it. After doing so, we can drop a corresponding module manifest for “utils_SetupEnv.psm1” that exports the beloved “import-cs” function for us. This module manifest will look something like this:

As you can see, we have set “import-cs” as a function to export via “FunctionsToExport”. This will export the function just as Export-ModuleMember would have. Since PowerShell module manifest files were not constrained to the same code signing requirements as other PowerShell files, we can simply create our own for the actual Microsoft signed script we want to abuse. After dropping the above manifest for the “utils_SetupEnv” PowerShell module, we can now use the “import-cs” function to execute arbitrary C# code, despite the newly introduced .PS1 import and Export-ModuleMember mitigation.

As I mentioned above, this bypass was issued CVE-2017-8715. The fix involved subjecting PowerShell module manifest files (.PSD1s) to the same code signing requirements as all the other files in a module in that if a module is signed and permitted by Device Guard or AppLocker policy, then a module manifest will only be honored if it is also signed pursuant to a whitelist rule. Doing so will prevent an attacker from modifying an existing manifest or using their own.

As always, report any UMCI bypasses to MSRC for CVEs 🙂

-Matt N.