首先,我们将介绍两种与线程挂起有关的新方法。它们不是最具革命性或有用的,但我会尽力做到最好。
绕过过程冻结
这是Microsoft在19H1中添加的一个可爱的小线程创建标记。有没有想过为什么在线程创建标志中存在漏洞?好吧,这个漏洞已经充满了一个我称之为的标志THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE
(我不知道它的实际名字),其值自然是0x40
。
为了演示它的作用,我将展示PsSuspendProcess的工作原理:
NTSTATUS PsSuspendProcess(_EPROCESS* Process) { const auto currentThread = KeGetCurrentThread(); KeEnterCriticalRegionThread(currentThread); NTSTATUS status = STATUS_SUCCESS; if ( ExAcquireRundownProtection(&Process->RundownProtect) ) { auto targetThread = PsGetNextProcessThread(Process, nullptr); while ( targetThread ) { // Our flag in action if ( !targetThread->Tcb.MiscFlags.BypassProcessFreeze ) PsSuspendThread(targetThread, nullptr); targetThread = PsGetNextProcessThread(Process, targetThread); } ExReleaseRundownProtection(&Process->RundownProtect); } else status = STATUS_PROCESS_IS_TERMINATING; if ( Process->Flags3.EnableThreadSuspendResumeLogging ) EtwTiLogSuspendResumeProcess(status, Process, Process, 0); KeLeaveCriticalRegionThread(currentThread); return status; }
如您所见,NtSuspendProcess
该调用PsSuspendProcess
将仅忽略带有此标志的线程。另一个好处是该线程也不会被挂起NtDebugActiveProcess
!据我所知,一旦使用该线程创建了线程,就无法查询或禁用该标志,因此您不能对此做太多事情。
就其有用性而言,我想说这只是防止转储的一个好方法,当您在Processhacker中单击“暂停”时,会引起混乱,并且该过程继续进行,就像什么也没发生一样。
例子
例如,这是一个有点有趣的代码,它将继续打印I am running
。我敢肯定,在倒车时看到这一点会引起很多困惑,为什么地狱会中止他自己的过程。
#define THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE 0x40 NTSTATUS printer(void*) { while(true) { std::puts("I am running\n"); Sleep(1000); } return STATUS_SUCCESS; } HANDLE handle; NtCreateThreadEx(&handle, MAXIMUM_ALLOWED, nullptr, NtCurrentProcess(), &printer, nullptr, THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE, 0, 0, 0, nullptr); NtSuspendProcess(NtCurrentProcess());
多挂点我
继续保持行为不检点的趋势NtSuspendProcess
,我们将再次滥用它如何检测我们的流程是否被暂停。
诀窍在于,暂停计数是一个带符号的8位值。就像上一个一样,这里有一些代码可以让您了解内部工作原理:
ULONG KeSuspendThread(_ETHREAD *Thread) { auto irql = KeRaiseIrql(DISPATCH_LEVEL); KiAcquireKobjectLockSafe(&Thread->Tcb.SuspendEvent); auto oldSuspendCount = Thread->Tcb.SuspendCount; if ( oldSuspendCount == MAXIMUM_SUSPEND_COUNT ) // 127 { _InterlockedAnd(&Thread->Tcb.SuspendEvent.Header.Lock, 0xFFFFFF7F); KeLowerIrql(irql); ExRaiseStatus(STATUS_SUSPEND_COUNT_EXCEEDED); } auto prcb = KeGetCurrentPrcb(); if ( KiSuspendThread(Thread, prcb) ) ++Thread->Tcb.SuspendCount; _InterlockedAnd(&Thread->Tcb.SuspendEvent.Header.Lock, 0xFFFFFF7F); KiExitDispatcher(prcb, 0, 1, 0, irql); return oldSuspendCount; }
如果您看一下第一个代码示例,PsSuspendProcess
它没有错误检查,也不会在乎是否不再挂起线程。那么打电话时会发生什么NtResumeProcess
呢?它减少了挂起计数!我们需要做的就是最大限度地利用它,当有人决定暂停并恢复我们时,他们实际上会将计数保持在以前未处于的状态。
例子
下面的简单代码相当有效:
- Visual Studio-防止它在附加后暂停进程。
- WinDbg-在连接时被检测到。
- x64dbg-暂停按钮变得粗略,并带有诸如“程序未运行”之类的错误消息,直到您手动切换到主线程为止。
- ScyllaHide-使用较旧的版本
NtSuspendProcess
并导致将其检测到,但是在我报告后已修复。
for(size_t i = 0; i < 128; ++i) NtSuspendThread(thread, nullptr); while(true) { if(NtSuspendThread(thread, nullptr) != STATUS_SUSPEND_COUNT_EXCEEDED) std::puts("I was suspended\n"); Sleep(1000); }
结论
如果有的话,我希望这表明最好不要依赖于NtSuspendProcess
您期望的工具来处理潜在的恶意代码或受保护的代码。希望您喜欢这篇文章,并希望在接下来的几周内能有更多的内容。