Как узнать реальную версию Windows из режима совместимости

в 16:06, , рубрики: Delphi, PEB, метки: ,

Думаю каждый хотя бы раз сталкивался с ситуацией, когда на современной ОС не удавалось запустить старую программу, и помогал в этом случае режим совместимости Windows.

Как узнать реальную версию Windows из режима совместимости

В основе работы данного механизма лежит перехват различных функций и эмуляция их поведения, свойственного указанной версии Windows, например, эмулируются ключи реестра, каталоги с документами и прочее. Все это нужно для того, чтобы программа думала, что запущена в выбранной среде.

Если приложение запущено в режиме совместимости, то вызов GetVersionEx вернет фиктивную версию Windows, что, вероятно, не подойдет для системных программ типа твикеров ОС. Как быть в этом случае?

Анализ экспортируемых функций

На просторах сети наткнулся на способ детектирования по наличию/отсутствию экспортируемых функций у системных библиотек. Пример кода:

TDSiWindowsVersion = (wvUnknown, wvWin31, wvWin95, wvWin95OSR2, wvWin98,
  wvWin98SE, wvWinME, wvWin9x, wvWinNT3, wvWinNT4, wvWin2000, wvWinXP,
  wvWinNT, wvWinServer2003, wvWinVista);
 
function DSiGetTrueWindowsVersion: TDSiWindowsVersion;
 
  function ExportsAPI(module: HMODULE; const apiName: string): boolean;
  begin
    Result := GetProcAddress(module, PChar(apiName)) <> nil;
  end; { ExportsAPI }
 
var
  hKernel32: HMODULE;
 
begin { DSiGetTrueWindowsVersion }
  hKernel32 := GetModuleHandle('kernel32');
  Win32Check(hKernel32 <> 0);
  if ExportsAPI(hKernel32, 'GetLocaleInfoEx') then
    Result := wvWinVista
  else if ExportsAPI(hKernel32, 'GetLargePageMinimum') then
    Result := wvWinServer2003
  else if ExportsAPI(hKernel32, 'GetNativeSystemInfo') then
    Result := wvWinXP
  else if ExportsAPI(hKernel32, 'ReplaceFile') then
    Result := wvWin2000
  else if ExportsAPI(hKernel32, 'OpenThread') then
    Result := wvWinME
  else if ExportsAPI(hKernel32, 'GetThreadPriorityBoost') then
    Result := wvWinNT4
  else if ExportsAPI(hKernel32, 'IsDebuggerPresent') then  //is also in NT4!
    Result := wvWin98
  else if ExportsAPI(hKernel32, 'GetDiskFreeSpaceEx') then  //is also in NT4!
    Result := wvWin95OSR2
  else if ExportsAPI(hKernel32, 'ConnectNamedPipe') then
    Result := wvWinNT3
  else if ExportsAPI(hKernel32, 'Beep') then
    Result := wvWin95
  else // we have no idea
    Result := DSiGetWindowsVersion;
end; { DSiGetTrueWindowsVersion }

Решение интересное, но не считаю его приемлемым, так как с выходом каждой версии Windows требуется нетривиальная поддержка.

Используя WMI

Из википедии

Windows Management Instrumentation (WMI) в дословном переводе — это инструментарий управления Windows. Если говорить более развернутo, то WMI — это одна из базовых технологий для централизованного управления и слежения за работой различных частей компьютерной инфраструктуры под управлением платформы Windows.

Из WMI можно получить и версию Windows. Из документации следует что это можно сделать таким запросом:

SELECT Version FROM Win32_OperatingSystem

Запустив WMI Explorer в режиме совместимости с Windows XP, можно увидеть, что это значение не эмулируется:
Как узнать реальную версию Windows из режима совместимости

Метод работает, более того, он полностью документирован, но медленный, и требует тянуть в проект кучу кода по работе с WMI.

Ищем в реестре

Пожалуй самый элегантный и правильный способ найденный в сети — это подсмотреть значение в реестре:
HLKMSOFTWAREMicrosoftWindows NTCurrentVersionCurrentVersion
Ну что же, попробуем:

with TRegistry.Create do
try
  RootKey := HKEY_LOCAL_MACHINE;
  if OpenKeyReadOnly('SOFTWAREMicrosoftWindows NTCurrentVersion') then
    Edit1.Text := ReadString('CurrentVersion');
finally
  Free;
end;

К сожалению, проверив его на Windows 7 оказалось, что этот ключ реестра эмулируется. Похоже в предыдущих версиях Windows этот способ работал, но, увы — сейчас этот трюк не сработает.

Анализ версии kernel32.dll

Сам не проверял, но говорят, что версия файла у kernel32.dll совпадает с версией Windows. На моем компьютере с Windows 7 это так:
Как узнать реальную версию Windows из режима совместимости
Вполне пригодный способ, но лично мне по непонятным причинам он не нравится, благо есть еще альтернатива.

Анализируем PEB процесса

У каждого Windows-процесса есть структура описывающая его, называется она PEB. Она заполняется при старте процесса и содержит в себе адрес загрузки, список загруженных модулей, параметры командной строки, и, в том числе, версию Windows. Ниже пример модуля, используя который можно получить реальную версию Windows (тестировался на Delphi 2010 Win32):

unit RealWindowsVerUnit;
 
interface
 
uses
  Windows;
 
var
  //Реальная версия ОС, а не та что выдается системой при запуске 
  //в режиме совместимости
  Win32MajorVersionReal: Integer;
  Win32MinorVersionReal: Integer;
 
implementation
 
type
  PPEB=^PEB;
  PEB = record
    InheritedAddressSpace: Boolean;
    ReadImageFileExecOptions: Boolean;
    BeingDebugged: Boolean;
    Spare: Boolean;
    Mutant: Cardinal;
    ImageBaseAddress: Pointer;
    LoaderData: Pointer;
    ProcessParameters: Pointer; //PRTL_USER_PROCESS_PARAMETERS;
    SubSystemData: Pointer;
    ProcessHeap: Pointer;
    FastPebLock: Pointer;
    FastPebLockRoutine: Pointer;
    FastPebUnlockRoutine: Pointer;
    EnvironmentUpdateCount: Cardinal;
    KernelCallbackTable: PPointer;
    EventLogSection: Pointer;
    EventLog: Pointer;
    FreeList: Pointer; //PPEB_FREE_BLOCK;
    TlsExpansionCounter: Cardinal;
    TlsBitmap: Pointer;
    TlsBitmapBits: array[0..1] of Cardinal;
    ReadOnlySharedMemoryBase: Pointer;
    ReadOnlySharedMemoryHeap: Pointer;
    ReadOnlyStaticServerData: PPointer;
    AnsiCodePageData: Pointer;
    OemCodePageData: Pointer;
    UnicodeCaseTableData: Pointer;
    NumberOfProcessors: Cardinal;
    NtGlobalFlag: Cardinal;
    Spare2: array[0..3] of Byte;
    CriticalSectionTimeout: LARGE_INTEGER;
    HeapSegmentReserve: Cardinal;
    HeapSegmentCommit: Cardinal;
    HeapDeCommitTotalFreeThreshold: Cardinal;
    HeapDeCommitFreeBlockThreshold: Cardinal;
    NumberOfHeaps: Cardinal;
    MaximumNumberOfHeaps: Cardinal;
    ProcessHeaps: Pointer;
    GdiSharedHandleTable: Pointer;
    ProcessStarterHelper: Pointer;
    GdiDCAttributeList: Pointer;
    LoaderLock: Pointer;
    OSMajorVersion: Cardinal;
    OSMinorVersion: Cardinal;
    OSBuildNumber: Cardinal;
    OSPlatformId: Cardinal;
    ImageSubSystem: Cardinal;
    ImageSubSystemMajorVersion: Cardinal;
    ImageSubSystemMinorVersion: Cardinal;
    GdiHandleBuffer: array [0..33] of Cardinal;
    PostProcessInitRoutine: Cardinal;
    TlsExpansionBitmap: Cardinal;
    TlsExpansionBitmapBits: array [0..127] of Byte;
    SessionId: Cardinal;
  end;
 
//Получить блок PEB своего процесса
function GetPDB: PPEB; stdcall;
asm
  MOV EAX, DWORD PTR FS:[30h]
end;
 
initialization
  //Получаем реальную версию ОС
  Win32MajorVersionReal := GetPDB^.OSMajorVersion;
  Win32MinorVersionReal := GetPDB^.OSMinorVersion;
end.

Скорость работы моментальная, ничего лишнего, единственное НО — недокументированная структура PEB, но как известно Microsoft очень заботится об обратной совместимости, так что с большой долей оптимизма можно считать, что раз описание структуры давно бродит по интернету, то в Microsoft она уже считается документированной.

Выводы

А выводы простые, если очень нужно получить реальную версию то WMI отличный вариант, если же требуется легковесное решение — смотри в PEB.

Автор: vic85

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js