• 关于 GetVersion 系列接口

    关于如何获取 Windows 系统版本号的话题,网上已经有了太多的帖子。但个人觉得总结的都不尽全面,或者没有给出比较稳定的解决方案。

    众所周知,获取 Windows 系统版本的 API 是 GetVersionGetVersionEx。这两个 API 的使用也都相当简单,一直被广泛使用(下文中我们将其统称为 GetVersion 系列)。后来在 Windows XP 中微软引入了应用程序兼容模式,可以选择以兼容之前 Windows 系统版本的模式运行程序。可能很多人并不知道其具体的实现原理,以及能造成如何影响,微软官网开始也未对此详细说明。但直到后来,随着 Windows Vista、Windows 7 等发布,开始有人反映这个 API 并不能获取到真正的系统版本号。我也开始尝试测试各种获取系统版本信息的 API,才慢慢发现应用兼容性设置其中一个影响是让用户无法获取正确的系统版本号。即,将程序设置为兼容旧的操作系统,则 GetVersion 系列接口获取到的版本就是该兼容系统的版本,也就是说结果与当前系统实际版本不符,是错误的。

    与此同时,Windows XP 中也引入了主题,引入了 manifest 文件(Visual Studio 中称之为清单文件)的概念。关于这个清单文件,我在这篇文章中讨论程序执行权限也提到过,此文暂不详述。清单文件中的设置会影响到程序的某些行为,比如是否使用主题、是否以管理员权限执行、程序支持的操作系统列表、是否受 DPI 缩放影响等。这里我们只讨论其中「支持的操作系统列表」这部分,因为这部分现在也会影响到在新版操作系统中调用 GetVersion 系列的结果。

    虽然微软的官网对于该系列 API 的行为进行了说明,但毕竟实践才是唯一标准。为了搞清楚各个系统版本的 GetVersion 系列接口结果行为有何不同,我详细测试后,将其整理如下:

    是否嵌入清单 程序无清单文件或清单未指定支持当前系统 程序有清单文件且清单指定支持当前系统
    兼容模式设置 未设置兼容模式 设置兼容模式 未设置兼容模式 设置兼容模式
    Windows 2000 5.0 5.0[1] 5.0[2] 5.0[2]
    Windows XP (x86) 5.1 兼容模式设置的兼容系统版本 5.1[3] 5.1[3]
    Windows XP (x64) 5.2 兼容模式设置的兼容系统版本 5.2[3] 5.2[3]
    Windows Vista 6.0 兼容模式设置的兼容系统版本 6.0 兼容模式设置的兼容系统版本
    Windows 7 6.1 兼容模式设置的兼容系统版本 6.1 兼容模式设置的兼容系统版本
    Windows 8 6.2 兼容模式设置的最高兼容系统版本,最高 6.2。 6.2 兼容模式设置的最高兼容系统版本,最高 6.2。
    Windows 8.1 6.2 兼容模式设置的最高兼容系统版本,最高 6.2。 6.3 兼容模式设置的最高兼容系统版本,最高 6.3。
    Windows 10 6.2 兼容模式设置的最高兼容系统版本,最高 6.2。 10.0 兼容模式设置的最高兼容系统版本,最高 10.0。
    [1] Windows 2000 不支持兼容模式,因此结果不受影响。
    [2] Windows 2000 不支持清单文件,因此结果不受影响。
    [3] Windows XP 不支持清单文件中指定的支持操作系统列表。

    可以看得出来,结果惨不忍睹,这还怎么能让人放心使用?实际上 GetVersion 系列接口的行为变更从 XP 时代就有,然而微软开始并没有在 MSDN 上给出相关说明,也没有多少人留意。起初微软是为设置应用程序以兼容模式运行,将其 hook 并返回错误的结果来实现让老程序在新的操作系统上以旧版本操作系统的「兼容模式」运行。然而,这个带来了更大的麻烦,再加上 manifest 的引入,使得这个 API 完全被微软玩坏。到 Windows 8 时代,该页面才注明该 API 已被废弃,并且给出其他的解决方案。只能说,这两个 API 走到今天这条路,微软也是自食其果,其返回值从一开始被 hook 修改就注定了今天被抛弃的结果。

  • 官方推荐的备用方案

    此外,微软也提供其他的几个 API 用来判断(不能获取)系统版本是否为特定版本,只是鲜为人知,使用频率较低。从 GetVersion 系列被抛弃开始,这些 API 才在 MSDN 被列出在 GetVersionEx 的说明页面,作为其他的备选方案。

    • IsOS

      这个 API 是判断特定版本的,但是最高支持也就到 Windows 2003。此后,微软 MSDN 页面未对参数进行更新。

    • VerifyVersionInfo

      该 API 需配合 VerSetConditionMask 预先设置条件和逻辑,再进行后续判断。微软已经将 VerifyVersionInfo 封装为如下更易使用的函数。使用这些函数需包含 VersionHelpers.h 头文件,较新版本的 Visual Studio 或 Windows SDK 中提供此头文件。

      这组函数依然只能够用来判断而不能获取系统版本。而且,根据 MSDN 的说明,其中的部分依然受到清单文件影响,但未测试。

    • NetWkstaGetInfo

      这个 API 也是微软官方推荐的获取系统版本号的替代方案之一。

      #include <windows.h>
      #include <lm.h>
      #pragma comment(lib, "netapi32.lib") DWORD PASCAL GetVersion( void )
      {
      DWORD dwVersion = 0;
      WKSTA_INFO_100 *wkstaInfo = NULL;
      NET_API_STATUS netStatus = NetWkstaGetInfo(NULL, 100, (BYTE **)&wkstaInfo);
      if (netStatus == NERR_Success)
      {
      DWORD dwMajVer = wkstaInfo->wki100_ver_major;
      DWORD dwMinVer = wkstaInfo->wki100_ver_minor;
      dwVersion = (DWORD)MAKELONG(dwMinVer, dwMajVer);
      NetApiBufferFree(wkstaInfo);
      }
      return dwVersion;
      }

      经测试,获取的系统主次版本号正确,不受兼容性设置和清单文件的影响,但无法获取 build 版本。某些场景下在 dll 中调用会失败,原因未知,使用时需要注意。

  • 非官方备用方案

    这里提供一些在网上搜索到的其他方案。由于部分使用了系统内部接口甚至数据结构,不保证后续依然有效。

    • 查询 kernel32.dll 版本

      通常情况下 kernel32.dll 的版本号和系统是同步的,但如果微软哪天不遵守这个约定,这个方法就不好用了。有的程序则是查询 ntoskrnl.exe 的版本信息,原理类似。

      #include <windows.h>
      #include <shlwapi.h>
      #pragma comment(lib, "shlwapi.lib")
      #pragma comment(lib, "version.lib") DWORD PASCAL GetKernelVersion( void )
      {
      DWORD dwVersion = 0;
      WCHAR szDLLName[MAX_PATH] = { 0 };
      HRESULT hr = SHGetFolderPathW(NULL, CSIDL_SYSTEM, NULL, SHGFP_TYPE_CURRENT, szDLLName);
      if ((hr == S_OK) && PathAppendW(szDLLName, L"kernel32.dll"))
      {
      DWORD dwVerInfoSize = GetFileVersionInfoSizeW(szDLLName, NULL);
      if (dwVerInfoSize > 0)
      {
      HANDLE hHeap = GetProcessHeap();
      LPVOID pvVerInfoData = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, dwVerInfoSize);
      if (pvVerInfoData != NULL)
      {
      if (GetFileVersionInfoW(szDLLName, 0, dwVerInfoSize, pvVerInfoData))
      {
      UINT ulLength = 0;
      VS_FIXEDFILEINFO *pvffi = NULL;
      if (VerQueryValueW(pvVerInfoData, L"\\", (LPVOID *)&pvffi, &ulLength))
      {
      dwVersion = pvffi->dwFileVersionMS;
      }
      }
      HeapFree(hHeap, 0, pvVerInfoData);
      }
      }
      }
      return dwVersion;
      }

      很不幸,经测试,如果程序没有嵌入清单文件,在 Windows 8.1 或 Windows 10,这个方法获取的结果也是 6.2,也就是说仍然受到清单文件的影响,有可能得到错误的结果。

    • 读取 kernel32.dll 版本

      什么,还有个读取?那么查询和读取有什么分别?没看到上面最后一行吗,连获取文件版本信息的 API 都拿不到正确结果了,微软还有什么能相信?好吧,你不给我正确结果,我就直接分析二进制总行了吧!

      #include <windows.h>
      
      DWORD PASCAL ReadKernelVersion( void )
      {
      DWORD dwVersion = 0;
      HMODULE hinstDLL = LoadLibraryExW(L"kernel32.dll", NULL, LOAD_LIBRARY_AS_DATAFILE);
      if (hinstDLL != NULL)
      {
      HRSRC hResInfo = FindResource(hinstDLL, MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION);
      if (hResInfo != NULL)
      {
      HGLOBAL hResData = LoadResource(hinstDLL, hResInfo);
      if (hResData != NULL)
      {
      static const WCHAR wszVerInfo[] = L"VS_VERSION_INFO";
      struct VS_VERSIONINFO {
      WORD wLength;
      WORD wValueLength;
      WORD wType;
      WCHAR szKey[ARRAYSIZE(wszVerInfo)];
      VS_FIXEDFILEINFO Value;
      WORD Children[];
      } *lpVI = (struct VS_VERSIONINFO *)LockResource(hResData);
      if ( (lpVI != NULL) && (lstrcmpiW(lpVI->szKey, wszVerInfo) == 0) && (lpVI->wValueLength > 0) )
      {
      dwVersion = lpVI->Value.dwFileVersionMS;
      }
      }
      }
      FreeLibrary(hinstDLL);
      }
      return dwVersion;
      }

      很高兴的告诉大家,这个结果即使在 Windows 8.1 或 Windows 10 上,也都依然是正确的。

    • 读取 PEB 数据结构

      PEB 结构是 Windows 系统的内部接口,读取的数据是最底层的,但是也正因为是内部结构,微软随时有可能变动。下面的结构体只是简略定义,对不需要或者不重点关注的成员进行了省略或者使用了 PVOID 指针来代替。务必注意,此方法仅供参考,如后期 Windows 系统变更数据结构,造成任何蓝屏死机问题,本人概不负责。

      #include <windows.h>
      
      typedef struct _PEB {
      BOOLEAN InheritedAddressSpace;
      BOOLEAN ReadImageFileExecOptions;
      BOOLEAN BeingDebugged;
      BOOLEAN BitField;
      HANDLE Mutant;
      PVOID ImageBaseAddress;
      PVOID Ldr;
      PVOID ProcessParameters;
      PVOID SubSystemData;
      PVOID ProcessHeap;
      PVOID FastPebLock;
      PVOID AtlThunkSListPtr;
      PVOID SparePtr2;
      ULONG EnvironmentUpdateCount;
      PVOID KernelCallbackTable;
      ULONG SystemReserved[1];
      ULONG SpareUlong;
      PVOID FreeList;
      ULONG TlsExpansionCounter;
      PVOID TlsBitmap;
      ULONG TlsBitmapBits[2];
      PVOID ReadOnlySharedMemoryBase;
      PVOID ReadOnlySharedMemoryHeap;
      PVOID *ReadOnlyStaticServerData;
      PVOID AnsiCodePageData;
      PVOID OemCodePageData;
      PVOID UnicodeCaseTableData;
      ULONG NumberOfProcessors;
      ULONG NtGlobalFlag;
      LARGE_INTEGER CriticalSectionTimeout;
      SIZE_T HeapSegmentReserve;
      SIZE_T HeapSegmentCommit;
      SIZE_T HeapDeCommitTotalFreeThreshold;
      SIZE_T HeapDeCommitFreeBlockThreshold;
      ULONG NumberOfHeaps;
      ULONG MaximumNumberOfHeaps;
      PVOID *ProcessHeaps;
      PVOID GdiSharedHandleTable;
      PVOID ProcessStarterHelper;
      ULONG GdiDCAttributeList;
      PVOID LoaderLock;
      ULONG OSMajorVersion;
      ULONG OSMinorVersion;
      USHORT OSBuildNumber;
      USHORT OSCSDVersion;
      ULONG OSPlatformId;
      } PEB, *PPEB; typedef struct _TEB {
      NT_TIB NtTib;
      PVOID EnvironmentPointer;
      struct {
      HANDLE UniqueProcess;
      HANDLE UniqueThread;
      } ClientId;
      PVOID ActiveRpcHandle;
      PVOID ThreadLocalStoragePointer;
      PEB *ProcessEnvironmentBlock;
      } TEB, *PTEB; DWORD PASCAL GetVersionPEB( void )
      {
      DWORD dwVersion = 0;
      TEB *lpTeb = NtCurrentTeb();
      if (lpTeb != NULL)
      {
      PEB *lpPeb = lpTeb->ProcessEnvironmentBlock;
      if (lpPeb != NULL)
      {
      DWORD dwMajVer = lpPeb->OSMajorVersion;
      DWORD dwMinVer = lpPeb->OSMinorVersion;
      dwVersion = (DWORD)MAKELONG(dwMinVer, dwMajVer);
      }
      }
      return dwVersion;
      }

      再次很高兴的告诉你,这个结果截止 Windows 10,也都能获取到正确的版本号。

    • RtlGetVersion

      使用时通常都是从 ntdll.dll 中动态加载,本人就不列出详细代码,仅以静态调用作为示例。

      NTSTATUS NTAPI RtlGetVersion(
      RTL_OSVERSIONINFOW *lpVersionInformation
      ); DWORD PASCAL GetVersionRtl( void )
      {
      DWORD dwVersion = 0;
      RTL_OSVERSIONINFOEXW osvi = { 0 };
      osvi.dwOSVersionInfoSize = sizeof(osvi);
      NTSTATUS status = RtlGetVersion((RTL_OSVERSIONINFOW *)&osvi);
      if (status == STATUS_SUCCESS)
      {
      DWORD dwMajVer = osvi.dwMajorVersion;
      DWORD dwMinVer = osvi.dwMinorVersion;
      dwVersion = (DWORD)MAKELONG(dwMinVer, dwMajVer);
      }
      return dwVersion;
      }

      最新测试发现,Windows 10 上可以获取到正确的版本号。但如果程序设置了兼容模式,仍会获取得到错误结果,即兼容系统的版本号。

    • RtlGetNtVersionNumbers

      同上,从 ntdll.dll 中加载。此接口系高手反编译所得,微软并未放出任何文档,请谨慎使用。Windows 2000 不支持,Windows XP 起支持。

      void NTAPI RtlGetNtVersionNumbers(
      DWORD *lpdwMajorVersion,
      DWORD *lpdwMinorVersion,
      DWORD *lpdwBuildNumber
      ); DWORD PASCAL GetVersionRtl( void )
      {
      DWORD dwMajorVersion = 0;
      DWORD dwMinorVersion = 0;
      RtlGetNtVersionNumbers(&dwMajorVersion, &dwMinorVersion, NULL);
      DWORD dwVersion = (DWORD)MAKELONG(dwMinorVersion, dwMajorVersion);
      return dwVersion;
      }

      在 Windows 10 上获取的结果是 10.0,目前看来是不会出问题的。

也谈如何获取真实正确的 Windows 系统版本号的更多相关文章

  1. Windows系统版本号判定那些事儿

    v\:* {behavior:url(#default#VML);} o\:* {behavior:url(#default#VML);} w\:* {behavior:url(#default#VM ...

  2. 如何正确入门Windows系统下驱动开发领域?

    [作者]猪头三个人网站 :http://www.x86asm.com/ [序言]很多人都对驱动开发有兴趣,但往往找不到正确的学习方式.当然这跟驱动开发的本土化资料少有关系.大多学的驱动开发资料都以英文 ...

  3. windows系统版本号

    windows操作系统版本号 操作系统 版本号 Windows8.1 6.3 Windows8 6.2 Windows7 6.1 Windows Server 2008 R2 6.1 Windows ...

  4. VC++ 获取Windows系统版本号、CPU名称

    转载:https://blog.csdn.net/sunflover454/article/details/51525179 转载:https://blog.csdn.net/magictong/ar ...

  5. 如何正确从windows系统(自己电脑)远程访问Linux系统(他人电脑)的mysql数据库(图文详解)

    这里,需要Linux系统开了root用户,我这给root用户密码为root.     同时,在mysql -uroot -proot执行进去之后 update user setHost='%' whe ...

  6. android通过代码获取华为手机的EMUI系统版本号

    因为app中用到华为推送,但是华为推送在不同版本上是存在不同问题的,需要单独来处理. 那么最基本的问题是要获取EMUI系统的版本号. 上网翻了很多博客帖子,基本上是在获取root权限下去读取/syst ...

  7. 小米抢购(简单版v0.1)-登录并验证抢购权限,以及获取真实抢购地址

    小米(简单版)-登录并验证抢购权限,以及获取真实抢购地址! 并不是复制到浏览器就行了的   还得传递所需要的参数 这里只是前部分  后面的自己发挥了 { "stime": 1389 ...

  8. Nginx 获取真实 IP 方案

    问题根源: 基于七层的负载均衡系统,获取IP的原理都是通过XRI和XFF进行处理,从中选出“正常情况下”的源头IP,然而这两个Header都是普通的HTTP头,任何代理程序都可以轻易修改伪造它们,使得 ...

  9. 关于httpservletrequest的获取真实的ip

    via 值为: 下面是一些DemoWTP/1.1 GDSZ-PS-GW010-WAP05.gd.chinamobile.com (Nokia WAP Gateway 4.0 CD3/ECD13_C/N ...

随机推荐

  1. 2019微软Power BI 每月功能更新系列——3月Power BI 新功能学习

    Power BI3月产品功能更新发布啦!本次新功能新增了热图和单选切片器:完善了新的DAX功能和对现有功能的改进(例如按钮和选择窗格):同时官方表示建模视图的全面改进也正在进行中~Woo~那么,本月更 ...

  2. hadoop day 7

    1.storm概述 应用于实时的流式计算,结合消息队列和数据库进行使用. Spouts:拓扑的消息源 Bolts:拓扑的处理逻辑单元,每个bolt可以在集群当中多实例的并发执行 tuple:消息元组, ...

  3. Vue小技巧-懒加载

    Vue懒加载包括图片懒加载与路由懒加载 1.图片懒加载: 首先安装 vue-lazyload包 然后导入并加载事先下载好的加载图片 import VueLazyLoad from 'vue-lazyl ...

  4. Error running second Activity: The activity must be exported or contain an intent-filter

    编译能成功,但是在虚拟机或真机上面调试时,弹出这个错误 后来查了一下,要在 AndroidManifest.xml 中,把每个窗口都加上一句 android:exported="true&q ...

  5. crunch--字典生成工具

    Crunch是一种创建密码字典工具,按照指定的规则生成密码字典,可以灵活的制定自己的字典文件.使用Crunch工具生成的密码可以输出到屏幕,保存到文件.或另一个程序.crunch程序在2004年及以前 ...

  6. JavaScript如何让1+1=11;{ } + { } = 2

    delete (          ) delete (          ) ;var  n = new Number( 1 ) console.log( n + 1 )   // 2  #请在括号 ...

  7. 一个简单的定向python爬虫爬取指定页面的jpg图片

    import requests as r import re resul=r.get("http://www.imooc.com/course/list") urlinfo=re. ...

  8. vpdn1

    在使用L2TP协议构建的VPDN典型组网中,包含LAC和LNS两部分. 1.LAC LAC表示L2TP访问集中器(L2TP Access Concentrator),是附属在交换网络上的具有PPP端系 ...

  9. 5. Web vulnerability scanners (网页漏洞扫描器 20个)

    5. Web vulnerability scanners (网页漏洞扫描器 20个) Burp Suite是攻击Web应用程序的集成平台. 它包含各种工具,它们之间有许多接口,旨在方便和加快攻击应用 ...

  10. 引擎设计跟踪(九.14.3) deferred shading 准备

    目前做的一些准备工作 1.depth prepass for forward shading. 做depth prepass的原因是为了完善渲染流程, 虽然架构上支持多个pass, 但实际上从来没有测 ...