Windows XP 多核同步(二)

ʕ •ᴥ•ʔ ɔ:

1 信号量SEMAPHORE

信号量(Semaphore)与事件(Event)的不同在于:信号量可以让多个线程同时进入自己的临界区执行代码,而事件只允许一个线程同一时刻进入自己的临界区执行代码。

信号量结构如下:

1
2
3
4
kd> dt _KSEMAPHORE
nt!_KSEMAPHORE
+0x000 Header : _DISPATCHER_HEADER
+0x010 Limit : Int4B

3环常用操作信号量相关函数:

  • CreateSemaphore(创建一个信号量)
  • OpenSemaphore(为现有的一个已命名信号量对象创建一个新句柄)
  • ReleaseSemaphore(递增信号量的当前资源计数)

1.1 3环实例

搞个3环例子看看。

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
#include "stdafx.h"		
#include "resource.h"
#include <stdio.h>

HANDLE hSemaphore;
HANDLE hThread[3];
HWND hEditSet,hEdit1,hEdit2,hEdit3;
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
TCHAR szBuffer[10];
DWORD dwTimmer=0;

while(dwTimmer<100)
{
WaitForSingleObject(hSemaphore, INFINITE);
Sleep(1150);
memset(szBuffer,0,10);
GetWindowText(hEdit1,szBuffer,10);
sscanf( szBuffer, "%d", &dwTimmer );
dwTimmer++;
memset(szBuffer,0,10);
sprintf(szBuffer,"%d",dwTimmer);
SetWindowText(hEdit1,szBuffer);
ReleaseSemaphore(hSemaphore, 1, NULL);
}

return 0;
}
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
TCHAR szBuffer[10];
DWORD dwTimmer=0;


while(dwTimmer<100)
{
WaitForSingleObject(hSemaphore, INFINITE);
Sleep(1150);
memset(szBuffer,0,10);
GetWindowText(hEdit2,szBuffer,10);
sscanf( szBuffer, "%d", &dwTimmer ); //szBuffer -> dwTimmer
dwTimmer++;
memset(szBuffer,0,10);
sprintf(szBuffer,"%d",dwTimmer);
SetWindowText(hEdit2,szBuffer);
ReleaseSemaphore(hSemaphore, 1, NULL);
}

return 0;
}
DWORD WINAPI ThreadProc3(LPVOID lpParameter)
{
TCHAR szBuffer[10];
DWORD dwTimmer=0;


while(dwTimmer<100)
{
WaitForSingleObject(hSemaphore, INFINITE);
Sleep(1150);
memset(szBuffer,0,10);
GetWindowText(hEdit3,szBuffer,10);
sscanf( szBuffer, "%d", &dwTimmer );
dwTimmer++;
memset(szBuffer,0,10);
sprintf(szBuffer,"%d",dwTimmer);
SetWindowText(hEdit3,szBuffer);
ReleaseSemaphore(hSemaphore, 1, NULL);
}

return 0;
}
DWORD WINAPI ThreadBegin(LPVOID lpParameter)
{
TCHAR szBuffer[10];
DWORD dwMoney=0;


hSemaphore = CreateSemaphore(NULL,0,3,NULL);

hThread[0] = ::CreateThread(NULL, 0, ThreadProc1,NULL, 0, NULL);
hThread[1] = ::CreateThread(NULL, 0, ThreadProc2,NULL, 0, NULL);
hThread[2] = ::CreateThread(NULL, 0, ThreadProc3,NULL, 0, NULL);

//开始准备红包
while(dwMoney<2000)
{
memset(szBuffer,0,10);
GetWindowText(hEditSet,szBuffer,10);
sscanf( szBuffer, "%d", &dwMoney );
dwMoney++;
memset(szBuffer,0,10);
sprintf(szBuffer,"%d",dwMoney);
SetWindowText(hEditSet,szBuffer);
}
ReleaseSemaphore(hSemaphore, 2, NULL);

::WaitForMultipleObjects(3, hThread,TRUE,INFINITE);
::CloseHandle(hSemaphore);

return 0;
}
BOOL CALLBACK MainDlgProc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
BOOL bRet = FALSE;

switch(uMsg)
{
case WM_CLOSE:
{
EndDialog(hDlg,0);
break;
}
case WM_INITDIALOG:
{
hEditSet = GetDlgItem(hDlg,IDC_EDIT_SET);
hEdit1 = GetDlgItem(hDlg,IDC_EDIT_SET_1);
hEdit2 = GetDlgItem(hDlg,IDC_EDIT_SET_2);
hEdit3 = GetDlgItem(hDlg,IDC_EDIT_SET_3);

SetWindowText(hEditSet,"0");
SetWindowText(hEdit1,"0");
SetWindowText(hEdit2,"0");
SetWindowText(hEdit3,"0");

break;
}
case WM_COMMAND:
switch (LOWORD (wParam))
{
case IDC_BUTTON_BEGIN:
{
CreateThread(NULL, 0, ThreadBegin,NULL, 0, NULL);

return TRUE;
}
}
break ;
}

return bRet;
}
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// TODO: Place code here.
DialogBox(hInstance,MAKEINTRESOURCE(IDD_DIALOG_MAIN),NULL,MainDlgProc);
return 0;
}

4.png

1.2 CreateSemaphore

CreateSemaphore函数:创建或打开命名或未命名信号量对象。

1
2
3
4
5
6
HANDLE WINAPI CreateSemaphore(
__in_opt LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,//如果此参数为NULL,则该句柄不能由子进程继承。
__in LONG lInitialCount, //创建信号量对象时允许同时访问自身临界区的线程个数。
__in LONG lMaximumCount, //信号量对象的最大计数。这个值必须大于零。
__in_opt LPCTSTR lpName //信号量对象的名称。该名称长度仅限于MAX_PATH字符。名称比较区分大小写。
);

上述参数对应内核结构体相关成员:

1
2
3
4
5
6
7
8
9
10
11
12
13
kd> dt _KSEMAPHORE
nt!_KSEMAPHORE
+0x000 Header : _DISPATCHER_HEADER
+0x010 Limit : Int4B //lMaximumCount

kd> dt _DISPATCHER_HEADER
nt!_DISPATCHER_HEADER
+0x000 Type : UChar //5
+0x001 Absolute : UChar
+0x002 Size : UChar
+0x003 Inserted : UChar
+0x004 SignalState : Int4B //lInitialCount
+0x008 WaitListHead : _LIST_ENTRY

CreateSemaphore函数调用过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
kernel32.dll
HANDLE __stdcall CreateSemaphoreA(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount,
LONG lMaximumCount, LPCSTR lpName)
↓ ↓
HANDLE __stdcall CreateSemaphoreW(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount,
LONG lMaximumCount, LPCWSTR lpName)
↓ ↓
ntdll.dll
__stdcall NtCreateSemaphore(x, x, x, x, x)
↓ ↓
ntkrpamp.exe
NTSTATUS __stdcall NtCreateSemaphore(PHANDLE SemaphoreHandle, ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes, LONG InitialCount, LONG MaximumCount)

1.3 NtCreateSemaphore

源码如下(base\ntos\ex\semphore.c):

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
NTSTATUS __stdcall NtCreateSemaphore (
IN PHANDLE SemaphoreHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN LONG InitialCount,
IN LONG MaximumCount
)
{

HANDLE Handle;
KPROCESSOR_MODE PreviousMode;
PVOID Semaphore;
NTSTATUS Status;

PreviousMode = KeGetPreviousMode();
if (PreviousMode != KernelMode) {
try {
ProbeForWriteHandle(SemaphoreHandle);
} except(ExSystemExceptionFilter()) {
return GetExceptionCode();
}
}

if ((MaximumCount <= 0) || (InitialCount < 0) ||
(InitialCount > MaximumCount)) {
return STATUS_INVALID_PARAMETER;
}

Status = ObCreateObject(PreviousMode,
ExSemaphoreObjectType,
ObjectAttributes,
PreviousMode,
NULL,
sizeof(KSEMAPHORE),
0,
0,
(PVOID *)&Semaphore);


if (NT_SUCCESS(Status)) {
KeInitializeSemaphore((PKSEMAPHORE)Semaphore,
InitialCount,
MaximumCount);

Status = ObInsertObject(Semaphore,
NULL,
DesiredAccess,
0,
(PVOID *)NULL,
&Handle);

if (NT_SUCCESS(Status)) {
if (PreviousMode != KernelMode) {
try {
*SemaphoreHandle = Handle;
} except(ExSystemExceptionFilter()) {
NOTHING;
}
}
else {
*SemaphoreHandle = Handle;
}
}
}

return Status;
}

逆向分析代码如下:

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
PAGE:0053D702 ; =============== S U B R O U T I N E =======================================
PAGE:0053D702
PAGE:0053D702 ; Attributes: bp-based frame
PAGE:0053D702
PAGE:0053D702 ; NTSTATUS __stdcall NtCreateSemaphore(PHANDLE SemaphoreHandle, ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes, LONG InitialCount, LONG MaximumCount)
PAGE:0053D702 _NtCreateSemaphore@20 proc near ; DATA XREF: .text:0042D51C↑o
PAGE:0053D702
PAGE:0053D702 var_2C = dword ptr -2Ch
PAGE:0053D702 var_28 = dword ptr -28h
PAGE:0053D702 Semaphore = dword ptr -24h
PAGE:0053D702 PreviousMode = byte ptr -20h
PAGE:0053D702 var_1C = dword ptr -1Ch
PAGE:0053D702 ms_exc = CPPEH_RECORD ptr -18h
PAGE:0053D702 SemaphoreHandle = dword ptr 8
PAGE:0053D702 DesiredAccess = dword ptr 0Ch
PAGE:0053D702 ObjectAttributes= dword ptr 10h
PAGE:0053D702 InitialCount = dword ptr 14h
PAGE:0053D702 MaximumCount = dword ptr 18h
PAGE:0053D702
PAGE:0053D702 //__unwind { //__SEH_prolog
PAGE:0053D702 push 1Ch
PAGE:0053D704 push offset stru_406258
PAGE:0053D709 call __SEH_prolog
PAGE:0053D70E mov eax, large fs:124h
PAGE:0053D714 mov al, [eax+_KTHREAD.PreviousMode]
PAGE:0053D71A mov [ebp+PreviousMode], al
PAGE:0053D71D xor ebx, ebx
PAGE:0053D71F cmp al, bl
PAGE:0053D721 jz short loc_53D75D //如果函数从0环调用,则无需检查参数
PAGE:0053D723 // __try { //__except at loc_53D74E
PAGE:0053D723 mov [ebp+ms_exc.registration.TryLevel], ebx //--
PAGE:0053D723 //--
PAGE:0053D723 //函数从3环调用,进行参数检查
PAGE:0053D723 //--
PAGE:0053D726 mov esi, [ebp+SemaphoreHandle] //esi是一个地址PHANDLE,用于保存返回的HANDLE
PAGE:0053D729 mov eax, _MmUserProbeAddress //_MmUserProbeAddress:用户空间可访问的最大地址
PAGE:0053D72E cmp esi, eax
PAGE:0053D730 jb short loc_53D734 //符合条件:PHANDLE < MmUserProbeAddress
PAGE:0053D732 mov [eax], ebx //PHANDLE >= MmUserProbeAddress
PAGE:0053D732 //MmUserProbeAddress = NULL
PAGE:0053D734
PAGE:0053D734 loc_53D734: //CODE XREF: NtCreateSemaphore(x,x,x,x,x)+2E↑j
PAGE:0053D734 mov eax, [esi]
PAGE:0053D736 mov [esi], eax
PAGE:0053D736 // } //starts at 53D723
PAGE:0053D738 or [ebp+ms_exc.registration.TryLevel], 0FFFFFFFFh
PAGE:0053D73C jmp short loc_53D760 //ebx == 0
PAGE:0053D73C //--
PAGE:0053D73C //函数正式处理业务
PAGE:0053D73C //--
PAGE:0053D73E ---------------------------------------------------------------------------
PAGE:0053D73E
PAGE:0053D73E loc_53D73E: //DATA XREF: .text:stru_406258↑o
PAGE:0053D73E // __except filter //owned by 53D723 //异常处理
PAGE:0053D73E mov eax, [ebp+ms_exc.exc_ptr]
PAGE:0053D741 mov eax, [eax]
PAGE:0053D743 mov eax, [eax]
PAGE:0053D745 mov [ebp+var_2C], eax
PAGE:0053D748 call _ExSystemExceptionFilter@0 //ExSystemExceptionFilter()
PAGE:0053D74D retn
PAGE:0053D74E ---------------------------------------------------------------------------
PAGE:0053D74E
PAGE:0053D74E loc_53D74E: //DATA XREF: .text:stru_406258↑o
PAGE:0053D74E // __except(loc_53D73E) //owned by 53D723 //异常处理
PAGE:0053D74E mov esp, [ebp+ms_exc.old_esp]
PAGE:0053D751 or [ebp+ms_exc.registration.TryLevel], 0FFFFFFFFh
PAGE:0053D755 mov eax, [ebp+var_2C]
PAGE:0053D758 jmp loc_53D7EF
PAGE:0053D75D ---------------------------------------------------------------------------
PAGE:0053D75D
PAGE:0053D75D loc_53D75D: //CODE XREF: NtCreateSemaphore(x,x,x,x,x)+1F↑j
PAGE:0053D75D mov esi, [ebp+SemaphoreHandle] //如果函数从0环调用,则无需检查参数
PAGE:0053D760
PAGE:0053D760 loc_53D760: //CODE XREF: NtCreateSemaphore(x,x,x,x,x)+3A↑j
PAGE:0053D760 cmp [ebp+MaximumCount], ebx //ebx == 0
PAGE:0053D760 //--
PAGE:0053D760 //函数正式处理业务
PAGE:0053D760 //--
PAGE:0053D763 jle loc_53D7EA //返回 STATUS_INVALID_PARAMETER (ntstatus.h)
PAGE:0053D769 mov edi, [ebp+InitialCount]
PAGE:0053D76C cmp edi, ebx
PAGE:0053D76E jl short loc_53D7EA //返回 STATUS_INVALID_PARAMETER (ntstatus.h)
PAGE:0053D770 cmp edi, [ebp+MaximumCount]
PAGE:0053D773 jg short loc_53D7EA //返回 STATUS_INVALID_PARAMETER (ntstatus.h)
PAGE:0053D775 lea eax, [ebp+Semaphore] //Semaphore局部变量
PAGE:0053D778 push eax //int
PAGE:0053D779 push ebx //int
PAGE:0053D77A push ebx //int
PAGE:0053D77B push 14h //int
PAGE:0053D77D push ebx //int
PAGE:0053D77E push dword ptr [ebp+PreviousMode] //BackTraceHash
PAGE:0053D781 push [ebp+ObjectAttributes] //int
PAGE:0053D784 push _ExSemaphoreObjectType //POBJECT_TYPE
PAGE:0053D78A push dword ptr [ebp+PreviousMode] //PreviousMode
PAGE:0053D78D call _ObCreateObject@36 //--
PAGE:0053D78D //创建对象,创建的对象暂时存到局部变量PVOID Semaphore中
PAGE:0053D78D //--
PAGE:0053D792 mov [ebp+var_28], eax
PAGE:0053D795 cmp eax, ebx
PAGE:0053D797 jl short loc_53D7E5 //ObCreateObject创建对象失败
PAGE:0053D799 push [ebp+MaximumCount] //Limit
PAGE:0053D79C push edi //Count
PAGE:0053D79D push [ebp+Semaphore] //Semaphore
PAGE:0053D7A0 call _KeInitializeSemaphore@12 //--
PAGE:0053D7A0 //对象虽然创建出来了,但是该对象的属性成员还没有赋值给对象自己
PAGE:0053D7A0 //本函数对这些属性进行赋值
PAGE:0053D7A0 //同时将对象插入到自己的等待链_KWAIT_BLOCK.WaitListEntry
PAGE:0053D7A0 //--
PAGE:0053D7A5 lea eax, [ebp+var_1C] //var_1C、eax保存返回的句柄值
PAGE:0053D7A8 push eax //int
PAGE:0053D7A9 push ebx //int
PAGE:0053D7AA push ebx //int
PAGE:0053D7AB push [ebp+DesiredAccess] //AccessMask
PAGE:0053D7AE push ebx //int
PAGE:0053D7AF push [ebp+Semaphore] //Object
PAGE:0053D7B2 call _ObInsertObject@24 //将所创建的对象插入对象目录和本进程句柄表(或内核句柄表),并返回句柄。
PAGE:0053D7B2 //https://pediy.com/kssd/pediy11/114734/820481/44303.pdf
PAGE:0053D7B7 mov [ebp+var_28], eax
PAGE:0053D7BA cmp eax, ebx //ebx == 0,eax保存返回的句柄值
PAGE:0053D7BC jl short loc_53D7E5 //ObCreateObject创建对象失败
PAGE:0053D7BE cmp [ebp+PreviousMode], bl
PAGE:0053D7C1 jz short loc_53D7E0 //var_1C保存返回的句柄值
PAGE:0053D7C3 // __try { //__except at loc_53D7DB
PAGE:0053D7C3 mov [ebp+ms_exc.registration.TryLevel], 1
PAGE:0053D7CA mov eax, [ebp+var_1C] //var_1C保存返回的句柄值
PAGE:0053D7CD mov [esi], eax //esi指向参数SemaphoreHandle(PHANDLE)
PAGE:0053D7CD // } //starts at 53D7C3
PAGE:0053D7CF
PAGE:0053D7CF loc_53D7CF: //CODE XREF: NtCreateSemaphore(x,x,x,x,x)+DC↓j
PAGE:0053D7CF or [ebp+ms_exc.registration.TryLevel], 0FFFFFFFFh
PAGE:0053D7D3 jmp short loc_53D7E5 //ObCreateObject创建对象失败
PAGE:0053D7D5 ---------------------------------------------------------------------------
PAGE:0053D7D5
PAGE:0053D7D5 loc_53D7D5: //DATA XREF: .text:stru_406258↑o
PAGE:0053D7D5 // __except filter //owned by 53D7C3
PAGE:0053D7D5 call _ExSystemExceptionFilter@0 //ExSystemExceptionFilter()
PAGE:0053D7DA retn
PAGE:0053D7DB ---------------------------------------------------------------------------
PAGE:0053D7DB
PAGE:0053D7DB loc_53D7DB: //DATA XREF: .text:stru_406258↑o
PAGE:0053D7DB // __except(loc_53D7D5) //owned by 53D7C3
PAGE:0053D7DB mov esp, [ebp+ms_exc.old_esp]
PAGE:0053D7DE jmp short loc_53D7CF
PAGE:0053D7E0 ---------------------------------------------------------------------------
PAGE:0053D7E0
PAGE:0053D7E0 loc_53D7E0: //CODE XREF: NtCreateSemaphore(x,x,x,x,x)+BF↑j
PAGE:0053D7E0 mov eax, [ebp+var_1C] //var_1C保存返回的句柄值
PAGE:0053D7E3 mov [esi], eax
PAGE:0053D7E5
PAGE:0053D7E5 loc_53D7E5: //CODE XREF: NtCreateSemaphore(x,x,x,x,x)+95↑j
PAGE:0053D7E5 //NtCreateSemaphore(x,x,x,x,x)+BA↑j ...
PAGE:0053D7E5 mov eax, [ebp+var_28] //ObCreateObject创建对象失败
PAGE:0053D7E8 jmp short loc_53D7EF
PAGE:0053D7EA ---------------------------------------------------------------------------
PAGE:0053D7EA
PAGE:0053D7EA loc_53D7EA: //CODE XREF: NtCreateSemaphore(x,x,x,x,x)+61↑j
PAGE:0053D7EA //NtCreateSemaphore(x,x,x,x,x)+6C↑j ...
PAGE:0053D7EA mov eax, 0C000000Dh //返回 STATUS_INVALID_PARAMETER (ntstatus.h)
PAGE:0053D7EF
PAGE:0053D7EF loc_53D7EF: //CODE XREF: NtCreateSemaphore(x,x,x,x,x)+56↑j
PAGE:0053D7EF //NtCreateSemaphore(x,x,x,x,x)+E6↑j
PAGE:0053D7EF call __SEH_epilog
PAGE:0053D7F4 retn 14h
PAGE:0053D7F4 //} //starts at 53D702
PAGE:0053D7F4 _NtCreateSemaphore@20 endp
PAGE:0053D7F4
PAGE:0053D7F4 ; ---------------------------------------------------------------------------

1.4 KeInitializeSemaphore

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
.text:00425ECE ; =============== S U B R O U T I N E =======================================
.text:00425ECE
.text:00425ECE ; --
.text:00425ECE ; 对象虽然创建出来了,但是该对象的属性成员还没有赋值给对象自己
.text:00425ECE ; 本函数对这些属性进行赋值
.text:00425ECE ; 同时将对象插入到自己的等待链_KWAIT_BLOCK.WaitListEntry
.text:00425ECE ; --
.text:00425ECE ; Attributes: bp-based frame
.text:00425ECE
.text:00425ECE ; void __stdcall KeInitializeSemaphore(PRKSEMAPHORE Semaphore, LONG Count, LONG Limit)
.text:00425ECE public _KeInitializeSemaphore@12
.text:00425ECE _KeInitializeSemaphore@12 proc near ; CODE XREF: ExpAllocateSharedWaiterSemaphore(x,x)+3C↓p
.text:00425ECE ; ExReinitializeResourceLite(x)+3F↓p ...
.text:00425ECE
.text:00425ECE Semaphore = dword ptr 8
.text:00425ECE Count = dword ptr 0Ch
.text:00425ECE Limit = dword ptr 10h
.text:00425ECE
.text:00425ECE mov edi, edi
.text:00425ED0 push ebp
.text:00425ED1 mov ebp, esp
.text:00425ED3 mov eax, [ebp+Semaphore]
.text:00425ED6 mov ecx, [ebp+Count]
.text:00425ED9 mov [eax+_KSEMAPHORE.Header.SignalState], ecx
.text:00425EDC lea ecx, [eax+_KSEMAPHORE.Header.WaitListHead.Flink]
.text:00425EDF mov [eax+_KSEMAPHORE.Header.Type], 5
.text:00425EE2 mov [eax+_KSEMAPHORE.Header.Size], 5
.text:00425EE6 mov [ecx+_KWAIT_BLOCK.WaitListEntry.Blink], ecx
.text:00425EE9 mov [ecx+_KWAIT_BLOCK.WaitListEntry.Flink], ecx
.text:00425EEB mov ecx, [ebp+Limit]
.text:00425EEE mov [eax+_KSEMAPHORE.Limit], ecx
.text:00425EF1 pop ebp
.text:00425EF2 retn 0Ch
.text:00425EF2 _KeInitializeSemaphore@12 endp
.text:00425EF2
.text:00425EF2 ; ---------------------------------------------------------------------------

1.5 ReleaseSemaphore

ReleaseSemaphore函数将指定信号量对象的计数增加指定数量。

设置$NewSignalState = OldSignalState + N(参数)$

1
2
3
4
5
6
BOOL WINAPI ReleaseSemaphore(
__in HANDLE hSemaphore,
__in LONG lReleaseCount,//信号量对象当前计数的增加量。该值必须大于零。
//如果指定的数量会导致信号量的计数超过创建信号量时指定的最大计数,则计数不会更改,函数返回FALSE。
__out_opt LPLONG lpPreviousCount//保存OldSignalState,若不需要之前的计数,此参数可以为NULL。
);

ReleaseSemaphore函数调用过程如下:

1
2
3
4
5
6
7
8
9
10
kernel32.dll
BOOL __stdcall ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount)
↓ ↓
ntdll.dll
__stdcall NtReleaseSemaphore(x, x, x) //【0BDh】
↓ ↓
ntkrpamp.exe
NTSTATUS __stdcall NtReleaseSemaphore(HANDLE SemaphoreHandle, LONG ReleaseCount, PLONG PreviousCount)
↓ ↓
LONG __stdcall KeReleaseSemaphore(PRKSEMAPHORE Semaphore, KPRIORITY Increment, LONG Adjustment, BOOLEAN Wait)

关于KeReleaseSemaphore的参数Wait:指定对KeReleaseSemaphore的调用后是否应立即调用KeWaitXxx例程之一。如果为真,KeReleaseSemaphore调用后必须调用KeWaitForMultipleObjects、KeWaitForMutexObject或KeWaitForSingleObject。

KeReleaseSemaphore例程可能会暂时提高IRQL。如果Wait参数为FALSE,则例程在返回之前,将IRQL恢复到调用开始时的原始值。

如果Wait = TRUE,则例程返回而不降低IRQL。在这种情况下,KeReleaseSemaphore调用后必须立即进行KeWaitXxx调用。通过设置Wait = TRUE,调用者可以防止KeReleaseSemaphore调用和KeWaitXxx调用之间发生不必要的上下文切换。KeWaitXxx例程在返回之前,在KeReleaseSemaphore调用开始时将IRQL恢复到原始值。虽然IRQL禁用了两个调用之间的上下文切换,但这些调用不能可靠地用作原子操作的开始和结束。例如,在这两个调用之间,在另一个处理器上同时运行的线程可能会更改信号量对象或等待目标的状态。

1.6 KeReleaseSemaphore

1
2
3
4
5
6
LONG __stdcall KeReleaseSemaphore(
IN PRKSEMAPHORE Semaphore, //信号量对象的指针
IN KPRIORITY Increment, //指定如果释放信号量导致等待得到满足,则要应用的优先级增量。
IN LONG Adjustment, //指定要添加到当前信号量计数的值。必须大于0
IN BOOLEAN Wait //指定对KeReleaseSemaphore的调用后是否应立即调用KeWaitXxx例程之一。
);

函数作用:

  1. 设置SignalState = SignalState + N(参数)
  1. 通过WaitListHead找到所有线程,并从等待链表中摘掉。

设置$NewSignalState = OldSignalState + N(参数)$

源代码如下(base\ntos\ke\semphobj.c):

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
LONG __stdcall KeReleaseSemaphore (
IN PRKSEMAPHORE Semaphore,
IN KPRIORITY Increment,
IN LONG Adjustment,
IN BOOLEAN Wait
)
{

LONG NewState;
KIRQL OldIrql;
LONG OldState;
PRKTHREAD Thread;

ASSERT_SEMAPHORE( Semaphore );
ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

// Raise IRQL to dispatcher level and lock dispatcher database.
KiLockDispatcherDatabase(&OldIrql);

OldState = Semaphore->Header.SignalState;
NewState = OldState + Adjustment;

if ((NewState > Semaphore->Limit) || (NewState < OldState)) {
KiUnlockDispatcherDatabase(OldIrql);
ExRaiseStatus(STATUS_SEMAPHORE_LIMIT_EXCEEDED);
}

//
// Set the new signal state of the semaphore object and set the wait
// next value. If the previous signal state was Not-Signaled (i.e.
// the count was zero), and the wait queue is not empty, then attempt
// to satisfy as many Waits as possible.
//

Semaphore->Header.SignalState = NewState;
if ((OldState == 0) && (IsListEmpty(&Semaphore->Header.WaitListHead) == FALSE)) {
KiWaitTest(Semaphore, Increment);
}

//OldState > 0 说明当前信号量对象已经被用,直接返回
if (Wait != FALSE) {
Thread = KeGetCurrentThread();
Thread->WaitNext = Wait;
Thread->WaitIrql = OldIrql;

} else {
KiUnlockDispatcherDatabase(OldIrql);
}

return OldState;
}

逆向分析代码如下:

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
.text:00425EFA ============== S U B R O U T I N E =======================================
.text:00425EFA
.text:00425EFA //Attributes: bp-based frame
.text:00425EFA
.text:00425EFA //LONG __stdcall KeReleaseSemaphore(PRKSEMAPHORE Semaphore, KPRIORITY Increment, LONG Adjustment,
//BOOLEAN Wait)
.text:00425EFA public _KeReleaseSemaphore@16
.text:00425EFA _KeReleaseSemaphore@16 proc near //CODE XREF: IoRaiseInformationalHardError(x,x,x)+205↑p
.text:00425EFA //MiCheckForControlAreaDeletion(x)+77↓p ...
.text:00425EFA
.text:00425EFA var_1 = byte ptr -1
.text:00425EFA Semaphore = dword ptr 8
.text:00425EFA Increment = dword ptr 0Ch
.text:00425EFA Adjustment = dword ptr 10h
.text:00425EFA Wait = byte ptr 14h
.text:00425EFA
.text:00425EFA mov edi, edi
.text:00425EFC push ebp
.text:00425EFD mov ebp, esp
.text:00425EFF push ecx
.text:00425F00 push ebx
.text:00425F01 push esi
.text:00425F02 push edi
.text:00425F03 xor ecx, ecx
.text:00425F05 call ds:__imp_@KeAcquireQueuedSpinLockRaiseToSynch@4
.text:00425F0B mov esi, [ebp+Semaphore]
.text:00425F0E mov ebx, [esi+_KSEMAPHORE.Header.SignalState] //ebx = OldSignalState
.text:00425F11 mov cl, al
.text:00425F13 mov eax, [ebp+Adjustment]
.text:00425F16 lea edi, [ebx+eax]
.text:00425F19 cmp edi, [esi+_KSEMAPHORE.Limit]
.text:00425F1C mov [ebp+var_1], cl //var_1 = 当前 IRQL
.text:00425F1F jg short loc_425F25 //--
.text:00425F1F //OldSignalState + Adjustment > Limit
.text:00425F1F //错误处理,返回
.text:00425F1F //--
.text:00425F21 cmp edi, ebx //--
.text:00425F21 //有可能加完之后溢出了,变得比加之前的SignalState还小
.text:00425F21 //则产生异常
.text:00425F21 //--
.text:00425F23 jge short loc_425F34 //正常处理业务
.text:00425F25
.text:00425F25 loc_425F25: //CODE XREF: KeReleaseSemaphore(x,x,x,x)+25↑j
.text:00425F25 call @KiUnlockDispatcherDatabase@4 //KiUnlockDispatcherDatabase(x)
.text:00425F2A push 0C0000047h //STATUS_SEMAPHORE_LIMIT_EXCEEDED
.text:00425F2F call _ExRaiseStatus@4 //ExRaiseStatus(x)
.text:00425F34 --------------------------------------------------------------------------
.text:00425F34
.text:00425F34 loc_425F34: //CODE XREF: KeReleaseSemaphore(x,x,x,x)+29↑j
.text:00425F34 test ebx, ebx //正常处理业务
.text:00425F36 mov [esi+_KSEMAPHORE.Header.SignalState], edi //edi = OldSignalState + Adjustment
.text:00425F39 jnz short loc_425F4C //OldSignalState != 0,说明当前信号量对象已经被用,直接返回
.text:00425F3B lea eax, [esi+_KSEMAPHORE.Header.WaitListHead.Flink] //--
.text:00425F3B //--
.text:00425F3B //OldSignalState == 0
.text:00425F3B //--
.text:00425F3E cmp [eax], eax //判断当前被等待对象是否有线程在等待
.text:00425F40 jz short loc_425F4C //还没有线程在等待该信号量对象,返回
.text:00425F42 mov edx, [ebp+Increment]
.text:00425F45 mov ecx, esi
.text:00425F47 call @KiWaitTest@8 //----
.text:00425F47 //循环地将在该等待对象上的所有线程从等待网上摘掉,并且从线程等待链上摘掉,信号
//值也进行处理
.text:00425F47 //ecx = 被等待对象
.text:00425F47 //edx = Increment
.text:00425F47 //SignalState == 1(Event),OldSignalState + Adjustment
.text:00425F47 //----
.text:00425F4C
.text:00425F4C loc_425F4C: //CODE XREF: KeReleaseSemaphore(x,x,x,x)+3F↑j
.text:00425F4C //KeReleaseSemaphore(x,x,x,x)+46↑j
.text:00425F4C mov cl, [ebp+Wait] //还没有线程在等待该信号量对象,返回
.text:00425F4F test cl, cl
.text:00425F51 jz short loc_425F64
.text:00425F53 mov eax, large fs:124h
.text:00425F59 mov [eax+_KTHREAD.WaitNext], cl
.text:00425F5C mov cl, [ebp+var_1]
.text:00425F5F mov [eax+_KTHREAD.WaitIrql], cl
.text:00425F62 jmp short loc_425F6C
.text:00425F64 --------------------------------------------------------------------------
.text:00425F64
.text:00425F64 loc_425F64: //CODE XREF: KeReleaseSemaphore(x,x,x,x)+57↑j
.text:00425F64 mov cl, [ebp+var_1]
.text:00425F67 call @KiUnlockDispatcherDatabase@4 //KiUnlockDispatcherDatabase(x)
.text:00425F6C
.text:00425F6C loc_425F6C: //CODE XREF: KeReleaseSemaphore(x,x,x,x)+68↑j
.text:00425F6C pop edi
.text:00425F6D pop esi
.text:00425F6E mov eax, ebx
.text:00425F70 pop ebx
.text:00425F71 leave
.text:00425F72 retn 10h
.text:00425F72 _KeReleaseSemaphore@16 endp

2 互斥体MUTANT

互斥体也是内核对象,结构如下:

1
2
3
4
5
6
7
8
kd> dt _KMUTANT
nt!_KMUTANT
+0x000 Header : _DISPATCHER_HEADER
+0x010 MutantListEntry : _LIST_ENTRY //拥有互斥体线程(KTHREAD+0x010 MutantListHead)是个链表头,
//此处圈着所有当前线程创建的互斥体对象+0x10
+0x018 OwnerThread : Ptr32 _KTHREAD //创建互斥体的线程
+0x01c Abandoned : UChar //是否已经被放弃不用
+0x01d ApcDisable : UChar //是否禁用内核APC
  • MutantListEntry:类似于_DISPATCHER_HEADER.WaitListHead与_KWAIT_BLOCK.WaitListEntry一样。链表的头是_KTHREAD.MutantListHead,_KMUTANT + 0x10 == _KTHREAD.MutantListHead.Flink。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
HANDLE CreateMutexA(
[in, optional] LPSECURITY_ATTRIBUTES lpMutexAttributes, //指向SECURITY_ATTRIBUTES结构的指针。
//如果此参数为NULL,则子进程无法继承句柄。
[in] BOOL bInitialOwner, //TRUE:该对象KMUTANT.OwnerThread指向当前创建者线程,未通知(SignalState = 0)
//FALSE:已通知(_DISPATCHER_HEADER.SignalState += 1)
[in, optional] LPCSTR lpName //互斥体对象的名称
);

kd> dt _DISPATCHER_HEADER
nt!_DISPATCHER_HEADER
+0x000 Type : UChar //2
+0x001 Absolute : UChar
+0x002 Size : UChar
+0x003 Inserted : UChar
+0x004 SignalState : Int4B //bInitialOwner == TRUE,SignalState == 0
//bInitialOwner == FALSE,SignalState == 1
+0x008 WaitListHead : _LIST_ENTRY

2.1 MutantListEntry

互斥体也是内核对象,结构如下:

1
2
3
4
5
6
7
8
kd> dt _KMUTANT
nt!_KMUTANT
+0x000 Header : _DISPATCHER_HEADER
+0x010 MutantListEntry : _LIST_ENTRY //拥有互斥体线程(KTHREAD+0x010 MutantListHead)是个链表头,
//此处圈着所有当前线程创建的互斥体对象+0x10
+0x018 OwnerThread : Ptr32 _KTHREAD //创建互斥体的线程
+0x01c Abandoned : UChar //是否已经被放弃不用
+0x01d ApcDisable : UChar //是否禁用内核APC
  • MutantListEntry:类似于_DISPATCHER_HEADER.WaitListHead与_KWAIT_BLOCK.WaitListEntry一样。

    链表的头是_KTHREAD.MutantListHead,_KMUTANT + 0x10 == _KTHREAD.MutantListHead.Flink。

为了验证:KMUTANT.MutantListEntry圈着所有当前线程创建的 互斥体对象+0x10,有如下代码:

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
#include "stdafx.h"
#include <windows.h>

HANDLE hThread[3] = {NULL};
HANDLE hMutant = NULL;
HANDLE hMutant_2 = NULL;
HANDLE hEvent = NULL;

DWORD WINAPI ThreadProc_1(LPVOID lpParameter)
{
hMutant = CreateMutex(NULL,TRUE,"TestMutant");
hMutant_2 = CreateMutex(NULL,TRUE,"TestMutant_2");
if(!hMutant)
{
printf("Failed to Create hMutant!\n");
return 0;
}
while(1)
{
continue;
}
printf("ThreadProc_1 END !\n");
return 0;
}

DWORD WINAPI ThreadProc_2(LPVOID lpParameter)
{
Sleep(500);
WaitForSingleObject(hMutant,-1);
WaitForSingleObject(hMutant_2,-1);
WaitForSingleObject(hMutant,-1);
printf("ThreadProc_2 END !\n");
while(1)
{
continue;
}
return 0;
}

DWORD WINAPI ThreadProc_3(LPVOID lpParameter)
{
Sleep(500);
WaitForSingleObject(hMutant,-1);
printf("ThreadProc_3 END !\n");
while(1)
{
continue;
}
return 0;
}

int main(int argc, char* argv[])
{
hThread[0] = CreateThread(NULL, 0, ThreadProc_1,NULL, 0, NULL);
hThread[1] = CreateThread(NULL, 0, ThreadProc_2,NULL, 0, NULL);
hThread[2] = CreateThread(NULL, 0, ThreadProc_3,NULL, 0, NULL);
getchar();
return 0;
}

如上代码,线程ThreadProc_1创建了两个互斥体,且bInitialOwner == TRUE,说明两个互斥体都属于当前线程。下面就通过对比线程ThreadProc_1和ThreadProc_2,说明KMUTANT.MutantListEntry圈着所有当前线程创建的 互斥体对象+0x10。

  1. 找到对应的线程。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    kd> !process 0 0
    **** NT ACTIVE PROCESS DUMP ****
    PROCESS 860a0788 SessionId: 0 Cid: 0d64 Peb: 7ffde000 ParentCid: 0a10
    DirBase: 06e403e0 ObjectTable: e281f858 HandleCount: 18.
    Image: 220518_Mutant.exe

    kd> !process 0x860a0788
    PROCESS 860a0788 SessionId: 0 Cid: 0d64 Peb: 7ffde000 ParentCid: 0a10
    DirBase: 06e403e0 ObjectTable: e281f858 HandleCount: 18.
    Image: 220518_Mutant.exe

    //主线程
    THREAD 863a85b0 Cid 0d64.0d68 Teb: 7ffdd000 Win32Thread: 00000000 WAIT: (WrLpcReply) UserMode Non-Alertable

    //ThreadProc_1
    THREAD 860e0790 Cid 0d64.0d6c Teb: 7ffdc000 Win32Thread: 00000000 RUNNING on processor 0

    //ThreadProc_2
    THREAD 8617e790 Cid 0d64.0d70 Teb: 7ffdb000 Win32Thread: 00000000 WAIT: (UserRequest) UserMode Non-Alertable

    //ThreadProc_3
    THREAD 8639b790 Cid 0d64.0d74 Teb: 7ffda000 Win32Thread: 00000000 WAIT: (UserRequest) UserMode Non-Alertable
    ...
  2. 分析线程ThreadProc_1也是互斥体的拥有者。

    1
    2
    3
    4
    5
    kd> dt _KTHREAD 0x860e0790
    ntdll!_KTHREAD
    +0x000 Header : _DISPATCHER_HEADER
    +0x010 MutantListHead : _LIST_ENTRY [ 0x862f6ef0 - 0x86271e40 ]
    ...

    这里需要注意的是,MutantListHead在_KTHREAD + 0x10的位置,MutantListEntry也在_KMUTANT + 0x10的位置。

    这里是链表头。

  3. 开始分析。

    第一轮循环:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    PLIST_ENTRY ListHead;
    PLIST_ENTRY ListEntry;
    PVOID Object;

    ListHead = &_KTHREAD->MutantListHead; //ListHead == 0x860e0790 + 0x10 == 0x860e07a0
    ListEntry = ListHead->Flink; //ListEntry == 0x862f6ef0
    while(ListHead != ListEntry) //0x860e07a0 != 0x862f6ef0
    {
    Object = ListEntry - 0x10; //Object == 0x862f6ef0 - 0x10 = 0x862f6ee0
    ListEntry = ListEntry->Flink;
    }

    然后看一下Object == 0x862f6ee0是什么?

    1
    2
    3
    4
    5
    6
    7
    8
    kd> dt _DISPATCHER_HEADER 0x862f6ee0
    ntdll!_DISPATCHER_HEADER
    +0x000 Type : 0x2 '' //互斥体 MutantObject
    +0x001 Absolute : 0 ''
    +0x002 Size : 0x8 ''
    +0x003 Inserted : 0 ''
    +0x004 SignalState : 0n0 //无信号
    +0x008 WaitListHead : _LIST_ENTRY [ 0x8617e800 - 0x8639b800 ]

    第二轮循环:

    继续往下分析ListEntry->Flink:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ListEntry == 0x862f6ef0	//ListHead == 0x860e07a0

    kd> dd 0x862f6ef0
    862f6ef0 86271e40 860e07a0 860e0790 00000000

    ListEntry = ListEntry->Flink == 0x86271e40;
    while(ListHead != ListEntry) //0x860e07a0 != 0x86271e40
    {
    Object = ListEntry - 0x10; //Object == 0x86271e40 - 0x10 = 0x86271e30
    ListEntry = ListEntry->Flink;
    }

    然后看一下Object == 0x86271e30:

    1
    2
    3
    4
    5
    6
    7
    8
    kd> dt _DISPATCHER_HEADER 0x86271e30
    ntdll!_DISPATCHER_HEADER
    +0x000 Type : 0x2 '' //互斥体 MutantObject
    +0x001 Absolute : 0 ''
    +0x002 Size : 0x8 ''
    +0x003 Inserted : 0 ''
    +0x004 SignalState : 0n0 //无信号
    +0x008 WaitListHead : _LIST_ENTRY [ 0x86271e38 - 0x86271e38 ]

    第三轮循环:

    继续往下分析ListEntry->Flink:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ListEntry == 0x86271e40	//ListHead == 0x860e07a0

    kd> dd 0x86271e40
    86271e40 860e07a0 862f6ef0 860e0790 00000000

    WaitEntry = ListEntry->Flink == 0x860e07a0;
    while(ListHead != ListEntry) //0x860e07a0 == 0x860e07a0
    {
    Object = ListEntry - 0x10; //Object == 0x860e07a0 - 0x10 = 0x860e0790
    ListEntry = ListEntry->Flink;
    }

    然后看一下Object == 0x860e0790:

    1
    2
    3
    4
    5
    6
    7
    8
    kd> dt _DISPATCHER_HEADER 0x860e0790
    ntdll!_DISPATCHER_HEADER
    +0x000 Type : 0x6 '' //线程 ThreadObject
    +0x001 Absolute : 0 ''
    +0x002 Size : 0x70 'p'
    +0x003 Inserted : 0 ''
    +0x004 SignalState : 0n0
    +0x008 WaitListHead : _LIST_ENTRY [ 0x860e0798 - 0x860e0798 ]

    此轮循环不会执行,因为ListHead == ListEntry == 0x860e07a0。

  4. 分析线程ThreadProc_2。

    1
    2
    3
    4
    5
    kd> dt _KTHREAD 0x8617e790
    ntdll!_KTHREAD
    +0x000 Header : _DISPATCHER_HEADER
    +0x010 MutantListHead : _LIST_ENTRY [ 0x8617e7a0 - 0x8617e7a0 ]
    ...

    可以看到,ListHead == 0x8617e790 + 0x10 == 0x8617e7a0 == ListHead->Flink,说明ThreadProc_2线程没有创建属于自己的互斥体,尽管自己在等待两个内核对象。

完整过程如下:

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
ThreadProc_1
THREAD 860e0790

+0x010 MutantListHead : _LIST_ENTRY [ 0x862f6ef0 - 0x86271e40 ]

kd> dt _DISPATCHER_HEADER 0x862f6ef0-0x10
ntdll!_DISPATCHER_HEADER
+0x000 Type : 0x2 '' //MutantObject
+0x001 Absolute : 0 ''
+0x002 Size : 0x8 ''
+0x003 Inserted : 0 ''
+0x004 SignalState : 0n0
+0x008 WaitListHead : _LIST_ENTRY [ 0x8617e800 - 0x8639b800 ]

kd> dd 0x862f6ef0
862f6ef0 86271e40 860e07a0 860e0790 00000000

kd> dt _DISPATCHER_HEADER 86271e40-0x10
ntdll!_DISPATCHER_HEADER
+0x000 Type : 0x2 '' //MutantObject
+0x001 Absolute : 0 ''
+0x002 Size : 0x8 ''
+0x003 Inserted : 0 ''
+0x004 SignalState : 0n0
+0x008 WaitListHead : _LIST_ENTRY [ 0x86271e38 - 0x86271e38 ]

kd> dd 86271e40
86271e40 860e07a0 862f6ef0 860e0790 00000000
86271e50 1a07000a 86282020 00300012 00000000

kd> dt _DISPATCHER_HEADER 860e07a0-0x10 //链表头
ntdll!_DISPATCHER_HEADER
+0x000 Type : 0x6 '' //ThreadObject
+0x001 Absolute : 0 ''
+0x002 Size : 0x70 'p'
+0x003 Inserted : 0 ''
+0x004 SignalState : 0n0
+0x008 WaitListHead : _LIST_ENTRY [ 0x860e0798 - 0x860e0798 ]

2.2 互斥体和其他内核对象差异

互斥体(MUTANT)与事件(EVENT)和信号量(SEMAPHORE)的相似与区别:

  • 相同点:都是内核对象,都可以用来进行线程的同步控制。
  • 区别:可以用来解决事件与信号量做不到的事,如防止线程突然终止造成的死锁、线程临界区重入(多次进入)等。

一、互斥体解决线程突然终止造成死锁的问题

有如下伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
A进程的 X 线程
{
WaitForSingleObject(被等待对象ObjectM,INFINITE);
临界区
SetEvent(ObjectM)/ReleaseSemaphore(ObjectM,1,NULL)/ReleaseMutex(ObjectM);
}

B进程的 Y 线程
{
WaitForSingleObject(被等待对象ObjectM,INFINITE);
临界区
SetEvent(ObjectM)/ReleaseSemaphore(ObjectM,1,NULL)/ReleaseMutex(ObjectM);
}
  • 事件/信号量:如果 A 进程的 X 线程还没有来得及调用修改SignalState的函数(如SetEvent)线程就突然终止,那么被等待对象ObjectM将被遗弃,这也就意味着 Y 线程将永远等下去直到进程 A 结束系统对其资源释放!
  • 互斥体:由于互斥体有成员KMUTANT.Abandoned,当线程意外终止时,系统会修改SignalState。

二、互斥体解决线程重复进入临界区的问题

有如下代码:

1
2
3
4
5
6
7
8
WaitForSingleObject(A)
{
.....
WaitForMultipleObjects(3,ABC,TRUE,-1);
.....
SetEvent/ReleaseSemaphore/ReleaseMutex;
}
SetEvent/ReleaseSemaphore/ReleaseMutex;
  • 事件/信号量:当线程执行到WaitForMultipleObjects时对象A一直没有得到信号,则会一直等待,就造成了死锁。
  • 互斥体:当线程执行到WaitForMultipleObjects时对象A不管信号<=0还是>0,都会继续执行,主要是利用KMUTANT.OwnerThread成员。

2.3 实验:互斥体的不同

R3函数CreateMutexA创建或打开一个命名或未命名的互斥对象。

1
2
3
4
5
6
7
HANDLE CreateMutexA(
[in, optional] LPSECURITY_ATTRIBUTES lpMutexAttributes, //指向SECURITY_ATTRIBUTES结构的指针。
//如果此参数为NULL,则子进程无法继承句柄。
[in] BOOL bInitialOwner, //TRUE:该对象KMUTANT.OwnerThread指向当前创建者线程,未通知(SignalState = 0)
//FALSE:已通知(_DISPATCHER_HEADER.SignalState += 1)
[in, optional] LPCSTR lpName //互斥体对象的名称
);

先举例说明一下:

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
#include "stdafx.h"
#include <windows.h>

HANDLE hThread[3] = {NULL};
HANDLE hMutant = NULL;
HANDLE hEvent = NULL;

DWORD WINAPI ThreadProc_1(LPVOID lpParameter)
{
hMutant = CreateMutex(NULL,TRUE,"TestMutant");
//hEvent = CreateEvent(NULL,FALSE,TRUE,NULL);
if(!hMutant)
{
printf("Failed to Create hMutant!\n");
return 0;
}
/*
while(1)
{
continue;
}
*/
printf("ThreadProc_1 END !\n");
return 0;
}

DWORD WINAPI ThreadProc_2(LPVOID lpParameter)
{
Sleep(500);
WaitForSingleObject(hMutant,-1);
//WaitForSingleObject(hEvent,-1);
printf("ThreadProc_2 END !\n");
while(1)
{
continue;
}
return 0;
}

int main(int argc, char* argv[])
{
hThread[0] = CreateThread(NULL, 0, ThreadProc_1,NULL, 0, NULL);
hThread[1] = CreateThread(NULL, 0, ThreadProc_2,NULL, 0, NULL);

//WaitForMultipleObjects(3,ABC,TRUE,-1);

getchar();
return 0;
}
  1. 情况一:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    ThreadProc_1
    {
    hMutant = CreateMutex(NULL,TRUE,"TestMutant"); //TRUE:hMutant.SignalState = 0
    while(1)
    {
    continue;
    }
    printf("ThreadProc_1 END !\n");
    }

    ThreadProc_2
    {
    WaitForSingleObject(hMutant,-1);
    printf("ThreadProc_2 END !\n");
    while(1)
    {
    continue;
    }
    }

    控制台输出:无输出。

  2. 情况二:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    ThreadProc_1
    {
    hMutant = CreateMutex(NULL,FALSE,"TestMutant"); //FALSE:hMutant.SignalState += 1
    while(1)
    {
    continue;
    }
    printf("ThreadProc_1 END !\n");
    }

    ThreadProc_2
    {
    WaitForSingleObject(hMutant,-1);
    printf("ThreadProc_2 END !\n");
    while(1)
    {
    continue;
    }
    }

    控制台输出:

    • ThreadProc_2 END !

    这个时候在Windbg里面查看ThreadProc_2线程等待的互斥体对象hMutant.OwnerThread == ThreadProc_1。

  3. 情况三:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    ThreadProc_1
    {
    hMutant = CreateMutex(NULL,TRUE,"TestMutant"); //FALSE:hMutant.SignalState = 0
    printf("ThreadProc_1 END !\n");
    }

    ThreadProc_2
    {
    WaitForSingleObject(hMutant,-1);
    printf("ThreadProc_2 END !\n");
    while(1)
    {
    continue;
    }
    }

    控制台输出:

    • ThreadProc_1 END !

    • ThreadProc_2 END !

      这里为什么会输出ThreadProc_2 END !?就是因为线程ThreadProc_1终止了之后,由于互斥体有成员KMUTANT.Abandoned,当线程终止时,系统会修改SignalState。

  4. 情况四:为了对比情况三。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    ThreadProc_1
    {
    hEvent = CreateEvent(NULL,FALSE,FALSE,NULL); //参数三FALSE:hEventt.SignalState = 0
    printf("ThreadProc_1 END !\n");
    }

    ThreadProc_2
    {
    WaitForSingleObject(hEvent,-1);
    printf("ThreadProc_2 END !\n");
    while(1)
    {
    continue;
    }
    }

    控制台输出:

    • ThreadProc_1 END !

    这里没有输出ThreadProc_2 END !就是因为线程ThreadProc_1终止了之后,系统并不会修改事件的SignalState,ThreadProc_2会一直等下去。

  5. 情况五:为了对比情况一,也为了说明KMUTANT.OwnerThread允许重入的意义。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    ThreadProc_1
    {
    hMutant = CreateMutex(NULL,TRUE,"TestMutant"); //TRUE:hMutant.SignalState = 0
    WaitForSingleObject(hMutant,-1);
    printf("ThreadProc_1 END !\n");

    while(1)
    {
    continue;
    }
    }

    ThreadProc_2
    {
    WaitForSingleObject(hMutant,-1);
    printf("ThreadProc_2 END !\n");
    while(1)
    {
    continue;
    }
    }

    控制台输出:

    • ThreadProc_1 END !

    由于KMUTANT.OwnerThread的存在,即使hMutant没有信号,但是KMUTANT.OwnerThread == ThreadProc_1会继续执行,也就是允许重入,后面会降到为什么,答案在函数KeWaitForSingleObject里面。

2.4 解决线程重入

互斥体可解决解决线程重入临界区的问题(重入的效果可参见2.3实验情况五。)

互斥体用OwnerThread成员来解决线程重入的问题。

1
2
3
4
5
6
7
8
kd> dt _KMUTANT
nt!_KMUTANT
+0x000 Header : _DISPATCHER_HEADER
+0x010 MutantListEntry : _LIST_ENTRY //拥有互斥体线程(KTHREAD+0x010 MutantListHead)是个链表头,
//此处圈着所有当前线程创建的互斥体对象+0x10
+0x018 OwnerThread : Ptr32 _KTHREAD //创建互斥体的线程
+0x01c Abandoned : UChar //是否已经被放弃不用
+0x01d ApcDisable : UChar //是否禁用内核APC

创建互斥体的3环函数A型为:

1
2
3
4
5
6
7
HANDLE CreateMutexA(
[in, optional] LPSECURITY_ATTRIBUTES lpMutexAttributes, //指向SECURITY_ATTRIBUTES结构的指针。
//如果此参数为NULL,则子进程无法继承句柄。
[in] BOOL bInitialOwner, //TRUE:该对象KMUTANT.OwnerThread指向当前创建者线程,未通知(SignalState = 0)
//FALSE:已通知(_DISPATCHER_HEADER.SignalState += 1)
[in, optional] LPCSTR lpName //互斥体对象的名称
);
  • bInitialOwner == TRUE,OwnerThread指向创建者线程的_KTHREAD。
  • bInitialOwner == FALSE,OwnerThread == NULL。

下面来分析一下函数KeWaitForSingleObject即可知道怎么解决重入问题:

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
.text:00423F61 loc_423F61:                             
.text:00423F61
.text:00423F61 cmp [ebx+_KMUTANT.Header.Type], 2
.text:00423F61 //判断是否是互斥体
.text:00423F61 //2:表示互斥体,即_DISPATCHER_HEADER.Type == 2 == MutantObject
.text:00423F61 //--
.text:00423F64 jnz short loc_423F94 //跳转:不是互斥体
.text:00423F66 mov eax, [ebx+_KMUTANT.Header.SignalState] //是互斥体则继续判断
.text:00423F69 test eax, eax
.text:00423F6B jg short loc_423F72 //跳转:有信号(被激活)
.text:00423F6D cmp esi, [ebx+_KMUTANT.OwnerThread] //esi == CurrentThread
.text:00423F6D //SignalState <= 0
.text:00423F6D //这里已经确定Object为Mutant
.text:00423F6D //对应的结构体为:_KMUTANT
.text:00423F6D //--
.text:00423F70 jnz short loc_423F9E //OwnerThread != CurrentThread,去处理 TestForAlertPending(Alertable)
.text:00423F72
.text:00423F72 loc_423F72:
.text:00423F72 cmp eax, 80000000h //SignalState > 0 || OwnerThread == CurrentThread
.text:00423F77 jnz loc_424121
.text:00423F7D mov cl, [esi+_KTHREAD.WaitIrql]
.text:00423F80 call @KiUnlockDispatcherDatabase@4 //KiUnlockDispatcherDatabase(x)
.text:00423F85 push 0C0000191h //STATUS_MUTANT_LIMIT_EXCEEDED
.text:00423F8A call _ExRaiseStatus@4 //ExRaiseStatus(x)
.text:00423F8F ---------------------------------------------------------------------------
.text:00424121 loc_424121: ; CODE XREF: KeWaitForSingleObject(x,x,x,x,x)+D1↑j
.text:00424121 dec [ebx+_KMUTANT.Header.SignalState]
.text:00424124 jnz short loc_424156
.text:00424126 movzx eax, [ebx+_KMUTANT.ApcDisable]
.text:0042412A sub [esi+_KTHREAD.KernelApcDisable], eax
.text:00424130 cmp [ebx+_KMUTANT.Abandoned], 1
.text:00424134 mov [ebx+_KMUTANT.OwnerThread], esi
.text:00424137 jnz short loc_424144
.text:00424139 mov [ebx+_KMUTANT.Abandoned], 0
.text:0042413D mov [esi+_KTHREAD.WaitStatus], 80h

可以发现:

1
2
SignalState > 0,               jg  short loc_423F72
OwnerThread == CurrentThread, jmp short loc_423F72

可以看到当互斥体有信号,和OwnerThread == CurrentThread处理逻辑一样,都是去让SignalState--。

可以重入的次数为0x80000000次。

2.5 解决线程终止造成死锁

解决线程终止造成死锁的问题

如2.3实验情况三,当创建互斥体的线程终止后,系统会将其创建的互斥体对象设置为有信号,直到进程结束。主要就是利用互斥体结构的Abandoned成员。

1
2
3
4
5
6
7
8
kd> dt _KMUTANT
nt!_KMUTANT
+0x000 Header : _DISPATCHER_HEADER
+0x010 MutantListEntry : _LIST_ENTRY //拥有互斥体线程(KTHREAD+0x010 MutantListHead)是个链表头,
//此处圈着所有当前线程创建的互斥体对象+0x10
+0x018 OwnerThread : Ptr32 _KTHREAD //创建互斥体的线程
+0x01c Abandoned : UChar //是否已经被放弃不用
+0x01d ApcDisable : UChar //是否禁用内核APC

释放互斥体信号的函数是ReleaseMutex,调用流程如下:

1
2
3
BOOL WINAPI ReleaseMutex(HANDLE hMutex);

ReleaseMutex --> NtReleaseMutant(内核函数) --> KeReleaseMutant(内核函数)

当互斥体的创建线程终止后,系统会调用MmUnloadSystemImage --> KeReleaseMutant(X,Y,Abandon == TRUE,Z)函数然后来释放信号。

KeReleaseMutant函数过程大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
KeReleaseMutant(X,Y,Abandon,Z)
{
if(Abandon == false) //正常调用
{
MUTANT.Header.SignalState++;
}
else //Abandon == TRUE
{
MUTANT.Header.SignalState == 1;
MUTANT.OwnerThread == NULL;
}

if(MUTANT.Header.SignalState == 1)
MUTANT.OwnerThread == NULL;
从当前线程互斥体链表中将当前互斥体移除
}

当Abandon == TRUE时,直接将SignalState == 1而不是++,然后OwnerThread == NULL,很干脆。

2.6 ApcDisable

互斥体结构如下:

1
2
3
4
5
6
7
8
kd> dt _KMUTANT
nt!_KMUTANT
+0x000 Header : _DISPATCHER_HEADER
+0x010 MutantListEntry : _LIST_ENTRY //拥有互斥体线程(KTHREAD+0x010 MutantListHead)是个链表头,
//此处圈着所有当前线程创建的互斥体对象+0x10
+0x018 OwnerThread : Ptr32 _KTHREAD //创建互斥体的线程
+0x01c Abandoned : UChar //是否已经被放弃不用
+0x01d ApcDisable : UChar //是否禁用内核APC

互斥体可以在3环调用创建,也可以在0环调用创建,但是有不同:

  • 3环调用创建:ApcDisable == 0。
  • 0环调用创建:ApcDisable == 1。
  1. 在3环创建调用路径:Mutant -> NtCreateMutant -> KeInitializeMutant(x, x),初始化时ApcDisable == 0,如下:

    1
    2
    .text:00425845                 mov     [esi+_KMUTANT.Abandoned], 0
    .text:00425849 mov [esi+_KMUTANT.ApcDisable], 0
  2. 在0环创建调用:Mutex ->.. KeInitializeMutex,初始化时ApcDisable == 1,如下:

    1
    2
    3
    .text:00425876                 and     [eax+_KMUTANT.OwnerThread], 0
    .text:0042587A mov [eax+_KMUTANT.Abandoned], 0
    .text:0042587E mov [eax+_KMUTANT.ApcDisable], 1

该成员KMUTANT.ApcDisable和线程的KTHREAD.KernelApcDisable(+0x0d4)配合使用。

KTHREAD.KernelApcDisable(+0x0d4)说明如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//Win XP SP1中如下
typedef struct _KTHREAD
{
...
ULONG KernelApcDisable;//+0xd4
...
} KTHREAD, *PKTHREAD, *RESTRICTED_POINTER PRKTHREAD;

//WRK中
#if !defined(_AMD64_) //32位
ULONG WaitTime;
union {
struct {
SHORT KernelApcDisable;
SHORT SpecialApcDisable;
};

ULONG CombinedApcDisable;
};
#endif
  • 源码:0 表示不禁止APC,负数表示禁止APC,正值表示驱动程序已启用特殊或普通 Apc 多次,KernelApcDisable为0 的时候,该线程才允许插入或提交APC。
  • WRK:KernelApcDisable和SpecialApcDisable域,或者合并成一个CombinedApcDisable域。KernelApcDisable 和SpecialApcDisable 都是16 位的整数值,0 表示不禁止APC,负数表示禁止APC,正值表示驱动程序已启用特殊或普通 Apc 多次。一个线程在执行过程中可以有多种因素要禁止APC,这些因素以负值来表示,并累加起来,当因素消除的时候再减去相应的负值。只有当KernelApcDisable或SpecialApcDisable为0 的时候,该线程才允许插入或提交APC。这两个值分别控制普通的内核APC 和特殊的内核APC,关于这两种内核模式APC 请参考《Windows内核原理与实现》5.2.6 节。

逆向分析KeWaitForSingleObject函数有关代码如下:

1
2
3
4
5
.text:00424121 loc_424121:                             
...
.text:00424126 movzx eax, [ebx+_KMUTANT.ApcDisable]
.text:0042412A sub [esi+_KTHREAD.KernelApcDisable], eax
...

KernelApcDisable != 0就禁用内核APC。

2.7 CreateMutexA

R3函数CreateMutexA创建或打开一个命名或未命名的互斥对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
HANDLE CreateMutexA(
[in, optional] LPSECURITY_ATTRIBUTES lpMutexAttributes, //指向SECURITY_ATTRIBUTES结构的指针。
//如果此参数为NULL,则子进程无法继承句柄。
[in] BOOL bInitialOwner, //TRUE:该对象KMUTANT.OwnerThread指向当前创建者线程,未通知(SignalState = 0)
//FALSE:已通知(_DISPATCHER_HEADER.SignalState += 1)
[in, optional] LPCSTR lpName //互斥体对象的名称
);

kd> dt _DISPATCHER_HEADER
nt!_DISPATCHER_HEADER
+0x000 Type : UChar //2
+0x001 Absolute : UChar
+0x002 Size : UChar
+0x003 Inserted : UChar
+0x004 SignalState : Int4B //bInitialOwner == TRUE,SignalState == 0
//bInitialOwner == FALSE,SignalState == 1
+0x008 WaitListHead : _LIST_ENTRY

调用过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#define NTAPI __stdcall	//ntdef.h
HANDLE CreateMutexA(
[in, optional] LPSECURITY_ATTRIBUTES lpMutexAttributes, //指向SECURITY_ATTRIBUTES结构的指针。
//如果此参数为NULL,则子进程无法继承句柄。
[in] BOOL bInitialOwner, //TRUE:该对象KMUTANT.OwnerThread指向当前创建者线程,未通知(SignalState = 0)
//FALSE:已通知(_DISPATCHER_HEADER.SignalState += 1)
[in, optional] LPCSTR lpName //互斥体对象的名称
);
↓ ↓
HANDLE __stdcall CreateMutexW(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCWSTR lpName)
↓ ↓
ntdll.dll
typedef NTSTATUS (NTAPI *pZwCreateMutant)(OUT PHANDLE MutantHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES
ObjectAttributes OPTIONAL, IN BOOLEAN InitialOwner);//2Bh
↓ ↓
ntkrpamp.exe
NTSTATUS __stdcall NtCreateMutant(OUT PHANDLE MutantHandle, IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, IN BOOLEAN InitialOwner)
↓ ↓
VOID __stdcall KeInitializeMutant(IN PRKMUTANT Mutant, IN BOOLEAN InitialOwner)

//0环创建互斥体最终调用 KeInitializeMutex
VOID __stdcall KeInitializeMutex(IN PRKMUTANT Mutant, IN ULONG Level)
//初始化互斥对象,将其设置为信号状态。Level:保留,驱动程序将此设置为零。

2.8 NtCreateMutant

源代码如下:

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
NTSTATUS NtCreateMutant (
OUT PHANDLE MutantHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN BOOLEAN InitialOwner
)
{
HANDLE Handle;
PVOID Mutant;
KPROCESSOR_MODE PreviousMode;
NTSTATUS Status;

try {
PreviousMode = KeGetPreviousMode();
if (PreviousMode != KernelMode) {
ProbeForWriteHandle(MutantHandle);
}
Status = ObCreateObject(PreviousMode,
ExMutantObjectType,
ObjectAttributes,
PreviousMode,
NULL,
sizeof(KMUTANT),
0,
0,
(PVOID *)&Mutant);

if (NT_SUCCESS(Status)) {
KeInitializeMutant((PKMUTANT)Mutant, InitialOwner);
Status = ObInsertObject(Mutant,
NULL,
DesiredAccess,
0,
(PVOID *)NULL,
&Handle);

// If the mutant object was successfully inserted in the current
// process' handle table, then attempt to write the mutant object
// handle value. If the write attempt fails, then do not report
// an error. When the caller attempts to access the handle value,
// an access violation will occur.

if (NT_SUCCESS(Status)) {
try {
*MutantHandle = Handle;

} except(ExSystemExceptionFilter()) {
}
}
}

// If an exception occurs during the probe of the output handle address,
// then always handle the exception and return the exception code as the
// status value.

} except(ExSystemExceptionFilter()) {
return GetExceptionCode();
}
return Status;
}

2.9 KeInitializeMutant

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
.text:004257E8 ; =============== S U B R O U T I N E =======================================
.text:004257E8
.text:004257E8 ; Attributes: bp-based frame
.text:004257E8
.text:004257E8 ; __stdcall KeInitializeMutant(x, x)
.text:004257E8 public _KeInitializeMutant@8
.text:004257E8 _KeInitializeMutant@8 proc near ; CODE XREF: NtCreateMutant(x,x,x,x)+6D↓p
.text:004257E8 ; VerifierKeInitializeMutant(x,x)+C↓p ...
.text:004257E8
.text:004257E8 arg_0 = dword ptr 8
.text:004257E8 arg_4 = byte ptr 0Ch
.text:004257E8
.text:004257E8 mov edi, edi
.text:004257EA push ebp
.text:004257EB mov ebp, esp
.text:004257ED xor eax, eax
.text:004257EF push esi
.text:004257F0 mov esi, [ebp+arg_0] ; esi == KMUTANT
.text:004257F3 inc eax
.text:004257F4 cmp [ebp+arg_4], al ; arg_4 == InitialOwner
.text:004257F7 mov [esi+_KMUTANT.Header.Type], 2
.text:004257FA mov [esi+_KMUTANT.Header.Size], 8
.text:004257FE jnz short loc_425836 ; --
.text:004257FE ; InitialOwner == FALSE
.text:004257FE ; SignalState = 1
.text:004257FE ; --
.text:00425800 push ebx
.text:00425801 push edi
.text:00425802 mov eax, large fs:124h
.text:00425808 and [esi+_KMUTANT.Header.SignalState], 0 ; SignalState == 0
.text:0042580C mov edi, eax
.text:0042580E xor ecx, ecx
.text:00425810 mov [esi+_KMUTANT.OwnerThread], edi ; OwnerThread == CurrentThread
.text:00425813 call ds:__imp_@KeAcquireQueuedSpinLockRaiseToSynch@4 ;
.text:00425819 mov edi, [edi+_KTHREAD.MutantListHead.Blink] ; --
.text:00425819 ; 将当前的互斥体插入线程互斥链表头。
.text:00425819 ; 注意此处是BBBBlink
.text:00425819 ; edi == 当前线程互斥体链尾
.text:00425819 ; edi就是旧链表尾(ListHead->Blink),头的上一个
.text:00425819 ; --
.text:0042581C mov ebx, [edi+_LIST_ENTRY.Flink] ; ebx == 旧链表头
.text:0042581E lea edx, [esi+_KMUTANT.MutantListEntry.Flink] ; edx = &MutantListEntry.Flink
.text:00425821 mov [edx+_LIST_ENTRY.Flink], ebx ; MutantListEntry.Flink = 旧链表头
.text:00425823 mov [edx+_LIST_ENTRY.Blink], edi
.text:00425826 mov [ebx+_LIST_ENTRY.Blink], edx
.text:00425829 mov cl, al
.text:0042582B mov [edi+_LIST_ENTRY.Flink], edx
.text:0042582D call @KiUnlockDispatcherDatabase@4 ; KiUnlockDispatcherDatabase(x)
.text:00425832 pop edi
.text:00425833 pop ebx
.text:00425834 jmp short loc_42583D
.text:00425836 ; ---------------------------------------------------------------------------
.text:00425836
.text:00425836 loc_425836: ; CODE XREF: KeInitializeMutant(x,x)+16↑j
.text:00425836 and [esi+_KMUTANT.OwnerThread], 0 ; --
.text:00425836 ; InitialOwner == FALSE
.text:00425836 ; SignalState = 1
.text:00425836 ; --
.text:0042583A mov [esi+_KMUTANT.Header.SignalState], eax
.text:0042583D
.text:0042583D loc_42583D: ; CODE XREF: KeInitializeMutant(x,x)+4C↑j
.text:0042583D lea eax, [esi+_KMUTANT.Header.WaitListHead.Flink]
.text:00425840 mov [eax+_KWAIT_BLOCK.WaitListEntry.Blink], eax
.text:00425843 mov [eax+_KWAIT_BLOCK.WaitListEntry.Flink], eax
.text:00425845 mov [esi+_KMUTANT.Abandoned], 0
.text:00425849 mov [esi+_KMUTANT.ApcDisable], 0
.text:0042584D pop esi
.text:0042584E pop ebp
.text:0042584F retn 8
.text:0042584F _KeInitializeMutant@8 endp

2.a KeInitializeMutex

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
.text:00425858 ; =============== S U B R O U T I N E =======================================
.text:00425858
.text:00425858 ; Attributes: bp-based frame
.text:00425858
.text:00425858 ; void __stdcall KeInitializeMutex(PRKMUTEX Mutex, ULONG Level)
.text:00425858 public _KeInitializeMutex@8
.text:00425858 _KeInitializeMutex@8 proc near ; CODE XREF: PpInitializeNotification()+35↓p
.text:00425858 ; RawInitializeVcb(x,x,x)+36↓p ...
.text:00425858
.text:00425858 Mutex = dword ptr 8
.text:00425858 Level = dword ptr 0Ch
.text:00425858
.text:00425858 mov edi, edi
.text:0042585A push ebp
.text:0042585B mov ebp, esp
.text:0042585D mov eax, [ebp+Mutex]
.text:00425860 lea ecx, [eax+_KMUTANT.Header.WaitListHead.Flink]
.text:00425863 mov [eax+_KMUTANT.Header.Type], 2
.text:00425866 mov [eax+_KMUTANT.Header.Size], 8
.text:0042586A mov [eax+_KMUTANT.Header.SignalState], 1
.text:00425871 mov [ecx+_LIST_ENTRY.Blink], ecx
.text:00425874 mov [ecx+_LIST_ENTRY.Flink], ecx
.text:00425876 and [eax+_KMUTANT.OwnerThread], 0
.text:0042587A mov [eax+_KMUTANT.Abandoned], 0
.text:0042587E mov [eax+_KMUTANT.ApcDisable], 1
.text:00425882 pop ebp
.text:00425883 retn 8
.text:00425883 _KeInitializeMutex@8 endp

2.b ReleaseMutex

1
2
3
4
5
BOOL __stdcall ReleaseMutex( [in] HANDLE hMutex )
↓ ↓
NTSYSAPI NTSTATUS NTAPI NtReleaseMutant(IN HANDLE MutantHandle, OUT PULONG PreviousState)
↓ ↓
LONG __stdcall KeReleaseMutant(IN PRKMUTANT Mutant,IN KPRIORITY Increment,IN BOOLEAN Abandoned,IN BOOLEAN Wait)

2.c KeReleaseMutant

  1. 从逆向及实际测试效果来看,谁WaitForSingleObject到Mutant,就会修改Mutant->OwnerThread为自己(然后线程可以进行重入了)。
  2. 每次调用KeReleaseMutant当SignalState == 1,就修改Mutant->OwnerThread = NULL。
  3. 如果创建Mutant时bInitialOwner == TRUE,则谁创建了该互斥体,谁调用ReleaseMutex才会起作用,其他Mutant->OwnerThread != CurrentThread的线程调用了没用。引申到:只有Mutant->OwnerThread == CurrentThread的线程调用ReleaseMutex才能起作用(实际代码测试过的)。

详细说明如下(经过实际代码测试):

  1. 从逆向及实际测试效果来看,谁WaitForSingleObject到Mutant,就会修改Mutant->OwnerThread为自己(然后线程可以进行重入了)。有如下代码进行说明:

    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
    KeWaitForSingleObject
    {
    if (Objectx->Header.Type == MutantObject)
    {
    if ((Objectx->Header.SignalState > 0) || (Thread == Objectx->OwnerThread))
    {
    if (Objectx->Header.SignalState != MINLONG)
    {
    KiWaitSatisfyMutant(Objectx, Thread);
    WaitStatus = (NTSTATUS)(Thread->WaitStatus);
    goto NoWait;

    }
    }
    }
    }
    #define KiWaitSatisfyMutant(_Object_, _Thread_) {
    (_Object_)->Header.SignalState -= 1;
    if ((_Object_)->Header.SignalState == 0) {
    (_Thread_)->KernelApcDisable -= (_Object_)->ApcDisable;
    (_Object_)->OwnerThread = (_Thread_);
    if ((_Object_)->Abandoned == TRUE) {
    (_Object_)->Abandoned = FALSE;
    (_Thread_)->WaitStatus = STATUS_ABANDONED;
    }

    InsertHeadList((_Thread_)->MutantListHead.Blink,
    &(_Object_)->MutantListEntry);
    }
    }
  2. 每次调用KeReleaseMutant当SignalState == 1,就修改Mutant->OwnerThread = NULL。有如下代码说明:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    KeReleaseMutant
    {
    ...
    if (Mutant->Header.SignalState == 1) {
    if (OldState <= 0) {
    RemoveEntryList(&Mutant->MutantListEntry);
    Thread->KernelApcDisable += Mutant->ApcDisable;
    if ((Thread->KernelApcDisable == 0) &&
    (IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode]) == FALSE)) {
    Thread->ApcState.KernelApcPending = TRUE;
    KiRequestSoftwareInterrupt(APC_LEVEL);
    }
    }

    Mutant->OwnerThread = (PKTHREAD)NULL;//重点

    if (IsListEmpty(&Mutant->Header.WaitListHead) == FALSE) {
    KiWaitTest(Mutant, Increment);
    }
    ...
    }
  3. 如果创建Mutant时bInitialOwner == TRUE,则谁创建了该互斥体,谁调用ReleaseMutex才会起作用,其他Mutant->OwnerThread != CurrentThread的线程调用了没用。引申到:只有Mutant->OwnerThread == CurrentThread的线程调用ReleaseMutex才能起作用。

    有如下代码可用来说明:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    KeReleaseMutant
    {
    ...
    //这里的ExRaiseStatus调用直接就退出函数KeReleaseMutant了
    //为什么这么说呢。可以参考KeReleaseSemaphore函数
    if (Mutant->OwnerThread != Thread) {
    KiUnlockDispatcherDatabase(OldIrql);
    ExRaiseStatus(Mutant->Abandoned ?
    STATUS_ABANDONED : STATUS_MUTANT_NOT_OWNED);
    }
    ...
    }

源码欣赏:

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
LONG KeReleaseMutant (
IN PRKMUTANT Mutant,
IN KPRIORITY Increment,
IN BOOLEAN Abandoned,
IN BOOLEAN Wait
)
/*++
Routine Description:
This function releases a mutant object by incrementing the mutant
count. If the resultant value is one, then an attempt is made to
satisfy as many Waits as possible. The previous signal state of
the mutant is returned as the function value. If the Abandoned
parameter is TRUE, then the mutant object is released by settings
the signal state to one.
--*/

{

KIRQL OldIrql;
LONG OldState;
PRKTHREAD Thread;

ASSERT_MUTANT(Mutant);
ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

// Raise IRQL to dispatcher level and lock dispatcher database.
KiLockDispatcherDatabase(&OldIrql);
OldState = Mutant->Header.SignalState;

// If the Abandoned parameter is TRUE, then force the release of the
// mutant object by setting its ownership count(SignalState) to one and setting its
// abandoned state to TRUE. Otherwise increment mutant ownership count.
// If the result count is one, then remove the mutant object from the
// thread's owned mutant list, set the owner thread to NULL, and attempt
// to satisfy a Wait for the mutant object if the mutant object wait
// list is not empty.

Thread = KeGetCurrentThread();
if (Abandoned != FALSE) {
Mutant->Header.SignalState = 1;
Mutant->Abandoned = TRUE;

} else {

// If the Mutant object is not owned by the current thread, then
// unlock the dispatcher data base and raise an exception. Otherwise
// increment the ownership count.

if (Mutant->OwnerThread != Thread) {
KiUnlockDispatcherDatabase(OldIrql);
ExRaiseStatus(Mutant->Abandoned ?
STATUS_ABANDONED : STATUS_MUTANT_NOT_OWNED);
}
Mutant->Header.SignalState += 1;
}

if (Mutant->Header.SignalState == 1) {
if (OldState <= 0) {
RemoveEntryList(&Mutant->MutantListEntry);
Thread->KernelApcDisable += Mutant->ApcDisable;
if ((Thread->KernelApcDisable == 0) &&
(IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode]) == FALSE)) {
Thread->ApcState.KernelApcPending = TRUE;
KiRequestSoftwareInterrupt(APC_LEVEL);
}
}

Mutant->OwnerThread = (PKTHREAD)NULL;
if (IsListEmpty(&Mutant->Header.WaitListHead) == FALSE) {
//循环地将在该等待对象上的所有线程从等待网上摘掉,并且从线程等待链上摘掉,信号值也进行处理
KiWaitTest(Mutant, Increment);
}
}

// If the value of the Wait argument is TRUE, then return to
// caller with IRQL raised and the dispatcher database locked.
// Else release the dispatcher database lock and lower IRQL to
// its previous value.

if (Wait != FALSE) {
Thread->WaitNext = Wait;
Thread->WaitIrql = OldIrql;
} else {
KiUnlockDispatcherDatabase(OldIrql);
}
return OldState;
}

海哥说的调用结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
KeReleaseMutant(X,Y,Abandon,Z)
{
if(Abandon == false) //正常调用
{
MUTANT.Header.SignalState++;
}
else //Abandon == TRUE
{
MUTANT.Header.SignalState == 1;
MUTANT.OwnerThread == NULL;
}

if(MUTANT.Header.SignalState == 1)
MUTANT.OwnerThread == NULL;
从当前线程互斥体链表中将当前互斥体移除
}

3 队列QUEUE

队列对象,这是Windows内核中用于支持线程池的机制。

1
2
3
4
5
6
7
kd> dt _KQUEUE
nt!_KQUEUE
+0x000 Header : _DISPATCHER_HEADER
+0x010 EntryListHead : _LIST_ENTRY
+0x018 CurrentCount : Uint4B //当前正在并发的线程个数
+0x01c MaximumCount : Uint4B //CPU当前支持并行运行的线程个数,默认为处理器(CPU)的数量。
+0x020 ThreadListHead : _LIST_ENTRY //圈着所有活动线程
  • EntryListHead:是一个链表头,该链表包含了队列对象中待处理的项。
  • MaximumCount:指定了队列对象中最多可以有多少个活动线程,以便控制通过该队列对象来处理任务的并发程度。
  • CurrentCount:记录了当前有多少个活动线程。
  • ThreadListHead:是一个链表头,该链表包含了当前已加入该队列对象的线程,每个节点都为线程KTHREAD对象的
    QueueListEntry成员。

可参考:《Windows内核原理与实现5.4 P361》

3.1 线程池

线程池:池子里面有很多已经创建好的线程,用户需要的时候可以取出来用,用完之后又还回去,不需要销毁。

线程池是代表应用程序高效执行异步回调的工作线程的集合。线程池主要用于减少应用程序线程的数量,并提供工作线程的管理。应用程序可以将工作项排队,将工作与可等待的句柄相关联,根据计时器自动排队,并与I/O绑定。

要请求线程池中的线程处理工作项,调用QueueUserWorkItem函数。此函数将参数带到从线程池中选择的线程调用的函数。工作项目排队后无法取消。

线程池是在您首次调用QueueUserWorkItem或BindIoCompletionCallback时创建的,或者当计时器队列计时器或注册的等待操作将回调函数排队时创建。默认情况下,可以在线程池中创建的线程数量约为500个。每个线程使用默认堆栈大小,并以默认优先级运行。

3.2 QueueUserWorkItem

QueueUserWorkItem函数:Queues a work item to a worker thread in the thread pool.(将工作项排到线程池中的工作线程。)

1
2
3
4
5
6
BOOL QueueUserWorkItem(
[in] LPTHREAD_START_ROUTINE Function, //线程回调函数,该函数将由线程池中的线程执行。此值表示线程的起始地址。
//此回调函数不得调用TerminateThread函数。不使用回调函数的返回值。
[in, optional] PVOID Context, //要传递给线程函数的单个参数值。
[in] ULONG Flags //控制执行的Flags,此参数可以是一个或多个值。
);

一旦队列对象的线程池中正在处理队列项的线程进入等待状态或被终止,则队列对象的活动线程数量将减1,因而,只要活动线程数量小于最大允许值,并且队列项链表不空,而且有线程正在等待该队列对象,就可以唤醒一个等待线程。
队列对象可以看做是线程调度机制为支持线程池而做的一个扩展,所以,队列对象的主要逻辑都是在线程调度器中完成的。Windows使用队列对象来实现工作项目(WorkItem)的管理和分发,这里的工作项目是指:运行在System进程的系统线程中的一些过程,由于它们运行在PASSIVE_LEVEL上,所以它们可以像普通的例程一样等待分发器对象或者访问换页内存。DPC例程或者设备驱动程序可以利用工作项目来完成一些必须在PASSIVE_LEVEL上执行的工作。它们通过ExQueueWorkItem或IoQueueWorkItem把一个工作项目插入到系统中,由专门的系统线程来执行该工作项目中指定的一个过程。《Windows内核原理与实现5.4 P366》