Windows 10设置系统DPI

现在的显示器分辨率越来越高2K,4K甚至5K,而很多程序并不支持这一的高分辨率,所以这些程序在桌面上会显示的很小,好在Windows 8以后的系统中,我们可以设置DPI来放大程序的窗口,如下图所示:

20170309145111

但是,微软并没有把设置DPI的接口文档化。所以我把这个功能逆了一下,还原的代码如下:

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

/*
BOOL ApplyDpiSetting(int val);
val 为DPI要设置的数字,例如0是100%,1是125%,以此类推,注意250%以后是300%。
另外上面的对应关系只是通常情况下的,还有可能有其他对应关系,例如0是300%,-1是250%等等。
具体怎么对应可以通过GetDpiForMonitor函数来获取
*/

typedef struct _SET_DPI {
DISPLAYCONFIG_DEVICE_INFO_HEADER header;
ULONG val;
} SET_DPI;

BOOL ApplyDpiSetting(ULONG val)
{
UINT32 num_of_paths = 0;
UINT32 num_of_modes = 0;
DISPLAYCONFIG_PATH_INFO* display_paths = NULL;
DISPLAYCONFIG_MODE_INFO* display_modes = NULL;
BOOL retval = FALSE;

do
{
if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS,
&num_of_paths,
&num_of_modes) != ERROR_SUCCESS) {
break;
}


display_paths = (DISPLAYCONFIG_PATH_INFO*)calloc((int)num_of_paths, sizeof(DISPLAYCONFIG_PATH_INFO));
display_modes = (DISPLAYCONFIG_MODE_INFO*)calloc((int)num_of_modes, sizeof(DISPLAYCONFIG_MODE_INFO));

if (QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS,
&num_of_paths,
display_paths,
&num_of_modes,
display_modes,
NULL) != ERROR_SUCCESS) {
break;
}

SET_DPI dpi;
dpi.header.type = (DISPLAYCONFIG_DEVICE_INFO_TYPE)0xFFFFFFFC;
dpi.header.size = sizeof(dpi);
dpi.header.adapterId = display_paths[0].sourceInfo.adapterId;
dpi.header.id = display_paths[0].sourceInfo.id;
dpi.val = val;

if (DisplayConfigSetDeviceInfo((DISPLAYCONFIG_DEVICE_INFO_HEADER*)&dpi) == ERROR_SUCCESS) {
retval = TRUE;
}

} while (0);

if (display_paths) {
free(display_paths);
}

if (display_modes) {
free(display_modes);
}

return retval;
}



Tips

让编译器不推荐(deprecate)使用一个函数

在开发一些公共库函数的时候,我们常常会对函数进行改写,这个时候我们会希望使用者用新的函数。为了提醒使用者,我们可以通过将函数声明为deprecated,这样编译器在编译的时候会抛出一个C4995或者C4996的警告。这个警告我们应该也经常看到过,比如使用strcpy,编译器会提示我们使用strcpy_s。

使用这个编译器特性有两种方法:

  1. __declspec(deprecated)
  2. #pragma deprecated

当然我们还可以给警告自定义消息信息

1
__declspec(deprecated("** this is a deprecated function **")) void func2(int) {}

Tips

PDB 下载工具

前段时间微软的符号服务器特别不稳定,Windbg下载符号文件老是失败,所以就专门写了个pdb的下载工具(pdbdownloader)放到Github上。

P.S. 用WPF写界面确实是很有趣

20170103120319

Tips

快速抛出失败的方法 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