Files
PowerToys/src/modules/FileLocksmith/FileLocksmithLibInterop/NtdllExtensions.cpp
Jaime Bernardo fb5ed13386 [Refactor]Port C++/CX to C++/WinRT (#34198)
## Summary of the Pull Request
Removes all C++/CX code, replacing it with C++/WinRT.

## Detailed Description of the Pull Request / Additional comments
Removes all C++/CX code.
Renames interop namespaces to be better consumed by CsWinRT.
Standardizes all projects on net8.0-windows10.0.20348.0, which is a
requirement for C++/WinRT usage.
FileLocksmithLibInterop brought to stdcpplatest and static analysis
errors were corrected.
Removed now unneeded string conversion code from
FileLocksmithLibInterop.
Changed interop KeyboardHook to use a single hook across all instances.
Required because on C++/WinRT we don't have the .NET runtime to bind a
object instance to a delegate and be able to pass it to a C function
pointer argument (still no idea why this worked correctly on C++/CX to
be honest). This change actually makes us create less low level keyboard
hooks.
Changed some code that depended on arrays since WinRT/C++ returns null
instead of an empty array through the interface.

## Validation Steps Performed
Built and tested runtime.
2024-08-08 15:26:43 +01:00

370 lines
12 KiB
C++

#include "pch.h"
#include "NtdllExtensions.h"
#include <thread>
#include <atomic>
#define STATUS_INFO_LENGTH_MISMATCH ((LONG)0xC0000004)
// Calls NtQuerySystemInformation and returns a buffer containing the result.
namespace
{
std::wstring_view unicode_to_view(UNICODE_STRING unicode_str)
{
return std::wstring_view(unicode_str.Buffer, unicode_str.Length / sizeof(WCHAR));
}
std::wstring unicode_to_str(UNICODE_STRING unicode_str)
{
return std::wstring(unicode_str.Buffer, unicode_str.Length / sizeof(WCHAR));
}
// Implementation adapted from src/common/utils
inline std::wstring get_module_name(HANDLE process, HMODULE mod)
{
wchar_t buffer[MAX_PATH + 1];
DWORD actual_length = GetModuleFileNameExW(process, mod, buffer, MAX_PATH + 1);
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
const DWORD long_path_length = 0xFFFF; // should be always enough
std::wstring long_filename(long_path_length, L'\0');
actual_length = GetModuleFileNameW(mod, long_filename.data(), long_path_length);
long_filename.resize(std::wcslen(long_filename.data()));
long_filename.shrink_to_fit();
return long_filename;
}
return { buffer, static_cast<UINT>(lstrlenW(buffer)) };
}
constexpr size_t DefaultModulesResultSize = 512;
std::vector<std::wstring> process_modules(DWORD pid)
{
HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
if (!process)
{
return {};
}
std::vector<std::wstring> result;
bool completed = false;
std::vector<HMODULE> modules(DefaultModulesResultSize);
while (!completed)
{
DWORD needed;
auto status = EnumProcessModules(process, modules.data(), static_cast<DWORD>(modules.size() * sizeof(HMODULE)), &needed);
if (!status)
{
// Give up
return {};
}
if (needed > modules.size() * sizeof(HMODULE))
{
// Array is too small
modules.resize(needed / sizeof(HMODULE));
continue;
}
// Okay
modules.resize(needed / sizeof(HMODULE));
for (auto mod : modules)
{
result.push_back(get_module_name(process, mod));
}
completed = true;
}
CloseHandle(process);
return result;
}
}
NtdllExtensions::MemoryLoopResult NtdllExtensions::NtQuerySystemInformationMemoryLoop(ULONG SystemInformationClass)
{
MemoryLoopResult result;
result.memory.resize(DefaultResultBufferSize);
while (result.memory.size() <= MaxResultBufferSize)
{
ULONG result_len;
result.status = NtQuerySystemInformation(SystemInformationClass, result.memory.data(), static_cast<ULONG>(result.memory.size()), &result_len);
if (result.status == STATUS_INFO_LENGTH_MISMATCH)
{
result.memory.resize(result.memory.size() * 2);
continue;
}
if (NT_ERROR(result.status))
{
result.memory.clear();
}
return result;
}
result.status = STATUS_INFO_LENGTH_MISMATCH;
result.memory.clear();
return result;
}
std::wstring NtdllExtensions::file_handle_to_kernel_name(HANDLE file_handle, std::vector<BYTE>& buffer)
{
if (GetFileType(file_handle) != FILE_TYPE_DISK)
{
return L"";
}
ULONG return_length;
auto status = NtQueryObject(file_handle, ObjectNameInformation, buffer.data(), static_cast<ULONG>(buffer.size()), &return_length);
if (NT_SUCCESS(status))
{
auto object_name_info = reinterpret_cast<UNICODE_STRING*>(buffer.data());
return unicode_to_str(*object_name_info);
}
return L"";
}
std::wstring NtdllExtensions::file_handle_to_kernel_name(HANDLE file_handle)
{
std::vector<BYTE> buffer(DefaultResultBufferSize);
return file_handle_to_kernel_name(file_handle, buffer);
}
std::wstring NtdllExtensions::path_to_kernel_name(LPCWSTR path)
{
HANDLE file_handle = CreateFileW(path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (file_handle == INVALID_HANDLE_VALUE)
{
return {};
}
auto kernel_name = file_handle_to_kernel_name(file_handle);
CloseHandle(file_handle);
return kernel_name;
}
std::vector<NtdllExtensions::HandleInfo> NtdllExtensions::handles() noexcept
{
auto get_info_result = NtQuerySystemInformationMemoryLoop(SystemExtendedHandleInformation);
if (NT_ERROR(get_info_result.status))
{
return {};
}
auto info_ptr = reinterpret_cast<SYSTEM_HANDLE_INFORMATION_EX*>(get_info_result.memory.data());
std::map<ULONG_PTR, HANDLE> pid_to_handle;
std::vector<HandleInfo> result;
std::vector<BYTE> object_info_buffer(DefaultResultBufferSize);
std::atomic<ULONG> i = 0;
std::atomic<ULONG_PTR> handle_count = info_ptr->NumberOfHandles;
std::atomic<HANDLE> process_handle = NULL;
std::atomic<HANDLE> handle_copy = NULL;
ULONG previous_i;
while (i < handle_count)
{
previous_i = i;
// The system calls we use in this block were reported to hang on some machines.
// We need to offload the cycle to another thread and keep track of progress to terminate and resume when needed.
// Unfortunately, there are no alternative APIs to what we're using that accept timeouts. (NtQueryObject and GetFileType)
auto offload_function = std::thread([&] {
for (; i < handle_count; i++)
{
process_handle = NULL;
handle_copy = NULL;
auto handle_info = info_ptr->Handles + i;
auto pid = handle_info->UniqueProcessId;
auto iter = pid_to_handle.find(pid);
if (iter != pid_to_handle.end())
{
process_handle = iter->second;
}
else
{
process_handle = OpenProcess(PROCESS_DUP_HANDLE, FALSE, static_cast<DWORD>(pid));
if (!process_handle)
{
continue;
}
pid_to_handle[pid] = process_handle;
}
// According to this:
// https://stackoverflow.com/questions/46384048/enumerate-handles
// NtQueryObject could hang
// TODO uncomment and investigate
// if (handle_info->GrantedAccess == 0x0012019f) {
// continue;
// }
HANDLE local_handle_copy;
auto dh_result = DuplicateHandle(process_handle, reinterpret_cast<HANDLE>(handle_info->HandleValue), GetCurrentProcess(), &local_handle_copy, 0, 0, DUPLICATE_SAME_ACCESS);
if (dh_result == 0)
{
// Ignore this handle.
continue;
}
handle_copy = local_handle_copy;
ULONG return_length;
auto status = NtQueryObject(handle_copy, ObjectTypeInformation, object_info_buffer.data(), static_cast<ULONG>(object_info_buffer.size()), &return_length);
if (NT_ERROR(status))
{
// Ignore this handle.
CloseHandle(handle_copy);
handle_copy = NULL;
continue;
}
auto object_type_info = reinterpret_cast<OBJECT_TYPE_INFORMATION*>(object_info_buffer.data());
auto type_name = unicode_to_str(object_type_info->Name);
std::wstring file_name;
if (type_name == L"File")
{
file_name = file_handle_to_kernel_name(handle_copy, object_info_buffer);
result.push_back(HandleInfo{ pid, handle_info->HandleValue, type_name, file_name });
}
CloseHandle(handle_copy);
handle_copy = NULL;
}
});
offload_function.detach();
do
{
Sleep(200); // Timeout in milliseconds for detecting that the system hang on getting information for a handle.
if (i >= handle_count)
{
// We're done.
break;
}
if (previous_i >= i)
{
// The thread looks like it's hanging on some handle. Let's kill it and resume.
// HACK: This is unsafe and may leak something, but looks like there's no way to properly clean up a thread when it's hanging on a system call.
TerminateThread(offload_function.native_handle(), 1);
// Close Handles that might be lingering.
if (handle_copy!=NULL)
{
CloseHandle(handle_copy);
}
i++;
break;
}
previous_i = i;
} while (1);
}
for (auto [pid, handle] : pid_to_handle)
{
CloseHandle(handle);
}
return result;
}
// Returns the list of all processes.
// On failure, returns an empty vector.
std::wstring NtdllExtensions::pid_to_user(DWORD pid)
{
HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
std::wstring user;
std::wstring domain;
if (process == nullptr)
{
return user;
}
HANDLE token = nullptr;
if (!OpenProcessToken(process, TOKEN_QUERY, &token))
{
return user;
}
DWORD token_size = 0;
const bool ok = GetTokenInformation(token, TokenUser, nullptr, 0, &token_size);
if ((!ok && GetLastError() != ERROR_INSUFFICIENT_BUFFER) || !token_size)
{
return user;
}
std::vector<BYTE> token_buffer(token_size);
GetTokenInformation(token, TokenUser, token_buffer.data(), token_size, &token_size);
TOKEN_USER* user_ptr = reinterpret_cast<TOKEN_USER*>(token_buffer.data());
PSID psid = user_ptr->User.Sid;
DWORD user_buf_size = 0;
DWORD domain_buf_size = 0;
SID_NAME_USE sid_name;
LookupAccountSidW(nullptr, psid, nullptr, &user_buf_size, nullptr, &domain_buf_size, &sid_name);
if (!user_buf_size || !domain_buf_size)
{
return user;
}
user.resize(user_buf_size);
domain.resize(domain_buf_size);
LookupAccountSidW(nullptr, psid, user.data(), &user_buf_size, domain.data(), &domain_buf_size, &sid_name);
user.resize(user.size() - 1);
domain.resize(domain.size() - 1);
CloseHandle(token);
CloseHandle(process);
return user;
}
std::vector<NtdllExtensions::ProcessInfo> NtdllExtensions::processes() noexcept
{
auto get_info_result = NtQuerySystemInformationMemoryLoop(SystemProcessInformation);
if (NT_ERROR(get_info_result.status))
{
return {};
}
std::vector<ProcessInfo> result;
auto info_ptr = reinterpret_cast<PSYSTEM_PROCESS_INFORMATION>(get_info_result.memory.data());
while (info_ptr->NextEntryOffset)
{
info_ptr = reinterpret_cast<decltype(info_ptr)>(reinterpret_cast<LPBYTE>(info_ptr) + info_ptr->NextEntryOffset);
ProcessInfo item;
item.name = unicode_to_str(info_ptr->ImageName);
item.pid = static_cast<DWORD>(reinterpret_cast<uintptr_t>(info_ptr->UniqueProcessId));
item.modules = process_modules(item.pid);
item.user = pid_to_user(item.pid);
result.push_back(item);
}
return result;
}