« Back to home

Protecting Your Malware with blockdlls and ACG

In an update to Cobalt Strike, the blockdlls command was introduced to provide operators with the option of protecting spawned processes from loading non-Microsoft signed DLL's. This is of course a method of blocking endpoint security products from loading their user-mode code via a DLL with the purpose of hooking and reporting on the execution of suspicious functions.

After a few discussions and tweets looking at just how this is implemented, I was asked some additional questions from people who wanted to use this themselves outside of Cobalt Strike, so in this post I will explore this functionality a little further by showing just how blockdlls works under the hood, how you can use it to protect your malware before a beacon is launched, and look at an additional process security option which could help us to deter endpoint security products from listening in so easily.

blockdlls Internals

blockdlls was released with version 3.14 of Cobalt Strike and is used to protect any child processes spawned by a beacon from loading non-Microsoft signed DLL's. To leverage this functionality, we simply use the blockdlls command on an active session and spawn a child process (for example, using the spawn command):

Once our child process has been spawned, we can see the resulting protection within something like ProcessHacker:

With the mitigation flag set, if a DLL which has not been signed by Microsoft is attempted to be loaded into the process, we find that this will fail, sometimes with a nice verbose error such as:

So how does Cobalt Strike go about implementing this functionality? Well if we hunt through a CS beacon binary, we see a reference to UpdateProcThreadAttribute:

The Attribute parameter of 0x20007 actually resolves to a definition of PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, and the value of 0x100000000000 resolves to PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON. So what Cobalt Strike is doing here is using a CreateProcess API call along with a STARTUPINFOEX struct containing a mitigation policy which, in this case, is being used to block non-Microsoft signed DLL's.

If we wanted to recreate this within our own tooling, we can simply use code such as:

Bridging The blockdlls Gap

So we now know just how Cobalt Strike achieves its protection, but during a typical engagement there is still a gap where arbitrary DLL's may trip us up. Let's look at a common phishing scenario where we are attempting to deliver a Cobalt Strike beacon via a macro enabled document:

In red we can see processes which do not benefit from blockdlls protection, whereas in blue we see each child spawned process from Cobalt Strike is protected with a mitigation policy. The risk for us here is obviously that a security product can load its DLL into our migrated process (shown here as Internet Explorer) and review our activity.

Bridging this gap however is relatively straight forward using the code shown above along with the PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON mitigation option. As we are discussing our initial payload in the context of a Word document, let's take the opportunity to port this code over to VBA:

Used correctly, we see that we can decrease our chances of detection from DLL instrumentation by limiting access to just the initial execution vector:

So what about that Word process left in red? Well there are ways to protect this, for example, we can simply call SetMitigationPolicy along with ProcessSignaturePolicy as a parameter and this would introduce our mitigation policy during runtime, that is, without having to re-execute via CreateProcess. It is likely however that by this point any unwanted DLL's would already be present within the Word address space way before our VBA runs, and attempting to further manipulate the process and trigger somewhat suspicious API calls could actually increase our chance of detection.

Arbitrary Code Guard

As you are have been reading this you may be wondering about Arbitrary Code Guard (ACG). If you haven't heard of this before, ACG is another mitigation option which is provided to stop code from allocating and/or modifying executable pages of memory, often required for introducing dynamic code into a process.

To see this mitigation policy in action, let's create a small program and attempt to use SetMitigationPolicy to add ACG along with a few test cases:

If we compile and execute this POC, we will see something like this:

Here we observe that attempts to allocate a RWX page of memory after the SetProcessMitigationPolicy fail as expected, along with attempts to use calls such as VirtualProtect which would allow modification of memory protection.

So why bring this up? Well unfortunately we do see examples of EDR DLL's being injected which are signed by Microsoft, for example, @Sektor7Net showed us that Crowdstrike Falcon contains one such a DLL which is unaffected by PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON:

But one common thing that many EDR products will do is to implement userspace hooks around interesting functions (see our previous post on Cylance which uses this exact technique). As hooking typically requires the ability to modify existing executable pages to add a trampoline, a call such as VirtualProtect is usually required to update memory protection. If we remove their ability to create RWX pages of memory, we may can force even a Microsoft signed DLL to fail.

To implement this within our VBA example, all we need to add is a further mitigation option of PROCESS_CREATION_MITIGATION_POLICY_PROHIBIT_DYNAMIC_CODE_ALWAYS_ON to enable this protection:

So this is great for protecting processes we are spawning, but what about if we want to inject some of our code into a process which is already protected with ACG? Well a common misconception that I hear is that we are unable to inject code into a process protected by Arbitrary Code Guard as, well we require some form of memory which has been writable and executable. But actually, ACG doesn't block a remote processes ability to call a function such as VirtualAllocEx.

For example, if we take some simple shellcode to spawn cmd.exe and inject this into a process protected via ACG, we will actually see that this executes just fine:

It should be noted that injecting something like Cobalt Strike beacon will not currently work with this method due to the reliance on allocating and modifying pages of memory to RWX. I've tried a few different malleable profile options to work around this (mostly the various userwx options provided), but currently it appears that modification of memory to be writable and later executable is required.

Operational Considerations

Now before we go and introduce these mitigations to all of our loaders/stagers, something that we need to consider is just how this may affect our operational security. For example, if we start to spawn arbitrary processes and protect them all using PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON, we may be sending a flag out to a knowledgable Blue Team who notice that suddenly random processes have mitigation policies assigned (although full credit to teams who spot this in their environment).

To help us to figure out how to blend in effectively, we want to enumerate any existing processes with a policy present. Now we could use Get-ProcessMitigation Powershell cmdlet, which will return any policies defined within the registry, however we know there are other ways to enable protection on a process during runtime, such as the SetMitigationPolicy API call, as well as simply spawning an arbitrary process via CreateProcessA as shown above.

To make sure we profile each process correctly, let's craft a simple tool which will use the GetProcessMitigationPolicy call to identify assigned mitigation policies:

Running this against a Windows 10 instance in my lab, several processes were found to have enabled mitigations:

Not surprisingly these processes mostly revolve around Edge, however we also have a number of other alternatives such as fontdrvhost.exe and dllhost.exe which can prove to be viable candidates for targeting and aren't subjected to low-integrity.

So hopefully this post has given you a few additional ideas for spawning and injecting your payloads, and if used carefully, I think we have an effective tool to cause some confusion.

If you do find these options to be effective, give me a shout via the usual channels, it would be good to see examples of vendors who may be affected by blockdlls and ACG. Happy hunting!