Admin
Администратор
Новые методы антидебага
Обход заморозки процесса
Это милый небольшой флаг создания потока, который Microsoft добавила в 19H1. Вы когда-нибудь задумывались, почему есть дыра во флагах создания потоков? Что ж, дыра заполнена флагом, который я назову THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE (я понятия не имею, как он на самом деле называется), значение которого, естественно, равно 0x40.
Дабы продемонстрировать что оно делает,Я покажу как работает PsSuspendProcess.
C:
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, и процесс продолжает работать, как будто ничего не произошло.
Пример:
Например, вот несколько угарный код, который будет продолжать печатать, что я запускаю. Я уверен, что, увидев это во время движения задним ходом, вы бы сильно запутались в том, какого черта можно приостановить свой собственный процесс.
C:
]#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-битное значение со знаком. Как и в предыдущем случае, вот код, который поможет вам понять внутреннюю работу:
C:
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 и вызывали его обнаружение, но это было исправлено, как только я сообщил об этом.
C:
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 в работе так хорошо, как вы ожидаете от инструментов, работающих с потенциально вредоносным или защищенным кодом. Надеюсь, вам понравился этот пост, и ожидаем, что в ближайшие недели появится больше контента.