Windows 8.1中获得系统版本信息的方法

在Windows 8.1之前的系统版本上,我们一直可以使用 GetVersionEx.aspx) 这个函数来获取当前系统的MajorVersion和MinorVersion。但是当Windows系统来到8.1时代,这个API似乎就不好用了。如果在Windows 8.1上调用这个函数,我们更有可能获得的版本号是Windows 8的版本6.2,而不是我们想要的6.3。在MSDN上提供了这样一段说明:

With the release of Windows 8.1, the behavior of the GetVersionEx API has changed in the value it will return for the operating system version. The value returned by the GetVersionEx function now depends on how the application is manifested.

Applications not manifested for Windows 8.1 will return the Windows 8 OS version value (6.2). Once an application is manifested for a given operating system version, GetVersionEx will always return the version that the application is manifested for in future releases.

好了,既然微软都这么说了,也没办法,还好微软也给我们提供了另一套函数,叫做Version Helper functions.aspx) ,看起来是一套很不错的API,能帮助我们方便的判断系统版本。但是,仔细一看,这套函数需要头文件VersionHelpers.h,而这个文件是 Windows 8.1 software development kit 的一部分。对于使用低版本的VS还得装新版SDK,岂不麻烦。那么我们希望能找到一套更好的解决方法。

我第一个能想到了,当然就是万能的WMI,使用Win32_OperatingSystem class中的Version可以获得一个形如6.3.9600的字符串,我们就能通过解析这个获得系统的版本了。但是说实话,不到万不得已我不太喜欢用WMI这套API,总感觉为了一个小功能,牵扯了一堆东西。

那么第二套方案,是我觉得比较满意的,那就是调用VerSetConditionMask.aspx)和VerifyVersionInfo.aspx)来完成对系统版本的判断。具体做法如下:

1
2
3
4
5
6
7
8
9
10
11
12
BOOL IsWindows8Point1()
{
OSVERSIONINFOEX version_info = {0};
version_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
version_info.dwMajorVersion = 6;
version_info.dwMinorVersion = 3;
ULONGLONG mask = VerSetConditionMask(0, VER_MAJORVERSION, VER_EQUAL);
mask = VerSetConditionMask(mask, VER_MINORVERSION, VER_EQUAL);
return VerifyVersionInfo(&version;_info, VER_MAJORVERSION | VER_MINORVERSION, mask);
}

那么,我们就可以用这个函数来判断系统是否是Windows 8.1,如果不是,我们就可以用老办法,GetVersionEx来获得系统的版本号作判断了。当然了,大家看到这估计也能看出,我们自己也能用这两个函数实现一套所谓的Version Helper functions。举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
BOOL WINAPI IsWindowsVersionOrGreater(
WORD wMajorVersion,
WORD wMinorVersion,
WORD wServicePackMajor
)
{
OSVERSIONINFOEX version_info = { 0 };
version_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
version_info.dwMajorVersion = wMajorVersion;
version_info.dwMinorVersion = wMinorVersion;
version_info.wServicePackMajor = wServicePackMajor;
ULONGLONG mask = VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL);
mask = VerSetConditionMask(mask, VER_MINORVERSION, VER_GREATER_EQUAL);
mask = VerSetConditionMask(mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
return VerifyVersionInfo(&version;_info, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, mask);
}

怎么样,是不是达到了以假乱真的效果了。那么最后,咱再看看这么写出来的API的效果如何:

20140413233019

===============想睡觉的分割线====================

更新另外一个方法,在网上看到的,感觉也还行。只不过需要引入其他DLL,可以作为备选方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
ULONG MyGetVersion()
{
LPBYTE raw_data;
ULONG retval = 0;
if (NetWkstaGetInfo(NULL, 100, &raw;_data) == NERR_Success) {
WKSTA_INFO_100 * ws_info = reinterpret_cast(raw_data);
retval = (ws_info->wki100_ver_major << 16) | ws_info->wki100_ver_minor;
NetApiBufferFree(raw_data);
return retval;
}
return retval;
}

Tips

Global Logger打开开机启动ETW日志

在我看来XP确实应该寿终正寝了,因为确实在很多机制方面不如新的系统,比如ETW。而微软的XPERF也没有能在XP上直接安装的。当然,老版本的XPERF还能在XP上运行,只是监控能力有限,到了新版本的XPERF,在XP上就运行不了了。但是,XP却在中国还活的好好的,所以优化其性能必不可少。于是,在没有XPERF支持的情况下,要做到开启ETW日志,我们就需要微软提供的Global Logger机制,打开开机启动的Trace Session。

微软对打开Global Logger的方法做了详细的说明:http://msdn.microsoft.com/en-us/library/windows/hardware/ff546686(v=vs.85).aspx .aspx ),我这里没必要再赘述了,只有一个地方需要注意,要说明一下EnableKernelFlags这个变量,他是一个REG_BINARY类型,他的值是EVENT_TRACE_PROPERTIES的EnableFlags。但是EnableFlags是一个DWORD,而EnableKernelFlags是一个32字节的数组。如果你设置的时候,只是设置了一个DWORD,那么你会发现ETW 日志不会开启。

话说到这,EnableKernelFlags中前4字节的DWORD是EVENT_TRACE_PROPERTIES的组合,那么后面还有28字节是干什么的呢?实际上ETW能记录的标志还有很多,只是没有在EVENT_TRACE_PROPERTIES中说明,他们都被分到8个分组里面去了,这也是为什么有32个字节的Flags。具体怎么分组,还有哪些标志位,可以参考WRK的代码。

我这里写了个小工具可以用来设置Global Flags,不过如果你能找到XP上可以用的XPERF当然是最好的选择了!

20140405104746

下载:GlobalLog

Tips

不显示对话框格式化磁盘的方法

我们都知道格式化磁盘会弹出如下图所示的这样的一个对话框。

20140316210304

编写格式化磁盘的程序的时候,我们需要用到SHFormatDrive这个API,同样的,他也会弹出一个对话框。出现对话框当然是为了安全考虑,防止磁盘被用户不知情的情况下格式化,导致数据丢失。但是,某些情况下,确实希望静默的去格式化,而不去打扰用户,例如格式化Ramdisk。那么就需要找点一个办法要求不弹出对话框的格式化磁盘。既然Windows并没有提供这样的API,那我们只能深入分析下调用过程,找出可以使用的API。

怎么找的就不想说了,无非用ProcMon看一下堆栈就清楚明白了。在fmifs.dll中有一些导出的API可以帮助完成这一的任务,比如FormatEx,FormatEx2。

这里简单的描述下FormatEx的用法:
函数原型是

1
2
3
4
5
6
7
8
9
10
VOID WINAPI FormatEx(
LPCWSTR DriveRoot,
MEDIA_TYPE MediaType,
LPCWSTR FileSystemTypeName,
LPCWSTR Label,
BOOL QuickFormat,
ULONG ClusterSize,
FILE_SYSTEM_CALLBACK Callback
);

参数
DriveRoot —— 盘符,如”K:\”
MediaType —— 磁盘类型,如FixedMedia
FileSystemTypeName —— 要格式化的文件系统,如”NTFS”
Label —— 标签,随便写吧
QuickFormat —— 快速格式化
ClusterSize —— 簇大小
Callback —— 状态回调函数

回调函数原型

1
2
3
4
5
6
typedef BOOLEAN (__stdcall *FILE_SYSTEM_CALLBACK)(
ULONG Command,
ULONG Action,
PVOID pData
);

Command 表示Action和pData的意义,比如Command = 0表示pData是进度,Command = 11表示完成。还有其他的状态,例如错误等等,这些google一下就知道了。这个函数返回TRUE表示函数继续运行,FALSE表示停止格式化。

比如,下面是格式化K盘:

1
2
FormatEx(L"K:\", FixedMedia, L"NTFS", L"0CCh", TRUE, 4096, FormatExCallback);

Tips

一个有意思的warning —— C4930

C4930是微软的C++编译器提示的一个警告,在维基百科中,把造成这种警告的语句描述成最让人为难的解析的语句。那么这里我们看看到底是有多么为难,这个可以帮助我们进一步了解C++和编译器。

那么首先,我最开始发现这个问题是在类似这样的代码中碰到的。

1
2
3
std::ifstream s("d:\\xxx.txt");
std::string str(std::istream_iterator<char>(s), std::istream_iterator<char>());

如果编译这个,编译器会毫不留情地扔给你一个C4930警告,提示编译器不知道怎么做,所以跳过编译。如果你没看懂这警告,后果就是这句话根本不会编译进去,即使编译通过了,运行也会和设想的不同。

先说个简单的C4930的例子吧。

1
2
MyClass sample();

这句代码非常简单,也很容易明白。如果这么写,那么编译器就混乱,因为这句话可以是描述:
1.一个变量的定义,调用默认构造。
2.一个函数的声明。
所以编译器就傻了,他把这个认为是函数声明,所以不会做任何事情。
同样的事情发生在

1
2
std::string str(std::istream_iterator<char>(s), std::istream_iterator<char>());

这里,我们实际上就是给string的构造函数传入iterator来构造这个string。但是编译器可不是这么觉得,他认为这句话应该这样解析:一个返回string的函数,函数名为str,函数参数有两个并且类型相同,都是istream_iterator,不同的是一个有参数名s,一个省略了参数名!

编译器这么解析,也真没错,我们就只能通过修改代码来明确目的了。通常的做法是给第一个iterator外加上括
号:

1
2
std::string str((std::istream_iterator<char>(s)), std::istream_iterator<char>());

不过,我写这两行代码也就是为了偷懒读取一个文件的字符串,所以我可以干脆改成一行:

1
2
std::string str(std::istream_iterator<char>(std::ifstream("d:\\xxx.txt")), std::istream_iterator<char>());

这样就能顺利编译运行了。

最近在看新版的《The C++ Standard Library A Tutorial and Reference》里面也看到了这个东西,很有意思所
以拿出来说下。而且新版的书中,已经包含了C++11的解释,很有意思。

Tips

GUID TOOL —— 一个转换GUID格式的小工具

周末闲来无事,逆向点有趣的功能的时候遇到这样一个问题。有些16进制的数貌似就是GUID,但是需要转换为注册表形式,才方便在注册表里面搜索。所以就写了个小工具转换16进制,C语言格式以及注册表格式的GUID。

usage: guid.exe <<-r|-c|-h> guid_string> | <-g>
-r Format registry guid string.
-c Format C code guid string.
-x Format HEX guid string.
-g Create new guid.

20140223220404

20140223220314

下载:guid

Tips

SSD TRIM功能的一些记录

如今SSD越来越普及了,本来就想了解下关于SSD的一些情况,正好工作中有机会接触这一块的东西,很幸运。这里先记录一些已知知识,方便以后自己查阅。

说到SSD,第一个想到的就是读写速度快。那个就要归功于其存储原理,关于SSD存储原理的文章很多,我这里简单通俗的描述一下:

  1. SSD存储不同于机械硬盘,他没有机械硬盘所谓的扇区、柱面,磁头。查询逻辑地址上的数据没有机械上的寻址(没有马达)。SSD的存储介质是闪存。
  2. SSD存储数据被覆盖的时候不会马上覆盖原有数据,而是继续往之前没有写的闪存上写。因为闪存擦除次数非常有限,同一个地方小范围反复擦除会导致整个SSD的寿命缩短。
  3. SSD内部是有自己的GC(垃圾回收器),这个GC擦除不需要的数据以及调整需要的数据的位置,能帮助SSD进行擦除平衡。

而这里重点要说到的TRIM功能就是辅助GC更好工作的一环。从Windows 7开始,文件系统上已经集成了自动TRIM功能。但是早些时候的系统,例如Windows XP,就没有这样的功能了。所以手动TRIM工具就出现了。比如Intel的固态硬盘工具集,其中就包含了手动TRIM功能。也许,有人回想,SSD的高端用户团体怎么会还在使用XP呢?不幸的是还真有这样一群坚守XP的SSD用户。下面就记录一些关于执行TRIM要做的事情。

先决条件:

  1. 要执行TRIM,首先要确保自己的硬盘模式是AHCI的。
  2. 然后,系统需要时XP SP2 RC2以上(我想就算是XP的用户,现在也应该都是SP3了吧)。

上面的第一条非常重要,因为目前世面上的大部分盗版盘和所有正版安装盘,都是没有带AHCI驱动的。也就是说,如果你的BIOS上把硬盘模式调整为AHCI,那你回没有任何意外看到一个蓝屏。少部分盗版系统盘会说明自己是支持AHCI的,否则,就需要在IDE/ATA模式下,安装系统,然后去网上找到你的BIOS所指定AHCI驱动,安装后在把BIOS调整回AHCI。

最后,来看看执行TRIM的一种思路:

  1. 判断系统版本,硬盘的控制器,SSD是否支持TRIM。
  2. 创建多个1G的文件,直到占满所有磁盘空间。
  3. 获得这些文件的基于卷的簇。
  4. 将簇转换成基于卷的逻辑地址。
  5. 将基于卷的逻辑地址转换成基于硬盘的逻辑地址。
  6. 按照ATA文档,发送TRIM指令。
  7. 删除所有创建的1G文件。

上面有几个和ATA相关的简单介绍下。首先是SSD是否支持TRIM的问题,需要发送DEVICE IDENTIFY指令,获得硬盘数据,其中WORD 169表示是否支持TRIM,如果是1就是支持了。其次,发生TRIM指令,实际上发送的是DATA SET MANAGEMENT指令,其中Features register设置为1,即为TRIM指令了。至于如何在没有驱动的情况下发生这些指令(这也是我要求系统版本要是XP SP2 RC2以上的原因),可以利用DeviceIOControl函数发送IOCTL_ATA_PASS_THROUGH来完成。

这些记录已经很详细了,那几百行的代码就没必要贴出来了。

另外TRIM还有一个思路,实现起来可以麻烦一些,简单说说吧:

  1. 首先获得NTFS的Bitmap,获得空闲的簇。
  2. 转换空闲的簇到基于硬盘的逻辑地址。
  3. 发送TRIM指令

之所以说这个比较麻烦,是因为他需要保证在进行TRIM的时候,NTFS不发生写操作。方法就是LOCK VOLUME,但不幸的是,系统盘是没法LOCK的,所以这就不得不写一个Native Application,放在开机的时候运行,也就是Check Disk运行的时机。这个思路的优点是:TRIM全面,精确,速度快。

差不多就是这些了,希望这个记录对自己和他人都有所帮助。

Tips

一点有关Ntdll中提供的bitmap系列函数

我们都知道STL中提供了一个bitset类,但是在我真正操作有关文件系统的时候,发现这个类提供的功能并不能满足我的需求。幸运的是Ntdll中提供了一套操作bitmap的API。于是我抽了点时间把这几个API总结了一下,写成了一个类。这个类只是简单的对Ntdll的bitmap相关API做很浅的封装,没啥好说的。要说的是这套bitmap的API用起来确实很方便。
这些API包括:
RtlInitializeBitMap
RtlFindClearBits
RtlFindClearBitsAndSet
RtlFindClearRuns
RtlFindLastBackwardRunClear
RtlFindLongestRunClear
RtlFindNextForwardRunClear
RtlFindSetBits
RtlFindSetBitsAndClear
RtlSetAllBits
RtlSetBits
RtlClearAllBits
RtlClearBits
RtlNumberOfClearBits
RtlNumberOfSetBits
RtlAreBitsClear
RtlAreBitsSet
以上这些,在MSDN上都能查到API的详细文档介绍。唯一不方便的就是使用的时候需要GetProcAddress一下。所以我为了自己以后使用方便才写了一个类。

在项目(https://github.com/0cch/bitmap)中,bitmap_class是封装类,整个工程是一个使用这套API,获得文件系统的bitmap,并且查找空闲簇的一个例子。

2014-01-18_101801

Tips

总结和展望:质量比较大点,就不容易被风吹动

又到了一年一度的总结和展望。时间过得真快,2012年定计划的那会好想就发生在昨天。还能清晰的记得当时的计划,当然也是因为计划定的足够的简单。当然还是把个人的计划放在后面,先来总结下2013年的工作。这年的工作真是富有戏剧性,工作中做了一些网页前端的工作,这确实让我措手不及,不过既然组里有这样的需求,也只能硬着头皮上了,还好是这也并不算忙,不影响个人计划的进展。另一方面,公司拿我们部门和其他公司合并了,
换句话说,我们部门被裁了,只不过以一种漂亮的方式。就如同所有的接纳新员工的老大一样,新公司的leader会给你谈未来画大饼。只不过,对不起,我真的不看好这种抱团取暖的合并,所以,我选择离开。由于平时有一定的积累,所以找份靠谱的工作也并不是特别难的事情。能预感到新的工作会比较忙,不过我想,应该还是能hold住的。

当然,要说最放不下的,要数公司的健身房。掐指一算,已经坚持锻炼了16个月了!依稀记得12年是拼命跑25分钟能跑到4km多点,而13年已经能跑过5km了。一年的时间,让我在25分钟里能超越过去的自己两圈,“想想还有点小激动呢!”。另外肱二头肌和肱三头肌已经比较明显了,上臂粗了好多,穿短袖看起来MAN了好多,不过腹肌虽然能看出来,但还是不算特别明显。这里不得不提醒一下sysdbg的博主,“我督促你健身了16个月,你是不是应该请我吃金钱豹啊?!别客气,跪谢就免了,嗯!”。噢!说到这货,我还想到了一个事情,就是练字。现在字终于写的有点人样了,虽然写急了还是很丑,但是应该比以前好点了吧……大概……是这样。

我记得,13年的个人计划只有一个就是山寨sysinternals的工具。这次算是完成的比较好吧,工具集中大部分小工具都山寨了,只有个别界面特别复杂,功能特别强大的工具没有山寨。实在是没有动力写界面。另外,又一次把minikernel重写了一次,也围绕这个发布了不少的blog,但是这个minikernel还是缺少挺多的东西,比如很核心的多进程和多线程。一方面是突然遇到公司方面的事情打断了进度,另一个方面也是自己懒。

至于2014年的个人计划,还真不好说,还不知道新工作是个什么情况,但是我个人还是比较想写一个脚本语言以及完善这个minikernel的。另外继续健身,练字也是必须的。最近因为灌篮高手高清重置版播出了,也导致我又想没事抽出点时间打打球了。另外在13年,花了很多时间看dota2的视频,这个也是被sysdbg的博主吐槽了好久,最近也已经开始戒dota了,时间真是越来越不够用。
希望15年写总结的时候,能看到一个质量更大的自己吧,希望写的这些计划能完成75%以上。

最后,还是祝福家人,朋友,自己在新的一年里幸福安康,合家欢乐!!!

XPerfHelper —— XPerf的Windows命令行脚本生成工具

使用过XPerf的应该都知道,写一个XPerf的命令行是多么的麻烦,如果不太熟悉,需要反复的查看帮助里的参数。所以一般情况下,大家会把命令写到一个cmd或者bat的脚本中,这样就可以双击来使用XPerf,只需要第一次费点心思写脚本罢了。但是我还是觉得,即使是只用写一次脚本,也还是挺麻烦的,于是写了这个小工具,生成cmd脚本文件。妈妈再也不用担心我的XPerf命令写错了。

20131201221223

如上图所示,我们可以选择kernel flag和stackwalk,然后选择providers,点击OK,生成cmd文件即可。下面是一个生成的cmd的内容:

20131201222315

下载XPerfHelper

Debugging