快速抛出失败的方法 INT 29H

从Windows8开始,Windows设计了一个新的中断,INT 29H,用来快速的抛出失败。在sdk中,他被声明为 __fastfail:

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
#define FAST_FAIL_LEGACY_GS_VIOLATION 0
#define FAST_FAIL_VTGUARD_CHECK_FAILURE 1
#define FAST_FAIL_STACK_COOKIE_CHECK_FAILURE 2
#define FAST_FAIL_CORRUPT_LIST_ENTRY 3
#define FAST_FAIL_INCORRECT_STACK 4
#define FAST_FAIL_INVALID_ARG 5
#define FAST_FAIL_GS_COOKIE_INIT 6
#define FAST_FAIL_FATAL_APP_EXIT 7
#define FAST_FAIL_RANGE_CHECK_FAILURE 8
#define FAST_FAIL_UNSAFE_REGISTRY_ACCESS 9
#define FAST_FAIL_GUARD_ICALL_CHECK_FAILURE 10
#define FAST_FAIL_GUARD_WRITE_CHECK_FAILURE 11
#define FAST_FAIL_INVALID_FIBER_SWITCH 12
#define FAST_FAIL_INVALID_SET_OF_CONTEXT 13
#define FAST_FAIL_INVALID_REFERENCE_COUNT 14
#define FAST_FAIL_INVALID_JUMP_BUFFER 18
#define FAST_FAIL_MRDATA_MODIFIED 19
#define FAST_FAIL_CERTIFICATION_FAILURE 20
#define FAST_FAIL_INVALID_EXCEPTION_CHAIN 21
#define FAST_FAIL_CRYPTO_LIBRARY 22
#define FAST_FAIL_INVALID_CALL_IN_DLL_CALLOUT 23
#define FAST_FAIL_INVALID_IMAGE_BASE 24
#define FAST_FAIL_DLOAD_PROTECTION_FAILURE 25
#define FAST_FAIL_UNSAFE_EXTENSION_CALL 26
#define FAST_FAIL_DEPRECATED_SERVICE_INVOKED 27
#define FAST_FAIL_INVALID_BUFFER_ACCESS 28
#define FAST_FAIL_INVALID_BALANCED_TREE 29
#define FAST_FAIL_INVALID_NEXT_THREAD 30
#define FAST_FAIL_GUARD_ICALL_CHECK_SUPPRESSED 31 // Telemetry, nonfatal
#define FAST_FAIL_APCS_DISABLED 32
#define FAST_FAIL_INVALID_IDLE_STATE 33
#define FAST_FAIL_MRDATA_PROTECTION_FAILURE 34
#define FAST_FAIL_UNEXPECTED_HEAP_EXCEPTION 35
#define FAST_FAIL_INVALID_FAST_FAIL_CODE 0xFFFFFFFF
#if _MSC_VER >= 1610
DECLSPEC_NORETURN
VOID
__fastfail(
_In_ unsigned int Code
);
#pragma intrinsic(__fastfail)
#endif
/*
// 汇编代码为
mov ecx, code
int 29h
*/

在中断代码执行后,操作系统会根据执行代码的环境来做出不同的处理。
如果fastfail发生在Ring0中,操作系统会抛出一个KERNEL_SECURITY_CHECK_FAILURE (0x139)的蓝屏。如果fastfail发生在Ring3,系统会抛出一个第二次机会的不可继续执行的异常,异常代码为0xC0000409,然后走进我们熟悉的Windows Error Reporting(WER)流程。另外,无论__fastfail发生在R0或者R3,如果有调试器正在调试系统或进程,都将得到一次中断到调试器的机会,这让我们能够看清楚具体发生了什么事情。但是正如我上面所说,这个是一个不可继续执行的异常,所以我们不能在调试器里处理了异常后让程序继续向前跑,当然也不能用try和except去捕获异常。

我觉得__fastfail是个非常不错的设计,它让程序可以快速的进入内核异常处理流程,不需要执行额外的用户层的代码,也不需要额外的内存空间,提高了不可恢复的异常处理的性能,更重要的是,简单快速不依赖内存的执行方式也保证了系统的安全。所以在系统的安全检查失败处理中,大量使用了这个方式,减少被攻击的可能性。

最后,如果INT 29H发生在Windows8以下的系统上,内核里会抛出一个常规的UNEXPECTED_KERNEL_MODE_TRAP的蓝屏,而用户层程序会抛出一个ACCESS VIOLATION的异常。

Debugging

windbg的lua脚本扩展luadbg

2012年的时候,我在blog上写到过开发了一个windbg的lua扩展dbglua,当时觉得windbg的原生脚本语法太奇怪了,而且太不容易使用。现在来看,依旧如此,只不过我已经很熟悉这个原生脚本了。而这个lua扩展反倒是没什么用,因为用起来也不太方便,比如访问结构体。

最近无意之中看了一眼pykd,他用重载.操作符的方式访问符号和结构体深深的吸引了我,感觉非常有趣。而python本身依赖比较多,这也促使我拿起之前的代码看了看,并且决定在github上重新建立这个项目叫做luadbg,这次我决定长期维护这个项目,想到新的功能就往里面写,就像我一直维护的0cchext一样。luadbg除了兼容了老dbglua的函数以外,还添加了几个我觉得很方便的类,主要是用重载.操作符的方式来访问模块和结构体的数据,效果如下图所示:

20161116113129

当然,也可以用!luacmd命令进入input模式,从而一条一条的输入语句来测试正确性。

Debugging

编译时自动增加build number

最近和朋友讨论版本号常用的几种规范,前三位<主版本>.<子版本>.<修正版本>基本上一致,不需要详说。主要区别产生在最后一位,有的是build number,有的是时间日期,还有的是git或者svn的revision。我习惯用build number,每次编译都会增加版本号最后一位的数字。但是手动去修改明显不科学也不可靠,所以给和我有一样习惯的朋友分享一个我早年写的python脚本,无论是自己的工具还是公司的产品我一直都在用这个。

1
用法就是在VS的工程属性Build Event -> Pre Build Event里设置x:\incbuildnum.py $(ProjectDir)$(ProjectName).rc。
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
import re
import os
import sys
import shutil
if os.path.isfile(sys.argv[1] + ".bak"):
os.remove(sys.argv[1] + ".bak")
shutil.copy(sys.argv[1], sys.argv[1] + ".bak")
with open(sys.argv[1], 'r+') as content_file:
content = content_file.read()
m = re.search("VALUE \"FileVersion\", \"(([\\d]+).[ ]*)*([\\d]+)\"", content)
new_ver = str(int(m.group(3)) + 1)
content = re.sub("(VALUE \"FileVersion\", \"([\\d]+.[ ]*)*)[\\d]+\"", "\\g<1>" + new_ver + "\"", content)
m = re.search("FILEVERSION (([\\d]+).[ ]*)*([\\d]+)", content)
new_ver = str(int(m.group(3)) + 1)
content = re.sub("(FILEVERSION ([\\d]+.[ ]*)*)([\\d]+)", "\\g<1>" + new_ver, content)
m = re.search("VALUE \"ProductVersion\", \"(([\\d]+).[ ]*)*([\\d]+)\"", content)
new_ver = str(int(m.group(3)) + 1)
content = re.sub("(VALUE \"ProductVersion\", \"([\\d]+.[ ]*)*)[\\d]+\"", "\\g<1>" + new_ver + "\"", content)
m = re.search("PRODUCTVERSION (([\\d]+).[ ]*)*([\\d]+)", content)
new_ver = str(int(m.group(3)) + 1)
content = re.sub("(PRODUCTVERSION ([\\d]+.[ ]*)*)([\\d]+)", "\\g<1>" + new_ver, content)
content_file.seek(0)
content_file.write(content)
content_file.truncate()
content_file.close()

Tips

验证文件签名

Sysinternal(http://forum.sysinternals.com/howto-verify-the-digital-signature-of-a-file_topic19247.html)上有关于验证签名的代码,不过代码有点问题,他只能验证PE签名,无法验证文件签名,所以我这里稍作了点修改,记录一下

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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
#define ENCODING (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING)
BOOL CheckFileTrust(LPCTSTR filename, CString &signer_file)
{
HCATADMIN cat_admin_handle = NULL;
if (!CryptCATAdminAcquireContext(&cat_admin_handle, NULL, 0))
{
return FALSE;
}
HANDLE hFile = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL);
if (INVALID_HANDLE_VALUE == hFile)
{
CryptCATAdminReleaseContext(cat_admin_handle, 0);
return FALSE;
}
DWORD hash_count = 100;
BYTE hash_data[100];
CryptCATAdminCalcHashFromFileHandle(hFile, &hash_count, hash_data, 0);
CloseHandle(hFile);
LPWSTR member_tag = new WCHAR[hash_count * 2 + 1];
for (DWORD dw = 0; dw < hash_count; ++dw)
{
wsprintfW(&member_tag[dw * 2], L"%02X", hash_data[dw]);
}
WINTRUST_DATA wd = { 0 };
WINTRUST_FILE_INFO wfi = { 0 };
WINTRUST_CATALOG_INFO wci = { 0 };
CATALOG_INFO ci = { 0 };
HCATINFO cat_admin_info = CryptCATAdminEnumCatalogFromHash(cat_admin_handle,
hash_data, hash_count, 0, NULL);
if (NULL == cat_admin_info)
{
wfi.cbStruct = sizeof(WINTRUST_FILE_INFO);
wfi.pcwszFilePath = filename;
wfi.hFile = NULL;
wfi.pgKnownSubject = NULL;
wd.cbStruct = sizeof(WINTRUST_DATA);
wd.dwUnionChoice = WTD_CHOICE_FILE;
wd.pFile = &wfi;
wd.dwUIChoice = WTD_UI_NONE;
wd.fdwRevocationChecks = WTD_REVOKE_NONE;
wd.dwStateAction = WTD_STATEACTION_IGNORE;
wd.dwProvFlags = WTD_SAFER_FLAG;
wd.hWVTStateData = NULL;
wd.pwszURLReference = NULL;
signer_file = filename;
}
else
{
CryptCATCatalogInfoFromContext(cat_admin_info, &ci, 0);
wci.cbStruct = sizeof(WINTRUST_CATALOG_INFO);
wci.pcwszCatalogFilePath = ci.wszCatalogFile;
wci.pcwszMemberFilePath = filename;
wci.pcwszMemberTag = member_tag;
wci.pbCalculatedFileHash = hash_data;
wci.cbCalculatedFileHash = hash_count;
wd.cbStruct = sizeof(WINTRUST_DATA);
wd.dwUnionChoice = WTD_CHOICE_CATALOG;
wd.pCatalog = &wci;
wd.dwUIChoice = WTD_UI_NONE;
wd.fdwRevocationChecks = WTD_REVOKE_WHOLECHAIN;
wd.dwProvFlags = 0;
wd.hWVTStateData = NULL;
wd.pwszURLReference = NULL;
signer_file = ci.wszCatalogFile;
}
GUID action = WINTRUST_ACTION_GENERIC_VERIFY_V2;
HRESULT hr = WinVerifyTrust(NULL, &action, &wd);
BOOL retval = SUCCEEDED(hr);
if (NULL != cat_admin_info) {
CryptCATAdminReleaseCatalogContext(cat_admin_handle, cat_admin_info, 0);
}
CryptCATAdminReleaseContext(cat_admin_handle, 0);
delete[] member_tag;
return retval;
}
BOOL GetCertificateInfo(PCCERT_CONTEXT cert_context, CString &signer_name)
{
LPTSTR name = NULL;
DWORD data;
if (!(data = CertGetNameString(cert_context,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
0,
NULL,
NULL,
0))) {
return FALSE;
}
// Allocate memory for subject name.
name = (LPTSTR)LocalAlloc(LPTR, data * sizeof(TCHAR));
if (!name) {
return FALSE;
}
// Get subject name.
if (!(CertGetNameString(cert_context,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
0,
NULL,
name,
data))) {
LocalFree(name);
return FALSE;
}
signer_name = name;
LocalFree(name);
return TRUE;
}
BOOL GetFileSigner(LPCTSTR szFileName, CString &signer_name)
{
HCERTSTORE store_handle = NULL;
HCRYPTMSG msg_handle = NULL;
PCCERT_CONTEXT cert_context = NULL;
BOOL retval = FALSE;
DWORD encoding, content_type, format_type;
PCMSG_SIGNER_INFO signer_info = NULL;
DWORD signer_info_size;
CERT_INFO cert_info;
do
{
// Get message handle and store handle from the signed file.
retval = CryptQueryObject(CERT_QUERY_OBJECT_FILE,
szFileName,
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
CERT_QUERY_FORMAT_FLAG_BINARY,
0,
&encoding,
&content_type,
&format_type,
&store_handle,
&msg_handle,
NULL);
if (!retval) {
break;
}
// Get signer information size.
retval = CryptMsgGetParam(msg_handle,
CMSG_SIGNER_INFO_PARAM,
0,
NULL,
&signer_info_size);
if (!retval) {
break;
}
// Allocate memory for signer information.
signer_info = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, signer_info_size);
if (!signer_info) {
break;
}
// Get Signer Information.
retval = CryptMsgGetParam(msg_handle,
CMSG_SIGNER_INFO_PARAM,
0,
(PVOID)signer_info,
&signer_info_size);
if (!retval) {
break;
}
// Search for the signer certificate in the temporary
// certificate store.
cert_info.Issuer = signer_info->Issuer;
cert_info.SerialNumber = signer_info->SerialNumber;
cert_context = CertFindCertificateInStore(store_handle,
ENCODING,
0,
CERT_FIND_SUBJECT_CERT,
(PVOID)&cert_info,
NULL);
if (!cert_context) {
break;
}
retval = GetCertificateInfo(cert_context, signer_name);
} while (0);
if (signer_info != NULL) {
LocalFree(signer_info);
}
if (cert_context != NULL) {
CertFreeCertificateContext(cert_context);
}
if (store_handle != NULL) {
CertCloseStore(store_handle, 0);
}
if (msg_handle != NULL) {
CryptMsgClose(msg_handle);
}
return retval;
}

Tips

在NTFS元文件目录里创建文件

说到Rootkit就不能提到他的文件隐藏,Rootkit隐藏文件的方式千奇百怪,这里说其中一个通过NTFS元文件目录无法被普通程序显示的特性隐藏文件的方法。

我们都知道NTFS是有元文件的,比如$MFT(NTFS主文件表),这种文件是我们看不到的,但是系统能访问。同样还有一种元文件目录,这个目录也是看不到的,无论你是否打开了显示系统文件,隐藏文件的选项。那么如果我们把要隐藏的文件放在这种目录下,那么就达到了隐藏的效果。

举个例子 $Extend\$RmMetadata 这个目录。我们可以通过Winhex解析NTFS来读取这个目录的情况,而普通程序不行。这里我们通过这样的代码来创建文件。

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
#define GPA(x) *(FARPROC *)&My##x = GetProcAddress(GetModuleHandle(L"ntdll.dll"), #x)
GPA(NtCreateFile);
GPA(RtlInitUnicodeString);
IO_STATUS_BLOCK iob;
HANDLE h;
UNICODE_STRING uni_str;
MyRtlInitUnicodeString(&uni_str, L"\\??\\Global\\D:\\$Extend\\$RmMetadata\\$0cch");
OBJECT_ATTRIBUTES oa;
InitializeObjectAttributes(&oa, &uni_str, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL)
LONG l = MyNtCreateFile(&h,
FILE_APPEND_DATA | SYNCHRONIZE,
&oa,
&iob,
0,
FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM,
0,
FILE_SUPERSEDE,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE,
NULL,
0);
char buffer[] = "0123456789";
WriteFile(h, buffer, strlen(buffer), (ULONG *)&l, NULL);
CloseHandle(h);

值得注意的是我们必须用System用户权限去运行这个程序,才能创建文件到元文件目录,这里要用到psexec:

psexec -s C:\0cch\Test.exe

然后我们看看效果

20160824115523

Tips

关于Windows Timer精确度

Windows Timer相比大家都用过,WM_TIMER, WM_SYSTIMER, Waitable Timer, Multimedia Timer, Timer Queue Timer,这么多种Timer,给我们变成提供了很大的方便,有窗口无窗口都能自如选择。所以尽量也不要自己再造轮子,用什么Sleep来写Timer。这种“自定义”的Timer肯定是没有由系统内核DPC触发的Timer效率高的。

OK,回到正题,关于Timer的精确度。首先看看SysInternal工具集的clockres的显示:

20160725102628

从图中可以看出,我这个系统的最大精确度15.6毫秒,最小是0.5毫秒,当前是15.6毫秒。默认情况下,Windows会用最大精确度,因为这样可以减少CPU的消耗,而且高精度的定时器,绝大多数程序都不会用到。基于15.6毫秒这个精度,那么我们设置Timer间隔为15.6毫秒以下都是没有意义的,这里再提一下,Sleep函数在内核也是用的定时器,也就是说这个精确度下,Sleep(10)也是没有意义的,间隔会达到15-16毫秒。

当然,我们有的时候也是需要高精度的定时器的,这个时候我们需要设置时间精度。timeBeginPeriod这个函数就可以完成这个任务,这个函数调用了ntdll的NtSetTimerResolution函数,我们也可以直接调用这个ntdll函数,只不过我们需要动态获得这个函数的地址罢了。值得注意的是,并不是你想设置什么精确度都可以,Windows内部实际上维护了一份可以设置的精度列表,他会选择一个和你设置相近的的精度设置上去,这个列表保存在Hal里面。

好了,再说下Windows时钟,Windows时钟更新时间总是用的最大精度,在我个系统上也就是每次更新时间都是间隔15.6毫秒。也就是说如果用GetTickCount来统计性能问题,最大精度也就是15-16毫秒。举个例子,一段代码运行时间不足15.6毫秒,要么统计结果是0,要么是15-16毫秒,时间精度不会影响Windows时钟更新。

最后说下Windows高精度时钟查询的实现,在2000和XP时代,系统用TSC来演算时间,但是那个时候,多核并不支持TSC同步,这回带来一些问题。Vista系统采用了High Precision Event Timer (HPET)或者ACPI Power Management Timer (PM timer),但是这种Timer的延时比较高,当然,这个延时是百纳秒级别的,可以说基本上不会对普通程序有什么影响。之后的系统就使用了固定频率的TSC,这样在多核状态下也能保证同步,而且延时很低。更详细的资料可以参考:https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx

Tips

Windows 10 任务管理器结束任务流程

从Win8开始,任务管理已经悄然发生变化了,这篇文章要说的就是结束任务这一个功能。以Win10的任务管理器为主来说明,没有了从窗口关闭进程的标签。取而代之的是一个区分前台和后台程序的进程树。通过这个界面结束进程也不再像以前一样调用User32的EndTask(https://msdn.microsoft.com/en-us/library/windows/desktop/ms633492(v=vs.85).aspx),而是重新规划了一套逻辑。

具体逻辑如下:

1.区分程序类型

2.如果是窗口程序,则给窗口发送WM_SYSCOMMAND+SC_CLOSE结束窗口来结束进程

3.如果是服务程序,则调用ControlService+SERVICE_CONTROL_STOP结束服务来结束进程

4.如果既没有窗口也不是服务的程序,或者说在第2,3步没有结束成功的进程,会调用TerminateProcess来强行结束进程。

5.第五步是和之前结束任务最大的一个区别,以前的任务管理器,如果没能结束进程,例如一些僵尸进程,他就不会做其他动作了,而新的任务管理器为了释放这种进程所占用的内核资源,他还会做另外一些事情,那就是关闭目标进程的所有句柄。使用的方式就是DuplicateHandle+DUPLICATE_CLOSE_SOURCE。这样做的另外一个好处就是,如果顽固进程还在运行,句柄关闭会造成其崩溃而结束。

Tips

Windows 8 Shell API对于长路径文件名的支持

在Windows 8之前,Shell API对于长路径的文件名的支持并不理想。比如PathAppend这个函数,函数规定pszPath,也就是第一个参数,它的buffer大小必须要能够容纳MAX_PATH个字符。第二个参数pszMore也不能超过MAX_PATH的长度。这样的API不仅不能满足我们对长文件路径需求,同时也可能让我们的软件由于字符串检查不严格出现严重BUG和漏洞。

还好,这个问题在Windows 8以及以后的系统上得到了解决。还是以路径拼接为例。微软向我们介绍了PathCchAppend和PathCchAppendEx函数。其中PathCchAppend函数,增加了cchPath参数,用来指定输出buffer的大小。用这样的方式来加强参数的检查,增加了函数的安全性。而PathCchAppendEx这个函数在PathCchAppend基础上,又加入了dwFlags,现在这个标志只有PATHCCH_ALLOW_LONG_PATHS,意思就是让我们的路径名超过MAX_PATH。

不知道微软设计PathCchAppend和PathCchAppendEx这两个API的时候是怎么样的一个想法,我觉得完全没必要设计成两个函数,一个PathCchAppendEx就足够了。大家是不是也有这个疑问呢?

最后,由于Windows 7现在的使用量还是非常大的,我们也不能因为要使用这些新的API而放弃兼容老版本的Windows。比较合适的做法还是动态导入这些函数,如果成功了就可以使用新的函数,失败就用老的函数。另外值得注意的是,PathCchAppend这类新的函数并不是放在shlwapi.dll里面,而是在kernelbase.dll,动态获取函数的时候需要注意这一点。

Tips

获取桌面图标位置

用来干什么就不用说了,反正不是什么好事情 =v=

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
typedef struct _DESKTOP_ICON_INFO {
LVITEMW item;
WCHAR item_text[MAX_PATH];
RECT rc;
} DESKTOP_ICON_INFO, *PDESKTOP_ICON_INFO;
BOOL GetDesktopIconInfo(LPCWSTR pattern, RECT &rc, HWND &desktop)
{
HWND progman = FindWindow(TEXT("Progman"), TEXT("Program Manager"));
if (progman == NULL) {
return FALSE;
}
HWND def_view = FindWindowEx(progman, NULL, TEXT("SHELLDLL_DefView"), NULL);
if (def_view == NULL) {
return FALSE;
}
HWND list_view = FindWindowEx(def_view, NULL, TEXT("SysListView32"), TEXT("FolderView"));
if (list_view == NULL) {
return FALSE;
}
desktop = list_view;
ULONG process_id = 0;
GetWindowThreadProcessId(progman, &process_id);
if (process_id == 0) {
return FALSE;
}
int count = (int)::SendMessage(list_view, LVM_GETITEMCOUNT, 0, 0);
HANDLE process_handle = OpenProcess(
PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_QUERY_INFORMATION, FALSE, process_id);
if (process_handle == NULL) {
return FALSE;
}
PUCHAR remote_addr = (PUCHAR)VirtualAllocEx(process_handle, NULL,
sizeof(DESKTOP_ICON_INFO), MEM_COMMIT, PAGE_READWRITE);
DESKTOP_ICON_INFO icon_info;
icon_info.item.iItem = 0;
icon_info.item.iSubItem = 0;
icon_info.item.mask = LVIF_TEXT;
icon_info.item.pszText = (WCHAR *)(remote_addr + offsetof(DESKTOP_ICON_INFO, item_text));
icon_info.item.cchTextMax = MAX_PATH;
for (int i = 0; i < count; i++) {
icon_info.rc.left = LVIR_BOUNDS;
ZeroMemory(icon_info.item_text, sizeof(icon_info.item_text));
if (WriteProcessMemory(process_handle, remote_addr, &icon_info, sizeof(icon_info), NULL)) {
::SendMessage(list_view, LVM_GETITEMTEXT, (WPARAM)i, (LPARAM)(remote_addr + offsetof(DESKTOP_ICON_INFO, item)));
::SendMessage(list_view, LVM_GETITEMRECT, (WPARAM)i, (LPARAM)(remote_addr + offsetof(DESKTOP_ICON_INFO, rc)));
ReadProcessMemory(process_handle, remote_addr, &icon_info, sizeof(icon_info), NULL);
if (_wcsicmp(icon_info.item_text, pattern) == 0) {
rc = icon_info.rc;
break;
}
}
}
VirtualFreeEx(process_handle, remote_addr, 0, MEM_RELEASE);
CloseHandle(process_handle);
return TRUE;
}

Tips

查看消息窗口工具

我们都知道用Spy++去查看窗口句柄的相关信息,但是这款工具无法找到消息窗口(Message-Only Windows)。所以写了个查看消息窗口的工具,帮我排查一些这方面的问题。

20160317120246

下载:MsgOnlyWnd

Tips