Windows系统版本判定那些事儿

https://blog.csdn.net/magictong/article/details/40753519
前言

本文并不是讨论Windows操作系统的版本来历和特点,也不是讨论为什么没有Win9,而是从程序员角度讨论下Windows获取系统版本的方法和遇到的一些问题。在Win8和Win10出来之后,在获取系统版本时,可能很多人都碰到了类似的问题,为什么以前工作得很好的API,突然开始说谎了?

我们一般怎么获取系统版本

我想用的最多的可能就是这两个API了吧。

DWORD WINAPI GetVersion (VOID);

BOOL WINAPI GetVersionExW(__inout LPOSVERSIONINFOW lpVersionInformation);

其实GetVersion和GetVersionExW的实现是类似的,内部都是调用的NtCurrentPeb这个函数,还有一个GetVersionExA内部则是调用的GetVersionExW来实现。

GetVersionExW大概是这么实现的(这只是Windows2000的源码,后面的新系统,OSVERSIONINFOW这个结构多了几倍的成员)。

WINBASEAPI BOOL WINAPI GetVersionExW(

LPOSVERSIONINFOW lpVersionInformation)

{

PPEB Peb;

if (lpVersionInformation->dwOSVersionInfoSize != sizeof( *lpVersionInformation )) {

    SetLastError( ERROR_INSUFFICIENT_BUFFER );

   return FALSE;

    }

Peb = NtCurrentPeb();

lpVersionInformation->dwMajorVersion = Peb->OSMajorVersion;

lpVersionInformation->dwMinorVersion = Peb->OSMinorVersion;

lpVersionInformation->dwBuildNumber  =Peb->OSBuildNumber;

lpVersionInformation->dwPlatformId  = Peb->OSPlatformId;

wcscpy(lpVersionInformation->szCSDVersion,BaseCSDVersion );

return TRUE;

}

其中BaseCSDVersion是个全局变量,存放的是系统SP的字符串信息,在DLL初始化的时候就已经赋值了,由BaseDllInitialize来初始化。重点看下NtCurrentPeb这个函数,其实很显然,GetVersionExW就是从PEB里面去拷贝版本信息。NtCurrentPeb是一个调用比较频繁的函数,它返回当前进程的PEB结构地址,也就是通过fs寄存器去定位PEB,然后在GetVersionExW里面把PEB里面的系统版本信息拷贝给GetVersionExW的传出参数,也就是上面的OSMajorVersion等成员。

现在为什么不行了

但是从Windows8.1出来之后,GetVersionExW这个API被微软明文给废弃了,这个坑下得可够大的(参考[1])。也就是说从Windows8.1开始之后(包括Windows10),这个API常规情况下就是返回6.2了。

“In Windows 8.1, the GetVersion(Ex)APIs have been deprecated. That means that while you can still call the APIs,if your app does not specifically target Windows 8.1, you will getWindows 8 versioning (6.2.0.0).”

但是此时你去查看应用软件PEB的信息,发现PEB里面的系统版本还是正确的,在Windows10下面调试了一下,发现但是GetVersionExW确实返回的是6.2,但是PEB里面的版本则是6.4。也就是说微软更改了这个API的实现。

去调试微软对这个API做了什么改动意义不大,反正现在的结果就是这个API返回的值不对了,API也开始说谎了~不过在[1]里面,微软同时给出一个解决方案,嗯,一边跟你说,这个API已经被废弃了,一边又说还是可以用的,这不是坑爹是什么……解决方案是什么呢?修改manifest文件。加一段compatibility节点。

<?xml version="1.0"encoding="UTF-8" standalone="yes"?>

<assembly manifestVersion="1.0"xmlns="urn:schemas-microsoft-com:asm.v1"xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">

<description> my appexe </description>

<trustInfoxmlns="urn:schemas-microsoft-com:asm.v3">

   <security>

       <requestedPrivileges>

           <requestedExecutionLevel

               level="asInvoker"

               uiAccess="false"

           />    

       </requestedPrivileges>

   </security>

</trustInfo>

<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">

   <application>

       <!-- Windows 8.1 -->

       <supportedOSId="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>

       <!-- Windows Vista -->

       <supportedOSId="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>

       <!-- Windows 7 -->

       <supportedOSId="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>

       <!-- Windows 8 -->

       <supportedOSId="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>

   </application>

</compatibility>

</assembly>

主要就是compatibility部分了,如果你已经有manifest文件了,只需要添加compatibility部分即可。对了Windows10怎么办?貌似[1]里面还没有说啊,别急,用

<supportedOSId="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>

就好了,怎么知道的?请叫我雷锋!在Windows10下面测试一把的结果如图。

image

兼容模式的影响

还有一个可能的情况会造成GetVersionExW返回的系统版本和实际的系统版本不一样。这个与Windows8.1,Windows10没有什么关系。纯粹是为了兼容考虑,在设置兼容模式之后,GetVersionExW返回的是兼容的目标版本的系统版本。启动调试去查看应用程序的PEB是不是被修改过了,结果发现,并没有修改过PEB。那么问题来了,为什么GetVersionExW的值发生变化了呢?

image
image

直接调试GetVersionExW发现,在设置兼容模式之后,微软使用IATHook的方式,Hook了一堆的(嗯,不是1-2个,而是一堆)系统API,其中GetVersionExW就被AcLayers.dll里面的一个函数给Hook了,然后Hook函数里面返回了兼容系统版本号。

怎样判断兼容模式

一般来说,应用程序不需要判断当前是否处于兼容模式下运行,微软实现这个机制的目的本意就是想对应用程序透明。主要是很多“古老的”程序内部严格限定只能在某个具体的系统下运行,譬如限定在WindowsXP SP3下运行(因为当时微软的系统最高版本可能就是XP),这样当用户操作系统升级之后,譬如升级到了Windows7,这个时候问题来了!本来一般情况下微软的系统是可以前向兼容的,结果应用程序自己主动不兼容,发现不是XP,主动退出,导致用户用不了了,因此微软发明了一个兼容模式,高版本的系统可以模拟一个低版本的系统运行环境,这样就解决大量的类似问题。

在兼容模式下,当应用程序调用GetVersionExW等API时,返回的是兼容的目标系统的系统版本,当然这只是兼容模式技术解决的一个问题而已,但是是较重要的一个问题(兼容模式还解决了很多其它问题)。

image

一般的应用程序不需要关心这个兼容模式。但是某些特殊的应用程序却恰恰需要,应用程序可能会根据不同的系统版本做不同的事情,而一个可能性是用户误把应用程序设置为某个低版本操作系统兼容运行,导致整个程序运行反而异常。

举个例子,像系统补丁修复程序,一般来说漏洞补丁都是和系统版本一一对应,如果程序使用GetVersionExW来获取系统版本,那么程序运行在Windows7下面,因为兼容模式的影响,导致补丁修复程序推送了一大批WindowsXP下面的补丁,想想这个场景,也是有点尴尬的。

从大部分的使用场景上面来说,放弃使用GetVersionExW也许是一个更好的选择。通过其它方式拿到更精确的系统版本,不用考虑兼容模式的副作用,也不用担心Windows8(主要是指Window8.1和Window10)以上的系统获取到错误的系统版本。

那么怎么判断当成程序正在兼容模式运行呢?方法应该有很多,比较简单的方法,[4]里面介绍过一种,不过这种方法要注意,在Windows8.1之后,它可能给出错误的结果,要按照上面提到的办法,让GetVersionExW返回正确的值。

另外一种更好的方法是判断注册表里面的应用程序兼容模式记录列表,当把一个应用程序设置为兼容模式或者管理员权限启动之后,系统会在HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers下面记录相应的信息,如果想所有用户起效,则修改HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers即可。我们可以试试设置之后的效果。我在Windows7SP1下面随意设置了几个。

image

可以很清楚的看到两个程序被设置为兼容WIN7RTM运行和兼容WINXPSP3运行,如果你去掉这两个注册表值,则应用程序就不再以兼容模式运行,因此实际上可以检测这个位置判断哪些程序被设置为兼容模式运行,甚至可以通过删除这里的内容,去掉某些应用程序的兼容模式设置。同时可以发现的是微软用很容易识别的字符串来描述兼容的目标系统,更多的兼容描述字符串可以参考[3],另外要注意的是从Windows8开始,这些字符串前面多了一个波浪线和空格(~ ),譬如兼容WINXPSP3,在Windows10下面是~ WINXPSP3。

判断系统版本更好的办法

GetVersionExW既然被微软废弃了,再使用总觉得拔凉拔凉的,有什么更好的判断系统版本的方法吗?答案是肯定的!下面给出几种实践中用过的方法。

1、首先从原理上来说,GetVersionExW是读取的PEB里面的版本信息,其实我们自己也可以读取PEB嘛,只是麻烦一点。这个就不给例子了。有兴趣可以自己实现一下。

2、微软在[1]里面其实推荐过一批更好的API([7]),号称接口名更人性化,从名字上面看确实含义更清晰了,不过使用起来是否方便就仁者见仁智者见智了,随意罗列几个,不过这套API声明在<VersionHelpers.h>里面,比较新的SDK才有。

VERSIONHELPERAPI IsWindows7OrGreater()

VERSIONHELPERAPIIsWindows7SP1OrGreater()

VERSIONHELPERAPI IsWindows8OrGreater()

VERSIONHELPERAPI IsWindows8_1OrGreater()

VERSIONHELPERAPI IsWindowsServer()

3、使用VerifyVersionInfo来进行版本判断(参考[8]),这个API声明在winbase.h里面,从Windows2000系统就已经开始提供了,但是我们可能很少使用,说实话,使用起来不是特别方便。我们先看看是怎么使用的,它本质是进行版本比较。

BOOL WINAPI VerifyVersionInfo(

In LPOSVERSIONINFOEX lpVersionInfo,

In DWORD dwTypeMask,

In DWORDLONG dwlConditionMask

);

这个函数的原型里面第一个参数是熟悉的OSVERSIONINFOEX,但是这里是做为传入参数使用,第二个参数dwTypeMask用于指定要比较哪些项,可以比较主版本,次版本,Build号等等,可以使用位组合。第三个参数则是比较的方法,是>、=还是<,或者>=,<=等等,可以通过VER_SET_CONDITION来设置,可以进行各种组合来判断,还是比较灵活的。看两个例子吧。

BOOL IsWinVerGreaterThan(DWORDdwMajorVersion, DWORD dwMinorVersion)

{

OSVERSIONINFOEXW osvi = {0};

DWORDLONG dwlConditionMask = 0;

ZeroMemory(&osvi, sizeof(osvi));

osvi.dwOSVersionInfoSize= sizeof(osvi);

osvi.dwMajorVersion= dwMajorVersion;

osvi.dwMinorVersion= dwMinorVersion;

// 主版本号判断

VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_GREATER);

if (::VerifyVersionInfoW(&osvi, VER_MAJORVERSION, dwlConditionMask))

   return TRUE;

// 次版本号判断

VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_EQUAL);

VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_GREATER);

return ::VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, dwlConditionMask);

}

//-------------------------------------------------------------------------

// 函数 : IsWinVerEqualTo

// 功能 : 判断是否=某个特定的系统版本

// 返回值 : BOOL

// 参数 : DWORD dwMajorVersion

// 参数 : DWORD dwMinorVersion

// 附注 :

//-------------------------------------------------------------------------

BOOL IsWinVerEqualTo(DWORDdwMajorVersion, DWORD dwMinorVersion)

{

OSVERSIONINFOEXW osvi = {0};

DWORDLONG dwlConditionMask = 0;

// 1、初始化系统版本信息数据结构

ZeroMemory(&osvi, sizeof(osvi));

osvi.dwOSVersionInfoSize= sizeof(osvi);

osvi.dwMajorVersion= dwMajorVersion;

osvi.dwMinorVersion= dwMinorVersion;

// 2、初始化条件掩码

VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_EQUAL);

VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_EQUAL);

return ::VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION, dwlConditionMask);

}

封装一下使用就更方便了,譬如要判断当前是Window7,用IsWinVerEqualTo(6,1)即可。或者你不想暴露一些“恶心”的MagicNumber,可以再封装一个IsWindows7()嘛。

4、还有一个我个人比较喜欢的方法是使用一个未文档化的函数来获取系统版本,也就是RtlGetNtVersionNumbers,这个是NTDLL里面的一个未文档化函数。但是这个函数微软把它导出了,因此我们就有办法使用了。

使用方法:

//-------------------------------------------------------------------------

// 函数 : GetNtVersionNumbers

// 功能 : 调用RtlGetNtVersionNumbers获取系统版本信息

// 返回值 : BOOL

// 参数 : DWORD& dwMajorVer 主版本

// 参数 : DWORD& dwMinorVer 次版本

// 参数 : DWORD& dwBuildNumber build号

// 附注 :

//-------------------------------------------------------------------------

BOOL GetNtVersionNumbers(DWORD&dwMajorVer, DWORD& dwMinorVer,DWORD& dwBuildNumber)

{

BOOL bRet= FALSE;

HMODULE hModNtdll= NULL;

if (hModNtdll= ::LoadLibraryW(L"ntdll.dll"))

{

    typedef void (WINAPI *pfRTLGETNTVERSIONNUMBERS)(DWORD*,DWORD*, DWORD*);

    pfRTLGETNTVERSIONNUMBERS pfRtlGetNtVersionNumbers;

    pfRtlGetNtVersionNumbers = (pfRTLGETNTVERSIONNUMBERS)::GetProcAddress(hModNtdll, "RtlGetNtVersionNumbers");

    if (pfRtlGetNtVersionNumbers)

    {

       pfRtlGetNtVersionNumbers(&dwMajorVer, &dwMinorVer,&dwBuildNumber);

       dwBuildNumber&= 0x0ffff;

       bRet = TRUE;

    }

    ::FreeLibrary(hModNtdll);

    hModNtdll = NULL;

}

return bRet;

}

使用未文档化的函数要注意的一个点是,需要分析清楚函数的传入参数的类型,否则传错了类型,如果类型大小不一样,轻则函数出错,重则程序崩溃(尤其是传出参数)。我们可以看下RtlGetNtVersionNumbers这个函数是怎么实现的(调试用的ntdll.dll的版本是6.1.7601.18247,其它系统的也差不多的,仅仅是Hardcode的数字不一样),下面是它的实现伪码(IDA生成)。

int __stdcall RtlGetNtVersionNumbers(int a1, int a2, int a3)

{

int result; // eax@5

if ( a1 )

*(_DWORD *)a1 = 6;

if ( a2 )

*(_DWORD *)a2 = 1;

result = a3;

if ( a3 )

*(_DWORD *)a3 = 0xF0001DB1u;

return result;

}

我只能说微软,你干得漂亮!直接Hardcode处理,简单干净!

5、还有一种方法是直接去获取NTDLL这个系统关键文件(其它的文件也可行,但是实践证明NTDLL最好)的文件版本号,一般来说,该文件的版本基本上就是系统的版本。像[4]里面用到的判断兼容的方法就是通过对比GetVersionEx的返回值和关键系统文件的版本,来判断是否当前应用程序处理兼容模式下

注:建议不要使用RtlGetVersion来进行版本判断。Windows2003之前它的行为在兼容模式下和GetVersionExW不一致,Vista之后在兼容模式下它的行为和GetVersionExW一致。

效果展示

分别在WindowsXP,Windows7,Windows10下面测试了这些方法。注意左边的是常规模式运行,右边的是兼容模式运行。

image
image
image

参考文献

[1] Operating system version changes inWindows 8.1 and Windows Server 2012 R2 http://msdn.microsoft.com/en-us/library/windows/desktop/dn302074(v=vs.85).aspx

[2] GetVersionExhttp://msdn.microsoft.com/en-us/library/ms724451(VS.85).aspx

[3] Running an Application asAdministrator or in Compatibility Mode http://www.verboon.info/2011/03/running-an-application-as-administrator-or-in-compatibility-mode/

[4] http://blog.csdn.net/magictong/article/details/5829065怎样判定应用程序自身运行在“兼容模式”下?

[5] http://blogs.msdn.com/b/chuckw/archive/2013/09/10/manifest-madness.aspxManifestMadness

[6] OSVERSIONINFOEX structure http://msdn.microsoft.com/en-us/library/ms724833(v=vs.85).aspx

[7] Version Helper functions http://msdn.microsoft.com/en-us/library/windows/desktop/dn424972(v=vs.85).aspx

[8] VerifyVersionInfofunction http://msdn.microsoft.com/en-us/ms725492(VS.85).aspx

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生x阅读 15,967评论 3 119
  • 1. 阅读预习资料(必做) 任务说明:了解黄金思维圈和PORT模型 参考资料:《黄金思维圈》 参考资料:《PORT...
    易查理阅读 271评论 0 1
  • 时间过得真快,一眨眼的功夫兔儿已经整整七周岁,成为了一名一年级的小学生。 兔儿当年戒奶的情景依然记忆犹...
    乖乖兔宝贝阅读 365评论 2 0
  • 黑色的夜 闪着 黑色的眼 黑色的眼 消融 异己的一切 孤冷的夜之角 站着 未名的一颗星 那极致的浓重的夜 要悄悄地...
    一只偷瓜的猹阅读 306评论 0 2
  • 有时候 你会突然发现 那个在电影院会流泪的男孩子 你依然爱着他
    晚风羽阅读 97评论 0 0