编译时自动增加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

c06d007f异常的解决方法

c06d007f这个异常通常是在PE的延迟加载dll的时候发生的,加载器找不到对应的dll就会抛出这个异常。如果我们对这个异常不熟悉,按照常规方式去找上下文,那么结果肯定会让你失望。例如3.2526.1373.0版本的libcef在XP上运行的情况。

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

0:000> kb
# ChildEBP RetAddr Args to Child
00 0012f218 7c92d9ac 7c86449d d0000144 00000004 ntdll!KiFastSystemCallRet
01 0012f21c 7c86449d d0000144 00000004 00000000 ntdll!ZwRaiseHardError+0xc
02 0012f4a0 7c843892 0012f4c8 7c839b21 0012f4d0 kernel32!UnhandledExceptionFilter+0x628
03 0012f4a8 7c839b21 0012f4d0 00000000 0012f4d0 kernel32!BaseProcessStart+0x39
04 0012f4d0 7c9232a8 0012f5bc 0012ffe0 0012f5d4 kernel32!_except_handler3+0x61
05 0012f4f4 7c92327a 0012f5bc 0012ffe0 0012f5d4 ntdll!ExecuteHandler2+0x26
06 0012f5a4 7c92e46a 00000000 0012f5d4 0012f5bc ntdll!ExecuteHandler+0x24
07 0012f5a4 00000000 00000000 0012f5d4 0012f5bc ntdll!KiUserExceptionDispatcher+0xe
WARNING: Frame IP not in any known module. Following frames may be wrong.
08 0012fff4 004a991e 00000000 78746341 00000020 0x0
09 0012fff8 00000000 78746341 00000020 00000001 cefclient!pre_c_init+0xb9 [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 261]

0:000> .cxr 0012f5d4;k
eax=0012f8a4 ebx=1314a58c ecx=00000000 edx=00000001 esi=0012f954 edi=68d60000
eip=00000000 esp=0012fff8 ebp=00000000 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
00000000 ?? ???
*** Stack trace for last set context - .thread/.cxr resets it
# ChildEBP RetAddr
WARNING: Frame IP not in any known module. Following frames may be wrong.
00 0012fff4 004a991e 0x0
01 0012fff8 00000000 cefclient!pre_c_init+0xb9 [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 261]

直接看栈回溯或者通过设置cxr看栈回溯,并没有帮助我们找到什么有用的信息。

这里要使用的方法是,利用异常的参数来找到具体延迟加载谁的时候发生了异常。

1
2
3
4
5
6
7
8

0:000> .exr 0012f5bc
ExceptionAddress: 7c812aeb (kernel32!RaiseException+0x00000053)
ExceptionCode: c06d007f
ExceptionFlags: 00000000
NumberParameters: 1
Parameter[0]: 0012f918

这里的参数0,就是我们要找的目标,记录了出错时候ebp-0x30的数据,也就是含有关键信息的地方。让我们仔细看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

0:000> dds 0012f918
0012f918 00000024
0012f91c 1314a58c libcef!_DELAY_IMPORT_DESCRIPTOR_dbghelp_dll
0012f920 13181dbc libcef!_imp__SymGetSearchPathW
0012f924 12ebdd20 libcef!_sz_dbghelp_dll
0012f928 00000001
0012f92c 1314ac8e libcef!dxva2_NULL_THUNK_DATA_DLN+0x7e
0012f930 68d60000 dbghelp!_imp__CryptAcquireContextA <PERF> (dbghelp+0x0)
0012f934 00000000
0012f938 0000007f
0012f93c 1314c138 libcef!dxva2_NULL_THUNK_DATA_DLN+0x1528
0012f940 00000003
0012f944 00000000
0012f948 0012f9f8
0012f94c 11d17587 libcef!_tailMerge_dbghelp_dll+0xd
0012f950 0012f918
0012f954 13181dbc libcef!_imp__SymGetSearchPathW
0012f958 00000008
0012f95c 7c9301bb ntdll!RtlAllocateHeap+0xeac
0012f960 1019014e libcef!base::debug::`anonymous namespace'::InitializeSymbols+0x9e [f:\stnts\browser\cef\ws\src\chromium\src\base\debug\stack_trace_win.cc @ 79]
0012f964 ffffffff
0012f968 00170880

我们可以清楚的看到加载器延迟加载SymGetSearchPathW的时候发生了问题。让我们进一步用depends工具验证一下

20160223003624

如上图所示,XP自带的dbghelp里没有SymGetSearchPathW这个导出函数。要解决这个异常,实际上就需要在运行目录里添加一个稍微新一点的dbghelp文件,我这里替换的是6.2.9200.16384的dbghelp,替换过后问题已经不再出现了。

20160223003711

debugging

调试器最早的中断应用程序的方法

这篇Blog分享一个Windbg的小技巧,就是让被调试程序更早的中断到调试器。熟悉Windbg的朋友都知道,用调试器运行程序,默认情况下都会中断到ntdll!LdrpDoDebuggerBreak。但是有时候我们会想去调试程序加载的过程,这个时候就需要我们更早的中断下来。那么这里就用利用到调试器最早接受到的调试事件了。CREATE_PROCESS_DEBUG_EVENT,这个调试事件是创建进程的时候进程发给调试器的,在这个时候,你甚至连ntdll都没有完成加载,这也导致ntdll的符号无法加载,很多有用的功能用不上。但幸运的是,虽然ntdll没有完成加载,但是已经加载到了内存,另外我们可以用手动加载符号的方法,把符号文件加载到ntdll的内存上去。

演示如下:

windbg.EXE -xe cpr -xe ld notepad.exe

这里设置中断系统事件cpr,也就是CREATE_PROCESS_DEBUG_EVENT

1
2
3
4
5
6
7
8

0:000> lm
start end module name
00007ff7`3f6e0000 00007ff7`3f721000 notepad (deferred)
0:000> !teb
TEB at 000000d995d21000
error InitTypeRead( TEB )...

中断下来后我们可以看到,!teb是没法用的

1
2
3
4
5
6
7
8

0:000> .imgscan
MZ at 00007ff7`3f6e0000, prot 00000002, type 01000000 - size 41000
Name: notepad.exe
MZ at 00007ffb`7c7b0000, prot 00000002, type 01000000 - size 1c1000
Name: ntdll.dll
0:000> .reload /f ntdll.dll=00007ffb`7c7b0000

我们需要找到ntdll的模块,然后手动加载符号,然后就可以使用和ntdll有关系的命令了。

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

0:000> lm
start end module name
00007ff7`3f6e0000 00007ff7`3f721000 notepad (deferred)
00007ffb`7c7b0000 00007ffb`7c971000 ntdll (pdb symbols) e:\workspace\mysymbols\ntdll.pdb\F296699DB5314A06935E88564D8CD2731\ntdll.pdb

0:000> !teb
TEB at 000000d995d21000
ExceptionList: 0000000000000000
StackBase: 000000d995af0000
StackLimit: 000000d995adf000
SubSystemTib: 0000000000000000
FiberData: 0000000000001e00
ArbitraryUserPointer: 0000000000000000
Self: 000000d995d21000
EnvironmentPointer: 0000000000000000
ClientId: 0000000000001c8c . 00000000000017c4
RpcHandle: 0000000000000000
Tls Storage: 0000000000000000
PEB Address: 000000d995d20000
LastErrorValue: 0
LastStatusValue: 0
Count Owned Locks: 0
HardErrorMode: 0

debugging