diff options
Diffstat (limited to 'src/detection/wm/wm_windows.c')
| -rw-r--r-- | src/detection/wm/wm_windows.c | 204 |
1 files changed, 204 insertions, 0 deletions
diff --git a/src/detection/wm/wm_windows.c b/src/detection/wm/wm_windows.c new file mode 100644 index 0000000..ee36399 --- /dev/null +++ b/src/detection/wm/wm_windows.c @@ -0,0 +1,204 @@ +#include "wm.h" +#include "common/mallocHelper.h" +#include "common/io.h" +#include "common/library.h" +#include "common/processing.h" +#include "common/windows/nt.h" +#include "common/windows/unicode.h" +#include "common/windows/version.h" + +#include <stdalign.h> +#include <windows.h> +#include <ntstatus.h> +#include <shlobj.h> +#include <softpub.h> + +typedef enum { + FF_PROCESS_TYPE_NONE, + FF_PROCESS_TYPE_SIGNED = 1 << 0, + FF_PROCESS_TYPE_WINDOWS_STORE = 1 << 1, + FF_PROCESS_TYPE_GUI = 1 << 2, + FF_PROCESS_TYPE_CUI = 1 << 3, +} FFProcessType; + +static bool verifySignature(const wchar_t* filePath) { + FF_LIBRARY_LOAD(wintrust, true, "wintrust" FF_LIBRARY_EXTENSION, -1) + FF_LIBRARY_LOAD_SYMBOL(wintrust, WinVerifyTrustEx, true) + + WINTRUST_FILE_INFO fileInfo = { + .cbStruct = sizeof(fileInfo), + .pcwszFilePath = filePath, + }; + + GUID actionID = WINTRUST_ACTION_GENERIC_VERIFY_V2; + + WINTRUST_DATA trustData = { + .cbStruct = sizeof(trustData), + .dwUIChoice = WTD_UI_NONE, + .fdwRevocationChecks = WTD_REVOKE_NONE, + .dwUnionChoice = WTD_CHOICE_FILE, + .pFile = &fileInfo, + .dwStateAction = WTD_STATEACTION_VERIFY, + .dwProvFlags = WTD_SAFER_FLAG, + }; + + LONG status = ffWinVerifyTrustEx(NULL, &actionID, &trustData); + trustData.dwStateAction = WTD_STATEACTION_CLOSE; + ffWinVerifyTrustEx(NULL, &actionID, &trustData); + + return status == ERROR_SUCCESS; +} + +static bool isProcessTrusted(DWORD processId, FFProcessType processType, UNICODE_STRING* buffer, size_t bufSize) { + FF_AUTO_CLOSE_FD HANDLE hProcess = NULL; + if (!NT_SUCCESS(NtOpenProcess(&hProcess, PROCESS_QUERY_LIMITED_INFORMATION, &(OBJECT_ATTRIBUTES) { + .Length = sizeof(OBJECT_ATTRIBUTES), + }, + &(CLIENT_ID) { .UniqueProcess = (HANDLE) (uintptr_t) processId }))) { + return false; + } + + ULONG size; + if (!NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessImageFileNameWin32, buffer, (ULONG) bufSize, &size)) || + buffer->Length == 0) { + return false; + } + assert(buffer->MaximumLength >= buffer->Length + 2); // NULL terminated + + if (processType & FF_PROCESS_TYPE_WINDOWS_STORE) { + static wchar_t windowsAppsPath[MAX_PATH]; + static uint32_t windowsAppsPathLen; + if (windowsAppsPathLen == 0) { + PWSTR pPath = NULL; + if (SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_ProgramFiles, KF_FLAG_DEFAULT, NULL, &pPath))) { + windowsAppsPathLen = (uint32_t) wcslen(pPath); + memcpy(windowsAppsPath, pPath, windowsAppsPathLen * sizeof(wchar_t)); + memcpy(windowsAppsPath + windowsAppsPathLen, L"\\WindowsApps\\", sizeof(L"\\WindowsApps\\")); + windowsAppsPathLen += strlen("\\WindowsApps\\"); + } else { + windowsAppsPathLen = -1u; + } + CoTaskMemFree(pPath); + } + if (windowsAppsPathLen != -1u && + (buffer->Length <= windowsAppsPathLen * sizeof(wchar_t) || // Path is too short to be in WindowsApps + _wcsnicmp(buffer->Buffer, windowsAppsPath, windowsAppsPathLen) != 0) // Path does not start with WindowsApps + ) { + return false; + } + } + + if (processType & FF_PROCESS_TYPE_SIGNED) { + if (!verifySignature(buffer->Buffer)) { + return false; + } + } + + if (processType & (FF_PROCESS_TYPE_GUI | FF_PROCESS_TYPE_CUI)) { + SECTION_IMAGE_INFORMATION info = {}; + if (!NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessImageInformation, &info, sizeof(info), &size)) || + size != sizeof(info)) { + return false; + } + + if ((processType & FF_PROCESS_TYPE_GUI) && info.SubSystemType != IMAGE_SUBSYSTEM_WINDOWS_GUI) { + return false; + } + if ((processType & FF_PROCESS_TYPE_CUI) && info.SubSystemType != IMAGE_SUBSYSTEM_WINDOWS_CUI) { + return false; + } + } + + return true; +} + +#define ffStrEqualNWS(str, compareTo) (_wcsnicmp(str, L##compareTo, sizeof(compareTo) - 1) == 0) + +const char* ffDetectWMPlugin(FFstrbuf* pluginName) { + alignas(UNICODE_STRING) uint8_t buffer[4096]; + UNICODE_STRING* filePath = (UNICODE_STRING*) buffer; + SYSTEM_PROCESS_INFORMATION* FF_AUTO_FREE pstart = NULL; + + // Multiple attempts in case processes change while + // we are in the middle of querying them. + ULONG size = 0; + for (int attempts = 0;; ++attempts) { + if (size) { + pstart = (SYSTEM_PROCESS_INFORMATION*) realloc(pstart, size); + assert(pstart); + } + NTSTATUS status = NtQuerySystemInformation(SystemProcessInformation, pstart, size, &size); + if (NT_SUCCESS(status)) { + break; + } else if (status == STATUS_INFO_LENGTH_MISMATCH && attempts < 4) { + size += sizeof(SYSTEM_PROCESS_INFORMATION) * 5; + } else { + return "NtQuerySystemInformation(SystemProcessInformation) failed"; + } + } + + for (SYSTEM_PROCESS_INFORMATION* ptr = pstart;; ptr = (SYSTEM_PROCESS_INFORMATION*) ((uint8_t*) ptr + ptr->NextEntryOffset)) { + assert(ptr->ImageName.Length == 0 || ptr->ImageName.MaximumLength >= ptr->ImageName.Length + 2); // NULL terminated + if (ptr->ImageName.Length == strlen("FancyWM-GUI.exe") * sizeof(wchar_t) && + ffStrEqualNWS(ptr->ImageName.Buffer, "FancyWM-GUI.exe") && + isProcessTrusted((DWORD) (uintptr_t) ptr->UniqueProcessId, FF_PROCESS_TYPE_WINDOWS_STORE | FF_PROCESS_TYPE_GUI, filePath, sizeof(buffer))) { + if (instance.config.general.detectVersion && ffGetFileVersion(filePath->Buffer, NULL, pluginName)) { + ffStrbufPrependS(pluginName, "FancyWM "); + } else { + ffStrbufSetStatic(pluginName, "FancyWM"); + } + break; + } else if (ptr->ImageName.Length == strlen("glazewm-watcher.exe") * sizeof(wchar_t) && + ffStrEqualNWS(ptr->ImageName.Buffer, "glazewm-watcher.exe") && + isProcessTrusted((DWORD) (uintptr_t) ptr->UniqueProcessId, FF_PROCESS_TYPE_SIGNED | FF_PROCESS_TYPE_GUI, filePath, sizeof(buffer))) { + if (instance.config.general.detectVersion && ffGetFileVersion(filePath->Buffer, NULL, pluginName)) { + ffStrbufPrependS(pluginName, "GlazeWM "); + } else { + ffStrbufSetStatic(pluginName, "GlazeWM"); + } + break; + } else if (ptr->ImageName.Length == strlen("komorebi.exe") * sizeof(wchar_t) && + ffStrEqualNWS(ptr->ImageName.Buffer, "komorebi.exe") && + isProcessTrusted((DWORD) (uintptr_t) ptr->UniqueProcessId, FF_PROCESS_TYPE_CUI, filePath, sizeof(buffer))) { + if (instance.config.general.detectVersion) { + FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateNWS(filePath->Length / sizeof(wchar_t), filePath->Buffer); + if (ffProcessAppendStdOut(pluginName, (char* const[]) { + path.chars, + "--version", + NULL, + }) == NULL) { + ffStrbufSubstrBeforeFirstC(pluginName, '\n'); + } + } + if (pluginName->length == 0) { + ffStrbufSetStatic(pluginName, "Komorebi"); + } + break; + } + + if (ptr->NextEntryOffset == 0) { + break; + } + } + + return NULL; +} + +const char* ffDetectWMVersion(const FFstrbuf* wmName, FFstrbuf* result, FF_A_UNUSED FFWMOptions* options) { + if (!wmName) { + return "No WM detected"; + } + + if (ffStrbufEqualS(wmName, "dwm.exe")) { + PWSTR pPath = NULL; + if (SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_System, KF_FLAG_DEFAULT, NULL, &pPath))) { + wchar_t fullPath[MAX_PATH]; + wcscpy(fullPath, pPath); + wcscat(fullPath, L"\\dwm.exe"); + ffGetFileVersion(fullPath, NULL, result); + } + CoTaskMemFree(pPath); + return NULL; + } + return "Not supported on this platform"; +} |