Skip to content

Instantly share code, notes, and snippets.

@gsuberland
Last active October 13, 2021 12:55
Show Gist options
  • Save gsuberland/d285b836a5ebc2990dc6cfd54f43b4e9 to your computer and use it in GitHub Desktop.
Save gsuberland/d285b836a5ebc2990dc6cfd54f43b4e9 to your computer and use it in GitHub Desktop.
Extremely hacky solution to close leaked handles in mscms.dll!OpenDisplay
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1201106
// see: https://twitter.com/gsuberland/status/1445547814965055488
#include <Windows.h>
#include <stdio.h>
#include <TlHelp32.h>
#include <memory>
#include <cassert>
#include <vector>
#pragma comment(lib, "ntdll")
#define NT_SUCCESS(status) (status >= 0)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
enum PROCESSINFOCLASS {
ProcessHandleInformation = 51
};
typedef struct _PROCESS_HANDLE_TABLE_ENTRY_INFO
{
HANDLE HandleValue;
ULONG_PTR HandleCount;
ULONG_PTR PointerCount;
ULONG GrantedAccess;
ULONG ObjectTypeIndex;
ULONG HandleAttributes;
ULONG Reserved;
} PROCESS_HANDLE_TABLE_ENTRY_INFO, * PPROCESS_HANDLE_TABLE_ENTRY_INFO;
typedef struct _PROCESS_HANDLE_SNAPSHOT_INFORMATION
{
ULONG_PTR NumberOfHandles;
ULONG_PTR Reserved;
PROCESS_HANDLE_TABLE_ENTRY_INFO Handles[1];
} PROCESS_HANDLE_SNAPSHOT_INFORMATION, * PPROCESS_HANDLE_SNAPSHOT_INFORMATION;
extern "C" NTSTATUS NTAPI NtQueryInformationProcess(
_In_ HANDLE ProcessHandle,
_In_ PROCESSINFOCLASS ProcessInformationClass,
_Out_writes_bytes_(ProcessInformationLength) PVOID ProcessInformation,
_In_ ULONG ProcessInformationLength,
_Out_opt_ PULONG ReturnLength);
typedef struct _UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING;
typedef enum _OBJECT_INFORMATION_CLASS
{
ObjectBasicInformation = 0, // q: OBJECT_BASIC_INFORMATION
ObjectNameInformation = 1, // q: OBJECT_NAME_INFORMATION
ObjectTypeInformation = 2, // q: OBJECT_TYPE_INFORMATION
ObjectTypesInformation = 3, // q: OBJECT_TYPES_INFORMATION
ObjectHandleFlagInformation = 4, // qs: OBJECT_HANDLE_FLAG_INFORMATION
ObjectSessionInformation = 5, // s: void // change object session // (requires SeTcbPrivilege)
ObjectSessionObjectInformation = 6, // s: void // change object session // (requires SeTcbPrivilege)
} OBJECT_INFORMATION_CLASS;
typedef struct _OBJECT_TYPE_INFORMATION
{
UNICODE_STRING TypeName;
ULONG TotalNumberOfObjects;
ULONG TotalNumberOfHandles;
ULONG TotalPagedPoolUsage;
ULONG TotalNonPagedPoolUsage;
ULONG TotalNamePoolUsage;
ULONG TotalHandleTableUsage;
ULONG HighWaterNumberOfObjects;
ULONG HighWaterNumberOfHandles;
ULONG HighWaterPagedPoolUsage;
ULONG HighWaterNonPagedPoolUsage;
ULONG HighWaterNamePoolUsage;
ULONG HighWaterHandleTableUsage;
ULONG InvalidAttributes;
GENERIC_MAPPING GenericMapping;
ULONG ValidAccessMask;
BOOLEAN SecurityRequired;
BOOLEAN MaintainHandleCount;
UCHAR TypeIndex; // since WINBLUE
CHAR ReservedByte;
ULONG PoolType;
ULONG DefaultPagedPoolCharge;
ULONG DefaultNonPagedPoolCharge;
} OBJECT_TYPE_INFORMATION, * POBJECT_TYPE_INFORMATION;
typedef struct _OBJECT_TYPES_INFORMATION
{
ULONG NumberOfTypes;
OBJECT_TYPE_INFORMATION Types[1];
} OBJECT_TYPES_INFORMATION, * POBJECT_TYPES_INFORMATION;
typedef struct _OBJECT_NAME_INFORMATION
{
UNICODE_STRING Name;
} OBJECT_NAME_INFORMATION, * POBJECT_NAME_INFORMATION;
extern "C" NTSTATUS NTAPI NtQueryObject(
_In_opt_ HANDLE Handle,
_In_ OBJECT_INFORMATION_CLASS ObjectInformationClass,
_Out_writes_bytes_opt_(ObjectInformationLength) PVOID ObjectInformation,
_In_ ULONG ObjectInformationLength,
_Out_opt_ PULONG ReturnLength);
UCHAR RegistryKeyHandleType = 0xFF;
void CleanupHandles(const DWORD pid)
{
if (pid == GetCurrentProcessId())
{
printf("Skipping own process.\n");
return;
}
// open a handle to the process. use a shared_ptr so that CloseHandle is always called when exiting
const std::shared_ptr<void> hProcess(OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_DUP_HANDLE, FALSE, pid), CloseHandle);
if (hProcess.get() == nullptr)
{
printf("CleanupHandles: Failed to open handle to process ID %u. Error: %u\n", pid, GetLastError());
return;
}
// get the handle table for this process
ULONG bufferSize = 0x1000;
std::vector<BYTE> buffer(bufferSize);
NTSTATUS status;
while (status = NtQueryInformationProcess(hProcess.get(), ProcessHandleInformation, buffer.data(), bufferSize, &bufferSize) == STATUS_INFO_LENGTH_MISMATCH)
{
bufferSize *= 2;
// fail if we're resizing the buffer to something very large.
if (bufferSize > 1024 * 1024 * 4)
{
printf("CleanupHandles: NtQueryObject requested buffer exceeding 4MB. Buffer size: %u\n", bufferSize);
return;
}
buffer.resize(bufferSize);
}
// if NtQueryInformationProcess failed with something other than STATUS_INFO_LENGTH_MISMATCH, something went wrong
if (!NT_SUCCESS(status))
{
printf("CleanupHandles: NtQueryInformationProcess call failed for process %u. Status: %x. Last error: %u\n", pid, status, GetLastError());
return;
}
// allocate a buffer that can store a OBJECT_NAME_INFORMATION structure with a path of the maximum allowed length
// this is the size of the struct (effectively just a UNICODE_STRING) plus the max size of a UNICODE_STRING's contents in bytes, plus one wchar_t null terminator
// this buffer used to store the name of the object that the handle is pointing to
const ULONG nameBufferSize = sizeof(OBJECT_NAME_INFORMATION) + UNICODE_STRING_MAX_BYTES + sizeof(wchar_t);
auto nameBuffer = std::vector<BYTE>(nameBufferSize);
const auto info = reinterpret_cast<PROCESS_HANDLE_SNAPSHOT_INFORMATION*>(buffer.data());
ULONG closeCount = 0;
for (ULONG i = 0; i < info->NumberOfHandles; i++)
{
// zero the name buffer before use (will contain previous path on second loop)
std::fill(nameBuffer.begin(), nameBuffer.end(), 0);
const HANDLE h = info->Handles[i].HandleValue;
// if we successfully found the registry key handle type, check that this handle matches that type
if (RegistryKeyHandleType != 0xFF)
{
if (info->Handles[i].ObjectTypeIndex != RegistryKeyHandleType)
{
continue;
}
}
// duplicate the handle into our process, so we can get its name information
// we use DUPLICATE_SAME_ACCESS at this point because we're just checking the handle's path; we don't know that we want DUPLICATE_CLOSE_SOURCE yet
HANDLE hTarget;
if (!DuplicateHandle(hProcess.get(), h, GetCurrentProcess(), &hTarget, 0, FALSE, DUPLICATE_SAME_ACCESS))
continue;
// grab the name information
ULONG resultLength = 0;
const auto status = NtQueryObject(hTarget, ObjectNameInformation, nameBuffer.data(), nameBufferSize, &resultLength);
// close the duplicated handle. we'll re-open it with DUPLICATE_CLOSE_SOURCE if we determine that it's one of the leaked handles
CloseHandle(hTarget);
if (!NT_SUCCESS(status))
continue;
if (resultLength < sizeof(OBJECT_NAME_INFORMATION))
{
printf("CleanupHandles: NtQueryObject call for ObjectNameInformation returned a buffer of size %u, which is smaller than the size of OBJECT_NAME_INFORMATION (%zu)\n", resultLength, sizeof(OBJECT_NAME_INFORMATION));
continue;
}
if (resultLength > nameBuffer.size())
{
printf("CleanupHandles: NtQueryObject call for ObjectNameInformation returned a buffer of size %u, which is larger than the maximum buffer size (%u)\n", resultLength, nameBufferSize);
continue;
}
const POBJECT_NAME_INFORMATION objName = reinterpret_cast<POBJECT_NAME_INFORMATION>(nameBuffer.data());
// sometimes handles will point to objects without names. we only care about ones with names
if (objName->Name.Buffer != nullptr && objName->Name.Length > 0)
{
// verify that the length makes sense
const auto maxAllowedNameLength = nameBuffer.size() - sizeof(OBJECT_NAME_INFORMATION);
if (objName->Name.Length > maxAllowedNameLength)
{
continue;
}
// is the handle pointing to the registry key of interest?
if (_wcsnicmp(L"\\REGISTRY\\MACHINE\\SYSTEM\\ControlSet001\\Control\\Class\\{4d36e96e-e325-11ce-bfc1-08002be10318}", objName->Name.Buffer, objName->Name.Length / sizeof(wchar_t)) == 0)
{
//printf("Closing handle %p - %ws\n", hTarget, objName->Name.Buffer);
if (DuplicateHandle(hProcess.get(), h, GetCurrentProcess(), &hTarget, 0, FALSE, DUPLICATE_CLOSE_SOURCE))
{
CloseHandle(hTarget);
closeCount++;
}
}
}
}
printf("Closed %u handles.\n", closeCount);
}
void EnumerateProcessesForCleanup()
{
const HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
{
printf("Failed to create process list snapshot. Error: %u\n", GetLastError());
return;
}
PROCESSENTRY32 pe = { 0 };
pe.dwSize = sizeof(pe);
// skip the idle process
Process32First(hSnapshot, &pe);
// loop through all processes and cleanup handles in the ones of interest
while (Process32Next(hSnapshot, &pe))
{
if (_wcsicmp(L"chrome.exe", pe.szExeFile) == 0 ||
_wcsicmp(L"typora.exe", pe.szExeFile) == 0 ||
_wcsicmp(L"slack.exe", pe.szExeFile) == 0 ||
_wcsicmp(L"whatsapp.exe", pe.szExeFile) == 0 ||
_wcsicmp(L"mspaint.exe", pe.szExeFile) == 0 ||
_wcsicmp(L"spotify.exe", pe.szExeFile) == 0 ||
_wcsicmp(L"telegram.exe", pe.szExeFile) == 0
)
{
printf("Handling process %u - %ws\n", pe.th32ProcessID, pe.szExeFile);
CleanupHandles(pe.th32ProcessID);
}
}
CloseHandle(hSnapshot);
}
bool GetRegistryKeyObjectTypeIndex(PUCHAR pIndexOut)
{
NTSTATUS status;
ULONG returnLength;
ULONG bufferSize = 0x1000;
std::vector<BYTE> buffer(bufferSize);
while ((status = NtQueryObject(
NULL,
OBJECT_INFORMATION_CLASS::ObjectTypesInformation,
buffer.data(),
bufferSize,
&returnLength)) == STATUS_INFO_LENGTH_MISMATCH)
{
bufferSize *= 2;
// Fail if we're resizing the buffer to something very large.
if (bufferSize > 1024 * 1024 * 4)
{
printf("GetRegistryKeyObjectTypeIndex: NtQueryObject requested buffer exceeding 4MB. Buffer size: %u\n", bufferSize);
return false;
}
buffer.resize(bufferSize);
}
if (!NT_SUCCESS(status))
{
printf("GetRegistryKeyObjectTypeIndex: NtQueryObject failed with status %u. Last error: %u\n", status, GetLastError());
return false;
}
const auto objectTypes = reinterpret_cast<POBJECT_TYPES_INFORMATION>(buffer.data());
PUCHAR ptr = reinterpret_cast<PUCHAR>(&objectTypes->Types);
printf("%u types found.\n", objectTypes->NumberOfTypes);
for (ULONG i = 0; i < objectTypes->NumberOfTypes; i++)
{
auto objectType = reinterpret_cast<POBJECT_TYPE_INFORMATION>(ptr);
//printf("Type %u name %ws\n", objectType->TypeIndex, objectType->TypeName.Buffer);
if (_wcsicmp(L"Key", objectType->TypeName.Buffer) == 0)
{
printf("Registry key type is %u\n", objectType->TypeIndex);
*pIndexOut = objectType->TypeIndex;
return true;
}
ptr += sizeof(OBJECT_TYPE_INFORMATION);
ptr += objectType->TypeName.Length + sizeof(wchar_t);
if ((size_t)ptr % 8 != 0)
ptr += 8 - ((size_t)ptr % 8);
}
printf("Exhausted search without finding registry key type.\n");
return false;
}
int main()
{
if (!GetRegistryKeyObjectTypeIndex(&RegistryKeyHandleType))
{
printf("Couldn't find registry key object type index.\n");
}
while (true)
{
printf("Clearing handles...\n");
EnumerateProcessesForCleanup();
printf("Waiting...\n\n");
Sleep(60 * 1000);
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment