diff --git a/src/common/windows/nt.h b/src/common/windows/nt.h index ceecbf9c8..dd2caa06d 100644 --- a/src/common/windows/nt.h +++ b/src/common/windows/nt.h @@ -13,6 +13,8 @@ enum { SystemLogicalProcessorAndGroupInformation = 107, SystemBasicPerformanceInformation = 123, SystemSecureBootInformation = 146, + SystemBasicProcessInformation = 252, + SystemHandleCountInformation = 253, }; typedef struct _PROCESSOR_POWER_INFORMATION { @@ -206,6 +208,20 @@ typedef struct _SYSTEM_BASIC_PERFORMANCE_INFORMATION { SIZE_T PeakCommitment; } SYSTEM_BASIC_PERFORMANCE_INFORMATION, *PSYSTEM_BASIC_PERFORMANCE_INFORMATION; +typedef struct _SYSTEM_BASICPROCESS_INFORMATION { + ULONG NextEntryOffset; + HANDLE UniqueProcessId; + HANDLE InheritedFromUniqueProcessId; + ULONG64 SequenceNumber; + UNICODE_STRING ImageName; +} SYSTEM_BASICPROCESS_INFORMATION, *PSYSTEM_BASICPROCESS_INFORMATION; + +typedef struct _SYSTEM_HANDLECOUNT_INFORMATION { + ULONG ProcessCount; + ULONG ThreadCount; + ULONG HandleCount; +} SYSTEM_HANDLECOUNT_INFORMATION, *PSYSTEM_HANDLECOUNT_INFORMATION; + NTSYSAPI NTSTATUS NTAPI NtDelayExecution(_In_ BOOLEAN Alertable, _In_ PLARGE_INTEGER DelayInterval); /** @@ -691,15 +707,19 @@ static inline uint64_t ffKSystemTimeToUInt64(const volatile KSYSTEM_TIME* pTime) static inline bool ffIsWindows10OrGreater() { #if FF_WIN81_COMPAT - return SharedUserData->NtMajorVersion >= 10; + return SharedUserData->NtBuildNumber >= 10240; #else return true; #endif } static inline bool ffIsWindows11OrGreater() { - return SharedUserData->NtMajorVersion > 10 || - (SharedUserData->NtMajorVersion == 10 && SharedUserData->NtBuildNumber >= 22000); + return SharedUserData->NtBuildNumber >= 22000; +} + +static inline bool ffIsSystemBasicProcessInfoAvailable() { // Includes HandleCountInformation, which was added together + // https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation#systembasicprocessinformation + return SharedUserData->NtBuildNumber >= 26200; // MSDN says it was added in 26100.4770; ntdoc says 25H2 } NTSYSAPI NTSTATUS NTAPI NtOpenProcessToken( diff --git a/src/detection/initsystem/initsystem_windows.c b/src/detection/initsystem/initsystem_windows.c index f41a66cfa..a6a90f3ce 100644 --- a/src/detection/initsystem/initsystem_windows.c +++ b/src/detection/initsystem/initsystem_windows.c @@ -7,32 +7,65 @@ #include #include +static bool fillResult(FFInitSystemResult* result, uint32_t ppid, uint32_t pid, uint16_t len, PCWSTR name) { + if (ppid != 4 /* System */ || len <= 4 || _wcsnicmp(name + len - 4, L".exe", 4) != 0) { // smss.exe + return false; + } + result->pid = pid; + // We have no permission to open the process for querying the full information + wchar_t exePath[MAX_PATH]; + _snwprintf(exePath, ARRAY_SIZE(exePath), L"%ls\\system32\\%.*ls", (const wchar_t*) SharedUserData->NtSystemRoot, len, name); + ffGetFileVersion(exePath, NULL, &result->version); + ffStrbufSetWS(&result->exe, exePath); + ffStrbufSetNWS(&result->name, len - 4, name); + return true; +} + const char* ffDetectInitSystem(FFInitSystemResult* result) { - // We only need to find the first user process, so 1024 entries should be enough - SYSTEM_PROCESS_INFORMATION buffer[1024] = {}; - ULONG size = sizeof(buffer); - NTSTATUS status = NtQuerySystemInformation(SystemProcessInformation, buffer, size, &size); - if (status != STATUS_INFO_LENGTH_MISMATCH && !NT_SUCCESS(status)) { - return "NtQuerySystemInformation(SystemProcessInformation) failed"; + if (ffIsSystemBasicProcessInfoAvailable()) { + // SYSTEM_BASICPROCESS_INFORMATION entries are much smaller than SYSTEM_PROCESS_INFORMATION ones, + // so a modest buffer should be enough to contain all processes + SYSTEM_BASICPROCESS_INFORMATION buffer[1024]; + NTSTATUS status = NtQuerySystemInformation(SystemBasicProcessInformation, buffer, sizeof(buffer), NULL); + if (status != STATUS_INFO_LENGTH_MISMATCH && !NT_SUCCESS(status)) { + goto fallback; + } + for (auto ptr = buffer; ;ptr = (PSYSTEM_BASICPROCESS_INFORMATION) ((uint8_t*) ptr + ptr->NextEntryOffset)) { + assert(ptr >= buffer && (uint8_t*) ptr < (uint8_t*) buffer + sizeof(buffer)); + if (fillResult(result, (uint32_t)(uintptr_t) ptr->InheritedFromUniqueProcessId, + (uint32_t)(uintptr_t) ptr->UniqueProcessId, + ptr->ImageName.Length / sizeof(*ptr->ImageName.Buffer), + ptr->ImageName.Buffer)) { + return nullptr; + } + // The last process in the list always has a NextEntryOffset of 0, even if the buffer was truncated. + if (!ptr->NextEntryOffset) { + return "Could not find init system process"; + } + } } - for (SYSTEM_PROCESS_INFORMATION* ptr = buffer; ; ptr = (SYSTEM_PROCESS_INFORMATION*) ((uint8_t*) ptr + ptr->NextEntryOffset)) { - assert(ptr >= buffer && (uint8_t*) ptr < (uint8_t*) buffer + sizeof(buffer)); - uint16_t len = ptr->ImageName.Length / sizeof(*ptr->ImageName.Buffer); - if (ptr->InheritedFromUniqueProcessId == (HANDLE)(uintptr_t) 4 /* System */ && - len > 4 && _wcsnicmp(ptr->ImageName.Buffer + len - 4, L".exe", 4) == 0) { // smss.exe - result->pid = (uint32_t)(uintptr_t) ptr->UniqueProcessId; - // We have no permission to open the process for querying the full information - wchar_t exePath[MAX_PATH]; - _snwprintf(exePath, ARRAY_SIZE(exePath), L"%ls\\system32\\%.*ls", (const wchar_t*) SharedUserData->NtSystemRoot, len, ptr->ImageName.Buffer); - ffGetFileVersion(exePath, NULL, &result->version); - ffStrbufSetWS(&result->exe, exePath); - ffStrbufSetNWS(&result->name, len - 4, ptr->ImageName.Buffer); - return nullptr; +fallback: + { + // We only need to find the first user process, so 1024 entries should be enough + SYSTEM_PROCESS_INFORMATION buffer[1024] = {}; + ULONG size = sizeof(buffer); + NTSTATUS status = NtQuerySystemInformation(SystemProcessInformation, buffer, size, &size); + if (status != STATUS_INFO_LENGTH_MISMATCH && !NT_SUCCESS(status)) { + return "NtQuerySystemInformation(SystemProcessInformation) failed"; } - // The last process in the list always has a NextEntryOffset of 0, even if the buffer was truncated. - if (!ptr->NextEntryOffset) { - return "Could not find init system process"; + + for (auto ptr = buffer; ; ptr = (SYSTEM_PROCESS_INFORMATION*) ((uint8_t*) ptr + ptr->NextEntryOffset)) { + assert(ptr >= buffer && (uint8_t*) ptr < (uint8_t*) buffer + sizeof(buffer)); + uint16_t len = ptr->ImageName.Length / sizeof(*ptr->ImageName.Buffer); + if (fillResult(result, (uint32_t)(uintptr_t) ptr->InheritedFromUniqueProcessId, + (uint32_t)(uintptr_t) ptr->UniqueProcessId, len, ptr->ImageName.Buffer)) { + return nullptr; + } + // The last process in the list always has a NextEntryOffset of 0, even if the buffer was truncated. + if (!ptr->NextEntryOffset) { + return "Could not find init system process"; + } } } }