diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f3359d94..9be417803 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1593,7 +1593,6 @@ elseif(WIN32) PRIVATE "winbrand" PRIVATE "propsys" PRIVATE "secur32" - PRIVATE "pdh" ) if(CMAKE_SYSTEM_PROCESSOR STREQUAL "ARM64") # WoA only works on Windows 10 or higher diff --git a/src/detection/cpu/cpu_windows.c b/src/detection/cpu/cpu_windows.c index 515262158..2cb1eef34 100644 --- a/src/detection/cpu/cpu_windows.c +++ b/src/detection/cpu/cpu_windows.c @@ -4,42 +4,116 @@ #include "util/mallocHelper.h" #include "util/smbiosHelper.h" -#include +#include +#include "perflib_.h" +#include -static void ffPdhOpenCloseQuery(HQUERY* query) +static inline void ffPerfCloseQueryHandle(HANDLE* phQuery) { - assert(query); - if (*query) + if (*phQuery != NULL) { - PdhCloseQuery(*query); - *query = NULL; + PerfCloseQueryHandle(*phQuery); + *phQuery = NULL; } } -static const char* detectThermalTemp(double* result) +const char* detectThermalTemp(double* result) { - // typeperf.exe -sc 1 "\Thermal Zone Information(*)\Temperature" + struct FFPerfQuerySpec + { + PERF_COUNTER_IDENTIFIER Identifier; + WCHAR Name[16]; + } querySpec = { + .Identifier = { + // Thermal Zone Information + // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\_V2Providers\{383487a6-3676-4870-a4e7-d45b30c35629}\{52bc5412-dac2-449c-8bc2-96443888fe6b} + .CounterSetGuid = { 0x52bc5412, 0xdac2, 0x449c, {0x8b, 0xc2, 0x96, 0x44, 0x38, 0x88, 0xfe, 0x6b} }, + .Size = sizeof(querySpec), + .CounterId = PERF_WILDCARD_COUNTER, + .InstanceId = PERF_WILDCARD_COUNTER, + }, + .Name = L"\\_TZ.CPUZ", // The standard(?) instance name for CPU temperature in the thermal provider + }; - __attribute__((__cleanup__(ffPdhOpenCloseQuery))) HQUERY query = NULL; + DWORD dataSize = 0; + if (PerfEnumerateCounterSetInstances(NULL, &querySpec.Identifier.CounterSetGuid, NULL, 0, &dataSize) != ERROR_NOT_ENOUGH_MEMORY) + return "PerfEnumerateCounterSetInstances() failed"; - if (PdhOpenQueryW(NULL, 0, &query) != ERROR_SUCCESS) - return "Failed to open PDH query"; + if (dataSize <= sizeof(PERF_INSTANCE_HEADER)) + return "No `Thermal Zone Information` instances found"; - HCOUNTER counter = NULL; - if (PdhAddEnglishCounterW(query, - L"\\Thermal Zone Information(*)\\Temperature", - 0, - &counter) != ERROR_SUCCESS) - return "Failed to add TZI temperature counter"; + { + FF_AUTO_FREE PERF_INSTANCE_HEADER* const pHead = malloc(dataSize); + if (PerfEnumerateCounterSetInstances(NULL, &querySpec.Identifier.CounterSetGuid, pHead, dataSize, &dataSize) != ERROR_SUCCESS) + return "PerfEnumerateCounterSetInstances() failed to get instance headers"; - if (PdhCollectQueryData(query) != ERROR_SUCCESS) - return "Failed to collect query data"; + PERF_INSTANCE_HEADER* pInstanceHeader = pHead; + while (1) + { + const wchar_t* instanceName = (const wchar_t*)((BYTE*)pInstanceHeader + sizeof(*pInstanceHeader)); + if (wcscmp(instanceName, querySpec.Name) == 0) + break; - PDH_FMT_COUNTERVALUE value; - if (PdhGetFormattedCounterValue(counter, PDH_FMT_DOUBLE, NULL, &value) != ERROR_SUCCESS) - return "Failed to format counter value"; + dataSize -= pInstanceHeader->Size; + if (dataSize == 0) + break; + pInstanceHeader = (PERF_INSTANCE_HEADER*)((BYTE*)pInstanceHeader + pInstanceHeader->Size); + } - *result = value.doubleValue - 273; + if (dataSize == 0) + { + const wchar_t* instanceName = (const wchar_t*)((BYTE*)pHead + sizeof(*pHead)); + wcscpy(querySpec.Name, instanceName); // Use the first instance name if the specific one is not found + } + } + + __attribute__((__cleanup__(ffPerfCloseQueryHandle))) + HANDLE hQuery = NULL; + + if (PerfOpenQueryHandle(NULL, &hQuery) != ERROR_SUCCESS) + return "PerfOpenQueryHandle() failed"; + + if (PerfAddCounters(hQuery, &querySpec.Identifier, sizeof(querySpec)) != ERROR_SUCCESS) + return "PerfAddCounters() failed"; + + if (querySpec.Identifier.Status != ERROR_SUCCESS) + return "PerfAddCounters() reports invalid identifier"; + + if (PerfQueryCounterData(hQuery, NULL, 0, &dataSize) != ERROR_NOT_ENOUGH_MEMORY) + return "PerfQueryCounterData(NULL) failed"; + + if (dataSize <= sizeof(PERF_DATA_HEADER) + sizeof(PERF_COUNTER_HEADER)) // PERF_ERROR_RETURN, should not happen + return "instance doesn't exist"; + + FF_AUTO_FREE PERF_DATA_HEADER* const pDataHeader = malloc(dataSize); + + if (PerfQueryCounterData(hQuery, pDataHeader, dataSize, &dataSize) != ERROR_SUCCESS) + return "PerfQueryCounterData(pDataHeader) failed"; + + PERF_COUNTER_HEADER* pCounterHeader = (PERF_COUNTER_HEADER*)(pDataHeader + 1); + if (pCounterHeader->dwType != PERF_MULTIPLE_COUNTERS) + return "Invalid counter type"; + + PERF_MULTI_COUNTERS* pMultiCounters = (PERF_MULTI_COUNTERS*)(pCounterHeader + 1); + PERF_COUNTER_DATA* pCounterData = (PERF_COUNTER_DATA*)((BYTE*)pMultiCounters + pMultiCounters->dwSize); + + for (ULONG iCounter = 0; iCounter != pMultiCounters->dwCounters; iCounter++) + { + if (pCounterData->dwDataSize == sizeof(int32_t)) + { + DWORD* pCounterIds = (DWORD*)(pMultiCounters + 1); + switch (pCounterIds[iCounter]) { + case 0: // Temperature + *result = *(int32_t*)(pCounterData + 1) - 273; + break; + case 3: // High Precision Temperature + *result = *(int32_t*)(pCounterData + 1) / 10.0 - 273; + break; + } + } + + pCounterData = (PERF_COUNTER_DATA*)((BYTE*)pCounterData + pCounterData->dwSize); + } return NULL; } diff --git a/src/detection/cpu/perflib_.h b/src/detection/cpu/perflib_.h new file mode 100644 index 000000000..5c6a3febc --- /dev/null +++ b/src/detection/cpu/perflib_.h @@ -0,0 +1,133 @@ +#pragma once + +#include +#include + +// Missing from of MinGW-w64 SDK + +#define PERF_WILDCARD_COUNTER 0xFFFFFFFF +#define PERF_WILDCARD_INSTANCE L"*" +#define PERF_AGGREGATE_INSTANCE L"_Total" +#define PERF_MAX_INSTANCE_NAME 1024 + +typedef struct _PERF_INSTANCE_HEADER { + ULONG Size; // = sizeof(PERF_INSTANCE_HEADER) + sizeof(InstanceName) + sizeof(Padding) + ULONG InstanceId; // Instance ID. + // Followed by: + // WCHAR InstanceName[]; // Nul-terminated. + // WCHAR Padding[]; // Pad to a multiple of 8 bytes +} PERF_INSTANCE_HEADER, *PPERF_INSTANCE_HEADER; + +typedef struct _PERF_COUNTER_IDENTIFIER { + GUID CounterSetGuid; // The GUID of the counterset. + ULONG Status; // Win32 error code indicating success/failure of the add/delete operation. + ULONG Size; // sizeof(PERF_COUNTER_IDENTIFIER) + sizeof(InstanceName) + sizeof(Padding) + ULONG CounterId; // CounterId, or PERF_WILDCARD_COUNTER for all counters. + ULONG InstanceId; // InstanceId, or 0xFFFFFFFF to not filter on instance ID. + ULONG Index; // Set by PerfQueryCounterInfo to the position in which the corresponding counter data is returned. + ULONG Reserved; // Reserved. + // Followed by: + // WCHAR InstanceName[]; + // WCHAR Padding[]; +} PERF_COUNTER_IDENTIFIER, * PPERF_COUNTER_IDENTIFIER; + +typedef struct _PERF_DATA_HEADER { + ULONG dwTotalSize; // = sizeof(PERF_DATA_HEADER) + sizeof(PERF_COUNTER_HEADER blocks...) + ULONG dwNumCounters; // The number of PERF_COUNTER_HEADER blocks. + LONGLONG PerfTimeStamp; // Timestamp from a high-resolution clock. + LONGLONG PerfTime100NSec; // The number of 100 nanosecond intervals since January 1, 1601, in Coordinated Universal Time (UTC). + LONGLONG PerfFreq; // The frequency of a high-resolution clock. + SYSTEMTIME SystemTime; // The time at which data is collected on the provider side. + // Followed by: + // PERF_COUNTER_HEADER blocks...; +} PERF_DATA_HEADER, * PPERF_DATA_HEADER; + +typedef enum _PerfCounterDataType { + PERF_ERROR_RETURN = 0, /* An error occurred when the performance counter value was queried. */ + PERF_SINGLE_COUNTER = 1, /* Query returned a single counter from a single-instance. */ + PERF_MULTIPLE_COUNTERS = 2, /* Query returned multiple counters from a single instance. */ + PERF_MULTIPLE_INSTANCES = 4, /* Query returned a single counter from each of multiple instances. */ + PERF_COUNTERSET = 6 /* Query returned multiple counters from each of multiple instances. */ +} PerfCounterDataType; + +typedef struct _PERF_COUNTER_HEADER { + ULONG dwStatus; // Win32 error code indicating success/failure of the query operation. + PerfCounterDataType dwType; // Result type - error, single/single, multi/single, single/multi, multi/multi. + ULONG dwSize; // = sizeof(PERF_COUNTER_HEADER) + sizeof(Additional data) + ULONG Reserved; // Reserved. + // Followed by additional data: + // If dwType == PERF_ERROR_RETURN: nothing. + // If dwType == PERF_SINGLE_COUNTER: PERF_COUNTER_DATA block. + // If dwType == PERF_MULTIPLE_COUNTERS: PERF_MULTI_COUNTERS block + PERF_COUNTER_DATA blocks. + // If dwType == PERF_MULTIPLE_INSTANCES: PERF_MULTI_INSTANCES block. + // If dwType == PERF_COUNTERSET: PERF_MULTI_COUNTERS block + PERF_MULTI_INSTANCES block. +} PERF_COUNTER_HEADER, * PPERF_COUNTER_HEADER; + +typedef struct _PERF_MULTI_COUNTERS { + ULONG dwSize; // sizeof(PERF_MULTI_COUNTERS) + sizeof(CounterIds) + ULONG dwCounters; // Number of counter ids. + // Followed by: + // DWORD CounterIds[dwCounters]; +} PERF_MULTI_COUNTERS, * PPERF_MULTI_COUNTERS; + +typedef struct _PERF_COUNTER_DATA { + ULONG dwDataSize; // Size of the counter data, in bytes. + ULONG dwSize; // = sizeof(PERF_COUNTER_DATA) + sizeof(Data) + sizeof(Padding) + // Followed by: + // BYTE Data[dwDataSize]; + // BYTE Padding[]; +} PERF_COUNTER_DATA, * PPERF_COUNTER_DATA; + +_Success_(return == ERROR_SUCCESS) +ULONG +WINAPI +PerfEnumerateCounterSetInstances( + _In_opt_z_ LPCWSTR szMachine, + _In_ LPCGUID pCounterSetId, + _Out_opt_bytecap_post_bytecount_(cbInstances, *pcbInstancesActual) PPERF_INSTANCE_HEADER pInstances, + DWORD cbInstances, + _Out_ LPDWORD pcbInstancesActual + ); + +_Success_(return == ERROR_SUCCESS) +ULONG +WINAPI +PerfOpenQueryHandle( + _In_opt_z_ LPCWSTR szMachine, + _Out_ HANDLE * phQuery + ); + +_Success_(return == ERROR_SUCCESS) +ULONG +WINAPI +PerfCloseQueryHandle( + _In_ HANDLE hQuery + ); + +_Success_(return == ERROR_SUCCESS) +ULONG +WINAPI +PerfAddCounters( + _In_ HANDLE hQuery, + _Inout_bytecount_(cbCounters) PPERF_COUNTER_IDENTIFIER pCounters, + DWORD cbCounters + ); + +_Success_(return == ERROR_SUCCESS) +ULONG +WINAPI +PerfDeleteCounters( + _In_ HANDLE hQuery, + _Inout_bytecount_(cbCounters) PPERF_COUNTER_IDENTIFIER pCounters, + DWORD cbCounters + ); + +_Success_(return == ERROR_SUCCESS) +ULONG +WINAPI +PerfQueryCounterData( + _In_ HANDLE hQuery, + _Out_opt_bytecap_post_bytecount_(cbCounterBlock, *pcbCounterBlockActual) PPERF_DATA_HEADER pCounterBlock, + DWORD cbCounterBlock, + _Out_ LPDWORD pcbCounterBlockActual + );