#include "pch.hpp" #include "utils.hpp" _Use_decl_annotations_ std::wstring Utils::FormatError(uint32_t Error) { LPWSTR buffer; std::wstring message; auto length = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, Error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), RCAST(LPWSTR)(&buffer), 0, nullptr); if ((buffer != nullptr) && (length > 0)) { message = std::wstring(buffer, length); } else { length = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, GetModuleHandleA("ntdll.dll"), Error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), RCAST(LPWSTR)(&buffer), 0, nullptr); if ((buffer != nullptr) && (length > 0)) { // // NT status codes are formatted with inserts, only use the // initial description if there is one, otherwise just use the // string as is. // message = std::wstring(buffer, length); if (message[0] == L'{') { auto pos = message.find(L'}', 1); if (pos != std::wstring::npos) { message = std::wstring(message.begin() + 1, message.begin() + pos); } } } } if (message.empty()) { message = L"Unknown Error"; } std::wstringstream ss; ss << L"0x" << std::hex << std::setfill(L'0') << std::setw(8) << Error << L" - " << std::move(message); auto res = ss.str(); EraseAll(res, { L'\r', L'\n', L'\t' }); LocalFree(buffer); return res; } _Use_decl_annotations_ HRESULT Utils::FillBufferWithPattern( std::vector& Buffer, std::span Pattern) { if (Buffer.empty()) { REPORT_AND_RETURN_WIN32("FillBufferWithPattern: Buffer is empty", ERROR_INVALID_PARAMETER); } auto bytesRemaining = Buffer.size(); while (bytesRemaining > 0) { auto len = (Pattern.size() > bytesRemaining ? bytesRemaining : Pattern.size()); std::memcpy(&Buffer[Buffer.size() - bytesRemaining], Pattern.data(), len); bytesRemaining -= len; } return S_OK; } _Use_decl_annotations_ HRESULT Utils::GetFileSize( handle_t FileHandle, uint64_t& FileSize) { FileSize = 0; LARGE_INTEGER fileSize; if (GetFileSizeEx(FileHandle, &fileSize) == 0) { REPORT_AND_RETURN_WIN32("GetFileSize: Error getting file size", GetLastError()); } if (fileSize.QuadPart < 0) { REPORT_AND_RETURN_WIN32("GetFileSize: Invalid file size", ERROR_FILE_INVALID); } FileSize = fileSize.QuadPart; return S_OK; } _Use_decl_annotations_ HRESULT Utils::SetFilePointer( handle_t FileHandle, int64_t DistanceToMove, uint32_t MoveMethod) { LARGE_INTEGER distance; distance.QuadPart = DistanceToMove; if (SetFilePointerEx(FileHandle, distance, nullptr, MoveMethod) == 0) { REPORT_AND_RETURN_WIN32("SetFilePointer: Error returned by SetFilePointerEx()", GetLastError()); } return S_OK; } _Use_decl_annotations_ HRESULT Utils::CopyFileByHandle( handle_t SourceHandle, handle_t TargetHandle) { // // Get the file sizes. // uint64_t sourceSize; if (FAILED(GetFileSize(SourceHandle, sourceSize))) { REPORT_AND_RETURN_WIN32("CopyFileByHandle: Error getting source file size", GetLastError()); } // // Set the file pointers to the beginning of the files. // HRESULT hr = SetFilePointer(SourceHandle, 0, FILE_BEGIN); if (FAILED(hr)) { REPORT_AND_RETURN_HR("CopyFileByHandle: Error setting source file pointer", hr); } hr = SetFilePointer(TargetHandle, 0, FILE_BEGIN); if (FAILED(hr)) { REPORT_AND_RETURN_HR("CopyFileByHandle: Error setting target file pointer", hr); } uint64_t bytesRemaining = sourceSize; std::vector buffer; if (bytesRemaining > MaxFileBuffer) { buffer.assign(MaxFileBuffer, 0); } else { buffer.assign(SCAST(size_t)(bytesRemaining), 0); } while (bytesRemaining > 0) { if (bytesRemaining < buffer.size()) { buffer.assign(SCAST(size_t)(bytesRemaining), 0); } DWORD bytesRead = 0; if (ReadFile(SourceHandle, buffer.data(), SCAST(DWORD)(buffer.size()), &bytesRead, nullptr) == 0) { REPORT_AND_RETURN_WIN32("CopyFileByHandle: Error reading source file", GetLastError()); } bytesRemaining -= bytesRead; DWORD bytesWitten = 0; if (WriteFile(TargetHandle, buffer.data(), SCAST(DWORD)(buffer.size()), &bytesWitten, nullptr) == 0) { REPORT_AND_RETURN_WIN32("CopyFileByHandle: Error writing target file", GetLastError()); } } if (FlushFileBuffers(TargetHandle) == 0) { REPORT_AND_RETURN_WIN32("CopyFileByHandle: Error flushing target file", GetLastError()); } if (SetEndOfFile(TargetHandle) == 0) { REPORT_AND_RETURN_WIN32("CopyFileByHandle: Error setting EoF on target file", GetLastError()); } return S_OK; } _Use_decl_annotations_ HRESULT Utils::OverwriteFileContentsWithPattern( handle_t FileHandle, std::span Pattern) { uint64_t targetSize; if (FAILED(Utils::GetFileSize(FileHandle, targetSize))) { REPORT_AND_RETURN_WIN32("OverwriteFileContentsWithPattern: Error getting file size", GetLastError()); } HRESULT hr = SetFilePointer(FileHandle, 0, FILE_BEGIN); if (FAILED(hr)) { REPORT_AND_RETURN_HR("OverwriteFileContentsWithPattern: Error setting file pointer", hr); } uint64_t bytesRemaining = targetSize; std::vector buffer; if (bytesRemaining > MaxFileBuffer) { buffer.resize(MaxFileBuffer); hr = FillBufferWithPattern(buffer, Pattern); if (FAILED(hr)) { REPORT_AND_RETURN_HR("OverwriteFileContentsWithPattern: Error in FillBufferWithPattern()", hr); } } else { buffer.resize(SCAST(size_t)(bytesRemaining)); hr = FillBufferWithPattern(buffer, Pattern); if (FAILED(hr)) { REPORT_AND_RETURN_HR("OverwriteFileContentsWithPattern: Error in FillBufferWithPattern()", hr); } } while (bytesRemaining > 0) { if (bytesRemaining < buffer.size()) { buffer.resize(SCAST(size_t)(bytesRemaining)); hr = FillBufferWithPattern(buffer, Pattern); if (FAILED(hr)) { REPORT_AND_RETURN_HR("OverwriteFileContentsWithPattern: Error in FillBufferWithPattern()", hr); } } DWORD bytesWritten = 0; if (WriteFile(FileHandle, buffer.data(), SCAST(DWORD)(buffer.size()), &bytesWritten, nullptr) == 0) { REPORT_AND_RETURN_WIN32("OverwriteFileContentsWithPattern: Error writing to file", GetLastError()); } bytesRemaining -= bytesWritten; } if (FlushFileBuffers(FileHandle) == 0) { REPORT_AND_RETURN_WIN32("OverwriteFileContentsWithPattern: Error flushing file", GetLastError()); } return S_OK; } _Use_decl_annotations_ HRESULT Utils::OverwriteFileAfterWithPattern( handle_t FileHandle, uint64_t FileOffset, std::span Pattern, uint32_t& WrittenBytes) { WrittenBytes = 0; uint64_t targetSize; if (FAILED(Utils::GetFileSize(FileHandle, targetSize))) { REPORT_AND_RETURN_WIN32("OverwriteFileAfterWithPattern: Error getting file size", GetLastError()); } if (FileOffset >= targetSize) { REPORT_AND_RETURN_WIN32("OverwriteFileAfterWithPattern: FileOffset cannot be greater than targetSize", ERROR_INVALID_PARAMETER); } HRESULT hr = SetFilePointer(FileHandle, FileOffset, FILE_BEGIN); if (FAILED(hr)) { REPORT_AND_RETURN_HR("OverwriteFileAfterWithPattern: Error setting file pointer", hr); } uint64_t bytesRemaining; bytesRemaining = (targetSize - FileOffset); std::vector buffer; if (bytesRemaining > MaxFileBuffer) { buffer.resize(MaxFileBuffer); hr = FillBufferWithPattern(buffer, Pattern); if (FAILED(hr)) { REPORT_AND_RETURN_HR("OverwriteFileAfterWithPattern: Error in FillBufferWithPattern()", hr); } } else { buffer.resize(SCAST(size_t)(bytesRemaining)); hr = FillBufferWithPattern(buffer, Pattern); if (FAILED(hr)) { REPORT_AND_RETURN_HR("OverwriteFileAfterWithPattern: Error in FillBufferWithPattern()", hr); } } while (bytesRemaining > 0) { DWORD bytesWritten = 0; if (bytesRemaining < buffer.size()) { buffer.resize(SCAST(size_t)(bytesRemaining)); hr = FillBufferWithPattern(buffer, Pattern); if (FAILED(hr)) { REPORT_AND_RETURN_HR("OverwriteFileAfterWithPattern: Error in FillBufferWithPattern()", hr); } } if (WriteFile(FileHandle, buffer.data(), SCAST(DWORD)(buffer.size()), &bytesWritten, nullptr) == 0) { REPORT_AND_RETURN_WIN32("OverwriteFileAfterWithPattern: Error writing to file", GetLastError()); } bytesRemaining -= bytesWritten; WrittenBytes += bytesWritten; } if (FlushFileBuffers(FileHandle) == 0) { REPORT_AND_RETURN_WIN32("OverwriteFileAfterWithPattern: Error flushing file", GetLastError()); } return S_OK; } _Use_decl_annotations_ HRESULT Utils::ExtendFileSecurityDirectory( handle_t FileHandle, uint32_t ExtendedBy) { uint64_t targetSize; if (FAILED(Utils::GetFileSize(FileHandle, targetSize))) { REPORT_AND_RETURN_WIN32("ExtendFileSecurityDirectory: Error getting file size", GetLastError()); } ULARGE_INTEGER mappingSize; mappingSize.QuadPart = targetSize; MappingHandle mapping; mapping.get() = CreateFileMappingW(FileHandle, nullptr, PAGE_READWRITE, mappingSize.HighPart, mappingSize.LowPart, nullptr); if (!mapping.valid()) { REPORT_AND_RETURN_WIN32("ExtendFileSecurityDirectory: Error creating file mapping", GetLastError()); } mapping.view() = MapViewOfFile(mapping.get(), FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, mappingSize.LowPart); if (mapping.view() == nullptr) { REPORT_AND_RETURN_WIN32("ExtendFileSecurityDirectory: Error returned by MapViewOfFile()", GetLastError()); } auto dosHeader = RCAST(PIMAGE_DOS_HEADER)(mapping.view()); if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) { // // This is not a PE file, we're done. // REPORT_AND_RETURN_WIN32("ExtendFileSecurityDirectory: Failed parse PE file", ERROR_INVALID_IMAGE_HASH); } auto ntHeader = RCAST(PIMAGE_NT_HEADERS32)(Add2Ptr(mapping.view(), dosHeader->e_lfanew)); if (ntHeader->Signature != IMAGE_NT_SIGNATURE) { REPORT_AND_RETURN_WIN32("ExtendFileSecurityDirectory: Failed parse PE NT Header (x32)", ERROR_INVALID_IMAGE_HASH); } IMAGE_DATA_DIRECTORY* secDir; if (ntHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { if (ntHeader->OptionalHeader.NumberOfRvaAndSizes < IMAGE_DIRECTORY_ENTRY_SECURITY) { // // No security directory, we're done. // return S_OK; } secDir = &ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]; } else if (ntHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) { auto ntHeader64 = RCAST(PIMAGE_NT_HEADERS64)(ntHeader); if (ntHeader64->OptionalHeader.NumberOfRvaAndSizes < IMAGE_DIRECTORY_ENTRY_SECURITY) { // // No security directory, we're done. // return S_OK; } secDir = &ntHeader64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]; } else { REPORT_AND_RETURN_WIN32("ExtendFileSecurityDirectory: Failed parse PE NT Header (x64)", ERROR_INVALID_IMAGE_HASH); } if ((secDir->VirtualAddress) == 0 || (secDir->Size == 0)) { // // No security directory, we're done. // return S_OK; } // // Extend the security directory size. // secDir->Size = (secDir->Size + ExtendedBy); if (FlushViewOfFile(mapping.view(), mappingSize.LowPart) == 0) { DWORD lastError = GetLastError(); REPORT_AND_RETURN_WIN32("ExtendFileSecurityDirectory: Error flushing view of file", lastError); } mapping.close(); if (FlushFileBuffers(FileHandle) == 0) { REPORT_AND_RETURN_WIN32("ExtendFileSecurityDirectory: Error flushing file", GetLastError()); } return S_OK; } _Use_decl_annotations_ HRESULT Utils::GetImageEntryPointRva( handle_t FileHandle, uint32_t& EntryPointRva) { EntryPointRva = 0; uint64_t fileSize; if (FAILED(Utils::GetFileSize(FileHandle, fileSize))) { REPORT_AND_RETURN_WIN32("ImageEntryPointRva: Error getting file size", GetLastError()); } ULARGE_INTEGER mappingSize; mappingSize.QuadPart = fileSize; MappingHandle mapping; mapping.get() = CreateFileMappingW(FileHandle, nullptr, PAGE_READONLY, mappingSize.HighPart, mappingSize.LowPart, nullptr); if (!mapping.valid()) { REPORT_AND_RETURN_WIN32("ImageEntryPointRva: Error creating file mapping", GetLastError()); } mapping.view() = MapViewOfFile(mapping.get(), FILE_MAP_READ, 0, 0, mappingSize.LowPart); if (mapping.view() == nullptr) { REPORT_AND_RETURN_WIN32("ImageEntryPointRva: Error returned by MapViewOfFile()", GetLastError()); } auto dosHeader = RCAST(PIMAGE_DOS_HEADER)(mapping.view()); if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) { REPORT_AND_RETURN_WIN32("ImageEntryPointRva: Failed parse PE file", ERROR_INVALID_IMAGE_HASH); } auto ntHeader = RCAST(PIMAGE_NT_HEADERS32)(Add2Ptr(mapping.view(), dosHeader->e_lfanew)); if (ntHeader->Signature != IMAGE_NT_SIGNATURE) { REPORT_AND_RETURN_WIN32("ImageEntryPointRva: Failed parse PE NT Header (x32)", ERROR_INVALID_IMAGE_HASH); } if (ntHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { EntryPointRva = ntHeader->OptionalHeader.AddressOfEntryPoint; } else if (ntHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) { auto ntHeader64 = RCAST(PIMAGE_NT_HEADERS64)(ntHeader); EntryPointRva = ntHeader64->OptionalHeader.AddressOfEntryPoint; } else { REPORT_AND_RETURN_WIN32("ImageEntryPointRva: Failed parse PE NT Header (x64)", ERROR_INVALID_IMAGE_HASH); } mapping.close(); return S_OK; } class OptionalUnicodeStringHelper { public: OptionalUnicodeStringHelper( _In_opt_ const std::optional& String) : m_String(String) { if (m_String.has_value()) { RtlInitUnicodeString(&m_Unicode, m_String->c_str()); } else { RtlInitUnicodeString(&m_Unicode, L""); } } PUNICODE_STRING Get() { if (m_String.has_value()) { return &m_Unicode; } return nullptr; } operator PUNICODE_STRING() { return Get(); } private: const std::optional& m_String; UNICODE_STRING m_Unicode; }; _Use_decl_annotations_ HRESULT Utils::WriteRemoteProcessParameters( handle_t ProcessHandle, const std::wstring ImageFileName, const std::optional& DllPath, const std::optional& CurrentDirectory, const std::optional& CommandLine, void* EnvironmentBlock, const std::optional& WindowTitle, const std::optional& DesktopInfo, const std::optional& ShellInfo, const std::optional& RuntimeData) { // // Get the basic info for the remote PEB address. // PROCESS_BASIC_INFORMATION pbi{}; NTSTATUS status = NtQueryInformationProcess(ProcessHandle, ProcessBasicInformation, &pbi, sizeof(pbi), nullptr); if (!NT_SUCCESS(status)) { REPORT_AND_RETURN_NT("WriteRemoteProcessParameters: Failed to query process info", status); } // // Generate the process parameters to write into the process. // UNICODE_STRING imageName; RtlInitUnicodeString(&imageName, ImageFileName.c_str()); OptionalUnicodeStringHelper dllPath(DllPath); OptionalUnicodeStringHelper commandLine(CommandLine); OptionalUnicodeStringHelper currentDirectory(CurrentDirectory); OptionalUnicodeStringHelper windowTitle(WindowTitle); OptionalUnicodeStringHelper desktopInfo(DesktopInfo); OptionalUnicodeStringHelper shellInfo(ShellInfo); OptionalUnicodeStringHelper runtimeData(RuntimeData); PRTL_USER_PROCESS_PARAMETERS params; // // Generate the process parameters and do not pass // RTL_USER_PROC_PARAMS_NORMALIZED, this will keep the process parameters // de-normalized (pointers will be offsets instead of addresses) then // LdrpInitializeProcess will call RtlNormalizeProcessParameters and fix // them up when the process starts. // // Note: There is an exception here, the Environment pointer is not // de-normalized - we'll fix that up ourself. // status = RtlCreateProcessParametersEx(¶ms, &imageName, dllPath, currentDirectory, commandLine, EnvironmentBlock, windowTitle, desktopInfo, shellInfo, runtimeData, 0); if (!NT_SUCCESS(status)) { REPORT_AND_RETURN_NT("WriteRemoteProcessParameters: Failed to create process parameters", status); } // // Calculate the required length. // size_t len = params->MaximumLength + params->EnvironmentSize; // // Allocate memory in the remote process to hold the process parameters. // auto remoteMemory = VirtualAllocEx(ProcessHandle, nullptr, len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (remoteMemory == nullptr) { RtlDestroyProcessParameters(params); REPORT_AND_RETURN_WIN32("WriteRemoteProcessParameters: Error allocating memory", GetLastError()); } // // Okay we have some memory in the remote process, go do the final fix-ups. // if (params->Environment != nullptr) { // // The environment block will always be right after the length, which // is the size of RTL_USER_PROCESS_PARAMETERS plus any extra field // data. // params->Environment = Add2Ptr(remoteMemory, params->Length); } // // Write the parameters into the remote process. // if (WriteProcessMemory(ProcessHandle, remoteMemory, params, len, nullptr) == 0) { RtlDestroyProcessParameters(params); REPORT_AND_RETURN_WIN32("WriteRemoteProcessParameters: Error writting parameters into the remote process", GetLastError()); } // // Write the parameter pointer to the remote process PEB. // if (WriteProcessMemory(ProcessHandle, Add2Ptr(pbi.PebBaseAddress, FIELD_OFFSET(PEB, ProcessParameters)), &remoteMemory, sizeof(remoteMemory), nullptr) == 0) { RtlDestroyProcessParameters(params); REPORT_AND_RETURN_WIN32("WriteRemoteProcessParameters: Error writting the parameter pointer to the remote process PEB", GetLastError()); } return S_OK; } #pragma optimize( "", off ) _Use_decl_annotations_ BOOL Utils::ShouldReplaceWithFile( const char* fileName) { return (fileName[0] == '\0') ? FALSE : TRUE; } #pragma optimize( "", on ) _Use_decl_annotations_ HRESULT Utils::GetFileName( const char* sourceFileName, std::wstring& finalFileName) { size_t cbTargetFileName = strnlen_s(sourceFileName, FILE_MAX_PATH); int sizeNeeded = MultiByteToWideChar(CP_UTF8, 0, sourceFileName, (int)cbTargetFileName, NULL, 0); if (sizeNeeded == 0) { REPORT_AND_RETURN_WIN32("GetFileName: Error getting required size to convert filename to wide chars", GetLastError()); } std::wstring TargetFileNameTmp(sizeNeeded, 0); if (MultiByteToWideChar(CP_UTF8, 0, sourceFileName, (int)cbTargetFileName, &TargetFileNameTmp[0], sizeNeeded) == 0) { REPORT_AND_RETURN_WIN32("GetFileName: Error converting filename to wide chars", GetLastError()); } sizeNeeded = ExpandEnvironmentStringsW(TargetFileNameTmp.c_str(), NULL, 0); if (sizeNeeded == 0) { REPORT_AND_RETURN_WIN32("GetFileName: Error getting required size to expand filename", GetLastError()); } finalFileName.resize(((size_t)sizeNeeded) - 1, 0); if (ExpandEnvironmentStringsW(TargetFileNameTmp.c_str(), &finalFileName[0], sizeNeeded) == 0) { REPORT_AND_RETURN_WIN32("GetFileName: Error expanding filename", GetLastError()); } return S_OK; } #ifndef _WIN64 // // Only needed for 32-bit Windows // _Use_decl_annotations_ HRESULT Utils::GetFileVersion(LPCWSTR lptstrFilename, PFILE_VERSION ver) { DWORD dwHandle; DWORD dwLen = GetFileVersionInfoSizeW(lptstrFilename, &dwHandle); if (dwLen == 0) { REPORT_AND_RETURN_WIN32("GetFileVersion: Error getting file version info size", GetLastError()); } LPVOID lpData = new LPVOID[dwLen]; if (!GetFileVersionInfoW(lptstrFilename, 0, dwLen, lpData)) { delete[] lpData; REPORT_AND_RETURN_WIN32("GetFileVersion: Error getting file version info", GetLastError()); } VS_FIXEDFILEINFO* versionInfo; UINT uLen; if (!VerQueryValueW(lpData, L"\\", (LPVOID*)&versionInfo, &uLen)) { delete[] lpData; REPORT_AND_RETURN_WIN32("GetFileVersion: Error getting version info", GetLastError()); } ver->MajorVersion = (versionInfo->dwProductVersionMS >> 16) & 0xFFFF; ver->MinorVersion = versionInfo->dwProductVersionMS & 0xFFFF; ver->BuildVersion = (versionInfo->dwProductVersionLS >> 16) & 0xFFFF; ver->RevisionVersion = versionInfo->dwProductVersionLS & 0xFFFF; delete[] lpData; return S_OK; } _Use_decl_annotations_ HRESULT Utils::IsBuggyKernel() { std::wstring kernelFile; HRESULT hr = Utils::GetFileName("%SystemRoot%\\System32\\ntoskrnl.exe", kernelFile); if (FAILED(hr)) { REPORT_AND_RETURN_HR("Failed to retrieve the target filename", hr); } FileHandle kernelHandle(kernelFile); kernelHandle.get() = CreateFileW(kernelFile.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (!kernelHandle.valid()) { REPORT_AND_RETURN_WIN32("BuggyKernel: Failed to open Kernel file", GetLastError()); } FILE_VERSION ver; hr = GetFileVersion(kernelFile.c_str(), &ver); if (FAILED(hr)) { REPORT_AND_RETURN_HR("BuggyKernel: Failed getting file version", hr); } dprintf("Version of %S is %hu.%hu.%hu.%hu", kernelFile.c_str(), ver.MajorVersion, ver.MinorVersion, ver.BuildVersion, ver.RevisionVersion ); if (ver.MajorVersion == 10 && ver.MinorVersion == 0 && ver.BuildVersion > 10240 && ver.BuildVersion < 16299) { return S_OK; } return S_FALSE; } #endif