With 2020 over, I’ll be releasing a bunch of new anti-debug methods that you most likely have never seen. To start off, we’ll take a look at two new methods, both relating to thread suspension. They aren’t the most revolutionary or useful, but I’m keeping the best for last.
Bypassing process freeze
This one is a cute little thread creation flag that Microsoft added into 19H1. Ever wondered why there is a hole in thread creation flags? Well, the hole has been filled with a flag that I’ll call THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE
(I have no idea what it’s actually called) whose value is, naturally, 0x40
.
To demonstrate what it does, I’ll show how PsSuspendProcess works:
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; }
So as you can see, NtSuspendProcess
that calls PsSuspendProcess
will simply ignore the thread with this flag. Another bonus is that the thread also doesn’t get suspended by NtDebugActiveProcess
! As far as I know, there is no way to query or disable the flag once a thread has been created with it, so you can’t do much against it.
As far as its usefulness goes, I’d say this is just a nice little extra against dumping and causes confusion when you click suspend in Processhacker, and the process continues to chug on as if nothing happened.
Example
For example, here is a somewhat funny code that will keep printing I am running
. I am sure that seeing this while reversing would cause a lot of confusion about why the hell one would suspend his own process.
#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());
Suspend me more
Continuing the trend of NtSuspendProcess
being badly behaved, we’ll again abuse how it works to detect whether our process was suspended.
The trick lies in the fact that suspend count is a signed 8-bit value. Just like for the previous one, here’s some code to give you an understanding of the inner workings:
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; }
If you take a look at the first code sample with PsSuspendProcess
it has no error checking and doesn’t care if you can’t suspend a thread anymore. So what happens when you call NtResumeProcess
? It decrements the suspend count! All we need to do is max it out, and when someone decides to suspend and resume us, they’ll actually leave the count in a state it wasn’t previously in.
Example
The simple code below is rather effective:
- Visual Studio - prevents it from pausing the process once attached.
- WinDbg - gets detected on attach.
- x64dbg - pause button becomes sketchy with error messages like “Program is not running” until you manually switch to the main thread.
- ScyllaHide - older versions used
NtSuspendProcess
and caused it to be detected, but it was fixed once I reported it.
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); }
Conclusion
If anything, I hope that this demonstrated that it’s best not to rely on NtSuspendProcess
to work as well as you’d expect for tools dealing with potentially malicious or protected code. Hope you liked this post and expect more content to come out in the upcoming weeks.