当我们的应用程序或者驱动程序需要根据操作系统的版本来选择执行不同的代码的时候,我们应该知道这部分的内容。
这篇文章花费了我好一段时间,在附件里有三份代码分别代表了三种不同的方法。这些内容都是前人的成果,鄙人纯属老生常谈。
废话少说,我们进入主题。
用户模式下的方法:
方法1:
利用Win32 API可以很容易的确定所安装的操作系统的版本。我们可以使用GetVersionEx函数,在MSDN中可以很容易的找到关于它的详细解释,什么那位同学看不懂E文,那我只好解释一下啦。
GetVersionEx函数的原型为:
BOOL GetVersionEx(LPOSVERSIONINFO lpVersionInformation);
我们需要传递一个叫做OSVERSIONINFO的结构的指针,函数返回一个BOOL值,非零表示成功;零表示失败的,这时可以使用GetLastError得到错误码,使用Visual stdio附带的ERROR LOOK.EXE可以很轻易的得到详细的错误提示。
需要注意的一点就是在使用之前,我们必须填充OSVERSIONINFO的dwOSVersionInfoSize字段,操作系统根据这个字段判断我们使用的是OSVERSIONINFO结构还是OSVERSIONINFOEX结构。
哈哈,忘了说这个结构的具体内容了。
typedef struct _OSVERSIONINFO{
DWORD dwOSVersionInfoSize; //设置为OSVERSIONINFO结构的大小
DWORD dwMajorVersion; //操作系统的主版本号
DWORD dwMinorVersion; //操作系统的副版本号
DWORD dwBuildNumber; //操作系统Build(构建)编号
DWORD dwPlatformId; //操作系统的平台
TCHAR szCSDVersion[128]; //服务补丁号
} OSVERSIONINFO;
在这里我们只使用OSVERSIONINFO结构,因为GetVersionEx使用的是OSVERSIONINFO结构类型的指针,如果使用OSVERSIONINFOEX的话,会涉及到结构指针类型的转换问题,对于这个问题我没有很好的解决方案,如果你有的话请发扬一下共享精神,告诉我一下哈哈。
下面的这个表节选自http://www.codeproject.com/,我对这个表格添加了一些内容。通过这个表可以很清楚的查到大部分操作系统对应字段的值。
| dwPlatformID | dwMajorVersion | dwMinorVersion | dwBuildNumber |
95 | 1 | 4 | 0 | 950 |
95 SP1 | 1 | 4 | 0 | > 950 && <= 1080 |
95 OSR2 | 1 | 4 | < 10 | > 1080 |
98 | 1 | 4 | 10 | 1998 |
98 SP1 | 1 | 4 | 10 | >1998 && < 2183 |
98 SE | 1 | 4 | 10 | >= 2183 |
Me | 1 | 4 | 90 | 3000 |
| ||||
NT 3.51 | 2 | 3 | 51 | 1057 |
NT 4 | 2 | 4 | 0 | 1381 |
2000 | 2 | 5 | 0 | 2195 |
XP | 2 | 5 | 1 |
|
2003 | 2 | 5 | 2 |
|
| ||||
CE 1.0 | 3 | 1 | 0 |
|
CE 2.0 | 3 | 2 | 0 |
|
CE 2.1 | 3 | 2 | 1 |
|
CE 3.0 | 3 | 3 | 0 |
|
| ||||
Vista |
| 6 | 0 |
|
我所写的代码是这样子的:
.data
osVersion OSVERSIONINFO <?>
.code
;先填充dwOSVersionInfoSize为OSVERSIONINFO结构的大小
mov osVersion.dwOSVersionInfoSize, SIZEOF OSVERSIONINFO
invoke GetVersionEx, addr osVersion
.if eax == 0
invoke GetLastError ;得到错误号
.else
.if osVersion.dwMajorVersion == 4 ;NT4.0
.elseif osVersion.dwMajorVersion == 5
.if osVersion.dwMinorVersion == 0 ;Win 2000
.elseif osVersion.dwMinorVersion == 1 ;XP
.elseif osVersion.dwMinorVersion == 2 ;Win 2003
.endif
.elseif osVersion.dwMajorVersion == 6 &&\
osVersion.dwMinorVersion==0 ;Windows Vista
.endif
.endif
当然这只是关键代码,详细代码见附件os OSVERSIONINFO目录,其中os.asm为源文件,os.rc为资源文件,os.exe为生成的可执行文件。按照常规方式链接,请注意查看各个包含文件的所在目录的层次是不是和你的机器是一样的。
方法2:
查询注册表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\子键下的值,其中CSDVersion键包含关于服务补丁包的字符串;CurrentBuildNumber包含操作系统的构建编号;CurrentVersion一小数形式包含内核的主要版本和次要版本等等信息等等。如果有足够权限的话,可以在运行中输入”regedt32”或”regedit”查看相应的键值。
我们使用RegOpenKeyEx 打开注册表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\子键,然后使用RegQueryValueEx查询相应的键值,完成任务后使用RegCloseKey关闭子键句柄。
以下内容节选自罗云彬老师的教材:
1. 打开和关闭子键
注册表函数对注册表的操作是通过句柄来完成的,与文件操作一样,在对某个键下的子键或者键值项进行操作之前,需要先将这个键打开,然后使用键句柄来引用这个键,在操作完毕以后再将键句柄关闭。注册表的根键不需要打开,它们的句柄是固定不变的,要使用根键的时候只要把这些句柄直接拿来用就是了,Windows.inc中已经预定义了它们的数值:
HKEY_CLASSES_ROOT equ 80000000h
HKEY_CURRENT_USER equ 80000001h
HKEY_LOCAL_MACHINE equ 80000002h
HKEY_USERS equ 80000003h
HKEY_PERFORMANCE_DATA equ 80000004h
HKEY_CURRENT_CONFIG equ 80000005h
HKEY_DYN_DATA equ 80000006h
在程序中可以随时将这些助记符当做句柄来引用对应的根键。在程序结束的时候,不需要关闭这些根键句柄。
打开子键使用RegOpenKeyEx函数,在Win16中还存在一个RegOpenKey函数,虽然在Win32中这个函数仍然存在,但这仅是为了兼容的目的而设置的。API手册中推荐使用RegOpenKeyEx函数:
invoke RegOpenKeyEx,hKey,lpSubKey,dwOptions,samDesired,phkResult
函数的hKey参数指定父键句柄,lpSubKey指向一个字符串,用来表示要打开的子键名称,在系统中一个子键的全称是以“根键\第1层子键\第2层子键\第n层子键”类型的字符串表示的,中间用“\”隔开,字符串的最后以0字符结束,这和目录名的表示方法是很像的。
既然子键的全称是这样表示的,那么要打开一个子键的时候,下面的两种表示方法有什么不同呢?
(1)父键=HKEY_LOCAL_MACHINE,子键=Software\RegTest\MySubkey
(2)父键=HKEY_LOCAL_MACHINE\Software,子键=RegTest\MySubkey
答案是:这两种表示方法是完全相同的。在使用RegOpenKeyEx函数打开子键的时候,既可以将hKey参数设置为HKEY_LOCAL_MACHINE根键的句柄,并将lpSubKey参数指向“Software\RegTest\MySubkey”字符串;也可以将hKey参数设置为“HKEY_LOCAL_ MACHINE\Software”的句柄,将lpSubKey参数指向“RegTest\MySubkey”字符串,得到的结果是一样的。但是,使用第一种方法时,hKey参数可以直接使用助记符HKEY_LOCAL_ MACHINE来表示,因为根键的句柄是固定的,不需要打开;而使用第二种方法时,还需要先打开“HKEY_LOCAL_MACHINE\Software”键来获取它的句柄,所以具体使用哪种方法还要根据具体情况灵活选用。
函数的其他几个参数的含义如下。
● dwOptions参数——系统保留参数,必须指定为0。
● samDesired参数——子键的打开方式,根据使用子键的方式,可以设置为下列取值的组合,只有指定了打开的方式,才能在打开子键后进行相应的操作:
■ KEY_ALL_ACCESS——允许所有的存取。
■ KEY_CREATE_LINK——允许建立符号列表。
■ KEY_CREATE_SUB_KEY——允许建立下一层子键。
■ KEY_ENUMERATE_SUB_KEYS——允许枚举下一层子键。
■ KEY_EXECUTE——允许读操作。
■ KEY_QUERY_VALUE——允许查询键值数据。
■ KEY_READ—KEY_QUERY_VALUE,KEY_ENUMERATE_SUB_KEYS和KEY_ NOTIFY的组合。
■ KEY_SET_VALUE——允许修改或创建键值数据。
■ KEY_WRITE——KEY_SET_VALUE和KEY_CREATE_SUB_KEY的组合。
● phkResult参数——指向一个双字变量,函数在这里返回打开的子键句柄。
如果函数执行成功,返回值是ERROR_SUCCESS,并且函数在phkResult参数指向的变量中返回子键句柄。
当不再需要继续使用键句柄的时候,可以使用RegCloseKey函数将它关闭:
invoke RegCloseKey,hKey
如果句柄被成功关闭,函数返回ERROR_SUCCESS。
2. 查询键值数据
读取键值项中的数据或者查询键值项的属性使用RegQueryValueEx函数,用法如下:
invoke RegQueryValueEx,hKey,lpValueName,lpReserved,\
lpType,lpData,lpcbData
参数hKey和lpValueName用来指定要读取的键值项所处的子键句柄和键值项的名称, lpReserved参数是保留参数,必须使用0。lpData参数指向一个缓冲区,用来接收返回的键值数据。
函数的其余几个参数使用时必须注意的是它们都是指向双字变量的指针,这一点和使用RegSetValueEx函数时是不同的:
● lpType参数——函数在这个参数指向的双字变量中返回读取的键值类型,如果不需要返回键值项的类型,可以将这个参数设置为NULL。
● lpcbData参数——在调用的时候,程序必须在这个参数指向的双字变量中放置缓冲区的长度(并不是直接用lpcbData参数指出缓冲区长度)。当函数返回的时候,双字变量被函数改为返回到缓冲区中的数据的实际长度。
当函数执行成功的时候,函数的返回值是ERROR_SUCCESS。当程序指定的缓冲区长度不足以容纳返回的数据的时候,函数的返回值是ERROR_MORE_DATA,这时lpcbData参数指向的双字变量中返回需要的长度。
如果仅需要查询键值长度而不需要返回实际的数据,可以将lpData参数设置为NULL,但是lpcbData参数不能为NULL,这时函数会在lpcbData参数指向的双字变量中返回键值数据的长度。如果仅想查询键值项的类型,也可以同时将lpcbData和lpData参数设置为NULL。在这些情况下如果函数查询成功,返回值也是ERROR_SUCCESS。
如果要在一个键中查询键值数据的话,键的打开方式中必须包括KEY_QUERY_VALUE方式。
详细代码见os Reg目录下的源代码文件。
内核模式下的方法:
方法1:
使用ZwQueryKey ,ZwQueryKey,ZwClose等函数查询注册表,获取相应的键值。在Kmdkit驱动开发包的Kdmkit\examples\basic\RegistryWorks目录下有关于内核模式操作注册表的示例。
方法2:
使用PsGetVersion函数。它的原型是
BOOLEAN PsGetVersion(
PULONG MajorVersion OPTIONNAL,
PULONG MinorVersion OPTIONNAL,
PULONG BuildNumber OPTIONNAL,
PUNICODE_STRING CSDVersion OPTIONNAL,
);
在Win XP和Win 2003中还支持RtlGetVersion API,它也使用之前的OSVERSIONINFO结构的指针作为参数。
函数原型为:
NTSTATUS RtlGetVersion( PRTL_OSVERSIONINFOW lpVersionInfomation );
在这里我们只使用PsGetVersion。
.data
dwMajorVersion dd 0
dwMinVersion dd 0
dwBuildNumber dd 0
usz dw 256 dup(0)
usCSDVersion UNICODE_STRING {sizeof usz-2, sizeof usz,offset usz}
.code
invoke PsGetVersion,addr dwMajorVersion,addr dwMinVersion,\
addr dwBuildNumber,addr usCSDVersion
.if dwMajorVersion == 4 ;NT4.0
.elseif dwMajorVersion == 5
.if dwMinVersion == 0 ;Win 2000
.elseif dwMinVersion == 1 ;Win XP
.elseif dwMinVersion == 2 ;Win 2003
.endif
.elseif dwMajorVersion == 6 && dwMinVersion == 0 ;Vista
.endif
接下来可以处理usCSDVersion UNICODE_STRING得到服务补丁号。
目前常见的方法是这些,“如果你有什么好方法的话,请给我们来信吧,与大家一同分享”,^_^。