SuspendThread 挂起线程
调用链
SuspendThread
→ KERNEL32!SuspendThreadStub
→ KERNELBASE!SuspendThread
→ ntdll!NtSuspendThread
→ PsSuspendThread
→ KeSuspendThread
→ KiSuspendThread
→ KiInsertQueueApc
过程分析
未挂起前:
- 查看线程的 apc 列表:(空的,表示目前没有 apc)

- 查看线程的结构,看看 SuspendCount,通过
dt _KTHREAD ffffd3071fedd080

通过 SuspendThread
挂起线程后,再查看一下状态:
- 线程的 apc 列表,增加了一个
ffffd3071fedd308
的 APC 对象

- KTHREAD 中的 SuspendCount 也增加了

- 再详细的看下增加的 APC 中包含什么内容

基本发现,就是塞进去了一个 nt!KiSchedulerApc
的方法。猜测这个方法是一直卡住,执行不完的。(Win2003中KiSuspendThread
逻辑就是等待 SuspendCount 变成0,KiSchedulerApc
中应该也是类似的逻辑)
💡 KiSchedulerApc
是在 Win10 22H2 上看到的,在 KiInsertQueueApc
中通过 Thread→SchedulerApc
塞进去的KiSchedulerApc
(结合UAC的 token 校验不过,直接终止,猜测其中包含 token 的判断逻辑);查看 Win2003 的代码(reactos也是)是通过 Thread→SuspendApc
,塞进去的 KiSuspendThread
。代码如下:
VOID
KiSuspendThread (
IN PVOID NormalContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)
/*++
Routine Description:
This function is the kernel routine for the builtin suspend APC of a
thread. It is executed as the result of queuing the builtin suspend
APC and suspends thread execution by waiting nonalerable on the thread's
builtin suspend semaphore. When the thread is resumed, execution of
thread is continued by simply returning.
Arguments:
NormalContext - Not used.
SystemArgument1 - Not used.
SystemArgument2 - Not used.
Return Value:
None.
--*/
{
PKTHREAD Thread;
UNREFERENCED_PARAMETER(NormalContext);
UNREFERENCED_PARAMETER(SystemArgument1);
UNREFERENCED_PARAMETER(SystemArgument2);
//
// Get the address of the current thread object and Wait nonalertable on
// the thread's builtin suspend semaphore.
//
Thread = KeGetCurrentThread();
KeWaitForSingleObject(&Thread->SuspendSemaphore,
Suspended,
KernelMode,
FALSE,
NULL);
return;
}
总结
可以看到其实挂起线程相对比较简单,只是插入了一个 APC,让此线程一直无法执行而已。
关于 APC 可以看这里:
ResumeThread 恢复线程
调用链
ResumeThread
→ KERNEL32!ResumeThreadStub
→ KERNELBASE!ResumeThread
→ ntdll!NtResumeThread
→ PsResumeThread
→ KeResumeThread
→ KiWaitTest
→ KiUnwaitThread
→ KiReadyThread
→ KiDeferredReadyThread
详细过程
KeResumeThread
代码如下:
ULONG
NTAPI
KeResumeThread(IN PKTHREAD Thread)
{
KLOCK_QUEUE_HANDLE ApcLock;
ULONG PreviousCount;
ASSERT_THREAD(Thread);
ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL);
/* Lock the APC Queue */
KiAcquireApcLockRaiseToSynch(Thread, &ApcLock);
/* Save the Old Count */
PreviousCount = Thread->SuspendCount;
/* Check if it existed */
if (PreviousCount)
{
/* Decrease the suspend count */
Thread->SuspendCount--;
/* Check if the thrad is still suspended or not */
if ((!Thread->SuspendCount) && (!Thread->FreezeCount))
{
/* Acquire the dispatcher lock */
KiAcquireDispatcherLockAtSynchLevel();
/* Signal the Suspend Semaphore */
Thread->SuspendSemaphore.Header.SignalState++;
KiWaitTest(&Thread->SuspendSemaphore.Header, IO_NO_INCREMENT);
/* Release the dispatcher lock */
KiReleaseDispatcherLockFromSynchLevel();
}
}
/* Release APC Queue lock and return the Old State */
KiReleaseApcLockFromSynchLevel(&ApcLock);
KiExitDispatcher(ApcLock.OldIrql);
return PreviousCount;
}
KeResumeThread
核心做了几件事:
- 根据 SuspendCount 和 FreezeCount 来判断是否需要恢复线程
- 通过发送信号量,调用
KiWaitTest
来恢复线程
KiWaitTest
的代码如下:
VOID
FASTCALL
KiWaitTest(IN PVOID ObjectPointer,
IN KPRIORITY Increment)
{
PLIST_ENTRY WaitEntry, WaitList;
PKWAIT_BLOCK WaitBlock;
PKTHREAD WaitThread;
PKMUTANT FirstObject = ObjectPointer;
NTSTATUS WaitStatus;
/* Loop the Wait Entries */
WaitList = &FirstObject->Header.WaitListHead;
WaitEntry = WaitList->Flink;
while ((FirstObject->Header.SignalState > 0) && (WaitEntry != WaitList))
{
/* Get the current wait block */
WaitBlock = CONTAINING_RECORD(WaitEntry, KWAIT_BLOCK, WaitListEntry);
WaitThread = WaitBlock->Thread;
WaitStatus = STATUS_KERNEL_APC;
/* Check the current Wait Mode */
if (WaitBlock->WaitType == WaitAny)
{
/* Easy case, satisfy only this wait */
WaitStatus = (NTSTATUS)WaitBlock->WaitKey;
KiSatisfyObjectWait(FirstObject, WaitThread);
}
/* Now do the rest of the unwait */
KiUnwaitThread(WaitThread, WaitStatus, Increment);
WaitEntry = WaitList->Flink;
}
}
KiWaitTest
主要做了以下事情:
- 通过等待对象找到对象的等待队列
- 将等待队列中的每一个线程通过
KiUnwaitThread
来进行唤醒 - 其中
KiSatisfyObjectWait
就是通过设置信号状态,唤醒等待线程、切换上下文来响应等待的线程(这里详述看
KiUnwaitThread
的代码如下:
VOID
FASTCALL
KiUnwaitThread(IN PKTHREAD Thread,
IN LONG_PTR WaitStatus,
IN KPRIORITY Increment)
{
/* Unlink the thread */
KiUnlinkThread(Thread, WaitStatus);
/* Tell the scheduler do to the increment when it readies the thread */
ASSERT(Increment >= 0);
Thread->AdjustIncrement = (SCHAR)Increment;
Thread->AdjustReason = AdjustUnwait;
/* Reschedule the Thread */
KiReadyThread(Thread);
}
KiUnwaitThread
主要分两块:
- KiUnlinkThread:主要将线程从它正在等待的内核对象队列中移除,即不等待任何对象
- KiReadyThread:主要根据线程的状态(优先级、是否被分页了等),将线程置于 ready 状态,待 CPU 调度
总结
所谓的恢复线程,就是将线程的各种状态修改成 Ready 状态,然后告诉系统,我可以被调度了,然后等待 CPU 调度。这里的核心在于线程的调度。
0