首页
社区
课程
招聘
[原创]Windows内核学习笔记之等待机制
发表于: 2022-1-9 16:42 10446

[原创]Windows内核学习笔记之等待机制

2022-1-9 16:42
10446

一.分发器对象

在Windows系统中,一个线程可以通过等待一个或者多个可等待对象,从而让线程进入等待状态,而其他的线程则可以在一些特定时刻可以唤醒这些等待这些可等待对象的线程。这些可等待对象也就是分发器对象,在Windows内核中,凡是以DISPATCH_HEADER结构开头的对象都是分发器对象,该结构定义如下:

1
2
3
4
5
6
7
8
kd> dt _DISPATCHER_HEADER
nt!_DISPATCHER_HEADER
   +0x000 Type             : UChar
   +0x001 Absolute         : UChar
   +0x002 Size             : UChar
   +0x003 Inserted         : UChar
   +0x004 SignalState      : Int4B
   +0x008 WaitListHead     : _LIST_ENTRY
名称 作用
Type 代表该对象的类型
SignalState 代表该分发器对象的信号状态
WaitListHead 双向链表头,此链表包含了所有正在等待该分发器对象的线程
 

以下是Windows中所有分发器的基本信息。

 

avatar

 

前三种是比较常见的三种分发器对象,接下来将对这三种分发器对象进行介绍。

二.事件对象

事件对象_KEVENT的定义如下:

1
2
3
kd> dt _KEVENT
ntdll!_KEVENT
   +0x000 Header           : _DISPATCHER_HEADER

由于事件对象只有一个DISPATCH_HEADER结构,因此它的初始化也比较简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.text:00410D36 ; void __stdcall KeInitializeEvent(PRKEVENT Event, EVENT_TYPE Type, BOOLEAN State)
.text:00410D36                 public _KeInitializeEvent@12
.text:00410D36 _KeInitializeEvent@12 proc near         ; CODE XREF: KeInitializeEventPair(x)+1B↓p
.text:00410D36                                         ; KeInitializeEventPair(x)+28↓p
.text:00410D36
.text:00410D36 Event           = dword ptr  8
.text:00410D36 Type            = dword ptr  0Ch
.text:00410D36 State           = byte ptr  10h
.text:00410D36
.text:00410D36                 mov     edi, edi
.text:00410D38                 push    ebp
.text:00410D39                 mov     ebp, esp
.text:00410D3B                 mov     eax, [ebp+Event]
.text:00410D3E                 mov     cl, byte ptr [ebp+Type]
.text:00410D41                 mov     [eax+_KEVENT.Header.Type], cl
.text:00410D43                 movzx   ecx, [ebp+State]
.text:00410D47                 mov     [eax+_KEVENT.Header.Size], 4
.text:00410D4B                 mov     [eax+_KEVENT.Header.SignalState], ecx
.text:00410D4E                 add     eax, 8          ; eax指向WaitListHead
.text:00410D51                 mov     [eax+LIST_ENTRY.Blink], eax
.text:00410D54                 mov     [eax+LIST_ENTRY.Flink], eax
.text:00410D56                 pop     ebp
.text:00410D57                 retn    0Ch
.text:00410D57 _KeInitializeEvent@12 endp

将事件对象设为有信号状态是通过KeSetEvent函数来实现的,该函数首先会判断是否有等待该分发器对象的线程,如果没有则直接修改事件对象的信号状态为有信号状态(1)。

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
.text:0040AFF9 ; LONG __stdcall KeSetEvent(PRKEVENT Event, KPRIORITY Increment, BOOLEAN Wait)
.text:0040AFF9                 public _KeSetEvent@12
.text:0040AFF9 _KeSetEvent@12  proc near               ; CODE XREF: MiInsertPageInFreeList(x)+4ED↓p
.text:0040AFF9                                         ; IopfCompleteRequest(x,x)+9912↓p ...
.text:0040AFF9
.text:0040AFF9 Event           = dword ptr  8
.text:0040AFF9 Increment       = dword ptr  0Ch
.text:0040AFF9 Wait            = byte ptr  10h
.text:0040AFF9
.text:0040AFF9
.text:0040AFF9                 mov     edi, edi
.text:0040AFFB                 push    ebp
.text:0040AFFC                 mov     ebp, esp
.text:0040AFFE                 push    ebx
.text:0040AFFF                 push    esi
.text:0040B000                 push    edi
.text:0040B001                 xor     ecx, ecx
.text:0040B003                 call    ds:__imp_@KeAcquireQueuedSpinLockRaiseToSynch@4 ; KeAcquireQueuedSpinLockRaiseToSynch(x)
.text:0040B009                 mov     ecx, [ebp+Event]
.text:0040B00C                 mov     edi, [ecx+KEVENT.Header.SignalState] ; 将分发器信号状态赋给edi
.text:0040B00F                 mov     bl, al
.text:0040B011                 lea     eax, [ecx+KEVENT.Header.WaitListHead]
.text:0040B014                 mov     esi, [eax+LIST_ENTRY.Flink]
.text:0040B016                 cmp     esi, eax        ; 判断是否有等待的线程
.text:0040B018                 jnz     short loc_40B03C
.text:0040B01A                 mov     [ecx+KEVENT.Header.SignalState], 1 ; 将信号状态修改为有信号状态

如果有在等待该事件对象的线程,则判断事件对象是事件通知对象还是事件同步对象(1为事件同步对象,0为事件通知对象)。如果是事件通知状态,则通过调用KiWaitTest唤醒所有正在等待该事件对象的线程并把信号状态改为有信号状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.text:0040B03C loc_40B03C:                             ; CODE XREF: KeSetEvent(x,x,x)+1F↑j
.text:0040B03C                 xor     eax, eax
.text:0040B03E                 inc     eax             ; eax=1
.text:0040B03F                 cmp     [ecx+KEVENT.Header.Type], 0 ; 判断为事件通知对象还是事件同步对象
.text:0040B042                 jnz     loc_40ED61
.text:0040B048
.text:0040B048 loc_40B048:                             ; CODE XREF: KeSetEvent(x,x,x)+3D6C↓j
.text:0040B048                 test    edi, edi        ; 判断是否有分发器信号
.text:0040B04A                 jnz     short loc_40B021
.text:0040B04C                 mov     edx, [ebp+Increment]
.text:0040B04F                 mov     [ecx+KEVENT.Header.SignalState], eax ; 修改信号状态为有信号状态
.text:0040B052                 call    @KiWaitTest@8   ; KiWaitTest(x,x)
.text:0040B057                 jmp     short loc_40B021
.text:0040B057 _KeSetEvent@12  endp

否则就调用KiUnwaitThread唤醒一个正在等待该事件对象的事件对象,此时不将信号状态设为有信号状态

1
2
3
4
5
6
7
8
9
.text:0040ED61 loc_40ED61:                             ; CODE XREF: KeSetEvent(x,x,x)+49↑j
.text:0040ED61                 cmp     [esi+_KWAIT_BLOCK.WaitType], ax
.text:0040ED65                 jnz     loc_40B048     
.text:0040ED6B                 movzx   edx, [esi+_KWAIT_BLOCK.WaitKey]
.text:0040ED6F                 mov     ecx, [esi+_KWAIT_BLOCK.Thread]
.text:0040ED72                 push    0
.text:0040ED74                 push    [ebp+Increment]
.text:0040ED77                 call    @KiUnwaitThread@16 ; KiUnwaitThread(x,x,x,x)
.text:0040ED7C                 jmp     loc_40B021c_40B021

三.信号量对象

信号量对象_KSEMAPHORE的定义如下:

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

由于信号量对象可以让多个线程共享一个资源,因此信号量对象增加了Limit成员,该成员用来指明信号量计数器的最大值。在信号量初始化中,会为该成员赋值,且此时的信号量由用户指定,可以不止为1。

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
.text:00411859 ; void __stdcall KeInitializeSemaphore(PRKSEMAPHORE Semaphore, LONG Count, LONG Limit)
.text:00411859                 public _KeInitializeSemaphore@12
.text:00411859 _KeInitializeSemaphore@12 proc near     ; CODE XREF: ExpAllocateSharedWaiterSemaphore(x,x)+32↓p
.text:00411859                                         ; ExReinitializeResourceLite(x)+57F7↓p ...
.text:00411859
.text:00411859 Semaphore       = dword ptr  8
.text:00411859 Count           = dword ptr  0Ch
.text:00411859 Limit           = dword ptr  10h
.text:00411859
.text:00411859                 mov     edi, edi
.text:0041185B                 push    ebp
.text:0041185C                 mov     ebp, esp
.text:0041185E                 mov     eax, [ebp+Semaphore]
.text:00411861                 mov     ecx, [ebp+Count]
.text:00411864                 mov     [eax+_KSEMAPHORE.Header.SignalState], ecx ; 为信号量赋值
.text:00411867                 lea     ecx, [eax+_KSEMAPHORE.Header.WaitListHead]
.text:0041186A                 mov     [eax+_KSEMAPHORE.Header.Type], SemaphoreObject
.text:0041186D                 mov     [eax+_KSEMAPHORE.Header.Size], 5
.text:00411871                 mov     [ecx+LIST_ENTRY.Blink], ecx
.text:00411874                 mov     [ecx+LIST_ENTRY.Flink], ecx
.text:00411876                 mov     ecx, [ebp+Limit]
.text:00411879                 mov     [eax+_KSEMAPHORE.Limit], ecx ; 为信号量最大值赋值
.text:0041187C                 pop     ebp
.text:0041187D                 retn    0Ch
.text:0041187D _KeInitializeSemaphore@12 endp

在释放信号量函数KeReleaseSemaphore中,首先会判断原来信号量和要增加的信号量是否大于最大信号量,以及原信号量在加上增加的信号量以后是否小于原信号量。

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
.text:004123CE ; LONG __stdcall KeReleaseSemaphore(PRKSEMAPHORE Semaphore, KPRIORITY Increment, LONG Adjustment, BOOLEAN Wait)
.text:004123CE                 public _KeReleaseSemaphore@16
.text:004123CE _KeReleaseSemaphore@16 proc near        ; CODE XREF: ExReleaseResourceLite(x)+CF19↓p
.text:004123CE                                         ; ExConvertExclusiveToSharedLite(x)+C708↓p ...
.text:004123CE
.text:004123CE var_1           = byte ptr -1
.text:004123CE Semaphore       = dword ptr  8
.text:004123CE Increment       = dword ptr  0Ch
.text:004123CE Adjustment      = dword ptr  10h
.text:004123CE Wait            = byte ptr  14h
.text:004123CE
.text:004123CE                 mov     edi, edi
.text:004123D0                 push    ebp
.text:004123D1                 mov     ebp, esp
.text:004123D3                 push    ecx
.text:004123D4                 push    ebx
.text:004123D5                 push    esi
.text:004123D6                 push    edi
.text:004123D7                 xor     ecx, ecx
.text:004123D9                 call    ds:__imp_@KeAcquireQueuedSpinLockRaiseToSynch@4 ; KeAcquireQueuedSpinLockRaiseToSynch(x)
.text:004123DF                 mov     esi, [ebp+Semaphore]
.text:004123E2                 mov     ebx, [esi+_KSEMAPHORE.Header.SignalState] ; 将信号量对象的信号量赋值给ebx
.text:004123E5                 mov     cl, al
.text:004123E7                 mov     eax, [ebp+Adjustment] ; 将要增加的信号量赋值给eax
.text:004123EA                 lea     edi, [eax+ebx]  ; 将信号量对象的信号状态以及增加的信号量的和赋给edi
.text:004123ED                 cmp     edi, [esi+_KSEMAPHORE.Limit] ; 判断增加以后的信号量是否大于最大信号量
.text:004123F0                 mov     [ebp+var_1], cl
.text:004123F3                 jg      loc_44ACE7
.text:004123F9                 cmp     edi, ebx        ; 判断增加以后的信号量是否小于原信号量
.text:004123FB                 jl      loc_44ACE7

如果任意一项满足,则抛出异常

1
2
3
4
5
.text:0044ACE7 loc_44ACE7:                             ; CODE XREF: KeReleaseSemaphore(x,x,x,x)+25↑j
.text:0044ACE7                                         ; KeReleaseSemaphore(x,x,x,x)+2D↑j
.text:0044ACE7                 call    @KiUnlockDispatcherDatabase@4 ; KiUnlockDispatcherDatabase(x)
.text:0044ACEC                 push    STATUS_SEMAPHORE_LIMIT_EXCEEDED ; Status
.text:0044ACF1                 call    _ExRaiseStatus@4

否则的话为信号量对象的信号状态赋值,判断原信号量是否为0且是否有等待该信号量对象的线程,如果两者都满足,则调用KiWaitTest来唤醒线程。

1
2
3
4
5
6
7
8
9
10
.text:00412401 loc_412401:                             ; CODE XREF: .text:0044ACF6↓j
.text:00412401                 test    ebx, ebx        ; 判断信号量对象的信号状态是否为0
.text:00412403                 mov     [esi+_KSEMAPHORE.Header.SignalState], edi ; 为信号状态赋值
.text:00412406                 jnz     short loc_412419
.text:00412408                 lea     eax, [esi+_KSEMAPHORE.Header.WaitListHead]
.text:0041240B                 cmp     [eax+LIST_ENTRY.Flink], eax ; 判断是否有等待该对象的线程
.text:0041240D                 jz      short loc_412419
.text:0041240F                 mov     edx, [ebp+Increment]
.text:00412412                 mov     ecx, esi
.text:00412414                 call    @KiWaitTest@8

四.突变体对象

这是Windows内核中互斥体(mutex)概念的具体实现,针对互斥体的操作实际上是突变体操作的特例。该对象不仅有信号状态,而且,如果它当前无信号,则一定被某个线程所占有,所以,它有所有者的概念。该对象的KMUTANT结构体定义如下:

1
2
3
4
5
6
7
kd> dt _KMUTANT
nt!_KMUTANT
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 MutantListEntry  : _LIST_ENTRY
   +0x018 OwnerThread      : Ptr32 _KTHREAD
   +0x01c Abandoned        : UChar
   +0x01d ApcDisable       : UChar
名称 作用
MutantListEntry 由于突变体对象有所有者概念,所有被一个线程拥有的突变体都将加入到该线程的一个链表中,以KTHREAD偏移0x10的MutantListHead成员为链表头。因此,当一个线程终止时,它可以把自己拥有的所有突变体释放掉,以便其它线程有机会获得这些对象
OwnerThread 代表了当前正在拥有该突变体对象的线程
Abandoned 表明了该突变体对象是否已被弃之不用
ApcDisable 表明是否禁止APC,会影响到所有者线程的KernelApcDisable域
 

在KeInitializeMutant中,函数会判断是否指定了所有者线程,如果没指定则将所有者线程赋值为NULL,信号量赋值为1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.text:0041DED9 ; int __stdcall KeInitializeMutant(PKMUTANT Mutant, BOOLEAN InitialOwner)
.text:0041DED9                 public _KeInitializeMutant@8
.text:0041DED9 _KeInitializeMutant@8 proc near         ; CODE XREF: NtCreateMutant(x,x,x,x)+6F↓p
.text:0041DED9                                         ; VerifierKeInitializeMutant(x,x)+C↓p ...
.text:0041DED9
.text:0041DED9 Mutant          = dword ptr  8
.text:0041DED9 InitialOwner    = byte ptr  0Ch
.text:0041DED9
.text:0041DED9 ; FUNCTION CHUNK AT .text:0041DF54 SIZE 00000039 BYTES
.text:0041DED9
.text:0041DED9                 mov     edi, edi
.text:0041DEDB                 push    ebp
.text:0041DEDC                 mov     ebp, esp
.text:0041DEDE                 xor     eax, eax
.text:0041DEE0                 push    esi
.text:0041DEE1                 mov     esi, [ebp+Mutant]
.text:0041DEE4                 inc     eax             ; eax=1
.text:0041DEE5                 cmp     [ebp+InitialOwner], al ; 判断是否指定了所有者线程
.text:0041DEE8                 mov     [esi+_KMUTANT.Header.Type], MutantObject
.text:0041DEEB                 mov     [esi+_KMUTANT.Header.Size], 8
.text:0041DEEF                 jz      short loc_41DF54
.text:0041DEF1                 and     [esi+_KMUTANT.OwnerThread], 0
.text:0041DEF5                 mov     [esi+_KMUTANT.Header.SignalState], eax

如果有,则指定突变体对象的所有者线程,并突变体对象加入到线程链表MutantListHead中,此时的信号量设为0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.text:0041DF54 loc_41DF54:                             ; CODE XREF: KeInitializeMutant(x,x)+16↑j
.text:0041DF54                 push    ebx
.text:0041DF55                 push    edi
.text:0041DF56                 mov     eax, large fs:124h
.text:0041DF5C                 and     [esi+_KMUTANT.Header.SignalState], 0
.text:0041DF60                 mov     edi, eax
.text:0041DF62                 xor     ecx, ecx
.text:0041DF64                 mov     [esi+_KMUTANT.OwnerThread], edi
.text:0041DF67                 call    ds:__imp_@KeAcquireQueuedSpinLockRaiseToSynch@4 ; KeAcquireQueuedSpinLockRaiseToSynch(x)
.text:0041DF6D                 mov     edi, [edi+_ETHREAD.Tcb.MutantListHead.Blink]
.text:0041DF70                 mov     ebx, [edi+LIST_ENTRY.Flink]
.text:0041DF72                 lea     edx, [esi+_KMUTANT.MutantListEntry]
.text:0041DF75                 mov     [edx+LIST_ENTRY.Flink], ebx
.text:0041DF77                 mov     [edx+LIST_ENTRY.Blink], edi
.text:0041DF7A                 mov     [ebx+LIST_ENTRY.Blink], edx
.text:0041DF7D                 mov     cl, al
.text:0041DF7F                 mov     [edi+LIST_ENTRY.Flink], edx
.text:0041DF81                 call    @KiUnlockDispatcherDatabase@4 ; KiUnlockDispatcherDatabase(x)
.text:0041DF86                 pop     edi
.text:0041DF87                 pop     ebx
.text:0041DF88                 jmp     loc_41DEF8

为剩余成员赋值以后退出函数

1
2
3
4
5
6
7
8
9
10
.text:0041DEF8 loc_41DEF8:                             ; CODE XREF: KeInitializeMutant(x,x)+AF↓j
.text:0041DEF8                 lea     eax, [esi+_KMUTANT.Header.WaitListHead]
.text:0041DEFB                 mov     [eax+LIST_ENTRY.Blink], eax
.text:0041DEFE                 mov     [eax+LIST_ENTRY.Flink], eax
.text:0041DF00                 mov     [esi+_KMUTANT.Abandoned], 0
.text:0041DF04                 mov     [esi+_KMUTANT.ApcDisable], 0
.text:0041DF08                 pop     esi
.text:0041DF09                 pop     ebp
.text:0041DF0A                 retn    8
.text:0041DF0A _KeInitializeMutant@8 endp

而在函数KeInitializeMutex中初始化的突变体对象是没有指定所有者线程的,且ApcDisable被设为了1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.text:00441B63 ; void __stdcall KeInitializeMutex(PRKMUTEX Mutex, ULONG Level)
.text:00441B63                 public _KeInitializeMutex@8
.text:00441B63 _KeInitializeMutex@8 proc near          ; CODE XREF: RawInitializeVcb(x,x,x)+36↓p
.text:00441B63                                         ; PpInitializeNotification()+2D↓p ...
.text:00441B63
.text:00441B63 Mutex           = dword ptr  8
.text:00441B63 Level           = dword ptr  0Ch
.text:00441B63
.text:00441B63                 mov     edi, edi
.text:00441B65                 push    ebp
.text:00441B66                 mov     ebp, esp
.text:00441B68                 mov     eax, [ebp+Mutex]
.text:00441B6B                 lea     ecx, [eax+_KMUTANT.Header.WaitListHead]
.text:00441B6E                 mov     [eax+_KMUTANT.Header.Type], 2
.text:00441B71                 mov     [eax+_KEVENT.Header.Size], 8
.text:00441B75                 mov     [eax+_KMUTANT.Header.SignalState], 1
.text:00441B7C                 mov     [ecx+LIST_ENTRY.Blink], ecx
.text:00441B7F                 mov     [ecx+LIST_ENTRY.Flink], ecx
.text:00441B81                 and     [eax+_KMUTANT.OwnerThread], 0
.text:00441B85                 mov     [eax+_KMUTANT.Abandoned], 0
.text:00441B89                 mov     [eax+_KMUTANT.ApcDisable], 1
.text:00441B8D                 pop     ebp
.text:00441B8E                 retn    8
.text:00441B8E _KeInitializeMutex@8 endp

在KeReleaseMutant中会判断是否要释放突变体对象

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
.text:00402B4C ; int __stdcall KeReleaseMutant(PKMUTANT Mutant, int Increment, BOOLEAN Abandoned, BOOLEAN Wait)
.text:00402B4C                 public _KeReleaseMutant@16
.text:00402B4C _KeReleaseMutant@16 proc near           ; CODE XREF: KeReleaseMutex(x,x)+F↓p
.text:00402B4C                                         ; ExpDeleteMutant(x)+E↓p ...
.text:00402B4C
.text:00402B4C Mutant          = dword ptr  8
.text:00402B4C Increment       = dword ptr  0Ch
.text:00402B4C Abandoned       = byte ptr  10h
.text:00402B4C Wait            = byte ptr  14h
.text:00402B4C
.text:00402B4C
.text:00402B4C                 mov     edi, edi
.text:00402B4E                 push    ebp
.text:00402B4F                 mov     ebp, esp
.text:00402B51                 push    ebx
.text:00402B52                 push    esi
.text:00402B53                 push    edi
.text:00402B54                 xor     ecx, ecx
.text:00402B56                 call    ds:__imp_@KeAcquireQueuedSpinLockRaiseToSynch@4 ; KeAcquireQueuedSpinLockRaiseToSynch(x)
.text:00402B5C                 mov     esi, [ebp+Mutant]
.text:00402B5F                 mov     bl, al
.text:00402B61                 mov     eax, [esi+_KMUTANT.Header.SignalState]
.text:00402B64                 mov     [ebp+Mutant], eax ; 将信号量保存在Mutant中
.text:00402B67                 mov     eax, large fs:124h
.text:00402B6D                 cmp     [ebp+Abandoned], 0
.text:00402B71                 mov     edi, eax        ; 将当前线程赋给edi
.text:00402B73                 jnz     loc_41DF28

如果要释放,则修改信号量为1,且Abandoned置为1

1
2
3
4
.text:0041DF28 loc_41DF28:                             ; CODE XREF: KeReleaseMutant(x,x,x,x)+27↑j
.text:0041DF28                 mov     [esi+_KMUTANT.Header.SignalState], 1
.text:0041DF2F                 mov     [esi+_KMUTANT.Abandoned], 1
.text:0041DF33                 jmp     loc_402B85

如果不需释放,则判断突变体的所有者线程是否是当前线程

1
2
.text:00402B79                 cmp     [esi+_KMUTANT.OwnerThread], edi ; 判断所有者线程是否是当前线程
.text:00402B7C                 jnz     loc_4425A0

如果不是当前线程,则抛出异常,增加信号量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.text:004425A0 loc_4425A0:                             ; CODE XREF: KeReleaseMutant(x,x,x,x)+30↑j
.text:004425A0                 mov     cl, bl
.text:004425A2                 call    @KiUnlockDispatcherDatabase@4 ; KiUnlockDispatcherDatabase(x)
.text:004425A7                 mov     al, [esi+_KMUTANT.Abandoned]
.text:004425AA                 neg     al
.text:004425AC                 sbb     eax, eax
.text:004425AE                 and     eax, 4000003Ah
.text:004425B3                 add     eax, 0C0000046h
.text:004425B8                 push    eax             ; Status
.text:004425B9                 call    _ExRaiseStatus@4 ; ExRaiseStatus(x)
.text:004425BE                 jmp     loc_402B82
        。。。
.text:00402B82 loc_402B82:                             ; CODE XREF: .text:004425BE↓j
.text:00402B82                 inc     [esi+_KMUTANT.Header.SignalState]

判断信号量是否为1,如果不为1则退出函数

1
2
3
.text:00402B85 loc_402B85:                             ; CODE XREF: KeReleaseMutant(x,x,x,x)+1B3E7↓j
.text:00402B85                 cmp     [esi+_KMUTANT.Header.SignalState], 1
.text:00402B89                 jnz     short loc_402BBB

如果为1,继续判断原信号量是否大于0,此时的Mutant中保存的是原信号量

1
2
.text:00402B8B                 cmp     [ebp+Mutant], 0 ; 判断信号量是否大于0
.text:00402B8F                 jg      short loc_402BAC

如果不大于0,则将突变体从链表中删除

1
2
3
4
5
6
7
.text:00402B91                 mov     eax, [esi+_KMUTANT.MutantListEntry.Flink]
.text:00402B94                 mov     ecx, [esi+_KMUTANT.MutantListEntry.Blink]
.text:00402B97                 mov     [ecx+LIST_ENTRY.Flink], eax
.text:00402B99                 mov     [eax+LIST_ENTRY.Blink], ecx
.text:00402B9C                 movzx   eax, [esi+_KMUTANT.ApcDisable]
.text:00402BA0                 add     [edi+_ETHREAD.Tcb.KernelApcDisable], eax
.text:00402BA6                 jz      loc_40E979

清空当前所有者线程,判断是否有其他线程在等待该突变体对象

1
2
3
4
5
6
.text:00402BAC loc_402BAC:                             ; CODE XREF: KeReleaseMutant(x,x,x,x)+43↑j
.text:00402BAC                                         ; KeReleaseMutant(x,x,x,x)+BE32↓j ...
.text:00402BAC                 and     [esi+_KMUTANT.OwnerThread], 0
.text:00402BB0                 lea     eax, [esi+_KMUTANT.Header.WaitListHead]
.text:00402BB3                 cmp     [eax+LIST_ENTRY.Flink], eax ; 判断是否有线程对象在等待该突变体对象
.text:00402BB5                 jnz     loc_424732

如果有等待该突变体对象的线程,则调用KiWaitTest来唤醒线程

1
2
3
4
5
.text:00424732 loc_424732:                             ; CODE XREF: KeReleaseMutant(x,x,x,x)+69↑j
.text:00424732                 mov     edx, [ebp+Increment]
.text:00424735                 mov     ecx, esi
.text:00424737                 call    @KiWaitTest@8   ; KiWaitTest(x,x)
.text:0042473C                 jmp     loc_402BBB

函数最终会将原信号量作为返回值返回给调用者

1
2
.text:00402BCD loc_402BCD:                             ; CODE XREF: KeReleaseMutant(x,x,x,x)+480DC↓j
.text:00402BCD                 mov     eax, [ebp+Mutant]

只有所有者线程才可以释放突变体对象,否则会触发异常。该函数参数Abandoned可以强迫释放突变体对象

 

KeReleaseMutex函数的实现是通过调用KeReleastMutant函数来实现的,此时的参数Abandoned被指定为FALSE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.text:00411471 ; LONG __stdcall KeReleaseMutex(PRKMUTEX Mutex, BOOLEAN Wait)
.text:00411471                 public _KeReleaseMutex@8
.text:00411471 _KeReleaseMutex@8 proc near             ; CODE XREF: RawCheckForDismount(x,x)+4F↓p
.text:00411471                                         ; WmipNotificationIrpCancel(x,x)+28↓p ...
.text:00411471
.text:00411471 Mutex           = dword ptr  8
.text:00411471 Wait            = byte ptr  0Ch
.text:00411471
.text:00411471                 mov     edi, edi
.text:00411473                 push    ebp
.text:00411474                 mov     ebp, esp
.text:00411476                 push    dword ptr [ebp+Wait] ; Wait
.text:00411479                 push    0               ; Abandoned
.text:0041147B                 push    1               ; Increment
.text:0041147D                 push    [ebp+Mutex]     ; Mutant
.text:00411480                 call    _KeReleaseMutant@16 ; KeReleaseMutant(x,x,x,x)
.text:00411485                 pop     ebp
.text:00411486                 retn    8
.text:00411486 _KeReleaseMutex@8 endp

五.线程与分发器对象的连接

分发器对象的DISPATCHER_HEADER中的WaitListHead成员是一个双向链表,链表上的每个节点代表了正在等待该分发器的线程。线程对象KTHREAD偏移0x5C的WaitBlockList成员,它指向一个链表,链表上的每个节点代表了该线程正在等待的分发器对象。这两个链表上的节点都是等待块对象,其数据类型是KWAIT_BLOCK,定义如下:

1
2
3
4
5
6
7
8
kd> dt _KWAIT_BLOCK
nt!_KWAIT_BLOCK
   +0x000 WaitListEntry    : _LIST_ENTRY
   +0x008 Thread           : Ptr32 _KTHREAD
   +0x00c Object           : Ptr32 Void
   +0x010 NextWaitBlock    : Ptr32 _KWAIT_BLOCK
   +0x014 WaitKey          : Uint2B
   +0x016 WaitType         : Uint2B
名称 作用
WaitListEntry 链入线程对象的双向链表
Thread 指向等待该分发器对象的线程对象
Object 指向被等待的分发器对象
NextWaitBlock 链入分发器对象的单向链表
WaitKey 该域是指当Thread等待Object成功从而被解除等待时的完成状态值
WaitType WAIT_TYPE枚举类型:WaitAll或WaitAny,说明线程Thread要等待所有对象变成有信号状态,或者只要任意一个对象变成有信号状态即可

等待快对象是要同时加入到线程对象中的链表和分发器对象中的链表。其中的WaitListEntry用来加入到线程的WaitBlockList链表中,这样线程就可以找到其等待的所有分发器对象,而NextWaitBlock则用来加入到分发器对象的WaitListHead中,这样同一类型的分发器对象就可以连接在一起

 

下图展现了线程对象,分发器对象和等待块对象的关系:

 

avatar

 

基于这样的结构,当一个线程通过某个Wait函数进入等待状态的时候,线程对象偏移0x5C的WaitBlockList将指向一个非空链表,链表上的每个等待块对象描述了它所等待的一个分发器对象。只要链表节点上的分发器对象是无信号状态,则该线程始终处于等待状态。

 

线程对象偏移0x70处预留了4个KWAIT_BLOCK,即WaitBlock数组成员。因此,如果一个线程要等待的分发器对象不超过4个,则它就不需要申请额外的KWAIT_BLOCK对象,直接使用预留的这4个等待块对象即可。

六.KeWaitForSingleObject函数分析

KeWaitForSingleObject函数能让线程进入等待状态的函数,该函数首先会对线程对象偏移0x70的WaitBlck[0]等待块对象赋值,将线程对象的WaitBlockList指向第一个等待块对象,判断超时时间是否为0

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
.text:00405400 ; NTSTATUS __stdcall KeWaitForSingleObject(PVOID Object, KWAIT_REASON WaitReason, KPROCESSOR_MODE WaitMode, BOOLEAN Alertable, PLARGE_INTEGER Timeout)
.text:00405400                 public _KeWaitForSingleObject@20
.text:00405400 _KeWaitForSingleObject@20 proc near     ; CODE XREF: ExAcquireFastMutexUnsafe(x)+15↑p
.text:00405400                                         ; ExpWaitForResource(x,x)+2A↓p ...
.text:00405400
.text:00405400 var_14          = dword ptr -14h
.text:00405400 var_10          = dword ptr -10h
.text:00405400 var_C           = byte ptr -0Ch
.text:00405400 var_4           = dword ptr -4
.text:00405400 Object          = dword ptr  8
.text:00405400 WaitReason      = dword ptr  0Ch
.text:00405400 WaitMode        = byte ptr  10h
.text:00405400 Alertable       = byte ptr  14h
.text:00405400 Timeout         = dword ptr  18h
.text:00405400
.text:00405400                 mov     edi, edi        ; KeWaitForMutexObject
.text:00405402                 push    ebp
.text:00405403                 mov     ebp, esp
.text:00405405                 sub     esp, 14h
.text:00405408                 push    ebx
.text:00405409                 push    esi
.text:0040540A                 push    edi
.text:0040540B                 mov     eax, large fs:124h
.text:00405411                 mov     edx, [ebp+Timeout]
.text:00405414                 mov     ebx, [ebp+Object]
.text:00405417                 mov     esi, eax        ; 将线程对象赋给esi
.text:00405419                 cmp     [esi+_KTHREAD.WaitNext], 0
.text:0040541D                 mov     [ebp+var_4], edx
.text:00405420                 lea     edi, [esi+_ETHREAD.Tcb.WaitBlock] ; edi指向WaitBlock[0]
.text:00405423                 lea     eax, [esi+0B8h] ; 取出ETHREAD.Tcb.WaitBlock[3]的地址
.text:00405429                 jnz     loc_43EBB5
.text:0040542F
.text:0040542F loc_40542F:                             ; CODE XREF: KeWaitForSingleObject(x,x,x,x,x)+579D↓j
.text:0040542F                                         ; KeWaitForSingleObject(x,x,x,x,x)+57B5↓j ...
.text:0040542F                 call    ds:__imp__KeRaiseIrqlToSynchLevel@0 ; KeRaiseIrqlToSynchLevel()
.text:00405435                 xor     ecx, ecx
.text:00405437                 cmp     [ebp+Timeout], ecx ; 判断超时时间是否为0
.text:0040543A                 mov     [esi+_KTHREAD.WaitIrql], al
.text:0040543D                 mov     [esi+_KTHREAD.WaitBlockList], edi
.text:00405440                 mov     [edi+_KWAIT_BLOCK.Object], ebx
.text:00405443                 mov     [edi+_KWAIT_BLOCK.WaitKey], cx
.text:00405447                 mov     [edi+_KWAIT_BLOCK.WaitType], WaitAny
.text:0040544D                 mov     [esi+_KTHREAD.WaitStatus], ecx
.text:00405450                 jz      loc_40EA6F

如果超时时间为0,则第一个等待块的NextWaitBlock指向自己

1
2
3
.text:0040EA6F loc_40EA6F:                             ; CODE XREF: KeWaitForSingleObject(x,x,x,x,x)+50↑j
.text:0040EA6F                 mov     [edi+_KWAIT_BLOCK.NextWaitBlock], edi
.text:0040EA72                 jmp     loc_40546E

如果超时时间不为0,则将第一个等待块和第四个等待块的NextWaitBlock互相连接起来,并将其连入线程的定时器对象链表中

1
2
3
4
5
.text:00405456                 lea     eax, [esi+0B8h] ; eax指向WaitBlock[3]的地址
.text:0040545C                 mov     [edi+_KWAIT_BLOCK.NextWaitBlock], eax
.text:0040545F                 mov     [eax+_KWAIT_BLOCK.NextWaitBlock], edi
.text:00405462                 mov     [esi+_KTHREAD.Timer.Header.WaitListHead.Flink], eax
.text:00405468                 mov     [esi+_KTHREAD.Timer.Header.WaitListHead.Blink], eax

继续为线程中的成员赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.text:0040546E loc_40546E:                             ; CODE XREF: KeWaitForSingleObject(x,x,x,x,x)+9672↓j
.text:0040546E                 mov     al, [ebp+Alertable]
.text:00405471                 mov     dl, byte ptr [ebp+WaitReason]
.text:00405474                 mov     [esi+_KTHREAD.Alertable], al
.text:0040547A                 mov     al, [ebp+WaitMode]
.text:0040547D                 test    al, al
.text:0040547F                 mov     [esi+_KTHREAD.WaitMode], al
.text:00405482                 mov     [esi+_KTHREAD.WaitReason], dl
.text:00405485                 mov     [esi+_KTHREAD.___u25.WaitListEntry.Flink], ecx
.text:00405488                 jz      loc_405538
.text:0040548E                 cmp     [esi+_KTHREAD.EnableStackSwap], 0
.text:00405495                 jz      loc_405538
.text:0040549B                 cmp     [esi+_KTHREAD.Priority], 19h
.text:0040549F                 mov     [ebp+Object], 1
.text:004054A6                 jge     loc_405538

接下来会进入一个死循环,每次线程被其他线程唤醒的时候都会进入到这个循环中。

 

在循环中会判断分发器对象是否是突变体对象,如果是则接着判断其信号量是否大于0,或者所有者线程是当前线程

1
2
3
4
5
6
7
8
.text:004054D2 loc_4054D2:                             ; CODE XREF: KeWaitForSingleObject(x,x,x,x,x)+3C1C8↓j
.text:004054D2                 cmp     [ebx+_DISPATCHER_HEADER.Type], 2 ; 判断是否是突变体对象
.text:004054D5                 jnz     loc_40265F
.text:004054DB                 mov     eax, [ebx+_DISPATCHER_HEADER.SignalState]
.text:004054DE                 test    eax, eax        ; 判断是否有信号量
.text:004054E0                 jg      loc_40261E
.text:004054E6                 cmp     esi, [ebx+_KMUTANT.OwnerThread]
.text:004054E9                 jz      loc_40261E

如果信号量大于0,或者所有者线程是当前线程,就会判断信号量是否等于0x80000000,如果不相等,则会将突变体对象的信号量减一,判断减一以后是否等于0,如果不等于0,则会退出函数

1
2
3
4
5
6
.text:0040261E loc_40261E:                             ; CODE XREF: KeWaitForSingleObject(x,x,x,x,x)+E0↓j
.text:0040261E                                         ; KeWaitForSingleObject(x,x,x,x,x)+E9↓j
.text:0040261E                 cmp     eax, 80000000h  ; 信号量是否等于0x80000000
.text:00402623                 jz      loc_44AB42
.text:00402629                 dec     [ebx+_KMUTANT.Header.SignalState] ; 信号量减1
.text:0040262C                 jnz     short loc_402657

如果等于0,则为突变体对象赋值,判断Abandoned是否为TRUE

1
2
3
4
5
.text:0040262E                 movzx   eax, [ebx+_KMUTANT.ApcDisable]
.text:00402632                 sub     [esi+_KTHREAD.KernelApcDisable], eax
.text:00402638                 cmp     [ebx+_KMUTANT.Abandoned], 1 ; 判断Abandoned是否等于1
.text:0040263C                 mov     [ebx+_KMUTANT.OwnerThread], esi
.text:0040263F                 jz      loc_442A97loc_405521

如果为TRUE,继续修改突变体对象

1
2
3
4
.text:00442A97 loc_442A97:                             ; CODE XREF: KeWaitForSingleObject(x,x,x,x,x)-2DC1↑j
.text:00442A97                 mov     [ebx+_KMUTANT.Abandoned], 0
.text:00442A9B                 mov     [esi+_KTHREAD.WaitStatus], 80h
.text:00442AA2                 jmp     loc_402645

将突变体对象从线程对象的MutantListHead中删除

1
2
3
4
5
6
7
8
9
10
11
12
.text:00402645 loc_402645:                             ; CODE XREF: KeWaitForSingleObject(x,x,x,x,x)+3D6A2↓j
.text:00402645                 mov     ecx, [esi+_KTHREAD.MutantListHead.Blink]
.text:00402648                 mov     edx, [ecx+LIST_ENTRY.Flink]
.text:0040264A                 lea     eax, [ebx+_KMUTANT.MutantListEntry]
.text:0040264D                 mov     [eax+LIST_ENTRY.Flink], edx
.text:0040264F                 mov     [eax+LIST_ENTRY.Blink], ecx
.text:00402652                 mov     [edx+LIST_ENTRY.Blink], eax
.text:00402655                 mov     [ecx+LIST_ENTRY.Flink], eax
.text:00402657
.text:00402657 loc_402657:                             ; CODE XREF: KeWaitForSingleObject(x,x,x,x,x)-2DD4↑j
.text:00402657                 mov     edi, [esi+_ETHREAD.Tcb.WaitStatus]
.text:0040265A                 jmp     loc_405521

如果分发器对象类型不是突变体对象,就会继续判断信号量是否小于等于0

1
2
3
.text:0040265F loc_40265F:                             ; CODE XREF: KeWaitForSingleObject(x,x,x,x,x)+D5↓j
.text:0040265F                 cmp     [ebx+_DISPATCHER_HEADER.SignalState], 0 ; 信号量是否小于等于0
.text:00402663                 jle     loc_4054EF

如果大于0,则取出分发器对象的类型,保留后3位后判断分发器对象的类型

1
2
3
4
5
6
7
.text:00402669                 mov     al, [ebx+_DISPATCHER_HEADER.Type]
.text:0040266B                 mov     cl, al
.text:0040266D                 and     cl, 7
.text:00402670                 cmp     cl, 1           ; 判断是否是事件对象
.text:00402673                 jz      loc_410930
.text:00402679                 cmp     al, 5           ; 判断是否是信号量对象
.text:0040267B                 jz      loc_4126C4

如果是事件类型,则将信号量赋值为0,接着退出函数

1
2
3
.text:00410930 loc_410930:                             ; CODE XREF: KeWaitForSingleObject(x,x,x,x,x)-2D8D↑j
.text:00410930                 and     [ebx+KEVENT.Header.SignalState], 0
.text:00410934                 jmp     loc_402681

如果是信号量类型,则将信号量减1,接着退出函数

1
2
3
.text:004126C4 loc_4126C4:                             ; CODE XREF: KeWaitForSingleObject(x,x,x,x,x)-2D85↑j
.text:004126C4                 dec     [ebx+_KSEMAPHORE.Header.SignalState]
.text:004126C7                 jmp     loc_402681

如果条件不满足(非突变体对象的信号量小于等于0或突变体对象没有退出循环),接下来就会将等待块对象(WAIT_BLOCK)连接进分发器对象的WaitListHead。此时的edi指向的就是WaitBlock数组的第一个等待块对象

1
2
3
4
5
6
7
8
.text:0040AB5D loc_40AB5D:                             ; CODE XREF: KeWaitForSingleObject(x,x,x,x,x)+10B↑j
.text:0040AB5D                                         ; KeWaitForSingleObject(x,x,x,x,x)+9944↓j
.text:0040AB5D                 lea     eax, [ebx+_DISPATCHER_HEADER.WaitListHead]
.text:0040AB60                 mov     ecx, [eax+LIST_ENTRY.Blink]
.text:0040AB63                 mov     [edi+LIST_ENTRY.Flink], eax
.text:0040AB65                 mov     [edi+LIST_ENTRY.Blink], ecx
.text:0040AB68                 mov     [ecx+LIST_ENTRY.Flink], edi
.text:0040AB6A                 mov     [eax+LIST_ENTRY.Blink], edi

修改线程的状态为等待状态,判断是否存在分发器对象

1
2
3
4
.text:0040AB7B loc_40AB7B:                             ; CODE XREF: KeWaitForSingleObject(x,x,x,x,x)+B982↓j
.text:0040AB7B                 cmp     [ebp+Object], 0 ; 判断是否存在分发器对象
.text:0040AB7F                 mov     [esi+_KTHREAD.State], 5
.text:0040AB83                 jnz     loc_40F8C4

如果存在,则将线程对象加入到KiWaitListHead链表中

1
2
3
4
5
6
7
8
.text:0040F8C4 loc_40F8C4:                             ; CODE XREF: KeWaitForSingleObject(x,x,x,x,x)+5783↑j
.text:0040F8C4                 mov     ecx, ds:dword_48B30C
.text:0040F8CA                 lea     eax, [esi+60h]  ; 取出线程对象的WaitListEntry
.text:0040F8CD                 mov     [eax+LIST_ENTRY.Flink], offset _KiWaitListHead
.text:0040F8D3                 mov     [eax+LIST_ENTRY.Blink], ecx
.text:0040F8D6                 mov     [ecx+LIST_ENTRY.Flink], eax
.text:0040F8D8                 mov     ds:dword_48B30C, eax
.text:0040F8DD                 jmp     loc_40AB89

调用KiSwapThread来主动切换线程,当该函数返回的时候,意味着此线程获得了控制权,若不考虑内核APC的因素,则意味着此等待以被满足;或者被等待的对象变成了有信号的状态,或者超时

1
2
3
4
5
6
7
8
9
10
11
12
13
.text:0040AB89 loc_40AB89:                             ; CODE XREF: KeWaitForSingleObject(x,x,x,x,x)+A4DD↓j
.text:0040AB89                 call    @KiSwapThread@0 ; KiSwapThread()
.text:0040AB8E                 cmp     eax, 100h
.text:0040AB93                 jnz     loc_405531
.text:0040AB99                 cmp     [ebp+Timeout], 0
.text:0040AB9D                 jz      loc_40542F
.text:0040ABA3                 mov     ecx, [ebp+var_4]
.text:0040ABA6                 lea     eax, [ebp+var_C]
.text:0040ABA9                 push    eax
.text:0040ABAA                 lea     edx, [ebp+var_14]
.text:0040ABAD                 call    @KiComputeWaitInterval@12 ; KiComputeWaitInterval(x,x,x)
.text:0040ABB2                 mov     [ebp+Timeout], eax
.text:0040ABB5                 jmp     loc_40542F

线程进入等待状态的核心操作是将线程对象挂到KiWaitListHead以后,调用KiSwapThread来主动切换线程,等待其他线程将其从链表中摘除

七.KiWaitTest函数分析

KeSetEvent,KeReleaseSemaphore和KeReleaseMutant函数可以改变分发器对象的信号状态,在这些函数中,它们会调用其他函数来测试一个等待是否已经满足,KiWaitTest函数就是其中之一。

 

在KiWaitTest中会首先取出分发器对象,判断信号量是否小于等于0,如果小于等于0,则退出函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.text:0040A687 ; __fastcall KiWaitTest(x, x)
.text:0040A687 @KiWaitTest@8   proc near               ; CODE XREF: KeSetEvent(x,x,x)+59↓p
.text:0040A687                                         ; KiTimerListExpire(x,x)+75↓p ...
.text:0040A687
.text:0040A687 var_10          = dword ptr -10h
.text:0040A687 var_C           = dword ptr -0Ch
.text:0040A687 var_Increment   = dword ptr -8
.text:0040A687 var_WaitKey     = dword ptr -4
.text:0040A687
.text:0040A687                 mov     edi, edi
.text:0040A689                 push    ebp
.text:0040A68A                 mov     ebp, esp
.text:0040A68C                 sub     esp, 10h
.text:0040A68F                 push    ebx
.text:0040A690                 push    esi             ; Thread
.text:0040A691                 mov     esi, ecx
.text:0040A693                 cmp     [esi+_DISPATCHER_HEADER.SignalState], 0 ; 判断信号量是否小于等于0
.text:0040A697                 lea     ecx, [ebp+var_10]
.text:0040A69A                 lea     ebx, [esi+_DISPATCHER_HEADER.WaitListHead]
.text:0040A69D                 mov     eax, [ebx+LIST_ENTRY.Flink]
.text:0040A69F                 mov     [ebp+var_Increment], edx
.text:0040A6A2                 mov     [ebp+var_C], ecx
.text:0040A6A5                 mov     [ebp+var_10], ecx
.text:0040A6A8                 jle     short loc_40A709 ; 小于等于0则跳转

每个KWAIT_BLOCK对象都代表了一个线程在等待该对象,利用KWAIT_BLOCK结构可以获得等待该分发器对象的线程以及等待类型,根据类型的不同,修改分发器的信号量等信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.text:0040A6AB loc_40A6AB:                             ; CODE XREF: KiWaitTest(x,x)+75↓j
.text:0040A6AB                 cmp     eax, ebx        ; 判断是否有分发器对象
.text:0040A6AD                 jz      short loc_40A6FE
.text:0040A6AF                 cmp     [eax+_KWAIT_BLOCK.WaitType], 1
.text:0040A6B4                 mov     ecx, [eax+_KWAIT_BLOCK.Thread]
.text:0040A6B7                 mov     [ebp+var_WaitKey], 100h
.text:0040A6BE                 jnz     short loc_40A6E7
.text:0040A6C0                 movzx   eax, [eax+_KWAIT_BLOCK.WaitKey]
.text:0040A6C4                 mov     [ebp+var_WaitKey], eax
.text:0040A6C7                 mov     al, [esi+_DISPATCHER_HEADER.Type]
.text:0040A6C9                 mov     dl, al
.text:0040A6CB                 and     dl, 7
.text:0040A6CE                 cmp     dl, 1
.text:0040A6D1                 jz      loc_4135F3
.text:0040A6D7                 cmp     al, 5
.text:0040A6D9                 jz      loc_412435
.text:0040A6DF                 cmp     al, 2
.text:0040A6E1                 jz      loc_4246FB

调用KiUnWaitThread函数

1
2
3
4
5
6
7
.text:0040A6E7 loc_40A6E7:                             ; CODE XREF: KiWaitTest(x,x)+37↑j
.text:0040A6E7                                         ; KiWaitTest(x,x)+7DB1↓j ...
.text:0040A6E7                 mov     edx, [ebp+var_WaitKey]
.text:0040A6EA                 lea     eax, [ebp+var_10]
.text:0040A6ED                 push    eax
.text:0040A6EE                 push    [ebp+var_Increment]
.text:0040A6F1                 call    @KiUnwaitThread@16

在KiUnwaitThread函数中会调用KiUnlinkThread函数,将一个满足等待条件的线程从它的等待块链表中移除出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.text:0040A5E8 ; __fastcall KiUnwaitThread(x, x, x, x)
.text:0040A5E8 @KiUnwaitThread@16 proc near            ; CODE XREF: KiWaitTest(x,x)+6A↓p
.text:0040A5E8                                         ; KeSetEvent(x,x,x)+3D7E↓p ...
.text:0040A5E8
.text:0040A5E8 var_4           = dword ptr -4
.text:0040A5E8 arg_0           = dword ptr  8
.text:0040A5E8 arg_4           = dword ptr  0Ch
.text:0040A5E8
.text:0040A5E8 ; FUNCTION CHUNK AT .text:00402F4B SIZE 00000036 BYTES
.text:0040A5E8 ; FUNCTION CHUNK AT .text:0040EE83 SIZE 00000023 BYTES
.text:0040A5E8 ; FUNCTION CHUNK AT .text:0040FBD0 SIZE 0000000B BYTES
.text:0040A5E8 ; FUNCTION CHUNK AT .text:0041C168 SIZE 00000008 BYTES
.text:0040A5E8
.text:0040A5E8                 mov     edi, edi
.text:0040A5EA                 push    ebp
.text:0040A5EB                 mov     ebp, esp
.text:0040A5ED                 push    ecx
.text:0040A5EE                 push    ebx
.text:0040A5EF                 push    esi             ; Thread
.text:0040A5F0                 mov     [ebp+var_4], edx
.text:0040A5F3                 mov     esi, ecx
.text:0040A5F5                 call    @KiUnlinkThread@8

KiUnwaitThread函数返回后,会调用KiReadThread让线程进入就绪状态,这就相当于将等待状态的线程唤醒

1
2
3
4
5
6
7
8
9
.text:0040A70D loc_40A70D:                             ; CODE XREF: KiWaitTest(x,x)+80↑j
.text:0040A70D                 mov     eax, [ecx]
.text:0040A70F                 lea     edx, [ebp+var_10]
.text:0040A712                 mov     [ebp+var_10], eax
.text:0040A715                 add     ecx, 0FFFFFFA0h ; Thread
.text:0040A718                 mov     [eax+4], edx
.text:0040A71B                 call    @KiReadyThread@4 ; KiReadyThread(x)
.text:0040A720                 jmp     short loc_40A6FF
.text:0040A720 @KiWaitTest@8   endp

KiUnlinkThread函数则首先设置线程的完成状态,将线程的等待块对象从线程中全部删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.text:0040A59A ; __fastcall KiUnlinkThread(x, x)
.text:0040A59A @KiUnlinkThread@8 proc near             ; CODE XREF: KiUnwaitThread(x,x,x,x)+D↓p
.text:0040A59A                                         ; KeSetEventBoostPriority(x,x)+8B↓p
.text:0040A59A
.text:0040A59A
.text:0040A59A                 or      [ecx+_KTHREAD.WaitStatus], edx
.text:0040A59D                 mov     eax, [ecx+_ETHREAD.Tcb.WaitBlockList] ; 获取线程的分发器对象
.text:0040A5A0                 push    esi
.text:0040A5A1
.text:0040A5A1 loc_40A5A1:                             ; CODE XREF: KiUnlinkThread(x,x)+17↓j
.text:0040A5A1                 mov     edx, [eax+_KWAIT_BLOCK.WaitListEntry.Flink]
.text:0040A5A3                 mov     esi, [eax+_KWAIT_BLOCK.WaitListEntry.Blink]
.text:0040A5A6                 mov     [esi+LIST_ENTRY.Flink], edx
.text:0040A5A8                 mov     [edx+LIST_ENTRY.Blink], esi
.text:0040A5AB                 mov     eax, [eax+_KWAIT_BLOCK.NextWaitBlock] ; 获取下一分发器
.text:0040A5AE                 cmp     eax, [ecx+_KTHREAD.WaitBlockList] ; 是否存在分发器对象
.text:0040A5B1                 jnz     short loc_40A5A1

判断线程是否在全局等待链表中,如果在则从全局的等待链表中删除

1
2
3
4
5
6
7
.text:0040A5B3                 cmp     [ecx+_KTHREAD.___u25.WaitListEntry.Flink], 0 ; 线程等待链表是否为NULL
.text:0040A5B7                 pop     esi
.text:0040A5B8                 jz      short loc_40A5C5
.text:0040A5BA                 mov     eax, [ecx+_KTHREAD.___u25.WaitListEntry.Flink]
.text:0040A5BD                 mov     edx, [ecx+_KTHREAD.___u25.WaitListEntry.Blink]
.text:0040A5C0                 mov     [edx+LIST_ENTRY.Flink], eax
.text:0040A5C2                 mov     [eax+LIST_ENTRY.Blink], edx

八.参考资料

  • 《Windows内核原理与实现》

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2022-1-10 13:56 被1900编辑 ,原因:
收藏
免费 3
支持
分享
最新回复 (3)
雪    币: 3736
活跃值: (3867)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
如果没记错,《windows内核原理与实现》这本书的图5.13画错了,楼主可以看wrk的代码核实一下。
2022-6-28 19:51
0
雪    币: 22411
活跃值: (25361)
能力值: ( LV15,RANK:910 )
在线值:
发帖
回帖
粉丝
3
fengyunabc 如果没记错,《windows内核原理与实现》这本书的图5.13画错了,楼主可以看wrk的代码核实一下。
这个任务就交给你们了
2022-6-29 14:48
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
嗨感谢分享,我想请教一个问题,假设一个线程调用waitforsingleobject陷入了等待,在这个函数里死循环的的简化逻辑是:检查信号量的值-陷入等待-被唤醒-检查信号量的值-陷入等待…我的问题是:在“被唤醒”的这个步骤里,已经有将信号量的值递减的操作了,因为唤醒一个线程需要消耗一个值,不仿假设信号量的值因此从1变为了0。被唤醒的线程进入了新的一轮循环,检查信号量的值,因为是零,所以又陷入了等待,那这个过程岂不是无休无止了吗?似乎跳不出这个死循环?
2023-4-13 07:44
0
游客
登录 | 注册 方可回帖
返回
//