更新:Windbg扩展0cchext.dll

Commands for 0cchext.dll:
!autocmd - Execute the debugger commands.(The config file is
autocmd.ini)
!dpx - Display the contents of memory in the given range.
!dtx - Displays information about structures. (The config file is
struct.ini)
!favcmd - Display the favorite debugger commands.(The config file is
favcmd.ini)
!grep - Search plain-text data sets for lines matching a regular
expression.
!help - Displays information on available extension commands
!hwnd - Show window information by handle.
!init_script_env - Initialize script environment.
!setvprot - Set the protection on a region of committed pages in the
virtual address space of the debuggee process.
!url - Open a URL in a default browser.
!version - Displays the version information for 0cchext.dll
!help will give more information for a particular command

下载:0cchext

Debugging

系统进程创建管理员进程的方法

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126

namespace ProcessHelper {

typedef BOOL (__stdcall *CREATEENVIRONMENTBLOCK)(LPVOID *lpEnvironment,
HANDLE hToken,
BOOL bInherit);

BOOL CreateProcessAsExplorer(LPCTSTR AppName, LPTSTR CommandLine, PPROCESS_INFORMATION pi)
{
ULONG ExplorerID = 0;
HANDLE ExplorerHandle;
HANDLE Snapshot;
ULONG CreationFlags = 0;
CREATEENVIRONMENTBLOCK CreateEnvironmentBlock;
HANDLE ExplorerToken;
HANDLE NewToken = 0;
LPVOID Environment = NULL;
ULONG ReturnLength = 0;
TOKEN_LINKED_TOKEN LinkedToken = {0};
STARTUPINFO si = {0};
PROCESSENTRY32 pe;
BOOL Ret;
HMODULE UserenvModule;
LUID Luid = {0};


Snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (Snapshot == INVALID_HANDLE_VALUE) {
return FALSE;
}

pe.dwSize = sizeof(pe);
if (Process32FirstW(Snapshot, &pe;)) {

for(;;) {

if (_tcsicmp(pe.szExeFile, L"explorer.exe") == 0) {
ProcessIdToSessionId(pe.th32ProcessID, &Luid.LowPart;);
ExplorerID = pe.th32ProcessID;
break;
}
if (!Process32Next(Snapshot, &pe;)) {
break;
}
}

}

CloseHandle(Snapshot);
if (ExplorerID == 0) {
return FALSE;
}

ExplorerHandle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, ExplorerID);
if (ExplorerHandle == NULL) {
return FALSE;
}

if (!OpenProcessToken(ExplorerHandle, TOKEN_ALL_ACCESS_P, &ExplorerToken;)) {
CloseHandle(ExplorerHandle);
return FALSE;
}

CloseHandle(ExplorerHandle);

if (GetTokenInformation(ExplorerToken,
TokenLinkedToken,
&LinkedToken;,
sizeof(TOKEN_LINKED_TOKEN),
&ReturnLength;)) {

NewToken = LinkedToken.LinkedToken;
}
else {

LookupPrivilegeValueW(0, L"SeDebugPrivilege", &Luid;);
DuplicateTokenEx(ExplorerToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &NewToken;);
}

CloseHandle(ExplorerToken);

UserenvModule = LoadLibrary(TEXT("Userenv.dll"));
if (UserenvModule == NULL) {
return FALSE;
}

CreateEnvironmentBlock = (CREATEENVIRONMENTBLOCK)GetProcAddress(UserenvModule, "CreateEnvironmentBlock");
if (CreateEnvironmentBlock == NULL) {
FreeLibrary(UserenvModule);
return FALSE;
}


if (CreateEnvironmentBlock(&Environment;, NewToken, TRUE)) {
CreationFlags = CREATE_UNICODE_ENVIRONMENT;
}

si.cb = sizeof(si);
Ret = CreateProcessAsUser(
NewToken,
AppName,
CommandLine,
NULL,
NULL,
FALSE,
CreationFlags,
Environment,
NULL,
&si;,
pi);

CloseHandle(NewToken);

if (Environment != NULL) {
DestroyEnvironmentBlock(Environment);
}

if (!Ret) {

return FALSE;
}

return TRUE;
}
}

Tips

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$$ 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命令挂起线程。

Debugging

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