0CCh Blog

一个查找指定栈回溯符号并执行命令的Windbg脚本

这是自己没事在家调试程序的一个小需求。

有时候比如IE这样的程序,线程实在是非常的多,我想操作某个特殊线程就比较麻烦,需要先找到线程然后再实行命令,为了偷懒就写了这个脚本。

$$ If a frame of a thread has the target symbol, we execute the command.
$$ Author: nighxie
$$ Blog: 0cch.net

.if (${/d:$arg1} & ${/d:$arg2} & ${/d:$arg3}) {
.for (r @$t0 = 0; @$t0 < 0n${$arg1}; r @$t0 = @$t0 + 1) {
r @$t1 = 0;
~[@$t0]s;
!for_each_frame .if($spat("${@#SymbolName}", "${$arg2}")) {r @$t1 = 1}
.if (@$t1 == 1) {
${$arg3};
}
}
}
.else {
.echo "Usage $$>a<${$arg0} thread_count pattern cmd";
.echo "e.g. $$>a<${$arg0} 5 ntdll* ~n";
}

就如同上面的例子指定线程数量,要匹配的符号,最后就是要执行的命令。
$$>a<${$arg0} 5 ntdll* n就表示在前5个线程里寻找栈回溯有关ntdll的线程,然后执行n命令挂起线程。

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

在Windows 8.1之前的系统版本上,我们一直可以使用 GetVersionEx 这个函数来获取当前系统的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 ,看起来是一套很不错的API,能帮助我们方便的判断系统版本。但是,仔细一看,这套函数需要头文件VersionHelpers.h,而这个文件是 Windows 8.1 software development kit 的一部分。对于使用低版本的VS还得装新版SDK,岂不麻烦。那么我们希望能找到一套更好的解决方法。

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

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

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。举个例子:

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,可以作为备选方案。

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;
}

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 ,我这里没必要再赘述了,只有一个地方需要注意,要说明一下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

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

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

20140316210304

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

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

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

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 —— 状态回调函数

回调函数原型

typedef BOOLEAN (__stdcall *FILE_SYSTEM_CALLBACK)(
ULONG Command,
ULONG Action,
PVOID pData
);

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

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

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

一个有意思的warning —— C4930

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

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

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

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

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

MyClass sample();

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

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

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

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

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

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

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的解释,很有意思。

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

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全面,精确,速度快。

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

一点有关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

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

又到了一年一度的总结和展望。时间过得真快,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%以上。

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

ShowIEDevTool —— 打开IE控件的开发人员工具

最近在网上看到一大牛blog(http://blog.titilima.com/launch-f12-tools.html),讨论的是在内嵌的IE窗口中,打开开发人员工具。文章写的很好,而且还提供了开源的工具,实在是业界良心。于是我臭不要脸的A了部分关键代码,弄了个命令行版工具。有兴趣的强烈推荐阅读上面的blog,以及其提供的开源工具的代码。

下载ShowIEDevTool