ʕ •ᴥ•ʔ ɔ:
线程只能自杀,不能他杀。”他杀”只不过是“他”在目标线程上挂一个APC,然后让目标线程在适当的时候去执行这个APC。
练习:
从KiDeliverApc开始将0环和返回三环的栈画出来.(想学好调试这道题必做)
1、分析TerminateThread/SuspendThread/ResumeThread是如何实现的(从3环开始分析). 控制别的线程,就是通过APC实现的 2、自己编写代码向某个线程插入一个用户APC. 使用现成的API就可以 体会什么是APC 3、自己分析APC的插入过程
1 TerminateThread分析 TerminateThread函数 :终止一个线程。
1 2 3 4 BOOL __stdcall TerminateThread ( [in, out] HANDLE hThread, [in] DWORD dwExitCode ) ;
dwExitCode:使用GetExitCodeThread 函数检索线程的退出值。
函数调用过程:
1 2 TerminateThread --> NtTerminateThread(102 h) --> NtTerminateThread --> PspTerminateThreadByPointer -->PspExitThread/KeInitializeApc
1.1 NtTerminateThread NtTerminateThread
函数:将指定的线程终止(This function causes the specified thread to terminate.)。
1 2 3 4 NTSTATUS __stdcall NtTerminateThread ( IN HANDLE ThreadHandle OPTIONAL, IN NTSTATUS ExitStatus )
逆向分析函数流程如下:
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 if (ThreadHandle == NULL ){ if (EPROCESS.ActiveThreads != 1 ) { PspTerminateThreadByPointer(Thread, ExitStatus); }else { return 0xC00000DB ; } } if (ThreadHandle != NULL ){ if (ThreadHandle == -2 ) { PspTerminateThreadByPointer(Thread, ExitStatus); } ObReferenceObjectByHandle(); if (Object != CurrentThread) { PspTerminateThreadByPointer(Thread, ExitStatus); ObfDereferenceObject(Thread); }else { ObfDereferenceObject(Thread) PspTerminateThreadByPointer(Thread, ExitStatus); } }
逆向分析如下:
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 PAGE:004F BB8F ; --------------------------------------------------------------------------- PAGE:004F BB92 align 8 PAGE:004F BB98 PAGE:004F BB98 ; =============== S U B R O U T I N E ======================================= PAGE:004F BB98 PAGE:004F BB98 ; Attributes: bp-based frame PAGE:004F BB98 PAGE:004F BB98 ; NTSTATUS __stdcall NtTerminateThread (HANDLE ThreadHandle, NTSTATUS ExitStatus) PAGE:004FBB98 _NtTerminateThread@8 proc near //DATA XREF: .text:0042D858↑o PAGE:004F BB98 PAGE:004F BB98 AccessMode = byte ptr -4 PAGE:004F BB98 ThreadHandle = dword ptr 8 PAGE:004F BB98 ExitStatus = dword ptr 0 Ch PAGE:004F BB98 PAGE:004F BB98 mov edi, edi PAGE:004F BB9A push ebp PAGE:004F BB9B mov ebp, esp PAGE:004F BB9D push ecx PAGE:004F BB9E push ebx PAGE:004F BB9F push esi PAGE:004F BBA0 push edi PAGE:004F BBA1 xor edi, edi PAGE:004F BBA3 mov eax, large fs:124 h PAGE:004F BBA9 cmp [ebp+ThreadHandle], edi PAGE:004F BBAC mov esi, eax PAGE:004F BBAE jnz short loc_4FBBC3 PAGE:004F BBAE PAGE:004F BBAE PAGE:004F BBAE ; PAGE:004F BBAE PAGE:004F BBAE PAGE:004F BBAE PAGE:004F BBAE PAGE:004F BBAE PAGE:004F BBAE PAGE:004F BBAE PAGE:004F BBAE PAGE:004F BBAE PAGE:004F BBB0 mov eax, [esi+_KTHREAD.ApcState.Process] PAGE:004F BBB0 PAGE:004F BBB0 PAGE:004F BBB0 PAGE:004F BBB0 ; PAGE:004F BBB0 PAGE:004F BBB0 PAGE:004F BBB0 PAGE:004F BBB3 cmp [eax+_EPROCESS.ActiveThreads], 1 PAGE:004F BBB3 PAGE:004F BBB3 PAGE:004F BBB3 PAGE:004F BBBA jnz short loc_4FBBFF PAGE:004F BBBA PAGE:004F BBBA PAGE:004F BBBA PAGE:004F BBBC mov eax, 0 C00000DBh PAGE:004F BBBC PAGE:004F BBBC PAGE:004F BBBC PAGE:004F BBBC PAGE:004F BBC1 jmp short loc_4FBC1E PAGE:004F BBC1 PAGE:004F BBC3 ; --------------------------------------------------------------------------- PAGE:004F BBC3 PAGE:004F BBC3 loc_4FBBC3: PAGE:004F BBC3 cmp [ebp+ThreadHandle], 0F FFFFFFEh PAGE:004F BBC3 PAGE:004F BBC3 PAGE:004F BBC3 ; PAGE:004F BBC3 PAGE:004F BBC3 PAGE:004F BBC3 PAGE:004F BBC3 PAGE:004F BBC3 PAGE:004F BBC3 PAGE:004F BBC3 PAGE:004F BBC3 PAGE:004F BBC3 PAGE:004F BBC7 jz short loc_4FBBFF PAGE:004F BBC9 mov al, [esi+_KTHREAD.PreviousMode] PAGE:004F BBCF push 0 PAGE:004F BBD1 mov [ebp+AccessMode], al PAGE:004F BBD4 lea eax, [ebp+ThreadHandle] PAGE:004F BBD7 push eax PAGE:004F BBD8 push dword ptr [ebp+AccessMode] PAGE:004F BBDB push _PsThreadType PAGE:004F BBE1 push 1 PAGE:004F BBE3 push [ebp+ThreadHandle] PAGE:004F BBE6 call _ObReferenceObjectByHandle@24 PAGE:004F BBE6 PAGE:004F BBE6 PAGE:004F BBE6 PAGE:004F BBE6 PAGE:004F BBE6 PAGE:004F BBE6 PAGE:004F BBE6 PAGE:004F BBEB mov edi, eax PAGE:004F BBED test edi, edi PAGE:004F BBEF jl short loc_4FBC1C PAGE:004F BBF1 mov ebx, [ebp+ThreadHandle] PAGE:004F BBF4 cmp ebx, esi PAGE:004F BBF6 jnz short loc_4FBC0A PAGE:004F BBF8 mov ecx, ebx PAGE:004F BBFA call @ObfDereferenceObject@4 PAGE:004F BBFA PAGE:004F BBFA PAGE:004F BBFA PAGE:004F BBFF PAGE:004F BBFF loc_4FBBFF: PAGE:004F BBFF PAGE:004F BBFF push [ebp+ExitStatus] PAGE:004F BC02 push esi PAGE:004F BC03 call _PspTerminateThreadByPointer@8 PAGE:004F BC03 PAGE:004F BC03 PAGE:004F BC03 PAGE:004F BC03 PAGE:004F BC03 PAGE:004F BC03 PAGE:004F BC03 PAGE:004F BC03 PAGE:004F BC03 PAGE:004F BC08 jmp short loc_4FBC1C PAGE:004F BC0A ; --------------------------------------------------------------------------- PAGE:004F BC0A PAGE:004F BC0A loc_4FBC0A: PAGE:004F BC0A push [ebp+ExitStatus] PAGE:004F BC0D push ebx PAGE:004F BC0E call _PspTerminateThreadByPointer@8 PAGE:004F BC0E PAGE:004F BC0E PAGE:004F BC0E PAGE:004F BC0E PAGE:004F BC0E PAGE:004F BC0E PAGE:004F BC0E PAGE:004F BC0E PAGE:004F BC0E PAGE:004F BC13 mov ecx, ebx PAGE:004F BC15 mov edi, eax PAGE:004F BC17 call @ObfDereferenceObject@4 PAGE:004F BC17 PAGE:004F BC17 PAGE:004F BC17 PAGE:004F BC1C PAGE:004F BC1C loc_4FBC1C: PAGE:004F BC1C PAGE:004F BC1C mov eax, edi PAGE:004F BC1E PAGE:004F BC1E loc_4FBC1E: PAGE:004F BC1E pop edi PAGE:004F BC1F pop esi PAGE:004F BC20 pop ebx PAGE:004F BC21 leave PAGE:004F BC22 retn 8 PAGE:004F BC22 _NtTerminateThread@8 endp PAGE:004F BC22 PAGE:004F BC22 ; ---------------------------------------------------------------------------
NULL句柄的含义 逆向分析得,句柄值的处理:
句柄值
含义
0
ActiveThreads = KTHREAD.ApcState.Process.ActiveThreads - ActiveThreads != 1,结束当前线程。 - ActiveThreads == 1,直接返回,也就是不能结束当前进程的最后一个线程。 在分析NtTerminateThread(IN HANDLE ThreadHandle OPTIONAL,IN NTSTATUS ExitStatus)
时逆得的。
-1
代表当前进程
-2
代表当前线程
一个为4倍数的正数
句柄表中的索引
负数
其值的绝对值为System进程中的句柄值 ,ObpKernelHandleTable,仅限于内核模式的函数可以引用。
还需要说说明的是:
Handle == -2,结束当前线程。
Handle == NULL
ActiveThreads != 1,结束当前线程。
ActiveThreads == 1,直接返回,不可 结束当前线程
1.2 PspTerminateThreadByPointer PspTerminateThreadByPointer
函数:将指定的线程终止(This function causes the specified thread to terminate.)。
1 2 3 4 NTSTATUS __stdcall PspTerminateThreadByPointer ( IN PETHREAD Thread, IN NTSTATUS ExitStatus ) ;
逆向分析函数流程如下:
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 if (ETHREAD.CrossThreadFlags == 0x40 ){ PspCatchCriticalBreak(); } if (Object == CurrentThread) { CrossThreadFlags.Terminated == 1 ; PspExitThread(); }else { if (ETHREAD.CrossThreadFlags == 0x10 ) { return 0xC0000022 ; }else { LPVOID pMem = NULL ; while (1 ) { pMem = ExAllocatePoolWithTag(); if (pMem != NULL ) { Old_CrossThreadFlags.Terminated = CrossThreadFlags.Terminated; CrossThreadFlags.Terminated = 1 ; break ; } KeDelayExecutionThread(); } if (Old_CrossThreadFlags.Terminated == 1 ) { ExFreePoolWithTag(); return ; }else { KeInitializeApc(); KeInsertQueueApc(); ExFreePoolWithTag(); return ; } } }
逆向分析得这样的结论:如果要Kill自己,使用PspExitThread
函数;如果要Kill其他线程,则需要插入APC去执行 。
《Windows内核原理与实现3.4 P147》
在PspTerminateThreadByPointer
函数中,如果当前线程被终止,则设置结束标志PS_CROSS_THREAD_FLAGS_TERMINATED
,并调用PspExitThread
退出线程,此函数执行实际的终止过程,并且不返回。如果不是当前线程,则在该线程中插人一个内核模式APC,为它指定PsExitSpecialApc
、PspExitApcRundown
和PspExitNormalApc
例程,由它们完成真正的终止过程。关于AP℃对象的交付过程,即它指定的这些例程是如何被执行的,请参考本书5.2.6节。最终PsExitSpecialApc
函数被执行,它也调用PspExitThread
函数来完成终止过程,所以下面只讨论PspExitThread函数的处理过程。
源码如下:
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 NTSTATUS PspTerminateThreadByPointer ( IN PETHREAD Thread, IN NTSTATUS ExitStatus, IN BOOLEAN DirectTerminate ) { NTSTATUS Status; PKAPC ExitApc=NULL ; ULONG OldMask; PAGED_CODE(); if (Thread->CrossThreadFlags & PS_CROSS_THREAD_FLAGS_BREAK_ON_TERMINATION) { PspCatchCriticalBreak("Terminating critical thread 0x%p (in %s)\n" , Thread, THREAD_TO_PROCESS(Thread)->ImageFileName); } if (DirectTerminate && Thread == PsGetCurrentThread()) { ASSERT (KeGetCurrentIrql() < APC_LEVEL); PS_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_TERMINATED); PspExitThread (ExitStatus); } else { if (IS_SYSTEM_THREAD (Thread)) { return STATUS_ACCESS_DENIED; } Status = STATUS_SUCCESS; while (1 ) { ExitApc = (PKAPC) ExAllocatePoolWithTag (NonPagedPool, sizeof (KAPC), 'xEsP'); if (ExitApc != NULL ) { break ; } KeDelayExecutionThread(KernelMode, FALSE, &ShortTime); } OldMask = PS_TEST_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_TERMINATED); if ((OldMask & PS_CROSS_THREAD_FLAGS_TERMINATED) == 0 ) { KeInitializeApc (ExitApc, PsGetKernelThread (Thread), OriginalApcEnvironment, PsExitSpecialApc, PspExitApcRundown, PspExitNormalApc, KernelMode, ULongToPtr (ExitStatus)); if (!KeInsertQueueApc (ExitApc, ExitApc, NULL , 2 )) { ExFreePool (ExitApc); Status = STATUS_UNSUCCESSFUL; } else { KeForceResumeThread (&Thread->Tcb); } } else { ExFreePool (ExitApc); } } return Status; }
ETHREAD.CrossThreadFlags CrossThreadFlags域是一些针对跨线程访问的标志位(《Windows内核原理与实现3.4 P128》),包括:
Terminated(线程已执行终止操作)
DeadThread(创建失败)
HideFromDebugger(该线程对于调试器不可见)
Activelmpersonationlnfo(线程正在模仿)
SystemThread(是一个系统线程)
HardErrorsAreDisabled(对于该线程,硬件错误无效)
BreakOnTermination(调试器在线程终止时停下该线程,即要求退出进程时中断到调试器)
SkipCreationMsg(不向调试器发送创建消息)
SkipTerminationMsg(不向调试器发送终止消息)。
在创建线程时,经常将CrossThreadFlags.SystemThread设置为1,来伪装成系统线程,这样普通线程就没有办法将自己Kill了。
由定义可以知道,低1字节的8bit为常用位,其他高位为未定义或者不常用。
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 union { ULONG CrossThreadFlags; struct { ULONG Terminated : 1 ; ULONG DeadThread : 1 ; ULONG HideFromDebugger : 1 ; ULONG ActiveImpersonationInfo : 1 ; ULONG SystemThread : 1 ; ULONG HardErrorsAreDisabled : 1 ; ULONG BreakOnTermination : 1 ; ULONG SkipCreationMsg : 1 ; ULONG SkipTerminationMsg : 1 ; }; }; ACCESS_MASK GrantedAccess; #define PS_CROSS_THREAD_FLAGS_TERMINATED 0x00000001UL #define PS_CROSS_THREAD_FLAGS_DEADTHREAD 0x00000002UL #define PS_CROSS_THREAD_FLAGS_HIDEFROMDBG 0x00000004UL #define PS_CROSS_THREAD_FLAGS_IMPERSONATING 0x00000008UL #define PS_CROSS_THREAD_FLAGS_SYSTEM 0x00000010UL #define PS_CROSS_THREAD_FLAGS_HARD_ERRORS_DISABLED 0x00000020UL #define PS_CROSS_THREAD_FLAGS_BREAK_ON_TERMINATION 0x00000040UL #define PS_CROSS_THREAD_FLAGS_SKIP_CREATION_MSG 0x00000080UL #define PS_CROSS_THREAD_FLAGS_SKIP_TERMINATION_MSG 0x00000100UL
KeDelayExecutionThread KeDelayExecutionThread
函数:将当前线程置于指定间隔内可被唤醒或不可吵醒的等待状态。
1 2 3 4 5 6 NTSTATUS __stdcall KeDelayExecutionThread ( [in] KPROCESSOR_MODE WaitMode, [in] BOOLEAN Alertable, [in] PLARGE_INTEGER Interval ) ;
1.3 PspExitThread PspExitThread
函数的功能:This function causes the currently executing thread to terminate . This function is only called from within the process structure. It is called either from mainline exit code to exit the current thread, or from PsExitSpecialApc (as a piggyback to user-mode PspExitNormalApc).
函数原型:
1 VOID __stdcall PspExitThread (IN NTSTATUS ExitStatus)
这个函数相当复杂,等我有时间,学到更多知识后再来逆😭。
2 SuspendThread SuspendThread
函数:挂起/暂停指定的线程。
64位应用程序可以使用Wow64SuspendThread 函数挂起WOW64线程。
WOW64 :WOW64(Windows-On-Windows 64bit)是X64 Windows操作系统的一个子系统,为32位应用程序提供运行环境。
WOW32 :负责在32位Windows系统上运行16位应用程序。
Wow64SuspendThread :此功能适用于64位应用程序。32位Windows不支持它;此类调用失败,并将最后一个错误代码设置为ERROR_INVALID_FUNCTION
。32位应用程序可以在WOW64线程上调用此函数;结果与调用SuspendThread 函数相同。在64位程序开发中,编译器识别本函数,可能无法识别SuspendThread。
函数原型如下:
1 DWORD __stdcall SuspendThread ([in] HANDLE hThread)
参数hThread
:待挂起的线程的句柄。
返回值:如果函数成功,返回值是线程之前的暂停计数 ;否则,它是(DWORD) -1
。要获取扩展错误信息,请使用GetLastError 函数。
暂停计数(SuspendCount
):也叫做挂起计数 , 记录线程被挂起的次数 。每个线程都有一个暂停计数(最大值为MAXIMUM_SUSPEND_COUNT )。
1 2 3 4 KTHREAD.SuspendCount(+0x1b9 ) #define MAXIMUM_SUSPEND_COUNT MAXCHAR #define MAXCHAR 255
如果挂起计数大于零,则线程将被挂起;否则,线程不会挂起,并且符合执行条件。调用``SuspendThread`导致目标线程的挂起计数增加。尝试超过最大挂起计数会导致错误而不增加计数。ResumeThread 函数会减去挂起线程的挂起计数。
函数调用过程:
1 SuspendThread --> NtSuspendThread(0F Eh) --> NtSuspendThread --> PsSuspendThread --> KeSuspendThread
2.1 NtSuspendThread NtSuspendThread
函数:This function suspends the target thread, and optionally returns the previous suspend count.
1 2 3 4 NTSTATUS __stdcall NtSuspendThread ( IN HANDLE ThreadHandle, OUT PULONG PreviousSuspendCount OPTIONAL )
注意这里的PreviousSuspendCount
是一个地址,指向先前线程被挂起的次数 。
逆向分析函数流程如下:
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 if (PreviousMode != 0 ) { if (PreviousSuspendCount == NULL ) goto StartOperations; else { if (PreviousSuspendCount >= MmUserProbeAddress) { MmUserProbeAddress = NULL ; } } } StartOperations: Object = ObReferenceObjectByHandle(); PsSuspendThread(Thread,PreviousSuspendCount); ObfDereferenceObject(); try { if (PreviousSuspendCount != NULL ) { *PreviousSuspendCount = (DWORD)SuspendThread; } } except (EXCEPTION_EXECUTE_HANDLER) { st = GetExceptionCode(); } return st; #define EXCEPTION_EXECUTE_HANDLER 1 #define EXCEPTION_CONTINUE_SEARCH 0 #define EXCEPTION_CONTINUE_EXECUTION -1
MmUserProbeAddress、MmHighestUserAddress MmUserProbeAddress
是一个全局变量,指向一个划定用户空间和内核空间界限的地址。
与该值的比较用于确定地址是指向用户空间还是内核空间。
1 2 kd> dd MmUserProbeAddress 80563134 7f ff0000 80000000 7f feffff 00000000
可以看到MmUserProbeAddress == 0x7fff0000
,则用户空间(3环)可以访问的最大地址是0x7fff0000
。
源码中有:PreviousSuspendCount >= MmUserProbeAddress
,就会MmUserProbeAddress = NULL
。就会抛出异常,因为NULL
地址不可写。实际上源码中用了try-except
,源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 try { Mode = KeGetPreviousMode(); if ( Mode != KernelMode ) { if (ARGUMENT_PRESENT(PreviousSuspendCount)) { ProbeForWriteUlong(PreviousSuspendCount); } } } except (EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode(); } #define ARGUMENT_PRESENT(p) ((p) != NULL) #define ProbeForWriteUlong(Address) { if ((Address) >= (ULONG * const )MM_USER_PROBE_ADDRESS) { *(volatile ULONG * const )MM_USER_PROBE_ADDRESS = 0 ; } *(volatile ULONG *)(Address) = *(volatile ULONG *)(Address); }
可以看到,当try
中的ProbeForWriteUlong(PreviousSuspendCount)
执行到*(volatile ULONG * const)MM_USER_PROBE_ADDRESS = 0
就会触发异常(因为NULL地址不可读写),然后执行except
中的代码。
0环下大部分的Nt-
函数都会对来自3环调用的参数进行检查,常用到MmUserProbeAddress
。
1 2 3 4 5 6 7 8 9 #define MM_HIGHEST_USER_ADDRESS *MmHighestUserAddress #define MM_SYSTEM_RANGE_START *MmSystemRangeStart #define MM_USER_PROBE_ADDRESS *MmUserProbeAddress #define PTE_BASE 0xc0000000 MmHighestUserAddress = 0x7ffeffff MmUserProbeAddress = 0x7fff0000 MmSystemRangeStart = 0x80000000 MmUserProbeAddress = MmHighestUserAddress + 1
用户空间可以访问的最高地址是MmHighestUserAddress(0x7ffeffff
),从MmUserProbeAddress(0x7fff0000
)开始就不能访问了。一般文献中讲Windows系统中用户空间与系统空间的分界线是0x80000000
,而这里之所以是0x7fff0000
而不是0x80000000
,是因为在分界下面留了64KB的空间不让访问,以此作为隔离区。(64KB = 2^6*2^10
共16位)
MmUserProbeAddress
是一个界限,该地址不可用 。
3环可用地址 <= MmHighestUserAddress
3环可用地址 < MmUserProbeAddress
这部分内容的讲解在《Windows内核情景分析5.2 P253》有、《Windows内核原理与实现4.2 P207》。
volatile类型修饰符 volatile关键字是一种类型修饰符。Volatile意思是“易变的”,应该解释为“直接存取原始内存地址”比较合适。这个关键词volatile总是与优化有关,多线程读取变量时也用到。
使用volatile
修饰的变量,在编译这样的变量时,编译器一般都会作减少存取内存的优化,但有可能会读脏数据。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问 。
但是也不能频繁的使用volatile
,否则会影响程序运行的性能。具体可以参考C/C++ 中 volatile 关键字详解 。
以下举个例子说明一下:
不使用volatile
时编译Release版本(vc6编译器):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include "stdafx.h" int TestNum = 0 ;int main (int argc, char * argv[]) { int temp = 0 ; TestNum = 2 ; temp = TestNum; TestNum = 5 ; getchar(); return 0 ; }
Release版本编译代码如下:
1 2 3 4 5 00401000 mov eax,dword ptr ds:[0x407034 ]00401005 mov dword ptr ds:[0x4098A8 ],0x5 0040100F dec eax 00401010 mov dword ptr ds:[0x407034 ],eax ...
0x4098A8
即为变量TestNum的地址,由于源代码最后一次赋值是TestNum = 5
,则代码TestNum = 2
是无用的被优化了。
代码temp = TestNum
中,赋值后temp
没有被用到,在代码中也被优化了。
使用volatile
修饰变量时编译Release版本(vc6编译器):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include "stdafx.h" volatile int TestNum = 0 ;int main (int argc, char * argv[]) { int temp = 0 ; TestNum = 2 ; temp = TestNum; TestNum = 5 ; getchar(); return 0 ; }
Release版本编译代码如下:
1 2 3 4 5 6 7 00401000 mov dword ptr ds:[0x4098A8 ],0x2 0040100 A mov eax,dword ptr ds:[0x4098A8 ]0040100F mov dword ptr ds:[0x4098A8 ],0x5 00401019 mov eax,dword ptr ds:[0x407034 ]0040101 E dec eax 0040101F mov dword ptr ds:[0x407034 ],eax ...
可以看到,两次对TestNum
的赋值都没有被优化,对于取地址0x4098A8
中的值给temp
赋值的代码也没有优化。
家宇花园
2.2 PsSuspendThread PsSuspendThread
函数:This function suspends the target thread, and optionally returns the previous suspend count.
1 2 3 4 NTSTATUS __stdcall PsSuspendThread ( IN PETHREAD Thread, OUT PULONG PreviousSuspendCount OPTIONAL )
逆向分析函数流程如下:
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 if (Object == CurrentThread) { KeSuspendThread(Thread); Status = STATUS_SUCCESS; }else { bRet = ExAcquireRundownProtectio(RunRef); if (bRet == FALSE) { Status = 0xC000004B ; }else { if (CrossThreadFlags.Terminated == 1 ) { Status = 0xC000004B ; }else { KeSuspendThread(Thread); Status = STATUS_SUCCESS; if (CrossThreadFlags.Terminated == 1 ) { KeForceResumeThread(); SuspendCount = 0 ; Status = 0xC000004B ; } } ExReleaseRundownProtection(); } } if (PreviousSuspendCount != 0 ) { PreviousSuspendCount = SuspendCount; } return Status;
ExAcquireRundownProtection ExAcquireRundownProtection
函数:尝试获取共享对象的运行保护,以便调用方可以安全地访问该对象。
当运行保护生效时,驱动程序可以安全地访问对象,而不会有在访问完成之前删除该对象的风险。
1 2 3 NTKERNELAPI BOOLEAN FASTCALL ExAcquireRundownProtection ( IN PEX_RUNDOWN_REF RunRef )
参数RunRef
:指向先前对 ExInitializeRundownProtection 例程的调用初始化的EX_RUNDOWN_REF 结构的指针。 运行保护例程使用此结构来跟踪关联的共享对象的运行状态。 此结构对驱动程序不透明。
返回值:如果例程成功获取调用方的运行保护,ExAcquireRundownProtection 将返回 TRUE 。 否则,它将返回 FALSE 。 FALSE 的返回值指示对象的运行已启动,并且该对象必须被视为无效,不可再访问该对象。
停止运行保护 (Run-Down Protection):从 Windows XP 开始,内核驱动就支持停运保护机制。驱动通过停运机制可以安全地 访问在系统内存中的对象,通常这些对象是由其他内核驱动创建和销毁的。
拥有共享对象的驱动程序可以允许其他驱动程序获取和释放对象的运行时保护。 当停运保护生效时,除对象的所有者外,其他驱动可以访问该对象而不用担心在访问结束前该对象会被其所有者删除 。在访问开始之前,要访问的驱动会提出对目标对象实施停运保护的请求。对于一个存活周期较长的对象来说,这类请求几乎都是被允许的。当访问结束时,执行访问的驱动会卸除之前对对象实施的停运保护。
参考:同步下的资源互斥:停运保护(Run-Down Protection)机制 。
2.3 KeSuspendThread KeSuspendThread
函数:将线程挂起。如果挂起计数溢出最大挂起计数,则会引发一个条件。
1 2 3 ULONG __stdcall KeSuspendThread ( IN PKTHREAD Thread )
逆向分析函数流程如下:
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 #define MAXIMUM_SUSPEND_COUNT 255 if (SuspendCount == MAXIMUM_SUSPEND_COUNT) { Status = 0xC000004A ; ExRaiseStatus(); } if (ApcQueueable == 1 ) { Old_SuspendCount = SuspendCount; SuspendCount++; if (Old_SuspendCount != 0 ) { return Old_SuspendCount; }else { if (FreezeCount == 0 ) { bRet = (BOOLEAN)KiInsertQueueApc(); if (bRet == FALSE) Thread->SuspendSemaphore.Header.SignalState--; } } } return Old_SuspendCount;
在调用SuspendThread时,SuspendCount 自加1;如果大于0表示挂起了。调用ResumeThread时,SuspendCount 自减1;如果等于0,系统会恢复线程。
当挂起和恢复时,FreezeCount必须为0,否则挂起和恢复操作都直接pass了。所以,可以修改 SuspendCount 和 FreezeCount来达到反挂起的目的。
源码如下:
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 ULONG KeSuspendThread ( IN PKTHREAD Thread ) { KLOCK_QUEUE_HANDLE LockHandle; ULONG OldCount; ASSERT_THREAD(Thread); ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); KeAcquireInStackQueuedSpinLockRaiseToSynch(&Thread->ApcQueueLock, &LockHandle); KiLockDispatcherDatabaseAtSynchLevel(); OldCount = Thread->SuspendCount; if (OldCount == MAXIMUM_SUSPEND_COUNT) { KiUnlockDispatcherDatabaseFromSynchLevel(); KeReleaseInStackQueuedSpinLock(&LockHandle); ExRaiseStatus(STATUS_SUSPEND_COUNT_EXCEEDED); } if (Thread->ApcQueueable == TRUE) { Thread->SuspendCount += 1 ; if ((OldCount == 0 ) && (Thread->FreezeCount == 0 )) { if (KiInsertQueueApc(&Thread->SuspendApc, RESUME_INCREMENT) == FALSE) { Thread->SuspendSemaphore.Header.SignalState -= 1 ; } } } KiUnlockDispatcherDatabaseFromSynchLevel(); KeReleaseInStackQueuedSpinLock(&LockHandle); return OldCount; }
KTHREAD.SuspendCount SuspendCount :记录线程被挂起的次数。
在调用SuspendThread时,SuspendCount 自加1;如果大于0表示挂起了。
调用ResumeThread时,SuspendCount 自减1;如果等于0,系统会恢复线程。
SuspendCount
的最大值为MAXIMUM_SUSPEND_COUNT
源码的定义为(线程能够被挂起的最大次数):
1 2 #define MAXIMUM_SUSPEND_COUNT MAXCHAR #define MAXCHAR 255
源码是:0xFF
,这里逆向分析的是0x7F
,为一半,不知道为啥 。
当挂起和恢复时,FreezeCount必须为0。
KTHREAD.FreezeCount 互联网上没有查到这个字段的详细解释,《Windows内核原理与实现》和《Windows内核情景分析》也没有解释,不过经过你想分析有这样的结论:
当线程首次挂起 和恢复 运行时,必须有FreezeCount == 0
才会继续执行。
所以,可以修改 SuspendCount 和 FreezeCount来达到反挂起的目的。
3 ResumeThread ResumeThread
函数:减少线程的挂起计数。当挂起计数降至零时,线程的执行将恢复。
⚠️注意 :该函数并不是直接将线程恢复,而只是减少线程的挂起计数。当挂起计数降至零时,线程的执行将恢复。
函数原型如下:
1 DWORD __stdcall ResumeThread ([in] HANDLE hThread)
返回值 :如果函数成功,返回值是线程之前的暂停计数。如果函数失败,返回值为(DWORD)-1。要获取扩展错误信息,请调用GetLastError 。
函数调用过程:
1 ResumeThread --> NtResumeThread(0 CEh) --> NtResumeThread --> PsResumeThread --> KeResumeThread
3.1 NtResumeThread NtResumeThread
函数:This function resumes a thread that was previously suspened.
1 2 3 4 NTSTATUS __stdcall NtResumeThread ( IN HANDLE ThreadHandle, OUT PULONG PreviousSuspendCount OPTIONAL )
逆向分析函数流程如下:
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 NtResumeThread(HANDLE ThreadHandle, PULONG SuspendCount) if (PreviousMode != 0 ) { try { if (SuspendCount >= MmUserProbeAddress) { MmUserProbeAddress = 0 ; } }except (EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode(); } } Object = ObReferenceObjectByHandle(); PsResumeThread(); ObfDereferenceObject(); try { if ( ((PULONG))SuspendCount ) *(PULONG)SuspendCount = OUT_SuspendCount; }except (EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode (); } return STATUS_SUCCESS;
⚠️注意:该函数只是简单的对参数进行了检查,然后将句柄转换为对象地址,最后调用函数PsResumeThread
。
并没有看到怎么处理 :减少线程的挂起计数。当挂起计数降至零时,线程的执行将恢复。
源码如下:
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 NTSTATUS NtResumeThread ( IN HANDLE ThreadHandle, OUT PULONG PreviousSuspendCount OPTIONAL ) { PETHREAD Thread; NTSTATUS st; KPROCESSOR_MODE Mode; ULONG LocalPreviousSuspendCount; PAGED_CODE(); try { Mode = KeGetPreviousMode(); if ( Mode != KernelMode ) { if (ARGUMENT_PRESENT(PreviousSuspendCount)) ProbeForWriteUlong(PreviousSuspendCount); } } except (EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode(); } st = ObReferenceObjectByHandle( ThreadHandle, THREAD_SUSPEND_RESUME, PsThreadType, Mode, (PVOID *)&Thread, NULL ); if ( !NT_SUCCESS(st) ) { return st; } PsResumeThread (Thread, &LocalPreviousSuspendCount); ObDereferenceObject (Thread); try { if (ARGUMENT_PRESENT (PreviousSuspendCount)) { *PreviousSuspendCount = LocalPreviousSuspendCount; } } except (EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode (); } return STATUS_SUCCESS; }
3.2 PsResumeThread PsResumeThread
函数:This function resumes a thread that was previously suspened.
1 2 3 4 NTSTATUS __stdcal PsResumeThread ( IN PETHREAD Thread, OUT PULONG PreviousSuspendCount OPTIONAL )
逆向分析函数流程如下:
1 2 ulRet = (ULONG)KeResumeThread(); if (PreviousSuspendCount) *PreviousSuspendCount = ulRet;
函数太简单了,下面看一下源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 NTSTATUS PsResumeThread ( IN PETHREAD Thread, OUT PULONG PreviousSuspendCount OPTIONAL ) { ULONG LocalPreviousSuspendCount; LocalPreviousSuspendCount = (ULONG) KeResumeThread(&Thread->Tcb); if (ARGUMENT_PRESENT (PreviousSuspendCount)) { *PreviousSuspendCount = LocalPreviousSuspendCount; } return STATUS_SUCCESS; }
⚠️注意:
并没有看到怎么处理 :减少线程的挂起计数。当挂起计数降至零时,线程的执行将恢复。
3.3 KeResumeThread KeResumeThread
函数:此函数恢复挂起线程的执行。如果指定的线程未挂起,则不执行任何操作。
1 ULONG __stdcall KeResumeThread ( IN PKTHREAD Thread )
返回值:The previous suspend count.
逆向分析函数流程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 if (SuspendCount != 0 ) { SuspendCount -= 1 ; if (SuspendCount != 0 ) return SuspendCount; else { if (FreezeCount == 0 ) { SuspendSemaphore.Header.SignalState += 1 ; KiWaitTest(); } } } return SuspendCount;
在这里我们看到了怎么处理 :减少线程的挂起计数。当挂起计数降至零时,线程的执行将恢复。
源代码如下:
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 ULONG KeResumeThread ( IN PKTHREAD Thread ) { ULONG OldCount; KIRQL OldIrql; ASSERT_THREAD(Thread); ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); KiLockDispatcherDatabase(&OldIrql); OldCount = Thread->SuspendCount; if (OldCount != 0 ) { Thread->SuspendCount -= 1 ; if ((Thread->SuspendCount == 0 ) && (Thread->FreezeCount == 0 )) { Thread->SuspendSemaphore.Header.SignalState += 1 ; KiWaitTest(&Thread->SuspendSemaphore, RESUME_INCREMENT); } } KiUnlockDispatcherDatabase(OldIrql); return OldCount; }